@namzu/cli 25.0.1 → 26.0.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 (90) hide show
  1. package/CHANGELOG.md +265 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +9 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/acp.d.ts.map +1 -1
  6. package/dist/commands/acp.js +5 -0
  7. package/dist/commands/acp.js.map +1 -1
  8. package/dist/commands/drain.d.ts.map +1 -1
  9. package/dist/commands/drain.js +5 -0
  10. package/dist/commands/drain.js.map +1 -1
  11. package/dist/commands/run-stream.d.ts.map +1 -1
  12. package/dist/commands/run-stream.js +5 -0
  13. package/dist/commands/run-stream.js.map +1 -1
  14. package/dist/commands/run.d.ts.map +1 -1
  15. package/dist/commands/run.js +5 -0
  16. package/dist/commands/run.js.map +1 -1
  17. package/dist/config/load.d.ts.map +1 -1
  18. package/dist/config/load.js +47 -0
  19. package/dist/config/load.js.map +1 -1
  20. package/dist/config/schema.d.ts +24 -0
  21. package/dist/config/schema.d.ts.map +1 -1
  22. package/dist/config/schema.js.map +1 -1
  23. package/dist/config/tool-result-screens.d.ts +103 -0
  24. package/dist/config/tool-result-screens.d.ts.map +1 -0
  25. package/dist/config/tool-result-screens.js +104 -0
  26. package/dist/config/tool-result-screens.js.map +1 -0
  27. package/dist/context/doctrine.d.ts +4 -2
  28. package/dist/context/doctrine.d.ts.map +1 -1
  29. package/dist/context/doctrine.js +4 -2
  30. package/dist/context/doctrine.js.map +1 -1
  31. package/dist/integrations/mcp/servers.d.ts +30 -0
  32. package/dist/integrations/mcp/servers.d.ts.map +1 -1
  33. package/dist/integrations/mcp/servers.js +32 -1
  34. package/dist/integrations/mcp/servers.js.map +1 -1
  35. package/dist/integrations/resident/session-step.d.ts.map +1 -1
  36. package/dist/integrations/resident/session-step.js +5 -0
  37. package/dist/integrations/resident/session-step.js.map +1 -1
  38. package/dist/integrations/subagents/activity.d.ts +285 -11
  39. package/dist/integrations/subagents/activity.d.ts.map +1 -1
  40. package/dist/integrations/subagents/activity.js +334 -31
  41. package/dist/integrations/subagents/activity.js.map +1 -1
  42. package/dist/integrations/subagents/replay.d.ts +46 -0
  43. package/dist/integrations/subagents/replay.d.ts.map +1 -0
  44. package/dist/integrations/subagents/replay.js +193 -0
  45. package/dist/integrations/subagents/replay.js.map +1 -0
  46. package/dist/integrations/subagents/runs.d.ts +72 -0
  47. package/dist/integrations/subagents/runs.d.ts.map +1 -0
  48. package/dist/integrations/subagents/runs.js +155 -0
  49. package/dist/integrations/subagents/runs.js.map +1 -0
  50. package/dist/integrations/subagents/runtime.d.ts +21 -0
  51. package/dist/integrations/subagents/runtime.d.ts.map +1 -1
  52. package/dist/integrations/subagents/runtime.js +134 -2
  53. package/dist/integrations/subagents/runtime.js.map +1 -1
  54. package/dist/tui/AgentExplorer.d.ts +46 -2
  55. package/dist/tui/AgentExplorer.d.ts.map +1 -1
  56. package/dist/tui/AgentExplorer.js +261 -22
  57. package/dist/tui/AgentExplorer.js.map +1 -1
  58. package/dist/tui/App.d.ts.map +1 -1
  59. package/dist/tui/App.js +456 -28
  60. package/dist/tui/App.js.map +1 -1
  61. package/dist/tui/ChoicePicker.d.ts +2 -0
  62. package/dist/tui/ChoicePicker.d.ts.map +1 -1
  63. package/dist/tui/ChoicePicker.js +3 -1
  64. package/dist/tui/ChoicePicker.js.map +1 -1
  65. package/dist/tui/Composer.d.ts +10 -7
  66. package/dist/tui/Composer.d.ts.map +1 -1
  67. package/dist/tui/Composer.js +2 -7
  68. package/dist/tui/Composer.js.map +1 -1
  69. package/dist/tui/StatusBar.d.ts +56 -10
  70. package/dist/tui/StatusBar.d.ts.map +1 -1
  71. package/dist/tui/StatusBar.js +106 -35
  72. package/dist/tui/StatusBar.js.map +1 -1
  73. package/dist/tui/agent.d.ts +46 -1
  74. package/dist/tui/agent.d.ts.map +1 -1
  75. package/dist/tui/agent.js +55 -5
  76. package/dist/tui/agent.js.map +1 -1
  77. package/dist/tui/choice-selection.d.ts +3 -1
  78. package/dist/tui/choice-selection.d.ts.map +1 -1
  79. package/dist/tui/choice-selection.js +2 -2
  80. package/dist/tui/choice-selection.js.map +1 -1
  81. package/dist/tui/permission-review.d.ts.map +1 -1
  82. package/dist/tui/permission-review.js +14 -2
  83. package/dist/tui/permission-review.js.map +1 -1
  84. package/dist/tui/slashCommands.d.ts +40 -0
  85. package/dist/tui/slashCommands.d.ts.map +1 -1
  86. package/dist/tui/slashCommands.js +62 -3
  87. package/dist/tui/slashCommands.js.map +1 -1
  88. package/dist/tui/types.d.ts +12 -0
  89. package/dist/tui/types.d.ts.map +1 -1
  90. 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`
@@ -586,6 +597,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
586
597
  /** Bounded child-run projection published by the current AgentSession. */
587
598
  const [subagents, setSubagentsState] = useState([]);
588
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([]);
589
607
  const [agentSurface, setAgentSurfaceState] = useState(null);
590
608
  const agentSurfaceRef = useRef(null);
591
609
  const agentSurfaceCommittedRef = useRef(null);
@@ -604,7 +622,30 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
604
622
  useEffect(() => {
605
623
  agentSurfaceCommittedRef.current = agentSurface;
606
624
  }, [agentSurface]);
607
- 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);
608
649
  const previous = subagentsRef.current;
609
650
  subagentsRef.current = next;
610
651
  setSubagentsState(next);
@@ -632,8 +673,40 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
632
673
  selectedId: fallbackAgent.viewId,
633
674
  focus: surface.kind === 'cockpit' ? surface.focus : surface.returnFocus,
634
675
  });
635
- }, [setAgentSurface]);
636
- 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(() => {
637
710
  const agents = subagentsRef.current;
638
711
  const workflows = agentWorkflows(agents);
639
712
  const currentWorkflow = [...workflows].reverse().find((workflow) => workflow.agents.some((agent) => agent.status === 'starting' || agent.status === 'queued' || agent.status === 'working')) ?? workflows.at(-1);
@@ -649,6 +722,33 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
649
722
  });
650
723
  return true;
651
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]);
652
752
  /** One git-backed review choice may be resolving while its visible picker remains authoritative. */
653
753
  const reviewChoiceInFlightRef = useRef(null);
654
754
  const exitArmedRef = useRef(false);
@@ -703,17 +803,32 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
703
803
  useEffect(() => {
704
804
  const source = session?.subagents;
705
805
  setAgentSurface(null);
806
+ savedSubagentsRef.current = [];
807
+ void hydrateSavedChildren();
706
808
  if (!source) {
707
809
  replaceSubagents([]);
810
+ setNarration([]);
708
811
  return;
709
812
  }
710
- const refresh = () => replaceSubagents(source.getSnapshot());
813
+ const refresh = () => {
814
+ replaceSubagents(source.getSnapshot());
815
+ setNarration(source.getNarration?.() ?? []);
816
+ };
711
817
  refresh();
712
818
  return source.subscribe(refresh);
713
- }, [replaceSubagents, session, setAgentSurface]);
819
+ }, [hydrateSavedChildren, replaceSubagents, session, setAgentSurface]);
714
820
  const resetSubagentActivity = useCallback(() => {
715
821
  session?.subagents?.reset();
716
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 = [];
717
832
  replaceSubagents([]);
718
833
  }, [replaceSubagents, session, setAgentSurface]);
719
834
  // Source of truth for in-flight tools (the event loop runs across renders, so
@@ -1002,6 +1117,92 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1002
1117
  ]);
1003
1118
  return id;
1004
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]);
1005
1206
  const reportedAgentsRef = useRef(new Set());
1006
1207
  useEffect(() => {
1007
1208
  reportedAgentsRef.current.clear();
@@ -1025,6 +1226,38 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1025
1226
  pushMessage('tool', `${agent.description} · ${status}`, false, completed ? '✓' : '✗', undefined, completed ? theme.status.ok : theme.status.error, 'ctrl+t · agent details');
1026
1227
  }
1027
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]);
1028
1261
  const applyPermissionMode = useCallback((mode) => {
1029
1262
  if (!session?.hasProvider) {
1030
1263
  pushMessage('system', 'Choose a model before changing permissions.');
@@ -1079,6 +1312,47 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1079
1312
  setReasoningEffort(effort);
1080
1313
  pushMessage('system', `Reasoning: ${effort} (this session).`);
1081
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]);
1082
1356
  const stepReasoningEffort = useCallback((direction) => {
1083
1357
  const levels = session?.reasoningEffortLevels;
1084
1358
  if (!session?.hasProvider || levels === undefined || levels.length === 0) {
@@ -1583,6 +1857,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1583
1857
  setChoicePicker({ ...child, back: picker });
1584
1858
  return;
1585
1859
  }
1860
+ if (picker.kind === 'agent-runs') {
1861
+ if (value === 'loading')
1862
+ return;
1863
+ void openOrchestrationRun(value);
1864
+ return;
1865
+ }
1586
1866
  if (picker.kind === 'archive-conversation') {
1587
1867
  if (value === 'archive')
1588
1868
  archiveCurrentConversation();
@@ -1636,16 +1916,22 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1636
1916
  removeStoredCredential(value);
1637
1917
  return;
1638
1918
  }
1919
+ if (picker.kind === 'reasoning-effort' && value === ORCHESTRATE_MODE_VALUE) {
1920
+ applyOrchestrateMode(!orchestrateModeRef.current, picker.selectedSession);
1921
+ return;
1922
+ }
1639
1923
  applyReasoningEffort(value, picker.selectedSession);
1640
1924
  }, [
1641
1925
  activateSkill,
1642
1926
  advanceQueueContinuation,
1643
1927
  appLifetime.signal,
1928
+ applyOrchestrateMode,
1644
1929
  applyPermissionMode,
1645
1930
  applyReasoningEffort,
1646
1931
  archiveCurrentConversation,
1647
1932
  ctx.cwd,
1648
1933
  enqueueQueued,
1934
+ openOrchestrationRun,
1649
1935
  pushMessage,
1650
1936
  recordFeedback,
1651
1937
  removeStoredCredential,
@@ -1863,6 +2149,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1863
2149
  ...(activeCtx.memory ? { memory: activeCtx.memory } : {}),
1864
2150
  ...(activeCtx.limits ? { limits: activeCtx.limits } : {}),
1865
2151
  ...(activeCtx.sandbox ? { sandbox: activeCtx.sandbox } : {}),
2152
+ // The operator's screens, on the same terms as every other
2153
+ // config field the App forwards. `!== undefined` rather than a
2154
+ // length test: `[]` is the off switch, and dropping it here
2155
+ // would leave the kernel's default in force in the interactive
2156
+ // session — always on, for the one operator who said otherwise.
2157
+ ...(activeCtx.toolResultScreens !== undefined
2158
+ ? { toolResultScreens: activeCtx.toolResultScreens }
2159
+ : {}),
1866
2160
  // Somebody is at this terminal, so the model may ask them one
1867
2161
  // question when a decision is genuinely theirs.
1868
2162
  askUser: true,
@@ -1905,9 +2199,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1905
2199
  // A picker-owned provider/model change is one state transition. Clear the
1906
2200
  // old model's effort selection before publishing the replacement session
1907
2201
  // or releasing any paused queue. Failed and superseded candidates returned
1908
- // above, so they leave the current session selection untouched.
1909
- if (signal !== undefined)
1910
- setReasoningEffort(undefined);
2202
+ // above, so they leave the current session selection untouched. Orchestrate
2203
+ // mode survives the switch: re-pin to the new model's highest published
2204
+ // level instead of clearing, exactly as it pinned when first turned on.
2205
+ if (signal !== undefined) {
2206
+ setReasoningEffort(orchestrateModeRef.current ? highestReasoningEffort(s.reasoningEffortLevels) : undefined);
2207
+ }
1911
2208
  // Re-hydration (a provider switch via /model) builds a second session;
1912
2209
  // without this the first one's tool-server child processes stay alive
1913
2210
  // for the rest of the TUI's life.
@@ -1915,7 +2212,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1915
2212
  void previousSessionRef.current?.close();
1916
2213
  previousSessionRef.current = s;
1917
2214
  setSession(s);
1918
- if (options.chooseReasoningEffort && s.reasoningEffortLevels?.length) {
2215
+ // Orchestrate mode already re-pinned effort above; reopening this picker
2216
+ // would ask the operator to redo a choice the mode just made for them.
2217
+ if (!orchestrateModeRef.current && options.chooseReasoningEffort && s.reasoningEffortLevels?.length) {
1919
2218
  // Own input before publishing ready or releasing a paused queue. The
1920
2219
  // menu closes only after choosing an effort or keeping the new default.
1921
2220
  setSelectedChoice(0);
@@ -2603,6 +2902,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2603
2902
  : null;
2604
2903
  pendingGoalResumeRef.current = conv.id;
2605
2904
  scope.sessionId = conv.id; // new turns now attribute to the resumed session
2905
+ // The scope now names the resumed conversation, so its finished
2906
+ // children are readable. Without this the first Ctrl+T after a resume
2907
+ // would have nothing to find.
2908
+ void hydrateSavedChildren();
2606
2909
  conversationMaterializedRef.current = true;
2607
2910
  pushMessage('system', `Resumed: ${conv.title}`);
2608
2911
  if (discardedQueued > 0) {
@@ -2623,6 +2926,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2623
2926
  nextId,
2624
2927
  pushMessage,
2625
2928
  cancelPendingModelSwitch,
2929
+ hydrateSavedChildren,
2626
2930
  resetSubagentActivity,
2627
2931
  session,
2628
2932
  resetTranscript,
@@ -2674,6 +2978,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2674
2978
  if (sourceScope && targetSessionId) {
2675
2979
  sourceScope.sessionId = targetSessionId;
2676
2980
  conversationMaterializedRef.current = true;
2981
+ // A fresh conversation owns no runs yet, so this normally reads
2982
+ // nothing. It runs anyway because the rule is "the scope changed,
2983
+ // re-read", and an exception here is how the resume path lost
2984
+ // its own re-read.
2985
+ void hydrateSavedChildren();
2677
2986
  }
2678
2987
  if (clearScreen) {
2679
2988
  setMessages([]);
@@ -2706,6 +3015,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2706
3015
  interruptTurn,
2707
3016
  pushMessage,
2708
3017
  cancelPendingModelSwitch,
3018
+ hydrateSavedChildren,
2709
3019
  resetSubagentActivity,
2710
3020
  session,
2711
3021
  resetTranscript,
@@ -2858,6 +3168,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2858
3168
  // is reloaded or reset. Only where the NEXT turn is written changes.
2859
3169
  scope.sessionId = forked.id;
2860
3170
  resetSubagentActivity();
3171
+ void hydrateSavedChildren();
2861
3172
  session?.resetTaskStore?.();
2862
3173
  goalActivation.clear();
2863
3174
  wakeGoalDriver();
@@ -2875,6 +3186,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2875
3186
  hasUnsettledTurn,
2876
3187
  materializeConversation,
2877
3188
  pushMessage,
3189
+ hydrateSavedChildren,
2878
3190
  resetSubagentActivity,
2879
3191
  session,
2880
3192
  wakeGoalDriver,
@@ -2968,6 +3280,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2968
3280
  goalActivation.clear();
2969
3281
  wakeGoalDriver();
2970
3282
  scope.sessionId = forked.id;
3283
+ void hydrateSavedChildren();
2971
3284
  modelHistoryRef.current = forked.messages;
2972
3285
  lastAssistantMessage.current = null;
2973
3286
  const persistedOutput = latestAssistantOutput(forked.messages);
@@ -3005,6 +3318,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3005
3318
  nextId,
3006
3319
  pushMessage,
3007
3320
  cancelPendingModelSwitch,
3321
+ hydrateSavedChildren,
3008
3322
  resetSubagentActivity,
3009
3323
  session,
3010
3324
  resetTranscript,
@@ -3229,6 +3543,17 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3229
3543
  event.toolName.toLowerCase() === 'agent' &&
3230
3544
  subagentsRef.current.some((agent) => agent.toolUseId === event.toolUseId &&
3231
3545
  (event.runId === undefined || agent.workflowId === event.runId));
3546
+ // The band above the rail already carries a narrated line, in the
3547
+ // parent's own words, from the moment it is written. Printing the
3548
+ // call that wrote it would show the same sentence twice, the
3549
+ // second time as protocol — the same duplicate-protocol case as
3550
+ // the represented Agent call just below, and refused the same
3551
+ // way. A REFUSED line keeps its row: nothing reached the screen,
3552
+ // and that row is the only thing that says so.
3553
+ if (!event.isError && event.toolName === NARRATION_TOOL_NAME) {
3554
+ setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
3555
+ break;
3556
+ }
3232
3557
  if (representedSuccessfulAgent) {
3233
3558
  // The automatic panel already owned this successful child from
3234
3559
  // start through settlement. Re-emitting its internal Agent call as
@@ -3684,6 +4009,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3684
4009
  activeTurnInboxRef.current = inbox;
3685
4010
  const turnPermissionMode = permissionModeRef.current;
3686
4011
  const turnReasoningEffort = reasoningEffortRef.current;
4012
+ const turnOrchestrateMode = orchestrateModeRef.current;
3687
4013
  const turnRunLimits = resolveRunGuards(ctxRef.current.limits, runLimitsOverrideRef.current);
3688
4014
  // Always carry the guarded callback. `auto` and `strict` decide before
3689
4015
  // calling it in makeResumeHandler; retaining it is what lets a session
@@ -3744,6 +4070,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3744
4070
  permissionMode: turnPermissionMode,
3745
4071
  limits: turnRunLimits,
3746
4072
  ...(turnReasoningEffort !== undefined ? { effort: turnReasoningEffort } : {}),
4073
+ ...(turnOrchestrateMode ? { orchestrate: true } : {}),
3747
4074
  ...(goalRound ? { goalRound } : {}),
3748
4075
  // The mode above decides whether this callback is consulted.
3749
4076
  onPermission: askPermission,
@@ -4224,9 +4551,20 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4224
4551
  return;
4225
4552
  }
4226
4553
  case 'agent-cockpit': {
4227
- if (!openAgentCockpit()) {
4228
- pushMessage('system', `No delegated agents in this conversation.${session?.agentIds.length ? `\nAvailable: ${session.agentIds.join(', ')}` : '\nNo agents are configured.'}`);
4229
- }
4554
+ // Reported after the read, not before it: the answer to "are
4555
+ // there any" may be on disk, and a conversation whose evidence
4556
+ // is sitting in its own directory must never be told it
4557
+ // delegated nothing.
4558
+ const agentIds = session?.agentIds ?? [];
4559
+ void openAgentCockpit().then((opened) => {
4560
+ if (opened)
4561
+ return;
4562
+ pushMessage('system', `No delegated agents in this conversation.${agentIds.length ? `\nAvailable: ${agentIds.join(', ')}` : '\nNo agents are configured.'}`);
4563
+ });
4564
+ return;
4565
+ }
4566
+ case 'agent-runs': {
4567
+ openAgentRunsPicker();
4230
4568
  return;
4231
4569
  }
4232
4570
  case 'provider-setup':
@@ -4563,7 +4901,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4563
4901
  const values = [undefined, ...levels];
4564
4902
  const current = reasoningEffortRef.current;
4565
4903
  setSelectedChoice(Math.max(0, values.findIndex((value) => value === current)));
4566
- setChoicePicker(reasoningEffortPicker(session, current) ?? null);
4904
+ setChoicePicker(reasoningEffortPicker(session, current, false, orchestrateModeRef.current) ?? null);
4567
4905
  return;
4568
4906
  }
4569
4907
  case 'login':
@@ -4981,6 +5319,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4981
5319
  : 'Raw output mode off — rich transcript rendering restored.');
4982
5320
  return;
4983
5321
  }
5322
+ case 'orchestrate-mode': {
5323
+ const enabled = slash.enabled === 'toggle' ? !orchestrateModeRef.current : slash.enabled;
5324
+ applyOrchestrateMode(enabled);
5325
+ return;
5326
+ }
4984
5327
  case 'export-picker': {
4985
5328
  if (!stableExportSource())
4986
5329
  return;
@@ -5063,6 +5406,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5063
5406
  hostCommands,
5064
5407
  nextId,
5065
5408
  openAgentCockpit,
5409
+ openAgentRunsPicker,
5066
5410
  pushMessage,
5067
5411
  rawOutput,
5068
5412
  removeStoredCredential,
@@ -5563,8 +5907,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5563
5907
  setAgentSurface(null);
5564
5908
  return;
5565
5909
  }
5566
- if (openAgentCockpit())
5910
+ if (openCockpitFromCurrentRows()) {
5911
+ void hydrateSavedChildren();
5567
5912
  return;
5913
+ }
5914
+ // Nothing on screen yet. The conversation's children may exist only
5915
+ // as saved evidence, so the request outlives that read rather than
5916
+ // being answered by an empty snapshot.
5917
+ void openAgentCockpit();
5568
5918
  }
5569
5919
  // Child observation owns navigation, but never outranks consent: the
5570
5920
  // permission branch above preempts it if a child or parent call asks.
@@ -5702,7 +6052,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5702
6052
  return;
5703
6053
  }
5704
6054
  if (key.home || key.end || key.pageUp || key.pageDown || key.upArrow || key.downArrow) {
5705
- const page = agentTranscriptPageSize(terminal.rows);
6055
+ // Reserving the replayed banner's row here too keeps one PgDn a
6056
+ // screenful of what is actually visible.
6057
+ const page = agentTranscriptPageSize(terminal.rows, selected.replayed ? 1 : 0);
5706
6058
  const max = maxAgentTranscriptTailOffset(selected, terminal.rows, terminal.columns);
5707
6059
  const offset = Math.min(agentView.tailOffset, max);
5708
6060
  const next = key.home
@@ -5976,6 +6328,17 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5976
6328
  ? permissionOwner ? `Agent: ${permissionOwner.description}` : `Run: ${permission.runId}`
5977
6329
  : undefined;
5978
6330
  const displayedPermissionMode = effectivePermissionMode(permissionMode, session?.approvalLatched() ?? false);
6331
+ // True exactly when the phase ternary below renders its final branch — the
6332
+ // normal composer view, as opposed to provider setup or a lifecycle
6333
+ // picker/prompt. The agent rail is a feature of that view: computing this
6334
+ // once here lets its render site sit after the footer, below, while still
6335
+ // never appearing during trust/resume/edit/picker/setup phases.
6336
+ const showComposerSurface = !providerSetup &&
6337
+ phase !== 'trust' &&
6338
+ phase !== 'unhealthy' &&
6339
+ phase !== 'resume' &&
6340
+ phase !== 'edit' &&
6341
+ phase !== 'picker';
5979
6342
  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: () => {
5980
6343
  setProviderSetup(false);
5981
6344
  setPickerInitialView('providers');
@@ -6009,7 +6372,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6009
6372
  ? queuePause.outcome === 'paused'
6010
6373
  ? 'held after a resumable run paused; wait for recovery, change model, or send a message to release it'
6011
6374
  : `paused after a ${queuePause.outcome} turn; send a message or change model to continue`
6012
- : 'sending when ready'] }) })) : null, _jsx(Composer, { reasoningEffortLevels: session?.reasoningEffortLevels, permissionMode: displayedPermissionMode, onCycleMode: cyclePermissionMode, disabled: outputViewer !== null ||
6375
+ : 'sending when ready'] }) })) : null, _jsx(Composer, { reasoningEffortLevels: session?.reasoningEffortLevels, onCycleMode: cyclePermissionMode, disabled: outputViewer !== null ||
6013
6376
  phase !== 'ready' ||
6014
6377
  state === 'awaiting-permission' ||
6015
6378
  compacting ||
@@ -6026,13 +6389,23 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6026
6389
  (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) => ({
6027
6390
  ...command,
6028
6391
  description: command.unavailable?.(slashCtx) ?? command.description,
6029
- })), 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: () => {
6392
+ })), 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 &&
6393
+ permission === null &&
6394
+ agentSurface === null &&
6395
+ outputViewer === null ? (_jsx(AgentNarrationBand, { lines: narration })) : null, showComposerSurface &&
6396
+ permission === null &&
6397
+ agentSurface === null &&
6398
+ outputViewer === null &&
6399
+ 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 &&
6400
+ permission === null &&
6401
+ agentSurface?.kind === 'transcript' &&
6402
+ selectedSubagent ? (_jsx(AgentTranscript, { agent: selectedSubagent, tailOffset: agentSurface.tailOffset, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : null, outputViewer && permission === null ? (_jsx(ToolOutputViewer, { onPrevious: () => {
6030
6403
  if (outputIndex > 0)
6031
6404
  setOutputViewer(outputBlocks[outputIndex - 1]);
6032
6405
  }, onNext: () => {
6033
6406
  if (outputIndex >= 0 && outputIndex + 1 < outputBlocks.length)
6034
6407
  setOutputViewer(outputBlocks[outputIndex + 1]);
6035
- }, 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 }) })] }) }));
6408
+ }, title: outputViewer.content, lines: outputViewer.detail ?? [], rows: terminal.rows, columns: terminal.columns, onClose: () => setOutputViewer(null) }, outputViewer.id)) : null] }) }));
6036
6409
  }
6037
6410
  /** One line for a background job that ended, for the transcript and the next turn. */
6038
6411
  function describeJobExit(job) {
@@ -6109,6 +6482,33 @@ function goalStatusLabel(goal, armed) {
6109
6482
  function labelOfOption(question, optionId) {
6110
6483
  return question.options.find((option) => option.id === optionId)?.label ?? optionId;
6111
6484
  }
6485
+ /** `/agents runs`' one-line-per-run text, mirroring `/jobs`' own column shape. */
6486
+ function agentRunOption(run) {
6487
+ const agents = `${run.agentsDone}/${run.agentsTotal} agent${run.agentsTotal === 1 ? '' : 's'}`;
6488
+ const tokens = `${run.tokensTotal.toLocaleString('en-US')} tokens`;
6489
+ const columns = [
6490
+ relativeRunStart(run.startedAt),
6491
+ run.phases.join(', '),
6492
+ agents,
6493
+ tokens,
6494
+ formatElapsed(run.elapsedMs),
6495
+ ];
6496
+ if (run.live)
6497
+ columns.push('live');
6498
+ return { label: run.name, description: columns.join(' · ') };
6499
+ }
6500
+ /** `just now` / `12m ago` / `3h ago` / `2d ago` — the same buckets a conversation's own resume picker shows. */
6501
+ function relativeRunStart(startedAt) {
6502
+ const minutes = Math.round((Date.now() - startedAt) / 60_000);
6503
+ if (minutes < 1)
6504
+ return 'just now';
6505
+ if (minutes < 60)
6506
+ return `${minutes}m ago`;
6507
+ const hours = Math.round(minutes / 60);
6508
+ if (hours < 24)
6509
+ return `${hours}h ago`;
6510
+ return `${Math.round(hours / 24)}d ago`;
6511
+ }
6112
6512
  function choicePickerSearchable(picker) {
6113
6513
  return ['command', 'skill', 'plugin', 'review-branch', 'review-commit'].includes(picker.kind);
6114
6514
  }
@@ -6190,11 +6590,27 @@ function permissionPicker(mode, approvedAll, more = false) {
6190
6590
  }),
6191
6591
  };
6192
6592
  }
6193
- function reasoningEffortPicker(session, current, afterModelSelection = false) {
6593
+ /**
6594
+ * The picker's sentinel for the orchestrate-mode row, below the rule.
6595
+ *
6596
+ * A plain string outside the `ReasoningEffort` union by construction — never
6597
+ * added to any model's published menu — so it can share the same picker
6598
+ * without ever being mistaken for a level `/effort <token>` could select.
6599
+ */
6600
+ const ORCHESTRATE_MODE_VALUE = 'orchestrate';
6601
+ /** Last entry of a low-to-high menu every provider publishes in that order; `undefined` when none is known. */
6602
+ function highestReasoningEffort(levels) {
6603
+ return levels && levels.length > 0 ? levels[levels.length - 1] : undefined;
6604
+ }
6605
+ function reasoningEffortPicker(session, current, afterModelSelection = false, orchestrateOn = false) {
6194
6606
  const levels = session.reasoningEffortLevels;
6195
6607
  if (!session.hasProvider || levels === undefined)
6196
6608
  return undefined;
6197
- const values = [undefined, ...levels];
6609
+ const effortValues = [undefined, ...levels];
6610
+ const values = [
6611
+ ...effortValues,
6612
+ ORCHESTRATE_MODE_VALUE,
6613
+ ];
6198
6614
  return {
6199
6615
  kind: 'reasoning-effort',
6200
6616
  title: `Select Reasoning Level for ${session.modelSummary ?? 'current model'}`,
@@ -6205,12 +6621,24 @@ function reasoningEffortPicker(session, current, afterModelSelection = false) {
6205
6621
  }
6206
6622
  : {}),
6207
6623
  values,
6208
- options: values.map((effort) => ({
6209
- label: effort ?? 'default',
6210
- description: reasoningEffortDescription(effort),
6211
- current: effort === current,
6212
- default: effort === undefined,
6213
- })),
6624
+ options: [
6625
+ ...effortValues.map((effort) => ({
6626
+ label: effort ?? 'default',
6627
+ description: reasoningEffortDescription(effort),
6628
+ current: effort === current,
6629
+ default: effort === undefined,
6630
+ })),
6631
+ {
6632
+ // Below a rule, visually apart from the levels above: a session
6633
+ // setting, not a sixth level the provider published.
6634
+ label: 'orchestrate',
6635
+ description: orchestrateOn
6636
+ ? 'On · highest level and delegate by default'
6637
+ : 'Off · highest level and delegate by default',
6638
+ current: orchestrateOn,
6639
+ ruleBefore: true,
6640
+ },
6641
+ ],
6214
6642
  };
6215
6643
  }
6216
6644
  function reasoningEffortDescription(effort) {