@namzu/cli 28.0.0 → 28.1.1
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 +32 -0
- 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 +9 -0
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +6 -0
- package/dist/integrations/subagents/runtime.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/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +180 -74
- 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 +43 -3
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +76 -8
- 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/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/types.d.ts +13 -0
- package/dist/tui/types.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/tui/App.js
CHANGED
|
@@ -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
|
|
@@ -2778,6 +2816,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2778
2816
|
for await (const event of session.resumePaused({
|
|
2779
2817
|
turnId: active.turnId,
|
|
2780
2818
|
signal: ac.signal,
|
|
2819
|
+
// The operator's mode, read at every decision like a new turn's:
|
|
2820
|
+
// `/resume` in plan mode stays read-only, and so do its children.
|
|
2821
|
+
permissionMode: permissionModeRef.current,
|
|
2822
|
+
currentPermissionMode: () => permissionModeRef.current,
|
|
2781
2823
|
})) {
|
|
2782
2824
|
applyEventRef.current?.(event, st);
|
|
2783
2825
|
}
|
|
@@ -3516,6 +3558,24 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3516
3558
|
});
|
|
3517
3559
|
sendTerminalNotification({ kind: 'approval-required' });
|
|
3518
3560
|
}), [sendTerminalNotification, setChoicePicker, setSelectedChoice]);
|
|
3561
|
+
/**
|
|
3562
|
+
* Grow this turn's open task block, or open one. The whole plan rides along
|
|
3563
|
+
* so the block shows the checklist as it stood after the operation; a
|
|
3564
|
+
* `null` operation (a `task_list`) refreshes it without naming a change.
|
|
3565
|
+
*/
|
|
3566
|
+
const writeTaskBlock = useCallback((st, operation, checklist) => {
|
|
3567
|
+
st.taskBlockKey ??= nextId();
|
|
3568
|
+
const key = st.taskBlockKey;
|
|
3569
|
+
const id = nextId();
|
|
3570
|
+
setMessages((prev) => applyTaskOperation(prev, {
|
|
3571
|
+
key,
|
|
3572
|
+
id,
|
|
3573
|
+
operation,
|
|
3574
|
+
checklist,
|
|
3575
|
+
settled: settledRef.current,
|
|
3576
|
+
glyphColor: theme.status.ok,
|
|
3577
|
+
}));
|
|
3578
|
+
}, [nextId]);
|
|
3519
3579
|
// Render one agent event onto the transcript. Shared by the local turn loop
|
|
3520
3580
|
// and the daemon-attach poller, so both paths produce identical output.
|
|
3521
3581
|
// `st` carries the streaming-assistant bubble id + accumulated text across
|
|
@@ -3700,6 +3760,28 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3700
3760
|
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3701
3761
|
break;
|
|
3702
3762
|
}
|
|
3763
|
+
if (isTaskTool(event.toolName)) {
|
|
3764
|
+
// The task event already wrote the block. A listing refreshes it
|
|
3765
|
+
// while there is a plan to show; with none it keeps its own row.
|
|
3766
|
+
if (!event.isError && event.toolName !== 'task_list') {
|
|
3767
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3768
|
+
break;
|
|
3769
|
+
}
|
|
3770
|
+
if (!event.isError && tasksRef.current.length > 0) {
|
|
3771
|
+
writeTaskBlock(st, null, tasksRef.current);
|
|
3772
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3773
|
+
break;
|
|
3774
|
+
}
|
|
3775
|
+
// Its own row, in words: the call's label, and the result's
|
|
3776
|
+
// label rather than the model's receipt, which names ids.
|
|
3777
|
+
pushMessage('tool', done?.label ?? formatToolCall(event.toolName, event.summary, true), false, event.isError ? '✗' : '✓', undefined, event.isError ? theme.status.error : theme.status.ok);
|
|
3778
|
+
const said = event.resultLabel ?? (event.isError ? 'the task tool refused the call' : '');
|
|
3779
|
+
if (said.length > 0) {
|
|
3780
|
+
pushMessage('tool', event.isError ? `failed: ${said}` : said, false, '⎿');
|
|
3781
|
+
}
|
|
3782
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3783
|
+
break;
|
|
3784
|
+
}
|
|
3703
3785
|
const catalogue = !event.isError && event.toolName === 'agent_models' && event.output !== undefined
|
|
3704
3786
|
? modelCatalogueView(event.output) : undefined;
|
|
3705
3787
|
if (catalogue !== undefined) {
|
|
@@ -3758,31 +3840,34 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3758
3840
|
break;
|
|
3759
3841
|
}
|
|
3760
3842
|
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);
|
|
3843
|
+
// The transcript owns the checklist: consecutive operations fold
|
|
3844
|
+
// into one block there (task-activity.ts). The live row above the
|
|
3845
|
+
// composer only names the current step.
|
|
3768
3846
|
const item = {
|
|
3769
3847
|
id: event.taskId,
|
|
3770
3848
|
subject: event.subject,
|
|
3771
3849
|
status: event.status,
|
|
3772
3850
|
};
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
if (event.
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3851
|
+
// A reply in progress ends here, as it does at a tool call, so
|
|
3852
|
+
// the block lands after the text that led to it.
|
|
3853
|
+
closeAssistant();
|
|
3854
|
+
let operation;
|
|
3855
|
+
if (event.removed) {
|
|
3856
|
+
// Removed from the plan: it leaves the checklist, and the block
|
|
3857
|
+
// says so, rather than drawing it as still open.
|
|
3858
|
+
const next = removeTask(tasksRef.current, item.id, item.subject);
|
|
3859
|
+
operation = next.operation;
|
|
3860
|
+
tasksRef.current = next.tasks;
|
|
3782
3861
|
}
|
|
3783
|
-
else
|
|
3784
|
-
|
|
3862
|
+
else {
|
|
3863
|
+
const previous = tasksRef.current.find((task) => task.id === item.id);
|
|
3864
|
+
operation = taskOperationFor(previous, item);
|
|
3865
|
+
tasksRef.current = upsertTask(tasksRef.current, item);
|
|
3785
3866
|
}
|
|
3867
|
+
const checklist = tasksRef.current;
|
|
3868
|
+
setTasks(checklist);
|
|
3869
|
+
if (operation)
|
|
3870
|
+
writeTaskBlock(st, operation, checklist);
|
|
3786
3871
|
break;
|
|
3787
3872
|
}
|
|
3788
3873
|
case 'job':
|
|
@@ -3852,7 +3937,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3852
3937
|
st.outcome = 'stopped';
|
|
3853
3938
|
st.queuePauseOutcome = 'paused';
|
|
3854
3939
|
st.notification = { kind: 'turn-settled', outcome: 'stopped' };
|
|
3855
|
-
pushMessage('system', describeTurnInterruption(event), false, '
|
|
3940
|
+
pushMessage('system', describeTurnInterruption(event), false, '‖');
|
|
3856
3941
|
break;
|
|
3857
3942
|
case 'error':
|
|
3858
3943
|
closeAssistant();
|
|
@@ -3865,7 +3950,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3865
3950
|
st.notification = null;
|
|
3866
3951
|
break;
|
|
3867
3952
|
}
|
|
3868
|
-
}, [appendToMessage, finalizeMessage, flushStream, pushMessage]);
|
|
3953
|
+
}, [appendToMessage, finalizeMessage, flushStream, pushMessage, writeTaskBlock]);
|
|
3869
3954
|
applyEventRef.current = applyEvent;
|
|
3870
3955
|
const runTurn = useCallback(async (prompt) => {
|
|
3871
3956
|
// A passive queue effect can still hold the previous render's session
|
|
@@ -3975,7 +4060,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3975
4060
|
// ended: a finished list stays on screen until the operator moves on,
|
|
3976
4061
|
// which is the moment it has told them everything it can.
|
|
3977
4062
|
setTasks([]);
|
|
3978
|
-
|
|
4063
|
+
tasksRef.current = [];
|
|
3979
4064
|
// The model interleaves text → tool → text across iterations; `applyEvent`
|
|
3980
4065
|
// renders each one in order.
|
|
3981
4066
|
const st = {
|
|
@@ -4201,6 +4286,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4201
4286
|
// prompt closes it. A paused turn is never closed this way.
|
|
4202
4287
|
abandonInterrupted: true,
|
|
4203
4288
|
permissionMode: turnPermissionMode,
|
|
4289
|
+
// Read at every decision: the operator may change the mode
|
|
4290
|
+
// while this turn runs, and the change governs what follows.
|
|
4291
|
+
currentPermissionMode: () => permissionModeRef.current,
|
|
4204
4292
|
limits: turnLimits,
|
|
4205
4293
|
...(turnReasoningEffort !== undefined ? { effort: turnReasoningEffort } : {}),
|
|
4206
4294
|
...(turnOrchestrateMode ? { orchestrate: true } : {}),
|
|
@@ -5254,6 +5342,24 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5254
5342
|
// painted as state of the one now on screen.
|
|
5255
5343
|
if (conversationGenRef.current !== generation)
|
|
5256
5344
|
return;
|
|
5345
|
+
const checklist = slash.name === 'tasks' && outcome?.kind === 'report'
|
|
5346
|
+
? taskReportChecklist(outcome.rows)
|
|
5347
|
+
: undefined;
|
|
5348
|
+
if (checklist) {
|
|
5349
|
+
// Drawn by the same checklist as the transcript's task
|
|
5350
|
+
// blocks: the same marks, and no id or owner column.
|
|
5351
|
+
const id = nextId();
|
|
5352
|
+
setMessages((prev) => [
|
|
5353
|
+
...prev,
|
|
5354
|
+
{
|
|
5355
|
+
id,
|
|
5356
|
+
role: 'system',
|
|
5357
|
+
content: checklist.length === 0 ? 'No tasks yet.' : checklistProgress(checklist),
|
|
5358
|
+
...(checklist.length > 0 ? { checklist } : {}),
|
|
5359
|
+
},
|
|
5360
|
+
]);
|
|
5361
|
+
return;
|
|
5362
|
+
}
|
|
5257
5363
|
pushMessage('system', outcome
|
|
5258
5364
|
? renderOutcome(outcome)
|
|
5259
5365
|
: `/${slash.name} is registered but this session cannot run it.`);
|
|
@@ -6490,7 +6596,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6490
6596
|
// where it is decided rather than guessed on the screen: with no
|
|
6491
6597
|
// session behind the picker, esc exits namzu and the footer says
|
|
6492
6598
|
// 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:
|
|
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 && 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
6600
|
state !== 'awaiting-permission' &&
|
|
6495
6601
|
!compacting &&
|
|
6496
6602
|
externalEditorRequest === null &&
|
|
@@ -6512,7 +6618,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6512
6618
|
textPrompt === null &&
|
|
6513
6619
|
choicePicker === null &&
|
|
6514
6620
|
copyPicker === null &&
|
|
6515
|
-
agentSurface === null && outputViewer === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: [queuePause ? '
|
|
6621
|
+
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
6622
|
? queuePause.outcome === 'paused'
|
|
6517
6623
|
? 'held after a resumable turn paused; wait for recovery, change model, or send a message to release it'
|
|
6518
6624
|
: `paused after a ${queuePause.outcome} turn; send a message or change model to continue`
|