@namzu/cli 28.1.1 → 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 +79 -0
- package/dist/integrations/subagents/runtime.d.ts +16 -0
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +34 -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 +231 -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 +25 -0
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +49 -3
- 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 +7 -7
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
|
|
@@ -3596,6 +3685,39 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3596
3685
|
st.assistantId = null;
|
|
3597
3686
|
}
|
|
3598
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
|
+
}
|
|
3599
3721
|
if (event.kind !== 'usage' && 'budget' in event && event.budget) {
|
|
3600
3722
|
const budget = event.budget;
|
|
3601
3723
|
const own = st.lastUsage;
|
|
@@ -3668,16 +3790,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3668
3790
|
(event.turnId === undefined || agent.workflowId === event.turnId))
|
|
3669
3791
|
: undefined;
|
|
3670
3792
|
const tool = {
|
|
3671
|
-
...(waitingAgent
|
|
3793
|
+
...(waitingAgent
|
|
3794
|
+
? { taskId: waitingAgent.taskId, waitingOn: waitingAgent.description }
|
|
3795
|
+
: {}),
|
|
3672
3796
|
id: event.toolUseId,
|
|
3673
3797
|
...(event.turnId ? { turnId: event.turnId } : {}),
|
|
3674
3798
|
toolName: event.toolName,
|
|
3675
3799
|
activity: event.activity,
|
|
3676
3800
|
label: waitingAgent
|
|
3677
3801
|
? `Waiting · ${waitingAgent.description}`
|
|
3678
|
-
:
|
|
3802
|
+
: event.web
|
|
3803
|
+
? webCallTitle(event.web)
|
|
3804
|
+
: formatToolCall(event.toolName, event.summary, event.standalone),
|
|
3679
3805
|
startedAt: Date.now(),
|
|
3680
3806
|
detail: event.detail,
|
|
3807
|
+
...(event.web ? { web: event.web, progress: webLiveStatus(event.web) } : {}),
|
|
3681
3808
|
};
|
|
3682
3809
|
activeToolsRef.current = [...activeToolsRef.current, tool];
|
|
3683
3810
|
setActiveTools(activeToolsRef.current);
|
|
@@ -3760,6 +3887,32 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3760
3887
|
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3761
3888
|
break;
|
|
3762
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
|
+
}
|
|
3763
3916
|
if (isTaskTool(event.toolName)) {
|
|
3764
3917
|
// The task event already wrote the block. A listing refreshes it
|
|
3765
3918
|
// while there is a plan to show; with none it keeps its own row.
|
|
@@ -3926,6 +4079,18 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3926
4079
|
const stopNotice = describeTurnStop(event.stopReason, event.budget);
|
|
3927
4080
|
if (stopNotice)
|
|
3928
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
|
+
}
|
|
3929
4094
|
break;
|
|
3930
4095
|
}
|
|
3931
4096
|
case 'paused':
|
|
@@ -4063,8 +4228,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4063
4228
|
tasksRef.current = [];
|
|
4064
4229
|
// The model interleaves text → tool → text across iterations; `applyEvent`
|
|
4065
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;
|
|
4066
4236
|
const st = {
|
|
4067
4237
|
assistantId: null,
|
|
4238
|
+
startedAt: Date.now(),
|
|
4068
4239
|
text: '',
|
|
4069
4240
|
conversationMessages: undefined,
|
|
4070
4241
|
pending: '',
|
|
@@ -6362,10 +6533,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6362
6533
|
setSelectedChoice((index) => moveChoiceSelection(options, index, key.home ? 'first' : key.end ? 'last' : key.pageUp ? 'previous-page' : 'next-page', pageSize));
|
|
6363
6534
|
return;
|
|
6364
6535
|
}
|
|
6365
|
-
if (key.upArrow) {
|
|
6536
|
+
if (key.upArrow || (key.leftArrow && picker.kind === 'reasoning-effort')) {
|
|
6366
6537
|
setSelectedChoice((index) => moveChoiceSelection(options, index, 'previous'));
|
|
6367
6538
|
return;
|
|
6368
6539
|
}
|
|
6540
|
+
if (key.rightArrow && picker.kind === 'reasoning-effort') {
|
|
6541
|
+
setSelectedChoice((index) => moveChoiceSelection(options, index, 'next'));
|
|
6542
|
+
return;
|
|
6543
|
+
}
|
|
6369
6544
|
if (key.downArrow) {
|
|
6370
6545
|
setSelectedChoice((index) => moveChoiceSelection(options, index, 'next'));
|
|
6371
6546
|
return;
|
|
@@ -6447,10 +6622,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6447
6622
|
return;
|
|
6448
6623
|
}
|
|
6449
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.
|
|
6450
6633
|
if (key.ctrl && input === 'o') {
|
|
6451
|
-
const
|
|
6634
|
+
const shown = messages.filter((m) => !m.pending);
|
|
6635
|
+
const live = shown.slice(settledRef.current);
|
|
6452
6636
|
const collapsible = live.filter((m) => (m.activity && (m.detail?.length ?? 0) > 0) || willCollapse(m.detail));
|
|
6453
|
-
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);
|
|
6454
6640
|
const block = collapsible.at(-1) ?? blocks.at(-1);
|
|
6455
6641
|
if (!block) {
|
|
6456
6642
|
pushMessage('system', 'Nothing to expand yet. Ctrl+O opens retained tool output when available.');
|
|
@@ -6459,6 +6645,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6459
6645
|
const expanding = collapsible.some((m) => m.detailExpanded !== true);
|
|
6460
6646
|
const ids = new Set(collapsible.map((m) => m.id));
|
|
6461
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
|
+
}
|
|
6462
6653
|
const projected = liveWindow({ messages: proposed.filter((m) => !m.pending), rows: terminal.rows,
|
|
6463
6654
|
columns: terminal.columns, furnitureRows: LIVE_FURNITURE_ROWS + taskFurniture + toolFurniture,
|
|
6464
6655
|
settled: settledRef.current, raw: rawOutput });
|
|
@@ -6532,6 +6723,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6532
6723
|
agentSurface === null
|
|
6533
6724
|
? goalStatusLabel(goalStatus, goalStatus ? goalActivation.isArmed(goalStatus.sessionId, goalStatus) : false)
|
|
6534
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;
|
|
6535
6731
|
const statusHint = conversationMutation === 'fork'
|
|
6536
6732
|
? 'forking conversation — input is paused'
|
|
6537
6733
|
: conversationMutation === 'edit'
|
|
@@ -6557,7 +6753,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6557
6753
|
: choicePicker
|
|
6558
6754
|
? choicePickerSearchable(choicePicker)
|
|
6559
6755
|
? 'type to filter · ↑↓ select · enter apply · esc back'
|
|
6560
|
-
:
|
|
6756
|
+
: effortSlider
|
|
6757
|
+
? '←/→ adjust · 1–9 select · enter apply · esc back'
|
|
6758
|
+
: '↑↓ / 1–9 select · enter apply · esc back'
|
|
6561
6759
|
: copyPicker
|
|
6562
6760
|
? 'copy target open — ↑↓ / 1–9 select · esc cancel'
|
|
6563
6761
|
: goalStatus && phase === 'ready' && state === 'idle'
|
|
@@ -6584,7 +6782,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6584
6782
|
phase !== 'resume' &&
|
|
6585
6783
|
phase !== 'edit' &&
|
|
6586
6784
|
phase !== 'picker';
|
|
6587
|
-
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: () => {
|
|
6588
6791
|
setProviderSetup(false);
|
|
6589
6792
|
setPickerInitialView('providers');
|
|
6590
6793
|
setPickerDetected(null);
|
|
@@ -6596,7 +6799,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6596
6799
|
// where it is decided rather than guessed on the screen: with no
|
|
6597
6800
|
// session behind the picker, esc exits namzu and the footer says
|
|
6598
6801
|
// so instead of offering a cancel nobody will see.
|
|
6599
|
-
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' &&
|
|
6600
6807
|
state !== 'awaiting-permission' &&
|
|
6601
6808
|
!compacting &&
|
|
6602
6809
|
externalEditorRequest === null &&
|
|
@@ -6643,10 +6850,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6643
6850
|
permission === null &&
|
|
6644
6851
|
agentSurface === null &&
|
|
6645
6852
|
outputViewer === null ? (_jsx(AgentNarrationBand, { lines: narration })) : null, showComposerSurface &&
|
|
6646
|
-
permission === null &&
|
|
6647
6853
|
agentSurface === null &&
|
|
6648
6854
|
outputViewer === null &&
|
|
6649
|
-
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 &&
|
|
6650
6860
|
permission === null &&
|
|
6651
6861
|
agentSurface?.kind === 'transcript' &&
|
|
6652
6862
|
selectedSubagent ? (_jsx(AgentTranscript, { agent: selectedSubagent, tailOffset: agentSurface.tailOffset, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : null, outputViewer && permission === null ? (_jsx(ToolOutputViewer, { onPrevious: () => {
|