Skip to content

terminal tools: detect prompts on the last output line#311765

Merged
meganrogge merged 4 commits into
microsoft:mainfrom
mossgowild:mossgowild/fix-output-monitor-last-line-detection
Apr 23, 2026
Merged

terminal tools: detect prompts on the last output line#311765
meganrogge merged 4 commits into
microsoft:mainfrom
mossgowild:mossgowild/fix-output-monitor-last-line-detection

Conversation

@mossgowild
Copy link
Copy Markdown
Contributor

Fixes #311757

OutputMonitor was running line-oriented prompt detection against the last 1000 characters of terminal output. When the tail contained multiple lines, stale lines in the scrollback could be treated as the current prompt and the monitor would repeatedly run prompt regexes over large multiline output.

This change scopes non-interactive help and input-required prompt detection to the last output line while keeping task-finish and generic "press any key" detection on the bounded tail.

It also adds a regression test that covers help text appearing on a non-final line.

Testing:

  • node --experimental-strip-types build/hygiene.ts src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts
  • npm run test-browser-no-install -- --run src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts --browser chromium

Copilot AI review requested due to automatic review settings April 21, 2026 19:24
@mossgowild mossgowild force-pushed the mossgowild/fix-output-monitor-last-line-detection branch from a91ef95 to 73c4a33 Compare April 21, 2026 19:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses terminal output monitoring stalls by narrowing line-oriented prompt/help detection to only the last output line, preventing repeated regex evaluation over large multi-line tails during idle polling.

Changes:

  • Scope detectsNonInteractiveHelpPattern and detectsInputRequiredPattern checks to the last output line (while keeping task-finish and generic “press any key” detection on the bounded tail).
  • Add a regression test ensuring non-interactive help text on a non-final line doesn’t stop custom polling.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts Introduces last-line extraction and applies it to line-oriented detectors in idle/idle-wait paths.
src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts Adds a regression test covering help text not on the final line.

Copy link
Copy Markdown
Collaborator

@meganrogge meganrogge left a comment

Choose a reason for hiding this comment

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

Thanks for the fix! The core approach is right, but I have a few concerns about _getLastLineForPatternDetection that are worth addressing before merge:

  1. Trailing newline produces an empty last line. Many CLIs flush prompts with a trailing \n (or the pty emits \r\n after a cursor update), e.g. Continue? [y/N]\n. With:

    const lastLineBreak = Math.max(output.lastIndexOf('\n'), output.lastIndexOf('\r'));
    return lastLineBreak === -1 ? output : output.slice(lastLineBreak + 1);

    slice(lastLineBreak + 1) returns "" and both detectsInputRequiredPattern and detectsNonInteractiveHelpPattern will miss it. Previously the $-anchored regexes would match the prompt against the tail. This looks like a regression — consider stripping trailing \r/\n first, or falling back to the previous non-empty line:

    const trimmed = output.replace(/[\r\n]+$/, '');
    const idx = Math.max(trimmed.lastIndexOf('\n'), trimmed.lastIndexOf('\r'));
    return idx === -1 ? trimmed : trimmed.slice(idx + 1);
  2. Bare \r progress updates truncate to a fragment. Tools like npm, pip, docker pull rewrite the same line with \r. lastIndexOf('\r') will then return a mid-line fragment rather than the real prompt line. (CRLF is fine because \n wins via Math.max, but bare \r redraws on Linux/macOS ttys will be truncated.)

  3. Prompts followed by explanatory text. Some tools write the prompt and then a hint line after it:

    Continue? [y/N]
    (press Ctrl-C to abort)
    

    The last line is now the hint, so the prompt no longer matches. Probably rare, but a behavioral change vs. the old tail-wide scan.

Could we add regression tests for (a) a prompt ending in \n and (b) a \r-based progress line, to cover these cases? The stall fix for #311757 is good — just want to make sure we don't silently break input-required detection for the common trailing-newline case.

@mossgowild
Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in 2c69c47.

Changes:

  • Updated last-line extraction to trim only trailing CR/LF before detection so newline-terminated prompts like Continue? [y/N]\n still match.
  • Treated bare \r within the final line as a redraw boundary so progress-style output does not get truncated to an empty/partial line.
  • Reused the same last-line extraction for logging so the trace output matches what prompt detection actually sees.
  • Added regression coverage for both the newline-terminated prompt case and the bare-\r progress-line case.

Validation:

  • npm run test-browser-no-install -- --run src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts --browser chromium
  • node --experimental-strip-types build/hygiene.ts src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts

I left the prompt-followed-by-hint behavior unchanged for now since that is a broader heuristic tradeoff rather than the concrete regression called out here.

Copy link
Copy Markdown
Collaborator

@meganrogge meganrogge left a comment

Choose a reason for hiding this comment

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

Thanks for the fix — scoping these detectors to the last line is the right direction since the help-pattern regexes aren't $-anchored. A few concerns:

  1. Possible regression when the tail ends with a newline. If outputTail ends in \n or \r (common when a command just printed its final line), _getLastLineForPatternDetection returns ''. Previously, several detectsInputRequiredPattern regexes (e.g. /:\s*$/, /\?\s*...$/) could still match because \s matches \n. After this change, a prompt like "Password:\n" in the buffer will no longer be detected. Consider trimming trailing newlines first, e.g.:

    const trimmed = output.replace(/[\r\n]+$/, '');
    const lastLineBreak = Math.max(trimmed.lastIndexOf('\n'), trimmed.lastIndexOf('\r'));
    return lastLineBreak === -1 ? trimmed : trimmed.slice(lastLineBreak + 1);
  2. Behavior change for detectsInputRequiredPattern. Most regexes in that function are already $-anchored, so scoping is a no-op for them. The one exception is /press a(?:ny)? key/i, which previously could match anywhere in the 1000-char tail and now only matches on the last line. That seems desirable, but worth confirming it doesn't regress any real scenarios.

  3. Brittle private-method access in the new test. Casting the monitor to { [key: string]: ... } to invoke _handleIdleState directly is unusual for this codebase and couples the test to the private shape. Two suggestions:

    • Prefer driving through the public flow (e.g. startMonitoring / the poll loop) so the test survives refactors.
    • Or export _getLastLineForPatternDetection as a free function and unit-test it directly — it has no this dependency.
      Also consider adding a positive case (help text on the last line → pattern detected) so both branches are covered.
  4. Minor. _getLastLineForPatternDetection could just be named _getLastLine (or a module-local free function), since it's purely string slicing.

Nothing blocking, but (1) feels like it could bite in practice.

Copy link
Copy Markdown
Collaborator

@meganrogge meganrogge left a comment

Choose a reason for hiding this comment

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

Thank you!

@meganrogge meganrogge merged commit 908ea9e into microsoft:main Apr 23, 2026
26 checks passed
@mossgowild mossgowild deleted the mossgowild/fix-output-monitor-last-line-detection branch April 24, 2026 01:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

run_in_terminal can freeze the window when OutputMonitor evaluates prompt regexes against multi-line terminal tails

5 participants