@namzu/cli 25.0.0 → 25.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 (63) hide show
  1. package/CHANGELOG.md +282 -0
  2. package/dist/context/doctrine.d.ts +4 -2
  3. package/dist/context/doctrine.d.ts.map +1 -1
  4. package/dist/context/doctrine.js +4 -2
  5. package/dist/context/doctrine.js.map +1 -1
  6. package/dist/context/runtime-message.d.ts.map +1 -1
  7. package/dist/context/runtime-message.js +4 -0
  8. package/dist/context/runtime-message.js.map +1 -1
  9. package/dist/integrations/mcp/servers.d.ts +30 -0
  10. package/dist/integrations/mcp/servers.d.ts.map +1 -1
  11. package/dist/integrations/mcp/servers.js +32 -1
  12. package/dist/integrations/mcp/servers.js.map +1 -1
  13. package/dist/integrations/subagents/activity.d.ts +285 -11
  14. package/dist/integrations/subagents/activity.d.ts.map +1 -1
  15. package/dist/integrations/subagents/activity.js +334 -31
  16. package/dist/integrations/subagents/activity.js.map +1 -1
  17. package/dist/integrations/subagents/replay.d.ts +46 -0
  18. package/dist/integrations/subagents/replay.d.ts.map +1 -0
  19. package/dist/integrations/subagents/replay.js +193 -0
  20. package/dist/integrations/subagents/replay.js.map +1 -0
  21. package/dist/integrations/subagents/runs.d.ts +72 -0
  22. package/dist/integrations/subagents/runs.d.ts.map +1 -0
  23. package/dist/integrations/subagents/runs.js +155 -0
  24. package/dist/integrations/subagents/runs.js.map +1 -0
  25. package/dist/integrations/subagents/runtime.d.ts +21 -0
  26. package/dist/integrations/subagents/runtime.d.ts.map +1 -1
  27. package/dist/integrations/subagents/runtime.js +121 -2
  28. package/dist/integrations/subagents/runtime.js.map +1 -1
  29. package/dist/tui/AgentExplorer.d.ts +46 -2
  30. package/dist/tui/AgentExplorer.d.ts.map +1 -1
  31. package/dist/tui/AgentExplorer.js +261 -22
  32. package/dist/tui/AgentExplorer.js.map +1 -1
  33. package/dist/tui/App.d.ts.map +1 -1
  34. package/dist/tui/App.js +458 -32
  35. package/dist/tui/App.js.map +1 -1
  36. package/dist/tui/ChoicePicker.d.ts +2 -0
  37. package/dist/tui/ChoicePicker.d.ts.map +1 -1
  38. package/dist/tui/ChoicePicker.js +3 -1
  39. package/dist/tui/ChoicePicker.js.map +1 -1
  40. package/dist/tui/Composer.d.ts +10 -7
  41. package/dist/tui/Composer.d.ts.map +1 -1
  42. package/dist/tui/Composer.js +2 -7
  43. package/dist/tui/Composer.js.map +1 -1
  44. package/dist/tui/StatusBar.d.ts +56 -10
  45. package/dist/tui/StatusBar.d.ts.map +1 -1
  46. package/dist/tui/StatusBar.js +106 -35
  47. package/dist/tui/StatusBar.js.map +1 -1
  48. package/dist/tui/agent.d.ts +38 -1
  49. package/dist/tui/agent.d.ts.map +1 -1
  50. package/dist/tui/agent.js +78 -10
  51. package/dist/tui/agent.js.map +1 -1
  52. package/dist/tui/choice-selection.d.ts +3 -1
  53. package/dist/tui/choice-selection.d.ts.map +1 -1
  54. package/dist/tui/choice-selection.js +2 -2
  55. package/dist/tui/choice-selection.js.map +1 -1
  56. package/dist/tui/permission-review.d.ts.map +1 -1
  57. package/dist/tui/permission-review.js +14 -2
  58. package/dist/tui/permission-review.js.map +1 -1
  59. package/dist/tui/slashCommands.d.ts +40 -0
  60. package/dist/tui/slashCommands.d.ts.map +1 -1
  61. package/dist/tui/slashCommands.js +62 -3
  62. package/dist/tui/slashCommands.js.map +1 -1
  63. package/package.json +3 -3
package/dist/tui/App.js CHANGED
@@ -29,6 +29,8 @@ import { terminalNotificationEnabled, writeTerminalNotification, } from '../inte
29
29
  import { beginCodexDeviceLogin, beginSubscriptionLogin, clearAllStoredCredentials, clearStoredCodexCredential, clearStoredSubscriptionCredential, credentialsPath, parsePastedInput, primaryProvider, readStoredCodexCredential, readStoredSubscriptionCredential, signedInSubscriptionProviders, writePreferences, } from '../integrations/providers/index.js';
30
30
  import { appendMessages, archiveConversation, forkConversation, forkConversationBeforeUser, listRecent, loadConversation, loadResumableConversation, openSessions, replaceConversation, requireWritableConversation, setTitle, startConversation, titleOf, } from '../integrations/sessions/store.js';
31
31
  import { conversationMarkdown, writeConversationExport, } from '../integrations/sessions/transcript-export.js';
32
+ import { liveOrchestrationRuns } from '../integrations/subagents/runs.js';
33
+ import { NARRATION_TOOL_NAME } from '../integrations/subagents/runtime.js';
32
34
  import { isTrusted, trustDir } from '../integrations/trust/store.js';
33
35
  import { checkUpdates } from '../integrations/updates.js';
34
36
  import { renderMemoryReport, renderMemorySaveResult } from '../memory/presentation.js';
@@ -36,7 +38,7 @@ import { appendMemoryWithStatus, readMemory } from '../memory/store.js';
36
38
  import { effectivePermissionMode, permissionModeDescription, permissionModeLabel, } from '../permissions/mode.js';
37
39
  import { composeSkillsPrompt, discoverSkills, loadSkillBody } from '../skills/store.js';
38
40
  import { discoverUserCommands } from '../user-commands/store.js';
39
- import { AgentCockpit, AgentTaskPanel, AgentTranscript, activeSubagentCohorts, agentCockpitIsWide, agentPhasePageSize, agentPhases, agentPickerPageSize, agentTranscriptPageSize, agentWorkflowPageSize, agentWorkflows, maxAgentTranscriptTailOffset, } from './AgentExplorer.js';
41
+ import { AgentCockpit, AgentNarrationBand, AgentTaskPanel, AgentTranscript, activeSubagentCohorts, agentCockpitIsWide, agentPhasePageSize, agentPhases, agentPickerPageSize, agentTranscriptPageSize, agentWorkflowPageSize, agentWorkflows, maxAgentTranscriptTailOffset, } from './AgentExplorer.js';
40
42
  import { BrandHeader } from './BrandHeader.js';
41
43
  import { ChoicePicker } from './ChoicePicker.js';
42
44
  import { emptyPluginReport, pluginDetails, pluginOption, pluginStartupState } from './plugin-view.js';
@@ -76,7 +78,7 @@ import { buildPermissionReview, buildPermissionSummary, permissionReviewPageRows
76
78
  import { describeRunInterruption, describeRunStop } from './run-interruption.js';
77
79
  import { moveSelection } from './selection-window.js';
78
80
  import { describeShellEscape, describeShellEscapeForModel, runShellEscape, shellEscapeCommand, } from './shell-escape.js';
79
- import { baseBranchReviewPrompt, commitReviewPrompt, hostCommandNames, kernelCommandDescriptors, mergeHostCommands, renderOutcome, reviewPrompt, runSlash, } from './slashCommands.js';
81
+ import { baseBranchReviewPrompt, combineOrchestrationRuns, commitReviewPrompt, hostCommandNames, kernelCommandDescriptors, mergeHostCommands, renderAgentRuns, renderOutcome, reviewPrompt, runSlash, } from './slashCommands.js';
80
82
  import { splitCompleteBlocks, splitSafeCut } from './stream-blocks.js';
81
83
  import { terminalSupportsHyperlinks } from './terminal-hyperlinks.js';
82
84
  import { theme } from './theme.js';
@@ -387,6 +389,15 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
387
389
  reasoningEffortRef.current = next;
388
390
  setReasoningEffortState(next);
389
391
  }, []);
392
+ // A session setting layered above effort, not a level a provider publishes
393
+ // — see docs/cli/slash-commands.md's /effort section. In-memory and
394
+ // per-session like reasoningEffort above; no preferences file involved.
395
+ const [orchestrateMode, setOrchestrateModeState] = useState(false);
396
+ const orchestrateModeRef = useRef(false);
397
+ const setOrchestrateMode = useCallback((next) => {
398
+ orchestrateModeRef.current = next;
399
+ setOrchestrateModeState(next);
400
+ }, []);
390
401
  const [activeSkills, setActiveSkills] = useState([]);
391
402
  // Read when the session comes up rather than on every keystroke: the
392
403
  // autocomplete dropdown consults this on each character, and a `readdirSync`
@@ -405,10 +416,16 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
405
416
  summaries: 0,
406
417
  reclaimedTokens: 0,
407
418
  });
408
- // Jobs that ended while no turn was running. The kernel's notice needs a
409
- // tool result to ride on, so between turns the exit is held here and
410
- // handed to the next send as part of the system text; the transcript
411
- // row is written at once.
419
+ // Jobs that ended while no turn was running.
420
+ //
421
+ // Only those: an exit that lands mid-turn belongs to the kernel, which
422
+ // rides it out on the next tool result or, for a job the model awaited,
423
+ // delivers it as the message that releases its own suspend. Queueing one
424
+ // here as well would announce the same exit twice, so the `abortRef`
425
+ // check below is the boundary between the two owners rather than an
426
+ // optimisation. Between turns the kernel is not listening, so the exit is
427
+ // held here and handed to the next send as part of the system text; the
428
+ // transcript row is written at once.
412
429
  const idleJobNoticesRef = useRef([]);
413
430
  const drainIdleJobNotices = () => {
414
431
  if (idleJobNoticesRef.current.length === 0)
@@ -580,6 +597,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
580
597
  /** Bounded child-run projection published by the current AgentSession. */
581
598
  const [subagents, setSubagentsState] = useState([]);
582
599
  const subagentsRef = useRef([]);
600
+ /**
601
+ * The parent's own commentary on the work it is coordinating, from the same
602
+ * monitor and the same subscription as the rows below it. Read through the
603
+ * optional reader, so a source that publishes no narration is simply a
604
+ * session with none.
605
+ */
606
+ const [narration, setNarration] = useState([]);
583
607
  const [agentSurface, setAgentSurfaceState] = useState(null);
584
608
  const agentSurfaceRef = useRef(null);
585
609
  const agentSurfaceCommittedRef = useRef(null);
@@ -598,7 +622,30 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
598
622
  useEffect(() => {
599
623
  agentSurfaceCommittedRef.current = agentSurface;
600
624
  }, [agentSurface]);
601
- const replaceSubagents = useCallback((next) => {
625
+ /**
626
+ * Children of this conversation's earlier runs, rebuilt from saved
627
+ * evidence, kept apart from the live monitor's own snapshot.
628
+ *
629
+ * A ref and not state because it is an input to `replaceSubagents`, which
630
+ * runs from the monitor's subscription: holding it as state would make
631
+ * every saved row arrive one render after the live rows it belongs beside.
632
+ */
633
+ const savedSubagentsRef = useRef([]);
634
+ /** Revokes an in-flight evidence read whose session or conversation is gone. */
635
+ const savedSubagentsTokenRef = useRef(0);
636
+ const withSavedSubagents = useCallback((live) => {
637
+ const saved = savedSubagentsRef.current;
638
+ if (saved.length === 0)
639
+ return live;
640
+ // A child the live monitor still holds wins over its own saved copy:
641
+ // both describe one run, and only one of the two is attached to
642
+ // anything. Saved rows land after the live ones, so a cohort that is
643
+ // still running keeps the top of the list.
644
+ const liveRunIds = new Set(live.map((agent) => agent.runId).filter((id) => id !== undefined));
645
+ return [...live, ...saved.filter((agent) => !agent.runId || !liveRunIds.has(agent.runId))];
646
+ }, []);
647
+ const replaceSubagents = useCallback((incoming) => {
648
+ const next = withSavedSubagents(incoming);
602
649
  const previous = subagentsRef.current;
603
650
  subagentsRef.current = next;
604
651
  setSubagentsState(next);
@@ -626,8 +673,40 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
626
673
  selectedId: fallbackAgent.viewId,
627
674
  focus: surface.kind === 'cockpit' ? surface.focus : surface.returnFocus,
628
675
  });
629
- }, [setAgentSurface]);
630
- const openAgentCockpit = useCallback(() => {
676
+ }, [setAgentSurface, withSavedSubagents]);
677
+ /**
678
+ * Re-reads what this conversation's finished children left on disk.
679
+ *
680
+ * Called when a session is installed, whenever the conversation the scope
681
+ * names changes — a resume is the whole point of this: the children worth
682
+ * opening belong to the runs of the conversation just switched TO — and
683
+ * whenever the cockpit is opened, which is what recovers a child the live
684
+ * monitor's eighty-agent bound has since evicted. All three are reads of
685
+ * directories that are already there; nothing is created, and a failure
686
+ * leaves the live rows untouched.
687
+ */
688
+ const hydrateSavedChildren = useCallback(async () => {
689
+ const read = session?.savedChildren;
690
+ if (!read)
691
+ return;
692
+ const token = ++savedSubagentsTokenRef.current;
693
+ let saved;
694
+ try {
695
+ saved = await read();
696
+ }
697
+ catch {
698
+ // Saved evidence is an extra view over a directory that may not exist
699
+ // yet, or may not be readable. Not finding it is not a reason to
700
+ // disturb a session whose live children are fine.
701
+ return;
702
+ }
703
+ if (token !== savedSubagentsTokenRef.current)
704
+ return;
705
+ savedSubagentsRef.current = saved;
706
+ replaceSubagents(session?.subagents?.getSnapshot() ?? []);
707
+ }, [replaceSubagents, session]);
708
+ /** Opens the cockpit on the rows already on screen; false when there are none. */
709
+ const openCockpitFromCurrentRows = useCallback(() => {
631
710
  const agents = subagentsRef.current;
632
711
  const workflows = agentWorkflows(agents);
633
712
  const currentWorkflow = [...workflows].reverse().find((workflow) => workflow.agents.some((agent) => agent.status === 'starting' || agent.status === 'queued' || agent.status === 'working')) ?? workflows.at(-1);
@@ -643,6 +722,33 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
643
722
  });
644
723
  return true;
645
724
  }, [setAgentSurface]);
725
+ /** The most recent cockpit request, so an older one cannot report on its behalf. */
726
+ const cockpitOpenTokenRef = useRef(0);
727
+ /**
728
+ * Ctrl+T and `/agents`: open the cockpit, and when nothing is on screen
729
+ * yet, WAIT for the saved evidence before answering.
730
+ *
731
+ * The waiting is the point. A conversation's children may exist only on
732
+ * disk — evicted by the eighty-agent bound, or written by a process that
733
+ * has since exited — and that read is asynchronous. Answering from the
734
+ * rows already up would make the first press after a resume report an
735
+ * absence that the read, still in flight, was about to disprove. Rows that
736
+ * ARE up open immediately and refresh the saved ones behind the screen, so
737
+ * the common case pays nothing for this.
738
+ */
739
+ const openAgentCockpit = useCallback(async () => {
740
+ if (openCockpitFromCurrentRows()) {
741
+ void hydrateSavedChildren();
742
+ return true;
743
+ }
744
+ const token = ++cockpitOpenTokenRef.current;
745
+ await hydrateSavedChildren();
746
+ // A later request owns the screen now. Reporting `false` here would put
747
+ // an empty-state line under a cockpit that request is about to open.
748
+ if (token !== cockpitOpenTokenRef.current)
749
+ return true;
750
+ return openCockpitFromCurrentRows();
751
+ }, [hydrateSavedChildren, openCockpitFromCurrentRows]);
646
752
  /** One git-backed review choice may be resolving while its visible picker remains authoritative. */
647
753
  const reviewChoiceInFlightRef = useRef(null);
648
754
  const exitArmedRef = useRef(false);
@@ -697,17 +803,32 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
697
803
  useEffect(() => {
698
804
  const source = session?.subagents;
699
805
  setAgentSurface(null);
806
+ savedSubagentsRef.current = [];
807
+ void hydrateSavedChildren();
700
808
  if (!source) {
701
809
  replaceSubagents([]);
810
+ setNarration([]);
702
811
  return;
703
812
  }
704
- const refresh = () => replaceSubagents(source.getSnapshot());
813
+ const refresh = () => {
814
+ replaceSubagents(source.getSnapshot());
815
+ setNarration(source.getNarration?.() ?? []);
816
+ };
705
817
  refresh();
706
818
  return source.subscribe(refresh);
707
- }, [replaceSubagents, session, setAgentSurface]);
819
+ }, [hydrateSavedChildren, replaceSubagents, session, setAgentSurface]);
708
820
  const resetSubagentActivity = useCallback(() => {
709
821
  session?.subagents?.reset();
710
822
  setAgentSurface(null);
823
+ // The monitor clears its own commentary on reset; this drops the copy
824
+ // already on screen in the same pass as the rows, so the band cannot
825
+ // outlive the conversation it was written in.
826
+ setNarration([]);
827
+ // A new conversation starts with no children on screen, saved ones
828
+ // included: the evidence stays on disk, but it belongs to the runs of
829
+ // the conversation that was just left behind.
830
+ savedSubagentsTokenRef.current += 1;
831
+ savedSubagentsRef.current = [];
711
832
  replaceSubagents([]);
712
833
  }, [replaceSubagents, session, setAgentSurface]);
713
834
  // Source of truth for in-flight tools (the event loop runs across renders, so
@@ -996,6 +1117,92 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
996
1117
  ]);
997
1118
  return id;
998
1119
  }, [nextId]);
1120
+ /**
1121
+ * `/agents runs`: opens a picker immediately with a disabled loading row,
1122
+ * then replaces it once the live snapshot and the disk walk both resolve.
1123
+ *
1124
+ * The composer stays reachable the whole time — nothing here awaits before
1125
+ * the first `setChoicePicker`. A stale read is dropped by comparing
1126
+ * `choicePickerRef.current` against the exact loading-picker object this
1127
+ * call put there (the idiom the `/review` pickers already use below),
1128
+ * rather than a counter: Escape and every other picker-replacing path
1129
+ * already route through `setChoicePicker`, so they retire this object as a
1130
+ * side effect and need no separate token bump of their own. That covers
1131
+ * both "the operator left this picker" (any other object is current now,
1132
+ * `null` included) and "opened it again" (a second call's own object is
1133
+ * current instead).
1134
+ */
1135
+ const openAgentRunsPicker = useCallback(() => {
1136
+ setSelectedChoice(0);
1137
+ const pending = {
1138
+ kind: 'agent-runs',
1139
+ title: 'Orchestration runs',
1140
+ values: ['loading'],
1141
+ options: [
1142
+ { label: 'Loading…', description: 'Reading saved evidence…', disabledReason: 'Loading…' },
1143
+ ],
1144
+ };
1145
+ setChoicePicker(pending);
1146
+ void (async () => {
1147
+ const live = liveOrchestrationRuns(subagentsRef.current, Date.now());
1148
+ let finished = [];
1149
+ try {
1150
+ finished = (await session?.listOrchestrationRuns?.()) ?? [];
1151
+ }
1152
+ catch {
1153
+ // Disk evidence is optional here: a live-only listing is still an
1154
+ // honest answer, and the empty-history message below still applies
1155
+ // if that live half turns out empty too.
1156
+ }
1157
+ if (appLifetime.signal.aborted || choicePickerRef.current !== pending)
1158
+ return;
1159
+ const listing = combineOrchestrationRuns(live, finished);
1160
+ const empty = renderAgentRuns(listing);
1161
+ if (empty) {
1162
+ setChoicePicker(null);
1163
+ pushMessage('system', empty);
1164
+ return;
1165
+ }
1166
+ setChoicePicker({
1167
+ kind: 'agent-runs',
1168
+ title: 'Orchestration runs',
1169
+ notice: listing.omitted > 0
1170
+ ? `${listing.omitted} older run${listing.omitted === 1 ? '' : 's'} not shown.`
1171
+ : undefined,
1172
+ values: listing.runs,
1173
+ options: listing.runs.map(agentRunOption),
1174
+ });
1175
+ })();
1176
+ }, [appLifetime, pushMessage, session, setChoicePicker, setSelectedChoice]);
1177
+ /**
1178
+ * Opens an `/agents runs` selection straight into its first agent's
1179
+ * transcript — where the replayed banner actually lives, for a finished
1180
+ * run — rather than the phase/agent list Ctrl+T lands on.
1181
+ *
1182
+ * A finished run's evidence is hydrated first, since a run this operator
1183
+ * has not opened the cockpit for yet may not be in `subagents` at all.
1184
+ * A live run skips that: it is already there, and re-reading disk for a
1185
+ * run this process is still writing would risk the very half-written
1186
+ * evidence `listSavedOrchestrationRuns` exists to stay off of.
1187
+ */
1188
+ const openOrchestrationRun = useCallback(async (run) => {
1189
+ if (!run.live)
1190
+ await hydrateSavedChildren();
1191
+ const workflow = agentWorkflows(subagentsRef.current).find((candidate) => candidate.agents.some((agent) => agent.workflowId === run.id));
1192
+ const firstPhase = workflow?.phases[0];
1193
+ const firstAgent = firstPhase?.agents[0];
1194
+ if (!firstPhase || !firstAgent) {
1195
+ pushMessage('system', `Could not open "${run.name}" — its saved evidence is no longer available.`);
1196
+ return;
1197
+ }
1198
+ setAgentSurface({
1199
+ kind: 'transcript',
1200
+ selectedPhaseId: firstPhase.id,
1201
+ selectedId: firstAgent.viewId,
1202
+ returnFocus: 'agents',
1203
+ tailOffset: 0,
1204
+ });
1205
+ }, [hydrateSavedChildren, pushMessage, setAgentSurface]);
999
1206
  const reportedAgentsRef = useRef(new Set());
1000
1207
  useEffect(() => {
1001
1208
  reportedAgentsRef.current.clear();
@@ -1019,6 +1226,38 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1019
1226
  pushMessage('tool', `${agent.description} · ${status}`, false, completed ? '✓' : '✗', undefined, completed ? theme.status.ok : theme.status.error, 'ctrl+t · agent details');
1020
1227
  }
1021
1228
  }, [subagents, pushMessage, session]);
1229
+ /**
1230
+ * A delivered `send_message` correction already lands its own row inside
1231
+ * the child's transcript (`SubagentActivityMonitor.recordMessage`); this
1232
+ * mirrors it into the main conversation so the operator sees what was
1233
+ * sent without drilling in. Deliberately a separate ref from
1234
+ * `reportedAgentsRef` above: that gate is exactly-once per terminal
1235
+ * agent, this one is exactly-once per delivered message, and folding the
1236
+ * two together would let one gate's reset silently double-report the
1237
+ * other's rows.
1238
+ */
1239
+ const reportedCorrectionsRef = useRef(new Set());
1240
+ useEffect(() => {
1241
+ reportedCorrectionsRef.current.clear();
1242
+ }, [session]);
1243
+ useEffect(() => {
1244
+ const current = session?.subagents?.getSnapshot() ?? [];
1245
+ for (const agent of subagents) {
1246
+ const live = current.find((item) => item.viewId === agent.viewId && item.workflowId === agent.workflowId);
1247
+ if (!live)
1248
+ continue;
1249
+ for (const row of agent.transcript) {
1250
+ if (row.kind !== 'system' || row.direction !== 'to-child')
1251
+ continue;
1252
+ if (!live.transcript.some((item) => item.id === row.id))
1253
+ continue;
1254
+ if (reportedCorrectionsRef.current.has(row.id))
1255
+ continue;
1256
+ reportedCorrectionsRef.current.add(row.id);
1257
+ pushMessage('tool', `${agent.description} · correction sent`, false, '→', [row.text]);
1258
+ }
1259
+ }
1260
+ }, [subagents, pushMessage, session]);
1022
1261
  const applyPermissionMode = useCallback((mode) => {
1023
1262
  if (!session?.hasProvider) {
1024
1263
  pushMessage('system', 'Choose a model before changing permissions.');
@@ -1073,6 +1312,47 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1073
1312
  setReasoningEffort(effort);
1074
1313
  pushMessage('system', `Reasoning: ${effort} (this session).`);
1075
1314
  }, [hasUnsettledTurn, pushMessage, session, setReasoningEffort, state]);
1315
+ /**
1316
+ * Turn the session's orchestrate mode on or off.
1317
+ *
1318
+ * On: pins effort to the model's highest published level (last entry of
1319
+ * `reasoningEffortLevels`, which every provider publishes low-to-high) and
1320
+ * strengthens delegation guidance for future turns via `SendOptions.orchestrate`
1321
+ * — see `applyReasoningEffort` above for the mirrored guard shape. When the
1322
+ * model publishes no exact menu, effort is left alone and the notice says
1323
+ * so explicitly rather than silently doing nothing.
1324
+ */
1325
+ const applyOrchestrateMode = useCallback((enabled, selectedSession) => {
1326
+ if (!session?.hasProvider) {
1327
+ pushMessage('system', 'No active session — pick a provider before turning orchestrate mode on.');
1328
+ return;
1329
+ }
1330
+ if (selectedSession !== undefined && selectedSession !== session) {
1331
+ pushMessage('system', 'Orchestrate mode was not changed: the selected model is no longer active.');
1332
+ return;
1333
+ }
1334
+ if (state !== 'idle' ||
1335
+ abortRef.current !== null ||
1336
+ hasUnsettledTurn() ||
1337
+ (queuedRef.current.length > 0 && selectedSession !== session) ||
1338
+ permissionResolveRef.current !== null ||
1339
+ compactingRef.current) {
1340
+ pushMessage('system', 'Orchestrate mode was not changed: wait for the active turn, prompt, compaction, and queued work to settle.');
1341
+ return;
1342
+ }
1343
+ setOrchestrateMode(enabled);
1344
+ if (!enabled) {
1345
+ pushMessage('system', 'Orchestrate mode is off.');
1346
+ return;
1347
+ }
1348
+ const highest = highestReasoningEffort(session.reasoningEffortLevels);
1349
+ if (highest !== undefined) {
1350
+ setReasoningEffort(highest);
1351
+ pushMessage('system', `Orchestrate mode is on — effort pinned to ${highest} for ${session.modelSummary ?? 'this model'}, and delegation guidance is strengthened for this session.`);
1352
+ return;
1353
+ }
1354
+ pushMessage('system', `Orchestrate mode is on — ${session.modelSummary ?? 'this model'} does not publish an exact effort menu, so effort was left as is. Delegation guidance is still strengthened for this session.`);
1355
+ }, [hasUnsettledTurn, pushMessage, session, setOrchestrateMode, setReasoningEffort, state]);
1076
1356
  const stepReasoningEffort = useCallback((direction) => {
1077
1357
  const levels = session?.reasoningEffortLevels;
1078
1358
  if (!session?.hasProvider || levels === undefined || levels.length === 0) {
@@ -1577,6 +1857,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1577
1857
  setChoicePicker({ ...child, back: picker });
1578
1858
  return;
1579
1859
  }
1860
+ if (picker.kind === 'agent-runs') {
1861
+ if (value === 'loading')
1862
+ return;
1863
+ void openOrchestrationRun(value);
1864
+ return;
1865
+ }
1580
1866
  if (picker.kind === 'archive-conversation') {
1581
1867
  if (value === 'archive')
1582
1868
  archiveCurrentConversation();
@@ -1630,16 +1916,22 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1630
1916
  removeStoredCredential(value);
1631
1917
  return;
1632
1918
  }
1919
+ if (picker.kind === 'reasoning-effort' && value === ORCHESTRATE_MODE_VALUE) {
1920
+ applyOrchestrateMode(!orchestrateModeRef.current, picker.selectedSession);
1921
+ return;
1922
+ }
1633
1923
  applyReasoningEffort(value, picker.selectedSession);
1634
1924
  }, [
1635
1925
  activateSkill,
1636
1926
  advanceQueueContinuation,
1637
1927
  appLifetime.signal,
1928
+ applyOrchestrateMode,
1638
1929
  applyPermissionMode,
1639
1930
  applyReasoningEffort,
1640
1931
  archiveCurrentConversation,
1641
1932
  ctx.cwd,
1642
1933
  enqueueQueued,
1934
+ openOrchestrationRun,
1643
1935
  pushMessage,
1644
1936
  recordFeedback,
1645
1937
  removeStoredCredential,
@@ -1899,9 +2191,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1899
2191
  // A picker-owned provider/model change is one state transition. Clear the
1900
2192
  // old model's effort selection before publishing the replacement session
1901
2193
  // or releasing any paused queue. Failed and superseded candidates returned
1902
- // above, so they leave the current session selection untouched.
1903
- if (signal !== undefined)
1904
- setReasoningEffort(undefined);
2194
+ // above, so they leave the current session selection untouched. Orchestrate
2195
+ // mode survives the switch: re-pin to the new model's highest published
2196
+ // level instead of clearing, exactly as it pinned when first turned on.
2197
+ if (signal !== undefined) {
2198
+ setReasoningEffort(orchestrateModeRef.current ? highestReasoningEffort(s.reasoningEffortLevels) : undefined);
2199
+ }
1905
2200
  // Re-hydration (a provider switch via /model) builds a second session;
1906
2201
  // without this the first one's tool-server child processes stay alive
1907
2202
  // for the rest of the TUI's life.
@@ -1909,7 +2204,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1909
2204
  void previousSessionRef.current?.close();
1910
2205
  previousSessionRef.current = s;
1911
2206
  setSession(s);
1912
- if (options.chooseReasoningEffort && s.reasoningEffortLevels?.length) {
2207
+ // Orchestrate mode already re-pinned effort above; reopening this picker
2208
+ // would ask the operator to redo a choice the mode just made for them.
2209
+ if (!orchestrateModeRef.current && options.chooseReasoningEffort && s.reasoningEffortLevels?.length) {
1913
2210
  // Own input before publishing ready or releasing a paused queue. The
1914
2211
  // menu closes only after choosing an effort or keeping the new default.
1915
2212
  setSelectedChoice(0);
@@ -2597,6 +2894,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2597
2894
  : null;
2598
2895
  pendingGoalResumeRef.current = conv.id;
2599
2896
  scope.sessionId = conv.id; // new turns now attribute to the resumed session
2897
+ // The scope now names the resumed conversation, so its finished
2898
+ // children are readable. Without this the first Ctrl+T after a resume
2899
+ // would have nothing to find.
2900
+ void hydrateSavedChildren();
2600
2901
  conversationMaterializedRef.current = true;
2601
2902
  pushMessage('system', `Resumed: ${conv.title}`);
2602
2903
  if (discardedQueued > 0) {
@@ -2617,6 +2918,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2617
2918
  nextId,
2618
2919
  pushMessage,
2619
2920
  cancelPendingModelSwitch,
2921
+ hydrateSavedChildren,
2620
2922
  resetSubagentActivity,
2621
2923
  session,
2622
2924
  resetTranscript,
@@ -2668,6 +2970,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2668
2970
  if (sourceScope && targetSessionId) {
2669
2971
  sourceScope.sessionId = targetSessionId;
2670
2972
  conversationMaterializedRef.current = true;
2973
+ // A fresh conversation owns no runs yet, so this normally reads
2974
+ // nothing. It runs anyway because the rule is "the scope changed,
2975
+ // re-read", and an exception here is how the resume path lost
2976
+ // its own re-read.
2977
+ void hydrateSavedChildren();
2671
2978
  }
2672
2979
  if (clearScreen) {
2673
2980
  setMessages([]);
@@ -2700,6 +3007,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2700
3007
  interruptTurn,
2701
3008
  pushMessage,
2702
3009
  cancelPendingModelSwitch,
3010
+ hydrateSavedChildren,
2703
3011
  resetSubagentActivity,
2704
3012
  session,
2705
3013
  resetTranscript,
@@ -2852,6 +3160,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2852
3160
  // is reloaded or reset. Only where the NEXT turn is written changes.
2853
3161
  scope.sessionId = forked.id;
2854
3162
  resetSubagentActivity();
3163
+ void hydrateSavedChildren();
2855
3164
  session?.resetTaskStore?.();
2856
3165
  goalActivation.clear();
2857
3166
  wakeGoalDriver();
@@ -2869,6 +3178,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2869
3178
  hasUnsettledTurn,
2870
3179
  materializeConversation,
2871
3180
  pushMessage,
3181
+ hydrateSavedChildren,
2872
3182
  resetSubagentActivity,
2873
3183
  session,
2874
3184
  wakeGoalDriver,
@@ -2962,6 +3272,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2962
3272
  goalActivation.clear();
2963
3273
  wakeGoalDriver();
2964
3274
  scope.sessionId = forked.id;
3275
+ void hydrateSavedChildren();
2965
3276
  modelHistoryRef.current = forked.messages;
2966
3277
  lastAssistantMessage.current = null;
2967
3278
  const persistedOutput = latestAssistantOutput(forked.messages);
@@ -2999,6 +3310,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2999
3310
  nextId,
3000
3311
  pushMessage,
3001
3312
  cancelPendingModelSwitch,
3313
+ hydrateSavedChildren,
3002
3314
  resetSubagentActivity,
3003
3315
  session,
3004
3316
  resetTranscript,
@@ -3223,6 +3535,17 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3223
3535
  event.toolName.toLowerCase() === 'agent' &&
3224
3536
  subagentsRef.current.some((agent) => agent.toolUseId === event.toolUseId &&
3225
3537
  (event.runId === undefined || agent.workflowId === event.runId));
3538
+ // The band above the rail already carries a narrated line, in the
3539
+ // parent's own words, from the moment it is written. Printing the
3540
+ // call that wrote it would show the same sentence twice, the
3541
+ // second time as protocol — the same duplicate-protocol case as
3542
+ // the represented Agent call just below, and refused the same
3543
+ // way. A REFUSED line keeps its row: nothing reached the screen,
3544
+ // and that row is the only thing that says so.
3545
+ if (!event.isError && event.toolName === NARRATION_TOOL_NAME) {
3546
+ setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
3547
+ break;
3548
+ }
3226
3549
  if (representedSuccessfulAgent) {
3227
3550
  // The automatic panel already owned this successful child from
3228
3551
  // start through settlement. Re-emitting its internal Agent call as
@@ -3678,6 +4001,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3678
4001
  activeTurnInboxRef.current = inbox;
3679
4002
  const turnPermissionMode = permissionModeRef.current;
3680
4003
  const turnReasoningEffort = reasoningEffortRef.current;
4004
+ const turnOrchestrateMode = orchestrateModeRef.current;
3681
4005
  const turnRunLimits = resolveRunGuards(ctxRef.current.limits, runLimitsOverrideRef.current);
3682
4006
  // Always carry the guarded callback. `auto` and `strict` decide before
3683
4007
  // calling it in makeResumeHandler; retaining it is what lets a session
@@ -3738,6 +4062,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3738
4062
  permissionMode: turnPermissionMode,
3739
4063
  limits: turnRunLimits,
3740
4064
  ...(turnReasoningEffort !== undefined ? { effort: turnReasoningEffort } : {}),
4065
+ ...(turnOrchestrateMode ? { orchestrate: true } : {}),
3741
4066
  ...(goalRound ? { goalRound } : {}),
3742
4067
  // The mode above decides whether this callback is consulted.
3743
4068
  onPermission: askPermission,
@@ -4218,9 +4543,20 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4218
4543
  return;
4219
4544
  }
4220
4545
  case 'agent-cockpit': {
4221
- if (!openAgentCockpit()) {
4222
- pushMessage('system', `No delegated agents in this conversation.${session?.agentIds.length ? `\nAvailable: ${session.agentIds.join(', ')}` : '\nNo agents are configured.'}`);
4223
- }
4546
+ // Reported after the read, not before it: the answer to "are
4547
+ // there any" may be on disk, and a conversation whose evidence
4548
+ // is sitting in its own directory must never be told it
4549
+ // delegated nothing.
4550
+ const agentIds = session?.agentIds ?? [];
4551
+ void openAgentCockpit().then((opened) => {
4552
+ if (opened)
4553
+ return;
4554
+ pushMessage('system', `No delegated agents in this conversation.${agentIds.length ? `\nAvailable: ${agentIds.join(', ')}` : '\nNo agents are configured.'}`);
4555
+ });
4556
+ return;
4557
+ }
4558
+ case 'agent-runs': {
4559
+ openAgentRunsPicker();
4224
4560
  return;
4225
4561
  }
4226
4562
  case 'provider-setup':
@@ -4557,7 +4893,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4557
4893
  const values = [undefined, ...levels];
4558
4894
  const current = reasoningEffortRef.current;
4559
4895
  setSelectedChoice(Math.max(0, values.findIndex((value) => value === current)));
4560
- setChoicePicker(reasoningEffortPicker(session, current) ?? null);
4896
+ setChoicePicker(reasoningEffortPicker(session, current, false, orchestrateModeRef.current) ?? null);
4561
4897
  return;
4562
4898
  }
4563
4899
  case 'login':
@@ -4975,6 +5311,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4975
5311
  : 'Raw output mode off — rich transcript rendering restored.');
4976
5312
  return;
4977
5313
  }
5314
+ case 'orchestrate-mode': {
5315
+ const enabled = slash.enabled === 'toggle' ? !orchestrateModeRef.current : slash.enabled;
5316
+ applyOrchestrateMode(enabled);
5317
+ return;
5318
+ }
4978
5319
  case 'export-picker': {
4979
5320
  if (!stableExportSource())
4980
5321
  return;
@@ -5057,6 +5398,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5057
5398
  hostCommands,
5058
5399
  nextId,
5059
5400
  openAgentCockpit,
5401
+ openAgentRunsPicker,
5060
5402
  pushMessage,
5061
5403
  rawOutput,
5062
5404
  removeStoredCredential,
@@ -5557,8 +5899,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5557
5899
  setAgentSurface(null);
5558
5900
  return;
5559
5901
  }
5560
- if (openAgentCockpit())
5902
+ if (openCockpitFromCurrentRows()) {
5903
+ void hydrateSavedChildren();
5561
5904
  return;
5905
+ }
5906
+ // Nothing on screen yet. The conversation's children may exist only
5907
+ // as saved evidence, so the request outlives that read rather than
5908
+ // being answered by an empty snapshot.
5909
+ void openAgentCockpit();
5562
5910
  }
5563
5911
  // Child observation owns navigation, but never outranks consent: the
5564
5912
  // permission branch above preempts it if a child or parent call asks.
@@ -5696,7 +6044,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5696
6044
  return;
5697
6045
  }
5698
6046
  if (key.home || key.end || key.pageUp || key.pageDown || key.upArrow || key.downArrow) {
5699
- const page = agentTranscriptPageSize(terminal.rows);
6047
+ // Reserving the replayed banner's row here too keeps one PgDn a
6048
+ // screenful of what is actually visible.
6049
+ const page = agentTranscriptPageSize(terminal.rows, selected.replayed ? 1 : 0);
5700
6050
  const max = maxAgentTranscriptTailOffset(selected, terminal.rows, terminal.columns);
5701
6051
  const offset = Math.min(agentView.tailOffset, max);
5702
6052
  const next = key.home
@@ -5970,6 +6320,17 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5970
6320
  ? permissionOwner ? `Agent: ${permissionOwner.description}` : `Run: ${permission.runId}`
5971
6321
  : undefined;
5972
6322
  const displayedPermissionMode = effectivePermissionMode(permissionMode, session?.approvalLatched() ?? false);
6323
+ // True exactly when the phase ternary below renders its final branch — the
6324
+ // normal composer view, as opposed to provider setup or a lifecycle
6325
+ // picker/prompt. The agent rail is a feature of that view: computing this
6326
+ // once here lets its render site sit after the footer, below, while still
6327
+ // never appearing during trust/resume/edit/picker/setup phases.
6328
+ const showComposerSurface = !providerSetup &&
6329
+ phase !== 'trust' &&
6330
+ phase !== 'unhealthy' &&
6331
+ phase !== 'resume' &&
6332
+ phase !== 'edit' &&
6333
+ phase !== 'picker';
5973
6334
  return (_jsx(Box, { flexDirection: "column", display: externalEditorRequest ? 'none' : 'flex', children: _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(TranscriptFrame, { children: _jsx(Transcript, { messages: finalized, pending: messages.find((m) => m.pending) ?? null, state: state, settled: transcriptOwned ? renderedSettled : 0, resetKey: resetKey, raw: rawOutput, hyperlinks: hyperlinks, showLive: !lifecycleOwnsViewport, header: transcriptOwned ? (_jsx(BrandHeader, { version: ctx.version, permissionMode: displayedPermissionMode })) : undefined }) }), providerSetup ? (_jsx(ProviderSetup, { cwd: ctx.cwd, onClose: () => setProviderSetup(false), onConnect: () => {
5974
6335
  setProviderSetup(false);
5975
6336
  setPickerInitialView('providers');
@@ -6003,7 +6364,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6003
6364
  ? queuePause.outcome === 'paused'
6004
6365
  ? 'held after a resumable run paused; wait for recovery, change model, or send a message to release it'
6005
6366
  : `paused after a ${queuePause.outcome} turn; send a message or change model to continue`
6006
- : 'sending when ready'] }) })) : null, _jsx(Composer, { reasoningEffortLevels: session?.reasoningEffortLevels, permissionMode: displayedPermissionMode, onCycleMode: cyclePermissionMode, disabled: outputViewer !== null ||
6367
+ : 'sending when ready'] }) })) : null, _jsx(Composer, { reasoningEffortLevels: session?.reasoningEffortLevels, onCycleMode: cyclePermissionMode, disabled: outputViewer !== null ||
6007
6368
  phase !== 'ready' ||
6008
6369
  state === 'awaiting-permission' ||
6009
6370
  compacting ||
@@ -6020,13 +6381,23 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6020
6381
  (state === 'thinking' || state === 'tool' || pendingModelSwitchRef.current !== null), onSubmit: handleSubmit, onNotice: (text) => pushMessage('system', text), onStepReasoningEffort: stepReasoningEffort, onExternalEdit: requestExternalEditor, onEditPrevious: openPromptEditor, draftToRestore: composerDraft, onDraftRestored: (token) => setComposerDraft((draft) => (draft?.token === token ? null : draft)), onDraftPresenceChange: setComposerHasDraft, onOpenAgentPanel: openAgentCockpit, userCommands: userCommands, builtins: hostCommands.map((command) => ({
6021
6382
  ...command,
6022
6383
  description: command.unavailable?.(slashCtx) ?? command.description,
6023
- })), mentionCandidates: mentionCandidates, history: history })] }), permission === null && agentSurface === null && outputViewer === null && liveSubagents.length > 0 ? (_jsx(AgentTaskPanel, { agents: liveSubagents, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : permission === null && agentSurface?.kind === 'cockpit' ? (_jsx(AgentCockpit, { agents: subagents, selectedPhaseId: agentSurface.selectedPhaseId, selectedId: agentSurface.selectedId, focus: agentSurface.focus, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : permission === null && agentSurface?.kind === 'transcript' && selectedSubagent ? (_jsx(AgentTranscript, { agent: selectedSubagent, tailOffset: agentSurface.tailOffset, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : null] })), outputViewer && permission === null ? (_jsx(ToolOutputViewer, { onPrevious: () => {
6384
+ })), mentionCandidates: mentionCandidates, history: history })] })] })), _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, effort: reasoningEffort, orchestrate: orchestrateMode, goal: statusGoal, state: state, hint: statusHint, permissionMode: displayedPermissionMode, canCycleMode: true }), showComposerSurface &&
6385
+ permission === null &&
6386
+ agentSurface === null &&
6387
+ outputViewer === null ? (_jsx(AgentNarrationBand, { lines: narration })) : null, showComposerSurface &&
6388
+ permission === null &&
6389
+ agentSurface === null &&
6390
+ outputViewer === null &&
6391
+ liveSubagents.length > 0 ? (_jsx(AgentTaskPanel, { agents: liveSubagents, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : showComposerSurface && permission === null && agentSurface?.kind === 'cockpit' ? (_jsx(AgentCockpit, { agents: subagents, selectedPhaseId: agentSurface.selectedPhaseId, selectedId: agentSurface.selectedId, focus: agentSurface.focus, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : showComposerSurface &&
6392
+ permission === null &&
6393
+ agentSurface?.kind === 'transcript' &&
6394
+ selectedSubagent ? (_jsx(AgentTranscript, { agent: selectedSubagent, tailOffset: agentSurface.tailOffset, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : null, outputViewer && permission === null ? (_jsx(ToolOutputViewer, { onPrevious: () => {
6024
6395
  if (outputIndex > 0)
6025
6396
  setOutputViewer(outputBlocks[outputIndex - 1]);
6026
6397
  }, onNext: () => {
6027
6398
  if (outputIndex >= 0 && outputIndex + 1 < outputBlocks.length)
6028
6399
  setOutputViewer(outputBlocks[outputIndex + 1]);
6029
- }, title: outputViewer.content, lines: outputViewer.detail ?? [], rows: terminal.rows, columns: terminal.columns, onClose: () => setOutputViewer(null) }, outputViewer.id)) : null, _jsx(Box, { paddingTop: 1, children: _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, effort: reasoningEffort ?? 'default', goal: statusGoal, state: state, hint: statusHint }) })] }) }));
6400
+ }, title: outputViewer.content, lines: outputViewer.detail ?? [], rows: terminal.rows, columns: terminal.columns, onClose: () => setOutputViewer(null) }, outputViewer.id)) : null] }) }));
6030
6401
  }
6031
6402
  /** One line for a background job that ended, for the transcript and the next turn. */
6032
6403
  function describeJobExit(job) {
@@ -6103,6 +6474,33 @@ function goalStatusLabel(goal, armed) {
6103
6474
  function labelOfOption(question, optionId) {
6104
6475
  return question.options.find((option) => option.id === optionId)?.label ?? optionId;
6105
6476
  }
6477
+ /** `/agents runs`' one-line-per-run text, mirroring `/jobs`' own column shape. */
6478
+ function agentRunOption(run) {
6479
+ const agents = `${run.agentsDone}/${run.agentsTotal} agent${run.agentsTotal === 1 ? '' : 's'}`;
6480
+ const tokens = `${run.tokensTotal.toLocaleString('en-US')} tokens`;
6481
+ const columns = [
6482
+ relativeRunStart(run.startedAt),
6483
+ run.phases.join(', '),
6484
+ agents,
6485
+ tokens,
6486
+ formatElapsed(run.elapsedMs),
6487
+ ];
6488
+ if (run.live)
6489
+ columns.push('live');
6490
+ return { label: run.name, description: columns.join(' · ') };
6491
+ }
6492
+ /** `just now` / `12m ago` / `3h ago` / `2d ago` — the same buckets a conversation's own resume picker shows. */
6493
+ function relativeRunStart(startedAt) {
6494
+ const minutes = Math.round((Date.now() - startedAt) / 60_000);
6495
+ if (minutes < 1)
6496
+ return 'just now';
6497
+ if (minutes < 60)
6498
+ return `${minutes}m ago`;
6499
+ const hours = Math.round(minutes / 60);
6500
+ if (hours < 24)
6501
+ return `${hours}h ago`;
6502
+ return `${Math.round(hours / 24)}d ago`;
6503
+ }
6106
6504
  function choicePickerSearchable(picker) {
6107
6505
  return ['command', 'skill', 'plugin', 'review-branch', 'review-commit'].includes(picker.kind);
6108
6506
  }
@@ -6184,11 +6582,27 @@ function permissionPicker(mode, approvedAll, more = false) {
6184
6582
  }),
6185
6583
  };
6186
6584
  }
6187
- function reasoningEffortPicker(session, current, afterModelSelection = false) {
6585
+ /**
6586
+ * The picker's sentinel for the orchestrate-mode row, below the rule.
6587
+ *
6588
+ * A plain string outside the `ReasoningEffort` union by construction — never
6589
+ * added to any model's published menu — so it can share the same picker
6590
+ * without ever being mistaken for a level `/effort <token>` could select.
6591
+ */
6592
+ const ORCHESTRATE_MODE_VALUE = 'orchestrate';
6593
+ /** Last entry of a low-to-high menu every provider publishes in that order; `undefined` when none is known. */
6594
+ function highestReasoningEffort(levels) {
6595
+ return levels && levels.length > 0 ? levels[levels.length - 1] : undefined;
6596
+ }
6597
+ function reasoningEffortPicker(session, current, afterModelSelection = false, orchestrateOn = false) {
6188
6598
  const levels = session.reasoningEffortLevels;
6189
6599
  if (!session.hasProvider || levels === undefined)
6190
6600
  return undefined;
6191
- const values = [undefined, ...levels];
6601
+ const effortValues = [undefined, ...levels];
6602
+ const values = [
6603
+ ...effortValues,
6604
+ ORCHESTRATE_MODE_VALUE,
6605
+ ];
6192
6606
  return {
6193
6607
  kind: 'reasoning-effort',
6194
6608
  title: `Select Reasoning Level for ${session.modelSummary ?? 'current model'}`,
@@ -6199,12 +6613,24 @@ function reasoningEffortPicker(session, current, afterModelSelection = false) {
6199
6613
  }
6200
6614
  : {}),
6201
6615
  values,
6202
- options: values.map((effort) => ({
6203
- label: effort ?? 'default',
6204
- description: reasoningEffortDescription(effort),
6205
- current: effort === current,
6206
- default: effort === undefined,
6207
- })),
6616
+ options: [
6617
+ ...effortValues.map((effort) => ({
6618
+ label: effort ?? 'default',
6619
+ description: reasoningEffortDescription(effort),
6620
+ current: effort === current,
6621
+ default: effort === undefined,
6622
+ })),
6623
+ {
6624
+ // Below a rule, visually apart from the levels above: a session
6625
+ // setting, not a sixth level the provider published.
6626
+ label: 'orchestrate',
6627
+ description: orchestrateOn
6628
+ ? 'On · highest level and delegate by default'
6629
+ : 'Off · highest level and delegate by default',
6630
+ current: orchestrateOn,
6631
+ ruleBefore: true,
6632
+ },
6633
+ ],
6208
6634
  };
6209
6635
  }
6210
6636
  function reasoningEffortDescription(effort) {