fix(subagent): stop 401-ing the aside-open with a guessed child mode
CI / ci (pull_request) Canceled after 0s
CI / ci-windows (pull_request) Canceled after 0s
CI / plugin-mount (pull_request) Canceled after 0s

The aside button could hit `subagent/unauthorized: subagent mode does not
match the supplied address`. The host's session-controller (dsh-api-session-
controller validateAddress) compares the `mode` in the resource address
against the child's durable descriptor mode and rejects any mismatch. The
address `mode` came from the row, and on the degraded path the row's mode is
a best-effort guess from the summary `running` flag (running => continuable)
— so a child that is genuinely one-shot but still running was guessed as
continuable and the very open it was meant to trigger 401'd.

Two layers, so it holds on both the real-catalog and the degraded path:

- subagentChatAddress: only pass a mode through when it is one of the two
  durable values (`one-shot` / `continuable`). Anything else is omitted,
  which the controller reads as `mode: "unknown"` and therefore skips the
  comparison — the safest form when the caller does not actually know.
- openChildAside: prefer the host's real descriptor mode for the child
  (the `subagentsByParent` entry carries the durable mode) over the address
  mode, falling back to the address only when the real catalog never arrived.

Gates: typecheck / lint / build / test 132 files 1392 passed.
This commit is contained in:
figmar
2026-10-14 10:18:58 +08:00
parent 6b4a29ba24
commit 061964e3e8
3 changed files with 46 additions and 6 deletions
+21
View File
@@ -49,4 +49,25 @@ describe('subagentChatAddress', () => {
it('names the page kind the native Sidebar roots these pages under', () => {
expect(SUBAGENT_CHAT_KIND).toBe('subagentchat')
})
it('omits an unknown mode so the host treats it as "unknown" and skips the check', () => {
// The degraded synthesized catalog guesses mode from `running`; a wrong
// guess used to 401 the open (the controller compares address.mode against
// the child's durable descriptor). An unrecognized mode must be dropped,
// which the controller reads as `mode: "unknown"` and does not compare.
const address = subagentChatAddress({
parentSessionId: 'p',
childSessionId: 'c',
mode: 'unknown' as never,
})
expect(address).toBe('dsh-resource://subagentchat/session/c?parent=p')
expect(address).not.toContain('mode=')
})
it('still emits the mode when it is one of the two durable values', () => {
expect(subagentChatAddress({ parentSessionId: 'p', childSessionId: 'c', mode: 'one-shot' }))
.toContain('mode=one-shot')
expect(subagentChatAddress({ parentSessionId: 'p', childSessionId: 'c', mode: 'continuable' }))
.toContain('mode=continuable')
})
})