Skip to content

Stream attached background shell output in Agent Host chats - #339220

Merged
Anthony Kim (anthonykim1) merged 5 commits into
mainfrom
anthonykim1/background-shell-output-stream
Oct 2, 2026
Merged

Anthony Kim (anthonykim1) merged 5 commits into
mainfrom
anthonykim1/background-shell-output-stream

Conversation

@anthonykim1

@anthonykim1 Anthony Kim (anthonykim1) commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Follows: #338723

Streams attached background shell output using only what @github/copilot-sdk 1.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.notification with kind.type: "shell_completed" (shellId, exitCode) reports the exit.
  • tool.execution_complete text from read_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 no shell_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:

  • Keep forwarding a shell call's tool.execution_partial_result after the call returns in the background. The Agent Host used to drop partial results once a call completed.
  • Append that output to the call's AHP output terminal (terminal/data), creating it if nothing was printed yet. Point both the tool result's terminal block and BackgroundShellWork.terminal at it.
  • Mark the terminal exited (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.
  • Skip detached shells, which get no partial output.
  • In a streaming shell's Background Shells details, show only the command, its status, and a read-only terminal that subscribes to BackgroundShellWork.terminal while the details are open. Once there's output, the terminal keeps a fixed 10 rows, so the details don't move as lines arrive.
  • Make that output a focusable region. Open Accessible View shows the command, status, and output without ANSI escapes.

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
  1. In a Copilot Agent Host chat, send: 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; done and ask it to reply "started" without waiting.
  2. Open 1 Background Shell and press Right Arrow on the shell. Its terminal keeps adding stream-step lines, and so does the tool call's terminal.
  3. Press Tab into the output, then Alt+F2 (Alt+Shift+F2 on Linux). The Accessible View shows it as text.
  4. In a new chat, ask it to run sleep 3; echo done the same way, then read_bash that shell until it completes. The AHP log shows terminal/exited with exitCode: 0.
Validation
  • npm run typecheck-client: passed.
  • npx eslint on the 18 changed TypeScript files: passed.
  • Unit tests on the 11 affected files: 1,338 passing, 11 pending. The test background shell does not create an output-only terminal is replaced by background 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.ts at e3dfcd3: 233 passing, 26 pending. Later commits only change workbench UI. The shell read helper remains a non-terminal tool AHP snapshot gains the async call's terminal block.
  • Manual, in Code OSS with the bundled 1.0.16 runtime at ae49758: the details streamed live (stream-step:50 to stream-step:57 in 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:

./scripts/test.sh \
  --run src/vs/platform/agentHost/test/node/copilotNonPtyShellTerminals.test.ts \
  --run src/vs/platform/agentHost/test/node/copilotAgentSession.test.ts \
  --run src/vs/platform/agentHost/test/node/agentHostStateManager.test.ts \
  --run src/vs/sessions/contrib/providers/agentHost/test/browser/localAgentHostSessionsProvider.test.ts \
  --run src/vs/workbench/contrib/chat/test/browser/agentHost/agentHostBackgroundShells.test.ts \
  --run src/vs/workbench/contrib/chat/test/browser/sessionBackgroundShellsControl.test.ts \
  --run src/vs/workbench/contrib/chat/test/browser/accessibility/chatBackgroundShellOutputAccessibleView.test.ts \
  --run src/vs/workbench/contrib/chat/test/browser/agentHost/agentHostSessionInputPills.test.ts \
  --run src/vs/workbench/contrib/chat/test/browser/chatInputPills.test.ts \
  --run src/vs/workbench/contrib/chat/test/browser/accessibility/chatAccessibilityHelp.test.ts \
  --run src/vs/workbench/contrib/chat/test/common/sessionChatPills.test.ts

Inspirations from

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.
Copilot AI balanced review requested due to automatic review settings October 2, 2026 02:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Medium severity

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.

Comment thread src/vs/platform/agentHost/node/copilot/copilotAgentSession.ts Outdated
Comment thread src/vs/workbench/contrib/chat/browser/sessionBackgroundShellOutputView.ts Outdated
The async shell call now completes with a terminal block, because its
output keeps streaming after the call returns.
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Screenshot Changes

Base: a60fe630 Current: abdd546f

Changed (3)

sessions/accountMenu/WeeklyAndFiveHourLimits/Light
Before After
before after
sessions/accountMenu/WeeklyLimitOnly/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/DiscoverPluginsLoadingMore/Light
Before After
before after

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.
@anthonykim1
Anthony Kim (anthonykim1) marked this pull request as ready for review October 2, 2026 16:01
@anthonykim1
Anthony Kim (anthonykim1) marked this pull request as draft October 2, 2026 16:01
@anthonykim1

Copy link
Copy Markdown
Contributor Author

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 terminal.xterm.reset() and then terminal.xterm.write(hideCursor + …) (sessionBackgroundShellOutputView.ts L149-L150). xterm processes write data asynchronously, but reset() takes effect immediately. Output from earlier writes that was still queued could therefore be parsed after the reset and land ahead of the replayed transcript.

The rewrite path now clears through the same write queue with \x1b[2J\x1b[3J\x1b[H, which erases the screen and scrollback and homes the cursor. That orders the clear after anything already queued (L203-L205). These sequences don't change terminal modes, so the hidden cursor no longer has to be sent again. shows only the command above a fixed-height terminal that streams its output while details are shown in sessionBackgroundShellsControl.test.ts covers it: the test expects the clear in the write stream instead of a reset() call.

Verified: ./scripts/test.sh --run src/vs/workbench/contrib/chat/test/browser/sessionBackgroundShellsControl.test.ts --run src/vs/workbench/contrib/chat/test/browser/accessibility/chatBackgroundShellOutputAccessibleView.test.ts --run src/vs/workbench/contrib/chat/test/browser/accessibility/chatAccessibilityHelp.test.ts: 38 passing.

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.

@anthonykim1
Anthony Kim (anthonykim1) marked this pull request as ready for review October 2, 2026 16:06
@anthonykim1 Anthony Kim (anthonykim1) added this to the 1.141.0 milestone Oct 2, 2026
@anthonykim1 Anthony Kim (anthonykim1) changed the title Stream background shell output in Agent Host chats Stream attached background shell output in Agent Host chats Oct 2, 2026
@anthonykim1
Anthony Kim (anthonykim1) enabled auto-merge (squash) October 2, 2026 16:54
@anthonykim1
Anthony Kim (anthonykim1) merged commit 57b4202 into main Oct 2, 2026
35 checks passed
@anthonykim1
Anthony Kim (anthonykim1) deleted the anthonykim1/background-shell-output-stream branch October 2, 2026 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants