fix(pty): keep the controlled prompt so persistent bash settles fast
tool-bash-persistent overwrote the backend's PS1, so terminal-bash prompt readiness never matched and every send degraded to the 3.5s silence tier (idleSilenceMs + handoffGraceMs) under production defaults. The controlled PROMPT_COMMAND now re-asserts PS1 before every prompt, so an in-shell override never survives to the next prompt. The tool initializes with stty -echo alone and detects the no-end-marker fallback through the seam's stdin_read wait reason instead of matching its own prompt text. Tool calls drop from 7180/3560/3566 ms to 355/88/91 ms (spawn+init+echo, echo, pwd; darwin, production defaults). The loader composition suite now pins the fast path by pushing idleSilenceMs beyond the send bound, and a real-PTY case proves PS1 self-healing. Fixes #2585
This commit is contained in:
@@ -100,7 +100,7 @@ type StubMode =
|
||||
| 'paged-scrollback'
|
||||
|
||||
class StubPtySession implements TerminalBackendSession {
|
||||
readonly motd = '__DSH_PERSISTENT_BASH_PROMPT__ '
|
||||
readonly motd = 'stub> '
|
||||
readonly pid = 123
|
||||
statusValue: TerminalSessionStatus = { kind: 'running' }
|
||||
scrollback = this.motd
|
||||
@@ -325,7 +325,7 @@ describe('tool-bash-persistent', () => {
|
||||
expect(ctx.tools.get('bash')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('handles inferred idle, prompt fallback, shell exit, clipping, and cleanup', async () => {
|
||||
it('handles inferred idle, stdin_read fallback, shell exit, clipping, and cleanup', async () => {
|
||||
const { ctx, owner, stub, fiber } = await setup({
|
||||
backendType: 'stub',
|
||||
maxOutputChars: 10,
|
||||
@@ -338,18 +338,16 @@ describe('tool-bash-persistent', () => {
|
||||
|
||||
session.mode = 'incremental-fallback'
|
||||
session.scrollback = ''
|
||||
expect(text(await call(ctx, owner, 'incremental fallback'))).toBe('increment')
|
||||
expect(text(await call(ctx, owner, 'incremental fallback'))).toContain('increment')
|
||||
|
||||
session.mode = 'prompt-only'
|
||||
const promptFallback = text(await call(ctx, owner, 'bad {'))
|
||||
expect(promptFallback).toContain('bash: synt')
|
||||
expect(promptFallback).not.toContain('DSH_PERSISTENT_BASH_PROMPT')
|
||||
|
||||
session.mode = 'prompt-crlf'
|
||||
session.scrollback = ''
|
||||
const crlfPromptFallback = text(await call(ctx, owner, 'bad {'))
|
||||
expect(crlfPromptFallback).toContain('bash: synt')
|
||||
expect(crlfPromptFallback).not.toContain('DSH_PERSISTENT_BASH_PROMPT')
|
||||
|
||||
session.mode = 'end-only'
|
||||
session.scrollback = ''
|
||||
@@ -435,7 +433,7 @@ describe('tool-bash-persistent', () => {
|
||||
expect(text(await call(ctx, owner, 'paged output'))).toBe('hello from stub')
|
||||
})
|
||||
|
||||
it('sanitizes a prompt fallback reached after multiple polling rounds', async () => {
|
||||
it('returns a stdin_read fallback reached after multiple polling rounds', async () => {
|
||||
const { ctx, owner, stub } = await setup({ backendType: 'stub', maxOutputChars: 1_000 })
|
||||
await call(ctx, owner, 'warm up')
|
||||
const session = stub.sessions[0]!
|
||||
@@ -444,7 +442,8 @@ describe('tool-bash-persistent', () => {
|
||||
const result = text(await call(ctx, owner, 'bad {'))
|
||||
expect(result).toContain('partial syntax output')
|
||||
expect(result).toContain('bash: syntax error')
|
||||
expect(result).not.toContain('DSH_PERSISTENT_BASH_PROMPT')
|
||||
// The backend owns the prompt text, so the fallback retains it verbatim.
|
||||
expect(result.endsWith('stub> ')).toBe(true)
|
||||
expect(result).not.toContain('DSH_PERSISTENT_BASH_START')
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user