Browse Source

fix(pty): observe readiness before exact probe threshold

Tianyi Cui 2 months ago
parent
commit
16958cdbe7
2 changed files with 35 additions and 6 deletions
  1. 10 6
      packages/pty/pty-local/src/session.ts
  2. 25 0
      packages/pty/pty-local/tests/session.spec.ts

+ 10 - 6
packages/pty/pty-local/src/session.ts

@@ -125,7 +125,8 @@ class LocalSendOperation implements PtySendOperation {
 
   acceptsStdinWait(pgid: number, waiting: boolean): boolean {
     // The same group may still expose the wait that existed before terminal.write.
-    // It becomes post-write evidence only after polling observes it leave that wait.
+    // Observe every poll so a departure before the exact-settlement threshold
+    // still makes a later return to that wait post-write evidence.
     if (pgid !== this.initialForegroundPgid) return waiting
     if (!waiting) this.initialForegroundLeftWait = true
     return waiting && this.initialForegroundLeftWait
@@ -338,12 +339,15 @@ export class LocalPtySession implements PtyBackendSession {
     }
     const elapsed = Date.now() - operation.startedAt
     const startupHasOutput = !this.initializing || this.scrollback.snapshot().text.length > 0
-    if (startupHasOutput && elapsed >= this.config.exactProbeAfterMs) {
+    let acceptsStdinWait = false
+    if (startupHasOutput) {
       const pgid = this.inspector.foregroundPgid(this.pid)
-      if (pgid !== undefined && operation.acceptsStdinWait(pgid, this.inspector.isStdinWaiting(pgid))) {
-        this.settleActive('stdin_read')
-        return
-      }
+      acceptsStdinWait = pgid !== undefined
+        && operation.acceptsStdinWait(pgid, this.inspector.isStdinWaiting(pgid))
+    }
+    if (elapsed >= this.config.exactProbeAfterMs && acceptsStdinWait) {
+      this.settleActive('stdin_read')
+      return
     }
     // A prompt candidate can race bash's foreground handoff, but an interactive
     // child also inherits PROMPT_COMMAND. Silence therefore remains the bound

+ 25 - 0
packages/pty/pty-local/tests/session.spec.ts

@@ -144,6 +144,31 @@ describe('LocalPtySession readiness and output', () => {
     expect((await operation.done).waitReason).toBe('stdin_read')
   })
 
+  it('tracks a pre-write wait exit before exact probing begins', async () => {
+    vi.useFakeTimers()
+    const terminal = new FakeTerminal()
+    const inspector = new FakeInspector()
+    const session = new LocalPtySession(terminal.asPty(), inspector, config({
+      exactProbeAfterMs: 50,
+      idleSilenceMs: 100,
+      timeoutMs: 200,
+    }))
+    await initialize(session, terminal)
+
+    inspector.waiting = true
+    const operation = session.startSend({ text: 'fast command', submit: true })
+    let settled = false
+    void operation.done.then(() => { settled = true })
+    inspector.waiting = false
+    await vi.advanceTimersByTimeAsync(10)
+    inspector.waiting = true
+    await vi.advanceTimersByTimeAsync(30)
+    expect(settled).toBe(false)
+    await vi.advanceTimersByTimeAsync(10)
+    expect(settled).toBe(true)
+    expect((await operation.done).waitReason).toBe('stdin_read')
+  })
+
   it('distinguishes inferred idle, timeout, exit signal, and operation reads', async () => {
     vi.useFakeTimers()
     const terminal = new FakeTerminal()