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:
@@ -688,6 +688,19 @@ export class SessionManager {
|
|||||||
this.pendingBuffers.delete(frame.sessionId) // a removed session's buffered frames must not replay on a future instantiation
|
this.pendingBuffers.delete(frame.sessionId) // a removed session's buffered frames must not replay on a future instantiation
|
||||||
this.waitingApprovals.delete(frame.sessionId) // a removed session cannot wait on anyone
|
this.waitingApprovals.delete(frame.sessionId) // a removed session cannot wait on anyone
|
||||||
if (!durableSubagent) this.projectionStores.delete(frame.sessionId)
|
if (!durableSubagent) this.projectionStores.delete(frame.sessionId)
|
||||||
|
// The removed session can no longer be the delivery owner of its
|
||||||
|
// catalog: invalidate availability immediately. Removal schedules no
|
||||||
|
// catalog refresh, and without this an addressed child keeps a
|
||||||
|
// writable editor against a dead continuation owner until an
|
||||||
|
// unrelated refresh (or forever, for a closed menu).
|
||||||
|
const ownedCatalog = this.catalogs.get(frame.sessionId)
|
||||||
|
if (ownedCatalog !== undefined && ownedCatalog.parentAvailable) {
|
||||||
|
this.catalogs.set(frame.sessionId, { ...ownedCatalog, parentAvailable: false })
|
||||||
|
}
|
||||||
|
for (const [childId, address] of this.addresses) {
|
||||||
|
if (address.parentSessionId !== frame.sessionId) continue
|
||||||
|
this.sessions.get(childId)?.handleSubagentParentAvailable(false)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case 'host/session-status': {
|
case 'host/session-status': {
|
||||||
|
|||||||
@@ -587,6 +587,30 @@ describe('subagent catalogs', () => {
|
|||||||
vi.useRealTimers()
|
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', () => {
|
describe('remaining branches', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user