@namzu/cli 14.1.0 → 14.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -9
- package/dist/commands/login.d.ts +21 -3
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +33 -21
- package/dist/commands/login.js.map +1 -1
- package/dist/integrations/providers/discover.d.ts +4 -0
- package/dist/integrations/providers/discover.d.ts.map +1 -1
- package/dist/integrations/providers/discover.js +55 -37
- package/dist/integrations/providers/discover.js.map +1 -1
- package/dist/integrations/providers/harness-credentials.d.ts +44 -9
- package/dist/integrations/providers/harness-credentials.d.ts.map +1 -1
- package/dist/integrations/providers/harness-credentials.js +140 -11
- package/dist/integrations/providers/harness-credentials.js.map +1 -1
- package/dist/integrations/providers/identity.d.ts +20 -24
- package/dist/integrations/providers/identity.d.ts.map +1 -1
- package/dist/integrations/providers/identity.js +20 -24
- package/dist/integrations/providers/identity.js.map +1 -1
- package/dist/integrations/providers/index.d.ts +1 -1
- package/dist/integrations/providers/index.d.ts.map +1 -1
- package/dist/integrations/providers/index.js +1 -1
- package/dist/integrations/providers/index.js.map +1 -1
- package/dist/integrations/providers/oauth.d.ts +8 -3
- package/dist/integrations/providers/oauth.d.ts.map +1 -1
- package/dist/integrations/providers/oauth.js +37 -11
- package/dist/integrations/providers/oauth.js.map +1 -1
- package/dist/integrations/providers/subscription-login.d.ts +7 -41
- package/dist/integrations/providers/subscription-login.d.ts.map +1 -1
- package/dist/integrations/providers/subscription-login.js +26 -149
- package/dist/integrations/providers/subscription-login.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +316 -83
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/ChoicePicker.d.ts +14 -0
- package/dist/tui/ChoicePicker.d.ts.map +1 -0
- package/dist/tui/ChoicePicker.js +15 -0
- package/dist/tui/ChoicePicker.js.map +1 -0
- package/dist/tui/Picker.d.ts +4 -2
- package/dist/tui/Picker.d.ts.map +1 -1
- package/dist/tui/Picker.js +99 -10
- package/dist/tui/Picker.js.map +1 -1
- package/dist/tui/StatusBar.d.ts +24 -40
- package/dist/tui/StatusBar.d.ts.map +1 -1
- package/dist/tui/StatusBar.js +71 -106
- package/dist/tui/StatusBar.js.map +1 -1
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +10 -8
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/login-prompt.d.ts +0 -1
- package/dist/tui/login-prompt.d.ts.map +1 -1
- package/dist/tui/login-prompt.js +1 -6
- package/dist/tui/login-prompt.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +8 -0
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +20 -15
- package/dist/tui/slashCommands.js.map +1 -1
- package/package.json +4 -4
package/dist/tui/App.js
CHANGED
|
@@ -19,7 +19,7 @@ import { pinTrustedProjectPath, resolveTrustedProjectContext, } from '../config/
|
|
|
19
19
|
import { visibleProjectInstructionPath } from '../context/project-path.js';
|
|
20
20
|
import { writeClipboardText } from '../integrations/clipboard/text.js';
|
|
21
21
|
import { terminalNotificationEnabled, writeTerminalNotification, } from '../integrations/notifications/terminal.js';
|
|
22
|
-
import { beginCodexDeviceLogin, beginSubscriptionLogin, clearAllStoredCredentials, credentialsPath, primaryProvider, readStoredCodexCredential, readStoredSubscriptionCredential, signedInSubscriptionProviders, writePreferences, } from '../integrations/providers/index.js';
|
|
22
|
+
import { beginCodexDeviceLogin, beginSubscriptionLogin, clearAllStoredCredentials, credentialsPath, parsePastedInput, primaryProvider, readStoredCodexCredential, readStoredSubscriptionCredential, signedInSubscriptionProviders, writePreferences, } from '../integrations/providers/index.js';
|
|
23
23
|
import { appendMessages, forkConversation, forkConversationBeforeUser, listRecent, loadConversation, openSessions, replaceConversation, requireWritableConversation, setTitle, startConversation, titleOf, } from '../integrations/sessions/store.js';
|
|
24
24
|
import { conversationMarkdown, writeConversationExport, } from '../integrations/sessions/transcript-export.js';
|
|
25
25
|
import { isTrusted, trustDir } from '../integrations/trust/store.js';
|
|
@@ -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
|
|
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];
|
|
@@ -762,15 +836,14 @@ export function App({ ctx: initialCtx }) {
|
|
|
762
836
|
* nobody can see. Everything this path says goes into `pickerNotice`, which
|
|
763
837
|
* the picker renders.
|
|
764
838
|
*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
* address from standard input and exists for exactly that machine.
|
|
839
|
+
* The registered browser callback shows a code rather than returning to a
|
|
840
|
+
* local listener. The picker therefore owns the matching paste field; sending a
|
|
841
|
+
* first-run operator to another terminal would make the sign-in technically
|
|
842
|
+
* started here but impossible to finish on the screen that started it.
|
|
770
843
|
*/
|
|
771
844
|
const startLoginFromPicker = useCallback(async (provider, signal) => {
|
|
772
845
|
if (signal.aborted)
|
|
773
|
-
return;
|
|
846
|
+
return 'failed';
|
|
774
847
|
cancelPendingLogin();
|
|
775
848
|
if (provider === 'codex') {
|
|
776
849
|
let start;
|
|
@@ -781,11 +854,11 @@ export function App({ ctx: initialCtx }) {
|
|
|
781
854
|
if (!signal.aborted) {
|
|
782
855
|
setPickerNotice(`Could not start Codex sign-in: ${error instanceof Error ? error.message : String(error)}`);
|
|
783
856
|
}
|
|
784
|
-
return;
|
|
857
|
+
return 'failed';
|
|
785
858
|
}
|
|
786
859
|
if (signal.aborted) {
|
|
787
860
|
start.cancel();
|
|
788
|
-
return;
|
|
861
|
+
return 'failed';
|
|
789
862
|
}
|
|
790
863
|
codexLoginRef.current = start;
|
|
791
864
|
setPickerNotice(describeCodexDeviceLoginStart({
|
|
@@ -795,7 +868,7 @@ export function App({ ctx: initialCtx }) {
|
|
|
795
868
|
}));
|
|
796
869
|
const outcome = await start.waitForCompletion();
|
|
797
870
|
if (codexLoginRef.current !== start || signal.aborted)
|
|
798
|
-
return;
|
|
871
|
+
return 'failed';
|
|
799
872
|
codexLoginRef.current = null;
|
|
800
873
|
start.cancel();
|
|
801
874
|
setPickerNotice(describeLoginOutcome(outcome));
|
|
@@ -803,7 +876,7 @@ export function App({ ctx: initialCtx }) {
|
|
|
803
876
|
setPickerInitialView('providers');
|
|
804
877
|
await runProbeRef.current?.(signal);
|
|
805
878
|
}
|
|
806
|
-
return;
|
|
879
|
+
return 'finished';
|
|
807
880
|
}
|
|
808
881
|
let start;
|
|
809
882
|
try {
|
|
@@ -811,13 +884,13 @@ export function App({ ctx: initialCtx }) {
|
|
|
811
884
|
}
|
|
812
885
|
catch (err) {
|
|
813
886
|
if (signal.aborted)
|
|
814
|
-
return;
|
|
887
|
+
return 'failed';
|
|
815
888
|
setPickerNotice(`Could not start a sign-in: ${err instanceof Error ? err.message : String(err)}`);
|
|
816
|
-
return;
|
|
889
|
+
return 'failed';
|
|
817
890
|
}
|
|
818
891
|
if (signal.aborted) {
|
|
819
892
|
start.cancel();
|
|
820
|
-
return;
|
|
893
|
+
return 'failed';
|
|
821
894
|
}
|
|
822
895
|
loginRef.current = start;
|
|
823
896
|
const cancelForPicker = () => {
|
|
@@ -832,36 +905,39 @@ export function App({ ctx: initialCtx }) {
|
|
|
832
905
|
signal.addEventListener('abort', cancelForPicker, { once: true });
|
|
833
906
|
if (signal.aborted) {
|
|
834
907
|
cancelForPicker();
|
|
835
|
-
return;
|
|
908
|
+
return 'failed';
|
|
836
909
|
}
|
|
837
910
|
setPickerNotice(describeLoginStart({
|
|
838
911
|
url: start.url,
|
|
839
|
-
loopback: start.loopback,
|
|
840
912
|
browserOpened: openInBrowser(start.url),
|
|
841
|
-
|
|
842
|
-
// slash command is what made the feature unreachable; telling them
|
|
843
|
-
// to run a command in another terminal is something they can do
|
|
844
|
-
// from where they are sitting.
|
|
845
|
-
completionHint: 'run "namzu login" in a terminal and paste it there',
|
|
913
|
+
completionHint: 'paste it below and press enter',
|
|
846
914
|
}));
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const
|
|
851
|
-
if (
|
|
852
|
-
|
|
915
|
+
return 'awaiting-input';
|
|
916
|
+
}, [cancelPendingLogin]);
|
|
917
|
+
const finishLoginFromPicker = useCallback(async (pasted, signal) => {
|
|
918
|
+
const pending = loginRef.current;
|
|
919
|
+
if (!pending) {
|
|
920
|
+
setPickerNotice('There is no Claude sign-in waiting for this code. Start it again.');
|
|
921
|
+
return 'finished';
|
|
922
|
+
}
|
|
923
|
+
if (!parsePastedInput(pasted).code) {
|
|
924
|
+
setPickerNotice('That does not contain an authorization code. Paste the whole finished address, or the code Claude showed.');
|
|
925
|
+
return 'retry';
|
|
926
|
+
}
|
|
927
|
+
const outcome = await pending.completeWithPastedCode(pasted);
|
|
928
|
+
if (loginRef.current !== pending || signal.aborted)
|
|
929
|
+
return 'finished';
|
|
853
930
|
loginRef.current = null;
|
|
854
|
-
|
|
931
|
+
loginAbortCleanupRef.current?.();
|
|
855
932
|
loginAbortCleanupRef.current = null;
|
|
856
|
-
|
|
857
|
-
if (!outcome.ok && outcome.reason.includes('cancelled'))
|
|
858
|
-
return;
|
|
933
|
+
pending.cancel();
|
|
859
934
|
setPickerNotice(describeLoginOutcome(outcome));
|
|
860
935
|
if (outcome.ok) {
|
|
861
936
|
setPickerInitialView('providers');
|
|
862
937
|
await runProbeRef.current?.(signal);
|
|
863
938
|
}
|
|
864
|
-
|
|
939
|
+
return 'finished';
|
|
940
|
+
}, []);
|
|
865
941
|
const runProbe = useCallback(async (signal) => {
|
|
866
942
|
try {
|
|
867
943
|
if (signal?.aborted)
|
|
@@ -2068,6 +2144,10 @@ export function App({ ctx: initialCtx }) {
|
|
|
2068
2144
|
setPhase('picker');
|
|
2069
2145
|
return;
|
|
2070
2146
|
case 'permission-mode': {
|
|
2147
|
+
applyPermissionMode(slash.mode);
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
case 'permission-mode-picker': {
|
|
2071
2151
|
if (!session?.hasProvider) {
|
|
2072
2152
|
pushMessage('system', 'No active session — pick a provider before changing permissions.');
|
|
2073
2153
|
return;
|
|
@@ -2077,24 +2157,34 @@ export function App({ ctx: initialCtx }) {
|
|
|
2077
2157
|
hasUnsettledTurn() ||
|
|
2078
2158
|
queuedRef.current.length > 0 ||
|
|
2079
2159
|
permissionResolveRef.current !== null ||
|
|
2080
|
-
compactingRef.current
|
|
2081
|
-
|
|
2082
|
-
|
|
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.');
|
|
2160
|
+
compactingRef.current ||
|
|
2161
|
+
!session.resetApprovalLatch) {
|
|
2162
|
+
pushMessage('system', 'Permission choices are unavailable until the active turn, prompt, compaction, queued work, or embedded-session approval latch can be safely settled.');
|
|
2086
2163
|
return;
|
|
2087
2164
|
}
|
|
2088
|
-
|
|
2089
|
-
permissionModeRef.current
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2165
|
+
const values = ['prompt', 'auto', 'strict'];
|
|
2166
|
+
setSelectedChoice(Math.max(0, values.indexOf(permissionModeRef.current)));
|
|
2167
|
+
setChoicePicker({
|
|
2168
|
+
kind: 'permission-mode',
|
|
2169
|
+
title: 'Select Permission Mode',
|
|
2170
|
+
notice: permissionPickerNotice(session),
|
|
2171
|
+
values,
|
|
2172
|
+
options: values.map((mode) => ({
|
|
2173
|
+
label: mode,
|
|
2174
|
+
description: permissionModeDescription(mode),
|
|
2175
|
+
current: mode === permissionModeRef.current,
|
|
2176
|
+
})),
|
|
2177
|
+
});
|
|
2093
2178
|
return;
|
|
2094
2179
|
}
|
|
2095
2180
|
case 'reasoning-effort': {
|
|
2096
|
-
|
|
2097
|
-
|
|
2181
|
+
applyReasoningEffort(slash.effort ?? undefined);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
case 'reasoning-effort-picker': {
|
|
2185
|
+
const levels = session?.reasoningEffortLevels;
|
|
2186
|
+
if (!session?.hasProvider || levels === undefined) {
|
|
2187
|
+
pushMessage('system', 'No exact reasoning-effort menu is available for the current session.');
|
|
2098
2188
|
return;
|
|
2099
2189
|
}
|
|
2100
2190
|
if (state !== 'idle' ||
|
|
@@ -2103,20 +2193,22 @@ export function App({ ctx: initialCtx }) {
|
|
|
2103
2193
|
queuedRef.current.length > 0 ||
|
|
2104
2194
|
permissionResolveRef.current !== null ||
|
|
2105
2195
|
compactingRef.current) {
|
|
2106
|
-
pushMessage('system', 'Reasoning
|
|
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.');
|
|
2196
|
+
pushMessage('system', 'Reasoning-effort choices are unavailable until the active turn, prompt, compaction, and queued work settle.');
|
|
2112
2197
|
return;
|
|
2113
2198
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2199
|
+
const values = [undefined, ...levels];
|
|
2200
|
+
const current = reasoningEffortRef.current;
|
|
2201
|
+
setSelectedChoice(Math.max(0, values.findIndex((value) => value === current)));
|
|
2202
|
+
setChoicePicker({
|
|
2203
|
+
kind: 'reasoning-effort',
|
|
2204
|
+
title: `Select Reasoning Level for ${session.modelSummary ?? 'current model'}`,
|
|
2205
|
+
values,
|
|
2206
|
+
options: values.map((effort) => ({
|
|
2207
|
+
label: effort ?? 'default',
|
|
2208
|
+
description: reasoningEffortDescription(effort),
|
|
2209
|
+
current: effort === current,
|
|
2210
|
+
})),
|
|
2211
|
+
});
|
|
2120
2212
|
return;
|
|
2121
2213
|
}
|
|
2122
2214
|
case 'login':
|
|
@@ -2587,6 +2679,8 @@ export function App({ ctx: initialCtx }) {
|
|
|
2587
2679
|
}, [
|
|
2588
2680
|
activeSkills,
|
|
2589
2681
|
advanceQueueContinuation,
|
|
2682
|
+
applyPermissionMode,
|
|
2683
|
+
applyReasoningEffort,
|
|
2590
2684
|
ctx.cwd,
|
|
2591
2685
|
doResume,
|
|
2592
2686
|
enqueueQueued,
|
|
@@ -2596,12 +2690,45 @@ export function App({ ctx: initialCtx }) {
|
|
|
2596
2690
|
pushMessage,
|
|
2597
2691
|
rawOutput,
|
|
2598
2692
|
resetTranscript,
|
|
2693
|
+
setChoicePicker,
|
|
2599
2694
|
setCopyPicker,
|
|
2600
2695
|
setReasoningEffort,
|
|
2601
2696
|
slashCtx,
|
|
2602
2697
|
startFreshConversation,
|
|
2603
2698
|
state,
|
|
2604
2699
|
]);
|
|
2700
|
+
// Keep the footer on the same durable goal record the driver mutates. A ref
|
|
2701
|
+
// alone cannot repaint React, so every goal mutation bumps goalDriveVersion;
|
|
2702
|
+
// generation/session checks stop a late disk read labelling a resumed chat.
|
|
2703
|
+
useEffect(() => {
|
|
2704
|
+
void goalDriveVersion;
|
|
2705
|
+
const sessions = sessionsRef.current;
|
|
2706
|
+
const scope = scopeRef.current;
|
|
2707
|
+
if (!sessions?.goals || !scope) {
|
|
2708
|
+
setGoalStatus(null);
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
2711
|
+
const generation = conversationGenRef.current;
|
|
2712
|
+
const sessionId = scope.sessionId;
|
|
2713
|
+
let disposed = false;
|
|
2714
|
+
void sessions.goals
|
|
2715
|
+
.getGoal(sessionId, sessions.tenantId)
|
|
2716
|
+
.then((goal) => {
|
|
2717
|
+
if (disposed ||
|
|
2718
|
+
conversationGenRef.current !== generation ||
|
|
2719
|
+
scopeRef.current?.sessionId !== sessionId) {
|
|
2720
|
+
return;
|
|
2721
|
+
}
|
|
2722
|
+
setGoalStatus(goal);
|
|
2723
|
+
})
|
|
2724
|
+
.catch(() => {
|
|
2725
|
+
if (!disposed && conversationGenRef.current === generation)
|
|
2726
|
+
setGoalStatus(null);
|
|
2727
|
+
});
|
|
2728
|
+
return () => {
|
|
2729
|
+
disposed = true;
|
|
2730
|
+
};
|
|
2731
|
+
}, [goalDriveVersion, phase, session]);
|
|
2605
2732
|
// Admit automatic work only at a whole-App durable boundary. This effect
|
|
2606
2733
|
// reserves; it never starts a turn itself. The one queue pump below remains
|
|
2607
2734
|
// the sole starter, which is what keeps a human prompt that arrives during
|
|
@@ -2622,6 +2749,7 @@ export function App({ ctx: initialCtx }) {
|
|
|
2622
2749
|
conversationMutationRef.current ||
|
|
2623
2750
|
exportingRef.current ||
|
|
2624
2751
|
compactingRef.current ||
|
|
2752
|
+
choicePickerRef.current ||
|
|
2625
2753
|
copyPickerRef.current ||
|
|
2626
2754
|
goalCommandInFlightRef.current) {
|
|
2627
2755
|
return;
|
|
@@ -2647,6 +2775,7 @@ export function App({ ctx: initialCtx }) {
|
|
|
2647
2775
|
conversationMutationRef.current ||
|
|
2648
2776
|
exportingRef.current ||
|
|
2649
2777
|
compactingRef.current ||
|
|
2778
|
+
choicePickerRef.current ||
|
|
2650
2779
|
copyPickerRef.current ||
|
|
2651
2780
|
goalCommandInFlightRef.current ||
|
|
2652
2781
|
queuedRef.current.length > 0 ||
|
|
@@ -2705,6 +2834,7 @@ export function App({ ctx: initialCtx }) {
|
|
|
2705
2834
|
conversationMutation,
|
|
2706
2835
|
session,
|
|
2707
2836
|
state,
|
|
2837
|
+
choicePicker,
|
|
2708
2838
|
copyPicker,
|
|
2709
2839
|
wakeGoalDriver,
|
|
2710
2840
|
]);
|
|
@@ -2720,6 +2850,7 @@ export function App({ ctx: initialCtx }) {
|
|
|
2720
2850
|
conversationMutationRef.current ||
|
|
2721
2851
|
exportingRef.current ||
|
|
2722
2852
|
compactingRef.current ||
|
|
2853
|
+
choicePickerRef.current ||
|
|
2723
2854
|
copyPickerRef.current ||
|
|
2724
2855
|
goalCommandInFlightRef.current)
|
|
2725
2856
|
return;
|
|
@@ -2735,6 +2866,7 @@ export function App({ ctx: initialCtx }) {
|
|
|
2735
2866
|
phase,
|
|
2736
2867
|
queued,
|
|
2737
2868
|
queuePause,
|
|
2869
|
+
choicePicker,
|
|
2738
2870
|
copyPicker,
|
|
2739
2871
|
dequeueQueued,
|
|
2740
2872
|
hasUnsettledTurn,
|
|
@@ -2978,6 +3110,34 @@ export function App({ ctx: initialCtx }) {
|
|
|
2978
3110
|
resolvePermission({ kind: 'approve-all' });
|
|
2979
3111
|
return;
|
|
2980
3112
|
}
|
|
3113
|
+
// Finite slash-command choices own the keyboard below a live permission
|
|
3114
|
+
// request and above the ordinary composer. Their values are a captured
|
|
3115
|
+
// menu, so a provider/session change cannot silently reinterpret Enter.
|
|
3116
|
+
if (choicePickerRef.current) {
|
|
3117
|
+
const options = choicePickerRef.current.options;
|
|
3118
|
+
if (key.escape || (key.ctrl && input === 'c')) {
|
|
3119
|
+
setChoicePicker(null);
|
|
3120
|
+
return;
|
|
3121
|
+
}
|
|
3122
|
+
if (key.upArrow) {
|
|
3123
|
+
setSelectedChoice((index) => Math.max(0, index - 1));
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
if (key.downArrow) {
|
|
3127
|
+
setSelectedChoice((index) => Math.min(options.length - 1, index + 1));
|
|
3128
|
+
return;
|
|
3129
|
+
}
|
|
3130
|
+
if (key.return) {
|
|
3131
|
+
applyChoiceSelection(selectedChoice);
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
3134
|
+
if (/^[1-9]$/.test(input)) {
|
|
3135
|
+
const index = Number(input) - 1;
|
|
3136
|
+
if (options[index])
|
|
3137
|
+
applyChoiceSelection(index);
|
|
3138
|
+
}
|
|
3139
|
+
return;
|
|
3140
|
+
}
|
|
2981
3141
|
// `/copy` holds a source snapshot, not a lifecycle phase. Permission
|
|
2982
3142
|
// stays above it in this handler so an approval request that arrives
|
|
2983
3143
|
// while the picker is open cannot have its keys stolen by the chooser.
|
|
@@ -3082,32 +3242,46 @@ export function App({ ctx: initialCtx }) {
|
|
|
3082
3242
|
// Background is left natural — we inherit the terminal's own background
|
|
3083
3243
|
// and only theme the foreground. Forcing
|
|
3084
3244
|
// a filled bg left mismatched patches around bordered areas, so we don't.
|
|
3085
|
-
return (
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3245
|
+
return (_jsx(Box, { flexDirection: "column", children: _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: startLoginFromPicker, onLoginComplete: finishLoginFromPicker, 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' &&
|
|
3246
|
+
phase === 'ready' &&
|
|
3247
|
+
conversationMutation === null &&
|
|
3248
|
+
choicePicker === null &&
|
|
3249
|
+
copyPicker === null, hidden: permission !== null || choicePicker !== null || copyPicker !== null, children: [queued.length > 0 &&
|
|
3250
|
+
permission === null &&
|
|
3251
|
+
choicePicker === null &&
|
|
3252
|
+
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
|
|
3253
|
+
? `paused after a ${queuePause.outcome} turn; send a message or change model to continue`
|
|
3254
|
+
: 'sending when ready'] }) })) : null, _jsx(Composer, { disabled: phase !== 'ready' ||
|
|
3255
|
+
state === 'awaiting-permission' ||
|
|
3256
|
+
compacting ||
|
|
3257
|
+
conversationMutation !== null, hidden: permission !== null || choicePicker !== null || copyPicker !== null,
|
|
3258
|
+
// A turn is running, so Esc is the interrupt and not
|
|
3259
|
+
// the composer's clear.
|
|
3260
|
+
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' &&
|
|
3261
|
+
state === 'idle' &&
|
|
3262
|
+
conversationMutation === null &&
|
|
3263
|
+
!compacting &&
|
|
3264
|
+
permission === null &&
|
|
3265
|
+
choicePicker === null &&
|
|
3266
|
+
copyPicker === null
|
|
3267
|
+
? goalStatusLabel(goalStatus)
|
|
3268
|
+
: null, state: state, hint: conversationMutation === 'fork'
|
|
3269
|
+
? 'forking conversation — input is paused'
|
|
3270
|
+
: conversationMutation === 'edit'
|
|
3271
|
+
? 'branching before prompt — input is paused'
|
|
3272
|
+
: conversationMutation === 'new'
|
|
3273
|
+
? 'starting a fresh conversation — input is paused'
|
|
3274
|
+
: compacting
|
|
3275
|
+
? 'compacting conversation — input is paused'
|
|
3276
|
+
: permission
|
|
3277
|
+
? hintForPhase(phase, state, session?.hasProvider === true)
|
|
3278
|
+
: choicePicker
|
|
3279
|
+
? 'choice open — ↑↓ / 1–9 select · enter apply · esc cancel'
|
|
3108
3280
|
: copyPicker
|
|
3109
3281
|
? 'copy target open — ↑↓ / 1–9 select · esc cancel'
|
|
3110
|
-
:
|
|
3282
|
+
: goalStatus && phase === 'ready' && state === 'idle'
|
|
3283
|
+
? undefined
|
|
3284
|
+
: hintForPhase(phase, state, session?.hasProvider === true), usage: usage, context: context }) })] }) }));
|
|
3111
3285
|
}
|
|
3112
3286
|
function Banner({ version, session, permissionMode, cwd, }) {
|
|
3113
3287
|
const cols = process.stdout.columns ?? 80;
|
|
@@ -3163,7 +3337,66 @@ canReturn = false) {
|
|
|
3163
3337
|
return 'y approve · n / esc reject · a approve all';
|
|
3164
3338
|
if (state !== 'idle')
|
|
3165
3339
|
return 'agent is working — esc to interrupt';
|
|
3166
|
-
|
|
3340
|
+
// The composer already points to /help. Keeping the complete key legend in
|
|
3341
|
+
// every idle frame made ordinary conversation read like a permanent setup
|
|
3342
|
+
// screen; only state-specific actions belong in the footer.
|
|
3343
|
+
return '';
|
|
3344
|
+
}
|
|
3345
|
+
function goalStatusLabel(goal) {
|
|
3346
|
+
if (!goal)
|
|
3347
|
+
return null;
|
|
3348
|
+
switch (goal.phase) {
|
|
3349
|
+
case 'active':
|
|
3350
|
+
return 'Pursuing goal';
|
|
3351
|
+
case 'paused':
|
|
3352
|
+
return 'Goal paused (/goal resume)';
|
|
3353
|
+
case 'blocked':
|
|
3354
|
+
return 'Goal stalled (/goal resume)';
|
|
3355
|
+
case 'complete':
|
|
3356
|
+
return 'Goal achieved';
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
function permissionModeDescription(mode) {
|
|
3360
|
+
switch (mode) {
|
|
3361
|
+
case 'prompt':
|
|
3362
|
+
return 'Ask before an undecided tool call runs';
|
|
3363
|
+
case 'auto':
|
|
3364
|
+
return 'Approve undecided calls unless a rule or safety gate refuses';
|
|
3365
|
+
case 'strict':
|
|
3366
|
+
return 'Refuse undecided calls; explicit allow rules still work';
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
function permissionPickerNotice(session) {
|
|
3370
|
+
const notices = [];
|
|
3371
|
+
if (session.approvalLatched()) {
|
|
3372
|
+
notices.push('Approve all is active; applying any mode revokes it.');
|
|
3373
|
+
}
|
|
3374
|
+
const exempt = session.promptExemptTools();
|
|
3375
|
+
if (exempt.length > 0)
|
|
3376
|
+
notices.push(`Never prompted: ${exempt.join(', ')}.`);
|
|
3377
|
+
return notices.length > 0 ? notices.join(' ') : undefined;
|
|
3378
|
+
}
|
|
3379
|
+
function reasoningEffortDescription(effort) {
|
|
3380
|
+
switch (effort) {
|
|
3381
|
+
case undefined:
|
|
3382
|
+
return 'Use the provider and model default';
|
|
3383
|
+
case 'none':
|
|
3384
|
+
return 'No deliberate reasoning where the model supports it';
|
|
3385
|
+
case 'minimal':
|
|
3386
|
+
return 'Fastest responses with minimal reasoning';
|
|
3387
|
+
case 'low':
|
|
3388
|
+
return 'Fast responses with light reasoning';
|
|
3389
|
+
case 'medium':
|
|
3390
|
+
return 'Balance speed and reasoning depth';
|
|
3391
|
+
case 'high':
|
|
3392
|
+
return 'Deeper reasoning for complex problems';
|
|
3393
|
+
case 'xhigh':
|
|
3394
|
+
return 'Very deep reasoning for hard problems';
|
|
3395
|
+
case 'max':
|
|
3396
|
+
return 'Maximum reasoning depth offered by this model';
|
|
3397
|
+
case 'ultra':
|
|
3398
|
+
return 'Highest host-level reasoning setting when supported';
|
|
3399
|
+
}
|
|
3167
3400
|
}
|
|
3168
3401
|
/**
|
|
3169
3402
|
* The summary text, as collapsible detail under the compaction row.
|