@namzu/cli 28.0.0 → 28.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/integrations/sessions/transcript-export.js +3 -1
  3. package/dist/integrations/sessions/transcript-export.js.map +1 -1
  4. package/dist/permissions/live-mode.d.ts +73 -0
  5. package/dist/permissions/live-mode.d.ts.map +1 -0
  6. package/dist/permissions/live-mode.js +62 -0
  7. package/dist/permissions/live-mode.js.map +1 -0
  8. package/dist/tui/App.d.ts.map +1 -1
  9. package/dist/tui/App.js +176 -74
  10. package/dist/tui/App.js.map +1 -1
  11. package/dist/tui/Checklist.d.ts +56 -0
  12. package/dist/tui/Checklist.d.ts.map +1 -0
  13. package/dist/tui/Checklist.js +90 -0
  14. package/dist/tui/Checklist.js.map +1 -0
  15. package/dist/tui/StatusBar.d.ts +10 -0
  16. package/dist/tui/StatusBar.d.ts.map +1 -1
  17. package/dist/tui/StatusBar.js +8 -1
  18. package/dist/tui/StatusBar.js.map +1 -1
  19. package/dist/tui/TaskList.d.ts +18 -25
  20. package/dist/tui/TaskList.d.ts.map +1 -1
  21. package/dist/tui/TaskList.js +34 -58
  22. package/dist/tui/TaskList.js.map +1 -1
  23. package/dist/tui/Transcript.d.ts.map +1 -1
  24. package/dist/tui/Transcript.js +8 -1
  25. package/dist/tui/Transcript.js.map +1 -1
  26. package/dist/tui/agent.d.ts +29 -3
  27. package/dist/tui/agent.d.ts.map +1 -1
  28. package/dist/tui/agent.js +38 -5
  29. package/dist/tui/agent.js.map +1 -1
  30. package/dist/tui/live-window.d.ts +22 -0
  31. package/dist/tui/live-window.d.ts.map +1 -1
  32. package/dist/tui/live-window.js +34 -0
  33. package/dist/tui/live-window.js.map +1 -1
  34. package/dist/tui/notices.d.ts +27 -0
  35. package/dist/tui/notices.d.ts.map +1 -0
  36. package/dist/tui/notices.js +27 -0
  37. package/dist/tui/notices.js.map +1 -0
  38. package/dist/tui/task-activity.d.ts +71 -0
  39. package/dist/tui/task-activity.d.ts.map +1 -0
  40. package/dist/tui/task-activity.js +157 -0
  41. package/dist/tui/task-activity.js.map +1 -0
  42. package/dist/tui/types.d.ts +13 -0
  43. package/dist/tui/types.d.ts.map +1 -1
  44. package/package.json +7 -7
package/dist/tui/App.js CHANGED
@@ -50,7 +50,10 @@ import { PermissionOverlay } from './PermissionOverlay.js';
50
50
  import { Picker } from './Picker.js';
51
51
  import { ResumePicker } from './ResumePicker.js';
52
52
  import { StatusBar } from './StatusBar.js';
53
- import { TaskList } from './TaskList.js';
53
+ import { isRepeatedNotice } from './notices.js';
54
+ import { checklistProgress } from './Checklist.js';
55
+ import { TaskList, taskListRows } from './TaskList.js';
56
+ import { applyTaskOperation, isTaskTool, removeTask, taskOperationFor, taskReportChecklist, upsertTask, } from './task-activity.js';
54
57
  import { TextPrompt } from './TextPrompt.js';
55
58
  import { modelCatalogueView } from './model-catalogue-view.js';
56
59
  import { conversationEvidenceView } from './conversation-evidence-view.js';
@@ -64,7 +67,7 @@ import { assistantTranscriptTexts } from './conversation-history.js';
64
67
  import { copyTargetsForResponse } from './copy-targets.js';
65
68
  import { editablePrompts } from './edit-prompts.js';
66
69
  import { editDraftInExternalEditor } from './external-editor.js';
67
- import { liveWindow } from './live-window.js';
70
+ import { checklistInView, liveWindow } from './live-window.js';
68
71
  import { resolveModelSwitch, } from './model-switch.js';
69
72
  import { parseModelSelectionIntent, resolveModelSelectionIntent } from './model-selection-intent.js';
70
73
  import { describeCodexDeviceLoginStart, describeLoginOutcome, describeLoginStart, describeLogout, describeProviderLogout, } from './login-prompt.js';
@@ -478,8 +481,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
478
481
  * it would read as work still pending on a turn that already ended.
479
482
  */
480
483
  const [tasks, setTasks] = useState([]);
481
- /** Ids already seen this request, so an opening row is written once. */
482
- const knownTaskIdsRef = useRef(new Set());
484
+ /**
485
+ * The same list, current NOW. React runs state updaters lazily, and each
486
+ * task event needs the task's previous state to say what changed and the
487
+ * whole list to draw the block it leaves in the transcript.
488
+ */
489
+ const tasksRef = useRef([]);
483
490
  // Bumped to reset the <Static> transcript log (on /clear, /clear-screen and /resume).
484
491
  const [resetKey, setResetKey] = useState(0);
485
492
  /**
@@ -1094,35 +1101,41 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1094
1101
  }, []);
1095
1102
  const pushMessage = useCallback((role, content, pending = false, glyph, detail, glyphColor, meta, activity) => {
1096
1103
  const id = nextId();
1097
- setMessages((prev) => [
1098
- ...prev,
1099
- {
1100
- id,
1101
- role,
1102
- content,
1103
- pending,
1104
- glyph,
1105
- detail,
1106
- glyphColor,
1107
- meta,
1108
- activity,
1109
- // Numbered only if this body will actually be COLLAPSED — the
1110
- // number exists to be read off a hint, and a body that fits
1111
- // prints no hint. Numbering every body instead would leave gaps
1112
- // the operator can see nothing of, make bare `/expand` reprint a
1113
- // two-line body while the truncated one above it stayed hidden,
1114
- // and let the out-of-range message quote a count that includes
1115
- // blocks no hint ever named.
1116
- //
1117
- // Derived from `prev` rather than a counter, so the number is a
1118
- // fact about the transcript rather than a second record of it.
1119
- ...((activity && detail?.length) || willCollapse(detail)
1120
- ? {
1121
- detailRef: prev.filter((m) => m.detailRef !== undefined).length + 1,
1122
- }
1123
- : {}),
1124
- },
1125
- ]);
1104
+ const candidate = { role, content, pending, glyph, detail, activity };
1105
+ setMessages((prev) =>
1106
+ // The notice layer, not each caller, keeps a notice from printing
1107
+ // twice in a row: the same sentence twice reads as two events.
1108
+ isRepeatedNotice(prev.at(-1), candidate)
1109
+ ? prev
1110
+ : [
1111
+ ...prev,
1112
+ {
1113
+ id,
1114
+ role,
1115
+ content,
1116
+ pending,
1117
+ glyph,
1118
+ detail,
1119
+ glyphColor,
1120
+ meta,
1121
+ activity,
1122
+ // Numbered only if this body will actually be COLLAPSED — the
1123
+ // number exists to be read off a hint, and a body that fits
1124
+ // prints no hint. Numbering every body instead would leave gaps
1125
+ // the operator can see nothing of, make bare `/expand` reprint a
1126
+ // two-line body while the truncated one above it stayed hidden,
1127
+ // and let the out-of-range message quote a count that includes
1128
+ // blocks no hint ever named.
1129
+ //
1130
+ // Derived from `prev` rather than a counter, so the number is a
1131
+ // fact about the transcript rather than a second record of it.
1132
+ ...((activity && detail?.length) || willCollapse(detail)
1133
+ ? {
1134
+ detailRef: prev.filter((m) => m.detailRef !== undefined).length + 1,
1135
+ }
1136
+ : {}),
1137
+ },
1138
+ ]);
1126
1139
  return id;
1127
1140
  }, [nextId]);
1128
1141
  /**
@@ -1272,30 +1285,42 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1272
1285
  }
1273
1286
  }
1274
1287
  }, [subagents, pushMessage, session]);
1275
- const applyPermissionMode = useCallback((mode) => {
1288
+ /**
1289
+ * Change the permission mode, now — including while a turn runs.
1290
+ *
1291
+ * It used to be refused until the current work settled. The reference
1292
+ * terminal applies the key at once, mid-turn, and so does this: the running
1293
+ * turn reads the mode at every decision (`SendOptions.currentPermissionMode`),
1294
+ * so the change governs its next tool call and every turn after. A dialog
1295
+ * already on screen is decided under the mode it was asked under; entering
1296
+ * plan refuses the next change the turn attempts; leaving plan re-runs
1297
+ * nothing it refused. The change is recorded on the running turn's log as
1298
+ * `approval_policy_changed`, which also tells the model once.
1299
+ *
1300
+ * `announce` is for a change asked for by name (`/permissions`), which gets
1301
+ * a reply. Shift+Tab is a reflex key: the footer is its reply, as it is in
1302
+ * the reference, and five presses no longer leave five transcript lines.
1303
+ */
1304
+ const applyPermissionMode = useCallback((mode, announce = true) => {
1276
1305
  if (!session?.hasProvider) {
1277
1306
  pushMessage('system', 'Choose a model before changing permissions.');
1278
1307
  return;
1279
1308
  }
1280
- if (state !== 'idle' ||
1281
- abortRef.current !== null ||
1282
- hasUnsettledTurn() ||
1283
- queuedRef.current.length > 0 ||
1284
- permissionResolveRef.current !== null ||
1285
- compactingRef.current) {
1286
- pushMessage('system', 'Permissions were not changed. Finish or stop the current work first.');
1287
- return;
1288
- }
1289
1309
  if (!session.resetApprovalLatch) {
1290
1310
  pushMessage('system', 'This session cannot reset approvals. Reconnect before changing permissions.');
1291
1311
  return;
1292
1312
  }
1293
1313
  session.resetApprovalLatch();
1314
+ // The ref first: a running turn reads it at its next decision, and the
1315
+ // record below makes the change durable before that decision is made.
1294
1316
  permissionModeRef.current = mode;
1295
1317
  permissionModeSourceRef.current = 'session';
1296
1318
  setPermissionModeState(mode);
1297
- pushMessage('system', `Permissions: ${permissionModeLabel(mode)} for this session. ${permissionModeDescription(mode)}`);
1298
- }, [hasUnsettledTurn, pushMessage, session, state]);
1319
+ void session.setPermissionMode?.(mode);
1320
+ if (announce) {
1321
+ pushMessage('system', `Permissions: ${permissionModeLabel(mode)} for this session. ${permissionModeDescription(mode)}`);
1322
+ }
1323
+ }, [pushMessage, session]);
1299
1324
  const applyReasoningEffort = useCallback((effort, selectedSession) => {
1300
1325
  if (!session?.hasProvider) {
1301
1326
  pushMessage('system', 'No active session — pick a provider before changing reasoning effort.');
@@ -1546,14 +1571,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1546
1571
  * only — `auto` and `strict` are deliberate choices made by name in
1547
1572
  * `/permissions`, not stops on a key an operator presses on reflex. From
1548
1573
  * either of those the key returns to `prompt`, which is the direction a
1549
- * reflex should fall. The change goes through the same gate `/permissions`
1550
- * uses, so it is refused while a turn is active, and the refusal is
1551
- * explained on screen.
1574
+ * reflex should fall. The change goes through the same path `/permissions`
1575
+ * uses and takes effect at once, mid-turn included; the footer is its only
1576
+ * on-screen reply (see `applyPermissionMode`).
1552
1577
  */
1553
1578
  const cyclePermissionMode = useCallback(() => {
1554
1579
  const current = permissionModeRef.current;
1555
1580
  const next = current === 'prompt' ? 'accept-edits' : current === 'accept-edits' ? 'plan' : 'prompt';
1556
- applyPermissionMode(next);
1581
+ applyPermissionMode(next, false);
1557
1582
  }, [applyPermissionMode]);
1558
1583
  const runConversationExport = useCallback((destination) => {
1559
1584
  const source = stableExportSource();
@@ -2604,10 +2629,23 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2604
2629
  const finalized = messages.filter((m) => !m.pending);
2605
2630
  // Activity and the plan share the terminal with the draft. Collapse the two
2606
2631
  // lists together only when their full previews would crowd the input area.
2607
- const fullTaskFurniture = tasks.length === 0 ? 0 : Math.min(tasks.length, 8) + 3;
2608
2632
  const fullToolFurniture = activeTools.length === 0 ? 0 : Math.min(activeTools.length, 3) * 2 + 2;
2633
+ // The current-step row stands in for a checklist that is out of view. While
2634
+ // the newest checklist block, everything printed after it and the live
2635
+ // furniture fit the screen, the block is on screen, and the row would only
2636
+ // say one of its lines again. Once later rows push the block's head off a
2637
+ // short screen, the row is what keeps the current step visible.
2638
+ const checklistShown = checklistInView({
2639
+ messages,
2640
+ rows: terminal.rows,
2641
+ columns: terminal.columns,
2642
+ furnitureRows: LIVE_FURNITURE_ROWS + fullToolFurniture,
2643
+ raw: rawOutput,
2644
+ });
2645
+ const liveTasks = checklistShown ? [] : tasks;
2646
+ const fullTaskFurniture = taskListRows(liveTasks);
2609
2647
  const compactWork = LIVE_FURNITURE_ROWS + fullTaskFurniture + fullToolFurniture >= terminal.rows;
2610
- const taskFurniture = compactWork && fullTaskFurniture > 0 ? 2 : fullTaskFurniture;
2648
+ const taskFurniture = fullTaskFurniture;
2611
2649
  const toolFurniture = compactWork && fullToolFurniture > 0 ? 2 : fullToolFurniture;
2612
2650
  // How much of the transcript is still redrawable. The rest belongs to native
2613
2651
  // terminal scrollback; the live tail stays deliberately small so an activity
@@ -3516,6 +3554,24 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3516
3554
  });
3517
3555
  sendTerminalNotification({ kind: 'approval-required' });
3518
3556
  }), [sendTerminalNotification, setChoicePicker, setSelectedChoice]);
3557
+ /**
3558
+ * Grow this turn's open task block, or open one. The whole plan rides along
3559
+ * so the block shows the checklist as it stood after the operation; a
3560
+ * `null` operation (a `task_list`) refreshes it without naming a change.
3561
+ */
3562
+ const writeTaskBlock = useCallback((st, operation, checklist) => {
3563
+ st.taskBlockKey ??= nextId();
3564
+ const key = st.taskBlockKey;
3565
+ const id = nextId();
3566
+ setMessages((prev) => applyTaskOperation(prev, {
3567
+ key,
3568
+ id,
3569
+ operation,
3570
+ checklist,
3571
+ settled: settledRef.current,
3572
+ glyphColor: theme.status.ok,
3573
+ }));
3574
+ }, [nextId]);
3519
3575
  // Render one agent event onto the transcript. Shared by the local turn loop
3520
3576
  // and the daemon-attach poller, so both paths produce identical output.
3521
3577
  // `st` carries the streaming-assistant bubble id + accumulated text across
@@ -3700,6 +3756,28 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3700
3756
  setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
3701
3757
  break;
3702
3758
  }
3759
+ if (isTaskTool(event.toolName)) {
3760
+ // The task event already wrote the block. A listing refreshes it
3761
+ // while there is a plan to show; with none it keeps its own row.
3762
+ if (!event.isError && event.toolName !== 'task_list') {
3763
+ setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
3764
+ break;
3765
+ }
3766
+ if (!event.isError && tasksRef.current.length > 0) {
3767
+ writeTaskBlock(st, null, tasksRef.current);
3768
+ setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
3769
+ break;
3770
+ }
3771
+ // Its own row, in words: the call's label, and the result's
3772
+ // label rather than the model's receipt, which names ids.
3773
+ pushMessage('tool', done?.label ?? formatToolCall(event.toolName, event.summary, true), false, event.isError ? '✗' : '✓', undefined, event.isError ? theme.status.error : theme.status.ok);
3774
+ const said = event.resultLabel ?? (event.isError ? 'the task tool refused the call' : '');
3775
+ if (said.length > 0) {
3776
+ pushMessage('tool', event.isError ? `failed: ${said}` : said, false, '⎿');
3777
+ }
3778
+ setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
3779
+ break;
3780
+ }
3703
3781
  const catalogue = !event.isError && event.toolName === 'agent_models' && event.output !== undefined
3704
3782
  ? modelCatalogueView(event.output) : undefined;
3705
3783
  if (catalogue !== undefined) {
@@ -3758,31 +3836,34 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3758
3836
  break;
3759
3837
  }
3760
3838
  case 'task': {
3761
- // The live list gets every change; the transcript records the
3762
- // opening and the close, as it did before the list existed.
3763
- // Decided from a ref, not inside the state updater: React runs
3764
- // updaters lazily, so a flag set there is still unset when the
3765
- // transcript row below is chosen.
3766
- const isNew = !knownTaskIdsRef.current.has(event.taskId);
3767
- knownTaskIdsRef.current.add(event.taskId);
3839
+ // The transcript owns the checklist: consecutive operations fold
3840
+ // into one block there (task-activity.ts). The live row above the
3841
+ // composer only names the current step.
3768
3842
  const item = {
3769
3843
  id: event.taskId,
3770
3844
  subject: event.subject,
3771
3845
  status: event.status,
3772
3846
  };
3773
- setTasks((prev) => {
3774
- const index = prev.findIndex((task) => task.id === item.id);
3775
- return index < 0 ? [...prev, item] : prev.map((task, i) => (i === index ? item : task));
3776
- });
3777
- if (event.status === 'completed') {
3778
- pushMessage('tool', event.subject, false, '☑');
3779
- }
3780
- else if (event.status === 'failed') {
3781
- pushMessage('tool', event.subject, false, '☒');
3847
+ // A reply in progress ends here, as it does at a tool call, so
3848
+ // the block lands after the text that led to it.
3849
+ closeAssistant();
3850
+ let operation;
3851
+ if (event.removed) {
3852
+ // Removed from the plan: it leaves the checklist, and the block
3853
+ // says so, rather than drawing it as still open.
3854
+ const next = removeTask(tasksRef.current, item.id, item.subject);
3855
+ operation = next.operation;
3856
+ tasksRef.current = next.tasks;
3782
3857
  }
3783
- else if (isNew) {
3784
- pushMessage('tool', event.subject, false, '☐');
3858
+ else {
3859
+ const previous = tasksRef.current.find((task) => task.id === item.id);
3860
+ operation = taskOperationFor(previous, item);
3861
+ tasksRef.current = upsertTask(tasksRef.current, item);
3785
3862
  }
3863
+ const checklist = tasksRef.current;
3864
+ setTasks(checklist);
3865
+ if (operation)
3866
+ writeTaskBlock(st, operation, checklist);
3786
3867
  break;
3787
3868
  }
3788
3869
  case 'job':
@@ -3852,7 +3933,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3852
3933
  st.outcome = 'stopped';
3853
3934
  st.queuePauseOutcome = 'paused';
3854
3935
  st.notification = { kind: 'turn-settled', outcome: 'stopped' };
3855
- pushMessage('system', describeTurnInterruption(event), false, '⏸');
3936
+ pushMessage('system', describeTurnInterruption(event), false, '‖');
3856
3937
  break;
3857
3938
  case 'error':
3858
3939
  closeAssistant();
@@ -3865,7 +3946,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3865
3946
  st.notification = null;
3866
3947
  break;
3867
3948
  }
3868
- }, [appendToMessage, finalizeMessage, flushStream, pushMessage]);
3949
+ }, [appendToMessage, finalizeMessage, flushStream, pushMessage, writeTaskBlock]);
3869
3950
  applyEventRef.current = applyEvent;
3870
3951
  const runTurn = useCallback(async (prompt) => {
3871
3952
  // A passive queue effect can still hold the previous render's session
@@ -3975,7 +4056,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3975
4056
  // ended: a finished list stays on screen until the operator moves on,
3976
4057
  // which is the moment it has told them everything it can.
3977
4058
  setTasks([]);
3978
- knownTaskIdsRef.current = new Set();
4059
+ tasksRef.current = [];
3979
4060
  // The model interleaves text → tool → text across iterations; `applyEvent`
3980
4061
  // renders each one in order.
3981
4062
  const st = {
@@ -4201,6 +4282,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4201
4282
  // prompt closes it. A paused turn is never closed this way.
4202
4283
  abandonInterrupted: true,
4203
4284
  permissionMode: turnPermissionMode,
4285
+ // Read at every decision: the operator may change the mode
4286
+ // while this turn runs, and the change governs what follows.
4287
+ currentPermissionMode: () => permissionModeRef.current,
4204
4288
  limits: turnLimits,
4205
4289
  ...(turnReasoningEffort !== undefined ? { effort: turnReasoningEffort } : {}),
4206
4290
  ...(turnOrchestrateMode ? { orchestrate: true } : {}),
@@ -5254,6 +5338,24 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5254
5338
  // painted as state of the one now on screen.
5255
5339
  if (conversationGenRef.current !== generation)
5256
5340
  return;
5341
+ const checklist = slash.name === 'tasks' && outcome?.kind === 'report'
5342
+ ? taskReportChecklist(outcome.rows)
5343
+ : undefined;
5344
+ if (checklist) {
5345
+ // Drawn by the same checklist as the transcript's task
5346
+ // blocks: the same marks, and no id or owner column.
5347
+ const id = nextId();
5348
+ setMessages((prev) => [
5349
+ ...prev,
5350
+ {
5351
+ id,
5352
+ role: 'system',
5353
+ content: checklist.length === 0 ? 'No tasks yet.' : checklistProgress(checklist),
5354
+ ...(checklist.length > 0 ? { checklist } : {}),
5355
+ },
5356
+ ]);
5357
+ return;
5358
+ }
5257
5359
  pushMessage('system', outcome
5258
5360
  ? renderOutcome(outcome)
5259
5361
  : `/${slash.name} is registered but this session cannot run it.`);
@@ -6490,7 +6592,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6490
6592
  // where it is decided rather than guessed on the screen: with no
6491
6593
  // session behind the picker, esc exits namzu and the footer says
6492
6594
  // so instead of offering a cancel nobody will see.
6493
- cancelExits: !session?.hasProvider })) : (_jsxs(_Fragment, { children: [agentSurface === null && outputViewer === null ? (_jsx(LiveActivity, { compact: compactWork, activeTools: visibleActiveTools, working: state === 'thinking' || state === 'tool', interruptible: abortRef.current !== null, animate: stdout.isTTY === true && permission === null && textPrompt === null, thinking: thinking })) : null, agentSurface === null && outputViewer === null && permission === null ? (_jsx(TaskList, { tasks: tasks, compact: compactWork })) : null, permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls, review: permission.review, summary: permission.summary, detailsOpen: permissionDetailsOpen, reviewOffset: permissionReviewOffset, choice: permissionChoice, queuedCount: queuedPermissionCount, sourceLabel: permissionSourceLabel, columns: terminal.columns, rows: terminal.rows })) : null, textPrompt ? (_jsx(TextPrompt, { columns: Math.max(1, (terminal.columns ?? 80) - 2), title: textPrompt.title, placeholder: textPrompt.placeholder, initialValue: textPrompt.initialValue, emptyNotice: textPrompt.emptyNotice, hidden: permission !== null || (agentSurface !== null || outputViewer !== null), onSubmit: submitTextPrompt, onCancel: cancelTextPrompt }, textPrompt.token)) : permission === null && agentSurface === null && outputViewer === null && choicePicker ? (_jsx(ChoicePicker, { busy: 'busy' in choicePicker && choicePicker.busy === true, columns: Math.max(1, (terminal.columns ?? 80) - 2), title: choicePicker.title, notice: choicePicker.notice, options: filterChoiceOptions(choicePicker.options, choiceQuery), query: choicePickerSearchable(choicePicker) ? choiceQuery : undefined, selected: selectedChoice, windowSize: choicePicker.kind === 'command' ? choicePicker.windowSize : undefined })) : permission === null && agentSurface === null && outputViewer === null && copyPicker ? (_jsx(CopyPicker, { targets: copyPicker.targets, selected: selectedCopy })) : null, _jsxs(ComposerFrame, { working: state === 'thinking' || state === 'tool' || visibleActiveTools.length > 0, focus: phase === 'ready' &&
6595
+ cancelExits: !session?.hasProvider })) : (_jsxs(_Fragment, { children: [agentSurface === null && outputViewer === null ? (_jsx(LiveActivity, { compact: compactWork, activeTools: visibleActiveTools, working: state === 'thinking' || state === 'tool', interruptible: abortRef.current !== null, animate: stdout.isTTY === true && permission === null && textPrompt === null, thinking: thinking })) : null, agentSurface === null && outputViewer === null && permission === null ? (_jsx(TaskList, { tasks: liveTasks })) : null, permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls, review: permission.review, summary: permission.summary, detailsOpen: permissionDetailsOpen, reviewOffset: permissionReviewOffset, choice: permissionChoice, queuedCount: queuedPermissionCount, sourceLabel: permissionSourceLabel, columns: terminal.columns, rows: terminal.rows })) : null, textPrompt ? (_jsx(TextPrompt, { columns: Math.max(1, (terminal.columns ?? 80) - 2), title: textPrompt.title, placeholder: textPrompt.placeholder, initialValue: textPrompt.initialValue, emptyNotice: textPrompt.emptyNotice, hidden: permission !== null || (agentSurface !== null || outputViewer !== null), onSubmit: submitTextPrompt, onCancel: cancelTextPrompt }, textPrompt.token)) : permission === null && agentSurface === null && outputViewer === null && choicePicker ? (_jsx(ChoicePicker, { busy: 'busy' in choicePicker && choicePicker.busy === true, columns: Math.max(1, (terminal.columns ?? 80) - 2), title: choicePicker.title, notice: choicePicker.notice, options: filterChoiceOptions(choicePicker.options, choiceQuery), query: choicePickerSearchable(choicePicker) ? choiceQuery : undefined, selected: selectedChoice, windowSize: choicePicker.kind === 'command' ? choicePicker.windowSize : undefined })) : permission === null && agentSurface === null && outputViewer === null && copyPicker ? (_jsx(CopyPicker, { targets: copyPicker.targets, selected: selectedCopy })) : null, _jsxs(ComposerFrame, { working: state === 'thinking' || state === 'tool' || visibleActiveTools.length > 0, focus: phase === 'ready' &&
6494
6596
  state !== 'awaiting-permission' &&
6495
6597
  !compacting &&
6496
6598
  externalEditorRequest === null &&
@@ -6512,7 +6614,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6512
6614
  textPrompt === null &&
6513
6615
  choicePicker === null &&
6514
6616
  copyPicker === null &&
6515
- agentSurface === null && outputViewer === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: [queuePause ? '⏸' : '⏎', " ", queued.length, " message", queued.length > 1 ? 's' : '', " queued \u2014", ' ', queuePause
6617
+ agentSurface === null && outputViewer === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: [queuePause ? '‖' : '⏎', " ", queued.length, " message", queued.length > 1 ? 's' : '', " queued \u2014", ' ', queuePause
6516
6618
  ? queuePause.outcome === 'paused'
6517
6619
  ? 'held after a resumable turn paused; wait for recovery, change model, or send a message to release it'
6518
6620
  : `paused after a ${queuePause.outcome} turn; send a message or change model to continue`