@namzu/cli 28.1.0 → 29.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/CHANGELOG.md +92 -0
- package/dist/integrations/subagents/runtime.d.ts +25 -0
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +40 -4
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/tui/AgentExplorer.d.ts +28 -4
- package/dist/tui/AgentExplorer.d.ts.map +1 -1
- package/dist/tui/AgentExplorer.js +177 -35
- package/dist/tui/AgentExplorer.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +235 -21
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Composer.d.ts.map +1 -1
- package/dist/tui/Composer.js +10 -4
- package/dist/tui/Composer.js.map +1 -1
- package/dist/tui/ComposerFrame.d.ts +10 -1
- package/dist/tui/ComposerFrame.d.ts.map +1 -1
- package/dist/tui/ComposerFrame.js +27 -4
- package/dist/tui/ComposerFrame.js.map +1 -1
- package/dist/tui/EffortSlider.d.ts +54 -0
- package/dist/tui/EffortSlider.d.ts.map +1 -0
- package/dist/tui/EffortSlider.js +131 -0
- package/dist/tui/EffortSlider.js.map +1 -0
- package/dist/tui/LiveActivity.d.ts +23 -3
- package/dist/tui/LiveActivity.d.ts.map +1 -1
- package/dist/tui/LiveActivity.js +23 -17
- package/dist/tui/LiveActivity.js.map +1 -1
- package/dist/tui/Markdown.d.ts +29 -2
- package/dist/tui/Markdown.d.ts.map +1 -1
- package/dist/tui/Markdown.js +74 -36
- package/dist/tui/Markdown.js.map +1 -1
- package/dist/tui/StatusBar.js +1 -1
- package/dist/tui/StatusBar.js.map +1 -1
- package/dist/tui/Transcript.d.ts +24 -1
- package/dist/tui/Transcript.d.ts.map +1 -1
- package/dist/tui/Transcript.js +35 -8
- package/dist/tui/Transcript.js.map +1 -1
- package/dist/tui/ViewportBound.d.ts +48 -0
- package/dist/tui/ViewportBound.d.ts.map +1 -0
- package/dist/tui/ViewportBound.js +55 -0
- package/dist/tui/ViewportBound.js.map +1 -0
- package/dist/tui/agent-transcript-rows.d.ts +59 -0
- package/dist/tui/agent-transcript-rows.d.ts.map +1 -0
- package/dist/tui/agent-transcript-rows.js +115 -0
- package/dist/tui/agent-transcript-rows.js.map +1 -0
- package/dist/tui/agent.d.ts +39 -0
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +86 -5
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/live-window.d.ts +16 -0
- package/dist/tui/live-window.d.ts.map +1 -1
- package/dist/tui/live-window.js +50 -3
- package/dist/tui/live-window.js.map +1 -1
- package/dist/tui/markdown-block-cache.d.ts +9 -7
- package/dist/tui/markdown-block-cache.d.ts.map +1 -1
- package/dist/tui/markdown-block-cache.js +9 -7
- package/dist/tui/markdown-block-cache.js.map +1 -1
- package/dist/tui/markdown-table.d.ts +55 -0
- package/dist/tui/markdown-table.d.ts.map +1 -0
- package/dist/tui/markdown-table.js +175 -0
- package/dist/tui/markdown-table.js.map +1 -0
- package/dist/tui/markdown-wrap.d.ts +53 -0
- package/dist/tui/markdown-wrap.d.ts.map +1 -0
- package/dist/tui/markdown-wrap.js +189 -0
- package/dist/tui/markdown-wrap.js.map +1 -0
- package/dist/tui/markdownParser.d.ts +6 -0
- package/dist/tui/markdownParser.d.ts.map +1 -1
- package/dist/tui/markdownParser.js +49 -10
- package/dist/tui/markdownParser.js.map +1 -1
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +3 -0
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/stream-blocks.d.ts.map +1 -1
- package/dist/tui/stream-blocks.js +32 -3
- package/dist/tui/stream-blocks.js.map +1 -1
- package/dist/tui/theme.d.ts +13 -0
- package/dist/tui/theme.d.ts.map +1 -1
- package/dist/tui/theme.js +16 -0
- package/dist/tui/theme.js.map +1 -1
- package/dist/tui/types.d.ts +7 -2
- package/dist/tui/types.d.ts.map +1 -1
- package/dist/tui/units.d.ts +6 -0
- package/dist/tui/units.d.ts.map +1 -0
- package/dist/tui/units.js +20 -0
- package/dist/tui/units.js.map +1 -0
- package/dist/tui/web-activity.d.ts +58 -0
- package/dist/tui/web-activity.d.ts.map +1 -0
- package/dist/tui/web-activity.js +105 -0
- package/dist/tui/web-activity.js.map +1 -0
- package/package.json +8 -8
package/dist/tui/App.js
CHANGED
|
@@ -38,14 +38,17 @@ import { effectivePermissionMode, permissionModeDescription, permissionModeLabel
|
|
|
38
38
|
import { composeSkillsPrompt, discoverSkills, loadSkillBody } from '../skills/store.js';
|
|
39
39
|
import { discoverUserCommands } from '../user-commands/store.js';
|
|
40
40
|
import { AgentCockpit, AgentNarrationBand, AgentTaskPanel, AgentTranscript, activeSubagentCohorts, agentCockpitIsWide, agentPhasePageSize, agentPhases, agentPickerPageSize, agentTranscriptPageSize, agentWorkflowPageSize, agentWorkflows, maxAgentTranscriptTailOffset, } from './AgentExplorer.js';
|
|
41
|
+
import { completionRow, launchReceipt, settleLine } from './agent-transcript-rows.js';
|
|
41
42
|
import { BrandHeader } from './BrandHeader.js';
|
|
42
43
|
import { ChoicePicker } from './ChoicePicker.js';
|
|
43
44
|
import { emptyPluginReport, pluginDetails, pluginOption, pluginStartupState } from './plugin-view.js';
|
|
44
45
|
import { Composer, suggestionWindowSize, } from './Composer.js';
|
|
45
46
|
import { ComposerFrame } from './ComposerFrame.js';
|
|
47
|
+
import { EffortSlider, effortSliderLayout } from './EffortSlider.js';
|
|
46
48
|
import { CopyPicker } from './CopyPicker.js';
|
|
47
49
|
import { EditPromptPicker } from './EditPromptPicker.js';
|
|
48
50
|
import { LiveActivity, formatElapsed } from './LiveActivity.js';
|
|
51
|
+
import { webCallTitle, webLiveStatus, webSettledLine } from './web-activity.js';
|
|
49
52
|
import { PermissionOverlay } from './PermissionOverlay.js';
|
|
50
53
|
import { Picker } from './Picker.js';
|
|
51
54
|
import { ResumePicker } from './ResumePicker.js';
|
|
@@ -67,7 +70,8 @@ import { assistantTranscriptTexts } from './conversation-history.js';
|
|
|
67
70
|
import { copyTargetsForResponse } from './copy-targets.js';
|
|
68
71
|
import { editablePrompts } from './edit-prompts.js';
|
|
69
72
|
import { editDraftInExternalEditor } from './external-editor.js';
|
|
70
|
-
import { checklistInView, liveWindow } from './live-window.js';
|
|
73
|
+
import { checklistInView, liveWindow, settledBeforeStreaming } from './live-window.js';
|
|
74
|
+
import { ViewportBound } from './ViewportBound.js';
|
|
71
75
|
import { resolveModelSwitch, } from './model-switch.js';
|
|
72
76
|
import { parseModelSelectionIntent, resolveModelSelectionIntent } from './model-selection-intent.js';
|
|
73
77
|
import { describeCodexDeviceLoginStart, describeLoginOutcome, describeLoginStart, describeLogout, describeProviderLogout, } from './login-prompt.js';
|
|
@@ -113,6 +117,12 @@ const FREE_TEXT_ANSWER = '__free_text__';
|
|
|
113
117
|
* writing rather than as silence.
|
|
114
118
|
*/
|
|
115
119
|
const STREAM_RELEASE_MS = 250;
|
|
120
|
+
/**
|
|
121
|
+
* How long a batch of delegated agents stays still before its launch receipt
|
|
122
|
+
* is written. Agents of one response are begun within a few milliseconds of
|
|
123
|
+
* one another; this is the margin that keeps them on one receipt.
|
|
124
|
+
*/
|
|
125
|
+
const RECEIPT_SETTLE_MS = 250;
|
|
116
126
|
/** Last non-empty assistant text in a durable conversation, newest first. */
|
|
117
127
|
function latestAssistantOutput(messages) {
|
|
118
128
|
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
@@ -471,6 +481,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
471
481
|
* than as silence. Cleared by the first text or tool call that follows.
|
|
472
482
|
*/
|
|
473
483
|
const [thinking, setThinking] = useState(null);
|
|
484
|
+
/** Output tokens of the running turn, for the Working row. See `StreamState`. */
|
|
485
|
+
const [turnTokens, setTurnTokens] = useState(0);
|
|
486
|
+
const turnTokensShownAt = useRef(0);
|
|
487
|
+
const turnTokensTimer = useRef(undefined);
|
|
488
|
+
useEffect(() => () => {
|
|
489
|
+
if (turnTokensTimer.current)
|
|
490
|
+
clearTimeout(turnTokensTimer.current);
|
|
491
|
+
}, []);
|
|
474
492
|
// Tools currently executing — rendered live (spinner + elapsed) below the
|
|
475
493
|
// transcript, then committed as static lines on completion.
|
|
476
494
|
const [activeTools, setActiveTools] = useState([]);
|
|
@@ -719,8 +737,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
719
737
|
const agents = subagentsRef.current;
|
|
720
738
|
const workflows = agentWorkflows(agents);
|
|
721
739
|
const currentWorkflow = [...workflows].reverse().find((workflow) => workflow.agents.some((agent) => agent.status === 'starting' || agent.status === 'queued' || agent.status === 'working')) ?? workflows.at(-1);
|
|
722
|
-
|
|
723
|
-
|
|
740
|
+
// Opened on the work that is moving: the first phase with a live agent,
|
|
741
|
+
// and its first live agent, rather than on a phase that finished
|
|
742
|
+
// minutes ago. With nothing live, the first phase, as before.
|
|
743
|
+
const live = (agent) => agent.status === 'starting' || agent.status === 'queued' || agent.status === 'working';
|
|
744
|
+
const firstPhase = currentWorkflow?.phases.find((phase) => phase.agents.some(live)) ?? currentWorkflow?.phases[0];
|
|
745
|
+
const firstAgent = firstPhase?.agents.find(live) ?? firstPhase?.agents[0];
|
|
724
746
|
if (!firstPhase || !firstAgent)
|
|
725
747
|
return false;
|
|
726
748
|
setAgentSurface({
|
|
@@ -1230,12 +1252,65 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1230
1252
|
tailOffset: 0,
|
|
1231
1253
|
});
|
|
1232
1254
|
}, [hydrateSavedChildren, pushMessage, setAgentSurface]);
|
|
1255
|
+
/**
|
|
1256
|
+
* Delegated work writes three kinds of row into settled history (see
|
|
1257
|
+
* `agent-transcript-rows.ts`): a launch receipt per batch, then one
|
|
1258
|
+
* completion row per agent. Each is exactly-once, gated by its own set,
|
|
1259
|
+
* and a batch's receipt is always written before any of its completions.
|
|
1260
|
+
*
|
|
1261
|
+
* A receipt waits until the batch is whole — no `Agent` call of the turn is
|
|
1262
|
+
* still starting without a row of its own, and the newest member arrived
|
|
1263
|
+
* at least {@link RECEIPT_SETTLE_MS} ago — so two agents launched in one
|
|
1264
|
+
* response read as one launch rather than two. A member that already
|
|
1265
|
+
* finished forces the receipt out, so it never trails its completion.
|
|
1266
|
+
*/
|
|
1233
1267
|
const reportedAgentsRef = useRef(new Set());
|
|
1268
|
+
const reportedBatchesRef = useRef(new Set());
|
|
1269
|
+
const batchSeenAtRef = useRef(new Map());
|
|
1270
|
+
const [receiptTick, setReceiptTick] = useState(0);
|
|
1234
1271
|
useEffect(() => {
|
|
1235
1272
|
reportedAgentsRef.current.clear();
|
|
1273
|
+
reportedBatchesRef.current.clear();
|
|
1274
|
+
batchSeenAtRef.current.clear();
|
|
1236
1275
|
}, [session]);
|
|
1237
1276
|
useEffect(() => {
|
|
1277
|
+
void receiptTick;
|
|
1238
1278
|
const current = session?.subagents?.getSnapshot() ?? [];
|
|
1279
|
+
const now = Date.now();
|
|
1280
|
+
const batches = new Map();
|
|
1281
|
+
for (const agent of subagents) {
|
|
1282
|
+
if (agent.replayed)
|
|
1283
|
+
continue;
|
|
1284
|
+
if (!current.some((item) => item.viewId === agent.viewId))
|
|
1285
|
+
continue;
|
|
1286
|
+
const key = JSON.stringify([agent.workflowId, agent.batchId]);
|
|
1287
|
+
const members = batches.get(key) ?? [];
|
|
1288
|
+
members.push(agent);
|
|
1289
|
+
batches.set(key, members);
|
|
1290
|
+
}
|
|
1291
|
+
const launching = activeTools.some((tool) => tool.toolName.toLowerCase() === 'agent' &&
|
|
1292
|
+
!subagents.some((agent) => agent.toolUseId === tool.id &&
|
|
1293
|
+
(tool.turnId === undefined || agent.workflowId === tool.turnId)));
|
|
1294
|
+
let retryIn;
|
|
1295
|
+
for (const [key, members] of batches) {
|
|
1296
|
+
if (reportedBatchesRef.current.has(key))
|
|
1297
|
+
continue;
|
|
1298
|
+
// Re-stamped when the batch grows: the settle window runs from its newest member.
|
|
1299
|
+
const previous = batchSeenAtRef.current.get(key);
|
|
1300
|
+
const stamp = previous !== undefined && previous.count === members.length
|
|
1301
|
+
? previous.at
|
|
1302
|
+
: now;
|
|
1303
|
+
if (stamp === now)
|
|
1304
|
+
batchSeenAtRef.current.set(key, { at: now, count: members.length });
|
|
1305
|
+
const settledMember = members.some((agent) => ['completed', 'failed', 'cancelled'].includes(agent.status));
|
|
1306
|
+
const waited = now - stamp;
|
|
1307
|
+
if (!settledMember && (launching || waited < RECEIPT_SETTLE_MS)) {
|
|
1308
|
+
retryIn = Math.min(retryIn ?? RECEIPT_SETTLE_MS, Math.max(50, RECEIPT_SETTLE_MS - waited));
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
reportedBatchesRef.current.add(key);
|
|
1312
|
+
pushMessage('tool', launchReceipt(members).content, false, '●', undefined, theme.accent.assistant);
|
|
1313
|
+
}
|
|
1239
1314
|
for (const agent of subagents) {
|
|
1240
1315
|
if (!current.some((item) => item.viewId === agent.viewId &&
|
|
1241
1316
|
item.workflowId === agent.workflowId &&
|
|
@@ -1245,14 +1320,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1245
1320
|
continue;
|
|
1246
1321
|
if (reportedAgentsRef.current.has(agent.viewId))
|
|
1247
1322
|
continue;
|
|
1323
|
+
// Its receipt first, even if that means writing it alone.
|
|
1324
|
+
const key = JSON.stringify([agent.workflowId, agent.batchId]);
|
|
1325
|
+
if (!agent.replayed && !reportedBatchesRef.current.has(key)) {
|
|
1326
|
+
reportedBatchesRef.current.add(key);
|
|
1327
|
+
pushMessage('tool', launchReceipt(batches.get(key) ?? [agent]).content, false, '●', undefined, theme.accent.assistant);
|
|
1328
|
+
}
|
|
1248
1329
|
reportedAgentsRef.current.add(agent.viewId);
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
? 'Completed'
|
|
1252
|
-
: (agent.latestActivity ?? (agent.status === 'cancelled' ? 'Cancelled' : 'Failed'));
|
|
1253
|
-
pushMessage('tool', `${agent.description} · ${status}`, false, completed ? '✓' : '✗', undefined, completed ? theme.status.ok : theme.status.error, 'ctrl+t · agent details');
|
|
1330
|
+
const row = completionRow(agent);
|
|
1331
|
+
pushMessage('tool', row.content, false, row.ok ? '✓' : '✗', row.detail.length > 0 ? row.detail : undefined, row.ok ? theme.status.ok : theme.status.error, row.hint, row.detail.length > 0 ? 'agent-result' : undefined);
|
|
1254
1332
|
}
|
|
1255
|
-
|
|
1333
|
+
if (retryIn === undefined)
|
|
1334
|
+
return;
|
|
1335
|
+
const timer = setTimeout(() => setReceiptTick((tick) => tick + 1), retryIn);
|
|
1336
|
+
return () => clearTimeout(timer);
|
|
1337
|
+
}, [subagents, activeTools, pushMessage, session, receiptTick]);
|
|
1256
1338
|
/**
|
|
1257
1339
|
* A delivered `send_message` correction already lands its own row inside
|
|
1258
1340
|
* the child's transcript (`SubagentActivityMonitor.recordMessage`); this
|
|
@@ -2627,6 +2709,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2627
2709
|
}
|
|
2628
2710
|
}, [activateTrustedProject, pushMessage, runProbe]);
|
|
2629
2711
|
const finalized = messages.filter((m) => !m.pending);
|
|
2712
|
+
// Where the streaming reply stands among the finalized rows: a row written
|
|
2713
|
+
// while it streams is drawn below it, as it will be once it has finished.
|
|
2714
|
+
const streamingAt = messages.findIndex((m) => m.pending);
|
|
2715
|
+
const pendingAt = streamingAt < 0
|
|
2716
|
+
? finalized.length
|
|
2717
|
+
: messages.slice(0, streamingAt).filter((m) => !m.pending).length;
|
|
2630
2718
|
// Activity and the plan share the terminal with the draft. Collapse the two
|
|
2631
2719
|
// lists together only when their full previews would crowd the input area.
|
|
2632
2720
|
const fullToolFurniture = activeTools.length === 0 ? 0 : Math.min(activeTools.length, 3) * 2 + 2;
|
|
@@ -2673,9 +2761,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2673
2761
|
// Freeze the parent's Static floor while it is open; otherwise a parent turn
|
|
2674
2762
|
// settling in the background prints through the child screen. Returning
|
|
2675
2763
|
// advances the floor once and emits those finalized rows exactly once.
|
|
2764
|
+
const nextSettled = settledBeforeStreaming(messages, window.settled, settledRef.current);
|
|
2676
2765
|
if (agentSurface === null && outputViewer === null)
|
|
2677
|
-
settledRef.current =
|
|
2678
|
-
const renderedSettled = agentSurface === null && outputViewer === null ?
|
|
2766
|
+
settledRef.current = nextSettled;
|
|
2767
|
+
const renderedSettled = agentSurface === null && outputViewer === null ? nextSettled : settledRef.current;
|
|
2679
2768
|
// One merged vocabulary for the session: this host's own commands plus
|
|
2680
2769
|
// whatever the kernel's registry reports. Built here so `/help`, the
|
|
2681
2770
|
// autocomplete and the dispatcher all answer from the same list — three
|
|
@@ -2816,6 +2905,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2816
2905
|
for await (const event of session.resumePaused({
|
|
2817
2906
|
turnId: active.turnId,
|
|
2818
2907
|
signal: ac.signal,
|
|
2908
|
+
// The operator's mode, read at every decision like a new turn's:
|
|
2909
|
+
// `/resume` in plan mode stays read-only, and so do its children.
|
|
2910
|
+
permissionMode: permissionModeRef.current,
|
|
2911
|
+
currentPermissionMode: () => permissionModeRef.current,
|
|
2819
2912
|
})) {
|
|
2820
2913
|
applyEventRef.current?.(event, st);
|
|
2821
2914
|
}
|
|
@@ -3592,6 +3685,39 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3592
3685
|
st.assistantId = null;
|
|
3593
3686
|
}
|
|
3594
3687
|
};
|
|
3688
|
+
// Redrawn at most five times a second: a count that changes on every
|
|
3689
|
+
// delta would re-render the live region per token for a figure no
|
|
3690
|
+
// one reads at that rate. A provider report always lands.
|
|
3691
|
+
// A count held back is drawn when the interval ends, so the last one
|
|
3692
|
+
// of a burst is never left unshown while the stream goes quiet.
|
|
3693
|
+
const noteTokens = (force = false) => {
|
|
3694
|
+
const show = () => {
|
|
3695
|
+
turnTokensShownAt.current = Date.now();
|
|
3696
|
+
setTurnTokens((st.reportedOutputTokens ?? 0) + Math.ceil((st.streamedChars ?? 0) / 4));
|
|
3697
|
+
};
|
|
3698
|
+
const wait = 200 - (Date.now() - turnTokensShownAt.current);
|
|
3699
|
+
if (force || wait <= 0) {
|
|
3700
|
+
if (turnTokensTimer.current)
|
|
3701
|
+
clearTimeout(turnTokensTimer.current);
|
|
3702
|
+
turnTokensTimer.current = undefined;
|
|
3703
|
+
show();
|
|
3704
|
+
}
|
|
3705
|
+
else if (!turnTokensTimer.current) {
|
|
3706
|
+
turnTokensTimer.current = setTimeout(() => {
|
|
3707
|
+
turnTokensTimer.current = undefined;
|
|
3708
|
+
show();
|
|
3709
|
+
}, wait);
|
|
3710
|
+
}
|
|
3711
|
+
};
|
|
3712
|
+
if (event.kind === 'delta' || event.kind === 'reasoning') {
|
|
3713
|
+
st.streamedChars = (st.streamedChars ?? 0) + event.text.length;
|
|
3714
|
+
noteTokens();
|
|
3715
|
+
}
|
|
3716
|
+
else if (event.kind === 'usage' && event.outputTokens !== undefined) {
|
|
3717
|
+
st.reportedOutputTokens = event.outputTokens;
|
|
3718
|
+
st.streamedChars = 0;
|
|
3719
|
+
noteTokens(true);
|
|
3720
|
+
}
|
|
3595
3721
|
if (event.kind !== 'usage' && 'budget' in event && event.budget) {
|
|
3596
3722
|
const budget = event.budget;
|
|
3597
3723
|
const own = st.lastUsage;
|
|
@@ -3664,16 +3790,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3664
3790
|
(event.turnId === undefined || agent.workflowId === event.turnId))
|
|
3665
3791
|
: undefined;
|
|
3666
3792
|
const tool = {
|
|
3667
|
-
...(waitingAgent
|
|
3793
|
+
...(waitingAgent
|
|
3794
|
+
? { taskId: waitingAgent.taskId, waitingOn: waitingAgent.description }
|
|
3795
|
+
: {}),
|
|
3668
3796
|
id: event.toolUseId,
|
|
3669
3797
|
...(event.turnId ? { turnId: event.turnId } : {}),
|
|
3670
3798
|
toolName: event.toolName,
|
|
3671
3799
|
activity: event.activity,
|
|
3672
3800
|
label: waitingAgent
|
|
3673
3801
|
? `Waiting · ${waitingAgent.description}`
|
|
3674
|
-
:
|
|
3802
|
+
: event.web
|
|
3803
|
+
? webCallTitle(event.web)
|
|
3804
|
+
: formatToolCall(event.toolName, event.summary, event.standalone),
|
|
3675
3805
|
startedAt: Date.now(),
|
|
3676
3806
|
detail: event.detail,
|
|
3807
|
+
...(event.web ? { web: event.web, progress: webLiveStatus(event.web) } : {}),
|
|
3677
3808
|
};
|
|
3678
3809
|
activeToolsRef.current = [...activeToolsRef.current, tool];
|
|
3679
3810
|
setActiveTools(activeToolsRef.current);
|
|
@@ -3756,6 +3887,32 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3756
3887
|
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3757
3888
|
break;
|
|
3758
3889
|
}
|
|
3890
|
+
const web = event.web ?? done?.web;
|
|
3891
|
+
if (web) {
|
|
3892
|
+
// Two rows, the call and what it came to, drawn as `web` activity:
|
|
3893
|
+
// consecutive ones sit together and the result list stays behind
|
|
3894
|
+
// Ctrl+O, as the reference terminal draws them.
|
|
3895
|
+
const activity = {
|
|
3896
|
+
...web,
|
|
3897
|
+
...(event.web?.target ?? done?.web?.target
|
|
3898
|
+
? { target: event.web?.target ?? done?.web?.target }
|
|
3899
|
+
: {}),
|
|
3900
|
+
};
|
|
3901
|
+
const durationMs = event.durationMs ?? (done ? Date.now() - done.startedAt : undefined);
|
|
3902
|
+
const output = web.hosted ? undefined : event.output;
|
|
3903
|
+
pushMessage('tool', webCallTitle(activity), false, event.isError ? '✗' : '✓', undefined, event.isError ? theme.status.error : theme.status.ok, undefined, 'web');
|
|
3904
|
+
const outputLines = output !== undefined ? output.split('\n') : undefined;
|
|
3905
|
+
pushMessage('tool', event.isError
|
|
3906
|
+
? `failed: ${event.summary}`
|
|
3907
|
+
: webSettledLine(activity, {
|
|
3908
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
3909
|
+
...(activity.kind === 'fetch' && output !== undefined
|
|
3910
|
+
? { bytes: Buffer.byteLength(output, 'utf8') }
|
|
3911
|
+
: {}),
|
|
3912
|
+
}), false, '⎿', outputLines, undefined, outputLines && outputLines.length > 0 ? 'ctrl+o output' : undefined, 'web');
|
|
3913
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3914
|
+
break;
|
|
3915
|
+
}
|
|
3759
3916
|
if (isTaskTool(event.toolName)) {
|
|
3760
3917
|
// The task event already wrote the block. A listing refreshes it
|
|
3761
3918
|
// while there is a plan to show; with none it keeps its own row.
|
|
@@ -3922,6 +4079,18 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3922
4079
|
const stopNotice = describeTurnStop(event.stopReason, event.budget);
|
|
3923
4080
|
if (stopNotice)
|
|
3924
4081
|
pushMessage('system', stopNotice, false, '■');
|
|
4082
|
+
if (st.startedAt !== undefined) {
|
|
4083
|
+
const since = st.startedAt;
|
|
4084
|
+
const delegated = subagentsRef.current.filter((agent) => agent.replayed !== true && agent.startedAt >= since);
|
|
4085
|
+
// A turn that delegated always says what its agents came to; one
|
|
4086
|
+
// that did not closes with its time only when it finished, since
|
|
4087
|
+
// a stop or a cancellation has already said how it ended.
|
|
4088
|
+
const closing = delegated.length > 0 || st.completed
|
|
4089
|
+
? settleLine(Date.now() - since, delegated)
|
|
4090
|
+
: undefined;
|
|
4091
|
+
if (closing)
|
|
4092
|
+
pushMessage('system', closing, false, '✻', undefined, theme.text.muted);
|
|
4093
|
+
}
|
|
3925
4094
|
break;
|
|
3926
4095
|
}
|
|
3927
4096
|
case 'paused':
|
|
@@ -4059,8 +4228,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4059
4228
|
tasksRef.current = [];
|
|
4060
4229
|
// The model interleaves text → tool → text across iterations; `applyEvent`
|
|
4061
4230
|
// renders each one in order.
|
|
4231
|
+
if (turnTokensTimer.current)
|
|
4232
|
+
clearTimeout(turnTokensTimer.current);
|
|
4233
|
+
turnTokensTimer.current = undefined;
|
|
4234
|
+
setTurnTokens(0);
|
|
4235
|
+
turnTokensShownAt.current = 0;
|
|
4062
4236
|
const st = {
|
|
4063
4237
|
assistantId: null,
|
|
4238
|
+
startedAt: Date.now(),
|
|
4064
4239
|
text: '',
|
|
4065
4240
|
conversationMessages: undefined,
|
|
4066
4241
|
pending: '',
|
|
@@ -6358,10 +6533,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6358
6533
|
setSelectedChoice((index) => moveChoiceSelection(options, index, key.home ? 'first' : key.end ? 'last' : key.pageUp ? 'previous-page' : 'next-page', pageSize));
|
|
6359
6534
|
return;
|
|
6360
6535
|
}
|
|
6361
|
-
if (key.upArrow) {
|
|
6536
|
+
if (key.upArrow || (key.leftArrow && picker.kind === 'reasoning-effort')) {
|
|
6362
6537
|
setSelectedChoice((index) => moveChoiceSelection(options, index, 'previous'));
|
|
6363
6538
|
return;
|
|
6364
6539
|
}
|
|
6540
|
+
if (key.rightArrow && picker.kind === 'reasoning-effort') {
|
|
6541
|
+
setSelectedChoice((index) => moveChoiceSelection(options, index, 'next'));
|
|
6542
|
+
return;
|
|
6543
|
+
}
|
|
6365
6544
|
if (key.downArrow) {
|
|
6366
6545
|
setSelectedChoice((index) => moveChoiceSelection(options, index, 'next'));
|
|
6367
6546
|
return;
|
|
@@ -6443,10 +6622,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6443
6622
|
return;
|
|
6444
6623
|
}
|
|
6445
6624
|
// Small expansions stay in place. Older or oversized bodies open a viewer.
|
|
6625
|
+
//
|
|
6626
|
+
// A settled row cannot be repainted, so its hint keeps saying Ctrl+O
|
|
6627
|
+
// for as long as it is on screen; this press must keep reaching it. The
|
|
6628
|
+
// first press expands the live bodies in place; the next one, while a
|
|
6629
|
+
// settled body sits above them, opens the viewer on the newest settled
|
|
6630
|
+
// body (←/→ reach the rest) and folds the live ones back behind it, so
|
|
6631
|
+
// the press after that expands them again. Only with nothing settled to
|
|
6632
|
+
// open does a second press simply collapse.
|
|
6446
6633
|
if (key.ctrl && input === 'o') {
|
|
6447
|
-
const
|
|
6634
|
+
const shown = messages.filter((m) => !m.pending);
|
|
6635
|
+
const live = shown.slice(settledRef.current);
|
|
6448
6636
|
const collapsible = live.filter((m) => (m.activity && (m.detail?.length ?? 0) > 0) || willCollapse(m.detail));
|
|
6449
|
-
const
|
|
6637
|
+
const hasBody = (m) => m.detailRef !== undefined && (m.detail?.length ?? 0) > 0;
|
|
6638
|
+
const blocks = messages.filter(hasBody);
|
|
6639
|
+
const olderBlock = shown.slice(0, settledRef.current).filter(hasBody).at(-1);
|
|
6450
6640
|
const block = collapsible.at(-1) ?? blocks.at(-1);
|
|
6451
6641
|
if (!block) {
|
|
6452
6642
|
pushMessage('system', 'Nothing to expand yet. Ctrl+O opens retained tool output when available.');
|
|
@@ -6455,6 +6645,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6455
6645
|
const expanding = collapsible.some((m) => m.detailExpanded !== true);
|
|
6456
6646
|
const ids = new Set(collapsible.map((m) => m.id));
|
|
6457
6647
|
const proposed = messages.map((m) => ids.has(m.id) ? { ...m, detailExpanded: expanding } : m);
|
|
6648
|
+
if (!expanding && olderBlock) {
|
|
6649
|
+
setMessages(proposed);
|
|
6650
|
+
setOutputViewer(olderBlock);
|
|
6651
|
+
return;
|
|
6652
|
+
}
|
|
6458
6653
|
const projected = liveWindow({ messages: proposed.filter((m) => !m.pending), rows: terminal.rows,
|
|
6459
6654
|
columns: terminal.columns, furnitureRows: LIVE_FURNITURE_ROWS + taskFurniture + toolFurniture,
|
|
6460
6655
|
settled: settledRef.current, raw: rawOutput });
|
|
@@ -6528,6 +6723,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6528
6723
|
agentSurface === null
|
|
6529
6724
|
? goalStatusLabel(goalStatus, goalStatus ? goalActivation.isArmed(goalStatus.sessionId, goalStatus) : false)
|
|
6530
6725
|
: null;
|
|
6726
|
+
// The effort chooser is a left-to-right slider wherever its stops fit on
|
|
6727
|
+
// one row, and the vertical list everywhere else (see EffortSlider.tsx).
|
|
6728
|
+
const effortSlider = choicePicker?.kind === 'reasoning-effort'
|
|
6729
|
+
? effortSliderLayout(choicePicker.options.map((option) => option.label), Math.max(1, (terminal.columns ?? 80) - 2))
|
|
6730
|
+
: undefined;
|
|
6531
6731
|
const statusHint = conversationMutation === 'fork'
|
|
6532
6732
|
? 'forking conversation — input is paused'
|
|
6533
6733
|
: conversationMutation === 'edit'
|
|
@@ -6553,7 +6753,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6553
6753
|
: choicePicker
|
|
6554
6754
|
? choicePickerSearchable(choicePicker)
|
|
6555
6755
|
? 'type to filter · ↑↓ select · enter apply · esc back'
|
|
6556
|
-
:
|
|
6756
|
+
: effortSlider
|
|
6757
|
+
? '←/→ adjust · 1–9 select · enter apply · esc back'
|
|
6758
|
+
: '↑↓ / 1–9 select · enter apply · esc back'
|
|
6557
6759
|
: copyPicker
|
|
6558
6760
|
? 'copy target open — ↑↓ / 1–9 select · esc cancel'
|
|
6559
6761
|
: goalStatus && phase === 'ready' && state === 'idle'
|
|
@@ -6580,7 +6782,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6580
6782
|
phase !== 'resume' &&
|
|
6581
6783
|
phase !== 'edit' &&
|
|
6582
6784
|
phase !== 'picker';
|
|
6583
|
-
return (
|
|
6785
|
+
return (
|
|
6786
|
+
// One row short of the terminal, always: see ViewportBound.tsx for the
|
|
6787
|
+
// renderer path a taller frame takes and the scrollback row it costs.
|
|
6788
|
+
_jsx(ViewportBound, { rows: terminal.rows, display: externalEditorRequest ? 'none' : 'flex', children: _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(TranscriptFrame, { children: _jsx(Transcript, { messages: finalized, pending: messages.find((m) => m.pending) ?? null, pendingAt: pendingAt, state: state, settled: transcriptOwned ? renderedSettled : 0, resetKey: resetKey, raw: rawOutput, hyperlinks: hyperlinks, showLive: !lifecycleOwnsViewport,
|
|
6789
|
+
// The one column of padding the enclosing box gives every row.
|
|
6790
|
+
staticIndent: 1, header: transcriptOwned ? (_jsx(BrandHeader, { version: ctx.version, permissionMode: displayedPermissionMode })) : undefined }) }), providerSetup ? (_jsx(ProviderSetup, { cwd: ctx.cwd, onClose: () => setProviderSetup(false), onConnect: () => {
|
|
6584
6791
|
setProviderSetup(false);
|
|
6585
6792
|
setPickerInitialView('providers');
|
|
6586
6793
|
setPickerDetected(null);
|
|
@@ -6592,7 +6799,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6592
6799
|
// where it is decided rather than guessed on the screen: with no
|
|
6593
6800
|
// session behind the picker, esc exits namzu and the footer says
|
|
6594
6801
|
// so instead of offering a cancel nobody will see.
|
|
6595
|
-
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 && permission === null && textPrompt === null, thinking: thinking })) : null, agentSurface === null && outputViewer === null && permission === 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 })) : 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 &&
|
|
6802
|
+
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 && permission === null && textPrompt === null, thinking: thinking, tokens: turnTokens })) : null, agentSurface === null && outputViewer === null && permission === 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 })) : 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 &&
|
|
6803
|
+
agentSurface === null &&
|
|
6804
|
+
outputViewer === null &&
|
|
6805
|
+
choicePicker?.kind === 'reasoning-effort' &&
|
|
6806
|
+
effortSlider ? (_jsx(EffortSlider, { title: choicePicker.title, notice: choicePicker.notice, options: choicePicker.options, selected: selectedChoice, layout: effortSlider, columns: Math.max(1, (terminal.columns ?? 80) - 2), highest: choicePicker.options.at(-2)?.label === 'default' ? undefined : choicePicker.options.at(-2)?.label })) : 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, ...(orchestrateMode ? { mode: 'orchestrate' } : {}), focus: phase === 'ready' &&
|
|
6596
6807
|
state !== 'awaiting-permission' &&
|
|
6597
6808
|
!compacting &&
|
|
6598
6809
|
externalEditorRequest === null &&
|
|
@@ -6639,10 +6850,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6639
6850
|
permission === null &&
|
|
6640
6851
|
agentSurface === null &&
|
|
6641
6852
|
outputViewer === null ? (_jsx(AgentNarrationBand, { lines: narration })) : null, showComposerSurface &&
|
|
6642
|
-
permission === null &&
|
|
6643
6853
|
agentSurface === null &&
|
|
6644
6854
|
outputViewer === null &&
|
|
6645
|
-
liveSubagents.length > 0 ? (
|
|
6855
|
+
liveSubagents.length > 0 ? (
|
|
6856
|
+
// Still drawn while a review is open, reduced to its header line:
|
|
6857
|
+
// the work already approved keeps moving, and the operator
|
|
6858
|
+
// deciding the next launch should be able to see that it does.
|
|
6859
|
+
_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 &&
|
|
6646
6860
|
permission === null &&
|
|
6647
6861
|
agentSurface?.kind === 'transcript' &&
|
|
6648
6862
|
selectedSubagent ? (_jsx(AgentTranscript, { agent: selectedSubagent, tailOffset: agentSurface.tailOffset, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : null, outputViewer && permission === null ? (_jsx(ToolOutputViewer, { onPrevious: () => {
|