@namzu/cli 16.0.0 → 16.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/README.md +20 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +43 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/state.d.ts +9 -0
- package/dist/commands/state.d.ts.map +1 -0
- package/dist/commands/state.js +110 -0
- package/dist/commands/state.js.map +1 -0
- package/dist/integrations/sessions/store.d.ts.map +1 -1
- package/dist/integrations/sessions/store.js +25 -2
- package/dist/integrations/sessions/store.js.map +1 -1
- package/dist/integrations/state/private-directory.d.ts +15 -0
- package/dist/integrations/state/private-directory.d.ts.map +1 -0
- package/dist/integrations/state/private-directory.js +41 -0
- package/dist/integrations/state/private-directory.js.map +1 -0
- package/dist/integrations/state/report.d.ts +125 -0
- package/dist/integrations/state/report.d.ts.map +1 -0
- package/dist/integrations/state/report.js +784 -0
- package/dist/integrations/state/report.js.map +1 -0
- package/dist/integrations/subagents/activity.d.ts +18 -0
- package/dist/integrations/subagents/activity.d.ts.map +1 -1
- package/dist/integrations/subagents/activity.js +64 -7
- package/dist/integrations/subagents/activity.js.map +1 -1
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +45 -7
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/tui/AgentExplorer.d.ts +26 -5
- package/dist/tui/AgentExplorer.d.ts.map +1 -1
- package/dist/tui/AgentExplorer.js +125 -14
- package/dist/tui/AgentExplorer.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +231 -65
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +18 -2
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +1 -1
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +2 -2
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/user-commands/store.d.ts.map +1 -1
- package/dist/user-commands/store.js +26 -5
- package/dist/user-commands/store.js.map +1 -1
- package/package.json +2 -2
package/dist/tui/App.js
CHANGED
|
@@ -12,7 +12,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
12
12
|
* automatic reuse is not mistaken for a durable operator preference.
|
|
13
13
|
*/
|
|
14
14
|
import { join, relative } from 'node:path';
|
|
15
|
-
import { DiskMessageFeedbackStore, GoalRoundLimitError, HostCommandRegistry, RunCancelled, SessionGoalActivation, StaleGoalError, asSessionId, createAssistantMessage, createUserMessage, generateRunId, isCompactionMessage, kernelHostCommands, } from '@namzu/sdk';
|
|
15
|
+
import { DefaultPathBuilder, DiskMessageFeedbackStore, GoalRoundLimitError, HostCommandRegistry, RunCancelled, SessionGoalActivation, StaleGoalError, asSessionId, createAssistantMessage, createUserMessage, generateRunId, generateSessionId, isCompactionMessage, kernelHostCommands, } from '@namzu/sdk';
|
|
16
16
|
import { Box, Text, useApp, useInput, useStdout, useWindowSize } from 'ink';
|
|
17
17
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
18
18
|
import { pinTrustedProjectPath, resolveTrustedProjectContext, } from '../config/trusted-project-context.js';
|
|
@@ -28,7 +28,7 @@ import { checkUpdates } from '../integrations/updates.js';
|
|
|
28
28
|
import { appendMemory, composeMemoryPrompt, readMemory } from '../memory/store.js';
|
|
29
29
|
import { composeSkillsPrompt, discoverSkills, loadSkillBody } from '../skills/store.js';
|
|
30
30
|
import { discoverUserCommands } from '../user-commands/store.js';
|
|
31
|
-
import {
|
|
31
|
+
import { AgentCockpit, AgentTranscript, agentCockpitIsWide, agentPickerPageSize, agentPhasePageSize, agentPhases, agentTranscriptPageSize, maxAgentTranscriptTailOffset, } from './AgentExplorer.js';
|
|
32
32
|
import { ChoicePicker } from './ChoicePicker.js';
|
|
33
33
|
import { Composer, suggestionWindowSize, } from './Composer.js';
|
|
34
34
|
import { CopyPicker } from './CopyPicker.js';
|
|
@@ -462,9 +462,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
462
462
|
subagentsRef.current = next;
|
|
463
463
|
setSubagentsState(next);
|
|
464
464
|
const surface = agentSurfaceRef.current;
|
|
465
|
-
if (surface
|
|
465
|
+
if (!surface || next.some((agent) => agent.viewId === surface.selectedId))
|
|
466
|
+
return;
|
|
467
|
+
const phases = agentPhases(next);
|
|
468
|
+
const fallbackPhase = phases.find((phase) => phase.id === surface.selectedPhaseId) ?? phases[0];
|
|
469
|
+
const fallbackAgent = fallbackPhase?.agents[0];
|
|
470
|
+
if (!fallbackPhase || !fallbackAgent) {
|
|
466
471
|
setAgentSurface(null);
|
|
472
|
+
return;
|
|
467
473
|
}
|
|
474
|
+
setAgentSurface({
|
|
475
|
+
kind: 'cockpit',
|
|
476
|
+
selectedPhaseId: fallbackPhase.id,
|
|
477
|
+
selectedId: fallbackAgent.viewId,
|
|
478
|
+
focus: surface.kind === 'cockpit' ? surface.focus : surface.returnFocus,
|
|
479
|
+
});
|
|
468
480
|
}, [setAgentSurface]);
|
|
469
481
|
/** One git-backed review choice may be resolving while its visible picker remains authoritative. */
|
|
470
482
|
const reviewChoiceInFlightRef = useRef(null);
|
|
@@ -551,9 +563,19 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
551
563
|
// turns attribute to the resumed conversation.
|
|
552
564
|
const sessionsRef = useRef(null);
|
|
553
565
|
const scopeRef = useRef(null);
|
|
566
|
+
/**
|
|
567
|
+
* A provider session can be ready before a person has started a conversation.
|
|
568
|
+
* The scope still needs a mutable session cursor, but its generated id is
|
|
569
|
+
* provisional until the first admitted prompt or an explicit conversation
|
|
570
|
+
* operation publishes the durable Session.
|
|
571
|
+
*/
|
|
572
|
+
const conversationMaterializedRef = useRef(false);
|
|
573
|
+
const materializationRef = useRef(null);
|
|
554
574
|
const exitWithSummary = useCallback(() => {
|
|
555
575
|
onExitSummary?.({
|
|
556
|
-
...(
|
|
576
|
+
...(conversationMaterializedRef.current && scopeRef.current?.sessionId
|
|
577
|
+
? { conversationId: scopeRef.current.sessionId }
|
|
578
|
+
: {}),
|
|
557
579
|
});
|
|
558
580
|
exit();
|
|
559
581
|
}, [exit, onExitSummary]);
|
|
@@ -830,12 +852,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
830
852
|
}
|
|
831
853
|
applyReasoningEffort(next);
|
|
832
854
|
}, [applyReasoningEffort, pushMessage, session]);
|
|
833
|
-
const recordFeedback = useCallback((runIdValue, messageIdValue, rating, note) => {
|
|
834
|
-
|
|
835
|
-
|
|
855
|
+
const recordFeedback = useCallback((sessionId, runIdValue, messageIdValue, rating, note) => {
|
|
856
|
+
const sessions = sessionsRef.current;
|
|
857
|
+
if (!sessions) {
|
|
858
|
+
pushMessage('system', 'Could not record feedback: conversation persistence is unavailable.');
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
// Run evidence is session-scoped in the canonical store. The old flat
|
|
862
|
+
// `<cwd>/.namzu/runs` lookup never existed after the hierarchy migration,
|
|
863
|
+
// so the feedback store correctly refused every TUI rating as unverifiable.
|
|
864
|
+
// Bind both trees to the exact conversation captured with the streamed
|
|
865
|
+
// message; `/resume` may change the active scope before a delayed click.
|
|
866
|
+
const sessionDir = new DefaultPathBuilder(sessions.root).sessionDir(sessions.projectId, sessionId);
|
|
836
867
|
const store = new DiskMessageFeedbackStore({
|
|
837
|
-
rootDir: join(
|
|
838
|
-
runsDir: join(
|
|
868
|
+
rootDir: join(sessionDir, 'feedback'),
|
|
869
|
+
runsDir: join(sessionDir, 'runs'),
|
|
839
870
|
});
|
|
840
871
|
const runId = runIdValue;
|
|
841
872
|
const messageId = messageIdValue;
|
|
@@ -857,7 +888,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
857
888
|
pushMessage('system', `Could not record feedback: ${error instanceof Error ? error.message : String(error)}`);
|
|
858
889
|
}
|
|
859
890
|
})();
|
|
860
|
-
}, [
|
|
891
|
+
}, [pushMessage]);
|
|
861
892
|
const activateSkill = useCallback((name) => {
|
|
862
893
|
const info = discoverSkills({ cwd: ctx.cwd }).find((skill) => skill.name === name);
|
|
863
894
|
if (!info) {
|
|
@@ -904,8 +935,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
904
935
|
return;
|
|
905
936
|
const sessions = sessionsRef.current;
|
|
906
937
|
const sessionId = scopeRef.current?.sessionId;
|
|
907
|
-
if (!sessions || !sessionId) {
|
|
908
|
-
pushMessage('system',
|
|
938
|
+
if (!sessions || !sessionId || !conversationMaterializedRef.current) {
|
|
939
|
+
pushMessage('system', !conversationMaterializedRef.current
|
|
940
|
+
? 'There is no conversation to archive yet — send a message first.'
|
|
941
|
+
: 'Cannot archive this conversation because durable session persistence is unavailable.');
|
|
909
942
|
return;
|
|
910
943
|
}
|
|
911
944
|
if (state !== 'idle' ||
|
|
@@ -956,8 +989,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
956
989
|
const stableExportSource = useCallback(() => {
|
|
957
990
|
const sessions = sessionsRef.current;
|
|
958
991
|
const sessionId = scopeRef.current?.sessionId;
|
|
959
|
-
if (!sessions || !sessionId) {
|
|
960
|
-
pushMessage('system',
|
|
992
|
+
if (!sessions || !sessionId || !conversationMaterializedRef.current) {
|
|
993
|
+
pushMessage('system', !conversationMaterializedRef.current
|
|
994
|
+
? 'There is no conversation to export yet — send a message first.'
|
|
995
|
+
: 'Cannot export this conversation because durable session persistence is unavailable.');
|
|
961
996
|
return null;
|
|
962
997
|
}
|
|
963
998
|
if (abortRef.current ||
|
|
@@ -1204,7 +1239,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1204
1239
|
return;
|
|
1205
1240
|
}
|
|
1206
1241
|
if (picker.kind === 'feedback-rating') {
|
|
1207
|
-
recordFeedback(picker.runId, picker.messageId, value);
|
|
1242
|
+
recordFeedback(picker.sessionId, picker.runId, picker.messageId, value);
|
|
1208
1243
|
return;
|
|
1209
1244
|
}
|
|
1210
1245
|
if (picker.kind === 'skill') {
|
|
@@ -1323,9 +1358,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1323
1358
|
? { text: persistedOutput, provenance: 'persisted' }
|
|
1324
1359
|
: null;
|
|
1325
1360
|
initialConversationIdRef.current = undefined;
|
|
1361
|
+
conversationMaterializedRef.current = true;
|
|
1326
1362
|
}
|
|
1327
1363
|
else {
|
|
1328
|
-
|
|
1364
|
+
// Provider/tool startup needs stable project/tenant/topic ids, not a
|
|
1365
|
+
// durable conversation. The cursor is replaced on first admitted use.
|
|
1366
|
+
sessionId = generateSessionId();
|
|
1329
1367
|
}
|
|
1330
1368
|
sessionsRef.current = sessions;
|
|
1331
1369
|
scopeRef.current = {
|
|
@@ -1342,6 +1380,45 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1342
1380
|
return undefined;
|
|
1343
1381
|
}
|
|
1344
1382
|
}, [nextId]);
|
|
1383
|
+
/** Publish the provisional conversation exactly once, at first durable use. */
|
|
1384
|
+
const materializeConversation = useCallback(async () => {
|
|
1385
|
+
const existing = await ensureSessions();
|
|
1386
|
+
if (!existing)
|
|
1387
|
+
return undefined;
|
|
1388
|
+
if (conversationMaterializedRef.current)
|
|
1389
|
+
return existing;
|
|
1390
|
+
if (materializationRef.current)
|
|
1391
|
+
return materializationRef.current;
|
|
1392
|
+
const pending = (async () => {
|
|
1393
|
+
const sessions = sessionsRef.current;
|
|
1394
|
+
const scope = scopeRef.current;
|
|
1395
|
+
if (!sessions || !scope)
|
|
1396
|
+
return undefined;
|
|
1397
|
+
conversationMutationRef.current = 'materialize';
|
|
1398
|
+
try {
|
|
1399
|
+
const sessionId = await startConversation(sessions);
|
|
1400
|
+
if (scopeRef.current !== scope || conversationMaterializedRef.current) {
|
|
1401
|
+
throw new Error('the active conversation changed while its first session was published');
|
|
1402
|
+
}
|
|
1403
|
+
scope.sessionId = sessionId;
|
|
1404
|
+
conversationMaterializedRef.current = true;
|
|
1405
|
+
return scope;
|
|
1406
|
+
}
|
|
1407
|
+
finally {
|
|
1408
|
+
if (conversationMutationRef.current === 'materialize') {
|
|
1409
|
+
conversationMutationRef.current = null;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
})();
|
|
1413
|
+
materializationRef.current = pending;
|
|
1414
|
+
try {
|
|
1415
|
+
return await pending;
|
|
1416
|
+
}
|
|
1417
|
+
finally {
|
|
1418
|
+
if (materializationRef.current === pending)
|
|
1419
|
+
materializationRef.current = null;
|
|
1420
|
+
}
|
|
1421
|
+
}, [ensureSessions]);
|
|
1345
1422
|
const hydrateSession = useCallback(async (prefs, detectedNow, signal) => {
|
|
1346
1423
|
if (signal?.aborted)
|
|
1347
1424
|
return;
|
|
@@ -1985,11 +2062,16 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1985
2062
|
setMessages(restored);
|
|
1986
2063
|
modelHistoryRef.current = msgs;
|
|
1987
2064
|
setHistory((previous) => [...previous, ...promptHistoryFromConversation(msgs)]);
|
|
2065
|
+
// Persisted conversation rows do not carry the live run/message identity
|
|
2066
|
+
// required by feedback validation. Do not leave the answer from the
|
|
2067
|
+
// conversation we just exited as a hidden `/feedback` target.
|
|
2068
|
+
lastAssistantMessage.current = null;
|
|
1988
2069
|
const persistedOutput = latestAssistantOutput(msgs);
|
|
1989
2070
|
lastCompletedOutputRef.current = persistedOutput
|
|
1990
2071
|
? { text: persistedOutput, provenance: 'persisted' }
|
|
1991
2072
|
: null;
|
|
1992
2073
|
scope.sessionId = conv.id; // new turns now attribute to the resumed session
|
|
2074
|
+
conversationMaterializedRef.current = true;
|
|
1993
2075
|
pushMessage('system', `Resumed: ${conv.title}`);
|
|
1994
2076
|
if (discardedQueued > 0) {
|
|
1995
2077
|
pushMessage('system', `Discarded ${discardedQueued} queued prompt${discardedQueued === 1 ? '' : 's'} from the conversation you left.`);
|
|
@@ -2053,8 +2135,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2053
2135
|
modelHistoryRef.current = [];
|
|
2054
2136
|
lastAssistantMessage.current = null;
|
|
2055
2137
|
lastCompletedOutputRef.current = null;
|
|
2056
|
-
if (sourceScope && targetSessionId)
|
|
2138
|
+
if (sourceScope && targetSessionId) {
|
|
2057
2139
|
sourceScope.sessionId = targetSessionId;
|
|
2140
|
+
conversationMaterializedRef.current = true;
|
|
2141
|
+
}
|
|
2058
2142
|
if (clearScreen) {
|
|
2059
2143
|
setMessages([]);
|
|
2060
2144
|
resetTranscript();
|
|
@@ -2098,8 +2182,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2098
2182
|
* history; it is a session-store operation with the session id captured here.
|
|
2099
2183
|
*/
|
|
2100
2184
|
const doTitle = useCallback(async (title, clear) => {
|
|
2101
|
-
const
|
|
2102
|
-
const
|
|
2185
|
+
const scope = await materializeConversation();
|
|
2186
|
+
const sessions = sessionsRef.current;
|
|
2103
2187
|
if (!sessions || !scope) {
|
|
2104
2188
|
pushMessage('system', 'Conversation history is unavailable in this folder.');
|
|
2105
2189
|
return;
|
|
@@ -2127,7 +2211,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2127
2211
|
catch (err) {
|
|
2128
2212
|
pushMessage('system', `Could not save the name: ${err instanceof Error ? err.message : String(err)}`);
|
|
2129
2213
|
}
|
|
2130
|
-
}, [
|
|
2214
|
+
}, [materializeConversation, pushMessage, setTextPrompt]);
|
|
2131
2215
|
const submitTextPrompt = useCallback((value) => {
|
|
2132
2216
|
const prompt = textPromptRef.current;
|
|
2133
2217
|
if (!prompt)
|
|
@@ -2195,8 +2279,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2195
2279
|
conversationMutationRef.current = 'fork';
|
|
2196
2280
|
setConversationMutation('fork');
|
|
2197
2281
|
try {
|
|
2198
|
-
const
|
|
2199
|
-
const
|
|
2282
|
+
const scope = await materializeConversation();
|
|
2283
|
+
const sessions = sessionsRef.current;
|
|
2200
2284
|
if (!sessions || !scope) {
|
|
2201
2285
|
pushMessage('system', 'Conversation history is unavailable in this folder.');
|
|
2202
2286
|
return;
|
|
@@ -2223,9 +2307,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2223
2307
|
setConversationMutation(null);
|
|
2224
2308
|
}
|
|
2225
2309
|
}, [
|
|
2226
|
-
ensureSessions,
|
|
2227
2310
|
goalActivation,
|
|
2228
2311
|
hasUnsettledTurn,
|
|
2312
|
+
materializeConversation,
|
|
2229
2313
|
pushMessage,
|
|
2230
2314
|
resetSubagentActivity,
|
|
2231
2315
|
wakeGoalDriver,
|
|
@@ -2374,10 +2458,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2374
2458
|
switch (event.kind) {
|
|
2375
2459
|
case 'delta': {
|
|
2376
2460
|
setState('thinking');
|
|
2377
|
-
if (event.messageId && event.runId) {
|
|
2461
|
+
if (event.messageId && event.runId && st.sessionId) {
|
|
2378
2462
|
lastAssistantMessage.current = {
|
|
2379
2463
|
runId: event.runId,
|
|
2380
2464
|
messageId: event.messageId,
|
|
2465
|
+
sessionId: st.sessionId,
|
|
2381
2466
|
};
|
|
2382
2467
|
}
|
|
2383
2468
|
st.text += event.text;
|
|
@@ -2533,13 +2618,22 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2533
2618
|
// state idle while the admission read was pending let a second submit
|
|
2534
2619
|
// start beside it and broke the queue's FIFO ownership.
|
|
2535
2620
|
setState('thinking');
|
|
2621
|
+
let durableScope;
|
|
2622
|
+
try {
|
|
2623
|
+
durableScope = await materializeConversation();
|
|
2624
|
+
}
|
|
2625
|
+
catch (error) {
|
|
2626
|
+
pushMessage('system', `Conversation persistence could not start: ${error instanceof Error ? error.message : String(error)}. This turn will continue in memory and cannot be resumed.`);
|
|
2627
|
+
}
|
|
2536
2628
|
// A conversation can be closed outside this App after it was opened or
|
|
2537
2629
|
// resumed. Re-check at the actual turn boundary, before expanding file
|
|
2538
2630
|
// mentions, recording run evidence, or creating a provider iterator.
|
|
2539
2631
|
// The persistence helpers repeat the same check at their own boundary;
|
|
2540
2632
|
// neither read is claimed to serialize a concurrent archive (that needs
|
|
2541
2633
|
// a store-level lease shared with ProjectManager).
|
|
2542
|
-
const destination =
|
|
2634
|
+
const destination = conversationMaterializedRef.current
|
|
2635
|
+
? (durableScope?.sessionId ?? scopeRef.current?.sessionId ?? null)
|
|
2636
|
+
: null;
|
|
2543
2637
|
const turnSessions = sessionsRef.current;
|
|
2544
2638
|
if (turnSessions && destination) {
|
|
2545
2639
|
try {
|
|
@@ -2591,6 +2685,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2591
2685
|
pending: '',
|
|
2592
2686
|
completed: false,
|
|
2593
2687
|
outcome: null,
|
|
2688
|
+
sessionId: destination,
|
|
2594
2689
|
notification: null,
|
|
2595
2690
|
};
|
|
2596
2691
|
const ac = new AbortController();
|
|
@@ -2917,6 +3012,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2917
3012
|
finalizeMessage,
|
|
2918
3013
|
flushStream,
|
|
2919
3014
|
goalActivation,
|
|
3015
|
+
materializeConversation,
|
|
2920
3016
|
onPermission,
|
|
2921
3017
|
pushMessage,
|
|
2922
3018
|
replaceQueued,
|
|
@@ -2941,7 +3037,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2941
3037
|
? 'branched for prompt editing'
|
|
2942
3038
|
: conversationMutationRef.current === 'archive'
|
|
2943
3039
|
? 'archived'
|
|
2944
|
-
:
|
|
3040
|
+
: conversationMutationRef.current === 'materialize'
|
|
3041
|
+
? 'initialized'
|
|
3042
|
+
: 'moved to a fresh conversation';
|
|
2945
3043
|
pushMessage('system', `Conversation history is being ${operation}. Wait for it to finish before sending another command or prompt.`);
|
|
2946
3044
|
return;
|
|
2947
3045
|
}
|
|
@@ -2978,15 +3076,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2978
3076
|
});
|
|
2979
3077
|
return;
|
|
2980
3078
|
}
|
|
2981
|
-
case 'agent-
|
|
3079
|
+
case 'agent-cockpit': {
|
|
2982
3080
|
const agents = subagentsRef.current;
|
|
2983
3081
|
if (agents.length === 0) {
|
|
2984
3082
|
pushMessage('system', 'No delegated agents are available in this conversation yet. When an Agent tool starts one, /agent opens its live activity and retained transcript.');
|
|
2985
3083
|
return;
|
|
2986
3084
|
}
|
|
3085
|
+
const firstPhase = agentPhases(agents)[0];
|
|
3086
|
+
const firstAgent = firstPhase?.agents[0];
|
|
3087
|
+
if (!firstPhase || !firstAgent)
|
|
3088
|
+
return;
|
|
2987
3089
|
setAgentSurface({
|
|
2988
|
-
kind: '
|
|
2989
|
-
|
|
3090
|
+
kind: 'cockpit',
|
|
3091
|
+
selectedPhaseId: firstPhase.id,
|
|
3092
|
+
selectedId: firstAgent.viewId,
|
|
3093
|
+
focus: 'agents',
|
|
2990
3094
|
});
|
|
2991
3095
|
return;
|
|
2992
3096
|
}
|
|
@@ -3324,29 +3428,46 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3324
3428
|
// THIS session can answer from. The descriptors used for
|
|
3325
3429
|
// the merge above carry no store — they are names — so the
|
|
3326
3430
|
// registry is rebuilt here with the live one.
|
|
3327
|
-
const durableSessions = sessionsRef.current;
|
|
3328
|
-
const runScope = scopeRef.current;
|
|
3329
3431
|
const generation = conversationGenRef.current;
|
|
3330
3432
|
const goalCommand = slash.name === 'goal';
|
|
3331
3433
|
if (goalCommand)
|
|
3332
3434
|
goalCommandInFlightRef.current = true;
|
|
3333
|
-
const registry = new HostCommandRegistry();
|
|
3334
|
-
registry.register(kernelHostCommands({
|
|
3335
|
-
allowedAgentIds: session?.agentIds ?? [],
|
|
3336
|
-
...(durableSessions && runScope
|
|
3337
|
-
? {
|
|
3338
|
-
goal: {
|
|
3339
|
-
store: durableSessions.goals,
|
|
3340
|
-
sessionId: runScope.sessionId,
|
|
3341
|
-
tenantId: durableSessions.tenantId,
|
|
3342
|
-
activation: goalActivation,
|
|
3343
|
-
},
|
|
3344
|
-
}
|
|
3345
|
-
: {}),
|
|
3346
|
-
}));
|
|
3347
3435
|
void (async () => {
|
|
3436
|
+
let goalCommandReleased = false;
|
|
3437
|
+
const releaseGoalCommand = () => {
|
|
3438
|
+
if (!goalCommand || goalCommandReleased)
|
|
3439
|
+
return;
|
|
3440
|
+
goalCommandReleased = true;
|
|
3441
|
+
goalCommandInFlightRef.current = false;
|
|
3442
|
+
wakeGoalDriver();
|
|
3443
|
+
};
|
|
3348
3444
|
try {
|
|
3445
|
+
if (goalCommand)
|
|
3446
|
+
await materializeConversation();
|
|
3447
|
+
const durableSessions = sessionsRef.current;
|
|
3448
|
+
const runScope = scopeRef.current;
|
|
3449
|
+
const registry = new HostCommandRegistry();
|
|
3450
|
+
registry.register(kernelHostCommands({
|
|
3451
|
+
allowedAgentIds: session?.agentIds ?? [],
|
|
3452
|
+
...(durableSessions && runScope && conversationMaterializedRef.current
|
|
3453
|
+
? {
|
|
3454
|
+
goal: {
|
|
3455
|
+
store: durableSessions.goals,
|
|
3456
|
+
sessionId: runScope.sessionId,
|
|
3457
|
+
tenantId: durableSessions.tenantId,
|
|
3458
|
+
activation: goalActivation,
|
|
3459
|
+
},
|
|
3460
|
+
}
|
|
3461
|
+
: {}),
|
|
3462
|
+
}));
|
|
3349
3463
|
const outcome = await registry.dispatch(`/${slash.name} ${slash.args.join(' ')}`.trim());
|
|
3464
|
+
// The dispatch result is the durable boundary. Release the input
|
|
3465
|
+
// gate before publishing that result: once the operator can see a
|
|
3466
|
+
// completed `/goal` row, the next goal command must be admissible.
|
|
3467
|
+
// Keeping the gate until `finally` left one render-sized window in
|
|
3468
|
+
// which a visible success was followed by "still reaching durable
|
|
3469
|
+
// state" when the operator immediately typed `/goal resume`.
|
|
3470
|
+
releaseGoalCommand();
|
|
3350
3471
|
// A late readout from the conversation just left must not be
|
|
3351
3472
|
// painted as state of the one now on screen.
|
|
3352
3473
|
if (conversationGenRef.current !== generation)
|
|
@@ -3361,10 +3482,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3361
3482
|
pushMessage('system', `Could not run /${slash.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3362
3483
|
}
|
|
3363
3484
|
finally {
|
|
3364
|
-
|
|
3365
|
-
goalCommandInFlightRef.current = false;
|
|
3366
|
-
wakeGoalDriver();
|
|
3367
|
-
}
|
|
3485
|
+
releaseGoalCommand();
|
|
3368
3486
|
}
|
|
3369
3487
|
})();
|
|
3370
3488
|
return;
|
|
@@ -3382,6 +3500,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3382
3500
|
title: 'Rate the latest answer',
|
|
3383
3501
|
runId: target.runId,
|
|
3384
3502
|
messageId: target.messageId,
|
|
3503
|
+
sessionId: target.sessionId,
|
|
3385
3504
|
values,
|
|
3386
3505
|
options: [
|
|
3387
3506
|
{
|
|
@@ -3399,7 +3518,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3399
3518
|
pushMessage('system', 'Nothing to rate yet.');
|
|
3400
3519
|
return;
|
|
3401
3520
|
}
|
|
3402
|
-
recordFeedback(target.runId, slash.messageId, slash.rating, slash.note);
|
|
3521
|
+
recordFeedback(target.sessionId, target.runId, slash.messageId, slash.rating, slash.note);
|
|
3403
3522
|
return;
|
|
3404
3523
|
}
|
|
3405
3524
|
case 'review': {
|
|
@@ -4115,21 +4234,42 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4115
4234
|
if (agentSurfaceCommittedRef.current !== agentView)
|
|
4116
4235
|
return;
|
|
4117
4236
|
const agents = subagentsRef.current;
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4237
|
+
if (agentView.kind === 'cockpit') {
|
|
4238
|
+
const phases = agentPhases(agents);
|
|
4239
|
+
const phaseIndex = Math.max(0, phases.findIndex((phase) => phase.id === agentView.selectedPhaseId));
|
|
4240
|
+
const phase = phases[phaseIndex];
|
|
4241
|
+
const phaseAgents = phase?.agents ?? [];
|
|
4242
|
+
const agentIndex = Math.max(0, phaseAgents.findIndex((agent) => agent.viewId === agentView.selectedId));
|
|
4243
|
+
const selected = phaseAgents[agentIndex];
|
|
4244
|
+
if (!phase || !selected) {
|
|
4245
|
+
setAgentSurface(null);
|
|
4246
|
+
return;
|
|
4247
|
+
}
|
|
4125
4248
|
if (key.escape || (key.ctrl && input === 'c') || input.toLowerCase() === 'q') {
|
|
4126
4249
|
setAgentSurface(null);
|
|
4127
4250
|
return;
|
|
4128
4251
|
}
|
|
4252
|
+
if (key.leftArrow || key.rightArrow || key.tab) {
|
|
4253
|
+
const focus = key.leftArrow
|
|
4254
|
+
? 'phases'
|
|
4255
|
+
: key.rightArrow
|
|
4256
|
+
? 'agents'
|
|
4257
|
+
: agentView.focus === 'phases'
|
|
4258
|
+
? 'agents'
|
|
4259
|
+
: 'phases';
|
|
4260
|
+
setAgentSurface({ ...agentView, focus });
|
|
4261
|
+
return;
|
|
4262
|
+
}
|
|
4129
4263
|
if (key.return) {
|
|
4264
|
+
if (agentView.focus === 'phases') {
|
|
4265
|
+
setAgentSurface({ ...agentView, focus: 'agents' });
|
|
4266
|
+
return;
|
|
4267
|
+
}
|
|
4130
4268
|
setAgentSurface({
|
|
4131
4269
|
kind: 'transcript',
|
|
4270
|
+
selectedPhaseId: phase.id,
|
|
4132
4271
|
selectedId: selected.viewId,
|
|
4272
|
+
returnFocus: agentView.focus,
|
|
4133
4273
|
tailOffset: 0,
|
|
4134
4274
|
});
|
|
4135
4275
|
return;
|
|
@@ -4146,19 +4286,43 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4146
4286
|
: key.upArrow
|
|
4147
4287
|
? 'previous'
|
|
4148
4288
|
: 'next';
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4289
|
+
if (agentView.focus === 'phases') {
|
|
4290
|
+
const next = moveSelection(phaseIndex, phases.length, movement, agentPhasePageSize(terminal.rows, agentCockpitIsWide(terminal.columns)));
|
|
4291
|
+
const targetPhase = phases[next];
|
|
4292
|
+
const targetAgent = targetPhase?.agents[0];
|
|
4293
|
+
if (targetPhase && targetAgent) {
|
|
4294
|
+
setAgentSurface({
|
|
4295
|
+
...agentView,
|
|
4296
|
+
selectedPhaseId: targetPhase.id,
|
|
4297
|
+
selectedId: targetAgent.viewId,
|
|
4298
|
+
});
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
else {
|
|
4302
|
+
const next = moveSelection(agentIndex, phaseAgents.length, movement, agentPickerPageSize(terminal.rows, agentCockpitIsWide(terminal.columns)));
|
|
4303
|
+
const target = phaseAgents[next];
|
|
4304
|
+
if (target)
|
|
4305
|
+
setAgentSurface({ ...agentView, selectedId: target.viewId });
|
|
4306
|
+
}
|
|
4153
4307
|
}
|
|
4154
4308
|
return;
|
|
4155
4309
|
}
|
|
4310
|
+
const selected = agents.find((agent) => agent.viewId === agentView.selectedId);
|
|
4311
|
+
if (!selected) {
|
|
4312
|
+
setAgentSurface(null);
|
|
4313
|
+
return;
|
|
4314
|
+
}
|
|
4156
4315
|
if (input.toLowerCase() === 'q' || (key.ctrl && input === 'c')) {
|
|
4157
4316
|
setAgentSurface(null);
|
|
4158
4317
|
return;
|
|
4159
4318
|
}
|
|
4160
4319
|
if (key.escape) {
|
|
4161
|
-
setAgentSurface({
|
|
4320
|
+
setAgentSurface({
|
|
4321
|
+
kind: 'cockpit',
|
|
4322
|
+
selectedPhaseId: agentView.selectedPhaseId,
|
|
4323
|
+
selectedId: selected.viewId,
|
|
4324
|
+
focus: agentView.returnFocus,
|
|
4325
|
+
});
|
|
4162
4326
|
return;
|
|
4163
4327
|
}
|
|
4164
4328
|
if (key.home || key.end || key.pageUp || key.pageDown || key.upArrow || key.downArrow) {
|
|
@@ -4383,8 +4547,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4383
4547
|
? 'compacting conversation — input is paused'
|
|
4384
4548
|
: permission
|
|
4385
4549
|
? hintForPhase(phase, state, session?.hasProvider === true)
|
|
4386
|
-
: agentSurface?.kind === '
|
|
4387
|
-
?
|
|
4550
|
+
: agentSurface?.kind === 'cockpit'
|
|
4551
|
+
? agentSurface.focus === 'phases'
|
|
4552
|
+
? 'agent phases — enter agents · esc return'
|
|
4553
|
+
: 'agents — enter inspect · left phases · esc return'
|
|
4388
4554
|
: agentSurface?.kind === 'transcript'
|
|
4389
4555
|
? 'observing agent — esc agents · q parent'
|
|
4390
4556
|
: textPrompt
|
|
@@ -4396,7 +4562,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4396
4562
|
: goalStatus && phase === 'ready' && state === 'idle'
|
|
4397
4563
|
? undefined
|
|
4398
4564
|
: hintForPhase(phase, state, session?.hasProvider === true, composerHasDraft);
|
|
4399
|
-
return (_jsx(Box, { flexDirection: "column", display: externalEditorRequest ? 'none' : 'flex', children: _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [transcriptOwned || !lifecycleOwnsViewport ? (_jsx(TranscriptFrame, { children: _jsx(Transcript, { messages: finalized, pending: messages.find((m) => m.pending) ?? null, state: state, settled: renderedSettled, resetKey: resetKey, raw: rawOutput, hyperlinks: hyperlinks, showLive: !lifecycleOwnsViewport, header: transcriptOwned ? (_jsx(Banner, { version: ctx.version, session: session, permissionMode: permissionMode, cwd: ctx.cwd })) : undefined }) })) : null, phase === 'trust' ? (_jsx(TrustPrompt, { cwd: ctx.cwd })) : 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, onCredential: handleTypedCredential, onLogin: startLoginFromPicker, onLoginComplete: finishLoginFromPicker, keyEntryFor: keyEntryFor, notice: pickerNotice })) : (_jsxs(_Fragment, { children: [agentSurface === null ? (_jsx(LiveActivity, { activeTools: activeTools, working: state === 'thinking' || state === 'tool', agentCount: liveSubagentCount, interruptible: abortRef.current !== null, animate: stdout.isTTY === true })) : null, permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls, review: permission.review, summary: permission.summary, detailsOpen: permissionDetailsOpen, reviewOffset: permissionReviewOffset, columns: terminal.columns })) : null, permission === null && agentSurface?.kind === '
|
|
4565
|
+
return (_jsx(Box, { flexDirection: "column", display: externalEditorRequest ? 'none' : 'flex', children: _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [transcriptOwned || !lifecycleOwnsViewport ? (_jsx(TranscriptFrame, { children: _jsx(Transcript, { messages: finalized, pending: messages.find((m) => m.pending) ?? null, state: state, settled: renderedSettled, resetKey: resetKey, raw: rawOutput, hyperlinks: hyperlinks, showLive: !lifecycleOwnsViewport, header: transcriptOwned ? (_jsx(Banner, { version: ctx.version, session: session, permissionMode: permissionMode, cwd: ctx.cwd })) : undefined }) })) : null, phase === 'trust' ? (_jsx(TrustPrompt, { cwd: ctx.cwd })) : 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, onCredential: handleTypedCredential, onLogin: startLoginFromPicker, onLoginComplete: finishLoginFromPicker, keyEntryFor: keyEntryFor, notice: pickerNotice })) : (_jsxs(_Fragment, { children: [agentSurface === null ? (_jsx(LiveActivity, { activeTools: activeTools, working: state === 'thinking' || state === 'tool', agentCount: liveSubagentCount, interruptible: abortRef.current !== null, animate: stdout.isTTY === true })) : null, permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls, review: permission.review, summary: permission.summary, detailsOpen: permissionDetailsOpen, reviewOffset: permissionReviewOffset, columns: terminal.columns })) : null, 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, textPrompt ? (_jsx(TextPrompt, { title: textPrompt.title, placeholder: textPrompt.placeholder, initialValue: textPrompt.initialValue, emptyNotice: textPrompt.emptyNotice, hidden: permission !== null || agentSurface !== null, onSubmit: submitTextPrompt, onCancel: cancelTextPrompt }, textPrompt.token)) : permission === null && agentSurface === null && choicePicker ? (_jsx(ChoicePicker, { title: choicePicker.title, notice: choicePicker.notice, options: choicePicker.options, selected: selectedChoice, windowSize: choicePicker.kind === 'command' ? choicePicker.windowSize : undefined })) : permission === null && agentSurface === null && copyPicker ? (_jsx(CopyPicker, { targets: copyPicker.targets, selected: selectedCopy })) : null, _jsxs(ComposerFrame, { focus: state === 'idle' &&
|
|
4400
4566
|
phase === 'ready' &&
|
|
4401
4567
|
conversationMutation === null &&
|
|
4402
4568
|
textPrompt === null &&
|