🤖 feat: stream agent_report previews and delay task cleanup #1306
+1,530
−319
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
agent_reporttool args fromtool-call-deltaso sub-agent workspaces show a live, in-flight report preview.agent_reporttool renderer with a compaction-like fade preview while executing.Testing
make static-checkbun test src/node/services/taskService.test.tsbun test src/common/utils/tools/agentReportArgsText.test.ts📋 Implementation Plan
Live-stream sub-agent
agent_reportoutput in UIProblem
agent_reporttool, the UI buffers the entire tool call.Goal
agent_reporttool call is in-flight.Non-goals
agent_report(it is still one logical tool call that completes once).Recommended approach: stream
agent_reportfromtool-call-delta(frontend-first)Why this works: the backend already emits
tool-call-deltaevents containing the model’s incrementalargsTextDelta, but the renderer currently ignores them for display. We can reconstruct a “live” preview from those deltas and render it with the same fade/preview affordances as compaction.1) Add a tiny incremental extractor for
reportMarkdownargsText(string){ reportMarkdown: string | null, title: string | null }where values are best-effort and may be incomplete."reportMarkdown", optionally"title") and decode a partial JSON string (handles escapes like\\n,\\", and tolerates truncated escape sequences).Net new LoC (product): ~80–140
2) Teach
StreamingMessageAggregatorto materialize/patch an in-flight tool partFiles/symbols:
src/browser/utils/messages/StreamingMessageAggregator.tshandleToolCallDelta()(currently token-tracking only)handleToolCallStart()(currently skips duplicates)Changes:
pendingToolArgsTextByToolCallId: Map<string, string>toolCallIdsWithDeltas: Set<string>(to avoid double-counting tokens at start)handleToolCallDelta():data.toolName !== "agent_report", keep current behavior (no UI changes).data.delta→ string; if empty, bail.tool-call-start:dynamic-toolpart yet, create one with:state: "input-available"timestamp: data.timestamp(so it appears in-order)input: { reportMarkdown: "" }(placeholder)pendingToolStarts.set(toolCallId, timestamp)) so the tool duration includes “report writing”.pendingToolArgsTextByToolCallId.inputto{ reportMarkdown, title? }(best-effort).invalidateCache()so the tool UI updates live.handleToolCallStart():inputwithdata.argsinstead of skipping.toolCallIdsWithDeltascontains this toolCallId, skiptrackDelta(..., "tool-args")for the start event.Net new LoC (product): ~60–120
3) Render a dedicated
AgentReportToolCallwith “compaction-like” previewFiles:
src/browser/components/tools/AgentReportToolCall.tsxsrc/browser/components/Messages/ToolMessage.tsxto routetoolName === "agent_report"to the new component.UI behavior:
status === "executing".args.reportMarkdown.CompactingMessageContent(max-height + fade) to mimic compaction.MarkdownCorein streaming mode if we want full fidelity.titleif present (fallback: first# Headingin markdown).Net new LoC (product): ~120–220
4) Tests + validation
*.test.ts).\\n,\\uXXXX, and\\"nullStreamingMessageAggregator:stream-start→tool-call-delta(agent_report) events and assertgetDisplayedMessages()contains a tool message whose args update.Manual QA:
Required UX fix: delay auto-delete of reported task workspaces (5s)
Background:
TaskService.cleanupReportedLeafTask()currently auto-deletes leaf task workspaces immediately once they reachtaskStatus: "reported". That makes the workspace appear “stuck” and then suddenly vanish.5) Backend: schedule deletion without blocking report propagation
Files/symbols:
src/node/services/taskService.tsfinalizeAgentTaskReport()(must still deliver report immediately)cleanupReportedLeafTask()(currently deletes immediately)Plan:
deliverReportToParent()+resolveWaiters()happening before any deletion work.scheduledTaskWorkspaceDeletes: Map<string, NodeJS.Timeout>.cleanupReportedLeafTask(workspaceId), if the workspace is eligible for deletion:setTimeout(() => void this.cleanupReportedLeafTask(workspaceId, { skipDelay: true }), 5000)skipDelay: true), run the existing leaf-first deletion loop:Defensive notes:
reported, no children).Net new LoC (product): ~40–80
6) Frontend: disable input + show “will delete in 5 seconds” message
Files:
src/browser/components/AIView.tsxsrc/browser/components/ChatInput/index.tsx(likely no changes; reusedisabledReason)Plan:
isReportedAgentTask = Boolean(meta?.parentWorkspaceId) && meta?.taskStatus === "reported"isReportedAgentTask:disabled={... || isReportedAgentTask})disabledReason="Completed — this agent task workspace will be deleted in 5 seconds."agent_reportresult immediately:workspaceService.remove(...); it must not block the tool call’s resolution.Net new LoC (product): ~20–60
Optional follow-ups
A) Mirror the streaming preview into the parent workspace
agent_reportdeltas from child → parent (new event plumbing).bash-output) rather than overloadingtool-call-delta.Alternatives considered
agent-report-outputeventagent_reportGenerated with
mux• Model: openai:gpt-5.2 • Thinking: xhigh