Sfoglia il codice sorgente

fix(tui): mark disposed when the agent leaves the registry under the TUI

agent/disposed only cleared the status line, so an agent-loop-only reload that
disposed the agent while the TUI stayed mounted left the local disposed flag
false. Since retained agents accept deliveries after detachment, later input
drove a zombie agent/session. Set disposed on agent/disposed so dispatchMessage
reports it. Adds a regression that sends after disposal and asserts no delivery.
_Kerman 2 mesi fa
parent
commit
71152bc4c0
2 ha cambiato i file con 24 aggiunte e 0 eliminazioni
  1. 5 0
      packages/ui/tui/src/index.ts
  2. 19 0
      packages/ui/tui/tests/tui.spec.ts

+ 5 - 0
packages/ui/tui/src/index.ts

@@ -3217,6 +3217,11 @@ export function createTuiChat(
   })
   const disposeAgent = ctx.on('agent/disposed', (subject) => {
     if (subject !== agent) return
+    // The agent left the registry (e.g. an agent-loop-only reload) while the
+    // TUI stays mounted. Retained agents accept deliveries after detachment, so
+    // without this a later send would drive a zombie agent/session; mark
+    // disposed so dispatchMessage reports it instead.
+    disposed = true
     clearStatus()
     appendNotice(`Agent "${agent.id}" was disposed.`, 'warning')
   })

+ 19 - 0
packages/ui/tui/tests/tui.spec.ts

@@ -2859,6 +2859,25 @@ describe('pi-tui chat lifecycle and transcript', () => {
     expect(events.terminal.output).toContain('was disposed')
     await dispose(events)
   })
+
+  it('rejects input after the agent is disposed out from under the TUI', async () => {
+    const result = await setup()
+
+    // The agent leaves the registry (e.g. an agent-loop-only reload) while the
+    // TUI stays mounted. A later send must report disposal, not drive the
+    // detached zombie agent.
+    agentEvents(result.ctx, result.agent).emit('agent/disposed')
+    await tick()
+    expect(result.terminal.output).toContain('was disposed')
+
+    result.terminal.send('drive the zombie')
+    result.terminal.send('\r')
+    await tick()
+    expect(result.agent.sent).toHaveLength(0)
+    expect(result.agent.steered).toHaveLength(0)
+    expect(result.terminal.output).toContain('is disposed')
+    await dispose(result)
+  })
 })
 
 describe('skill slash command', () => {