Quellcode durchsuchen

fix(plan): make direct exit discoverable

fz vor 2 Monaten
Ursprung
Commit
5e8943fd1c

+ 2 - 2
examples/tui-agent/tests/snapshots/multi-turn-conversation/terminal.expected.txt

@@ -11,8 +11,8 @@ buffer
 2| " deepseek-v4-flash  •  main-session"
   style 1-34 dim
 3| <blank>
-4| " Entering plan mode (applies from the next step).                                                   "
-  style 1-48 fg=bright-black
+4| " Entering plan mode (applies from the next step). Use /plan off to leave.                           "
+  style 1-72 fg=bright-black
 5| <blank>
 6| "▌ "
   style 0-0 fg=bright-blue

+ 6 - 4
examples/tui-agent/tests/tui-keyless-smoke.e2e.ts

@@ -87,14 +87,16 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
     const output = await smoke({
       label: 'tui-agent boot',
       actions: [
-        { waitFor: 'main-session-', send: '/plan\r' },
-        { waitFor: 'Entering plan mode (applies from the next step).', send: '/exit\r' },
+        { waitFor: 'main-session-', send: '/plan' },
+        { waitFor: '[off|message] — Enter or leave plan mode', send: '\r' },
+        { waitFor: 'Entering plan mode (applies from the next step). Use /plan off to leave.', send: '/exit\r' },
       ],
     })
     expect(output).toContain('DEEPSEEK')
     expect(output).toContain('HARNESS')
     expect(output).toContain('main-session-')
-    expect(output).toContain('Entering plan mode (applies from the next step).')
+    expect(output).toContain('[off|message] — Enter or leave plan mode')
+    expect(output).toContain('Entering plan mode (applies from the next step). Use /plan off to leave.')
     // Borderless: no box-drawing frame around the banner.
     expect(output).not.toContain('╭')
     expect(output).not.toContain('╮')
@@ -127,7 +129,7 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
       ],
     })
     expect(output).toContain('I need one decision before I continue.')
-    expect(output).toContain('Entering plan mode (applies from the next step).')
+    expect(output).toContain('Entering plan mode (applies from the next step). Use /plan off to leave.')
     expect(output).toContain('Leaving plan mode (applies from the next step).')
     expect(output).toContain('Default mode confirmed.')
     expect(output).toContain(String.raw`\x1b]2;MODEL_CONTROLLED\x07`)

+ 4 - 1
packages/plan/plan-mode/src/index.ts

@@ -228,7 +228,10 @@ export class PlanModeService extends Service {
           }
           this.set(agent, true)
           if (message !== '') agent.steer([{ type: 'text', text: message }])
-          return { kind: 'success', text: 'Entering plan mode (applies from the next step).' }
+          return {
+            kind: 'success',
+            text: 'Entering plan mode (applies from the next step). Use /plan off to leave.',
+          }
         },
       })
     })

+ 8 - 2
packages/plan/plan-mode/tests/plan-mode.spec.ts

@@ -553,7 +553,10 @@ describe('/plan', () => {
     expect(await ctx.commands.execute(plainAgent, '/mode', signal)).toBeUndefined()
     expect(await ctx.commands.execute(plainAgent, '/review', signal)).toBeUndefined()
     const plain = await ctx.commands.execute(plainAgent, '/plan', signal)
-    expect(plain).toEqual({ kind: 'success', text: 'Entering plan mode (applies from the next step).' })
+    expect(plain).toEqual({
+      kind: 'success',
+      text: 'Entering plan mode (applies from the next step). Use /plan off to leave.',
+    })
     expect(ctx.planMode.get(plainAgent)).toEqual({ active: false, pending: true })
     expect(plainSteer).not.toHaveBeenCalled()
 
@@ -561,7 +564,10 @@ describe('/plan', () => {
     const messageSteer = vi.fn()
     ;(messageAgent as unknown as { steer: typeof messageSteer }).steer = messageSteer
     const plan = await ctx.commands.execute(messageAgent, '/plan   draft the migration  ', signal)
-    expect(plan).toEqual({ kind: 'success', text: 'Entering plan mode (applies from the next step).' })
+    expect(plan).toEqual({
+      kind: 'success',
+      text: 'Entering plan mode (applies from the next step). Use /plan off to leave.',
+    })
     expect(ctx.planMode.get(messageAgent)).toEqual({ active: false, pending: true })
     expect(messageSteer).toHaveBeenCalledExactlyOnceWith([{ type: 'text', text: 'draft the migration' }])
   })

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

@@ -2384,6 +2384,7 @@ export function createTuiChat(
         ...ctx.commands.list(agent).map(command => ({
           name: command.name,
           description: command.description,
+          ...(command.input === undefined ? {} : { argumentHint: command.input.hint }),
         })),
         ...skillCommands,
       ],

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

@@ -1706,6 +1706,11 @@ describe('pi-tui chat lifecycle and transcript', () => {
       handler: () => ({ kind: 'error' as const, text: 'plugin error result' }),
     })
 
+    result.terminal.send('/plugin-ch')
+    await tick()
+    expect(result.terminal.output).toContain('<value> — Run a plugin command')
+    result.terminal.send('\x03')
+
     result.terminal.send('/plugin-check  value  ')
     result.terminal.send('\r')
     await tick()