@namzu/cli 27.0.0 → 28.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/README.md +24 -18
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +122 -12
- package/dist/cli.js.map +1 -1
- package/dist/commands/drain.js +2 -2
- package/dist/commands/drain.js.map +1 -1
- package/dist/commands/{run-flags.d.ts → exec-flags.d.ts} +29 -21
- package/dist/commands/exec-flags.d.ts.map +1 -0
- package/dist/commands/{run-flags.js → exec-flags.js} +31 -23
- package/dist/commands/exec-flags.js.map +1 -0
- package/dist/commands/{run-stream.d.ts → exec-json.d.ts} +20 -14
- package/dist/commands/exec-json.d.ts.map +1 -0
- package/dist/commands/exec-json.js +473 -0
- package/dist/commands/exec-json.js.map +1 -0
- package/dist/commands/{run.d.ts → exec.d.ts} +14 -13
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/exec.js +683 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/host-queries.d.ts +12 -0
- package/dist/commands/host-queries.d.ts.map +1 -0
- package/dist/commands/host-queries.js +149 -0
- package/dist/commands/host-queries.js.map +1 -0
- package/dist/commands/prior-messages.js +1 -1
- package/dist/commands/prior-messages.js.map +1 -1
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +2 -0
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/resident-flags.d.ts +2 -2
- package/dist/commands/resident-flags.d.ts.map +1 -1
- package/dist/commands/resident-flags.js +4 -2
- package/dist/commands/resident-flags.js.map +1 -1
- package/dist/commands/resident-learning.js +1 -1
- package/dist/commands/resident-learning.js.map +1 -1
- package/dist/commands/resident.js +1 -1
- package/dist/commands/resident.js.map +1 -1
- package/dist/commands/skills.js +1 -1
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/stdin.d.ts +5 -5
- package/dist/commands/stdin.js +5 -5
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +19 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +18 -4
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/config/{run-limits.d.ts → turn-guards.d.ts} +1 -1
- package/dist/config/turn-guards.d.ts.map +1 -0
- package/dist/config/{run-limits.js → turn-guards.js} +1 -1
- package/dist/config/turn-guards.js.map +1 -0
- package/dist/context/project.d.ts +1 -1
- package/dist/context/project.js +1 -1
- package/dist/doctor/checks/chain.js +2 -2
- package/dist/doctor/checks/chain.js.map +1 -1
- package/dist/integrations/providers/access.d.ts +4 -1
- package/dist/integrations/providers/access.d.ts.map +1 -1
- package/dist/integrations/providers/access.js +6 -3
- package/dist/integrations/providers/access.js.map +1 -1
- package/dist/integrations/providers/zen-catalogue.d.ts +100 -0
- package/dist/integrations/providers/zen-catalogue.d.ts.map +1 -0
- package/dist/integrations/providers/zen-catalogue.js +196 -0
- package/dist/integrations/providers/zen-catalogue.js.map +1 -0
- package/dist/integrations/resident/runner-launch.d.ts +3 -3
- package/dist/integrations/resident/runner-launch.d.ts.map +1 -1
- package/dist/integrations/resident/runner-worker.js +22 -0
- package/dist/integrations/resident/runner-worker.js.map +1 -1
- package/dist/integrations/resident/session-step.d.ts +2 -2
- package/dist/integrations/resident/session-step.d.ts.map +1 -1
- package/dist/integrations/resident/session-step.js +1 -1
- package/dist/integrations/resident/session-step.js.map +1 -1
- package/dist/integrations/sessions/transcript-export.js +3 -1
- package/dist/integrations/sessions/transcript-export.js.map +1 -1
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +1 -1
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/integrations/web/search.d.ts +1 -1
- package/dist/logging.d.ts +5 -5
- package/dist/logging.js +6 -6
- package/dist/permissions/headless-trust.d.ts +2 -2
- package/dist/permissions/headless-trust.js +3 -3
- package/dist/permissions/headless-trust.js.map +1 -1
- package/dist/permissions/live-mode.d.ts +73 -0
- package/dist/permissions/live-mode.d.ts.map +1 -0
- package/dist/permissions/live-mode.js +62 -0
- package/dist/permissions/live-mode.js.map +1 -0
- package/dist/termination.d.ts +1 -1
- package/dist/termination.js +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +181 -79
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Checklist.d.ts +56 -0
- package/dist/tui/Checklist.d.ts.map +1 -0
- package/dist/tui/Checklist.js +90 -0
- package/dist/tui/Checklist.js.map +1 -0
- package/dist/tui/StatusBar.d.ts +10 -0
- package/dist/tui/StatusBar.d.ts.map +1 -1
- package/dist/tui/StatusBar.js +8 -1
- package/dist/tui/StatusBar.js.map +1 -1
- package/dist/tui/TaskList.d.ts +18 -25
- package/dist/tui/TaskList.d.ts.map +1 -1
- package/dist/tui/TaskList.js +34 -58
- package/dist/tui/TaskList.js.map +1 -1
- package/dist/tui/Transcript.d.ts.map +1 -1
- package/dist/tui/Transcript.js +8 -1
- package/dist/tui/Transcript.js.map +1 -1
- package/dist/tui/agent.d.ts +33 -7
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +51 -9
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/live-window.d.ts +22 -0
- package/dist/tui/live-window.d.ts.map +1 -1
- package/dist/tui/live-window.js +34 -0
- package/dist/tui/live-window.js.map +1 -1
- package/dist/tui/notices.d.ts +27 -0
- package/dist/tui/notices.d.ts.map +1 -0
- package/dist/tui/notices.js +27 -0
- package/dist/tui/notices.js.map +1 -0
- package/dist/tui/slashCommands.d.ts +1 -1
- package/dist/tui/slashCommands.js +5 -5
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/status-panel-layout.js +1 -1
- package/dist/tui/status-panel-layout.js.map +1 -1
- package/dist/tui/task-activity.d.ts +71 -0
- package/dist/tui/task-activity.d.ts.map +1 -0
- package/dist/tui/task-activity.js +157 -0
- package/dist/tui/task-activity.js.map +1 -0
- package/dist/tui/turn-limits-settings.d.ts +1 -1
- package/dist/tui/turn-limits-settings.d.ts.map +1 -1
- package/dist/tui/turn-limits-settings.js +1 -1
- package/dist/tui/turn-limits-settings.js.map +1 -1
- package/dist/tui/types.d.ts +13 -0
- package/dist/tui/types.d.ts.map +1 -1
- package/dist/user-commands/store.d.ts +3 -3
- package/dist/user-commands/store.js +4 -4
- package/dist/user-commands/store.js.map +1 -1
- package/package.json +6 -6
- package/dist/commands/run-flags.d.ts.map +0 -1
- package/dist/commands/run-flags.js.map +0 -1
- package/dist/commands/run-stream.d.ts.map +0 -1
- package/dist/commands/run-stream.js +0 -647
- package/dist/commands/run-stream.js.map +0 -1
- package/dist/commands/run.d.ts.map +0 -1
- package/dist/commands/run.js +0 -617
- package/dist/commands/run.js.map +0 -1
- package/dist/config/run-limits.d.ts.map +0 -1
- package/dist/config/run-limits.js.map +0 -1
package/dist/tui/App.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { resolveTurnGuards } from '../config/
|
|
2
|
+
import { resolveTurnGuards } from '../config/turn-guards.js';
|
|
3
3
|
import { TURN_LIMIT_FIELDS, turnLimitCommands } from './turn-limits-settings.js';
|
|
4
4
|
/**
|
|
5
5
|
* TUI root. Composes the banner, transcript, composer, status bar, and
|
|
@@ -50,7 +50,10 @@ import { PermissionOverlay } from './PermissionOverlay.js';
|
|
|
50
50
|
import { Picker } from './Picker.js';
|
|
51
51
|
import { ResumePicker } from './ResumePicker.js';
|
|
52
52
|
import { StatusBar } from './StatusBar.js';
|
|
53
|
-
import {
|
|
53
|
+
import { isRepeatedNotice } from './notices.js';
|
|
54
|
+
import { checklistProgress } from './Checklist.js';
|
|
55
|
+
import { TaskList, taskListRows } from './TaskList.js';
|
|
56
|
+
import { applyTaskOperation, isTaskTool, removeTask, taskOperationFor, taskReportChecklist, upsertTask, } from './task-activity.js';
|
|
54
57
|
import { TextPrompt } from './TextPrompt.js';
|
|
55
58
|
import { modelCatalogueView } from './model-catalogue-view.js';
|
|
56
59
|
import { conversationEvidenceView } from './conversation-evidence-view.js';
|
|
@@ -64,7 +67,7 @@ import { assistantTranscriptTexts } from './conversation-history.js';
|
|
|
64
67
|
import { copyTargetsForResponse } from './copy-targets.js';
|
|
65
68
|
import { editablePrompts } from './edit-prompts.js';
|
|
66
69
|
import { editDraftInExternalEditor } from './external-editor.js';
|
|
67
|
-
import { liveWindow } from './live-window.js';
|
|
70
|
+
import { checklistInView, liveWindow } from './live-window.js';
|
|
68
71
|
import { resolveModelSwitch, } from './model-switch.js';
|
|
69
72
|
import { parseModelSelectionIntent, resolveModelSelectionIntent } from './model-selection-intent.js';
|
|
70
73
|
import { describeCodexDeviceLoginStart, describeLoginOutcome, describeLoginStart, describeLogout, describeProviderLogout, } from './login-prompt.js';
|
|
@@ -478,8 +481,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
478
481
|
* it would read as work still pending on a turn that already ended.
|
|
479
482
|
*/
|
|
480
483
|
const [tasks, setTasks] = useState([]);
|
|
481
|
-
/**
|
|
482
|
-
|
|
484
|
+
/**
|
|
485
|
+
* The same list, current NOW. React runs state updaters lazily, and each
|
|
486
|
+
* task event needs the task's previous state to say what changed and the
|
|
487
|
+
* whole list to draw the block it leaves in the transcript.
|
|
488
|
+
*/
|
|
489
|
+
const tasksRef = useRef([]);
|
|
483
490
|
// Bumped to reset the <Static> transcript log (on /clear, /clear-screen and /resume).
|
|
484
491
|
const [resetKey, setResetKey] = useState(0);
|
|
485
492
|
/**
|
|
@@ -1094,35 +1101,41 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1094
1101
|
}, []);
|
|
1095
1102
|
const pushMessage = useCallback((role, content, pending = false, glyph, detail, glyphColor, meta, activity) => {
|
|
1096
1103
|
const id = nextId();
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1104
|
+
const candidate = { role, content, pending, glyph, detail, activity };
|
|
1105
|
+
setMessages((prev) =>
|
|
1106
|
+
// The notice layer, not each caller, keeps a notice from printing
|
|
1107
|
+
// twice in a row: the same sentence twice reads as two events.
|
|
1108
|
+
isRepeatedNotice(prev.at(-1), candidate)
|
|
1109
|
+
? prev
|
|
1110
|
+
: [
|
|
1111
|
+
...prev,
|
|
1112
|
+
{
|
|
1113
|
+
id,
|
|
1114
|
+
role,
|
|
1115
|
+
content,
|
|
1116
|
+
pending,
|
|
1117
|
+
glyph,
|
|
1118
|
+
detail,
|
|
1119
|
+
glyphColor,
|
|
1120
|
+
meta,
|
|
1121
|
+
activity,
|
|
1122
|
+
// Numbered only if this body will actually be COLLAPSED — the
|
|
1123
|
+
// number exists to be read off a hint, and a body that fits
|
|
1124
|
+
// prints no hint. Numbering every body instead would leave gaps
|
|
1125
|
+
// the operator can see nothing of, make bare `/expand` reprint a
|
|
1126
|
+
// two-line body while the truncated one above it stayed hidden,
|
|
1127
|
+
// and let the out-of-range message quote a count that includes
|
|
1128
|
+
// blocks no hint ever named.
|
|
1129
|
+
//
|
|
1130
|
+
// Derived from `prev` rather than a counter, so the number is a
|
|
1131
|
+
// fact about the transcript rather than a second record of it.
|
|
1132
|
+
...((activity && detail?.length) || willCollapse(detail)
|
|
1133
|
+
? {
|
|
1134
|
+
detailRef: prev.filter((m) => m.detailRef !== undefined).length + 1,
|
|
1135
|
+
}
|
|
1136
|
+
: {}),
|
|
1137
|
+
},
|
|
1138
|
+
]);
|
|
1126
1139
|
return id;
|
|
1127
1140
|
}, [nextId]);
|
|
1128
1141
|
/**
|
|
@@ -1272,30 +1285,42 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1272
1285
|
}
|
|
1273
1286
|
}
|
|
1274
1287
|
}, [subagents, pushMessage, session]);
|
|
1275
|
-
|
|
1288
|
+
/**
|
|
1289
|
+
* Change the permission mode, now — including while a turn runs.
|
|
1290
|
+
*
|
|
1291
|
+
* It used to be refused until the current work settled. The reference
|
|
1292
|
+
* terminal applies the key at once, mid-turn, and so does this: the running
|
|
1293
|
+
* turn reads the mode at every decision (`SendOptions.currentPermissionMode`),
|
|
1294
|
+
* so the change governs its next tool call and every turn after. A dialog
|
|
1295
|
+
* already on screen is decided under the mode it was asked under; entering
|
|
1296
|
+
* plan refuses the next change the turn attempts; leaving plan re-runs
|
|
1297
|
+
* nothing it refused. The change is recorded on the running turn's log as
|
|
1298
|
+
* `approval_policy_changed`, which also tells the model once.
|
|
1299
|
+
*
|
|
1300
|
+
* `announce` is for a change asked for by name (`/permissions`), which gets
|
|
1301
|
+
* a reply. Shift+Tab is a reflex key: the footer is its reply, as it is in
|
|
1302
|
+
* the reference, and five presses no longer leave five transcript lines.
|
|
1303
|
+
*/
|
|
1304
|
+
const applyPermissionMode = useCallback((mode, announce = true) => {
|
|
1276
1305
|
if (!session?.hasProvider) {
|
|
1277
1306
|
pushMessage('system', 'Choose a model before changing permissions.');
|
|
1278
1307
|
return;
|
|
1279
1308
|
}
|
|
1280
|
-
if (state !== 'idle' ||
|
|
1281
|
-
abortRef.current !== null ||
|
|
1282
|
-
hasUnsettledTurn() ||
|
|
1283
|
-
queuedRef.current.length > 0 ||
|
|
1284
|
-
permissionResolveRef.current !== null ||
|
|
1285
|
-
compactingRef.current) {
|
|
1286
|
-
pushMessage('system', 'Permissions were not changed. Finish or stop the current work first.');
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
1309
|
if (!session.resetApprovalLatch) {
|
|
1290
1310
|
pushMessage('system', 'This session cannot reset approvals. Reconnect before changing permissions.');
|
|
1291
1311
|
return;
|
|
1292
1312
|
}
|
|
1293
1313
|
session.resetApprovalLatch();
|
|
1314
|
+
// The ref first: a running turn reads it at its next decision, and the
|
|
1315
|
+
// record below makes the change durable before that decision is made.
|
|
1294
1316
|
permissionModeRef.current = mode;
|
|
1295
1317
|
permissionModeSourceRef.current = 'session';
|
|
1296
1318
|
setPermissionModeState(mode);
|
|
1297
|
-
|
|
1298
|
-
|
|
1319
|
+
void session.setPermissionMode?.(mode);
|
|
1320
|
+
if (announce) {
|
|
1321
|
+
pushMessage('system', `Permissions: ${permissionModeLabel(mode)} for this session. ${permissionModeDescription(mode)}`);
|
|
1322
|
+
}
|
|
1323
|
+
}, [pushMessage, session]);
|
|
1299
1324
|
const applyReasoningEffort = useCallback((effort, selectedSession) => {
|
|
1300
1325
|
if (!session?.hasProvider) {
|
|
1301
1326
|
pushMessage('system', 'No active session — pick a provider before changing reasoning effort.');
|
|
@@ -1546,14 +1571,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1546
1571
|
* only — `auto` and `strict` are deliberate choices made by name in
|
|
1547
1572
|
* `/permissions`, not stops on a key an operator presses on reflex. From
|
|
1548
1573
|
* either of those the key returns to `prompt`, which is the direction a
|
|
1549
|
-
* reflex should fall. The change goes through the same
|
|
1550
|
-
* uses
|
|
1551
|
-
*
|
|
1574
|
+
* reflex should fall. The change goes through the same path `/permissions`
|
|
1575
|
+
* uses and takes effect at once, mid-turn included; the footer is its only
|
|
1576
|
+
* on-screen reply (see `applyPermissionMode`).
|
|
1552
1577
|
*/
|
|
1553
1578
|
const cyclePermissionMode = useCallback(() => {
|
|
1554
1579
|
const current = permissionModeRef.current;
|
|
1555
1580
|
const next = current === 'prompt' ? 'accept-edits' : current === 'accept-edits' ? 'plan' : 'prompt';
|
|
1556
|
-
applyPermissionMode(next);
|
|
1581
|
+
applyPermissionMode(next, false);
|
|
1557
1582
|
}, [applyPermissionMode]);
|
|
1558
1583
|
const runConversationExport = useCallback((destination) => {
|
|
1559
1584
|
const source = stableExportSource();
|
|
@@ -2604,10 +2629,23 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2604
2629
|
const finalized = messages.filter((m) => !m.pending);
|
|
2605
2630
|
// Activity and the plan share the terminal with the draft. Collapse the two
|
|
2606
2631
|
// lists together only when their full previews would crowd the input area.
|
|
2607
|
-
const fullTaskFurniture = tasks.length === 0 ? 0 : Math.min(tasks.length, 8) + 3;
|
|
2608
2632
|
const fullToolFurniture = activeTools.length === 0 ? 0 : Math.min(activeTools.length, 3) * 2 + 2;
|
|
2633
|
+
// The current-step row stands in for a checklist that is out of view. While
|
|
2634
|
+
// the newest checklist block, everything printed after it and the live
|
|
2635
|
+
// furniture fit the screen, the block is on screen, and the row would only
|
|
2636
|
+
// say one of its lines again. Once later rows push the block's head off a
|
|
2637
|
+
// short screen, the row is what keeps the current step visible.
|
|
2638
|
+
const checklistShown = checklistInView({
|
|
2639
|
+
messages,
|
|
2640
|
+
rows: terminal.rows,
|
|
2641
|
+
columns: terminal.columns,
|
|
2642
|
+
furnitureRows: LIVE_FURNITURE_ROWS + fullToolFurniture,
|
|
2643
|
+
raw: rawOutput,
|
|
2644
|
+
});
|
|
2645
|
+
const liveTasks = checklistShown ? [] : tasks;
|
|
2646
|
+
const fullTaskFurniture = taskListRows(liveTasks);
|
|
2609
2647
|
const compactWork = LIVE_FURNITURE_ROWS + fullTaskFurniture + fullToolFurniture >= terminal.rows;
|
|
2610
|
-
const taskFurniture =
|
|
2648
|
+
const taskFurniture = fullTaskFurniture;
|
|
2611
2649
|
const toolFurniture = compactWork && fullToolFurniture > 0 ? 2 : fullToolFurniture;
|
|
2612
2650
|
// How much of the transcript is still redrawable. The rest belongs to native
|
|
2613
2651
|
// terminal scrollback; the live tail stays deliberately small so an activity
|
|
@@ -3052,7 +3090,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3052
3090
|
// it runs when the abandoned turn finishes unwinding, which is after
|
|
3053
3091
|
// this line — and a surface that reports a result it has not read is
|
|
3054
3092
|
// the defect class this whole change is about. If that write fails,
|
|
3055
|
-
// the turn says so itself, in the same words `
|
|
3093
|
+
// the turn says so itself, in the same words `exec --json` uses.
|
|
3056
3094
|
pushMessage('system', 'The turn that was running was interrupted. Its reply so far is being saved to the conversation it started in, so it is not in the transcript above. A tool call already dispatched was not undone.');
|
|
3057
3095
|
}
|
|
3058
3096
|
}, [
|
|
@@ -3516,6 +3554,24 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3516
3554
|
});
|
|
3517
3555
|
sendTerminalNotification({ kind: 'approval-required' });
|
|
3518
3556
|
}), [sendTerminalNotification, setChoicePicker, setSelectedChoice]);
|
|
3557
|
+
/**
|
|
3558
|
+
* Grow this turn's open task block, or open one. The whole plan rides along
|
|
3559
|
+
* so the block shows the checklist as it stood after the operation; a
|
|
3560
|
+
* `null` operation (a `task_list`) refreshes it without naming a change.
|
|
3561
|
+
*/
|
|
3562
|
+
const writeTaskBlock = useCallback((st, operation, checklist) => {
|
|
3563
|
+
st.taskBlockKey ??= nextId();
|
|
3564
|
+
const key = st.taskBlockKey;
|
|
3565
|
+
const id = nextId();
|
|
3566
|
+
setMessages((prev) => applyTaskOperation(prev, {
|
|
3567
|
+
key,
|
|
3568
|
+
id,
|
|
3569
|
+
operation,
|
|
3570
|
+
checklist,
|
|
3571
|
+
settled: settledRef.current,
|
|
3572
|
+
glyphColor: theme.status.ok,
|
|
3573
|
+
}));
|
|
3574
|
+
}, [nextId]);
|
|
3519
3575
|
// Render one agent event onto the transcript. Shared by the local turn loop
|
|
3520
3576
|
// and the daemon-attach poller, so both paths produce identical output.
|
|
3521
3577
|
// `st` carries the streaming-assistant bubble id + accumulated text across
|
|
@@ -3700,6 +3756,28 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3700
3756
|
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3701
3757
|
break;
|
|
3702
3758
|
}
|
|
3759
|
+
if (isTaskTool(event.toolName)) {
|
|
3760
|
+
// The task event already wrote the block. A listing refreshes it
|
|
3761
|
+
// while there is a plan to show; with none it keeps its own row.
|
|
3762
|
+
if (!event.isError && event.toolName !== 'task_list') {
|
|
3763
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3764
|
+
break;
|
|
3765
|
+
}
|
|
3766
|
+
if (!event.isError && tasksRef.current.length > 0) {
|
|
3767
|
+
writeTaskBlock(st, null, tasksRef.current);
|
|
3768
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3769
|
+
break;
|
|
3770
|
+
}
|
|
3771
|
+
// Its own row, in words: the call's label, and the result's
|
|
3772
|
+
// label rather than the model's receipt, which names ids.
|
|
3773
|
+
pushMessage('tool', done?.label ?? formatToolCall(event.toolName, event.summary, true), false, event.isError ? '✗' : '✓', undefined, event.isError ? theme.status.error : theme.status.ok);
|
|
3774
|
+
const said = event.resultLabel ?? (event.isError ? 'the task tool refused the call' : '');
|
|
3775
|
+
if (said.length > 0) {
|
|
3776
|
+
pushMessage('tool', event.isError ? `failed: ${said}` : said, false, '⎿');
|
|
3777
|
+
}
|
|
3778
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3779
|
+
break;
|
|
3780
|
+
}
|
|
3703
3781
|
const catalogue = !event.isError && event.toolName === 'agent_models' && event.output !== undefined
|
|
3704
3782
|
? modelCatalogueView(event.output) : undefined;
|
|
3705
3783
|
if (catalogue !== undefined) {
|
|
@@ -3758,31 +3836,34 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3758
3836
|
break;
|
|
3759
3837
|
}
|
|
3760
3838
|
case 'task': {
|
|
3761
|
-
// The
|
|
3762
|
-
//
|
|
3763
|
-
//
|
|
3764
|
-
// updaters lazily, so a flag set there is still unset when the
|
|
3765
|
-
// transcript row below is chosen.
|
|
3766
|
-
const isNew = !knownTaskIdsRef.current.has(event.taskId);
|
|
3767
|
-
knownTaskIdsRef.current.add(event.taskId);
|
|
3839
|
+
// The transcript owns the checklist: consecutive operations fold
|
|
3840
|
+
// into one block there (task-activity.ts). The live row above the
|
|
3841
|
+
// composer only names the current step.
|
|
3768
3842
|
const item = {
|
|
3769
3843
|
id: event.taskId,
|
|
3770
3844
|
subject: event.subject,
|
|
3771
3845
|
status: event.status,
|
|
3772
3846
|
};
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
if (event.
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3847
|
+
// A reply in progress ends here, as it does at a tool call, so
|
|
3848
|
+
// the block lands after the text that led to it.
|
|
3849
|
+
closeAssistant();
|
|
3850
|
+
let operation;
|
|
3851
|
+
if (event.removed) {
|
|
3852
|
+
// Removed from the plan: it leaves the checklist, and the block
|
|
3853
|
+
// says so, rather than drawing it as still open.
|
|
3854
|
+
const next = removeTask(tasksRef.current, item.id, item.subject);
|
|
3855
|
+
operation = next.operation;
|
|
3856
|
+
tasksRef.current = next.tasks;
|
|
3782
3857
|
}
|
|
3783
|
-
else
|
|
3784
|
-
|
|
3858
|
+
else {
|
|
3859
|
+
const previous = tasksRef.current.find((task) => task.id === item.id);
|
|
3860
|
+
operation = taskOperationFor(previous, item);
|
|
3861
|
+
tasksRef.current = upsertTask(tasksRef.current, item);
|
|
3785
3862
|
}
|
|
3863
|
+
const checklist = tasksRef.current;
|
|
3864
|
+
setTasks(checklist);
|
|
3865
|
+
if (operation)
|
|
3866
|
+
writeTaskBlock(st, operation, checklist);
|
|
3786
3867
|
break;
|
|
3787
3868
|
}
|
|
3788
3869
|
case 'job':
|
|
@@ -3852,7 +3933,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3852
3933
|
st.outcome = 'stopped';
|
|
3853
3934
|
st.queuePauseOutcome = 'paused';
|
|
3854
3935
|
st.notification = { kind: 'turn-settled', outcome: 'stopped' };
|
|
3855
|
-
pushMessage('system', describeTurnInterruption(event), false, '
|
|
3936
|
+
pushMessage('system', describeTurnInterruption(event), false, '‖');
|
|
3856
3937
|
break;
|
|
3857
3938
|
case 'error':
|
|
3858
3939
|
closeAssistant();
|
|
@@ -3865,7 +3946,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3865
3946
|
st.notification = null;
|
|
3866
3947
|
break;
|
|
3867
3948
|
}
|
|
3868
|
-
}, [appendToMessage, finalizeMessage, flushStream, pushMessage]);
|
|
3949
|
+
}, [appendToMessage, finalizeMessage, flushStream, pushMessage, writeTaskBlock]);
|
|
3869
3950
|
applyEventRef.current = applyEvent;
|
|
3870
3951
|
const runTurn = useCallback(async (prompt) => {
|
|
3871
3952
|
// A passive queue effect can still hold the previous render's session
|
|
@@ -3975,7 +4056,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3975
4056
|
// ended: a finished list stays on screen until the operator moves on,
|
|
3976
4057
|
// which is the moment it has told them everything it can.
|
|
3977
4058
|
setTasks([]);
|
|
3978
|
-
|
|
4059
|
+
tasksRef.current = [];
|
|
3979
4060
|
// The model interleaves text → tool → text across iterations; `applyEvent`
|
|
3980
4061
|
// renders each one in order.
|
|
3981
4062
|
const st = {
|
|
@@ -4201,6 +4282,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4201
4282
|
// prompt closes it. A paused turn is never closed this way.
|
|
4202
4283
|
abandonInterrupted: true,
|
|
4203
4284
|
permissionMode: turnPermissionMode,
|
|
4285
|
+
// Read at every decision: the operator may change the mode
|
|
4286
|
+
// while this turn runs, and the change governs what follows.
|
|
4287
|
+
currentPermissionMode: () => permissionModeRef.current,
|
|
4204
4288
|
limits: turnLimits,
|
|
4205
4289
|
...(turnReasoningEffort !== undefined ? { effort: turnReasoningEffort } : {}),
|
|
4206
4290
|
...(turnOrchestrateMode ? { orchestrate: true } : {}),
|
|
@@ -4749,7 +4833,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4749
4833
|
setChoicePicker({
|
|
4750
4834
|
kind: 'command',
|
|
4751
4835
|
title: 'Configuration',
|
|
4752
|
-
notice: 'Model, reasoning, permission and
|
|
4836
|
+
notice: 'Model, reasoning, permission and turn-limit changes apply to this session.',
|
|
4753
4837
|
values: commands,
|
|
4754
4838
|
options: commands.map((command) => ({
|
|
4755
4839
|
label: command.label,
|
|
@@ -5069,7 +5153,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5069
5153
|
case 'list-skills': {
|
|
5070
5154
|
// The session's directory, not the process's — the same
|
|
5071
5155
|
// distinction the headless commands get from `--cwd`. They are
|
|
5072
|
-
// the same value today, and were the same value in `
|
|
5156
|
+
// the same value today, and were the same value in `exec --json`
|
|
5073
5157
|
// too until they were not.
|
|
5074
5158
|
const skills = discoverSkills({ cwd: ctx.cwd });
|
|
5075
5159
|
if (skills.length === 0) {
|
|
@@ -5254,6 +5338,24 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5254
5338
|
// painted as state of the one now on screen.
|
|
5255
5339
|
if (conversationGenRef.current !== generation)
|
|
5256
5340
|
return;
|
|
5341
|
+
const checklist = slash.name === 'tasks' && outcome?.kind === 'report'
|
|
5342
|
+
? taskReportChecklist(outcome.rows)
|
|
5343
|
+
: undefined;
|
|
5344
|
+
if (checklist) {
|
|
5345
|
+
// Drawn by the same checklist as the transcript's task
|
|
5346
|
+
// blocks: the same marks, and no id or owner column.
|
|
5347
|
+
const id = nextId();
|
|
5348
|
+
setMessages((prev) => [
|
|
5349
|
+
...prev,
|
|
5350
|
+
{
|
|
5351
|
+
id,
|
|
5352
|
+
role: 'system',
|
|
5353
|
+
content: checklist.length === 0 ? 'No tasks yet.' : checklistProgress(checklist),
|
|
5354
|
+
...(checklist.length > 0 ? { checklist } : {}),
|
|
5355
|
+
},
|
|
5356
|
+
]);
|
|
5357
|
+
return;
|
|
5358
|
+
}
|
|
5257
5359
|
pushMessage('system', outcome
|
|
5258
5360
|
? renderOutcome(outcome)
|
|
5259
5361
|
: `/${slash.name} is registered but this session cannot run it.`);
|
|
@@ -6490,7 +6592,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6490
6592
|
// where it is decided rather than guessed on the screen: with no
|
|
6491
6593
|
// session behind the picker, esc exits namzu and the footer says
|
|
6492
6594
|
// so instead of offering a cancel nobody will see.
|
|
6493
|
-
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:
|
|
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 && 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, focus: phase === 'ready' &&
|
|
6494
6596
|
state !== 'awaiting-permission' &&
|
|
6495
6597
|
!compacting &&
|
|
6496
6598
|
externalEditorRequest === null &&
|
|
@@ -6512,9 +6614,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6512
6614
|
textPrompt === null &&
|
|
6513
6615
|
choicePicker === null &&
|
|
6514
6616
|
copyPicker === null &&
|
|
6515
|
-
agentSurface === null && outputViewer === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: [queuePause ? '
|
|
6617
|
+
agentSurface === null && outputViewer === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: [queuePause ? '‖' : '⏎', " ", queued.length, " message", queued.length > 1 ? 's' : '', " queued \u2014", ' ', queuePause
|
|
6516
6618
|
? queuePause.outcome === 'paused'
|
|
6517
|
-
? 'held after a resumable
|
|
6619
|
+
? 'held after a resumable turn paused; wait for recovery, change model, or send a message to release it'
|
|
6518
6620
|
: `paused after a ${queuePause.outcome} turn; send a message or change model to continue`
|
|
6519
6621
|
: 'sending when ready'] }) })) : null, _jsx(Composer, { reasoningEffortLevels: session?.reasoningEffortLevels, onCycleMode: cyclePermissionMode, disabled: outputViewer !== null ||
|
|
6520
6622
|
phase !== 'ready' ||
|