diff --git a/packages/ui/desktop/src/app.ts b/packages/ui/desktop/src/app.ts index 7cc148e08c..4b05e2c994 100644 --- a/packages/ui/desktop/src/app.ts +++ b/packages/ui/desktop/src/app.ts @@ -74,12 +74,24 @@ interface LiveTurn { userText?: string thinking: string answer: string + plan?: readonly PlanItem[] tools: LiveTool[] expectedCompletedTurns: number status: 'sending' | 'streaming' | 'complete' | 'error' errorText?: string } +interface PlanItem { + readonly content: string + readonly status: string +} + +interface LiveToolMeta { + title?: string + kind?: string + locations?: readonly { path: string; line?: number }[] +} + interface LiveTool { readonly callId: string title: string @@ -157,7 +169,7 @@ const state = { expandedActivityIds: new Set(), traceLoadRevision: 0, liveTurnCounter: 0, - liveToolTitles: new Map(), + liveToolMeta: new Map(), traceCatchupTimer: undefined as number | undefined, traceCatchupAttempts: 0, } @@ -562,9 +574,17 @@ function handleSessionUpdate(payload: SessionUpdatePayload): void { } else if (kind === 'agent_thought_chunk') { live.thinking += contentText(update.content) live.status = 'streaming' + } else if (kind === 'plan') { + const entries = Array.isArray(update.entries) ? update.entries : [] + live.plan = entries.map((entry): PlanItem => ({ content: String(asRecord(entry).content ?? ''), status: String(asRecord(entry).status ?? 'pending') })) + live.status = 'streaming' } else if (kind === 'tool_call' || kind === 'tool_call_update') { const callId = String(update.toolCallId ?? `tool-${live.tools.length}`) - if (typeof update.title === 'string' && update.title.length > 0) state.liveToolTitles.set(callId, update.title) + const meta = state.liveToolMeta.get(callId) ?? {} + if (typeof update.title === 'string' && update.title.length > 0) meta.title = update.title + if (typeof update.kind === 'string' && update.kind.length > 0) meta.kind = update.kind + if (Array.isArray(update.locations)) meta.locations = update.locations.map(location => ({ path: String(asRecord(location).path ?? ''), ...(asRecord(location).line === undefined ? {} : { line: Number(asRecord(location).line) }) })) + state.liveToolMeta.set(callId, meta) const existing = live.tools.find(tool => tool.callId === callId) const title = String(update.title ?? existing?.title ?? t('chat.toolUse')) const status = String(update.status ?? existing?.status ?? '') @@ -616,6 +636,11 @@ function renderLiveTurn(): void { if (body !== null) body.textContent = tool.detail row.classList.toggle('failed', tool.status === 'failed') } + const planHost = el.liveTurn.querySelector('[data-live="plan"]') + if (planHost !== null) { + planHost.hidden = live.plan === undefined || live.plan.length === 0 + planHost.innerHTML = live.plan === undefined ? '' : renderPlanList(live.plan) + } const answer = el.liveTurn.querySelector('[data-live="answer"]') if (answer !== null) { answer.hidden = live.answer.length === 0 @@ -651,6 +676,7 @@ function ensureLiveSkeleton(live: LiveTurn): void {
A
+