diff --git a/packages/ui/desktop/src/app.ts b/packages/ui/desktop/src/app.ts index 4b05e2c994..c74acd94c3 100644 --- a/packages/ui/desktop/src/app.ts +++ b/packages/ui/desktop/src/app.ts @@ -81,6 +81,22 @@ interface LiveTurn { errorText?: string } +interface InteractionOption { + readonly optionId: string + readonly name: string + readonly kind: string +} + +/** A permission or elicitation request forwarded from the ACP client in main. */ +interface InteractionRequest { + readonly id: string + readonly kind: 'permission' | 'elicitation' + readonly sessionId?: string + readonly title: string + readonly detail: unknown + readonly options: readonly InteractionOption[] +} + interface PlanItem { readonly content: string readonly status: string @@ -170,6 +186,9 @@ const state = { traceLoadRevision: 0, liveTurnCounter: 0, liveToolMeta: new Map(), + interactions: [] as InteractionRequest[], + queues: new Map(), + pausedQueues: new Set(), traceCatchupTimer: undefined as number | undefined, traceCatchupAttempts: 0, } @@ -201,6 +220,9 @@ interface ShellRefs { waterfall: HTMLElement devCanvas: HTMLElement sessionCanvas: HTMLElement + interactionDock: HTMLElement + queueDock: HTMLElement + planDock: HTMLElement composerForm: HTMLFormElement composerInput: HTMLTextAreaElement composerHint: HTMLElement @@ -287,6 +309,9 @@ function buildShell(): void { + + +
@@ -337,6 +362,9 @@ function buildShell(): void { waterfall: pick('waterfall'), devCanvas: pick('devCanvas'), sessionCanvas: pick('sessionCanvas'), + interactionDock: pick('interactionDock'), + queueDock: pick('queueDock'), + planDock: pick('planDock'), composerForm: pick('composerForm') as HTMLFormElement, composerInput: pick('composerInput') as HTMLTextAreaElement, composerHint: pick('composerHint'), @@ -415,6 +443,11 @@ async function boot(): Promise { window.dshDesktop.sessions.onUpdate((payload) => { handleSessionUpdate(asSessionUpdate(payload)) }) + if (typeof window.dshDesktop.interaction?.onRequest === 'function') { + window.dshDesktop.interaction.onRequest((payload) => { + handleInteractionRequest(payload) + }) + } await Promise.all([refreshRuntime(), refreshDevStatus()]) await refreshSessions() } @@ -501,6 +534,8 @@ function applyTrace(trace: TracePayload, resetExpansion: boolean): void { renderTrajTree() renderWaterfall() renderInspector() + renderPlanDock() + renderQueueDock() scrollChatToBottom(resetExpansion) scheduleTraceCatchup(sessionId) } @@ -636,11 +671,7 @@ 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) - } + renderPlanDock() const answer = el.liveTurn.querySelector('[data-live="answer"]') if (answer !== null) { answer.hidden = live.answer.length === 0 @@ -670,13 +701,12 @@ function ensureLiveSkeleton(live: LiveTurn): void { el.liveTurn.innerHTML = ` ${live.userText === undefined ? '' : `
-
${escapeHtml(live.userText)}
+
${escapeHtml(live.userText.trim())}
`}
A
-