fix(web): deduplicate subagent navigation

This commit is contained in:
Dudu-0223
2026-07-30 11:34:57 +08:00
committed by Tianyi Cui
parent 16ffd63115
commit f0ab04273d
53 changed files with 294 additions and 95 deletions

View File

@@ -42,7 +42,7 @@ describe('sessions.list cold merge', () => {
utimesSync(logPath, 5000, 5000) // mtime 5_000_000 ms — newer than every createdAt below
const metas = [
header('session-a', 1000),
header('session-b', 2000, { parentSession: sid('session-parent') }),
header('session-b', 2000, { parentSession: sid('session-parent'), origin: 'subagent' }),
header('session-c', 1500),
]
// Structural fake of the persistence face list() consumes: list + locate.
@@ -74,6 +74,7 @@ describe('sessions.list cold merge', () => {
expect(a?.parentSessionId).toBeUndefined()
expect(b?.updatedAt).toBe(2000)
expect(b?.parentSessionId).toBe('session-parent')
expect(b?.origin).toBe('subagent')
expect(c?.updatedAt).toBe(1500)
})
})

View File

@@ -364,6 +364,36 @@ describe('session creation and Workspace membership', () => {
})
describe('Host Workspace increments', () => {
it('projects subagent origin in attached summaries and creation increments', async () => {
const { api, ctx } = await harness()
const abort = new AbortController()
const stream: AsyncIterator<RpcRequest<HostFrame>> =
api.events.host(request({}), abort.signal)[Symbol.asyncIterator]()
const pending = nextHostFrame(stream)
const childId = SessionId('session-subagent-child')
ctx.sessions.create(childId, {
meta: {
cwd: '/tmp',
parentSession: SessionId('session-parent'),
origin: 'subagent',
},
})
expect(await pending).toMatchObject({
payload: {
type: 'host/session-added',
sessionId: childId,
parentSessionId: 'session-parent',
origin: 'subagent',
},
})
expect(expectOk(await api.sessions.list(request({}))).items).toContainEqual(
expect.objectContaining({ sessionId: childId, origin: 'subagent' }),
)
abort.abort()
})
it('streams committed Workspace and Session increments after empty baselines', async () => {
const { api } = await harness()
expect(expectOk(await api.workspace.list(request({}))).items).toEqual([])

View File

@@ -141,7 +141,13 @@ describe('sessions domain schemas', () => {
expect(sessionIdSchema.parse('s1')).toBe('s1')
expect(() => sessionIdSchema.parse('')).toThrow()
expect(sessionSummarySchema.parse({ sessionId: 's1', updatedAt: 1, running: false, blank: true })).toMatchObject({ sessionId: 's1', blank: true })
expect(sessionSummarySchema.parse({ sessionId: 's1', updatedAt: 1, running: true, blank: false, parentSessionId: 'p', cwd: '/x' }).cwd).toBe('/x')
expect(sessionSummarySchema.parse({
sessionId: 's1', updatedAt: 1, running: true, blank: false,
parentSessionId: 'p', origin: 'subagent', cwd: '/x',
})).toMatchObject({ origin: 'subagent', cwd: '/x' })
expect(() => sessionSummarySchema.parse({
sessionId: 's1', updatedAt: 1, running: false, blank: false, origin: 'fork',
})).toThrow()
// blank is mandatory: a summary without it fails the parse.
expect(() => sessionSummarySchema.parse({ sessionId: 's1', updatedAt: 1, running: false })).toThrow()
const event = sessionEventSchema.parse({
@@ -498,7 +504,7 @@ describe('events frame schemas', () => {
it('accepts every host frame branch', () => {
const frames = [
{ type: 'host/session-added', sessionId: 's', blank: true, parentSessionId: 'p' },
{ type: 'host/session-added', sessionId: 's', blank: true, parentSessionId: 'p', origin: 'subagent' },
{ type: 'host/session-added', sessionId: 's', blank: true },
{ type: 'host/session-removed', sessionId: 's' },
{ type: 'host/session-status', sessionId: 's', running: true },
@@ -512,6 +518,9 @@ describe('events frame schemas', () => {
{ type: 'stream/error', error: { code: 'internal', message: 'm', details: {} } },
]
for (const frame of frames) expect(hostFrameSchema.parse(frame)).toMatchObject({ type: frame.type })
expect(() => hostFrameSchema.parse({
type: 'host/session-added', sessionId: 's', blank: true, origin: 'fork',
})).toThrow()
})
})