fix(web): invalidate catalog availability when the owning parent is removed

A removed session can no longer be the delivery owner of its continuable
children, but the `host/session-removed` handler only reconciled the
removed row's own activity. `parentAvailable` was updated exclusively from
`refreshSubagents` success, and removal schedules no catalog refresh — so
after the parent's Activation detaches, an addressed child kept a writable
editor against a dead continuation owner until an unrelated refresh (or
forever, for a closed menu).

Flip `parentAvailable` to false on the owned catalog and push
`handleSubagentParentAvailable(false)` to every addressed child Session at
removal time, matching the refresh path's notification. New Session
instances already read `parentAvailable` from the catalog, so they inherit
the invalidated state.

Adds a regression test: removing the catalog's owning parent flips the
snapshot's `parentAvailable` and notifies the addressed child instance.
This commit is contained in:
Tianyi Cui
2026-08-02 12:19:08 +08:00
parent b270b3ef9f
commit 8431dbead3
2 changed files with 37 additions and 0 deletions

View File

@@ -587,6 +587,30 @@ describe('subagent catalogs', () => {
vi.useRealTimers()
}
})
it('invalidates catalog availability when the owning parent is removed', async () => {
const api = new FakeApiClient()
const root = 'fk-root' as SessionId
api.onSubagentList = () => Promise.resolve(ok({
entries: [{
kind: 'child', id: S2, mode: 'continuable', label: 'worker',
activity: 'inactive', hasChildren: false,
}] as never[],
parentAvailable: true,
}))
const manager = new SessionManager(api)
await manager.refreshSubagents(root)
manager.selectSubagent({ parentSessionId: root, childSessionId: S2, mode: 'continuable' })
expect(manager.get(S2).getSnapshot().subagent).toMatchObject({ parentAvailable: true })
manager.handleHostEnvelope({
rpcId: 'parent-removed' as never,
payload: { type: 'host/session-removed', sessionId: root },
})
expect(manager.getListSnapshot().subagentsByParent[root]?.parentAvailable).toBe(false)
expect(manager.get(S2).getSnapshot().subagent).toMatchObject({ parentAvailable: false })
})
})
describe('remaining branches', () => {