Stream attached background shell output in Agent Host chats - #339220
Conversation
Keep an attached shell's tool call terminal live after an async call returns and keep appending its partial output. Point the shell's background work entry at that terminal, settle it on shell_completed, and show the command and live output in the Background Shells details.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Superseded task snapshots can leave terminals running, rewritten output can race xterm parsing, and streamed output lacks accessible-view support.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Streams attached background-shell output through Agent Host terminal channels into editor and Agents window shell details.
Changes:
- Keeps non-PTY terminals live after backgrounding and records shell exit state.
- Projects advertised terminal output into background-shell pills.
- Adds a live read-only terminal UI and tests.
| File | Description |
|---|---|
sessionBackgroundShellsControl.test.ts |
Tests live shell details. |
agentHostBackgroundShells.test.ts |
Tests opaque terminal subscriptions. |
sessionChatPills.ts |
Defines shell output states. |
sessionBackgroundShellsControl.ts |
Manages output-view lifecycle. |
sessionBackgroundShellOutputView.ts |
Renders streamed terminal output. |
sessionBackgroundShellOutput.css |
Styles shell output. |
agentHostSessionInputPills.ts |
Connects editor pills to output. |
agentHostBackgroundShells.ts |
Observes advertised terminals. |
baseAgentHostSessionsProvider.ts |
Exposes output in Agents window. |
copilotNonPtyShellTerminals.test.ts |
Tests background stream lifecycle. |
copilotAgentSession.test.ts |
Tests session-level streaming. |
copilotNonPtyShellTerminals.ts |
Retains and finalizes terminal streams. |
copilotAgentSession.ts |
Publishes terminals and routes updates. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The async shell call now completes with a terminal block, because its output keeps streaming after the call returns.
When the agent polls a background shell with read_bash, the runtime puts the exit code in the read result and sends no shell_completed notification, so settle the shell from read, write, and stop results. The task-list fallback now counts any read that started after the call went to the background, including reads that a newer read superseded, so a shell that exits before it is ever listed still settles. Clear the background shell output view through the xterm write queue instead of reset(), so output that xterm is still parsing can't land after the clear.
Addresses review comment 4162451160. The Background Shells details drew the output only in a detached xterm, inside a div that had a name but no role, so screen readers couldn't review the streamed lines. The output is now a focusable region named for its command, with the Terminal Chat Output hint. Open Accessible View shows the command, its status, and the output without ANSI escapes, and closing it returns focus to the output, or to the chat input once the picker has closed.
While a background shell's output is shown, its details now hold just the command, its status, and the terminal. The badge, shell ID, and start time stay in the row's description. The terminal also keeps a fixed height of 10 rows instead of growing with the output. The details are pinned above the chat input, so each new row pushed the command header up. Lines now fill the terminal from the top, then scroll.
|
The Copilot review overview also flagged that rewritten output can race xterm parsing. It has no inline thread, so I'm answering here. Valid, and fixed in e3dfcd3. At ae49758, a rewrite called The rewrite path now clears through the same write queue with Verified: The review's two inline findings are answered and resolved in their threads. The overview's "Open (2)" list is a snapshot from the first review and doesn't update when threads are resolved. |

Follows: #338723
Streams attached background shell output using only what
@github/copilot-sdk1.0.16 already ships. There are no SDK, runtime, or AHP protocol changes.SDK APIs this relies on, all received through
CopilotSession.on()except the RPC:tool.execution_partial_result(ToolExecutionPartialData.partialOutput) is the live output. The runtime keeps sending it for a shell tool call after the call returns with<command started in background with shellId: …>, or another attached "still running" result.system.notificationwithkind.type: "shell_completed"(shellId,exitCode) reports the exit.tool.execution_completetext fromread_bash/write_bash(<shellId: … completed with exit code N>) or a stop also reports the exit. When a read sees the exit, the runtime sends noshell_completed.session.rpc.tasks.list()is the fallback: a read that starts after the call went to the background and no longer lists the shell as running or idle means it exited.Changes:
tool.execution_partial_resultafter the call returns in the background. The Agent Host used to drop partial results once a call completed.terminal/data), creating it if nothing was printed yet. Point both the tool result's terminal block andBackgroundShellWork.terminalat it.terminal/exited, with the exit code when known) as soon as one of the signals above reports it. It stays subscribable until the session is disposed.BackgroundShellWork.terminalwhile the details are open. Once there's output, the terminal keeps a fixed 10 rows, so the details don't move as lines arrive.Limits: past about 10 KiB, each partial result carries only the tail of the output, so very fast output can skip lines. The tool call's status icon stays neutral after a background exit.
How to test
Use your shell tool in async mode (do not detach) to run exactly: for i in $(seq 1 60); do echo "stream-step:$i"; sleep 1; doneand ask it to reply "started" without waiting.stream-steplines, and so does the tool call's terminal.sleep 3; echo donethe same way, thenread_bashthat shell until it completes. The AHP log showsterminal/exitedwithexitCode: 0.Validation
npm run typecheck-client: passed.npx eslinton the 18 changed TypeScript files: passed.background shell does not create an output-only terminalis replaced bybackground shell without early output gets an output-only terminal that streams until shell_completed../scripts/test-integration.sh --run src/vs/platform/agentHost/test/node/e2e/providers/copilotAgentHostE2E.integrationTest.tsat e3dfcd3: 233 passing, 26 pending. Later commits only change workbench UI. Theshell read helper remains a non-terminal toolAHP snapshot gains the async call'sterminalblock.stream-step:50tostream-step:57in about five seconds), and the tool call's terminal received all 60 lines. The accessible view hasn't been checked with a screen reader.Unit test command:
Inspirations from
BackgroundShellWork.terminaland its SHOULD come fromBackgroundShellWork.ToolResultTerminalContentandChatToolCallContentChangedAction, which only changes a running tool call.parseCompletedShell.NonPtyShellTerminalStreams.completeToolCall, which already kept it unretired, and changesonToolPartialResult, which dropped partial results for completed calls.AgentHostSessionHandler.AgentHostOutputChannel.ChatTerminalToolProgressPart's title and output markup, 10-row limit, status decorations, and named output region.DetachedTerminalCommandMirrorandcomputeChatTerminalMirrorCols.ChatBackgroundShellOutputAccessibleViewfollowsChatTerminalOutputAccessibleView.