@namzu/cli 15.1.1 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -6
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +8 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +8 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/context/capabilities.d.ts +7 -4
- package/dist/context/capabilities.d.ts.map +1 -1
- package/dist/context/capabilities.js +7 -4
- package/dist/context/capabilities.js.map +1 -1
- package/dist/context/sandbox.d.ts +5 -0
- package/dist/context/sandbox.d.ts.map +1 -1
- package/dist/context/sandbox.js +11 -4
- package/dist/context/sandbox.js.map +1 -1
- package/dist/doctor/checks/chain.d.ts.map +1 -1
- package/dist/doctor/checks/chain.js +1 -0
- package/dist/doctor/checks/chain.js.map +1 -1
- package/dist/integrations/subagents/activity.d.ts +70 -0
- package/dist/integrations/subagents/activity.d.ts.map +1 -0
- package/dist/integrations/subagents/activity.js +332 -0
- package/dist/integrations/subagents/activity.js.map +1 -0
- package/dist/integrations/subagents/runtime.d.ts +11 -1
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +27 -2
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/tui/AgentExplorer.d.ts +36 -0
- package/dist/tui/AgentExplorer.d.ts.map +1 -0
- package/dist/tui/AgentExplorer.js +174 -0
- package/dist/tui/AgentExplorer.js.map +1 -0
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +269 -106
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Composer.d.ts +7 -1
- package/dist/tui/Composer.d.ts.map +1 -1
- package/dist/tui/Composer.js +26 -5
- package/dist/tui/Composer.js.map +1 -1
- package/dist/tui/LiveActivity.d.ts +9 -3
- package/dist/tui/LiveActivity.d.ts.map +1 -1
- package/dist/tui/LiveActivity.js +27 -10
- package/dist/tui/LiveActivity.js.map +1 -1
- package/dist/tui/PermissionOverlay.d.ts +8 -9
- package/dist/tui/PermissionOverlay.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.js +6 -10
- package/dist/tui/PermissionOverlay.js.map +1 -1
- package/dist/tui/StatusBar.d.ts +1 -47
- package/dist/tui/StatusBar.d.ts.map +1 -1
- package/dist/tui/StatusBar.js +9 -79
- package/dist/tui/StatusBar.js.map +1 -1
- package/dist/tui/agent.d.ts +3 -0
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +199 -176
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/permission-review.d.ts +18 -2
- package/dist/tui/permission-review.d.ts.map +1 -1
- package/dist/tui/permission-review.js +88 -2
- package/dist/tui/permission-review.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +4 -0
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +14 -0
- package/dist/tui/slashCommands.js.map +1 -1
- package/package.json +4 -4
package/dist/tui/App.js
CHANGED
|
@@ -28,19 +28,18 @@ import { checkUpdates } from '../integrations/updates.js';
|
|
|
28
28
|
import { appendMemory, composeMemoryPrompt, readMemory } from '../memory/store.js';
|
|
29
29
|
import { composeSkillsPrompt, discoverSkills, loadSkillBody } from '../skills/store.js';
|
|
30
30
|
import { discoverUserCommands } from '../user-commands/store.js';
|
|
31
|
+
import { AgentPicker, AgentTranscript, agentPickerPageSize, agentTranscriptPageSize, maxAgentTranscriptTailOffset, } from './AgentExplorer.js';
|
|
31
32
|
import { ChoicePicker } from './ChoicePicker.js';
|
|
32
33
|
import { Composer, suggestionWindowSize, } from './Composer.js';
|
|
33
34
|
import { CopyPicker } from './CopyPicker.js';
|
|
34
35
|
import { EditPromptPicker } from './EditPromptPicker.js';
|
|
35
36
|
import { LiveActivity, formatElapsed } from './LiveActivity.js';
|
|
36
37
|
import { PermissionOverlay } from './PermissionOverlay.js';
|
|
37
|
-
import { PERMISSION_REVIEW_PAGE_ROWS, buildPermissionReview, permissionReviewRefusal, permissionReviewRows, } from './permission-review.js';
|
|
38
38
|
import { Picker } from './Picker.js';
|
|
39
39
|
import { ResumePicker } from './ResumePicker.js';
|
|
40
40
|
import { StatusBar } from './StatusBar.js';
|
|
41
41
|
import { TextPrompt } from './TextPrompt.js';
|
|
42
42
|
import { Transcript, willCollapse } from './Transcript.js';
|
|
43
|
-
import { terminalSupportsHyperlinks } from './terminal-hyperlinks.js';
|
|
44
43
|
import { TrustPrompt } from './TrustPrompt.js';
|
|
45
44
|
import { createAgentSession, probeAgentSession, } from './agent.js';
|
|
46
45
|
import { keepRecentRows } from './compact-transcript.js';
|
|
@@ -48,18 +47,20 @@ import { approvalIsDeliberate } from './consent-timing.js';
|
|
|
48
47
|
import { planTurnPublication } from './conversation-history.js';
|
|
49
48
|
import { copyTargetsForResponse } from './copy-targets.js';
|
|
50
49
|
import { editablePrompts } from './edit-prompts.js';
|
|
50
|
+
import { editDraftInExternalEditor } from './external-editor.js';
|
|
51
51
|
import { liveWindow } from './live-window.js';
|
|
52
|
-
import { describeRunInterruption } from './run-interruption.js';
|
|
53
52
|
import { describeCodexDeviceLoginStart, describeLoginOutcome, describeLoginStart, describeLogout, describeProviderLogout, } from './login-prompt.js';
|
|
54
53
|
import { NAMZU_MARK, NAMZU_MARK_COLOR, NAMZU_WORDMARK, NAMZU_WORDMARK_GRADIENT, NAMZU_WORDMARK_MIN_WIDTH, } from './logo.js';
|
|
55
54
|
import { expandFileMentions, listMentionableFiles } from './mentions.js';
|
|
56
55
|
import { openInBrowser } from './open-browser.js';
|
|
56
|
+
import { PERMISSION_REVIEW_PAGE_ROWS, buildPermissionReview, buildPermissionSummary, permissionReviewRefusal, permissionReviewRows, } from './permission-review.js';
|
|
57
|
+
import { describeRunInterruption } from './run-interruption.js';
|
|
58
|
+
import { moveSelection } from './selection-window.js';
|
|
57
59
|
import { baseBranchReviewPrompt, commitReviewPrompt, hostCommandNames, kernelCommandDescriptors, mergeHostCommands, renderOutcome, reviewPrompt, runSlash, } from './slashCommands.js';
|
|
58
60
|
import { splitCompleteBlocks } from './stream-blocks.js';
|
|
59
|
-
import {
|
|
61
|
+
import { terminalSupportsHyperlinks } from './terminal-hyperlinks.js';
|
|
60
62
|
import { theme } from './theme.js';
|
|
61
63
|
import { useSelectionIndex } from './use-selection-index.js';
|
|
62
|
-
import { editDraftInExternalEditor } from './external-editor.js';
|
|
63
64
|
import { renderWorkspaceDiff, workspaceDiff } from './workspace-diff.js';
|
|
64
65
|
import { listReviewBranches, listReviewCommits, reviewMergeBase, } from './workspace-review.js';
|
|
65
66
|
/** Last non-empty assistant text in a durable conversation, newest first. */
|
|
@@ -293,6 +294,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
293
294
|
const [permission, setPermission] = useState(null);
|
|
294
295
|
const [permissionReviewOffset, setPermissionReviewOffsetState] = useState(0);
|
|
295
296
|
const permissionReviewOffsetRef = useRef(0);
|
|
297
|
+
const [permissionDetailsOpen, setPermissionDetailsOpenState] = useState(false);
|
|
298
|
+
const permissionDetailsOpenRef = useRef(false);
|
|
299
|
+
const setPermissionDetailsOpen = useCallback((next) => {
|
|
300
|
+
permissionDetailsOpenRef.current = next;
|
|
301
|
+
setPermissionDetailsOpenState(next);
|
|
302
|
+
}, []);
|
|
296
303
|
const setPermissionReviewOffset = useCallback((next) => {
|
|
297
304
|
permissionReviewOffsetRef.current = next;
|
|
298
305
|
setPermissionReviewOffsetState(next);
|
|
@@ -319,10 +326,6 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
319
326
|
const [mentionCandidates, setMentionCandidates] = useState([]);
|
|
320
327
|
const mentionLoadOwnerRef = useRef(null);
|
|
321
328
|
const [usage, setUsage] = useState(null);
|
|
322
|
-
// Context fill, straight from the kernel and held apart from `usage` —
|
|
323
|
-
// they are different quantities and conflating them is what made the
|
|
324
|
-
// gauge climb with turn count instead of with context.
|
|
325
|
-
const [context, setContext] = useState(null);
|
|
326
329
|
// Tools currently executing — rendered live (spinner + elapsed) below the
|
|
327
330
|
// transcript, then committed as static lines on completion.
|
|
328
331
|
const [activeTools, setActiveTools] = useState([]);
|
|
@@ -392,11 +395,17 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
392
395
|
/** `/copy` owns this exact response snapshot until selection or cancellation. */
|
|
393
396
|
const [copyPicker, setCopyPickerState] = useState(null);
|
|
394
397
|
const copyPickerRef = useRef(null);
|
|
398
|
+
const copyPickerCommittedRef = useRef(null);
|
|
395
399
|
const { selection: selectedCopy, selectionRef: selectedCopyRef, setSelection: setSelectedCopy, } = useSelectionIndex(0);
|
|
396
400
|
const setCopyPicker = useCallback((next) => {
|
|
397
401
|
copyPickerRef.current = next;
|
|
402
|
+
if (next === null)
|
|
403
|
+
copyPickerCommittedRef.current = null;
|
|
398
404
|
setCopyPickerState(next);
|
|
399
405
|
}, []);
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
copyPickerCommittedRef.current = copyPicker;
|
|
408
|
+
}, [copyPicker]);
|
|
400
409
|
/** Finite slash-command choice owned synchronously until apply or cancel. */
|
|
401
410
|
const [choicePicker, setChoicePickerState] = useState(null);
|
|
402
411
|
const choicePickerRef = useRef(null);
|
|
@@ -426,7 +435,37 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
426
435
|
setTextPromptState(next);
|
|
427
436
|
}, []);
|
|
428
437
|
const [composerDraft, setComposerDraft] = useState(null);
|
|
438
|
+
const [composerHasDraft, setComposerHasDraft] = useState(false);
|
|
429
439
|
const composerDraftTokenRef = useRef(0);
|
|
440
|
+
/** Bounded child-run projection published by the current AgentSession. */
|
|
441
|
+
const [subagents, setSubagentsState] = useState([]);
|
|
442
|
+
const subagentsRef = useRef([]);
|
|
443
|
+
const [agentSurface, setAgentSurfaceState] = useState(null);
|
|
444
|
+
const agentSurfaceRef = useRef(null);
|
|
445
|
+
const agentSurfaceCommittedRef = useRef(null);
|
|
446
|
+
const setAgentSurface = useCallback((next) => {
|
|
447
|
+
const wasVisible = agentSurfaceCommittedRef.current !== null;
|
|
448
|
+
agentSurfaceRef.current = next;
|
|
449
|
+
if (next === null)
|
|
450
|
+
agentSurfaceCommittedRef.current = null;
|
|
451
|
+
// Only opening the surface needs a paint fence. Once the operator has
|
|
452
|
+
// seen it, navigation may update the state object several times in one
|
|
453
|
+
// terminal input burst; those updates remain actionable immediately.
|
|
454
|
+
else if (wasVisible)
|
|
455
|
+
agentSurfaceCommittedRef.current = next;
|
|
456
|
+
setAgentSurfaceState(next);
|
|
457
|
+
}, []);
|
|
458
|
+
useEffect(() => {
|
|
459
|
+
agentSurfaceCommittedRef.current = agentSurface;
|
|
460
|
+
}, [agentSurface]);
|
|
461
|
+
const replaceSubagents = useCallback((next) => {
|
|
462
|
+
subagentsRef.current = next;
|
|
463
|
+
setSubagentsState(next);
|
|
464
|
+
const surface = agentSurfaceRef.current;
|
|
465
|
+
if (surface && !next.some((agent) => agent.viewId === surface.selectedId)) {
|
|
466
|
+
setAgentSurface(null);
|
|
467
|
+
}
|
|
468
|
+
}, [setAgentSurface]);
|
|
430
469
|
/** One git-backed review choice may be resolving while its visible picker remains authoritative. */
|
|
431
470
|
const reviewChoiceInFlightRef = useRef(null);
|
|
432
471
|
const exitArmedRef = useRef(false);
|
|
@@ -466,6 +505,22 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
466
505
|
* server's child process outlives the object that opened it.
|
|
467
506
|
*/
|
|
468
507
|
const previousSessionRef = useRef(null);
|
|
508
|
+
useEffect(() => {
|
|
509
|
+
const source = session?.subagents;
|
|
510
|
+
setAgentSurface(null);
|
|
511
|
+
if (!source) {
|
|
512
|
+
replaceSubagents([]);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const refresh = () => replaceSubagents(source.getSnapshot());
|
|
516
|
+
refresh();
|
|
517
|
+
return source.subscribe(refresh);
|
|
518
|
+
}, [replaceSubagents, session, setAgentSurface]);
|
|
519
|
+
const resetSubagentActivity = useCallback(() => {
|
|
520
|
+
session?.subagents?.reset();
|
|
521
|
+
setAgentSurface(null);
|
|
522
|
+
replaceSubagents([]);
|
|
523
|
+
}, [replaceSubagents, session, setAgentSurface]);
|
|
469
524
|
// Source of truth for in-flight tools (the event loop runs across renders, so
|
|
470
525
|
// a ref avoids stale state); `activeTools` mirrors it for rendering.
|
|
471
526
|
const activeToolsRef = useRef([]);
|
|
@@ -1341,8 +1396,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1341
1396
|
const mentionLoadOwner = {};
|
|
1342
1397
|
mentionLoadOwnerRef.current = mentionLoadOwner;
|
|
1343
1398
|
void listMentionableFiles(activeCtx.cwd, appLifetime.signal).then((files) => {
|
|
1344
|
-
if (!appLifetime.signal.aborted &&
|
|
1345
|
-
mentionLoadOwnerRef.current === mentionLoadOwner)
|
|
1399
|
+
if (!appLifetime.signal.aborted && mentionLoadOwnerRef.current === mentionLoadOwner)
|
|
1346
1400
|
setMentionCandidates(files);
|
|
1347
1401
|
});
|
|
1348
1402
|
setCurrentProvider(primaryProvider(prefs).id);
|
|
@@ -1715,7 +1769,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1715
1769
|
settled: settledRef.current,
|
|
1716
1770
|
raw: rawOutput,
|
|
1717
1771
|
});
|
|
1718
|
-
|
|
1772
|
+
// A child view is a redrawable overlay above the parent's native scrollback.
|
|
1773
|
+
// Freeze the parent's Static floor while it is open; otherwise a parent turn
|
|
1774
|
+
// settling in the background prints through the child screen. Returning
|
|
1775
|
+
// advances the floor once and emits those finalized rows exactly once.
|
|
1776
|
+
if (agentSurface === null)
|
|
1777
|
+
settledRef.current = window.settled;
|
|
1778
|
+
const renderedSettled = agentSurface === null ? window.settled : settledRef.current;
|
|
1719
1779
|
// One merged vocabulary for the session: this host's own commands plus
|
|
1720
1780
|
// whatever the kernel's registry reports. Built here so `/help`, the
|
|
1721
1781
|
// autocomplete and the dispatcher all answer from the same list — three
|
|
@@ -1801,10 +1861,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1801
1861
|
permissionResolveRef.current = null;
|
|
1802
1862
|
permissionOpenedAtRef.current = null;
|
|
1803
1863
|
setPermissionReviewOffset(0);
|
|
1864
|
+
setPermissionDetailsOpen(false);
|
|
1804
1865
|
setPermission(null);
|
|
1805
1866
|
if (resolve)
|
|
1806
1867
|
resolve(decision);
|
|
1807
|
-
}, [setPermissionReviewOffset]);
|
|
1868
|
+
}, [setPermissionDetailsOpen, setPermissionReviewOffset]);
|
|
1808
1869
|
/**
|
|
1809
1870
|
* Stop the running turn, and hand the screen back in a usable state.
|
|
1810
1871
|
*
|
|
@@ -1918,6 +1979,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1918
1979
|
goalActivation.clear();
|
|
1919
1980
|
wakeGoalDriver();
|
|
1920
1981
|
conversationGenRef.current += 1;
|
|
1982
|
+
resetSubagentActivity();
|
|
1921
1983
|
activeTurnTokenRef.current = null;
|
|
1922
1984
|
resetTranscript();
|
|
1923
1985
|
setMessages(restored);
|
|
@@ -1946,6 +2008,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1946
2008
|
interruptTurn,
|
|
1947
2009
|
nextId,
|
|
1948
2010
|
pushMessage,
|
|
2011
|
+
resetSubagentActivity,
|
|
1949
2012
|
resetTranscript,
|
|
1950
2013
|
wakeGoalDriver,
|
|
1951
2014
|
]);
|
|
@@ -1985,11 +2048,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1985
2048
|
goalActivation.clear();
|
|
1986
2049
|
wakeGoalDriver();
|
|
1987
2050
|
conversationGenRef.current += 1;
|
|
2051
|
+
resetSubagentActivity();
|
|
1988
2052
|
activeTurnTokenRef.current = null;
|
|
1989
2053
|
modelHistoryRef.current = [];
|
|
1990
2054
|
lastAssistantMessage.current = null;
|
|
1991
2055
|
lastCompletedOutputRef.current = null;
|
|
1992
|
-
setContext(null);
|
|
1993
2056
|
if (sourceScope && targetSessionId)
|
|
1994
2057
|
sourceScope.sessionId = targetSessionId;
|
|
1995
2058
|
if (clearScreen) {
|
|
@@ -2017,7 +2080,15 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2017
2080
|
conversationMutationRef.current = null;
|
|
2018
2081
|
setConversationMutation(null);
|
|
2019
2082
|
}
|
|
2020
|
-
}, [
|
|
2083
|
+
}, [
|
|
2084
|
+
discardQueued,
|
|
2085
|
+
goalActivation,
|
|
2086
|
+
interruptTurn,
|
|
2087
|
+
pushMessage,
|
|
2088
|
+
resetSubagentActivity,
|
|
2089
|
+
resetTranscript,
|
|
2090
|
+
wakeGoalDriver,
|
|
2091
|
+
]);
|
|
2021
2092
|
/**
|
|
2022
2093
|
* `/rename` (and the older `/title` alias): edit or set the name this
|
|
2023
2094
|
* conversation appears under.
|
|
@@ -2139,6 +2210,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2139
2210
|
// The transcript on screen is already the fork's history, so nothing
|
|
2140
2211
|
// is reloaded or reset. Only where the NEXT turn is written changes.
|
|
2141
2212
|
scope.sessionId = forked.id;
|
|
2213
|
+
resetSubagentActivity();
|
|
2142
2214
|
goalActivation.clear();
|
|
2143
2215
|
wakeGoalDriver();
|
|
2144
2216
|
pushMessage('system', `Forked into "${forked.title}" — ${forked.copied} message(s) copied. This screen continues in the copy; ${original} is unchanged and still in /resume.`);
|
|
@@ -2150,7 +2222,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2150
2222
|
conversationMutationRef.current = null;
|
|
2151
2223
|
setConversationMutation(null);
|
|
2152
2224
|
}
|
|
2153
|
-
}, [
|
|
2225
|
+
}, [
|
|
2226
|
+
ensureSessions,
|
|
2227
|
+
goalActivation,
|
|
2228
|
+
hasUnsettledTurn,
|
|
2229
|
+
pushMessage,
|
|
2230
|
+
resetSubagentActivity,
|
|
2231
|
+
wakeGoalDriver,
|
|
2232
|
+
]);
|
|
2154
2233
|
/** Open the durable prompt picker after the composer's second empty Esc. */
|
|
2155
2234
|
const openPromptEditor = useCallback(() => {
|
|
2156
2235
|
if (conversationMutationRef.current || compactingRef.current)
|
|
@@ -2206,6 +2285,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2206
2285
|
.slice(0, target.userOrdinal)
|
|
2207
2286
|
.map((prompt) => prompt.displayText);
|
|
2208
2287
|
conversationGenRef.current += 1;
|
|
2288
|
+
resetSubagentActivity();
|
|
2209
2289
|
activeTurnTokenRef.current = null;
|
|
2210
2290
|
goalActivation.clear();
|
|
2211
2291
|
wakeGoalDriver();
|
|
@@ -2246,6 +2326,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2246
2326
|
hasUnsettledTurn,
|
|
2247
2327
|
nextId,
|
|
2248
2328
|
pushMessage,
|
|
2329
|
+
resetSubagentActivity,
|
|
2249
2330
|
resetTranscript,
|
|
2250
2331
|
wakeGoalDriver,
|
|
2251
2332
|
]);
|
|
@@ -2259,15 +2340,17 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2259
2340
|
feedback: permissionReviewRefusal(review.reason),
|
|
2260
2341
|
});
|
|
2261
2342
|
}
|
|
2343
|
+
const summary = buildPermissionSummary(review.text);
|
|
2262
2344
|
return new Promise((resolve) => {
|
|
2263
2345
|
permissionResolveRef.current = resolve;
|
|
2264
2346
|
permissionOpenedAtRef.current = Date.now();
|
|
2265
2347
|
setPermissionReviewOffset(0);
|
|
2266
|
-
|
|
2348
|
+
setPermissionDetailsOpen(!summary.complete);
|
|
2349
|
+
setPermission({ ...req, review: review.text, summary });
|
|
2267
2350
|
setState('awaiting-permission');
|
|
2268
2351
|
sendTerminalNotification({ kind: 'approval-required' });
|
|
2269
2352
|
});
|
|
2270
|
-
}, [sendTerminalNotification, setPermissionReviewOffset]);
|
|
2353
|
+
}, [sendTerminalNotification, setPermissionDetailsOpen, setPermissionReviewOffset]);
|
|
2271
2354
|
// Render one agent event onto the transcript. Shared by the local turn loop
|
|
2272
2355
|
// and the daemon-attach poller, so both paths produce identical output.
|
|
2273
2356
|
// `st` carries the streaming-assistant bubble id + accumulated text across
|
|
@@ -2368,20 +2451,6 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2368
2451
|
}
|
|
2369
2452
|
case 'usage':
|
|
2370
2453
|
setUsage({ totalTokens: event.totalTokens, cost: event.cost });
|
|
2371
|
-
// Mirrored verbatim, absences included. A run that reports no
|
|
2372
|
-
// window clears the gauge rather than leaving the last one up:
|
|
2373
|
-
// a stale bar is read as current, and this bar's whole defect
|
|
2374
|
-
// was being read as something it was not.
|
|
2375
|
-
setContext({
|
|
2376
|
-
...(event.contextTokens !== undefined ? { tokens: event.contextTokens } : {}),
|
|
2377
|
-
...(event.contextWindowTokens !== undefined
|
|
2378
|
-
? { windowTokens: event.contextWindowTokens }
|
|
2379
|
-
: {}),
|
|
2380
|
-
...(event.contextMeasuredBy !== undefined
|
|
2381
|
-
? { measuredBy: event.contextMeasuredBy }
|
|
2382
|
-
: {}),
|
|
2383
|
-
...(event.windowSource !== undefined ? { windowSource: event.windowSource } : {}),
|
|
2384
|
-
});
|
|
2385
2454
|
break;
|
|
2386
2455
|
case 'task':
|
|
2387
2456
|
pushMessage('tool', event.subject, false, event.status === 'completed' ? '☑' : '☐');
|
|
@@ -2909,6 +2978,18 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2909
2978
|
});
|
|
2910
2979
|
return;
|
|
2911
2980
|
}
|
|
2981
|
+
case 'agent-picker': {
|
|
2982
|
+
const agents = subagentsRef.current;
|
|
2983
|
+
if (agents.length === 0) {
|
|
2984
|
+
pushMessage('system', 'No delegated agents are available in this conversation yet. When an Agent tool starts one, /agent opens its live activity and retained transcript.');
|
|
2985
|
+
return;
|
|
2986
|
+
}
|
|
2987
|
+
setAgentSurface({
|
|
2988
|
+
kind: 'picker',
|
|
2989
|
+
selectedId: agents[0]?.viewId ?? '',
|
|
2990
|
+
});
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2912
2993
|
case 'clear-screen':
|
|
2913
2994
|
setMessages([]);
|
|
2914
2995
|
resetTranscript();
|
|
@@ -3417,11 +3498,6 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3417
3498
|
await replaceConversation(sessions, destination, result.messages);
|
|
3418
3499
|
}
|
|
3419
3500
|
modelHistoryRef.current = result.messages;
|
|
3420
|
-
// The old gauge measured the history that was just replaced.
|
|
3421
|
-
// Clear it only after the durable projection and live model
|
|
3422
|
-
// history agree; until then the old measurement is still the
|
|
3423
|
-
// truthful one. The next run publishes a fresh measurement.
|
|
3424
|
-
setContext(null);
|
|
3425
3501
|
// How many user/assistant turns survived the pass.
|
|
3426
3502
|
// `keepRecentRows` explains why the transcript is trimmed
|
|
3427
3503
|
// to match rather than rebuilt from `result.messages`.
|
|
@@ -3906,13 +3982,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3906
3982
|
return;
|
|
3907
3983
|
}
|
|
3908
3984
|
if (key.home || key.end || key.pageUp || key.pageDown) {
|
|
3909
|
-
setSelectedEdit((index) => moveSelection(index, editList.length, key.home
|
|
3910
|
-
? 'first'
|
|
3911
|
-
: key.end
|
|
3912
|
-
? 'last'
|
|
3913
|
-
: key.pageUp
|
|
3914
|
-
? 'previous-page'
|
|
3915
|
-
: 'next-page'));
|
|
3985
|
+
setSelectedEdit((index) => moveSelection(index, editList.length, key.home ? 'first' : key.end ? 'last' : key.pageUp ? 'previous-page' : 'next-page'));
|
|
3916
3986
|
return;
|
|
3917
3987
|
}
|
|
3918
3988
|
if (key.escape || key.leftArrow || key.upArrow) {
|
|
@@ -3939,13 +4009,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3939
4009
|
if (resumeCommittedRef.current)
|
|
3940
4010
|
return;
|
|
3941
4011
|
if (key.home || key.end || key.pageUp || key.pageDown) {
|
|
3942
|
-
setSelectedResume((index) => moveSelection(index, resumeList.length, key.home
|
|
3943
|
-
? 'first'
|
|
3944
|
-
: key.end
|
|
3945
|
-
? 'last'
|
|
3946
|
-
: key.pageUp
|
|
3947
|
-
? 'previous-page'
|
|
3948
|
-
: 'next-page'));
|
|
4012
|
+
setSelectedResume((index) => moveSelection(index, resumeList.length, key.home ? 'first' : key.end ? 'last' : key.pageUp ? 'previous-page' : 'next-page'));
|
|
3949
4013
|
}
|
|
3950
4014
|
else if (key.upArrow)
|
|
3951
4015
|
setSelectedResume((index) => moveSelection(index, resumeList.length, 'previous'));
|
|
@@ -3989,9 +4053,18 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3989
4053
|
}
|
|
3990
4054
|
// A pending permission prompt owns the keyboard: y/n/a decide it.
|
|
3991
4055
|
if (permissionResolveRef.current) {
|
|
4056
|
+
const ch = input.toLowerCase();
|
|
4057
|
+
if (permission && ch === 'd') {
|
|
4058
|
+
setPermissionReviewOffset(0);
|
|
4059
|
+
setPermissionDetailsOpen(!permissionDetailsOpenRef.current);
|
|
4060
|
+
return;
|
|
4061
|
+
}
|
|
3992
4062
|
if (permission &&
|
|
3993
4063
|
(key.upArrow || key.downArrow || key.pageUp || key.pageDown || key.home || key.end)) {
|
|
3994
|
-
const
|
|
4064
|
+
const source = permissionDetailsOpenRef.current
|
|
4065
|
+
? permission.review
|
|
4066
|
+
: permission.summary.text;
|
|
4067
|
+
const count = permissionReviewRows(source, terminal.columns).length;
|
|
3995
4068
|
const maxOffset = Math.max(0, count - PERMISSION_REVIEW_PAGE_ROWS);
|
|
3996
4069
|
const current = Math.min(permissionReviewOffsetRef.current, maxOffset);
|
|
3997
4070
|
const next = key.home
|
|
@@ -4008,7 +4081,6 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4008
4081
|
setPermissionReviewOffset(next);
|
|
4009
4082
|
return;
|
|
4010
4083
|
}
|
|
4011
|
-
const ch = input.toLowerCase();
|
|
4012
4084
|
// Refusing is never deferred or gated — it is the direction a
|
|
4013
4085
|
// mistake is recoverable in, so it answers on the first press.
|
|
4014
4086
|
if (key.ctrl && input === 'c') {
|
|
@@ -4034,6 +4106,80 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4034
4106
|
resolvePermission({ kind: 'approve-all' });
|
|
4035
4107
|
return;
|
|
4036
4108
|
}
|
|
4109
|
+
// Child observation owns navigation, but never outranks consent: the
|
|
4110
|
+
// permission branch above preempts it if a child or parent call asks.
|
|
4111
|
+
const agentView = agentSurfaceRef.current;
|
|
4112
|
+
if (agentView) {
|
|
4113
|
+
// The Return that opened `/agent` cannot also drill into the
|
|
4114
|
+
// highlighted child before the list has appeared on screen.
|
|
4115
|
+
if (agentSurfaceCommittedRef.current !== agentView)
|
|
4116
|
+
return;
|
|
4117
|
+
const agents = subagentsRef.current;
|
|
4118
|
+
const index = Math.max(0, agents.findIndex((agent) => agent.viewId === agentView.selectedId));
|
|
4119
|
+
const selected = agents[index];
|
|
4120
|
+
if (!selected) {
|
|
4121
|
+
setAgentSurface(null);
|
|
4122
|
+
return;
|
|
4123
|
+
}
|
|
4124
|
+
if (agentView.kind === 'picker') {
|
|
4125
|
+
if (key.escape || (key.ctrl && input === 'c') || input.toLowerCase() === 'q') {
|
|
4126
|
+
setAgentSurface(null);
|
|
4127
|
+
return;
|
|
4128
|
+
}
|
|
4129
|
+
if (key.return) {
|
|
4130
|
+
setAgentSurface({
|
|
4131
|
+
kind: 'transcript',
|
|
4132
|
+
selectedId: selected.viewId,
|
|
4133
|
+
tailOffset: 0,
|
|
4134
|
+
});
|
|
4135
|
+
return;
|
|
4136
|
+
}
|
|
4137
|
+
if (key.home || key.end || key.pageUp || key.pageDown || key.upArrow || key.downArrow) {
|
|
4138
|
+
const movement = key.home
|
|
4139
|
+
? 'first'
|
|
4140
|
+
: key.end
|
|
4141
|
+
? 'last'
|
|
4142
|
+
: key.pageUp
|
|
4143
|
+
? 'previous-page'
|
|
4144
|
+
: key.pageDown
|
|
4145
|
+
? 'next-page'
|
|
4146
|
+
: key.upArrow
|
|
4147
|
+
? 'previous'
|
|
4148
|
+
: 'next';
|
|
4149
|
+
const next = moveSelection(index, agents.length, movement, agentPickerPageSize(terminal.rows));
|
|
4150
|
+
const target = agents[next];
|
|
4151
|
+
if (target)
|
|
4152
|
+
setAgentSurface({ kind: 'picker', selectedId: target.viewId });
|
|
4153
|
+
}
|
|
4154
|
+
return;
|
|
4155
|
+
}
|
|
4156
|
+
if (input.toLowerCase() === 'q' || (key.ctrl && input === 'c')) {
|
|
4157
|
+
setAgentSurface(null);
|
|
4158
|
+
return;
|
|
4159
|
+
}
|
|
4160
|
+
if (key.escape) {
|
|
4161
|
+
setAgentSurface({ kind: 'picker', selectedId: selected.viewId });
|
|
4162
|
+
return;
|
|
4163
|
+
}
|
|
4164
|
+
if (key.home || key.end || key.pageUp || key.pageDown || key.upArrow || key.downArrow) {
|
|
4165
|
+
const page = agentTranscriptPageSize(terminal.rows);
|
|
4166
|
+
const max = maxAgentTranscriptTailOffset(selected, terminal.rows, terminal.columns);
|
|
4167
|
+
const offset = Math.min(agentView.tailOffset, max);
|
|
4168
|
+
const next = key.home
|
|
4169
|
+
? max
|
|
4170
|
+
: key.end
|
|
4171
|
+
? 0
|
|
4172
|
+
: key.pageUp
|
|
4173
|
+
? Math.min(max, offset + page)
|
|
4174
|
+
: key.pageDown
|
|
4175
|
+
? Math.max(0, offset - page)
|
|
4176
|
+
: key.upArrow
|
|
4177
|
+
? Math.min(max, offset + 1)
|
|
4178
|
+
: Math.max(0, offset - 1);
|
|
4179
|
+
setAgentSurface({ ...agentView, tailOffset: next });
|
|
4180
|
+
}
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4037
4183
|
// A host-owned text prompt has its own input hook. This synchronous ref
|
|
4038
4184
|
// claims the interval before React paints it too, so App cannot read a
|
|
4039
4185
|
// key repeat as an interrupt, exit ladder, or finite-menu choice.
|
|
@@ -4058,13 +4204,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4058
4204
|
}
|
|
4059
4205
|
if (key.home || key.end || key.pageUp || key.pageDown) {
|
|
4060
4206
|
const pageSize = picker.kind === 'command' ? picker.windowSize : undefined;
|
|
4061
|
-
setSelectedChoice((index) => moveSelection(index, options.length, key.home
|
|
4062
|
-
? 'first'
|
|
4063
|
-
: key.end
|
|
4064
|
-
? 'last'
|
|
4065
|
-
: key.pageUp
|
|
4066
|
-
? 'previous-page'
|
|
4067
|
-
: 'next-page', pageSize));
|
|
4207
|
+
setSelectedChoice((index) => moveSelection(index, options.length, key.home ? 'first' : key.end ? 'last' : key.pageUp ? 'previous-page' : 'next-page', pageSize));
|
|
4068
4208
|
return;
|
|
4069
4209
|
}
|
|
4070
4210
|
if (key.upArrow) {
|
|
@@ -4090,19 +4230,17 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4090
4230
|
// stays above it in this handler so an approval request that arrives
|
|
4091
4231
|
// while the picker is open cannot have its keys stolen by the chooser.
|
|
4092
4232
|
if (copyPickerRef.current) {
|
|
4233
|
+
// The Return that submitted `/copy` is an ownership fence, not a
|
|
4234
|
+
// selection made against a picker the operator has not seen yet.
|
|
4235
|
+
if (copyPickerCommittedRef.current !== copyPickerRef.current)
|
|
4236
|
+
return;
|
|
4093
4237
|
const targets = copyPickerRef.current.targets;
|
|
4094
4238
|
if (key.escape || (key.ctrl && input === 'c')) {
|
|
4095
4239
|
setCopyPicker(null);
|
|
4096
4240
|
return;
|
|
4097
4241
|
}
|
|
4098
4242
|
if (key.home || key.end || key.pageUp || key.pageDown) {
|
|
4099
|
-
setSelectedCopy((index) => moveSelection(index, targets.length, key.home
|
|
4100
|
-
? 'first'
|
|
4101
|
-
: key.end
|
|
4102
|
-
? 'last'
|
|
4103
|
-
: key.pageUp
|
|
4104
|
-
? 'previous-page'
|
|
4105
|
-
: 'next-page'));
|
|
4243
|
+
setSelectedCopy((index) => moveSelection(index, targets.length, key.home ? 'first' : key.end ? 'last' : key.pageUp ? 'previous-page' : 'next-page'));
|
|
4106
4244
|
return;
|
|
4107
4245
|
}
|
|
4108
4246
|
if (key.upArrow) {
|
|
@@ -4213,21 +4351,71 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4213
4351
|
// Background is left natural — we inherit the terminal's own background
|
|
4214
4352
|
// and only theme the foreground. Forcing
|
|
4215
4353
|
// a filled bg left mismatched patches around bordered areas, so we don't.
|
|
4216
|
-
const
|
|
4217
|
-
|
|
4354
|
+
const liveSubagentCount = subagents.filter((agent) => agent.status === 'starting' || agent.status === 'working').length;
|
|
4355
|
+
const selectedSubagent = agentSurface
|
|
4356
|
+
? subagents.find((agent) => agent.viewId === agentSurface.selectedId)
|
|
4357
|
+
: undefined;
|
|
4358
|
+
const lifecycleOwnsViewport = phase === 'trust' ||
|
|
4359
|
+
phase === 'resume' ||
|
|
4360
|
+
phase === 'edit' ||
|
|
4361
|
+
phase === 'picker' ||
|
|
4362
|
+
agentSurface !== null;
|
|
4363
|
+
const statusGoal = phase === 'ready' &&
|
|
4364
|
+
state === 'idle' &&
|
|
4365
|
+
conversationMutation === null &&
|
|
4366
|
+
!compacting &&
|
|
4367
|
+
permission === null &&
|
|
4368
|
+
textPrompt === null &&
|
|
4369
|
+
choicePicker === null &&
|
|
4370
|
+
copyPicker === null &&
|
|
4371
|
+
agentSurface === null
|
|
4372
|
+
? goalStatusLabel(goalStatus)
|
|
4373
|
+
: null;
|
|
4374
|
+
const statusHint = conversationMutation === 'fork'
|
|
4375
|
+
? 'forking conversation — input is paused'
|
|
4376
|
+
: conversationMutation === 'edit'
|
|
4377
|
+
? 'branching before prompt — input is paused'
|
|
4378
|
+
: conversationMutation === 'archive'
|
|
4379
|
+
? 'archiving conversation — input is paused'
|
|
4380
|
+
: conversationMutation === 'new'
|
|
4381
|
+
? 'starting a fresh conversation — input is paused'
|
|
4382
|
+
: compacting
|
|
4383
|
+
? 'compacting conversation — input is paused'
|
|
4384
|
+
: permission
|
|
4385
|
+
? hintForPhase(phase, state, session?.hasProvider === true)
|
|
4386
|
+
: agentSurface?.kind === 'picker'
|
|
4387
|
+
? 'agent list — enter inspect · esc return'
|
|
4388
|
+
: agentSurface?.kind === 'transcript'
|
|
4389
|
+
? 'observing agent — esc agents · q parent'
|
|
4390
|
+
: textPrompt
|
|
4391
|
+
? 'name editor open — enter save · esc cancel'
|
|
4392
|
+
: choicePicker
|
|
4393
|
+
? 'choice open — ↑↓ / 1–9 select · enter apply · esc cancel'
|
|
4394
|
+
: copyPicker
|
|
4395
|
+
? 'copy target open — ↑↓ / 1–9 select · esc cancel'
|
|
4396
|
+
: goalStatus && phase === 'ready' && state === 'idle'
|
|
4397
|
+
? undefined
|
|
4398
|
+
: hintForPhase(phase, state, session?.hasProvider === true, composerHasDraft);
|
|
4399
|
+
return (_jsx(Box, { flexDirection: "column", display: externalEditorRequest ? 'none' : 'flex', children: _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [transcriptOwned || !lifecycleOwnsViewport ? (_jsx(TranscriptFrame, { children: _jsx(Transcript, { messages: finalized, pending: messages.find((m) => m.pending) ?? null, state: state, settled: renderedSettled, resetKey: resetKey, raw: rawOutput, hyperlinks: hyperlinks, showLive: !lifecycleOwnsViewport, header: transcriptOwned ? (_jsx(Banner, { version: ctx.version, session: session, permissionMode: permissionMode, cwd: ctx.cwd })) : undefined }) })) : null, 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: [agentSurface === null ? (_jsx(LiveActivity, { activeTools: activeTools, working: state === 'thinking' || state === 'tool', agentCount: liveSubagentCount, interruptible: abortRef.current !== null, animate: stdout.isTTY === true })) : null, permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls, review: permission.review, summary: permission.summary, detailsOpen: permissionDetailsOpen, reviewOffset: permissionReviewOffset, columns: terminal.columns })) : null, permission === null && agentSurface?.kind === 'picker' ? (_jsx(AgentPicker, { agents: subagents, selectedId: agentSurface.selectedId, terminalRows: terminal.rows })) : permission === null && agentSurface?.kind === 'transcript' && selectedSubagent ? (_jsx(AgentTranscript, { agent: selectedSubagent, tailOffset: agentSurface.tailOffset, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : null, textPrompt ? (_jsx(TextPrompt, { title: textPrompt.title, placeholder: textPrompt.placeholder, initialValue: textPrompt.initialValue, emptyNotice: textPrompt.emptyNotice, hidden: permission !== null || agentSurface !== null, onSubmit: submitTextPrompt, onCancel: cancelTextPrompt }, textPrompt.token)) : permission === null && agentSurface === null && choicePicker ? (_jsx(ChoicePicker, { title: choicePicker.title, notice: choicePicker.notice, options: choicePicker.options, selected: selectedChoice, windowSize: choicePicker.kind === 'command' ? choicePicker.windowSize : undefined })) : permission === null && agentSurface === null && copyPicker ? (_jsx(CopyPicker, { targets: copyPicker.targets, selected: selectedCopy })) : null, _jsxs(ComposerFrame, { focus: state === 'idle' &&
|
|
4218
4400
|
phase === 'ready' &&
|
|
4219
4401
|
conversationMutation === null &&
|
|
4220
4402
|
textPrompt === null &&
|
|
4221
4403
|
choicePicker === null &&
|
|
4222
|
-
copyPicker === null
|
|
4404
|
+
copyPicker === null &&
|
|
4405
|
+
agentSurface === null, hidden: permission !== null ||
|
|
4406
|
+
choicePicker !== null ||
|
|
4407
|
+
copyPicker !== null ||
|
|
4408
|
+
agentSurface !== null, children: [pendingSteers.length > 0 &&
|
|
4223
4409
|
permission === null &&
|
|
4224
4410
|
textPrompt === null &&
|
|
4225
4411
|
choicePicker === null &&
|
|
4226
|
-
copyPicker === null
|
|
4412
|
+
copyPicker === null &&
|
|
4413
|
+
agentSurface === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.accent.user, children: ["\u21B3 ", pendingSteers.length, " message", pendingSteers.length > 1 ? 's' : '', " steering the active turn \u2014 waiting for its next response boundary"] }) })) : null, queued.length > 0 &&
|
|
4227
4414
|
permission === null &&
|
|
4228
4415
|
textPrompt === null &&
|
|
4229
4416
|
choicePicker === null &&
|
|
4230
|
-
copyPicker === null
|
|
4417
|
+
copyPicker === null &&
|
|
4418
|
+
agentSurface === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: [queuePause ? '⏸' : '⏎', " ", queued.length, " message", queued.length > 1 ? 's' : '', " queued \u2014", ' ', queuePause
|
|
4231
4419
|
? queuePause.outcome === 'paused'
|
|
4232
4420
|
? 'held after a resumable run paused; wait for recovery, change model, or send a message to release it'
|
|
4233
4421
|
: `paused after a ${queuePause.outcome} turn; send a message or change model to continue`
|
|
@@ -4235,42 +4423,15 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4235
4423
|
state === 'awaiting-permission' ||
|
|
4236
4424
|
compacting ||
|
|
4237
4425
|
conversationMutation !== null ||
|
|
4238
|
-
externalEditorRequest !== null
|
|
4426
|
+
externalEditorRequest !== null ||
|
|
4427
|
+
agentSurface !== null, hidden: permission !== null ||
|
|
4239
4428
|
textPrompt !== null ||
|
|
4240
4429
|
choicePicker !== null ||
|
|
4241
|
-
copyPicker !== null
|
|
4430
|
+
copyPicker !== null ||
|
|
4431
|
+
agentSurface !== null,
|
|
4242
4432
|
// A turn is running, so Esc is the interrupt and not
|
|
4243
4433
|
// the composer's clear.
|
|
4244
|
-
escapeInterrupts: !compacting && (state === 'thinking' || state === 'tool'), onSubmit: handleSubmit, onNotice: (text) => pushMessage('system', text), onStepReasoningEffort: stepReasoningEffort, onExternalEdit: requestExternalEditor, onEditPrevious: openPromptEditor, draftToRestore: composerDraft, onDraftRestored: (token) => setComposerDraft((draft) => (draft?.token === token ? null : draft)), userCommands: userCommands, mentionCandidates: mentionCandidates, history: history })] })] })), _jsx(Box, { paddingTop: 1, children: _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, effort: reasoningEffort ?? 'default', goal:
|
|
4245
|
-
state === 'idle' &&
|
|
4246
|
-
conversationMutation === null &&
|
|
4247
|
-
!compacting &&
|
|
4248
|
-
permission === null &&
|
|
4249
|
-
textPrompt === null &&
|
|
4250
|
-
choicePicker === null &&
|
|
4251
|
-
copyPicker === null
|
|
4252
|
-
? goalStatusLabel(goalStatus)
|
|
4253
|
-
: null, state: state, hint: conversationMutation === 'fork'
|
|
4254
|
-
? 'forking conversation — input is paused'
|
|
4255
|
-
: conversationMutation === 'edit'
|
|
4256
|
-
? 'branching before prompt — input is paused'
|
|
4257
|
-
: conversationMutation === 'archive'
|
|
4258
|
-
? 'archiving conversation — input is paused'
|
|
4259
|
-
: conversationMutation === 'new'
|
|
4260
|
-
? 'starting a fresh conversation — input is paused'
|
|
4261
|
-
: compacting
|
|
4262
|
-
? 'compacting conversation — input is paused'
|
|
4263
|
-
: permission
|
|
4264
|
-
? hintForPhase(phase, state, session?.hasProvider === true)
|
|
4265
|
-
: textPrompt
|
|
4266
|
-
? 'name editor open — enter save · esc cancel'
|
|
4267
|
-
: choicePicker
|
|
4268
|
-
? 'choice open — ↑↓ / 1–9 select · enter apply · esc cancel'
|
|
4269
|
-
: copyPicker
|
|
4270
|
-
? 'copy target open — ↑↓ / 1–9 select · esc cancel'
|
|
4271
|
-
: goalStatus && phase === 'ready' && state === 'idle'
|
|
4272
|
-
? undefined
|
|
4273
|
-
: hintForPhase(phase, state, session?.hasProvider === true), usage: usage, context: context }) })] }) }));
|
|
4434
|
+
escapeInterrupts: !compacting && (state === 'thinking' || state === 'tool'), onSubmit: handleSubmit, onNotice: (text) => pushMessage('system', text), onStepReasoningEffort: stepReasoningEffort, onExternalEdit: requestExternalEditor, onEditPrevious: openPromptEditor, draftToRestore: composerDraft, onDraftRestored: (token) => setComposerDraft((draft) => (draft?.token === token ? null : draft)), onDraftPresenceChange: setComposerHasDraft, userCommands: userCommands, mentionCandidates: mentionCandidates, history: history })] })] })), _jsx(Box, { paddingTop: 1, children: _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, effort: reasoningEffort ?? 'default', goal: statusGoal, state: state, hint: statusHint }) })] }) }));
|
|
4274
4435
|
}
|
|
4275
4436
|
function Banner({ version, session, permissionMode, cwd, }) {
|
|
4276
4437
|
const { columns } = useWindowSize();
|
|
@@ -4302,7 +4463,9 @@ function formatToolCall(toolName, summary, standalone = false) {
|
|
|
4302
4463
|
}
|
|
4303
4464
|
function hintForPhase(phase, state,
|
|
4304
4465
|
/** Whether there is a working session behind the picker to go back to. */
|
|
4305
|
-
canReturn = false
|
|
4466
|
+
canReturn = false,
|
|
4467
|
+
/** Whether the active-turn composer contains text, a paste, or an attachment. */
|
|
4468
|
+
hasDraft = false) {
|
|
4306
4469
|
// Enter is absent because Enter grants nothing here, deliberately — see the
|
|
4307
4470
|
// trust branch in the key handler above.
|
|
4308
4471
|
if (phase === 'trust')
|
|
@@ -4328,7 +4491,7 @@ canReturn = false) {
|
|
|
4328
4491
|
if (state === 'awaiting-permission')
|
|
4329
4492
|
return 'y approve · n / esc reject · a approve all';
|
|
4330
4493
|
if (state !== 'idle')
|
|
4331
|
-
return 'enter steer · tab queue · esc interrupt';
|
|
4494
|
+
return hasDraft ? 'enter steer · tab queue · esc interrupt' : '';
|
|
4332
4495
|
// The composer already points to /help. Keeping the complete key legend in
|
|
4333
4496
|
// every idle frame made ordinary conversation read like a permanent setup
|
|
4334
4497
|
// screen; only state-specific actions belong in the footer.
|