@namzu/cli 28.0.0 → 28.1.1

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 (48) hide show
  1. package/CHANGELOG.md +32 -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/integrations/subagents/runtime.d.ts +9 -0
  5. package/dist/integrations/subagents/runtime.d.ts.map +1 -1
  6. package/dist/integrations/subagents/runtime.js +6 -0
  7. package/dist/integrations/subagents/runtime.js.map +1 -1
  8. package/dist/permissions/live-mode.d.ts +73 -0
  9. package/dist/permissions/live-mode.d.ts.map +1 -0
  10. package/dist/permissions/live-mode.js +62 -0
  11. package/dist/permissions/live-mode.js.map +1 -0
  12. package/dist/tui/App.d.ts.map +1 -1
  13. package/dist/tui/App.js +180 -74
  14. package/dist/tui/App.js.map +1 -1
  15. package/dist/tui/Checklist.d.ts +56 -0
  16. package/dist/tui/Checklist.d.ts.map +1 -0
  17. package/dist/tui/Checklist.js +90 -0
  18. package/dist/tui/Checklist.js.map +1 -0
  19. package/dist/tui/StatusBar.d.ts +10 -0
  20. package/dist/tui/StatusBar.d.ts.map +1 -1
  21. package/dist/tui/StatusBar.js +8 -1
  22. package/dist/tui/StatusBar.js.map +1 -1
  23. package/dist/tui/TaskList.d.ts +18 -25
  24. package/dist/tui/TaskList.d.ts.map +1 -1
  25. package/dist/tui/TaskList.js +34 -58
  26. package/dist/tui/TaskList.js.map +1 -1
  27. package/dist/tui/Transcript.d.ts.map +1 -1
  28. package/dist/tui/Transcript.js +8 -1
  29. package/dist/tui/Transcript.js.map +1 -1
  30. package/dist/tui/agent.d.ts +43 -3
  31. package/dist/tui/agent.d.ts.map +1 -1
  32. package/dist/tui/agent.js +76 -8
  33. package/dist/tui/agent.js.map +1 -1
  34. package/dist/tui/live-window.d.ts +22 -0
  35. package/dist/tui/live-window.d.ts.map +1 -1
  36. package/dist/tui/live-window.js +34 -0
  37. package/dist/tui/live-window.js.map +1 -1
  38. package/dist/tui/notices.d.ts +27 -0
  39. package/dist/tui/notices.d.ts.map +1 -0
  40. package/dist/tui/notices.js +27 -0
  41. package/dist/tui/notices.js.map +1 -0
  42. package/dist/tui/task-activity.d.ts +71 -0
  43. package/dist/tui/task-activity.d.ts.map +1 -0
  44. package/dist/tui/task-activity.js +157 -0
  45. package/dist/tui/task-activity.js.map +1 -0
  46. package/dist/tui/types.d.ts +13 -0
  47. package/dist/tui/types.d.ts.map +1 -1
  48. 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
@@ -2778,6 +2816,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2778
2816
  for await (const event of session.resumePaused({
2779
2817
  turnId: active.turnId,
2780
2818
  signal: ac.signal,
2819
+ // The operator's mode, read at every decision like a new turn's:
2820
+ // `/resume` in plan mode stays read-only, and so do its children.
2821
+ permissionMode: permissionModeRef.current,
2822
+ currentPermissionMode: () => permissionModeRef.current,
2781
2823
  })) {
2782
2824
  applyEventRef.current?.(event, st);
2783
2825
  }
@@ -3516,6 +3558,24 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3516
3558
  });
3517
3559
  sendTerminalNotification({ kind: 'approval-required' });
3518
3560
  }), [sendTerminalNotification, setChoicePicker, setSelectedChoice]);
3561
+ /**
3562
+ * Grow this turn's open task block, or open one. The whole plan rides along
3563
+ * so the block shows the checklist as it stood after the operation; a
3564
+ * `null` operation (a `task_list`) refreshes it without naming a change.
3565
+ */
3566
+ const writeTaskBlock = useCallback((st, operation, checklist) => {
3567
+ st.taskBlockKey ??= nextId();
3568
+ const key = st.taskBlockKey;
3569
+ const id = nextId();
3570
+ setMessages((prev) => applyTaskOperation(prev, {
3571
+ key,
3572
+ id,
3573
+ operation,
3574
+ checklist,
3575
+ settled: settledRef.current,
3576
+ glyphColor: theme.status.ok,
3577
+ }));
3578
+ }, [nextId]);
3519
3579
  // Render one agent event onto the transcript. Shared by the local turn loop
3520
3580
  // and the daemon-attach poller, so both paths produce identical output.
3521
3581
  // `st` carries the streaming-assistant bubble id + accumulated text across
@@ -3700,6 +3760,28 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3700
3760
  setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
3701
3761
  break;
3702
3762
  }
3763
+ if (isTaskTool(event.toolName)) {
3764
+ // The task event already wrote the block. A listing refreshes it
3765
+ // while there is a plan to show; with none it keeps its own row.
3766
+ if (!event.isError && event.toolName !== 'task_list') {
3767
+ setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
3768
+ break;
3769
+ }
3770
+ if (!event.isError && tasksRef.current.length > 0) {
3771
+ writeTaskBlock(st, null, tasksRef.current);
3772
+ setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
3773
+ break;
3774
+ }
3775
+ // Its own row, in words: the call's label, and the result's
3776
+ // label rather than the model's receipt, which names ids.
3777
+ pushMessage('tool', done?.label ?? formatToolCall(event.toolName, event.summary, true), false, event.isError ? '✗' : '✓', undefined, event.isError ? theme.status.error : theme.status.ok);
3778
+ const said = event.resultLabel ?? (event.isError ? 'the task tool refused the call' : '');
3779
+ if (said.length > 0) {
3780
+ pushMessage('tool', event.isError ? `failed: ${said}` : said, false, '⎿');
3781
+ }
3782
+ setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
3783
+ break;
3784
+ }
3703
3785
  const catalogue = !event.isError && event.toolName === 'agent_models' && event.output !== undefined
3704
3786
  ? modelCatalogueView(event.output) : undefined;
3705
3787
  if (catalogue !== undefined) {
@@ -3758,31 +3840,34 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3758
3840
  break;
3759
3841
  }
3760
3842
  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);
3843
+ // The transcript owns the checklist: consecutive operations fold
3844
+ // into one block there (task-activity.ts). The live row above the
3845
+ // composer only names the current step.
3768
3846
  const item = {
3769
3847
  id: event.taskId,
3770
3848
  subject: event.subject,
3771
3849
  status: event.status,
3772
3850
  };
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, '☒');
3851
+ // A reply in progress ends here, as it does at a tool call, so
3852
+ // the block lands after the text that led to it.
3853
+ closeAssistant();
3854
+ let operation;
3855
+ if (event.removed) {
3856
+ // Removed from the plan: it leaves the checklist, and the block
3857
+ // says so, rather than drawing it as still open.
3858
+ const next = removeTask(tasksRef.current, item.id, item.subject);
3859
+ operation = next.operation;
3860
+ tasksRef.current = next.tasks;
3782
3861
  }
3783
- else if (isNew) {
3784
- pushMessage('tool', event.subject, false, '☐');
3862
+ else {
3863
+ const previous = tasksRef.current.find((task) => task.id === item.id);
3864
+ operation = taskOperationFor(previous, item);
3865
+ tasksRef.current = upsertTask(tasksRef.current, item);
3785
3866
  }
3867
+ const checklist = tasksRef.current;
3868
+ setTasks(checklist);
3869
+ if (operation)
3870
+ writeTaskBlock(st, operation, checklist);
3786
3871
  break;
3787
3872
  }
3788
3873
  case 'job':
@@ -3852,7 +3937,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3852
3937
  st.outcome = 'stopped';
3853
3938
  st.queuePauseOutcome = 'paused';
3854
3939
  st.notification = { kind: 'turn-settled', outcome: 'stopped' };
3855
- pushMessage('system', describeTurnInterruption(event), false, '⏸');
3940
+ pushMessage('system', describeTurnInterruption(event), false, '‖');
3856
3941
  break;
3857
3942
  case 'error':
3858
3943
  closeAssistant();
@@ -3865,7 +3950,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3865
3950
  st.notification = null;
3866
3951
  break;
3867
3952
  }
3868
- }, [appendToMessage, finalizeMessage, flushStream, pushMessage]);
3953
+ }, [appendToMessage, finalizeMessage, flushStream, pushMessage, writeTaskBlock]);
3869
3954
  applyEventRef.current = applyEvent;
3870
3955
  const runTurn = useCallback(async (prompt) => {
3871
3956
  // A passive queue effect can still hold the previous render's session
@@ -3975,7 +4060,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3975
4060
  // ended: a finished list stays on screen until the operator moves on,
3976
4061
  // which is the moment it has told them everything it can.
3977
4062
  setTasks([]);
3978
- knownTaskIdsRef.current = new Set();
4063
+ tasksRef.current = [];
3979
4064
  // The model interleaves text → tool → text across iterations; `applyEvent`
3980
4065
  // renders each one in order.
3981
4066
  const st = {
@@ -4201,6 +4286,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4201
4286
  // prompt closes it. A paused turn is never closed this way.
4202
4287
  abandonInterrupted: true,
4203
4288
  permissionMode: turnPermissionMode,
4289
+ // Read at every decision: the operator may change the mode
4290
+ // while this turn runs, and the change governs what follows.
4291
+ currentPermissionMode: () => permissionModeRef.current,
4204
4292
  limits: turnLimits,
4205
4293
  ...(turnReasoningEffort !== undefined ? { effort: turnReasoningEffort } : {}),
4206
4294
  ...(turnOrchestrateMode ? { orchestrate: true } : {}),
@@ -5254,6 +5342,24 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5254
5342
  // painted as state of the one now on screen.
5255
5343
  if (conversationGenRef.current !== generation)
5256
5344
  return;
5345
+ const checklist = slash.name === 'tasks' && outcome?.kind === 'report'
5346
+ ? taskReportChecklist(outcome.rows)
5347
+ : undefined;
5348
+ if (checklist) {
5349
+ // Drawn by the same checklist as the transcript's task
5350
+ // blocks: the same marks, and no id or owner column.
5351
+ const id = nextId();
5352
+ setMessages((prev) => [
5353
+ ...prev,
5354
+ {
5355
+ id,
5356
+ role: 'system',
5357
+ content: checklist.length === 0 ? 'No tasks yet.' : checklistProgress(checklist),
5358
+ ...(checklist.length > 0 ? { checklist } : {}),
5359
+ },
5360
+ ]);
5361
+ return;
5362
+ }
5257
5363
  pushMessage('system', outcome
5258
5364
  ? renderOutcome(outcome)
5259
5365
  : `/${slash.name} is registered but this session cannot run it.`);
@@ -6490,7 +6596,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6490
6596
  // where it is decided rather than guessed on the screen: with no
6491
6597
  // session behind the picker, esc exits namzu and the footer says
6492
6598
  // 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' &&
6599
+ 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
6600
  state !== 'awaiting-permission' &&
6495
6601
  !compacting &&
6496
6602
  externalEditorRequest === null &&
@@ -6512,7 +6618,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6512
6618
  textPrompt === null &&
6513
6619
  choicePicker === null &&
6514
6620
  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
6621
+ 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
6622
  ? queuePause.outcome === 'paused'
6517
6623
  ? 'held after a resumable turn paused; wait for recovery, change model, or send a message to release it'
6518
6624
  : `paused after a ${queuePause.outcome} turn; send a message or change model to continue`