@namzu/cli 21.0.0 → 22.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 +116 -0
- package/README.md +31 -10
- package/dist/commands/run.js +1 -1
- package/dist/commands/run.js.map +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +23 -2
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +13 -9
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/doctor/checks/credentials.d.ts.map +1 -1
- package/dist/doctor/checks/credentials.js +2 -0
- package/dist/doctor/checks/credentials.js.map +1 -1
- package/dist/installation.d.ts +5 -0
- package/dist/installation.d.ts.map +1 -0
- package/dist/installation.js +48 -0
- package/dist/installation.js.map +1 -0
- package/dist/integrations/providers/current-credential.d.ts +4 -0
- package/dist/integrations/providers/current-credential.d.ts.map +1 -0
- package/dist/integrations/providers/current-credential.js +45 -0
- package/dist/integrations/providers/current-credential.js.map +1 -0
- package/dist/integrations/providers/discover.d.ts +12 -1
- package/dist/integrations/providers/discover.d.ts.map +1 -1
- package/dist/integrations/providers/discover.js +17 -1
- package/dist/integrations/providers/discover.js.map +1 -1
- package/dist/integrations/providers/gemini-credentials.d.ts +13 -0
- package/dist/integrations/providers/gemini-credentials.d.ts.map +1 -0
- package/dist/integrations/providers/gemini-credentials.js +148 -0
- package/dist/integrations/providers/gemini-credentials.js.map +1 -0
- package/dist/integrations/providers/register.d.ts.map +1 -1
- package/dist/integrations/providers/register.js +5 -0
- package/dist/integrations/providers/register.js.map +1 -1
- package/dist/integrations/providers/registry.d.ts +1 -1
- package/dist/integrations/providers/registry.d.ts.map +1 -1
- package/dist/integrations/providers/registry.js +10 -0
- package/dist/integrations/providers/registry.js.map +1 -1
- package/dist/integrations/providers/setup.d.ts +49 -0
- package/dist/integrations/providers/setup.d.ts.map +1 -0
- package/dist/integrations/providers/setup.js +105 -0
- package/dist/integrations/providers/setup.js.map +1 -0
- package/dist/integrations/sessions/store.d.ts +1 -0
- package/dist/integrations/sessions/store.d.ts.map +1 -1
- package/dist/integrations/sessions/store.js +17 -4
- package/dist/integrations/sessions/store.js.map +1 -1
- package/dist/integrations/subagents/activity.d.ts.map +1 -1
- package/dist/integrations/subagents/activity.js +12 -3
- package/dist/integrations/subagents/activity.js.map +1 -1
- package/dist/integrations/subagents/history.d.ts +26 -0
- package/dist/integrations/subagents/history.d.ts.map +1 -0
- package/dist/integrations/subagents/history.js +76 -0
- package/dist/integrations/subagents/history.js.map +1 -0
- package/dist/integrations/subagents/model-effort.d.ts +4 -0
- package/dist/integrations/subagents/model-effort.d.ts.map +1 -0
- package/dist/integrations/subagents/model-effort.js +12 -0
- package/dist/integrations/subagents/model-effort.js.map +1 -0
- package/dist/integrations/subagents/runtime.d.ts +21 -4
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +205 -23
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/integrations/web/search.d.ts +11 -0
- package/dist/integrations/web/search.d.ts.map +1 -0
- package/dist/integrations/web/search.js +73 -0
- package/dist/integrations/web/search.js.map +1 -0
- package/dist/tui/AgentExplorer.d.ts.map +1 -1
- package/dist/tui/AgentExplorer.js +5 -5
- package/dist/tui/AgentExplorer.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +242 -88
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/BrandHeader.js +3 -3
- package/dist/tui/BrandHeader.js.map +1 -1
- package/dist/tui/Composer.d.ts.map +1 -1
- package/dist/tui/Composer.js +18 -2
- package/dist/tui/Composer.js.map +1 -1
- package/dist/tui/PermissionOverlay.d.ts +4 -1
- package/dist/tui/PermissionOverlay.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.js +6 -5
- package/dist/tui/PermissionOverlay.js.map +1 -1
- package/dist/tui/Picker.d.ts +2 -1
- package/dist/tui/Picker.d.ts.map +1 -1
- package/dist/tui/Picker.js +10 -3
- package/dist/tui/Picker.js.map +1 -1
- package/dist/tui/ProviderSetup.d.ts +6 -0
- package/dist/tui/ProviderSetup.d.ts.map +1 -0
- package/dist/tui/ProviderSetup.js +125 -0
- package/dist/tui/ProviderSetup.js.map +1 -0
- package/dist/tui/ResumePicker.d.ts.map +1 -1
- package/dist/tui/ResumePicker.js +5 -3
- package/dist/tui/ResumePicker.js.map +1 -1
- package/dist/tui/StatusPanel.d.ts +5 -0
- package/dist/tui/StatusPanel.d.ts.map +1 -0
- package/dist/tui/StatusPanel.js +11 -0
- package/dist/tui/StatusPanel.js.map +1 -0
- package/dist/tui/ToolOutputViewer.d.ts +11 -0
- package/dist/tui/ToolOutputViewer.d.ts.map +1 -0
- package/dist/tui/ToolOutputViewer.js +39 -0
- package/dist/tui/ToolOutputViewer.js.map +1 -0
- package/dist/tui/Transcript.d.ts.map +1 -1
- package/dist/tui/Transcript.js +9 -3
- package/dist/tui/Transcript.js.map +1 -1
- package/dist/tui/agent.d.ts +7 -1
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +195 -46
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/live-window.d.ts.map +1 -1
- package/dist/tui/live-window.js +5 -1
- package/dist/tui/live-window.js.map +1 -1
- package/dist/tui/logo.d.ts +4 -4
- package/dist/tui/logo.d.ts.map +1 -1
- package/dist/tui/logo.js +4 -4
- package/dist/tui/logo.js.map +1 -1
- package/dist/tui/model-catalogue-view.d.ts +3 -0
- package/dist/tui/model-catalogue-view.d.ts.map +1 -0
- package/dist/tui/model-catalogue-view.js +50 -0
- package/dist/tui/model-catalogue-view.js.map +1 -0
- package/dist/tui/permission-review.d.ts +2 -0
- package/dist/tui/permission-review.d.ts.map +1 -1
- package/dist/tui/permission-review.js +57 -3
- package/dist/tui/permission-review.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +7 -0
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +75 -1
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/status-card.d.ts +3 -0
- package/dist/tui/status-card.d.ts.map +1 -0
- package/dist/tui/status-card.js +30 -0
- package/dist/tui/status-card.js.map +1 -0
- package/dist/tui/status-panel-layout.d.ts +11 -0
- package/dist/tui/status-panel-layout.d.ts.map +1 -0
- package/dist/tui/status-panel-layout.js +42 -0
- package/dist/tui/status-panel-layout.js.map +1 -0
- package/dist/tui/types.d.ts +4 -0
- package/dist/tui/types.d.ts.map +1 -1
- package/package.json +4 -3
package/dist/tui/App.js
CHANGED
|
@@ -11,6 +11,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
11
11
|
* 4. Otherwise show <Picker/>. An explicit picker choice is persisted;
|
|
12
12
|
* automatic reuse is not mistaken for a durable operator preference.
|
|
13
13
|
*/
|
|
14
|
+
import { discoverProviders } from '../integrations/providers/discover.js';
|
|
15
|
+
import { ProviderSetup } from './ProviderSetup.js';
|
|
16
|
+
import { ToolOutputViewer } from './ToolOutputViewer.js';
|
|
14
17
|
import { join, relative } from 'node:path';
|
|
15
18
|
import { DEFAULT_MAX_GOAL_ROUNDS, DefaultPathBuilder, DiskMessageFeedbackStore, GoalRoundLimitError, HostCommandRegistry, RunCancelled, SessionGoalActivation, StaleGoalError, asSessionId, createAssistantMessage, createUserMessage, generateRunId, generateSessionId, isCompactionMessage, kernelHostCommands, } from '@namzu/sdk';
|
|
16
19
|
import { Box, Text, useApp, useInput, useStdout, useWindowSize } from 'ink';
|
|
@@ -44,6 +47,7 @@ import { ResumePicker } from './ResumePicker.js';
|
|
|
44
47
|
import { StatusBar } from './StatusBar.js';
|
|
45
48
|
import { TaskList } from './TaskList.js';
|
|
46
49
|
import { TextPrompt } from './TextPrompt.js';
|
|
50
|
+
import { modelCatalogueView } from './model-catalogue-view.js';
|
|
47
51
|
import { Transcript, willCollapse } from './Transcript.js';
|
|
48
52
|
import { TrustPrompt } from './TrustPrompt.js';
|
|
49
53
|
import { createAgentSession, describeProviderModels, probeAgentSession, } from './agent.js';
|
|
@@ -277,6 +281,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
277
281
|
const [messages, setMessages] = useState([]);
|
|
278
282
|
/** Plain, copy-friendly rendering of the same retained transcript rows. */
|
|
279
283
|
const [rawOutput, setRawOutput] = useState(false);
|
|
284
|
+
const [outputViewer, setOutputViewer] = useState(null);
|
|
280
285
|
/**
|
|
281
286
|
* The conversation sent to the model, in the SDK's own lossless shape.
|
|
282
287
|
*
|
|
@@ -533,6 +538,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
533
538
|
copyPickerCommittedRef.current = copyPicker;
|
|
534
539
|
}, [copyPicker]);
|
|
535
540
|
/** Finite slash-command choice owned synchronously until apply or cancel. */
|
|
541
|
+
const [providerSetup, setProviderSetup] = useState(false);
|
|
536
542
|
const [choicePicker, setChoicePickerState] = useState(null);
|
|
537
543
|
const choicePickerRef = useRef(null);
|
|
538
544
|
const [choiceQuery, setChoiceQuery] = useState('');
|
|
@@ -708,6 +714,18 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
708
714
|
setActiveTools([]);
|
|
709
715
|
}, []);
|
|
710
716
|
const permissionResolveRef = useRef(null);
|
|
717
|
+
const permissionQueueRef = useRef([]);
|
|
718
|
+
const [queuedPermissionCount, setQueuedPermissionCount] = useState(0);
|
|
719
|
+
useEffect(() => () => {
|
|
720
|
+
const decision = {
|
|
721
|
+
kind: 'reject',
|
|
722
|
+
feedback: 'The approval screen closed.',
|
|
723
|
+
};
|
|
724
|
+
permissionResolveRef.current?.(decision);
|
|
725
|
+
permissionResolveRef.current = null;
|
|
726
|
+
for (const pending of permissionQueueRef.current.splice(0))
|
|
727
|
+
pending.resolve(decision);
|
|
728
|
+
}, []);
|
|
711
729
|
/**
|
|
712
730
|
* When the pending prompt took the screen, or `null` with none open.
|
|
713
731
|
*
|
|
@@ -767,6 +785,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
767
785
|
const goalDriveInFlightRef = useRef(false);
|
|
768
786
|
const [goalDriveVersion, setGoalDriveVersion] = useState(0);
|
|
769
787
|
const [goalStatus, setGoalStatus] = useState(null);
|
|
788
|
+
const pendingGoalResumeRef = useRef(null);
|
|
770
789
|
const wakeGoalDriver = useCallback(() => setGoalDriveVersion((version) => version + 1), []);
|
|
771
790
|
/**
|
|
772
791
|
* Conversation writes in the order the operator produced them.
|
|
@@ -940,7 +959,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
940
959
|
externalEditorRequestRef.current = null;
|
|
941
960
|
request.controller.abort(new Error('the terminal session closed'));
|
|
942
961
|
}, []);
|
|
943
|
-
const pushMessage = useCallback((role, content, pending = false, glyph, detail, glyphColor, meta) => {
|
|
962
|
+
const pushMessage = useCallback((role, content, pending = false, glyph, detail, glyphColor, meta, activity) => {
|
|
944
963
|
const id = nextId();
|
|
945
964
|
setMessages((prev) => [
|
|
946
965
|
...prev,
|
|
@@ -953,6 +972,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
953
972
|
detail,
|
|
954
973
|
glyphColor,
|
|
955
974
|
meta,
|
|
975
|
+
activity,
|
|
956
976
|
// Numbered only if this body will actually be COLLAPSED — the
|
|
957
977
|
// number exists to be read off a hint, and a body that fits
|
|
958
978
|
// prints no hint. Numbering every body instead would leave gaps
|
|
@@ -963,7 +983,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
963
983
|
//
|
|
964
984
|
// Derived from `prev` rather than a counter, so the number is a
|
|
965
985
|
// fact about the transcript rather than a second record of it.
|
|
966
|
-
...(willCollapse(detail)
|
|
986
|
+
...((activity && detail?.length) || willCollapse(detail)
|
|
967
987
|
? {
|
|
968
988
|
detailRef: prev.filter((m) => m.detailRef !== undefined).length + 1,
|
|
969
989
|
}
|
|
@@ -972,6 +992,29 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
972
992
|
]);
|
|
973
993
|
return id;
|
|
974
994
|
}, [nextId]);
|
|
995
|
+
const reportedAgentsRef = useRef(new Set());
|
|
996
|
+
useEffect(() => {
|
|
997
|
+
reportedAgentsRef.current.clear();
|
|
998
|
+
}, [session]);
|
|
999
|
+
useEffect(() => {
|
|
1000
|
+
const current = session?.subagents?.getSnapshot() ?? [];
|
|
1001
|
+
for (const agent of subagents) {
|
|
1002
|
+
if (!current.some((item) => item.viewId === agent.viewId &&
|
|
1003
|
+
item.workflowId === agent.workflowId &&
|
|
1004
|
+
item.status === agent.status))
|
|
1005
|
+
continue;
|
|
1006
|
+
if (!['completed', 'failed', 'cancelled'].includes(agent.status))
|
|
1007
|
+
continue;
|
|
1008
|
+
if (reportedAgentsRef.current.has(agent.viewId))
|
|
1009
|
+
continue;
|
|
1010
|
+
reportedAgentsRef.current.add(agent.viewId);
|
|
1011
|
+
const completed = agent.status === 'completed';
|
|
1012
|
+
const status = completed
|
|
1013
|
+
? 'Completed'
|
|
1014
|
+
: (agent.latestActivity ?? (agent.status === 'cancelled' ? 'Cancelled' : 'Failed'));
|
|
1015
|
+
pushMessage('tool', `${agent.description} · ${status}`, false, completed ? '✓' : '✗', undefined, completed ? theme.status.ok : theme.status.error, 'ctrl+t · agent details');
|
|
1016
|
+
}
|
|
1017
|
+
}, [subagents, pushMessage, session]);
|
|
975
1018
|
const applyPermissionMode = useCallback((mode) => {
|
|
976
1019
|
if (!session?.hasProvider) {
|
|
977
1020
|
pushMessage('system', 'Choose a model before changing permissions.');
|
|
@@ -1449,6 +1492,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1449
1492
|
return;
|
|
1450
1493
|
}
|
|
1451
1494
|
setChoicePicker(null);
|
|
1495
|
+
if (picker.kind === 'resume-goal') {
|
|
1496
|
+
if (value === 'resume' && scopeRef.current?.sessionId === picker.sessionId)
|
|
1497
|
+
commandPickerSubmitRef.current('/goal resume');
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1452
1500
|
if (picker.kind === 'command') {
|
|
1453
1501
|
const command = value;
|
|
1454
1502
|
if (command.problem) {
|
|
@@ -1646,6 +1694,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1646
1694
|
lastCompletedOutputRef.current = persistedOutput
|
|
1647
1695
|
? { text: persistedOutput, provenance: 'persisted' }
|
|
1648
1696
|
: null;
|
|
1697
|
+
pendingGoalResumeRef.current = requestedConversationId;
|
|
1649
1698
|
initialConversationIdRef.current = undefined;
|
|
1650
1699
|
conversationMaterializedRef.current = true;
|
|
1651
1700
|
}
|
|
@@ -1785,6 +1834,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1785
1834
|
// Re-hydration (a provider switch via /model) builds a second session;
|
|
1786
1835
|
// without this the first one's tool-server child processes stay alive
|
|
1787
1836
|
// for the rest of the TUI's life.
|
|
1837
|
+
const previousInstructionFiles = previousSessionRef.current?.instructionFiles;
|
|
1788
1838
|
void previousSessionRef.current?.close();
|
|
1789
1839
|
previousSessionRef.current = s;
|
|
1790
1840
|
setSession(s);
|
|
@@ -1826,7 +1876,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1826
1876
|
// they did not expect — an instructions file in a parent directory
|
|
1827
1877
|
// they forgot about is exactly the thing that makes the agent
|
|
1828
1878
|
// behave oddly for no visible reason.
|
|
1829
|
-
if (s.instructionFiles.length > 0
|
|
1879
|
+
if (s.instructionFiles.length > 0 &&
|
|
1880
|
+
JSON.stringify([...s.instructionFiles].sort()) !==
|
|
1881
|
+
JSON.stringify(previousInstructionFiles ? [...previousInstructionFiles].sort() : undefined)) {
|
|
1830
1882
|
pushMessage('system', `Project instructions: ${s.instructionFiles.map((p) => relative(activeCtx.cwd, p) || p).join(', ')}`);
|
|
1831
1883
|
}
|
|
1832
1884
|
for (const skip of s.skippedInstructionFiles) {
|
|
@@ -2107,7 +2159,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2107
2159
|
setPickerDetected(signedIn);
|
|
2108
2160
|
setPickerSelectionKind('signed-in-subscription');
|
|
2109
2161
|
setPickerInitialView('providers');
|
|
2110
|
-
setPickerNotice('
|
|
2162
|
+
setPickerNotice('Multiple model sessions are already signed in on this device. Choose which one Namzu should use.');
|
|
2111
2163
|
setPhase('picker');
|
|
2112
2164
|
return;
|
|
2113
2165
|
}
|
|
@@ -2177,6 +2229,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2177
2229
|
// terminal scrollback; the live tail stays deliberately small so an activity
|
|
2178
2230
|
// tick never repaints the whole conversation.
|
|
2179
2231
|
//
|
|
2232
|
+
useEffect(() => {
|
|
2233
|
+
setOutputViewer(null);
|
|
2234
|
+
}, [resetKey]);
|
|
2235
|
+
useEffect(() => {
|
|
2236
|
+
if (permission !== null)
|
|
2237
|
+
setOutputViewer(null);
|
|
2238
|
+
}, [permission]);
|
|
2180
2239
|
// A ref, and mutated during render, because the split has to be MONOTONIC:
|
|
2181
2240
|
// a row that has been printed to scrollback can never come back, and `max`
|
|
2182
2241
|
// is idempotent, so a repeated render reaches the same answer.
|
|
@@ -2192,9 +2251,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2192
2251
|
// Freeze the parent's Static floor while it is open; otherwise a parent turn
|
|
2193
2252
|
// settling in the background prints through the child screen. Returning
|
|
2194
2253
|
// advances the floor once and emits those finalized rows exactly once.
|
|
2195
|
-
if (agentSurface === null)
|
|
2254
|
+
if (agentSurface === null && outputViewer === null)
|
|
2196
2255
|
settledRef.current = window.settled;
|
|
2197
|
-
const renderedSettled = agentSurface === null ? window.settled : settledRef.current;
|
|
2256
|
+
const renderedSettled = agentSurface === null && outputViewer === null ? window.settled : settledRef.current;
|
|
2198
2257
|
// One merged vocabulary for the session: this host's own commands plus
|
|
2199
2258
|
// whatever the kernel's registry reports. Built here so `/help`, the
|
|
2200
2259
|
// autocomplete and the dispatcher all answer from the same list — three
|
|
@@ -2260,6 +2319,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2260
2319
|
instructionFiles: session?.instructionFiles ?? [],
|
|
2261
2320
|
userCommands,
|
|
2262
2321
|
configDebug: ctx.configDebug ?? null,
|
|
2322
|
+
webSearchSummary: session?.webSearchSummary,
|
|
2323
|
+
columns: terminal.columns,
|
|
2324
|
+
sessionId: scopeRef.current?.sessionId,
|
|
2263
2325
|
};
|
|
2264
2326
|
// `/resume`: open the picker with this folder's recent conversations.
|
|
2265
2327
|
const doResume = useCallback(async () => {
|
|
@@ -2304,6 +2366,19 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2304
2366
|
setState('thinking');
|
|
2305
2367
|
resolve(decision);
|
|
2306
2368
|
}
|
|
2369
|
+
if (decision.kind === 'approve-all') {
|
|
2370
|
+
for (const pending of permissionQueueRef.current.splice(0))
|
|
2371
|
+
pending.resolve(decision);
|
|
2372
|
+
}
|
|
2373
|
+
const next = permissionQueueRef.current.shift();
|
|
2374
|
+
setQueuedPermissionCount(permissionQueueRef.current.length);
|
|
2375
|
+
if (next) {
|
|
2376
|
+
permissionResolveRef.current = next.resolve;
|
|
2377
|
+
permissionOpenedAtRef.current = Date.now();
|
|
2378
|
+
setPermissionDetailsOpen(!next.permission.summary.complete);
|
|
2379
|
+
setPermission(next.permission);
|
|
2380
|
+
setState('awaiting-permission');
|
|
2381
|
+
}
|
|
2307
2382
|
}, [setPermissionDetailsOpen, setPermissionReviewOffset]);
|
|
2308
2383
|
/**
|
|
2309
2384
|
* Stop the running turn, and hand the screen back in a usable state.
|
|
@@ -2325,6 +2400,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2325
2400
|
*/
|
|
2326
2401
|
const interruptTurn = useCallback(() => {
|
|
2327
2402
|
const cancelledSwitch = cancelPendingModelSwitch();
|
|
2403
|
+
for (const pending of permissionQueueRef.current.splice(0)) {
|
|
2404
|
+
pending.resolve({ kind: 'reject', feedback: 'User interrupted.' });
|
|
2405
|
+
}
|
|
2406
|
+
setQueuedPermissionCount(0);
|
|
2328
2407
|
if (permissionResolveRef.current)
|
|
2329
2408
|
resolvePermission({ kind: 'reject', feedback: 'User interrupted.' });
|
|
2330
2409
|
const ac = abortRef.current;
|
|
@@ -2445,6 +2524,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2445
2524
|
lastCompletedOutputRef.current = persistedOutput
|
|
2446
2525
|
? { text: persistedOutput, provenance: 'persisted' }
|
|
2447
2526
|
: null;
|
|
2527
|
+
pendingGoalResumeRef.current = conv.id;
|
|
2448
2528
|
scope.sessionId = conv.id; // new turns now attribute to the resumed session
|
|
2449
2529
|
conversationMaterializedRef.current = true;
|
|
2450
2530
|
pushMessage('system', `Resumed: ${conv.title}`);
|
|
@@ -2859,6 +2939,14 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2859
2939
|
}
|
|
2860
2940
|
const summary = buildPermissionSummary(review.text);
|
|
2861
2941
|
return new Promise((resolve) => {
|
|
2942
|
+
if (permissionResolveRef.current) {
|
|
2943
|
+
permissionQueueRef.current.push({
|
|
2944
|
+
permission: { ...req, review: review.text, summary },
|
|
2945
|
+
resolve,
|
|
2946
|
+
});
|
|
2947
|
+
setQueuedPermissionCount(permissionQueueRef.current.length);
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2862
2950
|
permissionResolveRef.current = resolve;
|
|
2863
2951
|
permissionOpenedAtRef.current = Date.now();
|
|
2864
2952
|
setPermissionReviewOffset(0);
|
|
@@ -2937,6 +3025,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2937
3025
|
}
|
|
2938
3026
|
switch (event.kind) {
|
|
2939
3027
|
case 'delta': {
|
|
3028
|
+
if (event.messageId && st.sourceMessageId !== event.messageId) {
|
|
3029
|
+
if (st.sourceMessageId !== undefined)
|
|
3030
|
+
closeAssistant();
|
|
3031
|
+
st.sourceMessageId = event.messageId;
|
|
3032
|
+
}
|
|
2940
3033
|
setState('thinking');
|
|
2941
3034
|
if (event.messageId && event.runId && st.sessionId) {
|
|
2942
3035
|
lastAssistantMessage.current = {
|
|
@@ -2981,11 +3074,19 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2981
3074
|
closeAssistant();
|
|
2982
3075
|
setThinking(null);
|
|
2983
3076
|
setState('tool');
|
|
3077
|
+
const waitingAgent = event.toolName === 'wait_for_task' && event.taskId
|
|
3078
|
+
? subagentsRef.current.find((agent) => agent.taskId === event.taskId &&
|
|
3079
|
+
(event.runId === undefined || agent.workflowId === event.runId))
|
|
3080
|
+
: undefined;
|
|
2984
3081
|
const tool = {
|
|
3082
|
+
...(waitingAgent ? { taskId: waitingAgent.taskId } : {}),
|
|
2985
3083
|
id: event.toolUseId,
|
|
2986
3084
|
...(event.runId ? { runId: event.runId } : {}),
|
|
2987
3085
|
toolName: event.toolName,
|
|
2988
|
-
|
|
3086
|
+
activity: event.activity,
|
|
3087
|
+
label: waitingAgent
|
|
3088
|
+
? `Waiting · ${waitingAgent.description}`
|
|
3089
|
+
: formatToolCall(event.toolName, event.summary, event.standalone),
|
|
2989
3090
|
startedAt: Date.now(),
|
|
2990
3091
|
detail: event.detail,
|
|
2991
3092
|
};
|
|
@@ -3029,6 +3130,15 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3029
3130
|
activeToolsRef.current = [...running.slice(0, i), ...running.slice(i + 1)];
|
|
3030
3131
|
setActiveTools(activeToolsRef.current);
|
|
3031
3132
|
}
|
|
3133
|
+
// Lifecycle updates own completion rows, even when the model never waits.
|
|
3134
|
+
// Keep unknown waits and errors visible; never hide an unrepresented task.
|
|
3135
|
+
const inspectableWait = done?.taskId && subagentsRef.current.some((agent) => agent.taskId === done.taskId &&
|
|
3136
|
+
(event.runId === undefined || agent.workflowId === event.runId) &&
|
|
3137
|
+
agent.transcript.some((row) => row.kind === 'assistant' && row.text.trim().length > 0));
|
|
3138
|
+
if (!event.isError && inspectableWait && event.toolName === 'wait_for_task') {
|
|
3139
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3140
|
+
break;
|
|
3141
|
+
}
|
|
3032
3142
|
const representedSuccessfulAgent = !event.isError &&
|
|
3033
3143
|
event.toolName.toLowerCase() === 'agent' &&
|
|
3034
3144
|
subagentsRef.current.some((agent) => agent.toolUseId === event.toolUseId &&
|
|
@@ -3041,6 +3151,22 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3041
3151
|
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3042
3152
|
break;
|
|
3043
3153
|
}
|
|
3154
|
+
if (!event.isError && done?.activity === 'exploration') {
|
|
3155
|
+
// Preserve the result, including the summary removed from detail by the adapter.
|
|
3156
|
+
const output = event.output !== undefined
|
|
3157
|
+
? event.output.split('\n')
|
|
3158
|
+
: [...(event.summary ? [event.summary] : []), ...(event.detail ?? [])];
|
|
3159
|
+
pushMessage('tool', done.label, false, '└', output, theme.text.muted, output.length > 0 ? 'ctrl+o output' : undefined, 'exploration');
|
|
3160
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3161
|
+
break;
|
|
3162
|
+
}
|
|
3163
|
+
const catalogue = !event.isError && event.toolName === 'agent_models' && event.output !== undefined
|
|
3164
|
+
? modelCatalogueView(event.output) : undefined;
|
|
3165
|
+
if (catalogue !== undefined) {
|
|
3166
|
+
pushMessage('tool', catalogue, false, '✓', event.output?.split('\n'), theme.status.ok, 'ctrl+o details', 'catalogue');
|
|
3167
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3168
|
+
break;
|
|
3169
|
+
}
|
|
3044
3170
|
pushMessage('tool', done?.label ?? formatToolCall(event.toolName, event.summary), false, event.isError ? '✗' : '✓', done?.detail, event.isError ? theme.status.error : theme.status.ok, event.durationMs !== undefined
|
|
3045
3171
|
? formatElapsed(event.durationMs)
|
|
3046
3172
|
: done
|
|
@@ -3636,6 +3762,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3636
3762
|
abortRef.current = null;
|
|
3637
3763
|
if (ownsTurn)
|
|
3638
3764
|
activeTurnTokenRef.current = null;
|
|
3765
|
+
permissionResolveRef.current?.({ kind: 'reject', feedback: 'The owning turn ended.' });
|
|
3766
|
+
for (const pending of permissionQueueRef.current.splice(0))
|
|
3767
|
+
pending.resolve({ kind: 'reject', feedback: 'The owning turn ended.' });
|
|
3768
|
+
setQueuedPermissionCount(0);
|
|
3639
3769
|
permissionResolveRef.current = null;
|
|
3640
3770
|
permissionOpenedAtRef.current = null;
|
|
3641
3771
|
setPermission(null);
|
|
@@ -3959,7 +4089,16 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3959
4089
|
if (slash) {
|
|
3960
4090
|
switch (slash.kind) {
|
|
3961
4091
|
case 'message':
|
|
3962
|
-
|
|
4092
|
+
if (slash.statusRows) {
|
|
4093
|
+
setMessages((previous) => [...previous, {
|
|
4094
|
+
id: nextId(),
|
|
4095
|
+
role: slash.role,
|
|
4096
|
+
content: slash.content,
|
|
4097
|
+
statusRows: slash.statusRows,
|
|
4098
|
+
}]);
|
|
4099
|
+
}
|
|
4100
|
+
else
|
|
4101
|
+
pushMessage(slash.role, slash.content);
|
|
3963
4102
|
return;
|
|
3964
4103
|
case 'command-picker': {
|
|
3965
4104
|
if (slash.commands.length === 0) {
|
|
@@ -3989,6 +4128,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3989
4128
|
}
|
|
3990
4129
|
return;
|
|
3991
4130
|
}
|
|
4131
|
+
case 'provider-setup':
|
|
4132
|
+
if (state !== 'idle' || permission || choicePickerRef.current) {
|
|
4133
|
+
pushMessage('system', 'Provider setup is available once the active turn and prompts finish.');
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
4136
|
+
setProviderSetup(true);
|
|
4137
|
+
return;
|
|
3992
4138
|
case 'settings-picker': {
|
|
3993
4139
|
const commands = [
|
|
3994
4140
|
{
|
|
@@ -4010,16 +4156,22 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4010
4156
|
description: `${permissionModeLabel(effectivePermissionMode(permissionModeRef.current, session?.approvalLatched() ?? false))} · this session.`,
|
|
4011
4157
|
},
|
|
4012
4158
|
{
|
|
4013
|
-
name: 'status
|
|
4014
|
-
label: '
|
|
4159
|
+
name: 'status',
|
|
4160
|
+
label: 'Web & session',
|
|
4161
|
+
description: `${session?.webSearchSummary ?? 'Not resolved'} · view tools, workspace and usage.`,
|
|
4162
|
+
},
|
|
4163
|
+
{ name: 'setup', label: 'Provider setup', description: 'Check installed CLIs and access; connect or install without leaving Namzu.' },
|
|
4164
|
+
{
|
|
4165
|
+
name: 'config sources',
|
|
4166
|
+
label: 'Setting sources',
|
|
4015
4167
|
description: 'Show which configuration files and overrides are in use.',
|
|
4016
4168
|
},
|
|
4017
4169
|
];
|
|
4018
4170
|
setSelectedChoice(0);
|
|
4019
4171
|
setChoicePicker({
|
|
4020
4172
|
kind: 'command',
|
|
4021
|
-
title: '
|
|
4022
|
-
notice: '
|
|
4173
|
+
title: 'Configuration',
|
|
4174
|
+
notice: 'Model, reasoning and permission changes apply to this session.',
|
|
4023
4175
|
values: commands,
|
|
4024
4176
|
options: commands.map((command) => ({
|
|
4025
4177
|
label: command.label,
|
|
@@ -4779,6 +4931,19 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4779
4931
|
return;
|
|
4780
4932
|
}
|
|
4781
4933
|
setGoalStatus(goal);
|
|
4934
|
+
if (pendingGoalResumeRef.current === sessionId && phase === 'ready' && !choicePickerRef.current) {
|
|
4935
|
+
pendingGoalResumeRef.current = null;
|
|
4936
|
+
if (goal && (goal.phase === 'active' || goal.phase === 'paused')) {
|
|
4937
|
+
setSelectedChoice(0);
|
|
4938
|
+
setChoicePicker({ kind: 'resume-goal', title: 'Continue the saved goal?',
|
|
4939
|
+
notice: goal.objective, sessionId, values: ['resume', 'later'],
|
|
4940
|
+
options: [
|
|
4941
|
+
{ label: 'Resume goal', description: 'Continue automatic work toward this objective.' },
|
|
4942
|
+
{ label: 'Not now', description: 'Keep automatic work stopped; resume later with /goal resume.' },
|
|
4943
|
+
],
|
|
4944
|
+
});
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4782
4947
|
})
|
|
4783
4948
|
.catch(() => {
|
|
4784
4949
|
if (!disposed && conversationGenRef.current === generation)
|
|
@@ -4787,7 +4952,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4787
4952
|
return () => {
|
|
4788
4953
|
disposed = true;
|
|
4789
4954
|
};
|
|
4790
|
-
}, [goalDriveVersion, phase, session]);
|
|
4955
|
+
}, [goalDriveVersion, phase, session, setChoicePicker]);
|
|
4791
4956
|
// Admit automatic work only at a whole-App durable boundary. This effect
|
|
4792
4957
|
// reserves; it never starts a turn itself. The one queue pump below remains
|
|
4793
4958
|
// the sole starter, which is what keeps a human prompt that arrives during
|
|
@@ -5049,6 +5214,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5049
5214
|
exitWithSummary();
|
|
5050
5215
|
}, [session, exitWithSummary]);
|
|
5051
5216
|
useInput((input, key) => {
|
|
5217
|
+
if (outputViewer !== null)
|
|
5218
|
+
return;
|
|
5052
5219
|
// Startup has refused admission, so there is no draft or active turn to
|
|
5053
5220
|
// protect with the ready screen's two-press exit ladder.
|
|
5054
5221
|
if (phase === 'unhealthy') {
|
|
@@ -5169,9 +5336,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5169
5336
|
if (permission && (key.pageUp || key.pageDown || key.home || key.end)) {
|
|
5170
5337
|
const source = permissionDetailsOpenRef.current
|
|
5171
5338
|
? permission.review
|
|
5172
|
-
: permission.summary.text;
|
|
5339
|
+
: (permission.summary.compactText ?? permission.summary.text);
|
|
5173
5340
|
const count = permissionReviewRows(source, terminal.columns).length;
|
|
5174
|
-
const pageRows = permissionReviewPageRows(terminal.rows);
|
|
5341
|
+
const pageRows = Math.max(1, permissionReviewPageRows(terminal.rows) - (permission.runId ? 1 : 0));
|
|
5175
5342
|
const maxOffset = Math.max(0, count - pageRows);
|
|
5176
5343
|
const current = Math.min(permissionReviewOffsetRef.current, maxOffset);
|
|
5177
5344
|
const next = key.home
|
|
@@ -5187,8 +5354,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5187
5354
|
// Refusing is never deferred or gated — it is the direction a
|
|
5188
5355
|
// mistake is recoverable in, so it answers on the first press.
|
|
5189
5356
|
if (key.ctrl && input === 'c') {
|
|
5190
|
-
|
|
5191
|
-
abortRef.current?.abort(new RunCancelled('user'));
|
|
5357
|
+
interruptTurn();
|
|
5192
5358
|
return;
|
|
5193
5359
|
}
|
|
5194
5360
|
if (ch === 'n' || key.escape) {
|
|
@@ -5508,64 +5674,27 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5508
5674
|
pushMessage('system', 'Interrupted.');
|
|
5509
5675
|
return;
|
|
5510
5676
|
}
|
|
5511
|
-
//
|
|
5512
|
-
//
|
|
5513
|
-
// It was advertised — on every collapsed body — as toggling full
|
|
5514
|
-
// expansion for everything, and in that use it did nothing: finalized
|
|
5515
|
-
// rows went through `<Static>`, which renders `items.slice(index)` and
|
|
5516
|
-
// calls the render function only for items it has not emitted yet, so
|
|
5517
|
-
// output already on screen was beyond its reach. Measured, with a
|
|
5518
|
-
// twelve-line body up: pressing it produced one further frame whose
|
|
5519
|
-
// transcript region was byte-identical to the one before.
|
|
5520
|
-
//
|
|
5521
|
-
// The rows at the end of the transcript are now drawn live rather than
|
|
5522
|
-
// printed once, so for those the key does what it always claimed —
|
|
5523
|
-
// flipping the flag re-renders the row where it already is. It reaches
|
|
5524
|
-
// exactly as far back as the window does, and says so when that is
|
|
5525
|
-
// nowhere. It does not quietly fall back to appending a copy: `/expand`
|
|
5526
|
-
// is that, deliberately and visibly, and a key that sometimes redraws
|
|
5527
|
-
// in place and sometimes prints a second copy further down would be two
|
|
5528
|
-
// commands wearing one name.
|
|
5677
|
+
// Small expansions stay in place. Older or oversized bodies open a viewer.
|
|
5529
5678
|
if (key.ctrl && input === 'o') {
|
|
5530
|
-
|
|
5531
|
-
const
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
];
|
|
5550
|
-
}
|
|
5551
|
-
return [
|
|
5552
|
-
...prev,
|
|
5553
|
-
{
|
|
5554
|
-
id,
|
|
5555
|
-
role: 'system',
|
|
5556
|
-
content: `${block.content} — in full (${block.detail?.length ?? 0} lines)`,
|
|
5557
|
-
glyph: '⤢',
|
|
5558
|
-
detail: block.detail,
|
|
5559
|
-
detailExpanded: true,
|
|
5560
|
-
},
|
|
5561
|
-
];
|
|
5562
|
-
}
|
|
5563
|
-
// Expanded unless they are all open already, in which case this
|
|
5564
|
-
// closes them again — the toggle it was always described as.
|
|
5565
|
-
const expanding = collapsible.some((m) => m.detailExpanded !== true);
|
|
5566
|
-
const ids = new Set(collapsible.map((m) => m.id));
|
|
5567
|
-
return prev.map((m) => (ids.has(m.id) ? { ...m, detailExpanded: expanding } : m));
|
|
5568
|
-
});
|
|
5679
|
+
const live = messages.filter((m) => !m.pending).slice(settledRef.current);
|
|
5680
|
+
const collapsible = live.filter((m) => (m.activity && (m.detail?.length ?? 0) > 0) || willCollapse(m.detail));
|
|
5681
|
+
const blocks = messages.filter((m) => m.detailRef !== undefined && (m.detail?.length ?? 0) > 0);
|
|
5682
|
+
const block = collapsible.at(-1) ?? blocks.at(-1);
|
|
5683
|
+
if (!block) {
|
|
5684
|
+
pushMessage('system', 'Nothing to expand yet. Ctrl+O opens retained tool output when available.');
|
|
5685
|
+
return;
|
|
5686
|
+
}
|
|
5687
|
+
const expanding = collapsible.some((m) => m.detailExpanded !== true);
|
|
5688
|
+
const ids = new Set(collapsible.map((m) => m.id));
|
|
5689
|
+
const proposed = messages.map((m) => ids.has(m.id) ? { ...m, detailExpanded: expanding } : m);
|
|
5690
|
+
const projected = liveWindow({ messages: proposed.filter((m) => !m.pending), rows: terminal.rows,
|
|
5691
|
+
columns: terminal.columns, furnitureRows: LIVE_FURNITURE_ROWS + taskFurniture + toolFurniture,
|
|
5692
|
+
settled: settledRef.current, raw: rawOutput });
|
|
5693
|
+
if (collapsible.length === 0 || (expanding && projected.settled > settledRef.current)) {
|
|
5694
|
+
setOutputViewer(block);
|
|
5695
|
+
}
|
|
5696
|
+
else
|
|
5697
|
+
setMessages(proposed);
|
|
5569
5698
|
return;
|
|
5570
5699
|
}
|
|
5571
5700
|
if (key.ctrl && input === 'c') {
|
|
@@ -5590,7 +5719,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5590
5719
|
// Active on every phase. It was gated off during the picker, which is what
|
|
5591
5720
|
// left that screen without a usable exit; the picker branch above returns
|
|
5592
5721
|
// immediately, so the Picker still owns everything except Ctrl+C.
|
|
5593
|
-
{ isActive: externalEditorRequest === null });
|
|
5722
|
+
{ isActive: externalEditorRequest === null && !providerSetup });
|
|
5594
5723
|
// Background is left natural — we inherit the terminal's own background
|
|
5595
5724
|
// and only theme the foreground. Forcing
|
|
5596
5725
|
// a filled bg left mismatched patches around bordered areas, so we don't.
|
|
@@ -5607,10 +5736,15 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5607
5736
|
!(tool.runId
|
|
5608
5737
|
? representedSubagentTools.has(JSON.stringify([tool.runId, tool.id]))
|
|
5609
5738
|
: representedUnscopedSubagentToolUseIds.has(tool.id)));
|
|
5739
|
+
const outputBlocks = outputViewer
|
|
5740
|
+
? messages.filter((m) => m.detailRef !== undefined && (m.detail?.length ?? 0) > 0)
|
|
5741
|
+
: [];
|
|
5742
|
+
const outputIndex = outputBlocks.findIndex((m) => m.id === outputViewer?.id);
|
|
5610
5743
|
const selectedSubagent = agentSurface
|
|
5611
5744
|
? subagents.find((agent) => agent.viewId === agentSurface.selectedId)
|
|
5612
5745
|
: undefined;
|
|
5613
|
-
const lifecycleOwnsViewport =
|
|
5746
|
+
const lifecycleOwnsViewport = outputViewer !== null ||
|
|
5747
|
+
phase === 'trust' ||
|
|
5614
5748
|
phase === 'resume' ||
|
|
5615
5749
|
phase === 'edit' ||
|
|
5616
5750
|
phase === 'picker' ||
|
|
@@ -5624,7 +5758,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5624
5758
|
choicePicker === null &&
|
|
5625
5759
|
copyPicker === null &&
|
|
5626
5760
|
agentSurface === null
|
|
5627
|
-
? goalStatusLabel(goalStatus)
|
|
5761
|
+
? goalStatusLabel(goalStatus, goalStatus ? goalActivation.isArmed(goalStatus.sessionId, goalStatus) : false)
|
|
5628
5762
|
: null;
|
|
5629
5763
|
const statusHint = conversationMutation === 'fork'
|
|
5630
5764
|
? 'forking conversation — input is paused'
|
|
@@ -5657,8 +5791,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5657
5791
|
: goalStatus && phase === 'ready' && state === 'idle'
|
|
5658
5792
|
? undefined
|
|
5659
5793
|
: hintForPhase(phase, state, session?.hasProvider === true, composerHasDraft);
|
|
5794
|
+
const permissionOwner = permission?.runId
|
|
5795
|
+
? subagents.find((agent) => agent.runId === permission.runId)
|
|
5796
|
+
: undefined;
|
|
5797
|
+
const permissionSourceLabel = permission?.runId
|
|
5798
|
+
? permissionOwner ? `Agent: ${permissionOwner.description}` : `Run: ${permission.runId}`
|
|
5799
|
+
: undefined;
|
|
5660
5800
|
const displayedPermissionMode = effectivePermissionMode(permissionMode, session?.approvalLatched() ?? false);
|
|
5661
|
-
return (_jsx(Box, { flexDirection: "column", 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, state: state, settled: transcriptOwned ? renderedSettled : 0, resetKey: resetKey, raw: rawOutput, hyperlinks: hyperlinks, showLive: !lifecycleOwnsViewport, header: transcriptOwned ? (_jsx(BrandHeader, { version: ctx.version, permissionMode: displayedPermissionMode })) : undefined }) }),
|
|
5801
|
+
return (_jsx(Box, { flexDirection: "column", 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, state: state, settled: transcriptOwned ? renderedSettled : 0, resetKey: resetKey, raw: rawOutput, hyperlinks: hyperlinks, showLive: !lifecycleOwnsViewport, header: transcriptOwned ? (_jsx(BrandHeader, { version: ctx.version, permissionMode: displayedPermissionMode })) : undefined }) }), providerSetup ? (_jsx(ProviderSetup, { cwd: ctx.cwd, onClose: () => setProviderSetup(false), onConnect: () => {
|
|
5802
|
+
setProviderSetup(false);
|
|
5803
|
+
setPickerInitialView('providers');
|
|
5804
|
+
setPickerDetected(null);
|
|
5805
|
+
setPickerSelectionKind('provider-and-model');
|
|
5806
|
+
setPhase('picker');
|
|
5807
|
+
void discoverProviders({ skipProbes: true }).then(setPickerDetected).catch((error) => setPickerNotice(String(error)));
|
|
5808
|
+
} })) : phase === 'trust' ? (_jsx(TrustPrompt, { cwd: ctx.cwd })) : phase === 'unhealthy' ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: theme.status.error, bold: true, children: "Startup stopped" }), _jsx(Text, { color: theme.text.secondary, children: "Resolve the error above, then restart Namzu." })] })) : phase === 'resume' ? (_jsx(ResumePicker, { conversations: resumeList, selected: selectedResume })) : phase === 'edit' ? (_jsx(EditPromptPicker, { prompts: editList, selected: selectedEdit })) : phase === 'picker' ? (_jsx(Picker, { detected: pickerDetected ?? detected, currentProvider: currentProvider, selectionKind: pickerSelectionKind, currentModel: session?.modelSummary ?? null, initialView: pickerInitialView, onSubmit: handlePickerSubmit, onCancel: handlePickerCancel, onSetup: () => setProviderSetup(true), onCredential: handleTypedCredential, onLogin: startLoginFromPicker, onLoginComplete: finishLoginFromPicker, keyEntryFor: keyEntryFor, notice: pickerNotice })) : (_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, thinking: thinking })) : null, agentSurface === null && outputViewer === null && permission === null ? (_jsx(TaskList, { tasks: tasks, compact: compactWork })) : 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, { 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' &&
|
|
5662
5809
|
state !== 'awaiting-permission' &&
|
|
5663
5810
|
!compacting &&
|
|
5664
5811
|
externalEditorRequest === null &&
|
|
@@ -5666,41 +5813,48 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5666
5813
|
textPrompt === null &&
|
|
5667
5814
|
choicePicker === null &&
|
|
5668
5815
|
copyPicker === null &&
|
|
5669
|
-
agentSurface === null, hidden: permission !== null ||
|
|
5816
|
+
agentSurface === null && outputViewer === null, hidden: permission !== null ||
|
|
5670
5817
|
textPrompt !== null ||
|
|
5671
5818
|
choicePicker !== null ||
|
|
5672
5819
|
copyPicker !== null ||
|
|
5673
|
-
agentSurface
|
|
5820
|
+
(agentSurface !== null || outputViewer !== null), children: [pendingSteers.length > 0 &&
|
|
5674
5821
|
permission === null &&
|
|
5675
5822
|
textPrompt === null &&
|
|
5676
5823
|
choicePicker === null &&
|
|
5677
5824
|
copyPicker === null &&
|
|
5678
|
-
agentSurface === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.accent.user, children: ["\u21B3 ", pendingSteers.length, " message", pendingSteers.length > 1 ? 's' : '', " steering the active turn \u2014 waiting for its next response boundary"] }) })) : null, queued.length > 0 &&
|
|
5825
|
+
agentSurface === null && outputViewer === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.accent.user, children: ["\u21B3 ", pendingSteers.length, " message", pendingSteers.length > 1 ? 's' : '', " steering the active turn \u2014 waiting for its next response boundary"] }) })) : null, queued.length > 0 &&
|
|
5679
5826
|
permission === null &&
|
|
5680
5827
|
textPrompt === null &&
|
|
5681
5828
|
choicePicker === null &&
|
|
5682
5829
|
copyPicker === null &&
|
|
5683
|
-
agentSurface === null ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: [queuePause ? '⏸' : '⏎', " ", queued.length, " message", queued.length > 1 ? 's' : '', " queued \u2014", ' ', queuePause
|
|
5830
|
+
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
|
|
5684
5831
|
? queuePause.outcome === 'paused'
|
|
5685
5832
|
? 'held after a resumable run paused; wait for recovery, change model, or send a message to release it'
|
|
5686
5833
|
: `paused after a ${queuePause.outcome} turn; send a message or change model to continue`
|
|
5687
|
-
: 'sending when ready'] }) })) : null, _jsx(Composer, { reasoningEffortLevels: session?.reasoningEffortLevels, permissionMode: displayedPermissionMode, onCycleMode: cyclePermissionMode, disabled:
|
|
5834
|
+
: 'sending when ready'] }) })) : null, _jsx(Composer, { reasoningEffortLevels: session?.reasoningEffortLevels, permissionMode: displayedPermissionMode, onCycleMode: cyclePermissionMode, disabled: outputViewer !== null ||
|
|
5835
|
+
phase !== 'ready' ||
|
|
5688
5836
|
state === 'awaiting-permission' ||
|
|
5689
5837
|
compacting ||
|
|
5690
5838
|
conversationMutation !== null ||
|
|
5691
5839
|
externalEditorRequest !== null ||
|
|
5692
|
-
agentSurface !== null, hidden: permission !== null ||
|
|
5840
|
+
(agentSurface !== null || outputViewer !== null), hidden: permission !== null ||
|
|
5693
5841
|
textPrompt !== null ||
|
|
5694
5842
|
choicePicker !== null ||
|
|
5695
5843
|
copyPicker !== null ||
|
|
5696
|
-
agentSurface
|
|
5844
|
+
(agentSurface !== null || outputViewer !== null),
|
|
5697
5845
|
// A turn is running, so Esc is the interrupt and not
|
|
5698
5846
|
// the composer's clear.
|
|
5699
5847
|
escapeInterrupts: !compacting &&
|
|
5700
5848
|
(state === 'thinking' || state === 'tool' || pendingModelSwitchRef.current !== null), onSubmit: handleSubmit, onNotice: (text) => pushMessage('system', text), onStepReasoningEffort: stepReasoningEffort, onExternalEdit: requestExternalEditor, onEditPrevious: openPromptEditor, draftToRestore: composerDraft, onDraftRestored: (token) => setComposerDraft((draft) => (draft?.token === token ? null : draft)), onDraftPresenceChange: setComposerHasDraft, onOpenAgentPanel: openAgentCockpit, userCommands: userCommands, builtins: hostCommands.map((command) => ({
|
|
5701
5849
|
...command,
|
|
5702
5850
|
description: command.unavailable?.(slashCtx) ?? command.description,
|
|
5703
|
-
})), mentionCandidates: mentionCandidates, history: history })] }), permission === null && agentSurface === null && liveSubagents.length > 0 ? (_jsx(AgentTaskPanel, { agents: liveSubagents, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : permission === null && agentSurface?.kind === 'cockpit' ? (_jsx(AgentCockpit, { agents: subagents, selectedPhaseId: agentSurface.selectedPhaseId, selectedId: agentSurface.selectedId, focus: agentSurface.focus, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : permission === null && agentSurface?.kind === 'transcript' && selectedSubagent ? (_jsx(AgentTranscript, { agent: selectedSubagent, tailOffset: agentSurface.tailOffset, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : null] })),
|
|
5851
|
+
})), mentionCandidates: mentionCandidates, history: history })] }), permission === null && agentSurface === null && outputViewer === null && liveSubagents.length > 0 ? (_jsx(AgentTaskPanel, { agents: liveSubagents, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : permission === null && agentSurface?.kind === 'cockpit' ? (_jsx(AgentCockpit, { agents: subagents, selectedPhaseId: agentSurface.selectedPhaseId, selectedId: agentSurface.selectedId, focus: agentSurface.focus, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : permission === null && agentSurface?.kind === 'transcript' && selectedSubagent ? (_jsx(AgentTranscript, { agent: selectedSubagent, tailOffset: agentSurface.tailOffset, terminalRows: terminal.rows, terminalColumns: terminal.columns })) : null] })), outputViewer && permission === null ? (_jsx(ToolOutputViewer, { onPrevious: () => {
|
|
5852
|
+
if (outputIndex > 0)
|
|
5853
|
+
setOutputViewer(outputBlocks[outputIndex - 1]);
|
|
5854
|
+
}, onNext: () => {
|
|
5855
|
+
if (outputIndex >= 0 && outputIndex + 1 < outputBlocks.length)
|
|
5856
|
+
setOutputViewer(outputBlocks[outputIndex + 1]);
|
|
5857
|
+
}, title: outputViewer.content, lines: outputViewer.detail ?? [], rows: terminal.rows, columns: terminal.columns, onClose: () => setOutputViewer(null) }, outputViewer.id)) : null, _jsx(Box, { paddingTop: 1, children: _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, effort: reasoningEffort ?? 'default', goal: statusGoal, state: state, hint: statusHint }) })] }) }));
|
|
5704
5858
|
}
|
|
5705
5859
|
/** One line for a background job that ended, for the transcript and the next turn. */
|
|
5706
5860
|
function describeJobExit(job) {
|
|
@@ -5759,12 +5913,12 @@ hasDraft = false) {
|
|
|
5759
5913
|
// screen; only state-specific actions belong in the footer.
|
|
5760
5914
|
return '';
|
|
5761
5915
|
}
|
|
5762
|
-
function goalStatusLabel(goal) {
|
|
5916
|
+
function goalStatusLabel(goal, armed) {
|
|
5763
5917
|
if (!goal)
|
|
5764
5918
|
return null;
|
|
5765
5919
|
switch (goal.phase) {
|
|
5766
5920
|
case 'active':
|
|
5767
|
-
return 'Pursuing goal';
|
|
5921
|
+
return armed ? 'Pursuing goal' : 'Goal paused (/goal resume)';
|
|
5768
5922
|
case 'paused':
|
|
5769
5923
|
return 'Goal paused (/goal resume)';
|
|
5770
5924
|
case 'blocked':
|