@namzu/cli 17.0.1 → 18.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/README.md +22 -12
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/acp.d.ts.map +1 -1
- package/dist/commands/acp.js +1 -0
- package/dist/commands/acp.js.map +1 -1
- package/dist/commands/run-stream.d.ts.map +1 -1
- package/dist/commands/run-stream.js +1 -0
- package/dist/commands/run-stream.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +1 -0
- package/dist/commands/run.js.map +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +15 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +17 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/context/doctrine.d.ts +44 -0
- package/dist/context/doctrine.d.ts.map +1 -0
- package/dist/context/doctrine.js +81 -0
- package/dist/context/doctrine.js.map +1 -0
- package/dist/context/turn-snapshot.d.ts +59 -0
- package/dist/context/turn-snapshot.d.ts.map +1 -0
- package/dist/context/turn-snapshot.js +122 -0
- package/dist/context/turn-snapshot.js.map +1 -0
- package/dist/integrations/subagents/activity.d.ts +8 -0
- package/dist/integrations/subagents/activity.d.ts.map +1 -1
- package/dist/integrations/subagents/activity.js +38 -2
- package/dist/integrations/subagents/activity.js.map +1 -1
- package/dist/integrations/subagents/policy.d.ts +6 -0
- package/dist/integrations/subagents/policy.d.ts.map +1 -0
- package/dist/integrations/subagents/policy.js +6 -0
- package/dist/integrations/subagents/policy.js.map +1 -0
- package/dist/integrations/subagents/runtime.d.ts +13 -0
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +77 -13
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/permissions/mode.d.ts +25 -0
- package/dist/permissions/mode.d.ts.map +1 -1
- package/dist/permissions/mode.js +11 -1
- package/dist/permissions/mode.js.map +1 -1
- package/dist/tui/AgentExplorer.d.ts +21 -0
- package/dist/tui/AgentExplorer.d.ts.map +1 -1
- package/dist/tui/AgentExplorer.js +52 -1
- package/dist/tui/AgentExplorer.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +298 -26
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Composer.d.ts +8 -1
- package/dist/tui/Composer.d.ts.map +1 -1
- package/dist/tui/Composer.js +23 -3
- package/dist/tui/Composer.js.map +1 -1
- package/dist/tui/PermissionOverlay.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.js +16 -1
- package/dist/tui/PermissionOverlay.js.map +1 -1
- package/dist/tui/TaskList.d.ts +30 -0
- package/dist/tui/TaskList.d.ts.map +1 -0
- package/dist/tui/TaskList.js +58 -0
- package/dist/tui/TaskList.js.map +1 -0
- package/dist/tui/agent.d.ts +49 -4
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +171 -20
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/permission-review.d.ts.map +1 -1
- package/dist/tui/permission-review.js +151 -1
- package/dist/tui/permission-review.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +2 -0
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +6 -3
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/stream-blocks.d.ts +24 -0
- package/dist/tui/stream-blocks.d.ts.map +1 -1
- package/dist/tui/stream-blocks.js +74 -0
- package/dist/tui/stream-blocks.js.map +1 -1
- package/dist/tui/types.d.ts +3 -1
- package/dist/tui/types.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/tui/App.js
CHANGED
|
@@ -28,12 +28,13 @@ 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 { AgentCockpit, AgentTranscript,
|
|
31
|
+
import { AgentCockpit, AgentTaskPanel, AgentTranscript, activeSubagentCohorts, agentCockpitIsWide, agentPhasePageSize, agentPhases, agentPickerPageSize, agentTranscriptPageSize, maxAgentTranscriptTailOffset, } from './AgentExplorer.js';
|
|
32
32
|
import { ChoicePicker } from './ChoicePicker.js';
|
|
33
33
|
import { Composer, suggestionWindowSize, } from './Composer.js';
|
|
34
34
|
import { CopyPicker } from './CopyPicker.js';
|
|
35
35
|
import { EditPromptPicker } from './EditPromptPicker.js';
|
|
36
36
|
import { LiveActivity, formatElapsed } from './LiveActivity.js';
|
|
37
|
+
import { TaskList } from './TaskList.js';
|
|
37
38
|
import { PermissionOverlay } from './PermissionOverlay.js';
|
|
38
39
|
import { Picker } from './Picker.js';
|
|
39
40
|
import { ResumePicker } from './ResumePicker.js';
|
|
@@ -57,12 +58,36 @@ import { PERMISSION_REVIEW_PAGE_ROWS, buildPermissionReview, buildPermissionSumm
|
|
|
57
58
|
import { describeRunInterruption } from './run-interruption.js';
|
|
58
59
|
import { moveSelection } from './selection-window.js';
|
|
59
60
|
import { baseBranchReviewPrompt, commitReviewPrompt, hostCommandNames, kernelCommandDescriptors, mergeHostCommands, renderOutcome, reviewPrompt, runSlash, } from './slashCommands.js';
|
|
60
|
-
import { splitCompleteBlocks } from './stream-blocks.js';
|
|
61
|
+
import { splitCompleteBlocks, splitSafeCut } from './stream-blocks.js';
|
|
61
62
|
import { terminalSupportsHyperlinks } from './terminal-hyperlinks.js';
|
|
62
63
|
import { theme } from './theme.js';
|
|
63
64
|
import { useSelectionIndex } from './use-selection-index.js';
|
|
64
65
|
import { renderWorkspaceDiff, workspaceDiff } from './workspace-diff.js';
|
|
65
66
|
import { listReviewBranches, listReviewCommits, reviewMergeBase, } from './workspace-review.js';
|
|
67
|
+
/** The value a question row carries: an option id, or the free-text escape. */
|
|
68
|
+
const FREE_TEXT_ANSWER = '__free_text__';
|
|
69
|
+
/**
|
|
70
|
+
* The streaming assistant bubble, carried across events within one turn.
|
|
71
|
+
*
|
|
72
|
+
* `text` is everything the model has said and is what gets persisted;
|
|
73
|
+
* `pending` is the part not yet on screen, held until it forms a whole block.
|
|
74
|
+
* The two are separate because they answer different questions — what was
|
|
75
|
+
* said, and what has been shown — and conflating them is how a reply gets
|
|
76
|
+
* saved with a paragraph the operator never saw, or shown twice.
|
|
77
|
+
*/
|
|
78
|
+
/**
|
|
79
|
+
* How long held reply text may wait before a sentence of it is shown.
|
|
80
|
+
*
|
|
81
|
+
* Blocks are released as they complete (`splitCompleteBlocks`), which keeps a
|
|
82
|
+
* fast reply landing a paragraph at a time. A slow one — a long paragraph
|
|
83
|
+
* arriving over twenty seconds — completes no block until its last character
|
|
84
|
+
* and was invisible for its whole length. Past this interval the pending
|
|
85
|
+
* text is released to its last safe cut instead (`splitSafeCut`): a sentence
|
|
86
|
+
* or a line, never a half-word, never inside a fence. Long enough that a
|
|
87
|
+
* fast stream never reaches it; short enough that a slow one reads as
|
|
88
|
+
* writing rather than as silence.
|
|
89
|
+
*/
|
|
90
|
+
const STREAM_RELEASE_MS = 250;
|
|
66
91
|
/** Last non-empty assistant text in a durable conversation, newest first. */
|
|
67
92
|
function latestAssistantOutput(messages) {
|
|
68
93
|
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
@@ -292,6 +317,22 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
292
317
|
*/
|
|
293
318
|
const [pickerNotice, setPickerNotice] = useState(null);
|
|
294
319
|
const [permission, setPermission] = useState(null);
|
|
320
|
+
/**
|
|
321
|
+
* The question currently on screen and the promise the tool is waiting
|
|
322
|
+
* on. Resolved exactly once: by a chosen row, by free text, by Esc (skip)
|
|
323
|
+
* or by Ctrl+C (abort). A second resolution is a no-op, not a second
|
|
324
|
+
* answer.
|
|
325
|
+
*/
|
|
326
|
+
const questionRef = useRef(null);
|
|
327
|
+
const resolveQuestion = useCallback((answer) => {
|
|
328
|
+
const pending = questionRef.current;
|
|
329
|
+
if (!pending?.resolve)
|
|
330
|
+
return;
|
|
331
|
+
const resolve = pending.resolve;
|
|
332
|
+
pending.resolve = null;
|
|
333
|
+
questionRef.current = null;
|
|
334
|
+
resolve(answer);
|
|
335
|
+
}, []);
|
|
295
336
|
const [permissionReviewOffset, setPermissionReviewOffsetState] = useState(0);
|
|
296
337
|
const permissionReviewOffsetRef = useRef(0);
|
|
297
338
|
const [permissionDetailsOpen, setPermissionDetailsOpenState] = useState(false);
|
|
@@ -329,6 +370,15 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
329
370
|
// Tools currently executing — rendered live (spinner + elapsed) below the
|
|
330
371
|
// transcript, then committed as static lines on completion.
|
|
331
372
|
const [activeTools, setActiveTools] = useState([]);
|
|
373
|
+
/**
|
|
374
|
+
* The model's plan for the current request, in creation order, updated in
|
|
375
|
+
* place on every status change. Cleared when the next request begins —
|
|
376
|
+
* a plan is scoped to the request it was made for, and one that outlived
|
|
377
|
+
* it would read as work still pending on a turn that already ended.
|
|
378
|
+
*/
|
|
379
|
+
const [tasks, setTasks] = useState([]);
|
|
380
|
+
/** Ids already seen this request, so an opening row is written once. */
|
|
381
|
+
const knownTaskIdsRef = useRef(new Set());
|
|
332
382
|
// Bumped to reset the <Static> transcript log (on /clear, /clear-screen and /resume).
|
|
333
383
|
const [resetKey, setResetKey] = useState(0);
|
|
334
384
|
/**
|
|
@@ -462,9 +512,16 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
462
512
|
subagentsRef.current = next;
|
|
463
513
|
setSubagentsState(next);
|
|
464
514
|
const surface = agentSurfaceRef.current;
|
|
465
|
-
if (!surface
|
|
515
|
+
if (!surface)
|
|
516
|
+
return;
|
|
517
|
+
const activeCohorts = activeSubagentCohorts(next);
|
|
518
|
+
if (activeCohorts.length === 0) {
|
|
519
|
+
setAgentSurface(null);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (activeCohorts.some((agent) => agent.viewId === surface.selectedId))
|
|
466
523
|
return;
|
|
467
|
-
const phases = agentPhases(
|
|
524
|
+
const phases = agentPhases(activeCohorts);
|
|
468
525
|
const fallbackPhase = phases.find((phase) => phase.id === surface.selectedPhaseId) ?? phases[0];
|
|
469
526
|
const fallbackAgent = fallbackPhase?.agents[0];
|
|
470
527
|
if (!fallbackPhase || !fallbackAgent) {
|
|
@@ -479,7 +536,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
479
536
|
});
|
|
480
537
|
}, [setAgentSurface]);
|
|
481
538
|
const openAgentCockpit = useCallback(() => {
|
|
482
|
-
const agents = subagentsRef.current;
|
|
539
|
+
const agents = activeSubagentCohorts(subagentsRef.current);
|
|
483
540
|
const firstPhase = agentPhases(agents)[0];
|
|
484
541
|
const firstAgent = firstPhase?.agents[0];
|
|
485
542
|
if (!firstPhase || !firstAgent)
|
|
@@ -1020,6 +1077,20 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1020
1077
|
}
|
|
1021
1078
|
return { sessions, sessionId };
|
|
1022
1079
|
}, [hasUnsettledTurn, pushMessage, state]);
|
|
1080
|
+
/**
|
|
1081
|
+
* Shift+Tab: `prompt` → `accept-edits` → `plan` → `prompt`. Three stops
|
|
1082
|
+
* only — `auto` and `strict` are deliberate choices made by name in
|
|
1083
|
+
* `/permissions`, not stops on a key an operator presses on reflex. From
|
|
1084
|
+
* either of those the key returns to `prompt`, which is the direction a
|
|
1085
|
+
* reflex should fall. The change goes through the same gate `/permissions`
|
|
1086
|
+
* uses, so it is refused while a turn is active, and the refusal is
|
|
1087
|
+
* explained on screen.
|
|
1088
|
+
*/
|
|
1089
|
+
const cyclePermissionMode = useCallback(() => {
|
|
1090
|
+
const current = permissionModeRef.current;
|
|
1091
|
+
const next = current === 'prompt' ? 'accept-edits' : current === 'accept-edits' ? 'plan' : 'prompt';
|
|
1092
|
+
applyPermissionMode(next);
|
|
1093
|
+
}, [applyPermissionMode]);
|
|
1023
1094
|
const runConversationExport = useCallback((destination) => {
|
|
1024
1095
|
const source = stableExportSource();
|
|
1025
1096
|
if (!source)
|
|
@@ -1202,6 +1273,39 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1202
1273
|
})();
|
|
1203
1274
|
return;
|
|
1204
1275
|
}
|
|
1276
|
+
if (picker.kind === 'user-question') {
|
|
1277
|
+
const pending = questionRef.current;
|
|
1278
|
+
setChoicePicker(null);
|
|
1279
|
+
if (!pending)
|
|
1280
|
+
return;
|
|
1281
|
+
if (value === FREE_TEXT_ANSWER) {
|
|
1282
|
+
// The scope on a text prompt is what its other kinds check
|
|
1283
|
+
// against a conversation switch; a question belongs to the turn
|
|
1284
|
+
// that asked it, and the turn holds the promise.
|
|
1285
|
+
const scope = scopeRef.current;
|
|
1286
|
+
if (!scope) {
|
|
1287
|
+
resolveQuestion({ kind: 'skip' });
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
textPromptTokenRef.current += 1;
|
|
1291
|
+
setTextPrompt({
|
|
1292
|
+
token: textPromptTokenRef.current,
|
|
1293
|
+
kind: 'user-question',
|
|
1294
|
+
title: pending.question.question,
|
|
1295
|
+
placeholder: 'Type your answer and press Enter · Esc skips',
|
|
1296
|
+
emptyNotice: 'An empty answer is a skip. Press Esc to skip.',
|
|
1297
|
+
initialValue: '',
|
|
1298
|
+
sessionId: scope.sessionId,
|
|
1299
|
+
});
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
// The tool's own result row carries the answer the model saw; this
|
|
1303
|
+
// row is the operator's, so a reader of the transcript sees the
|
|
1304
|
+
// question and the choice together where it happened.
|
|
1305
|
+
pushMessage('system', `Answered "${pending.question.question}": ${labelOfOption(pending.question, value)}`, false, '?');
|
|
1306
|
+
resolveQuestion({ kind: 'answer', selectedOptionIds: [value] });
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1205
1309
|
if (picker.kind === 'review-commit') {
|
|
1206
1310
|
advanceQueueContinuation();
|
|
1207
1311
|
enqueueQueued({
|
|
@@ -1341,12 +1445,35 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1341
1445
|
* if every exit releases it; this is the function every exit calls.
|
|
1342
1446
|
*/
|
|
1343
1447
|
const flushStream = useCallback((st) => {
|
|
1448
|
+
if (st.releaseTimer) {
|
|
1449
|
+
clearTimeout(st.releaseTimer);
|
|
1450
|
+
st.releaseTimer = undefined;
|
|
1451
|
+
}
|
|
1344
1452
|
if (!st.pending || st.pending.length === 0)
|
|
1345
1453
|
return;
|
|
1346
1454
|
const id = st.assistantId ?? pushMessage('assistant', '', true);
|
|
1347
1455
|
st.assistantId = id;
|
|
1348
1456
|
appendToMessage(id, st.pending);
|
|
1349
1457
|
st.pending = '';
|
|
1458
|
+
st.releasedAt = Date.now();
|
|
1459
|
+
}, [appendToMessage, pushMessage]);
|
|
1460
|
+
/**
|
|
1461
|
+
* Release held text to its last safe cut — a sentence or a line — when it
|
|
1462
|
+
* has waited longer than `STREAM_RELEASE_MS`. Called from the delta path
|
|
1463
|
+
* with the clock, and from the timer the delta path arms so a stream that
|
|
1464
|
+
* goes quiet mid-paragraph still shows what it has.
|
|
1465
|
+
*/
|
|
1466
|
+
const releaseStalled = useCallback((st) => {
|
|
1467
|
+
if (!st.pending || st.pending.length === 0)
|
|
1468
|
+
return;
|
|
1469
|
+
const { ready, rest } = splitSafeCut(st.pending);
|
|
1470
|
+
if (ready.length === 0)
|
|
1471
|
+
return;
|
|
1472
|
+
const id = st.assistantId ?? pushMessage('assistant', '', true);
|
|
1473
|
+
st.assistantId = id;
|
|
1474
|
+
st.pending = rest;
|
|
1475
|
+
appendToMessage(id, ready);
|
|
1476
|
+
st.releasedAt = Date.now();
|
|
1350
1477
|
}, [appendToMessage, pushMessage]);
|
|
1351
1478
|
const finalizeMessage = useCallback((id, finalContent) => {
|
|
1352
1479
|
setMessages((prev) => prev.map((m) => m.id === id ? { ...m, content: finalContent ?? m.content, pending: false } : m));
|
|
@@ -1445,7 +1572,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1445
1572
|
...(sessionsRef.current ? { sessionGoals: sessionsRef.current.goals } : {}),
|
|
1446
1573
|
...(activeCtx.mcpServers ? { mcpServers: activeCtx.mcpServers } : {}),
|
|
1447
1574
|
...(activeCtx.plugins ? { plugins: activeCtx.plugins } : {}),
|
|
1575
|
+
...(activeCtx.web ? { web: activeCtx.web } : {}),
|
|
1448
1576
|
...(activeCtx.sandbox ? { sandbox: activeCtx.sandbox } : {}),
|
|
1577
|
+
// Somebody is at this terminal, so the model may ask them one
|
|
1578
|
+
// question when a decision is genuinely theirs.
|
|
1579
|
+
askUser: true,
|
|
1449
1580
|
});
|
|
1450
1581
|
if (signal?.aborted) {
|
|
1451
1582
|
void s.close();
|
|
@@ -2236,6 +2367,19 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2236
2367
|
if (!prompt)
|
|
2237
2368
|
return;
|
|
2238
2369
|
setTextPrompt(null);
|
|
2370
|
+
if (prompt.kind === 'user-question') {
|
|
2371
|
+
const text = value.trim();
|
|
2372
|
+
const pending = questionRef.current;
|
|
2373
|
+
if (text.length === 0) {
|
|
2374
|
+
resolveQuestion({ kind: 'skip' });
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
if (pending) {
|
|
2378
|
+
pushMessage('system', `Answered "${pending.question.question}": ${text}`, false, '?');
|
|
2379
|
+
}
|
|
2380
|
+
resolveQuestion({ kind: 'answer', selectedOptionIds: [], freeText: text });
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2239
2383
|
const scope = scopeRef.current;
|
|
2240
2384
|
if (!scope || scope.sessionId !== prompt.sessionId) {
|
|
2241
2385
|
pushMessage('system', prompt.kind === 'conversation-title'
|
|
@@ -2268,8 +2412,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2268
2412
|
queueMicrotask(() => {
|
|
2269
2413
|
if (textPromptRef.current === prompt)
|
|
2270
2414
|
setTextPrompt(null);
|
|
2415
|
+
// A cancelled free-text answer is a skipped question, not a hung tool.
|
|
2416
|
+
if (prompt?.kind === 'user-question')
|
|
2417
|
+
resolveQuestion({ kind: 'skip' });
|
|
2271
2418
|
});
|
|
2272
|
-
}, [setTextPrompt]);
|
|
2419
|
+
}, [resolveQuestion, setTextPrompt]);
|
|
2273
2420
|
/**
|
|
2274
2421
|
* `/fork`: continue in a copy, leaving this conversation where it is.
|
|
2275
2422
|
*
|
|
@@ -2454,6 +2601,39 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2454
2601
|
sendTerminalNotification({ kind: 'approval-required' });
|
|
2455
2602
|
});
|
|
2456
2603
|
}, [sendTerminalNotification, setPermissionDetailsOpen, setPermissionReviewOffset]);
|
|
2604
|
+
/**
|
|
2605
|
+
* `ask_user_question`, on screen. The question becomes a finite chooser —
|
|
2606
|
+
* the model's options, then "Something else…" when it allowed free text —
|
|
2607
|
+
* and the tool's promise waits on the row the operator picks. One question
|
|
2608
|
+
* at a time: the tool is not concurrency-safe, so a second cannot arrive
|
|
2609
|
+
* while the first is up.
|
|
2610
|
+
*/
|
|
2611
|
+
const askQuestion = useCallback((question) => new Promise((resolve) => {
|
|
2612
|
+
questionRef.current = { question, resolve };
|
|
2613
|
+
const values = [
|
|
2614
|
+
...question.options.map((option) => option.id),
|
|
2615
|
+
...(question.allowFreeText ? [FREE_TEXT_ANSWER] : []),
|
|
2616
|
+
];
|
|
2617
|
+
setSelectedChoice(0);
|
|
2618
|
+
setChoicePicker({
|
|
2619
|
+
kind: 'user-question',
|
|
2620
|
+
title: question.question,
|
|
2621
|
+
notice: question.header
|
|
2622
|
+
? `${question.header} · Enter answers · Esc skips`
|
|
2623
|
+
: 'Enter answers · Esc skips',
|
|
2624
|
+
values,
|
|
2625
|
+
options: [
|
|
2626
|
+
...question.options.map((option) => ({
|
|
2627
|
+
label: option.label,
|
|
2628
|
+
description: option.description ?? '',
|
|
2629
|
+
})),
|
|
2630
|
+
...(question.allowFreeText
|
|
2631
|
+
? [{ label: 'Something else…', description: 'Answer in your own words' }]
|
|
2632
|
+
: []),
|
|
2633
|
+
],
|
|
2634
|
+
});
|
|
2635
|
+
sendTerminalNotification({ kind: 'approval-required' });
|
|
2636
|
+
}), [sendTerminalNotification, setChoicePicker, setSelectedChoice]);
|
|
2457
2637
|
// Render one agent event onto the transcript. Shared by the local turn loop
|
|
2458
2638
|
// and the daemon-attach poller, so both paths produce identical output.
|
|
2459
2639
|
// `st` carries the streaming-assistant bubble id + accumulated text across
|
|
@@ -2484,6 +2664,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2484
2664
|
sessionId: st.sessionId,
|
|
2485
2665
|
};
|
|
2486
2666
|
}
|
|
2667
|
+
// The release clock starts with the first character, not with the
|
|
2668
|
+
// turn: the wait before the first token is the model's, and
|
|
2669
|
+
// counting it would release the opening sentence on arrival.
|
|
2670
|
+
st.releasedAt ??= Date.now();
|
|
2487
2671
|
st.text += event.text;
|
|
2488
2672
|
// Held, not appended. Appending each delta is what produced text
|
|
2489
2673
|
// that types itself out — nothing animates it, but a few
|
|
@@ -2494,7 +2678,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2494
2678
|
if (ready.length > 0) {
|
|
2495
2679
|
st.pending = rest;
|
|
2496
2680
|
appendToMessage(ensureAssistant(), ready);
|
|
2681
|
+
st.releasedAt = Date.now();
|
|
2682
|
+
}
|
|
2683
|
+
// A block is the preferred unit; a sentence is the fallback for
|
|
2684
|
+
// a paragraph that is taking longer than an operator will wait
|
|
2685
|
+
// for it. The timer covers the stream going quiet between two
|
|
2686
|
+
// deltas, which the clock check alone cannot see.
|
|
2687
|
+
if (st.releaseTimer)
|
|
2688
|
+
clearTimeout(st.releaseTimer);
|
|
2689
|
+
if (Date.now() - (st.releasedAt ?? 0) >= STREAM_RELEASE_MS) {
|
|
2690
|
+
releaseStalled(st);
|
|
2497
2691
|
}
|
|
2692
|
+
st.releaseTimer = setTimeout(() => {
|
|
2693
|
+
st.releaseTimer = undefined;
|
|
2694
|
+
releaseStalled(st);
|
|
2695
|
+
}, STREAM_RELEASE_MS);
|
|
2498
2696
|
break;
|
|
2499
2697
|
}
|
|
2500
2698
|
case 'tool-start': {
|
|
@@ -2502,6 +2700,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2502
2700
|
setState('tool');
|
|
2503
2701
|
const tool = {
|
|
2504
2702
|
id: event.toolUseId,
|
|
2703
|
+
...(event.runId ? { runId: event.runId } : {}),
|
|
2505
2704
|
toolName: event.toolName,
|
|
2506
2705
|
label: formatToolCall(event.toolName, event.summary, event.standalone),
|
|
2507
2706
|
startedAt: Date.now(),
|
|
@@ -2513,7 +2712,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2513
2712
|
}
|
|
2514
2713
|
case 'tool-progress': {
|
|
2515
2714
|
const running = activeToolsRef.current;
|
|
2516
|
-
const index = running.findIndex((tool) => tool.id === event.toolUseId
|
|
2715
|
+
const index = running.findIndex((tool) => tool.id === event.toolUseId &&
|
|
2716
|
+
(event.runId === undefined || tool.runId === event.runId));
|
|
2517
2717
|
// A terminal event may already have removed this call. Never recreate
|
|
2518
2718
|
// a live row from late diagnostic state.
|
|
2519
2719
|
if (index < 0)
|
|
@@ -2539,12 +2739,25 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2539
2739
|
// active tool" — under parallel calls that mis-attributes a
|
|
2540
2740
|
// result to the wrong call. If no id matches, render the
|
|
2541
2741
|
// completion on its own (label from the end event itself).
|
|
2542
|
-
const i = running.findIndex((
|
|
2742
|
+
const i = running.findIndex((tool) => tool.id === event.toolUseId &&
|
|
2743
|
+
(event.runId === undefined || tool.runId === event.runId));
|
|
2543
2744
|
const done = i >= 0 ? running[i] : undefined;
|
|
2544
2745
|
if (i >= 0) {
|
|
2545
2746
|
activeToolsRef.current = [...running.slice(0, i), ...running.slice(i + 1)];
|
|
2546
2747
|
setActiveTools(activeToolsRef.current);
|
|
2547
2748
|
}
|
|
2749
|
+
const representedSuccessfulAgent = !event.isError &&
|
|
2750
|
+
event.toolName.toLowerCase() === 'agent' &&
|
|
2751
|
+
subagentsRef.current.some((agent) => agent.toolUseId === event.toolUseId &&
|
|
2752
|
+
(event.runId === undefined || agent.workflowId === event.runId));
|
|
2753
|
+
if (representedSuccessfulAgent) {
|
|
2754
|
+
// The automatic panel already owned this successful child from
|
|
2755
|
+
// start through settlement. Re-emitting its internal Agent call as
|
|
2756
|
+
// a transcript tool row after the panel closes is duplicate protocol,
|
|
2757
|
+
// not useful history. Failures remain durable transcript rows.
|
|
2758
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
2759
|
+
break;
|
|
2760
|
+
}
|
|
2548
2761
|
pushMessage('tool', done?.label ?? formatToolCall(event.toolName, event.summary), false, event.isError ? '✗' : '✓', done?.detail, event.isError ? theme.status.error : theme.status.ok, event.durationMs !== undefined
|
|
2549
2762
|
? formatElapsed(event.durationMs)
|
|
2550
2763
|
: done
|
|
@@ -2560,9 +2773,34 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2560
2773
|
case 'usage':
|
|
2561
2774
|
setUsage({ totalTokens: event.totalTokens, cost: event.cost });
|
|
2562
2775
|
break;
|
|
2563
|
-
case 'task':
|
|
2564
|
-
|
|
2776
|
+
case 'task': {
|
|
2777
|
+
// The live list gets every change; the transcript records the
|
|
2778
|
+
// opening and the close, as it did before the list existed.
|
|
2779
|
+
// Decided from a ref, not inside the state updater: React runs
|
|
2780
|
+
// updaters lazily, so a flag set there is still unset when the
|
|
2781
|
+
// transcript row below is chosen.
|
|
2782
|
+
const isNew = !knownTaskIdsRef.current.has(event.taskId);
|
|
2783
|
+
knownTaskIdsRef.current.add(event.taskId);
|
|
2784
|
+
const item = {
|
|
2785
|
+
id: event.taskId,
|
|
2786
|
+
subject: event.subject,
|
|
2787
|
+
status: event.status,
|
|
2788
|
+
};
|
|
2789
|
+
setTasks((prev) => {
|
|
2790
|
+
const index = prev.findIndex((task) => task.id === item.id);
|
|
2791
|
+
return index < 0 ? [...prev, item] : prev.map((task, i) => (i === index ? item : task));
|
|
2792
|
+
});
|
|
2793
|
+
if (event.status === 'completed') {
|
|
2794
|
+
pushMessage('tool', event.subject, false, '☑');
|
|
2795
|
+
}
|
|
2796
|
+
else if (event.status === 'failed') {
|
|
2797
|
+
pushMessage('tool', event.subject, false, '☒');
|
|
2798
|
+
}
|
|
2799
|
+
else if (isNew) {
|
|
2800
|
+
pushMessage('tool', event.subject, false, '☐');
|
|
2801
|
+
}
|
|
2565
2802
|
break;
|
|
2803
|
+
}
|
|
2566
2804
|
case 'context':
|
|
2567
2805
|
// Into the TRANSCRIPT, not a status line. A status indicator is
|
|
2568
2806
|
// present while nothing is happening and gone afterwards, so
|
|
@@ -2699,6 +2937,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2699
2937
|
else {
|
|
2700
2938
|
pushMessage('user', text, false, undefined, undefined, undefined, humanPromptMeta(attached.length, attachments));
|
|
2701
2939
|
}
|
|
2940
|
+
// A plan is scoped to the request it was made for. Cleared here, at
|
|
2941
|
+
// the start of the next one, rather than when the previous turn
|
|
2942
|
+
// ended: a finished list stays on screen until the operator moves on,
|
|
2943
|
+
// which is the moment it has told them everything it can.
|
|
2944
|
+
setTasks([]);
|
|
2945
|
+
knownTaskIdsRef.current = new Set();
|
|
2702
2946
|
// The model interleaves text → tool → text across iterations; `applyEvent`
|
|
2703
2947
|
// renders each one in order.
|
|
2704
2948
|
const st = {
|
|
@@ -2848,6 +3092,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2848
3092
|
...(goalRound ? { goalRound } : {}),
|
|
2849
3093
|
// The mode above decides whether this callback is consulted.
|
|
2850
3094
|
onPermission: askPermission,
|
|
3095
|
+
onQuestion: askQuestion,
|
|
2851
3096
|
inboundMessages: () => inbox.drain(),
|
|
2852
3097
|
extraSystem: composeSkillsPrompt(activeSkills) ?? undefined,
|
|
2853
3098
|
onConversationMessages: (messages) => {
|
|
@@ -3101,7 +3346,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3101
3346
|
}
|
|
3102
3347
|
case 'agent-cockpit': {
|
|
3103
3348
|
if (!openAgentCockpit()) {
|
|
3104
|
-
pushMessage('system', 'No delegated agents are
|
|
3349
|
+
pushMessage('system', 'No delegated agents are active. When an Agent tool starts one, its live activity appears below the composer and this view can inspect it.');
|
|
3105
3350
|
}
|
|
3106
3351
|
return;
|
|
3107
3352
|
}
|
|
@@ -3196,7 +3441,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3196
3441
|
pushMessage('system', 'Permission choices are unavailable until the active turn, prompt, compaction, queued work, or embedded-session approval latch can be safely settled.');
|
|
3197
3442
|
return;
|
|
3198
3443
|
}
|
|
3199
|
-
const values = ['prompt', 'auto', 'strict'];
|
|
3444
|
+
const values = ['prompt', 'accept-edits', 'auto', 'strict', 'plan'];
|
|
3200
3445
|
setSelectedChoice(Math.max(0, values.indexOf(permissionModeRef.current)));
|
|
3201
3446
|
setChoicePicker({
|
|
3202
3447
|
kind: 'permission-mode',
|
|
@@ -4241,8 +4486,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4241
4486
|
// Open the same retained cockpit as `/agent`; Enter then drills into a
|
|
4242
4487
|
// child's transcript. Keep the shortcut below consent so it can never
|
|
4243
4488
|
// hide an approval that owns the keyboard.
|
|
4244
|
-
if (key.ctrl && input === 't'
|
|
4245
|
-
|
|
4489
|
+
if (key.ctrl && input === 't') {
|
|
4490
|
+
if (agentSurfaceRef.current) {
|
|
4491
|
+
setAgentSurface(null);
|
|
4492
|
+
return;
|
|
4493
|
+
}
|
|
4494
|
+
if (openAgentCockpit())
|
|
4495
|
+
return;
|
|
4496
|
+
}
|
|
4246
4497
|
// Child observation owns navigation, but never outranks consent: the
|
|
4247
4498
|
// permission branch above preempts it if a child or parent call asks.
|
|
4248
4499
|
const agentView = agentSurfaceRef.current;
|
|
@@ -4251,7 +4502,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4251
4502
|
// highlighted child before the list has appeared on screen.
|
|
4252
4503
|
if (agentSurfaceCommittedRef.current !== agentView)
|
|
4253
4504
|
return;
|
|
4254
|
-
const agents = subagentsRef.current;
|
|
4505
|
+
const agents = activeSubagentCohorts(subagentsRef.current);
|
|
4255
4506
|
if (agentView.kind === 'cockpit') {
|
|
4256
4507
|
const phases = agentPhases(agents);
|
|
4257
4508
|
const phaseIndex = Math.max(0, phases.findIndex((phase) => phase.id === agentView.selectedPhaseId));
|
|
@@ -4382,6 +4633,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4382
4633
|
if (key.escape || (key.ctrl && input === 'c')) {
|
|
4383
4634
|
reviewChoiceInFlightRef.current = null;
|
|
4384
4635
|
setChoicePicker(null);
|
|
4636
|
+
// Esc leaves the question unanswered and the turn running; the
|
|
4637
|
+
// tool tells the model so. Ctrl+C is the operator saying stop.
|
|
4638
|
+
if (picker.kind === 'user-question') {
|
|
4639
|
+
resolveQuestion({ kind: key.escape ? 'skip' : 'abort' });
|
|
4640
|
+
}
|
|
4385
4641
|
return;
|
|
4386
4642
|
}
|
|
4387
4643
|
if (key.home || key.end || key.pageUp || key.pageDown) {
|
|
@@ -4533,9 +4789,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4533
4789
|
// Background is left natural — we inherit the terminal's own background
|
|
4534
4790
|
// and only theme the foreground. Forcing
|
|
4535
4791
|
// a filled bg left mismatched patches around bordered areas, so we don't.
|
|
4536
|
-
const
|
|
4792
|
+
const liveSubagents = activeSubagentCohorts(subagents);
|
|
4793
|
+
const representedSubagentTools = new Set(subagents
|
|
4794
|
+
.filter((agent) => agent.toolUseId !== undefined)
|
|
4795
|
+
.map((agent) => JSON.stringify([agent.workflowId, agent.toolUseId])));
|
|
4796
|
+
const representedUnscopedSubagentToolUseIds = new Set(subagents.map((agent) => agent.toolUseId).filter((id) => id !== undefined));
|
|
4797
|
+
// Keep the exact parent Agent row suppressed through the child-settled →
|
|
4798
|
+
// tool-end hand-off. The monitor publishes terminal state synchronously,
|
|
4799
|
+
// while the provider event that removes the generic tool row arrives on the
|
|
4800
|
+
// next tick; filtering only live children made that raw row flash back.
|
|
4801
|
+
const visibleActiveTools = activeTools.filter((tool) => tool.toolName.toLowerCase() !== 'agent' ||
|
|
4802
|
+
!(tool.runId
|
|
4803
|
+
? representedSubagentTools.has(JSON.stringify([tool.runId, tool.id]))
|
|
4804
|
+
: representedUnscopedSubagentToolUseIds.has(tool.id)));
|
|
4537
4805
|
const selectedSubagent = agentSurface
|
|
4538
|
-
?
|
|
4806
|
+
? liveSubagents.find((agent) => agent.viewId === agentSurface.selectedId)
|
|
4539
4807
|
: undefined;
|
|
4540
4808
|
const lifecycleOwnsViewport = phase === 'trust' ||
|
|
4541
4809
|
phase === 'resume' ||
|
|
@@ -4580,16 +4848,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4580
4848
|
: goalStatus && phase === 'ready' && state === 'idle'
|
|
4581
4849
|
? undefined
|
|
4582
4850
|
: hintForPhase(phase, state, session?.hasProvider === true, composerHasDraft);
|
|
4583
|
-
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:
|
|
4851
|
+
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: visibleActiveTools, working: state === 'thinking' || state === 'tool', interruptible: abortRef.current !== null, animate: stdout.isTTY === true })) : null, agentSurface === null && permission === null ? _jsx(TaskList, { tasks: tasks }) : null, permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls, review: permission.review, summary: permission.summary, detailsOpen: permissionDetailsOpen, reviewOffset: permissionReviewOffset, columns: 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' &&
|
|
4584
4852
|
phase === 'ready' &&
|
|
4585
4853
|
conversationMutation === null &&
|
|
4586
4854
|
textPrompt === null &&
|
|
4587
4855
|
choicePicker === null &&
|
|
4588
4856
|
copyPicker === null &&
|
|
4589
|
-
agentSurface === null, hidden: permission !== null ||
|
|
4590
|
-
choicePicker !== null ||
|
|
4591
|
-
copyPicker !== null ||
|
|
4592
|
-
agentSurface !== null, children: [pendingSteers.length > 0 &&
|
|
4857
|
+
agentSurface === null, hidden: permission !== null || choicePicker !== null || copyPicker !== null, children: [pendingSteers.length > 0 &&
|
|
4593
4858
|
permission === null &&
|
|
4594
4859
|
textPrompt === null &&
|
|
4595
4860
|
choicePicker === null &&
|
|
@@ -4603,7 +4868,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4603
4868
|
? queuePause.outcome === 'paused'
|
|
4604
4869
|
? 'held after a resumable run paused; wait for recovery, change model, or send a message to release it'
|
|
4605
4870
|
: `paused after a ${queuePause.outcome} turn; send a message or change model to continue`
|
|
4606
|
-
: 'sending when ready'] }) })) : null, _jsx(Composer, { disabled: phase !== 'ready' ||
|
|
4871
|
+
: 'sending when ready'] }) })) : null, _jsx(Composer, { permissionMode: permissionMode, onCycleMode: cyclePermissionMode, disabled: phase !== 'ready' ||
|
|
4607
4872
|
state === 'awaiting-permission' ||
|
|
4608
4873
|
compacting ||
|
|
4609
4874
|
conversationMutation !== null ||
|
|
@@ -4611,11 +4876,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4611
4876
|
agentSurface !== null, hidden: permission !== null ||
|
|
4612
4877
|
textPrompt !== null ||
|
|
4613
4878
|
choicePicker !== null ||
|
|
4614
|
-
copyPicker !== null
|
|
4615
|
-
agentSurface !== null,
|
|
4879
|
+
copyPicker !== null,
|
|
4616
4880
|
// A turn is running, so Esc is the interrupt and not
|
|
4617
4881
|
// the composer's clear.
|
|
4618
|
-
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 }) })] }) }));
|
|
4882
|
+
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, onOpenAgentPanel: openAgentCockpit, userCommands: userCommands, mentionCandidates: mentionCandidates, history: history })] }), permission === null && agentSurface === null && liveSubagents.length > 0 ? (_jsx(AgentTaskPanel, { agents: liveSubagents, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : permission === null && agentSurface?.kind === 'cockpit' ? (_jsx(AgentCockpit, { agents: liveSubagents, selectedPhaseId: agentSurface.selectedPhaseId, selectedId: agentSurface.selectedId, focus: agentSurface.focus, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : permission === null && agentSurface?.kind === 'transcript' && selectedSubagent ? (_jsx(AgentTranscript, { agent: selectedSubagent, tailOffset: agentSurface.tailOffset, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : null] })), _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 }) })] }) }));
|
|
4619
4883
|
}
|
|
4620
4884
|
function Banner({ version, session, permissionMode, cwd, }) {
|
|
4621
4885
|
const { columns } = useWindowSize();
|
|
@@ -4695,10 +4959,18 @@ function goalStatusLabel(goal) {
|
|
|
4695
4959
|
return 'Goal achieved';
|
|
4696
4960
|
}
|
|
4697
4961
|
}
|
|
4962
|
+
/** The label the operator saw for the option id they chose. */
|
|
4963
|
+
function labelOfOption(question, optionId) {
|
|
4964
|
+
return question.options.find((option) => option.id === optionId)?.label ?? optionId;
|
|
4965
|
+
}
|
|
4698
4966
|
function permissionModeDescription(mode) {
|
|
4699
4967
|
switch (mode) {
|
|
4700
4968
|
case 'prompt':
|
|
4701
4969
|
return 'Ask before an undecided tool call runs';
|
|
4970
|
+
case 'accept-edits':
|
|
4971
|
+
return 'Approve file edits and writes without asking; shell and everything else still ask';
|
|
4972
|
+
case 'plan':
|
|
4973
|
+
return 'Read-only: the agent explores and presents a plan; leave plan mode to carry it out';
|
|
4702
4974
|
case 'auto':
|
|
4703
4975
|
return 'Approve undecided calls unless a rule or safety gate refuses';
|
|
4704
4976
|
case 'strict':
|