feat(web): rewrite subagent conversations for FIFO activation

This commit is contained in:
Dudu-0223
2026-07-30 23:33:07 +08:00
committed by Tianyi Cui
parent f0ab04273d
commit 8a518e353b
52 changed files with 829 additions and 420 deletions

View File

@@ -153,7 +153,7 @@ export class SessionManager {
selectSubagent(address: SubagentAddress): void {
const catalog = this.catalogs.get(address.parentSessionId)
const entry = catalog?.entries.find(candidate => candidate.id === address.childSessionId)
if (entry === undefined || entry.kind !== 'child') {
if (entry === undefined || entry.kind !== 'child' || entry.mode !== address.mode) {
throw new Error(`sessions.selectSubagent: ${address.childSessionId} is not a healthy catalog child`)
}
this.addresses.set(address.childSessionId, address)
@@ -625,7 +625,7 @@ export class SessionManager {
case 'host/session-removed': {
this.recordMutation({ kind: 'remove', sessionId: frame.sessionId })
if (this.addresses.has(frame.sessionId)) {
// A continuable activation detaching is not durable child deletion:
// An Activation detaching is not durable child deletion:
// keep the addressed conversation usable and return its catalog row
// to the inactive state.
this.sessions.get(frame.sessionId)?.handleRunning(false)

View File

@@ -642,7 +642,7 @@ export class SessionsService implements ISessions {
if (child?.kind === 'child') {
byId[current] = {
id: current,
displayTitle: child.label,
displayTitle: child.label ?? current,
parentId: currentAddress.parentSessionId,
origin: 'subagent',
running: child.activity === 'running',
@@ -660,7 +660,8 @@ export class SessionsService implements ISessions {
} else if (byId[current] !== undefined
&& (persisted !== current
|| this.selection.getSnapshot().subagentAddress?.childSessionId !== currentAddress?.childSessionId
|| this.selection.getSnapshot().subagentAddress?.parentSessionId !== currentAddress?.parentSessionId)) {
|| this.selection.getSnapshot().subagentAddress?.parentSessionId !== currentAddress?.parentSessionId
|| this.selection.getSnapshot().subagentAddress?.mode !== currentAddress?.mode)) {
this.selection.set({
sessionId: current,
...(currentAddress === undefined ? {} : { subagentAddress: currentAddress }),

View File

@@ -223,6 +223,15 @@ export class Session implements SessionFace {
try {
if (this.address === undefined) {
result = (await this.api.sessions.prompt({ sessionId: this.sessionId, mode, content })).result
} else if (this.address.mode === 'one-shot') {
result = {
ok: false,
error: {
code: 'subagent-not-resumable',
message: 'one-shot subagent conversations are read-only',
details: { childSessionId: this.address.childSessionId },
},
}
} else {
const routed = (await this.api.subagents.prompt({ ...this.address, content })).result
result = routed.ok ? { ok: true, value: { accepted: true } } : routed
@@ -270,7 +279,7 @@ export class Session implements SessionFace {
const result: RpcResult<{ accepted: true }> = {
ok: false,
error: {
code: 'subagent-not-delivered',
code: 'subagent-delivery-unavailable',
message: 'subagent activation cancellation is unavailable',
details: { childSessionId: this.address.childSessionId },
},
@@ -512,6 +521,7 @@ export class Session implements SessionFace {
configureSubagent(address: SubagentAddress | undefined, parentAvailable = false): void {
const same = this.address?.parentSessionId === address?.parentSessionId
&& this.address?.childSessionId === address?.childSessionId
&& this.address?.mode === address?.mode
this.address = address
this.parentAvailable = parentAvailable
if (!same && this.openState !== 'cold') void this.resync()