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
+3
View File
@@ -283,6 +283,7 @@ function summarize(session: Session, running: boolean): SessionSummary {
running,
blank: sessionBlank(session),
...session.header.parentSession === undefined ? {} : { parentSessionId: session.header.parentSession },
...session.header.origin === undefined ? {} : { origin: session.header.origin },
...session.header.cwd === undefined ? {} : { cwd: session.header.cwd },
}
}
@@ -318,6 +319,7 @@ async function summarizeCold(
// cold session is served as not-blank (its log holds its conversation).
blank: false,
...meta.parentSession === undefined ? {} : { parentSessionId: meta.parentSession },
...meta.origin === undefined ? {} : { origin: meta.origin },
/* v8 ignore next -- the empty arm needs a cwd-less meta, but list()
filters those out (legacy logs are not served); the conditional mirrors
summarize() shape. */
@@ -2340,6 +2342,7 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
// has run no turn yet, so this is constantly true in practice.
blank: sessionBlank(session),
...session.header.parentSession === undefined ? {} : { parentSessionId: session.header.parentSession },
...session.header.origin === undefined ? {} : { origin: session.header.origin },
// cwd rides the frame so the client list needs no refresh to group the new session.
...session.header.cwd === undefined ? {} : { cwd: session.header.cwd },
}))
@@ -65,7 +65,14 @@ export const muxFrameSchema = z.discriminatedUnion('type', [
/** HostFrame union (payload slot of a host-stream ServerRequest). */
export const hostFrameSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('host/session-added'), sessionId: sessionIdSchema, blank: z.boolean(), parentSessionId: sessionIdSchema.optional(), cwd: z.string().optional() }),
z.object({
type: z.literal('host/session-added'),
sessionId: sessionIdSchema,
blank: z.boolean(),
parentSessionId: sessionIdSchema.optional(),
origin: z.literal('subagent').optional(),
cwd: z.string().optional(),
}),
z.object({ type: z.literal('host/session-removed'), sessionId: sessionIdSchema }),
z.object({ type: z.literal('host/session-status'), sessionId: sessionIdSchema, running: z.boolean() }),
z.object({ type: z.literal('host/agent-error'), sessionId: sessionIdSchema, message: z.string() }),
+11 -4
View File
@@ -90,9 +90,9 @@ export type MuxFrame =
| { type: 'stream/error'; error: RpcError }
/**
* Host stream frames. session-added carries the lineage anchor, the project
* cwd, and the blank bit (the list-summary fields a client cannot wait for a
* refresh to learn); the frame fires at session/created, so blank is
* Host stream frames. session-added carries the lineage anchor, product
* origin, project cwd, and blank bit (the list-summary fields a client cannot
* wait for a refresh to learn); the frame fires at session/created, so blank is
* constantly true — clients flip it on the session's first
* `host/session-status(running:true)` (a blank session never runs), and a
* reconnecting client takes `session.list`'s summary.blank as authoritative.
@@ -106,7 +106,14 @@ export type MuxFrame =
* workspace-changed — `workspace.list` re-baselines it on reconnect).
*/
export type HostFrame =
| { type: 'host/session-added'; sessionId: SessionId; blank: boolean; parentSessionId?: SessionId; cwd?: string }
| {
type: 'host/session-added'
sessionId: SessionId
blank: boolean
parentSessionId?: SessionId
origin?: 'subagent'
cwd?: string
}
| { type: 'host/session-removed'; sessionId: SessionId }
| { type: 'host/session-status'; sessionId: SessionId; running: boolean }
| { type: 'host/agent-error'; sessionId: SessionId; message: string }
@@ -53,6 +53,7 @@ export const sessionSummarySchema = z.object({
running: z.boolean(),
blank: z.boolean(),
parentSessionId: sessionIdSchema.optional(),
origin: z.literal('subagent').optional(),
cwd: z.string().optional(),
projections: z.lazy(() => sessionProjectionsBlockSchema).optional(),
}) as unknown as z.ZodType<Wire<SessionSummary>>
@@ -153,6 +153,8 @@ export interface SessionSummary {
blank: boolean
/** fork/spawn lineage (session.header.parentSession passthrough); absent for root sessions. */
parentSessionId?: SessionId
/** Coarse durable origin used by navigation surfaces; never proves resumability. */
origin?: 'subagent'
/** Session working directory (header.cwd passthrough); absent when unrecorded. */
cwd?: string
/**