@namzu/cli 14.1.0 → 14.2.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 (39) hide show
  1. package/README.md +18 -9
  2. package/dist/integrations/providers/discover.d.ts +4 -0
  3. package/dist/integrations/providers/discover.d.ts.map +1 -1
  4. package/dist/integrations/providers/discover.js +55 -37
  5. package/dist/integrations/providers/discover.js.map +1 -1
  6. package/dist/integrations/providers/harness-credentials.d.ts +44 -9
  7. package/dist/integrations/providers/harness-credentials.d.ts.map +1 -1
  8. package/dist/integrations/providers/harness-credentials.js +140 -11
  9. package/dist/integrations/providers/harness-credentials.js.map +1 -1
  10. package/dist/integrations/providers/identity.d.ts +15 -14
  11. package/dist/integrations/providers/identity.d.ts.map +1 -1
  12. package/dist/integrations/providers/identity.js +15 -14
  13. package/dist/integrations/providers/identity.js.map +1 -1
  14. package/dist/integrations/providers/oauth.d.ts +8 -3
  15. package/dist/integrations/providers/oauth.d.ts.map +1 -1
  16. package/dist/integrations/providers/oauth.js +37 -11
  17. package/dist/integrations/providers/oauth.js.map +1 -1
  18. package/dist/integrations/providers/subscription-login.d.ts.map +1 -1
  19. package/dist/integrations/providers/subscription-login.js +12 -8
  20. package/dist/integrations/providers/subscription-login.js.map +1 -1
  21. package/dist/tui/App.d.ts.map +1 -1
  22. package/dist/tui/App.js +262 -34
  23. package/dist/tui/App.js.map +1 -1
  24. package/dist/tui/ChoicePicker.d.ts +14 -0
  25. package/dist/tui/ChoicePicker.d.ts.map +1 -0
  26. package/dist/tui/ChoicePicker.js +15 -0
  27. package/dist/tui/ChoicePicker.js.map +1 -0
  28. package/dist/tui/StatusBar.d.ts +23 -39
  29. package/dist/tui/StatusBar.d.ts.map +1 -1
  30. package/dist/tui/StatusBar.js +67 -105
  31. package/dist/tui/StatusBar.js.map +1 -1
  32. package/dist/tui/agent.d.ts.map +1 -1
  33. package/dist/tui/agent.js +10 -8
  34. package/dist/tui/agent.js.map +1 -1
  35. package/dist/tui/slashCommands.d.ts +8 -0
  36. package/dist/tui/slashCommands.d.ts.map +1 -1
  37. package/dist/tui/slashCommands.js +20 -15
  38. package/dist/tui/slashCommands.js.map +1 -1
  39. package/package.json +4 -4
package/dist/tui/App.js CHANGED
@@ -27,6 +27,7 @@ import { checkUpdates } from '../integrations/updates.js';
27
27
  import { appendMemory, composeMemoryPrompt, readMemory } from '../memory/store.js';
28
28
  import { composeSkillsPrompt, discoverSkills, loadSkillBody } from '../skills/store.js';
29
29
  import { discoverUserCommands } from '../user-commands/store.js';
30
+ import { ChoicePicker } from './ChoicePicker.js';
30
31
  import { Composer } from './Composer.js';
31
32
  import { CopyPicker } from './CopyPicker.js';
32
33
  import { EditPromptPicker } from './EditPromptPicker.js';
@@ -42,7 +43,7 @@ import { bottomSpacerRows } from './bottom-spacer.js';
42
43
  import { keepRecentRows } from './compact-transcript.js';
43
44
  import { approvalIsDeliberate } from './consent-timing.js';
44
45
  import { planTurnPublication } from './conversation-history.js';
45
- import { copyTargetsForResponse, } from './copy-targets.js';
46
+ import { copyTargetsForResponse } from './copy-targets.js';
46
47
  import { editablePrompts } from './edit-prompts.js';
47
48
  import { liveWindow, transcriptLines } from './live-window.js';
48
49
  import { describeCodexDeviceLoginStart, describeLoginOutcome, describeLoginStart, describeLogout, } from './login-prompt.js';
@@ -328,6 +329,14 @@ export function App({ ctx: initialCtx }) {
328
329
  copyPickerRef.current = next;
329
330
  setCopyPickerState(next);
330
331
  }, []);
332
+ /** Finite slash-command choice owned synchronously until apply or cancel. */
333
+ const [choicePicker, setChoicePickerState] = useState(null);
334
+ const choicePickerRef = useRef(null);
335
+ const [selectedChoice, setSelectedChoice] = useState(0);
336
+ const setChoicePicker = useCallback((next) => {
337
+ choicePickerRef.current = next;
338
+ setChoicePickerState(next);
339
+ }, []);
331
340
  const [composerDraft, setComposerDraft] = useState(null);
332
341
  const composerDraftTokenRef = useRef(0);
333
342
  const exitArmedRef = useRef(false);
@@ -399,6 +408,7 @@ export function App({ ctx: initialCtx }) {
399
408
  const [goalActivation] = useState(() => new SessionGoalActivation());
400
409
  const goalDriveInFlightRef = useRef(false);
401
410
  const [goalDriveVersion, setGoalDriveVersion] = useState(0);
411
+ const [goalStatus, setGoalStatus] = useState(null);
402
412
  const wakeGoalDriver = useCallback(() => setGoalDriveVersion((version) => version + 1), []);
403
413
  /**
404
414
  * Conversation writes in the order the operator produced them.
@@ -517,6 +527,70 @@ export function App({ ctx: initialCtx }) {
517
527
  ]);
518
528
  return id;
519
529
  }, [nextId]);
530
+ const applyPermissionMode = useCallback((mode) => {
531
+ if (!session?.hasProvider) {
532
+ pushMessage('system', 'No active session — pick a provider before changing permissions.');
533
+ return;
534
+ }
535
+ if (state !== 'idle' ||
536
+ abortRef.current !== null ||
537
+ hasUnsettledTurn() ||
538
+ queuedRef.current.length > 0 ||
539
+ permissionResolveRef.current !== null ||
540
+ compactingRef.current) {
541
+ pushMessage('system', 'Permission mode was not changed: wait for the active turn, prompt, compaction, and queued work to settle.');
542
+ return;
543
+ }
544
+ if (!session.resetApprovalLatch) {
545
+ pushMessage('system', 'Permission mode was not changed: this embedded session cannot revoke an earlier "approve all" choice. Reconnect it before changing modes.');
546
+ return;
547
+ }
548
+ session.resetApprovalLatch();
549
+ permissionModeRef.current = mode;
550
+ permissionModeSourceRef.current = 'session';
551
+ setPermissionModeState(mode);
552
+ pushMessage('system', `Permission mode changed to ${mode}. Any earlier "approve all" choice was revoked. Declarative deny rules and the built-in safety gate still take precedence.`);
553
+ }, [hasUnsettledTurn, pushMessage, session, state]);
554
+ const applyReasoningEffort = useCallback((effort) => {
555
+ if (!session?.hasProvider) {
556
+ pushMessage('system', 'No active session — pick a provider before changing reasoning effort.');
557
+ return;
558
+ }
559
+ if (state !== 'idle' ||
560
+ abortRef.current !== null ||
561
+ hasUnsettledTurn() ||
562
+ queuedRef.current.length > 0 ||
563
+ permissionResolveRef.current !== null ||
564
+ compactingRef.current) {
565
+ pushMessage('system', 'Reasoning effort was not changed: wait for the active turn, prompt, compaction, and queued work to settle.');
566
+ return;
567
+ }
568
+ if (effort === undefined) {
569
+ setReasoningEffort(undefined);
570
+ pushMessage('system', 'Reasoning effort reset to the provider default for future main-query turns.');
571
+ return;
572
+ }
573
+ if (!session.reasoningEffortLevels?.includes(effort)) {
574
+ pushMessage('system', `Reasoning effort was not changed: ${effort} is not offered by every usable provider-chain member for this model.`);
575
+ return;
576
+ }
577
+ setReasoningEffort(effort);
578
+ pushMessage('system', `Reasoning effort changed to ${effort} for future main-query turns in this session.`);
579
+ }, [hasUnsettledTurn, pushMessage, session, setReasoningEffort, state]);
580
+ const applyChoiceSelection = useCallback((index) => {
581
+ const picker = choicePickerRef.current;
582
+ if (!picker)
583
+ return;
584
+ const value = picker.values[index];
585
+ if (index < 0 || index >= picker.values.length)
586
+ return;
587
+ setChoicePicker(null);
588
+ if (picker.kind === 'permission-mode') {
589
+ applyPermissionMode(value);
590
+ return;
591
+ }
592
+ applyReasoningEffort(value);
593
+ }, [applyPermissionMode, applyReasoningEffort, setChoicePicker]);
520
594
  const sendCopyRequest = useCallback((index) => {
521
595
  const picker = copyPickerRef.current;
522
596
  const target = picker?.targets[index];
@@ -2068,6 +2142,10 @@ export function App({ ctx: initialCtx }) {
2068
2142
  setPhase('picker');
2069
2143
  return;
2070
2144
  case 'permission-mode': {
2145
+ applyPermissionMode(slash.mode);
2146
+ return;
2147
+ }
2148
+ case 'permission-mode-picker': {
2071
2149
  if (!session?.hasProvider) {
2072
2150
  pushMessage('system', 'No active session — pick a provider before changing permissions.');
2073
2151
  return;
@@ -2077,24 +2155,34 @@ export function App({ ctx: initialCtx }) {
2077
2155
  hasUnsettledTurn() ||
2078
2156
  queuedRef.current.length > 0 ||
2079
2157
  permissionResolveRef.current !== null ||
2080
- compactingRef.current) {
2081
- pushMessage('system', 'Permission mode was not changed: wait for the active turn, prompt, compaction, and queued work to settle.');
2082
- return;
2083
- }
2084
- if (!session.resetApprovalLatch) {
2085
- pushMessage('system', 'Permission mode was not changed: this embedded session cannot revoke an earlier "approve all" choice. Reconnect it before changing modes.');
2158
+ compactingRef.current ||
2159
+ !session.resetApprovalLatch) {
2160
+ pushMessage('system', 'Permission choices are unavailable until the active turn, prompt, compaction, queued work, or embedded-session approval latch can be safely settled.');
2086
2161
  return;
2087
2162
  }
2088
- session.resetApprovalLatch();
2089
- permissionModeRef.current = slash.mode;
2090
- permissionModeSourceRef.current = 'session';
2091
- setPermissionModeState(slash.mode);
2092
- pushMessage('system', `Permission mode changed to ${slash.mode}. Any earlier "approve all" choice was revoked. Declarative deny rules and the built-in safety gate still take precedence.`);
2163
+ const values = ['prompt', 'auto', 'strict'];
2164
+ setSelectedChoice(Math.max(0, values.indexOf(permissionModeRef.current)));
2165
+ setChoicePicker({
2166
+ kind: 'permission-mode',
2167
+ title: 'Select Permission Mode',
2168
+ notice: permissionPickerNotice(session),
2169
+ values,
2170
+ options: values.map((mode) => ({
2171
+ label: mode,
2172
+ description: permissionModeDescription(mode),
2173
+ current: mode === permissionModeRef.current,
2174
+ })),
2175
+ });
2093
2176
  return;
2094
2177
  }
2095
2178
  case 'reasoning-effort': {
2096
- if (!session?.hasProvider) {
2097
- pushMessage('system', 'No active session — pick a provider before changing reasoning effort.');
2179
+ applyReasoningEffort(slash.effort ?? undefined);
2180
+ return;
2181
+ }
2182
+ case 'reasoning-effort-picker': {
2183
+ const levels = session?.reasoningEffortLevels;
2184
+ if (!session?.hasProvider || levels === undefined) {
2185
+ pushMessage('system', 'No exact reasoning-effort menu is available for the current session.');
2098
2186
  return;
2099
2187
  }
2100
2188
  if (state !== 'idle' ||
@@ -2103,20 +2191,22 @@ export function App({ ctx: initialCtx }) {
2103
2191
  queuedRef.current.length > 0 ||
2104
2192
  permissionResolveRef.current !== null ||
2105
2193
  compactingRef.current) {
2106
- pushMessage('system', 'Reasoning effort was not changed: wait for the active turn, prompt, compaction, and queued work to settle.');
2107
- return;
2108
- }
2109
- if (slash.effort === null) {
2110
- setReasoningEffort(undefined);
2111
- pushMessage('system', 'Reasoning effort reset to the provider default for future main-query turns.');
2112
- return;
2113
- }
2114
- if (!session.reasoningEffortLevels?.includes(slash.effort)) {
2115
- pushMessage('system', `Reasoning effort was not changed: ${slash.effort} is not offered by every usable provider-chain member for this model.`);
2194
+ pushMessage('system', 'Reasoning-effort choices are unavailable until the active turn, prompt, compaction, and queued work settle.');
2116
2195
  return;
2117
2196
  }
2118
- setReasoningEffort(slash.effort);
2119
- pushMessage('system', `Reasoning effort changed to ${slash.effort} for future main-query turns in this session.`);
2197
+ const values = [undefined, ...levels];
2198
+ const current = reasoningEffortRef.current;
2199
+ setSelectedChoice(Math.max(0, values.findIndex((value) => value === current)));
2200
+ setChoicePicker({
2201
+ kind: 'reasoning-effort',
2202
+ title: `Select Reasoning Level for ${session.modelSummary ?? 'current model'}`,
2203
+ values,
2204
+ options: values.map((effort) => ({
2205
+ label: effort ?? 'default',
2206
+ description: reasoningEffortDescription(effort),
2207
+ current: effort === current,
2208
+ })),
2209
+ });
2120
2210
  return;
2121
2211
  }
2122
2212
  case 'login':
@@ -2587,6 +2677,8 @@ export function App({ ctx: initialCtx }) {
2587
2677
  }, [
2588
2678
  activeSkills,
2589
2679
  advanceQueueContinuation,
2680
+ applyPermissionMode,
2681
+ applyReasoningEffort,
2590
2682
  ctx.cwd,
2591
2683
  doResume,
2592
2684
  enqueueQueued,
@@ -2596,12 +2688,45 @@ export function App({ ctx: initialCtx }) {
2596
2688
  pushMessage,
2597
2689
  rawOutput,
2598
2690
  resetTranscript,
2691
+ setChoicePicker,
2599
2692
  setCopyPicker,
2600
2693
  setReasoningEffort,
2601
2694
  slashCtx,
2602
2695
  startFreshConversation,
2603
2696
  state,
2604
2697
  ]);
2698
+ // Keep the footer on the same durable goal record the driver mutates. A ref
2699
+ // alone cannot repaint React, so every goal mutation bumps goalDriveVersion;
2700
+ // generation/session checks stop a late disk read labelling a resumed chat.
2701
+ useEffect(() => {
2702
+ void goalDriveVersion;
2703
+ const sessions = sessionsRef.current;
2704
+ const scope = scopeRef.current;
2705
+ if (!sessions?.goals || !scope) {
2706
+ setGoalStatus(null);
2707
+ return;
2708
+ }
2709
+ const generation = conversationGenRef.current;
2710
+ const sessionId = scope.sessionId;
2711
+ let disposed = false;
2712
+ void sessions.goals
2713
+ .getGoal(sessionId, sessions.tenantId)
2714
+ .then((goal) => {
2715
+ if (disposed ||
2716
+ conversationGenRef.current !== generation ||
2717
+ scopeRef.current?.sessionId !== sessionId) {
2718
+ return;
2719
+ }
2720
+ setGoalStatus(goal);
2721
+ })
2722
+ .catch(() => {
2723
+ if (!disposed && conversationGenRef.current === generation)
2724
+ setGoalStatus(null);
2725
+ });
2726
+ return () => {
2727
+ disposed = true;
2728
+ };
2729
+ }, [goalDriveVersion, phase, session]);
2605
2730
  // Admit automatic work only at a whole-App durable boundary. This effect
2606
2731
  // reserves; it never starts a turn itself. The one queue pump below remains
2607
2732
  // the sole starter, which is what keeps a human prompt that arrives during
@@ -2622,6 +2747,7 @@ export function App({ ctx: initialCtx }) {
2622
2747
  conversationMutationRef.current ||
2623
2748
  exportingRef.current ||
2624
2749
  compactingRef.current ||
2750
+ choicePickerRef.current ||
2625
2751
  copyPickerRef.current ||
2626
2752
  goalCommandInFlightRef.current) {
2627
2753
  return;
@@ -2647,6 +2773,7 @@ export function App({ ctx: initialCtx }) {
2647
2773
  conversationMutationRef.current ||
2648
2774
  exportingRef.current ||
2649
2775
  compactingRef.current ||
2776
+ choicePickerRef.current ||
2650
2777
  copyPickerRef.current ||
2651
2778
  goalCommandInFlightRef.current ||
2652
2779
  queuedRef.current.length > 0 ||
@@ -2705,6 +2832,7 @@ export function App({ ctx: initialCtx }) {
2705
2832
  conversationMutation,
2706
2833
  session,
2707
2834
  state,
2835
+ choicePicker,
2708
2836
  copyPicker,
2709
2837
  wakeGoalDriver,
2710
2838
  ]);
@@ -2720,6 +2848,7 @@ export function App({ ctx: initialCtx }) {
2720
2848
  conversationMutationRef.current ||
2721
2849
  exportingRef.current ||
2722
2850
  compactingRef.current ||
2851
+ choicePickerRef.current ||
2723
2852
  copyPickerRef.current ||
2724
2853
  goalCommandInFlightRef.current)
2725
2854
  return;
@@ -2735,6 +2864,7 @@ export function App({ ctx: initialCtx }) {
2735
2864
  phase,
2736
2865
  queued,
2737
2866
  queuePause,
2867
+ choicePicker,
2738
2868
  copyPicker,
2739
2869
  dequeueQueued,
2740
2870
  hasUnsettledTurn,
@@ -2978,6 +3108,34 @@ export function App({ ctx: initialCtx }) {
2978
3108
  resolvePermission({ kind: 'approve-all' });
2979
3109
  return;
2980
3110
  }
3111
+ // Finite slash-command choices own the keyboard below a live permission
3112
+ // request and above the ordinary composer. Their values are a captured
3113
+ // menu, so a provider/session change cannot silently reinterpret Enter.
3114
+ if (choicePickerRef.current) {
3115
+ const options = choicePickerRef.current.options;
3116
+ if (key.escape || (key.ctrl && input === 'c')) {
3117
+ setChoicePicker(null);
3118
+ return;
3119
+ }
3120
+ if (key.upArrow) {
3121
+ setSelectedChoice((index) => Math.max(0, index - 1));
3122
+ return;
3123
+ }
3124
+ if (key.downArrow) {
3125
+ setSelectedChoice((index) => Math.min(options.length - 1, index + 1));
3126
+ return;
3127
+ }
3128
+ if (key.return) {
3129
+ applyChoiceSelection(selectedChoice);
3130
+ return;
3131
+ }
3132
+ if (/^[1-9]$/.test(input)) {
3133
+ const index = Number(input) - 1;
3134
+ if (options[index])
3135
+ applyChoiceSelection(index);
3136
+ }
3137
+ return;
3138
+ }
2981
3139
  // `/copy` holds a source snapshot, not a lifecycle phase. Permission
2982
3140
  // stays above it in this handler so an approval request that arrives
2983
3141
  // while the picker is open cannot have its keys stolen by the chooser.
@@ -3082,19 +3240,29 @@ export function App({ ctx: initialCtx }) {
3082
3240
  // Background is left natural — we inherit the terminal's own background
3083
3241
  // and only theme the foreground. Forcing
3084
3242
  // a filled bg left mismatched patches around bordered areas, so we don't.
3085
- return (_jsxs(Box, { flexDirection: "column", children: [phase !== 'ready' ? (_jsx(Banner, { version: ctx.version, session: session, permissionMode: permissionMode, cwd: ctx.cwd })) : null, _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [phase === 'trust' ? (_jsx(TrustPrompt, { cwd: ctx.cwd })) : phase === 'resume' ? (_jsx(ResumePicker, { conversations: resumeList, selected: selectedResume })) : phase === 'edit' ? (_jsx(EditPromptPicker, { prompts: editList, selected: selectedEdit })) : phase === 'picker' ? (_jsx(Picker, { detected: pickerDetected ?? detected, currentProvider: currentProvider, selectionKind: pickerSelectionKind, currentModel: session?.modelSummary ?? null, initialView: pickerInitialView, onSubmit: handlePickerSubmit, onCancel: handlePickerCancel, onCredential: handleTypedCredential, onLogin: (provider, signal) => void startLoginFromPicker(provider, signal), keyEntryFor: keyEntryFor, notice: pickerNotice })) : (_jsxs(_Fragment, { children: [_jsx(TranscriptFrame, { children: _jsx(Transcript, { messages: finalized, pending: messages.find((m) => m.pending) ?? null, state: state, settled: window.settled, resetKey: resetKey, raw: rawOutput, header: phase === 'ready' ? (_jsx(Banner, { version: ctx.version, session: session, permissionMode: permissionMode, cwd: ctx.cwd })) : undefined }) }), spacerRows > 0 ? _jsx(Box, { height: spacerRows }) : null, _jsx(LiveActivity, { activeTools: activeTools, thinking: state === 'thinking' && !messages.some((m) => m.pending) }), permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls })) : copyPicker ? (_jsx(CopyPicker, { targets: copyPicker.targets, selected: selectedCopy })) : null, _jsxs(ComposerFrame, { focus: state === 'idle' &&
3243
+ return (_jsxs(Box, { flexDirection: "column", children: [phase !== 'ready' ? (_jsx(Banner, { version: ctx.version, session: session, permissionMode: permissionMode, cwd: ctx.cwd })) : null, _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [phase === 'trust' ? (_jsx(TrustPrompt, { cwd: ctx.cwd })) : phase === 'resume' ? (_jsx(ResumePicker, { conversations: resumeList, selected: selectedResume })) : phase === 'edit' ? (_jsx(EditPromptPicker, { prompts: editList, selected: selectedEdit })) : phase === 'picker' ? (_jsx(Picker, { detected: pickerDetected ?? detected, currentProvider: currentProvider, selectionKind: pickerSelectionKind, currentModel: session?.modelSummary ?? null, initialView: pickerInitialView, onSubmit: handlePickerSubmit, onCancel: handlePickerCancel, onCredential: handleTypedCredential, onLogin: (provider, signal) => void startLoginFromPicker(provider, signal), keyEntryFor: keyEntryFor, notice: pickerNotice })) : (_jsxs(_Fragment, { children: [_jsx(TranscriptFrame, { children: _jsx(Transcript, { messages: finalized, pending: messages.find((m) => m.pending) ?? null, state: state, settled: window.settled, resetKey: resetKey, raw: rawOutput, header: phase === 'ready' ? (_jsx(Banner, { version: ctx.version, session: session, permissionMode: permissionMode, cwd: ctx.cwd })) : undefined }) }), spacerRows > 0 ? _jsx(Box, { height: spacerRows }) : null, _jsx(LiveActivity, { activeTools: activeTools, thinking: state === 'thinking' && !messages.some((m) => m.pending) }), permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls })) : choicePicker ? (_jsx(ChoicePicker, { title: choicePicker.title, notice: choicePicker.notice, options: choicePicker.options, selected: selectedChoice })) : copyPicker ? (_jsx(CopyPicker, { targets: copyPicker.targets, selected: selectedCopy })) : null, _jsxs(ComposerFrame, { focus: state === 'idle' &&
3086
3244
  phase === 'ready' &&
3087
3245
  conversationMutation === null &&
3088
- copyPicker === null, hidden: permission !== null || copyPicker !== null, children: [queued.length > 0 && permission === null && copyPicker === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: [queuePause ? '⏸' : '⏎', " ", queued.length, " message", queued.length > 1 ? 's' : '', " queued \u2014", ' ', queuePause
3246
+ choicePicker === null &&
3247
+ copyPicker === null, hidden: permission !== null || choicePicker !== null || copyPicker !== null, children: [queued.length > 0 &&
3248
+ permission === null &&
3249
+ choicePicker === null &&
3250
+ copyPicker === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: [queuePause ? '⏸' : '⏎', " ", queued.length, " message", queued.length > 1 ? 's' : '', " queued \u2014", ' ', queuePause
3089
3251
  ? `paused after a ${queuePause.outcome} turn; send a message or change model to continue`
3090
3252
  : 'sending when ready'] }) })) : null, _jsx(Composer, { disabled: phase !== 'ready' ||
3091
3253
  state === 'awaiting-permission' ||
3092
3254
  compacting ||
3093
- conversationMutation !== null, hidden: permission !== null || copyPicker !== null,
3255
+ conversationMutation !== null, hidden: permission !== null || choicePicker !== null || copyPicker !== null,
3094
3256
  // A turn is running, so Esc is the interrupt and not
3095
3257
  // the composer's clear.
3096
- escapeInterrupts: !compacting && (state === 'thinking' || state === 'tool'), onSubmit: handleSubmit, onNotice: (text) => pushMessage('system', text), onEditPrevious: openPromptEditor, draftToRestore: composerDraft, onDraftRestored: (token) => setComposerDraft((draft) => (draft?.token === token ? null : draft)), userCommands: userCommands, history: history })] })] })), _jsx(Box, { paddingTop: 1, children: _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary
3097
- ? `${session.modelSummary}${reasoningEffort ? ` · effort ${reasoningEffort}` : ''}`
3258
+ escapeInterrupts: !compacting && (state === 'thinking' || state === 'tool'), onSubmit: handleSubmit, onNotice: (text) => pushMessage('system', text), onEditPrevious: openPromptEditor, draftToRestore: composerDraft, onDraftRestored: (token) => setComposerDraft((draft) => (draft?.token === token ? null : draft)), userCommands: userCommands, history: history })] })] })), _jsx(Box, { paddingTop: 1, children: _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, effort: reasoningEffort ?? 'default', goal: phase === 'ready' &&
3259
+ state === 'idle' &&
3260
+ conversationMutation === null &&
3261
+ !compacting &&
3262
+ permission === null &&
3263
+ choicePicker === null &&
3264
+ copyPicker === null
3265
+ ? goalStatusLabel(goalStatus)
3098
3266
  : null, state: state, hint: conversationMutation === 'fork'
3099
3267
  ? 'forking conversation — input is paused'
3100
3268
  : conversationMutation === 'edit'
@@ -3105,9 +3273,13 @@ export function App({ ctx: initialCtx }) {
3105
3273
  ? 'compacting conversation — input is paused'
3106
3274
  : permission
3107
3275
  ? hintForPhase(phase, state, session?.hasProvider === true)
3108
- : copyPicker
3109
- ? 'copy target open — ↑↓ / 1–9 select · esc cancel'
3110
- : hintForPhase(phase, state, session?.hasProvider === true), usage: usage, context: context }) })] })] }));
3276
+ : choicePicker
3277
+ ? 'choice open — ↑↓ / 1–9 select · enter apply · esc cancel'
3278
+ : copyPicker
3279
+ ? 'copy target open — ↑↓ / 1–9 select · esc cancel'
3280
+ : goalStatus && phase === 'ready' && state === 'idle'
3281
+ ? undefined
3282
+ : hintForPhase(phase, state, session?.hasProvider === true), usage: usage, context: context }) })] })] }));
3111
3283
  }
3112
3284
  function Banner({ version, session, permissionMode, cwd, }) {
3113
3285
  const cols = process.stdout.columns ?? 80;
@@ -3165,6 +3337,62 @@ canReturn = false) {
3165
3337
  return 'agent is working — esc to interrupt';
3166
3338
  return '/help · Esc×2 edit previous · @file / Ctrl+V attach · Ctrl+C ×2 to exit';
3167
3339
  }
3340
+ function goalStatusLabel(goal) {
3341
+ if (!goal)
3342
+ return null;
3343
+ switch (goal.phase) {
3344
+ case 'active':
3345
+ return 'Pursuing goal';
3346
+ case 'paused':
3347
+ return 'Goal paused (/goal resume)';
3348
+ case 'blocked':
3349
+ return 'Goal stalled (/goal resume)';
3350
+ case 'complete':
3351
+ return 'Goal achieved';
3352
+ }
3353
+ }
3354
+ function permissionModeDescription(mode) {
3355
+ switch (mode) {
3356
+ case 'prompt':
3357
+ return 'Ask before an undecided tool call runs';
3358
+ case 'auto':
3359
+ return 'Approve undecided calls unless a rule or safety gate refuses';
3360
+ case 'strict':
3361
+ return 'Refuse undecided calls; explicit allow rules still work';
3362
+ }
3363
+ }
3364
+ function permissionPickerNotice(session) {
3365
+ const notices = [];
3366
+ if (session.approvalLatched()) {
3367
+ notices.push('Approve all is active; applying any mode revokes it.');
3368
+ }
3369
+ const exempt = session.promptExemptTools();
3370
+ if (exempt.length > 0)
3371
+ notices.push(`Never prompted: ${exempt.join(', ')}.`);
3372
+ return notices.length > 0 ? notices.join(' ') : undefined;
3373
+ }
3374
+ function reasoningEffortDescription(effort) {
3375
+ switch (effort) {
3376
+ case undefined:
3377
+ return 'Use the provider and model default';
3378
+ case 'none':
3379
+ return 'No deliberate reasoning where the model supports it';
3380
+ case 'minimal':
3381
+ return 'Fastest responses with minimal reasoning';
3382
+ case 'low':
3383
+ return 'Fast responses with light reasoning';
3384
+ case 'medium':
3385
+ return 'Balance speed and reasoning depth';
3386
+ case 'high':
3387
+ return 'Deeper reasoning for complex problems';
3388
+ case 'xhigh':
3389
+ return 'Very deep reasoning for hard problems';
3390
+ case 'max':
3391
+ return 'Maximum reasoning depth offered by this model';
3392
+ case 'ultra':
3393
+ return 'Highest host-level reasoning setting when supported';
3394
+ }
3395
+ }
3168
3396
  /**
3169
3397
  * The summary text, as collapsible detail under the compaction row.
3170
3398
  *