fix(terminal-bash): handle terminal protocol replies

Unix PowerShell emits cursor-position requests while PSReadLine starts and
redraws prompts. The subprocess PTY is only a transport, so those requests
went unanswered. Startup could then accept the dsh> literal echoed from its
setup source as a rendered prompt, and later sends were lost or clipped.

Feed raw PTY output into a zero-scrollback @xterm/headless state machine and
write generated replies through the provider-owned terminal handle. Drain
replies before caller input, repeat foreground inspection when terminal
activity races the sample, and retain send ownership until parser and reply
work quiesce. Coalesce raw chunks behind one active parser write so large
Windows output cannot create thousands of queued parse callbacks.

Publish pwsh only from backend stdin_read evidence and start one timeoutMs
deadline before the complete startup retry loop, so inferred-idle follow-ups
cannot reset the bound. Dispose the emulator when the
terminal or cleanup fails. Document the fail-loud ConstrainedLanguage path
and add focused coverage for split queries, reply ordering, foreground
resampling, failure containment, batching, timeout, and disposal.
This commit is contained in:
Tianyi Cui
2026-08-23 19:24:50 +08:00
parent 3c1c6a89b1
commit 4f3a47d792
19 changed files with 590 additions and 63 deletions
@@ -1,5 +1,5 @@
import { spawnSync } from 'node:child_process'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
@@ -72,7 +72,7 @@ function text(result: { content: { type: string; text?: string }[] }): string {
describe.skipIf(!hasPwsh)('persistent pwsh through a real cordis.yml Loader composition', () => {
it('preserves cwd and environment across calls', async () => {
root = await mkdtemp(join(tmpdir(), 'dsh-persistent-pwsh-loader-'))
root = await realpath(await mkdtemp(join(tmpdir(), 'dsh-persistent-pwsh-loader-')))
const configPath = join(root, 'cordis.yml')
await writeFile(configPath, [
"- name: '@deepseek-ai/dsh-agent'",
@@ -93,7 +93,7 @@ describe.skipIf(!hasPwsh)('persistent pwsh through a real cordis.yml Loader comp
' idleSilenceMs: 300',
' handoffGraceMs: 300',
' scrollbackLines: 20000',
' timeoutMs: 8000',
' timeoutMs: 60000',
' disposeGraceMs: 500',
"- name: '@deepseek-ai/dsh-tool-pwsh-persistent'",
' config:',