@namzu/cli 32.0.0 → 33.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/dist/browser/commands.js +11 -7
- package/dist/browser/commands.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +21 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/archive.d.ts +4 -0
- package/dist/commands/archive.d.ts.map +1 -0
- package/dist/commands/archive.js +65 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/drain.js +3 -3
- package/dist/commands/drain.js.map +1 -1
- package/dist/commands/exec.js +1 -1
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +33 -11
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/mcp-oauth-login.d.ts +19 -0
- package/dist/commands/mcp-oauth-login.d.ts.map +1 -0
- package/dist/commands/mcp-oauth-login.js +213 -0
- package/dist/commands/mcp-oauth-login.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +288 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/schedule.d.ts.map +1 -1
- package/dist/commands/schedule.js +5 -2
- package/dist/commands/schedule.js.map +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +60 -5
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/worktree.d.ts +6 -0
- package/dist/commands/worktree.d.ts.map +1 -0
- package/dist/commands/worktree.js +86 -0
- package/dist/commands/worktree.js.map +1 -0
- package/dist/config/user-config.d.ts +2 -0
- package/dist/config/user-config.d.ts.map +1 -1
- package/dist/config/user-config.js +76 -6
- package/dist/config/user-config.js.map +1 -1
- package/dist/doctor/checks/credentials.d.ts.map +1 -1
- package/dist/doctor/checks/credentials.js +2 -0
- package/dist/doctor/checks/credentials.js.map +1 -1
- package/dist/integrations/mcp/oauth-store.d.ts +40 -0
- package/dist/integrations/mcp/oauth-store.d.ts.map +1 -0
- package/dist/integrations/mcp/oauth-store.js +549 -0
- package/dist/integrations/mcp/oauth-store.js.map +1 -0
- package/dist/integrations/mcp/servers.d.ts.map +1 -1
- package/dist/integrations/mcp/servers.js +155 -2
- package/dist/integrations/mcp/servers.js.map +1 -1
- package/dist/integrations/mcp/user-config.d.ts +25 -0
- package/dist/integrations/mcp/user-config.d.ts.map +1 -0
- package/dist/integrations/mcp/user-config.js +92 -0
- package/dist/integrations/mcp/user-config.js.map +1 -0
- package/dist/integrations/providers/credential-store.d.ts +8 -0
- package/dist/integrations/providers/credential-store.d.ts.map +1 -1
- package/dist/integrations/providers/credential-store.js +111 -22
- package/dist/integrations/providers/credential-store.js.map +1 -1
- package/dist/integrations/providers/discover.d.ts +11 -5
- package/dist/integrations/providers/discover.d.ts.map +1 -1
- package/dist/integrations/providers/discover.js +26 -12
- package/dist/integrations/providers/discover.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/sessions/display-title.d.ts +3 -0
- package/dist/integrations/sessions/display-title.d.ts.map +1 -0
- package/dist/integrations/sessions/display-title.js +5 -0
- package/dist/integrations/sessions/display-title.js.map +1 -0
- package/dist/integrations/sessions/store.d.ts +23 -5
- package/dist/integrations/sessions/store.d.ts.map +1 -1
- package/dist/integrations/sessions/store.js +164 -24
- package/dist/integrations/sessions/store.js.map +1 -1
- package/dist/integrations/subagents/runtime.d.ts +4 -2
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +62 -8
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/integrations/subagents/worktree-driver.d.ts +17 -0
- package/dist/integrations/subagents/worktree-driver.d.ts.map +1 -0
- package/dist/integrations/subagents/worktree-driver.js +72 -0
- package/dist/integrations/subagents/worktree-driver.js.map +1 -0
- package/dist/integrations/worktrees/managed.d.ts +49 -0
- package/dist/integrations/worktrees/managed.d.ts.map +1 -0
- package/dist/integrations/worktrees/managed.js +231 -0
- package/dist/integrations/worktrees/managed.js.map +1 -0
- package/dist/schedule/build.d.ts +2 -0
- package/dist/schedule/build.d.ts.map +1 -1
- package/dist/schedule/build.js +11 -2
- package/dist/schedule/build.js.map +1 -1
- package/dist/schedule/commands/add.d.ts +1 -1
- package/dist/schedule/commands/add.d.ts.map +1 -1
- package/dist/schedule/commands/add.js +12 -2
- package/dist/schedule/commands/add.js.map +1 -1
- package/dist/schedule/commands/list.d.ts.map +1 -1
- package/dist/schedule/commands/list.js +10 -1
- package/dist/schedule/commands/list.js.map +1 -1
- package/dist/schedule/daemon/daemon.js +1 -1
- package/dist/schedule/daemon/daemon.js.map +1 -1
- package/dist/schedule/fire/fire.d.ts.map +1 -1
- package/dist/schedule/fire/fire.js +6 -1
- package/dist/schedule/fire/fire.js.map +1 -1
- package/dist/schedule/script-check.d.ts.map +1 -1
- package/dist/schedule/script-check.js +2 -1
- package/dist/schedule/script-check.js.map +1 -1
- package/dist/skills/audit.d.ts +31 -0
- package/dist/skills/audit.d.ts.map +1 -0
- package/dist/skills/audit.js +75 -0
- package/dist/skills/audit.js.map +1 -0
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +355 -97
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/BrandHeader.js +1 -1
- package/dist/tui/BrandHeader.js.map +1 -1
- package/dist/tui/Composer.d.ts.map +1 -1
- package/dist/tui/Composer.js +6 -11
- package/dist/tui/Composer.js.map +1 -1
- package/dist/tui/Picker.d.ts.map +1 -1
- package/dist/tui/Picker.js +18 -3
- package/dist/tui/Picker.js.map +1 -1
- package/dist/tui/ResumePicker.d.ts +2 -1
- package/dist/tui/ResumePicker.d.ts.map +1 -1
- package/dist/tui/ResumePicker.js +5 -4
- package/dist/tui/ResumePicker.js.map +1 -1
- package/dist/tui/SaveSkillOverlay.js +1 -1
- package/dist/tui/SaveSkillOverlay.js.map +1 -1
- package/dist/tui/ScheduleReviewOverlay.d.ts +27 -0
- package/dist/tui/ScheduleReviewOverlay.d.ts.map +1 -0
- package/dist/tui/ScheduleReviewOverlay.js +166 -0
- package/dist/tui/ScheduleReviewOverlay.js.map +1 -0
- package/dist/tui/Transcript.js +2 -2
- package/dist/tui/Transcript.js.map +1 -1
- package/dist/tui/agent.d.ts +1 -1
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +15 -6
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/browser-notices.d.ts.map +1 -1
- package/dist/tui/browser-notices.js +6 -1
- package/dist/tui/browser-notices.js.map +1 -1
- package/dist/tui/credential-entry.d.ts +4 -29
- package/dist/tui/credential-entry.d.ts.map +1 -1
- package/dist/tui/credential-entry.js +7 -29
- package/dist/tui/credential-entry.js.map +1 -1
- package/dist/tui/login-prompt.d.ts +3 -1
- package/dist/tui/login-prompt.d.ts.map +1 -1
- package/dist/tui/login-prompt.js +11 -0
- package/dist/tui/login-prompt.js.map +1 -1
- package/dist/tui/provider-list.d.ts.map +1 -1
- package/dist/tui/provider-list.js +3 -1
- package/dist/tui/provider-list.js.map +1 -1
- package/dist/tui/schedule/host-commands.d.ts +3 -0
- package/dist/tui/schedule/host-commands.d.ts.map +1 -1
- package/dist/tui/schedule/host-commands.js +72 -15
- package/dist/tui/schedule/host-commands.js.map +1 -1
- package/dist/tui/schedule/integration.d.ts +2 -0
- package/dist/tui/schedule/integration.d.ts.map +1 -1
- package/dist/tui/schedule/integration.js +3 -0
- package/dist/tui/schedule/integration.js.map +1 -1
- package/dist/tui/schedule/resume.js +2 -2
- package/dist/tui/schedule/resume.js.map +1 -1
- package/dist/tui/schedule/tool-host.d.ts +3 -0
- package/dist/tui/schedule/tool-host.d.ts.map +1 -1
- package/dist/tui/schedule/tool-host.js +34 -9
- package/dist/tui/schedule/tool-host.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +10 -1
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +17 -4
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/turn-interruption.d.ts.map +1 -1
- package/dist/tui/turn-interruption.js +4 -0
- package/dist/tui/turn-interruption.js.map +1 -1
- package/package.json +6 -5
- package/skills/schedule-task/SKILL.md +41 -15
package/dist/tui/App.js
CHANGED
|
@@ -19,15 +19,17 @@ import { ToolOutputViewer } from './ToolOutputViewer.js';
|
|
|
19
19
|
import { join, relative } from 'node:path';
|
|
20
20
|
import { DEFAULT_MAX_GOAL_ROUNDS, DiskMessageFeedbackStore, GoalRoundLimitError, HostCommandRegistry, SessionGoalActivation, StaleGoalError, TurnCancelled, asSessionId, createAssistantMessage, createUserMessage, generateSessionId, generateTurnId, isCompactionMessage, kernelHostCommands, SKILL_TOOL_NAME, } from '@namzu/sdk';
|
|
21
21
|
import { Box, Text, useApp, useInput, useStdout, useWindowSize } from 'ink';
|
|
22
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
22
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
23
23
|
import { pinTrustedProjectPath, resolveTrustedProjectContext, } from '../config/trusted-project-context.js';
|
|
24
24
|
import { visibleProjectInstructionPath } from '../context/project-path.js';
|
|
25
25
|
import { runtimeContextLabel } from '../context/runtime-message.js';
|
|
26
26
|
import { writeClipboardText } from '../integrations/clipboard/text.js';
|
|
27
27
|
import { terminalNotificationEnabled, writeTerminalNotification, } from '../integrations/notifications/terminal.js';
|
|
28
|
-
import { beginCodexDeviceLogin, beginSubscriptionLogin, clearAllStoredCredentials, clearStoredCodexCredential, clearStoredSubscriptionCredential, credentialsPath, parsePastedInput, primaryProvider, readStoredCodexCredential, readStoredSubscriptionCredential, signedInSubscriptionProviders, writePreferences, } from '../integrations/providers/index.js';
|
|
29
|
-
import { activeConversationTurn, archiveConversation, forkConversation, forkConversationBeforeUser, listRecent, loadConversation, loadResumableConversation, openSessions, requireWritableConversation, setTitle, startConversation, titleOf, } from '../integrations/sessions/store.js';
|
|
28
|
+
import { beginCodexDeviceLogin, beginSubscriptionLogin, clearAllStoredCredentials, clearStoredCodexCredential, clearStoredGeminiApiKey as clearStoredGoogleApiKey, clearStoredSubscriptionCredential, credentialsPath, googleApiKeyPath, parsePastedInput, primaryProvider, readStoredCodexCredential, readStoredGeminiApiKey as readStoredGoogleApiKey, readStoredSubscriptionCredential, signedInSubscriptionProviders, writePreferences, writeStoredGeminiApiKey as writeStoredGoogleApiKey, } from '../integrations/providers/index.js';
|
|
29
|
+
import { ConversationIndexRefreshError, activeConversationTurn, archiveConversation, forkConversation, forkConversationBeforeUser, listArchived, listRecent, loadConversation, loadResumableConversation, openSessions, requireWritableConversation, setTitle, startConversation, titleOf, unarchiveConversation, } from '../integrations/sessions/store.js';
|
|
30
30
|
import { conversationMarkdown, writeConversationExport, } from '../integrations/sessions/transcript-export.js';
|
|
31
|
+
import { displayConversationTitle } from '../integrations/sessions/display-title.js';
|
|
32
|
+
import { openManagedWorktrees, worktreeOpenHint } from '../integrations/worktrees/managed.js';
|
|
31
33
|
import { NARRATION_TOOL_NAME } from '../integrations/subagents/runtime.js';
|
|
32
34
|
import { agentsSlashCommand } from '../integrations/subagents/slash.js';
|
|
33
35
|
import { isTrusted, trustDir } from '../integrations/trust/store.js';
|
|
@@ -61,6 +63,7 @@ import { Picker } from './Picker.js';
|
|
|
61
63
|
import { ResumePicker } from './ResumePicker.js';
|
|
62
64
|
import { resolveNamzuHome } from '../integrations/state/home.js';
|
|
63
65
|
import { createScheduleIntegration } from './schedule/integration.js';
|
|
66
|
+
import { ScheduleReviewOverlay, } from './ScheduleReviewOverlay.js';
|
|
64
67
|
import { SaveSkillOverlay } from './SaveSkillOverlay.js';
|
|
65
68
|
import { SKILL_CREATOR_SKILL, buildSaveSkillTool, learnSkillPrompt, newSkillPrompt, } from '../skills/save.js';
|
|
66
69
|
import { SUGGESTIONS_STOPPED_NOTICE, createTurnActivity, judgeSkillSuggestion, observeTurnEvent, skillSuggestionNotice, } from './skills/learning.js';
|
|
@@ -88,7 +91,7 @@ import { checklistInView, liveWindow, settledBeforeStreaming } from './live-wind
|
|
|
88
91
|
import { ViewportBound } from './ViewportBound.js';
|
|
89
92
|
import { resolveModelSwitch, } from './model-switch.js';
|
|
90
93
|
import { parseModelSelectionIntent, resolveModelSelectionIntent } from './model-selection-intent.js';
|
|
91
|
-
import { describeCodexDeviceLoginStart, describeLoginOutcome, describeLoginStart,
|
|
94
|
+
import { describeCodexDeviceLoginStart, describeLoginOutcome, describeLoginStart, describeAllCredentialsLogout, describeProviderLogout, } from './login-prompt.js';
|
|
92
95
|
import { selectPrimaryProvider } from './provider-selection.js';
|
|
93
96
|
import { eraseLastChoiceGrapheme } from './terminal-choice-text.js';
|
|
94
97
|
import { renderCheckpoints, renderRestore } from '../checkpoints/store.js';
|
|
@@ -101,6 +104,7 @@ import { moveSelection } from './selection-window.js';
|
|
|
101
104
|
import { describeShellEscape, describeShellEscapeForModel, runShellEscape, shellEscapeCommand, } from './shell-escape.js';
|
|
102
105
|
import { baseBranchReviewPrompt, commitReviewPrompt, hostCommandNames, kernelCommandDescriptors, mergeHostCommands, renderAgents, renderOutcome, reviewPrompt, runSlash, } from './slashCommands.js';
|
|
103
106
|
import { splitCompleteBlocks, splitSafeCut } from './stream-blocks.js';
|
|
107
|
+
import { terminalDisplayText } from './terminal-display.js';
|
|
104
108
|
import { terminalSupportsHyperlinks } from './terminal-hyperlinks.js';
|
|
105
109
|
import { theme } from './theme.js';
|
|
106
110
|
import { useSelectionIndex } from './use-selection-index.js';
|
|
@@ -488,7 +492,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
488
492
|
...(job.signal ? { signal: job.signal } : {}),
|
|
489
493
|
});
|
|
490
494
|
idleJobNoticesRef.current.push(text);
|
|
491
|
-
pushMessage('system', text, false, '
|
|
495
|
+
pushMessage('system', text, false, 'J');
|
|
492
496
|
});
|
|
493
497
|
}, [session]);
|
|
494
498
|
const [usage, setUsage] = useState(null);
|
|
@@ -622,7 +626,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
622
626
|
choicePickerCommittedRef.current = null;
|
|
623
627
|
setChoicePickerState(next);
|
|
624
628
|
}, []);
|
|
625
|
-
|
|
629
|
+
// A painted menu must already accept its first key. A passive effect can
|
|
630
|
+
// run after Ink flushes that frame, leaving the ownership fence closed.
|
|
631
|
+
useLayoutEffect(() => {
|
|
626
632
|
choicePickerCommittedRef.current = choicePicker;
|
|
627
633
|
}, [choicePicker]);
|
|
628
634
|
/** Host-owned text decision; its value never enters model prompt history. */
|
|
@@ -988,6 +994,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
988
994
|
isIdle: () => stateRef.current === 'idle' && abortRef.current === null,
|
|
989
995
|
say: (text) => scheduleLiveRef.current?.say(text),
|
|
990
996
|
ask: (question, signal) => scheduleLiveRef.current?.ask(question, signal) ?? Promise.resolve({ kind: 'skip' }),
|
|
997
|
+
review: (request, signal) => scheduleLiveRef.current?.review(request, signal) ?? Promise.resolve('cancel'),
|
|
991
998
|
askPermission: (request) => scheduleLiveRef.current?.askPermission(request) ??
|
|
992
999
|
Promise.resolve({ kind: 'reject', feedback: 'Nobody can answer yet.' }),
|
|
993
1000
|
submit: (text, createdBy) => scheduleLiveRef.current?.submit(text, createdBy),
|
|
@@ -1004,6 +1011,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1004
1011
|
saveSkillPromptRef.current = next;
|
|
1005
1012
|
setSaveSkillPromptState(next);
|
|
1006
1013
|
}, []);
|
|
1014
|
+
const [scheduleReviewPrompt, setScheduleReviewPromptState] = useState(null);
|
|
1015
|
+
const scheduleReviewTokenRef = useRef(0);
|
|
1016
|
+
const scheduleReviewPromptRef = useRef(null);
|
|
1017
|
+
const setScheduleReviewPrompt = useCallback((next) => {
|
|
1018
|
+
scheduleReviewPromptRef.current = next;
|
|
1019
|
+
setScheduleReviewPromptState(next);
|
|
1020
|
+
}, []);
|
|
1007
1021
|
const saveSkillNotifyRef = useRef(null);
|
|
1008
1022
|
const saveSkillToolRef = useRef(null);
|
|
1009
1023
|
if (saveSkillToolRef.current === null) {
|
|
@@ -1037,7 +1051,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1037
1051
|
setSaveSkillPrompt(entry);
|
|
1038
1052
|
saveSkillNotifyRef.current?.();
|
|
1039
1053
|
}),
|
|
1040
|
-
saved: ({ name, path }) => scheduleLiveRef.current?.say(
|
|
1054
|
+
saved: ({ name, path }) => scheduleLiveRef.current?.say(`Saved skill ${name} to ${path}. The model is offered it from the next turn; /skills ${name} activates it now.`),
|
|
1041
1055
|
});
|
|
1042
1056
|
}
|
|
1043
1057
|
/**
|
|
@@ -1165,9 +1179,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1165
1179
|
// outlived the row it named would resolve `/expand 3` to output the operator
|
|
1166
1180
|
// can no longer see anywhere, which is the failure this surface is being
|
|
1167
1181
|
// cleaned of, one layer up.
|
|
1168
|
-
const resetTranscript = useCallback(() => {
|
|
1182
|
+
const resetTranscript = useCallback((options = {}) => {
|
|
1169
1183
|
if (stdout.isTTY)
|
|
1170
1184
|
writeStdout('\x1b[2J\x1b[3J\x1b[H');
|
|
1185
|
+
if (!options.preserveOutputViewer)
|
|
1186
|
+
setOutputViewer(null);
|
|
1171
1187
|
// The scrollback floor goes with the log it counted. <Static> is remounted
|
|
1172
1188
|
// by the key below and has emitted nothing again; a floor that survived
|
|
1173
1189
|
// would keep the next conversation's rows out of the live window.
|
|
@@ -1179,12 +1195,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1179
1195
|
const previous = previousTerminalRef.current;
|
|
1180
1196
|
previousTerminalRef.current = terminal;
|
|
1181
1197
|
if (externalEditorRequestRef.current === null &&
|
|
1182
|
-
(terminal.columns
|
|
1183
|
-
// A contraction can
|
|
1184
|
-
// receives resize
|
|
1185
|
-
//
|
|
1186
|
-
// messages, draft and selected child. Regular frames
|
|
1187
|
-
|
|
1198
|
+
(terminal.columns !== previous.columns || terminal.rows < previous.rows)) {
|
|
1199
|
+
// A contraction can move live rows into native scrollback before Ink
|
|
1200
|
+
// receives resize; expansion leaves already-printed Static rows wrapped
|
|
1201
|
+
// at their old width. Rebuild the normal-buffer transcript at the new
|
|
1202
|
+
// width, retaining messages, draft and selected child. Regular frames
|
|
1203
|
+
// remain incremental.
|
|
1204
|
+
resetTranscript({ preserveOutputViewer: true });
|
|
1188
1205
|
}
|
|
1189
1206
|
}, [terminal, resetTranscript]);
|
|
1190
1207
|
const requestExternalEditor = useCallback((seed) => {
|
|
@@ -1802,26 +1819,44 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1802
1819
|
}
|
|
1803
1820
|
}, [conversationLearning, ctx.cwd, ctx.skills, pushMessage]);
|
|
1804
1821
|
const removeStoredCredential = useCallback((target) => {
|
|
1805
|
-
const path = credentialsPath();
|
|
1822
|
+
const path = target === 'google' ? googleApiKeyPath() : credentialsPath();
|
|
1806
1823
|
const hadClaude = readStoredSubscriptionCredential() !== null;
|
|
1807
1824
|
const hadCodex = readStoredCodexCredential() !== null;
|
|
1825
|
+
const hadGoogleKey = readStoredGoogleApiKey() !== null;
|
|
1826
|
+
if (target === 'all') {
|
|
1827
|
+
const failures = [];
|
|
1828
|
+
try {
|
|
1829
|
+
clearAllStoredCredentials();
|
|
1830
|
+
}
|
|
1831
|
+
catch (error) {
|
|
1832
|
+
failures.push(`${credentialsPath()}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1833
|
+
}
|
|
1834
|
+
try {
|
|
1835
|
+
clearStoredGoogleApiKey();
|
|
1836
|
+
}
|
|
1837
|
+
catch (error) {
|
|
1838
|
+
failures.push(`${googleApiKeyPath()}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1839
|
+
}
|
|
1840
|
+
if (failures.length > 0) {
|
|
1841
|
+
pushMessage('system', `Some stored credentials could not be removed: ${failures.join('; ')}. Check both stores before retrying.`);
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
pushMessage('system', describeAllCredentialsLogout(path, googleApiKeyPath(), hadClaude || hadCodex || hadGoogleKey));
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1808
1847
|
try {
|
|
1809
1848
|
if (target === 'anthropic')
|
|
1810
1849
|
clearStoredSubscriptionCredential();
|
|
1811
1850
|
else if (target === 'codex')
|
|
1812
1851
|
clearStoredCodexCredential();
|
|
1813
1852
|
else
|
|
1814
|
-
|
|
1853
|
+
clearStoredGoogleApiKey();
|
|
1815
1854
|
}
|
|
1816
1855
|
catch (error) {
|
|
1817
1856
|
pushMessage('system', `Could not remove ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1818
1857
|
return;
|
|
1819
1858
|
}
|
|
1820
|
-
|
|
1821
|
-
pushMessage('system', describeLogout(path, hadClaude || hadCodex));
|
|
1822
|
-
return;
|
|
1823
|
-
}
|
|
1824
|
-
pushMessage('system', describeProviderLogout(path, target, target === 'anthropic' ? hadClaude : hadCodex));
|
|
1859
|
+
pushMessage('system', describeProviderLogout(path, target, target === 'anthropic' ? hadClaude : target === 'codex' ? hadCodex : hadGoogleKey));
|
|
1825
1860
|
}, [pushMessage]);
|
|
1826
1861
|
/** Publish one recoverable, read-only tombstone before leaving the TUI. */
|
|
1827
1862
|
const archiveCurrentConversation = useCallback(() => {
|
|
@@ -1864,7 +1899,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1864
1899
|
closeAndExit();
|
|
1865
1900
|
}
|
|
1866
1901
|
catch (error) {
|
|
1867
|
-
pushMessage('system',
|
|
1902
|
+
pushMessage('system', error instanceof ConversationIndexRefreshError
|
|
1903
|
+
? error.message
|
|
1904
|
+
: `Could not archive this conversation: ${error instanceof Error ? error.message : String(error)}`);
|
|
1868
1905
|
}
|
|
1869
1906
|
finally {
|
|
1870
1907
|
if (!archived && conversationMutationRef.current === 'archive') {
|
|
@@ -2648,10 +2685,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2648
2685
|
setMentionCandidates(files);
|
|
2649
2686
|
});
|
|
2650
2687
|
setCurrentProvider(primaryProvider(prefs).id);
|
|
2651
|
-
//
|
|
2652
|
-
//
|
|
2653
|
-
//
|
|
2654
|
-
|
|
2688
|
+
// Failed and superseded provider choices returned above. A usable
|
|
2689
|
+
// provider change can recover a failed turn's dependent queue, but a
|
|
2690
|
+
// paused turn still owns the conversation's durable active slot. Keep
|
|
2691
|
+
// that queue held until its checkpoint is resumed and settles.
|
|
2692
|
+
if (signal !== undefined && queuePauseRef.current?.outcome !== 'paused')
|
|
2655
2693
|
advanceQueueContinuation();
|
|
2656
2694
|
if (s.hasProvider) {
|
|
2657
2695
|
setTranscriptOwned(true);
|
|
@@ -3044,9 +3082,6 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3044
3082
|
// terminal scrollback; the live tail stays deliberately small so an activity
|
|
3045
3083
|
// tick never repaints the whole conversation.
|
|
3046
3084
|
//
|
|
3047
|
-
useEffect(() => {
|
|
3048
|
-
setOutputViewer(null);
|
|
3049
|
-
}, [resetKey]);
|
|
3050
3085
|
useEffect(() => {
|
|
3051
3086
|
if (permission !== null)
|
|
3052
3087
|
setOutputViewer(null);
|
|
@@ -3067,9 +3102,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3067
3102
|
// settling in the background prints through the child screen. Returning
|
|
3068
3103
|
// advances the floor once and emits those finalized rows exactly once.
|
|
3069
3104
|
const nextSettled = settledBeforeStreaming(messages, window.settled, settledRef.current);
|
|
3070
|
-
if (agentSurface === null && outputViewer === null)
|
|
3105
|
+
if (agentSurface === null && outputViewer === null && scheduleReviewPrompt === null)
|
|
3071
3106
|
settledRef.current = nextSettled;
|
|
3072
|
-
const renderedSettled = agentSurface === null && outputViewer === null
|
|
3107
|
+
const renderedSettled = agentSurface === null && outputViewer === null && scheduleReviewPrompt === null
|
|
3108
|
+
? nextSettled
|
|
3109
|
+
: settledRef.current;
|
|
3073
3110
|
// One merged vocabulary for the session: this host's own commands plus
|
|
3074
3111
|
// whatever the kernel's registry reports. Built here so `/help`, the
|
|
3075
3112
|
// autocomplete and the dispatcher all answer from the same list — three
|
|
@@ -3185,7 +3222,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3185
3222
|
const scope = scopeRef.current;
|
|
3186
3223
|
if (!sessions || !scope || !conversationMaterializedRef.current || !session?.hasProvider)
|
|
3187
3224
|
return false;
|
|
3188
|
-
const
|
|
3225
|
+
const resumedSessionId = scope.sessionId;
|
|
3226
|
+
const active = await activeConversationTurn(sessions, resumedSessionId).catch(() => undefined);
|
|
3189
3227
|
if (!active)
|
|
3190
3228
|
return false;
|
|
3191
3229
|
if (abortRef.current || state !== 'idle') {
|
|
@@ -3245,7 +3283,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3245
3283
|
}
|
|
3246
3284
|
// Said once the turn really continues: a scheduled park can still
|
|
3247
3285
|
// be left waiting or abandoned above.
|
|
3248
|
-
pushMessage('system', PAUSED_TURN_LINES.resuming
|
|
3286
|
+
pushMessage('system', PAUSED_TURN_LINES.resuming);
|
|
3249
3287
|
for await (const event of session.resumePaused({
|
|
3250
3288
|
turnId: active.turnId,
|
|
3251
3289
|
signal: ac.signal,
|
|
@@ -3266,7 +3304,19 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3266
3304
|
finalizeMessage(st.assistantId);
|
|
3267
3305
|
// The resumed segment was appended to the log; the fold is the context
|
|
3268
3306
|
// the next prompt continues from.
|
|
3269
|
-
modelHistoryRef.current = await loadConversation(sessions,
|
|
3307
|
+
modelHistoryRef.current = await loadConversation(sessions, resumedSessionId);
|
|
3308
|
+
// A successfully completed checkpoint frees the conversation's active
|
|
3309
|
+
// slot. Only then may prompts queued behind the paused turn start. A
|
|
3310
|
+
// repeated pause, failed resume, unreadable log, or conversation switch
|
|
3311
|
+
// keeps dependent work held for an explicit operator decision.
|
|
3312
|
+
if (st.completed &&
|
|
3313
|
+
queuePauseRef.current?.outcome === 'paused' &&
|
|
3314
|
+
scopeRef.current?.sessionId === resumedSessionId &&
|
|
3315
|
+
(await activeConversationTurn(sessions, resumedSessionId)
|
|
3316
|
+
.then((next) => next === undefined)
|
|
3317
|
+
.catch(() => false))) {
|
|
3318
|
+
advanceQueueContinuation();
|
|
3319
|
+
}
|
|
3270
3320
|
}
|
|
3271
3321
|
catch (err) {
|
|
3272
3322
|
pushMessage('system', `Could not resume the turn: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3283,7 +3333,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3283
3333
|
await restoreBrowser?.().catch(() => undefined);
|
|
3284
3334
|
}
|
|
3285
3335
|
return true;
|
|
3286
|
-
}, [detected, finalizeMessage, flushStream, pushMessage, session, setHandoffPark, state]);
|
|
3336
|
+
}, [advanceQueueContinuation, detected, finalizeMessage, flushStream, pushMessage, session, setHandoffPark, state]);
|
|
3287
3337
|
// `namzu resume <id>` of a scheduled run parked on a decision: what the
|
|
3288
3338
|
// notification and `/schedule` tell the operator to run. Continue it once
|
|
3289
3339
|
// the screen is ready, exactly as `/resume` would, so the parked call is on
|
|
@@ -3331,6 +3381,35 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3331
3381
|
pushMessage('system', `Could not list conversations: ${err instanceof Error ? err.message : String(err)}`);
|
|
3332
3382
|
}
|
|
3333
3383
|
}, [ensureSessions, pushMessage, resumeActiveTurn]);
|
|
3384
|
+
const doUnarchive = useCallback(async () => {
|
|
3385
|
+
if (state !== 'idle' ||
|
|
3386
|
+
abortRef.current !== null ||
|
|
3387
|
+
hasUnsettledTurn() ||
|
|
3388
|
+
queuedRef.current.length > 0 ||
|
|
3389
|
+
compactingRef.current ||
|
|
3390
|
+
exportingRef.current) {
|
|
3391
|
+
pushMessage('system', 'Finish the current turn and queued work before restoring a conversation.');
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
const sessions = sessionsRef.current ?? (await ensureSessions(), sessionsRef.current);
|
|
3395
|
+
if (!sessions) {
|
|
3396
|
+
pushMessage('system', 'Conversation history is unavailable in this folder.');
|
|
3397
|
+
return;
|
|
3398
|
+
}
|
|
3399
|
+
try {
|
|
3400
|
+
const archived = await listArchived(sessions, 100);
|
|
3401
|
+
if (archived.length === 0) {
|
|
3402
|
+
pushMessage('system', 'No archived conversations in this project.');
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3405
|
+
setResumeList(archived);
|
|
3406
|
+
setSelectedResume(0);
|
|
3407
|
+
setPhase('unarchive');
|
|
3408
|
+
}
|
|
3409
|
+
catch (err) {
|
|
3410
|
+
pushMessage('system', `Could not list archived conversations: ${err instanceof Error ? err.message : String(err)}`);
|
|
3411
|
+
}
|
|
3412
|
+
}, [ensureSessions, hasUnsettledTurn, pushMessage, state]);
|
|
3334
3413
|
// Resolve a pending permission prompt with the user's decision and tear
|
|
3335
3414
|
// down the overlay. No-op if nothing is pending.
|
|
3336
3415
|
const resolvePermission = useCallback((decision) => {
|
|
@@ -3511,6 +3590,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3511
3590
|
resumeCommittedRef.current = true;
|
|
3512
3591
|
let msgs;
|
|
3513
3592
|
try {
|
|
3593
|
+
await requireWritableConversation(sessions, conv.id, 'resume conversation');
|
|
3514
3594
|
msgs = await loadConversation(sessions, conv.id);
|
|
3515
3595
|
}
|
|
3516
3596
|
catch (err) {
|
|
@@ -3555,7 +3635,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3555
3635
|
// would have nothing to find.
|
|
3556
3636
|
void hydrateSavedChildren();
|
|
3557
3637
|
conversationMaterializedRef.current = true;
|
|
3558
|
-
pushMessage('system', `Resumed: ${conv.title}`);
|
|
3638
|
+
pushMessage('system', `Resumed: ${displayConversationTitle(conv.title)}`);
|
|
3559
3639
|
if (discardedQueued > 0) {
|
|
3560
3640
|
pushMessage('system', `Discarded ${discardedQueued} queued prompt${discardedQueued === 1 ? '' : 's'} from the conversation you left.`);
|
|
3561
3641
|
}
|
|
@@ -3580,6 +3660,42 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3580
3660
|
resetTranscript,
|
|
3581
3661
|
wakeGoalDriver,
|
|
3582
3662
|
]);
|
|
3663
|
+
const restoreArchivedConversation = useCallback(async (conv) => {
|
|
3664
|
+
const sessions = sessionsRef.current;
|
|
3665
|
+
if (!sessions) {
|
|
3666
|
+
setPhase('ready');
|
|
3667
|
+
return;
|
|
3668
|
+
}
|
|
3669
|
+
if (state !== 'idle' ||
|
|
3670
|
+
abortRef.current !== null ||
|
|
3671
|
+
hasUnsettledTurn() ||
|
|
3672
|
+
queuedRef.current.length > 0 ||
|
|
3673
|
+
compactingRef.current ||
|
|
3674
|
+
exportingRef.current) {
|
|
3675
|
+
setPhase('ready');
|
|
3676
|
+
pushMessage('system', 'Finish the current turn and queued work before restoring a conversation.');
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
resumeCommittedRef.current = true;
|
|
3680
|
+
try {
|
|
3681
|
+
await unarchiveConversation(sessions, conv.id);
|
|
3682
|
+
}
|
|
3683
|
+
catch (err) {
|
|
3684
|
+
resumeCommittedRef.current = false;
|
|
3685
|
+
setPhase('ready');
|
|
3686
|
+
pushMessage('system', err instanceof ConversationIndexRefreshError
|
|
3687
|
+
? err.message
|
|
3688
|
+
: `Could not restore: ${err instanceof Error ? err.message : String(err)}`);
|
|
3689
|
+
return;
|
|
3690
|
+
}
|
|
3691
|
+
pushMessage('system', `Restored: ${displayConversationTitle(conv.title)}`);
|
|
3692
|
+
try {
|
|
3693
|
+
await resumeConversation(conv);
|
|
3694
|
+
}
|
|
3695
|
+
finally {
|
|
3696
|
+
resumeCommittedRef.current = false;
|
|
3697
|
+
}
|
|
3698
|
+
}, [hasUnsettledTurn, pushMessage, resumeConversation, state]);
|
|
3583
3699
|
/**
|
|
3584
3700
|
* Start a new conversation without deleting the one being left.
|
|
3585
3701
|
*
|
|
@@ -3814,7 +3930,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3814
3930
|
session?.resetTaskStore?.();
|
|
3815
3931
|
goalActivation.clear();
|
|
3816
3932
|
wakeGoalDriver();
|
|
3817
|
-
pushMessage('system', `Forked into "${forked.title}" — ${forked.copied} message(s) copied. This screen continues in the copy; ${original} is unchanged and still in /resume.`);
|
|
3933
|
+
pushMessage('system', `Forked into "${displayConversationTitle(forked.title)}" — ${forked.copied} message(s) copied. This screen continues in the copy; ${original} is unchanged and still in /resume.`);
|
|
3818
3934
|
}
|
|
3819
3935
|
catch (err) {
|
|
3820
3936
|
pushMessage('system', `Could not fork: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3941,7 +4057,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3941
4057
|
});
|
|
3942
4058
|
setEditList([]);
|
|
3943
4059
|
setPhase('ready');
|
|
3944
|
-
pushMessage('system', `Forked into "${forked.title}" before the selected prompt. Edit it below; ${source} is unchanged and remains in /resume.`);
|
|
4060
|
+
pushMessage('system', `Forked into "${displayConversationTitle(forked.title)}" before the selected prompt. Edit it below; ${source} is unchanged and remains in /resume.`);
|
|
3945
4061
|
}
|
|
3946
4062
|
catch (err) {
|
|
3947
4063
|
setPhase('ready');
|
|
@@ -4053,6 +4169,39 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4053
4169
|
setChoicePicker(picker);
|
|
4054
4170
|
sendTerminalNotification({ kind: 'approval-required' });
|
|
4055
4171
|
}), [closeLiveReply, sendTerminalNotification, setChoicePicker, setSelectedChoice, resolveQuestion]);
|
|
4172
|
+
const reviewSchedule = useCallback((request, signal) => new Promise((resolve) => {
|
|
4173
|
+
if (signal?.aborted) {
|
|
4174
|
+
resolve('cancel');
|
|
4175
|
+
return;
|
|
4176
|
+
}
|
|
4177
|
+
closeLiveReply();
|
|
4178
|
+
// A second tool call must not replace an open review and leave its
|
|
4179
|
+
// Promise waiting forever. Each decision still belongs to its own
|
|
4180
|
+
// captured proposal.
|
|
4181
|
+
scheduleReviewPromptRef.current?.resolve('cancel');
|
|
4182
|
+
let settled = false;
|
|
4183
|
+
const entry = {
|
|
4184
|
+
token: ++scheduleReviewTokenRef.current,
|
|
4185
|
+
request,
|
|
4186
|
+
resolve: (answer) => {
|
|
4187
|
+
if (settled)
|
|
4188
|
+
return;
|
|
4189
|
+
settled = true;
|
|
4190
|
+
signal?.removeEventListener('abort', onAbort);
|
|
4191
|
+
// Let the overlay's key dispatch finish before App regains its
|
|
4192
|
+
// keyboard handler, so Esc cannot interrupt this same turn.
|
|
4193
|
+
queueMicrotask(() => {
|
|
4194
|
+
if (scheduleReviewPromptRef.current === entry)
|
|
4195
|
+
setScheduleReviewPrompt(null);
|
|
4196
|
+
});
|
|
4197
|
+
resolve(answer);
|
|
4198
|
+
},
|
|
4199
|
+
};
|
|
4200
|
+
const onAbort = () => entry.resolve('cancel');
|
|
4201
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
4202
|
+
setScheduleReviewPrompt(entry);
|
|
4203
|
+
sendTerminalNotification({ kind: 'approval-required' });
|
|
4204
|
+
}), [closeLiveReply, sendTerminalNotification, setScheduleReviewPrompt]);
|
|
4056
4205
|
/**
|
|
4057
4206
|
* Grow this turn's open task block, or open one. The whole plan rides along
|
|
4058
4207
|
* so the block shows the checklist as it stood after the operation; a
|
|
@@ -4444,7 +4593,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4444
4593
|
case 'job':
|
|
4445
4594
|
// The kernel saw it end during a turn; the model reads the notice
|
|
4446
4595
|
// on its next tool result, the operator reads this row.
|
|
4447
|
-
pushMessage('system', describeJobExit(event), false, '
|
|
4596
|
+
pushMessage('system', describeJobExit(event), false, 'J');
|
|
4448
4597
|
break;
|
|
4449
4598
|
case 'context':
|
|
4450
4599
|
// Into the TRANSCRIPT, not a status line. A status indicator is
|
|
@@ -4539,8 +4688,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4539
4688
|
`${describeTurnInterruption(event)}\nWhen that is done, press Enter to continue · Esc to stop.`, false, '‖');
|
|
4540
4689
|
setHandoffPark({ turnId: event.turnId, reason: event.handoff.reason });
|
|
4541
4690
|
}
|
|
4542
|
-
else
|
|
4543
|
-
pushMessage('system', describeTurnInterruption(event), false, '‖');
|
|
4691
|
+
else {
|
|
4692
|
+
pushMessage('system', `${describeTurnInterruption(event)}\nUse /resume to continue this checkpoint on the selected provider, or /abandon to start a new turn.`, false, '‖');
|
|
4693
|
+
}
|
|
4544
4694
|
break;
|
|
4545
4695
|
case 'error':
|
|
4546
4696
|
closeAssistant();
|
|
@@ -5816,6 +5966,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5816
5966
|
const choices = [
|
|
5817
5967
|
...(readStoredSubscriptionCredential() ? ['anthropic'] : []),
|
|
5818
5968
|
...(readStoredCodexCredential() ? ['codex'] : []),
|
|
5969
|
+
...(readStoredGoogleApiKey() ? ['google'] : []),
|
|
5819
5970
|
];
|
|
5820
5971
|
if (choices.length === 0) {
|
|
5821
5972
|
removeStoredCredential('all');
|
|
@@ -5828,14 +5979,16 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5828
5979
|
setSelectedChoice(0);
|
|
5829
5980
|
setChoicePicker({
|
|
5830
5981
|
kind: 'credential-logout',
|
|
5831
|
-
title: 'Choose a stored
|
|
5982
|
+
title: 'Choose a stored credential to remove',
|
|
5832
5983
|
notice: 'Only credentials created by Namzu are listed. Device sessions owned by other tools are left alone.',
|
|
5833
5984
|
values: choices,
|
|
5834
5985
|
options: choices.map((choice) => ({
|
|
5835
|
-
label: choice === 'anthropic' ? 'Claude' : 'Codex',
|
|
5986
|
+
label: choice === 'anthropic' ? 'Claude' : choice === 'codex' ? 'Codex' : 'Gemini API key',
|
|
5836
5987
|
description: choice === 'anthropic'
|
|
5837
5988
|
? 'Remove only Namzu’s Claude subscription.'
|
|
5838
|
-
:
|
|
5989
|
+
: choice === 'codex'
|
|
5990
|
+
? 'Remove only Namzu’s Codex subscription.'
|
|
5991
|
+
: 'Remove only Namzu’s saved Gemini API key.',
|
|
5839
5992
|
})),
|
|
5840
5993
|
});
|
|
5841
5994
|
return;
|
|
@@ -5992,12 +6145,80 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5992
6145
|
case 'resume':
|
|
5993
6146
|
void doResume();
|
|
5994
6147
|
return;
|
|
6148
|
+
case 'unarchive':
|
|
6149
|
+
void doUnarchive();
|
|
6150
|
+
return;
|
|
5995
6151
|
case 'title':
|
|
5996
6152
|
void doTitle(slash.title, slash.clear);
|
|
5997
6153
|
return;
|
|
5998
6154
|
case 'fork':
|
|
5999
6155
|
void doFork();
|
|
6000
6156
|
return;
|
|
6157
|
+
case 'worktree': {
|
|
6158
|
+
const [verb = 'list', label, extra] = slash.args;
|
|
6159
|
+
const notice = (content) => pushMessage('system', terminalDisplayText(content));
|
|
6160
|
+
if (!((verb === 'list' && label === undefined) ||
|
|
6161
|
+
(verb === 'create' && extra === undefined) ||
|
|
6162
|
+
(verb === 'fork' && extra === undefined) ||
|
|
6163
|
+
(verb === 'resume' &&
|
|
6164
|
+
label !== undefined &&
|
|
6165
|
+
extra === undefined))) {
|
|
6166
|
+
pushMessage('system', 'Usage: /worktree [list|create [name]|fork [name]|resume <name>]');
|
|
6167
|
+
return;
|
|
6168
|
+
}
|
|
6169
|
+
if (verb === 'fork' &&
|
|
6170
|
+
(abortRef.current ||
|
|
6171
|
+
hasUnsettledTurn() ||
|
|
6172
|
+
queuedRef.current.length > 0)) {
|
|
6173
|
+
pushMessage('system', 'Wait for the current turn and queued prompts to finish before forking to a worktree.');
|
|
6174
|
+
return;
|
|
6175
|
+
}
|
|
6176
|
+
if (conversationMutationRef.current)
|
|
6177
|
+
return;
|
|
6178
|
+
if (verb === 'fork') {
|
|
6179
|
+
conversationMutationRef.current = 'fork';
|
|
6180
|
+
setConversationMutation('fork');
|
|
6181
|
+
}
|
|
6182
|
+
void (async () => {
|
|
6183
|
+
try {
|
|
6184
|
+
const manager = await openManagedWorktrees(ctx.cwd);
|
|
6185
|
+
if (verb === 'list') {
|
|
6186
|
+
const items = await manager.list();
|
|
6187
|
+
notice(items.length === 0
|
|
6188
|
+
? 'No managed worktrees in this repository.'
|
|
6189
|
+
: items
|
|
6190
|
+
.map((item) => `${item.label}: ${item.path}${item.dirty ? ' (uncommitted files)' : ''}`)
|
|
6191
|
+
.join('\n'));
|
|
6192
|
+
}
|
|
6193
|
+
else if (verb === 'create') {
|
|
6194
|
+
const created = await manager.create(label);
|
|
6195
|
+
notice(`Created ${created.branch}. Open it in a new terminal: ${worktreeOpenHint(created.path)}${created.sourceDirty ? '\nUncommitted files stayed in this checkout.' : ''}`);
|
|
6196
|
+
}
|
|
6197
|
+
else if (verb === 'fork') {
|
|
6198
|
+
const scope = await materializeConversation();
|
|
6199
|
+
if (!scope)
|
|
6200
|
+
throw new Error('Conversation history is unavailable in this folder.');
|
|
6201
|
+
await persistenceTailRef.current;
|
|
6202
|
+
const forked = await manager.fork(scope.sessionId, label);
|
|
6203
|
+
notice(`Forked ${forked.copied} messages into ${displayConversationTitle(forked.title)}. Open the new checkout in a new terminal: ${worktreeOpenHint(forked.path, forked.conversationId)}${forked.sourceDirty ? '\nUncommitted files stayed in this checkout.' : ''}`);
|
|
6204
|
+
}
|
|
6205
|
+
else if (label) {
|
|
6206
|
+
const target = await manager.resume(label);
|
|
6207
|
+
notice(`Open ${label} in a new terminal: ${worktreeOpenHint(target.worktree.path, target.conversationId)}`);
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
catch (error) {
|
|
6211
|
+
notice(`Worktree: ${error instanceof Error ? error.message : String(error)}`);
|
|
6212
|
+
}
|
|
6213
|
+
finally {
|
|
6214
|
+
if (verb === 'fork') {
|
|
6215
|
+
conversationMutationRef.current = null;
|
|
6216
|
+
setConversationMutation(null);
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
})();
|
|
6220
|
+
return;
|
|
6221
|
+
}
|
|
6001
6222
|
case 'restore':
|
|
6002
6223
|
void doRestore(slash.turn);
|
|
6003
6224
|
return;
|
|
@@ -6448,12 +6669,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6448
6669
|
appLifetime,
|
|
6449
6670
|
ctx.cwd,
|
|
6450
6671
|
doResume,
|
|
6672
|
+
doUnarchive,
|
|
6451
6673
|
enqueueQueued,
|
|
6452
6674
|
exitWithSummary,
|
|
6453
6675
|
hasUnsettledTurn,
|
|
6454
6676
|
goalActivation,
|
|
6455
6677
|
goalStatus,
|
|
6456
6678
|
hostCommands,
|
|
6679
|
+
materializeConversation,
|
|
6457
6680
|
nextId,
|
|
6458
6681
|
openAgentCockpit,
|
|
6459
6682
|
runAgentsCommand,
|
|
@@ -6529,6 +6752,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6529
6752
|
pushMessage('system', text);
|
|
6530
6753
|
},
|
|
6531
6754
|
ask: askQuestion,
|
|
6755
|
+
review: reviewSchedule,
|
|
6532
6756
|
askPermission: onPermission,
|
|
6533
6757
|
submit: (text, createdBy) => handleSubmit(text, undefined, 'submit', {
|
|
6534
6758
|
source: createdBy === 'operator' ? 'operator-loop' : 'model-loop',
|
|
@@ -6767,7 +6991,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6767
6991
|
if (ups.length === 0)
|
|
6768
6992
|
return;
|
|
6769
6993
|
const lines = ups.map((u) => ` • ${u.name} ${u.current} → ${u.latest} (${u.how})`);
|
|
6770
|
-
pushMessage('system',
|
|
6994
|
+
pushMessage('system', `Update available:\n${lines.join('\n')}`);
|
|
6771
6995
|
});
|
|
6772
6996
|
}, [phase, ctx.version, pushMessage]);
|
|
6773
6997
|
const hydrateFromPicker = useCallback(async (prefs, detectedNow, signal, options = {}) => {
|
|
@@ -6778,29 +7002,24 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6778
7002
|
announce: true,
|
|
6779
7003
|
chooseReasoningEffort: options.chooseReasoningEffort,
|
|
6780
7004
|
});
|
|
7005
|
+
return !signal.aborted;
|
|
6781
7006
|
}
|
|
6782
7007
|
catch (err) {
|
|
6783
7008
|
// A superseded choice no longer owns even its failure message. Its
|
|
6784
7009
|
// eventual session object is disposed inside `hydrateSession`; a live
|
|
6785
7010
|
// choice stays on the picker with the actionable construction error.
|
|
6786
7011
|
if (signal.aborted)
|
|
6787
|
-
return;
|
|
7012
|
+
return false;
|
|
6788
7013
|
if (options.revealAllOnFailure) {
|
|
6789
7014
|
setPickerDetected(null);
|
|
6790
7015
|
setPickerSelectionKind('provider-and-model');
|
|
6791
7016
|
setPickerInitialView('providers');
|
|
6792
7017
|
}
|
|
6793
7018
|
setPickerNotice(`Could not start the selected provider: ${err instanceof Error ? err.message : String(err)}`);
|
|
7019
|
+
return false;
|
|
6794
7020
|
}
|
|
6795
7021
|
}, [hydrateSession]);
|
|
6796
|
-
/**
|
|
6797
|
-
* A credential the operator typed. Held in memory for this process only.
|
|
6798
|
-
*
|
|
6799
|
-
* Deliberately does NOT call `writePreferences`: preferences are a file, and
|
|
6800
|
-
* the whole contract of this entry point is that nothing lands on disk. The
|
|
6801
|
-
* provider choice is not persisted either, because persisting it would leave
|
|
6802
|
-
* a preference pointing at a credential that will not exist next launch.
|
|
6803
|
-
*/
|
|
7022
|
+
/** A typed Google API key is saved only after its provider session opens. */
|
|
6804
7023
|
const handleTypedCredential = useCallback((credential, disposition, signal) => {
|
|
6805
7024
|
const next = [credential, ...detected.filter((d) => d.entry.id !== credential.entry.id)];
|
|
6806
7025
|
setDetected(next);
|
|
@@ -6816,7 +7035,32 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6816
7035
|
const prefs = saved && primaryProvider(saved).id === credential.entry.id
|
|
6817
7036
|
? saved
|
|
6818
7037
|
: selectPrimaryProvider(saved, { id: credential.entry.id });
|
|
6819
|
-
void
|
|
7038
|
+
void (async () => {
|
|
7039
|
+
const started = await hydrateFromPicker(prefs, next, signal);
|
|
7040
|
+
if (!started || credential.entry.id !== 'google')
|
|
7041
|
+
return;
|
|
7042
|
+
let path;
|
|
7043
|
+
try {
|
|
7044
|
+
path = writeStoredGoogleApiKey(credential.apiKey ?? '');
|
|
7045
|
+
}
|
|
7046
|
+
catch (error) {
|
|
7047
|
+
pushMessage('system', `Gemini is connected, but its API key could not be saved privately: ${error instanceof Error ? error.message : String(error)}. It is usable only in this session.`);
|
|
7048
|
+
return;
|
|
7049
|
+
}
|
|
7050
|
+
setDetected((current) => current.map((provider) => provider.entry.id === 'google' &&
|
|
7051
|
+
provider.source.kind === 'session' &&
|
|
7052
|
+
provider.apiKey === credential.apiKey
|
|
7053
|
+
? { ...provider, source: { kind: 'stored-gemini-key', path } }
|
|
7054
|
+
: provider));
|
|
7055
|
+
try {
|
|
7056
|
+
writePreferences(prefs);
|
|
7057
|
+
}
|
|
7058
|
+
catch (error) {
|
|
7059
|
+
pushMessage('system', `Gemini API key was saved privately at ${path}, but the provider preference could not be saved: ${error instanceof Error ? error.message : String(error)}. Run /logout gemini to remove the key.`);
|
|
7060
|
+
return;
|
|
7061
|
+
}
|
|
7062
|
+
pushMessage('system', `Gemini API key saved privately at ${path} for future launches. Run /logout gemini to remove it.`);
|
|
7063
|
+
})();
|
|
6820
7064
|
}, [detected, hydrateFromPicker, pushMessage]);
|
|
6821
7065
|
const handlePickerSubmit = useCallback((selection, signal) => {
|
|
6822
7066
|
const prefs = selectPrimaryProvider(savedPrefsRef.current, {
|
|
@@ -6868,7 +7112,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6868
7112
|
// The save-skill screen owns its keys, Esc and Ctrl+C included: neither
|
|
6869
7113
|
// may interrupt the turn that is waiting on its answer. A permission
|
|
6870
7114
|
// request (an agent's, say) takes the screen over it and gets the keys.
|
|
6871
|
-
if (saveSkillPromptRef.current && permission === null)
|
|
7115
|
+
if ((saveSkillPromptRef.current || scheduleReviewPromptRef.current) && permission === null)
|
|
6872
7116
|
return;
|
|
6873
7117
|
// Startup has refused admission, so there is no draft or active turn to
|
|
6874
7118
|
// protect with the ready screen's two-press exit ladder.
|
|
@@ -6923,7 +7167,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6923
7167
|
return;
|
|
6924
7168
|
}
|
|
6925
7169
|
// Resume picker owns the keyboard while open.
|
|
6926
|
-
if (phase === 'resume') {
|
|
7170
|
+
if (phase === 'resume' || phase === 'unarchive') {
|
|
6927
7171
|
// Once a conversation is being read, the keyboard does nothing here.
|
|
6928
7172
|
// The choice is already being acted on; a second Enter would start a
|
|
6929
7173
|
// second read and an Esc would hand back a screen that is about to be
|
|
@@ -6940,7 +7184,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6940
7184
|
else if (key.return) {
|
|
6941
7185
|
const conv = resumeList[selectedResumeRef.current];
|
|
6942
7186
|
if (conv)
|
|
6943
|
-
void resumeConversation(conv);
|
|
7187
|
+
void (phase === 'unarchive' ? restoreArchivedConversation(conv) : resumeConversation(conv));
|
|
6944
7188
|
}
|
|
6945
7189
|
else if (key.escape || (key.ctrl && input === 'c'))
|
|
6946
7190
|
setPhase('ready');
|
|
@@ -7459,8 +7703,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
7459
7703
|
? subagents.find((agent) => agent.viewId === agentSurface.selectedId)
|
|
7460
7704
|
: undefined;
|
|
7461
7705
|
const lifecycleOwnsViewport = outputViewer !== null ||
|
|
7706
|
+
scheduleReviewPrompt !== null ||
|
|
7462
7707
|
phase === 'trust' ||
|
|
7463
7708
|
phase === 'resume' ||
|
|
7709
|
+
phase === 'unarchive' ||
|
|
7464
7710
|
phase === 'edit' ||
|
|
7465
7711
|
phase === 'picker' ||
|
|
7466
7712
|
agentSurface !== null;
|
|
@@ -7472,6 +7718,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
7472
7718
|
textPrompt === null &&
|
|
7473
7719
|
choicePicker === null &&
|
|
7474
7720
|
copyPicker === null &&
|
|
7721
|
+
scheduleReviewPrompt === null &&
|
|
7475
7722
|
agentSurface === null
|
|
7476
7723
|
? goalStatusLabel(goalStatus, goalStatus ? goalActivation.isArmed(goalStatus.sessionId, goalStatus) : false)
|
|
7477
7724
|
: null;
|
|
@@ -7490,33 +7737,35 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
7490
7737
|
? 'starting a fresh conversation — input is paused'
|
|
7491
7738
|
: compacting
|
|
7492
7739
|
? 'compacting conversation — input is paused'
|
|
7493
|
-
:
|
|
7494
|
-
?
|
|
7495
|
-
:
|
|
7496
|
-
?
|
|
7497
|
-
:
|
|
7498
|
-
?
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
? '
|
|
7502
|
-
:
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
:
|
|
7506
|
-
? '
|
|
7507
|
-
:
|
|
7508
|
-
?
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
? '
|
|
7512
|
-
:
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
:
|
|
7516
|
-
? '
|
|
7517
|
-
:
|
|
7518
|
-
?
|
|
7519
|
-
:
|
|
7740
|
+
: permission
|
|
7741
|
+
? hintForPhase(phase, state, session?.hasProvider === true)
|
|
7742
|
+
: scheduleReviewPrompt
|
|
7743
|
+
? 'review schedule — ↑/↓ scroll · ←/→ choose · enter apply · esc cancel'
|
|
7744
|
+
: saveSkillPrompt
|
|
7745
|
+
? 'save skill — ←/→ choose · enter apply · esc cancel'
|
|
7746
|
+
: agentSurface?.kind === 'cockpit'
|
|
7747
|
+
? agentSurface.focus === 'workflows'
|
|
7748
|
+
? 'workflows — enter select · esc return'
|
|
7749
|
+
: agentSurface.focus === 'phases'
|
|
7750
|
+
? 'agent phases — enter agents · esc return'
|
|
7751
|
+
: 'agents — enter inspect · left phases · esc return'
|
|
7752
|
+
: agentSurface?.kind === 'transcript'
|
|
7753
|
+
? 'observing agent — esc agents · q parent'
|
|
7754
|
+
: textPrompt
|
|
7755
|
+
? 'editor open · enter apply · esc cancel'
|
|
7756
|
+
: choicePicker
|
|
7757
|
+
? choicePickerSearchable(choicePicker)
|
|
7758
|
+
? 'type to filter · ↑↓ select · enter apply · esc back'
|
|
7759
|
+
: effortSlider
|
|
7760
|
+
? '←/→ adjust · 1–9 select · enter apply · esc back'
|
|
7761
|
+
: '↑↓ / 1–9 select · enter apply · esc back'
|
|
7762
|
+
: copyPicker
|
|
7763
|
+
? 'copy target open — ↑↓ / 1–9 select · esc cancel'
|
|
7764
|
+
: handoffPark && phase === 'ready' && state === 'idle' && !composerHasDraft
|
|
7765
|
+
? 'enter continue · esc stop'
|
|
7766
|
+
: goalStatus && phase === 'ready' && state === 'idle'
|
|
7767
|
+
? undefined
|
|
7768
|
+
: hintForPhase(phase, state, session?.hasProvider === true, composerHasDraft);
|
|
7520
7769
|
const permissionSource = reviewSource(permission, scopeRef.current?.sessionId);
|
|
7521
7770
|
const permissionOwner = permissionSource
|
|
7522
7771
|
? subagents.find((agent) => agent.sessionId === permissionSource)
|
|
@@ -7568,24 +7817,26 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
7568
7817
|
setPickerSelectionKind('provider-and-model');
|
|
7569
7818
|
setPhase('picker');
|
|
7570
7819
|
void discoverProviders({ skipProbes: true }).then(setPickerDetected).catch((error) => setPickerNotice(String(error)));
|
|
7571
|
-
} })) : phase === 'trust' ? (_jsx(TrustPrompt, { cwd: ctx.cwd })) : phase === 'unhealthy' ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: theme.status.error, bold: true, children: "Startup stopped" }), _jsx(Text, { color: theme.text.secondary, children: "Resolve the error above, then restart Namzu." })] })) : 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, onSetup: () => setProviderSetup(true), onCredential: handleTypedCredential, onLogin: startLoginFromPicker, onLoginComplete: finishLoginFromPicker, keyEntryFor: keyEntryFor, notice: pickerNotice,
|
|
7820
|
+
} })) : phase === 'trust' ? (_jsx(TrustPrompt, { cwd: ctx.cwd })) : phase === 'unhealthy' ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: theme.status.error, bold: true, children: "Startup stopped" }), _jsx(Text, { color: theme.text.secondary, children: "Resolve the error above, then restart Namzu." })] })) : phase === 'resume' || phase === 'unarchive' ? (_jsx(ResumePicker, { conversations: resumeList, selected: selectedResume, mode: phase === 'unarchive' ? 'restore' : 'resume' })) : 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, onSetup: () => setProviderSetup(true), onCredential: handleTypedCredential, onLogin: startLoginFromPicker, onLoginComplete: finishLoginFromPicker, keyEntryFor: keyEntryFor, notice: pickerNotice,
|
|
7572
7821
|
// The same condition `handlePickerCancel` branches on, read
|
|
7573
7822
|
// where it is decided rather than guessed on the screen: with no
|
|
7574
7823
|
// session behind the picker, esc exits namzu and the footer says
|
|
7575
7824
|
// so instead of offering a cancel nobody will see.
|
|
7576
|
-
cancelExits: !session?.hasProvider })) : (_jsxs(_Fragment, { children: [agentSurface === null && outputViewer === null ? (_jsx(LiveActivity, { compact: compactWork, activeTools: visibleActiveTools, working: state === 'thinking' || state === 'tool', interruptible: abortRef.current !== null, animate: stdout.isTTY === true &&
|
|
7825
|
+
cancelExits: !session?.hasProvider })) : (_jsxs(_Fragment, { children: [agentSurface === null && outputViewer === null && scheduleReviewPrompt === null ? (_jsx(LiveActivity, { compact: compactWork, activeTools: visibleActiveTools, working: state === 'thinking' || state === 'tool', interruptible: abortRef.current !== null, animate: stdout.isTTY === true &&
|
|
7577
7826
|
permission === null &&
|
|
7578
7827
|
textPrompt === null &&
|
|
7579
|
-
saveSkillPrompt === null
|
|
7828
|
+
saveSkillPrompt === null &&
|
|
7829
|
+
scheduleReviewPrompt === null, thinking: thinking, tokens: turnTokens, waitingForYou: choicePicker !== null ||
|
|
7580
7830
|
permission !== null ||
|
|
7581
7831
|
textPrompt !== null ||
|
|
7582
|
-
saveSkillPrompt !== null
|
|
7832
|
+
saveSkillPrompt !== null ||
|
|
7833
|
+
scheduleReviewPrompt !== null })) : null, agentSurface === null && outputViewer === null && permission === null && scheduleReviewPrompt === null ? (_jsx(TaskList, { tasks: liveTasks })) : null, permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls, review: permission.review, summary: permission.summary, detailsOpen: permissionDetailsOpen, reviewOffset: permissionReviewOffset, choice: permissionChoice, queuedCount: queuedPermissionCount, sourceLabel: permissionSourceLabel, columns: terminal.columns, rows: terminal.rows, batchOnly: permission.batchOnly === true, siteNotes: permission.siteNotes, ...(permission.screenConsent === true
|
|
7583
7834
|
? { screenConsent: { provider: session?.providerSummary ?? null } }
|
|
7584
|
-
: {}) })) : null, saveSkillPrompt && permission === null ? (_jsx(SaveSkillOverlay, { request: saveSkillPrompt.request, cwd: ctx.cwd, columns: Math.max(1, (terminal.columns ?? 80) - 2), rows: terminal.rows, onAnswer: saveSkillPrompt.resolve }, saveSkillPrompt.request.markdown)) : null, textPrompt ? (_jsx(TextPrompt, { columns: Math.max(1, (terminal.columns ?? 80) - 2), title: textPrompt.title, placeholder: textPrompt.placeholder, initialValue: textPrompt.initialValue, emptyNotice: textPrompt.emptyNotice, hidden: permission !== null || (agentSurface !== null || outputViewer !== null), onSubmit: submitTextPrompt, onCancel: cancelTextPrompt }, textPrompt.token)) : permission === null &&
|
|
7835
|
+
: {}) })) : null, saveSkillPrompt && permission === null && scheduleReviewPrompt === null ? (_jsx(SaveSkillOverlay, { request: saveSkillPrompt.request, cwd: ctx.cwd, columns: Math.max(1, (terminal.columns ?? 80) - 2), rows: terminal.rows, onAnswer: saveSkillPrompt.resolve }, saveSkillPrompt.request.markdown)) : null, scheduleReviewPrompt && permission === null ? (_jsx(ScheduleReviewOverlay, { request: scheduleReviewPrompt.request, columns: Math.max(1, (terminal.columns ?? 80) - 2), rows: Math.max(1, (terminal.rows ?? 24) - 2), onAnswer: scheduleReviewPrompt.resolve }, scheduleReviewPrompt.token)) : null, textPrompt ? (_jsx(TextPrompt, { columns: Math.max(1, (terminal.columns ?? 80) - 2), title: textPrompt.title, placeholder: textPrompt.placeholder, initialValue: textPrompt.initialValue, emptyNotice: textPrompt.emptyNotice, hidden: permission !== null || scheduleReviewPrompt !== null || (agentSurface !== null || outputViewer !== null), onSubmit: submitTextPrompt, onCancel: cancelTextPrompt }, textPrompt.token)) : permission === null && scheduleReviewPrompt === null &&
|
|
7585
7836
|
agentSurface === null &&
|
|
7586
7837
|
outputViewer === null &&
|
|
7587
7838
|
choicePicker?.kind === 'reasoning-effort' &&
|
|
7588
|
-
effortSlider ? (_jsx(EffortSlider, { title: choicePicker.title, notice: choicePicker.notice, options: choicePicker.options, selected: selectedChoice, layout: effortSlider, columns: Math.max(1, (terminal.columns ?? 80) - 2) })) : permission === null && agentSurface === null && outputViewer === null && choicePicker ? (_jsx(ChoicePicker, { busy: 'busy' in choicePicker && choicePicker.busy === true, columns: Math.max(1, (terminal.columns ?? 80) - 2), title: choicePicker.title, notice: choicePicker.notice, options: filterChoiceOptions(choicePicker.options, choiceQuery), query: choicePickerSearchable(choicePicker) ? choiceQuery : undefined, selected: selectedChoice, windowSize: choicePicker.kind === 'command' ? choicePicker.windowSize : undefined })) : permission === null && agentSurface === null && outputViewer === null && copyPicker ? (_jsx(CopyPicker, { targets: copyPicker.targets, selected: selectedCopy })) : null, _jsxs(ComposerFrame, { working: state === 'thinking' || state === 'tool' || visibleActiveTools.length > 0, ...(hypermode ? { mode: HYPERMODE } : {}), focus: phase === 'ready' &&
|
|
7839
|
+
effortSlider ? (_jsx(EffortSlider, { title: choicePicker.title, notice: choicePicker.notice, options: choicePicker.options, selected: selectedChoice, layout: effortSlider, columns: Math.max(1, (terminal.columns ?? 80) - 2) })) : permission === null && scheduleReviewPrompt === null && agentSurface === null && outputViewer === null && choicePicker ? (_jsx(ChoicePicker, { busy: 'busy' in choicePicker && choicePicker.busy === true, columns: Math.max(1, (terminal.columns ?? 80) - 2), title: choicePicker.title, notice: choicePicker.notice, options: filterChoiceOptions(choicePicker.options, choiceQuery), query: choicePickerSearchable(choicePicker) ? choiceQuery : undefined, selected: selectedChoice, windowSize: choicePicker.kind === 'command' ? choicePicker.windowSize : undefined })) : permission === null && scheduleReviewPrompt === null && agentSurface === null && outputViewer === null && copyPicker ? (_jsx(CopyPicker, { targets: copyPicker.targets, selected: selectedCopy })) : null, _jsxs(ComposerFrame, { working: state === 'thinking' || state === 'tool' || visibleActiveTools.length > 0, ...(hypermode ? { mode: HYPERMODE } : {}), focus: phase === 'ready' &&
|
|
7589
7840
|
state !== 'awaiting-permission' &&
|
|
7590
7841
|
!compacting &&
|
|
7591
7842
|
externalEditorRequest === null &&
|
|
@@ -7594,8 +7845,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
7594
7845
|
choicePicker === null &&
|
|
7595
7846
|
copyPicker === null &&
|
|
7596
7847
|
saveSkillPrompt === null &&
|
|
7848
|
+
scheduleReviewPrompt === null &&
|
|
7597
7849
|
agentSurface === null && outputViewer === null, hidden: permission !== null ||
|
|
7598
7850
|
saveSkillPrompt !== null ||
|
|
7851
|
+
scheduleReviewPrompt !== null ||
|
|
7599
7852
|
textPrompt !== null ||
|
|
7600
7853
|
choicePicker !== null ||
|
|
7601
7854
|
copyPicker !== null ||
|
|
@@ -7621,6 +7874,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
7621
7874
|
externalEditorRequest !== null ||
|
|
7622
7875
|
(agentSurface !== null || outputViewer !== null), hidden: permission !== null ||
|
|
7623
7876
|
saveSkillPrompt !== null ||
|
|
7877
|
+
scheduleReviewPrompt !== null ||
|
|
7624
7878
|
textPrompt !== null ||
|
|
7625
7879
|
choicePicker !== null ||
|
|
7626
7880
|
copyPicker !== null ||
|
|
@@ -7637,17 +7891,19 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
7637
7891
|
})), mentionCandidates: mentionCandidates, history: history, ...(composerTriggers ? { triggers: composerTriggers } : {}), turnActive: !compacting && (state === 'thinking' || state === 'tool') })] })] })), _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, effort: reasoningEffort, hypermode: hypermode, goal: statusGoal, state: state, hint: statusHint, permissionMode: displayedPermissionMode, canCycleMode: true }), showComposerSurface &&
|
|
7638
7892
|
permission === null &&
|
|
7639
7893
|
agentSurface === null &&
|
|
7640
|
-
outputViewer === null
|
|
7894
|
+
outputViewer === null &&
|
|
7895
|
+
scheduleReviewPrompt === null ? (_jsx(AgentNarrationBand, { lines: narration })) : null, showComposerSurface &&
|
|
7641
7896
|
agentSurface === null &&
|
|
7642
7897
|
outputViewer === null &&
|
|
7898
|
+
scheduleReviewPrompt === null &&
|
|
7643
7899
|
liveSubagents.length > 0 ? (
|
|
7644
|
-
//
|
|
7645
|
-
// the
|
|
7646
|
-
|
|
7647
|
-
_jsx(AgentTaskPanel, { agents: liveSubagents, terminalRows: terminal.rows, terminalColumns: terminal.columns, compact: permission !== null })) : 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 &&
|
|
7900
|
+
// The active agents remain in memory while the schedule review
|
|
7901
|
+
// owns the available rows; the panel returns after the decision.
|
|
7902
|
+
_jsx(AgentTaskPanel, { agents: liveSubagents, terminalRows: terminal.rows, terminalColumns: terminal.columns, compact: permission !== null })) : showComposerSurface && permission === null && scheduleReviewPrompt === null && agentSurface?.kind === 'cockpit' ? (_jsx(AgentCockpit, { agents: subagents, selectedPhaseId: agentSurface.selectedPhaseId, selectedId: agentSurface.selectedId, focus: agentSurface.focus, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : showComposerSurface &&
|
|
7648
7903
|
permission === null &&
|
|
7904
|
+
scheduleReviewPrompt === null &&
|
|
7649
7905
|
agentSurface?.kind === 'transcript' &&
|
|
7650
|
-
selectedSubagent ? (_jsx(AgentTranscript, { agent: selectedSubagent, tailOffset: agentSurface.tailOffset, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : null, outputViewer && permission === null ? (_jsx(ToolOutputViewer, { onPrevious: () => {
|
|
7906
|
+
selectedSubagent ? (_jsx(AgentTranscript, { agent: selectedSubagent, tailOffset: agentSurface.tailOffset, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : null, outputViewer && permission === null && scheduleReviewPrompt === null ? (_jsx(ToolOutputViewer, { onPrevious: () => {
|
|
7651
7907
|
if (outputIndex > 0)
|
|
7652
7908
|
setOutputViewer(outputBlocks[outputIndex - 1]);
|
|
7653
7909
|
}, onNext: () => {
|
|
@@ -7687,6 +7943,8 @@ hasDraft = false) {
|
|
|
7687
7943
|
return 'y trust this folder · n / esc exit';
|
|
7688
7944
|
if (phase === 'resume')
|
|
7689
7945
|
return '↑↓ navigate · enter resume · esc cancel';
|
|
7946
|
+
if (phase === 'unarchive')
|
|
7947
|
+
return '↑↓ navigate · enter restore · esc cancel';
|
|
7690
7948
|
if (phase === 'edit')
|
|
7691
7949
|
return '← older · → newer · enter fork and edit · esc cancel';
|
|
7692
7950
|
if (phase === 'probing')
|