fix(api): resolve review findings on stream boundary, inject staleness, and ctx discipline

- api/gateway: mark terminal Remote-stream escapes (carrier retry
  exhaustion and pre-acceptance end classification) as
  RemoteError('gateway/internal') at the two escape points; marked
  failures pass through verbatim. The carrier class stays the
  retry-internal signal for carrierFailed and the ended(true) retry
  trigger. Regression coverage lands on the session and workspace
  stream consumers.
- client/ui-tool, client/ui-workspace: read $host.home through a hooks
  observable subscribed to connection/reset; the slot renderer memoizes
  inject results per entry, so the previous plain-value injection froze
  home at the first render.
- client/ui-settings-models: components no longer receive ctx; apply
  binds the credential and settings Remote operations into callbacks,
  and the settings/conflict code judgment stays in the apply world.
This commit is contained in:
imccyu
2026-08-28 22:37:36 +08:00
parent 5af9eec51c
commit 2f2e6d627b
27 changed files with 451 additions and 111 deletions
@@ -231,6 +231,27 @@ describe('Workspace Controller Client apply', () => {
expect(ctx.get('workspaces')).toBeUndefined()
})
it('publishes exhausted carrier retries as a gateway/internal error state', async () => {
const ctx = new Context()
// Neither generation reaches an accepted baseline, so the retry budget runs
// out and the escaping carrier failure crosses the stream boundary marked.
const remote = new ScriptedWorkspaceRemote([
{ frames: [], error: new RemoteStreamCarrierError('generation lost') },
{ frames: [], error: new RemoteStreamCarrierError('generation lost again') },
])
provideClientServices(ctx, remote)
const fiber = ctx.plugin(WorkspaceClientPlugin)
await fiber
await waitFor(() => {
expect(ctx.workspaces.list.getSnapshot()).toMatchObject({
state: 'error',
error: { code: 'gateway/internal', message: 'generation lost again' },
})
})
expect(remote.calls).toBe(2)
await fiber.dispose()
})
it('marks carrier loss while retrying and publishes a later protocol failure', async () => {
const ctx = new Context()
const remote = new ScriptedWorkspaceRemote([