mixdog 0.9.53 → 0.9.55
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/package.json +2 -1
- package/scripts/agent-model-liveness-test.mjs +68 -0
- package/scripts/anthropic-transport-policy-test.mjs +260 -0
- package/scripts/desktop-session-bridge-test.mjs +47 -0
- package/scripts/gemini-provider-test.mjs +396 -1
- package/scripts/grok-oauth-refresh-race-test.mjs +76 -1
- package/scripts/interrupted-turn-history-test.mjs +28 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +81 -1
- package/scripts/pending-completion-drop-test.mjs +160 -4
- package/scripts/pending-messages-lock-nonblocking-test.mjs +62 -0
- package/scripts/process-lifecycle-test.mjs +18 -4
- package/scripts/prompt-input-parity-test.mjs +145 -0
- package/scripts/provider-admission-scheduler-test.mjs +4 -5
- package/scripts/provider-contract-test.mjs +91 -33
- package/scripts/provider-toolcall-test.mjs +97 -17
- package/scripts/streaming-tail-window-test.mjs +146 -0
- package/scripts/tui-runtime-load-bench-entry.jsx +608 -0
- package/scripts/tui-runtime-load-bench.mjs +45 -0
- package/scripts/tui-store-frame-batch-test.mjs +99 -0
- package/scripts/tui-transcript-perf-test.mjs +367 -2
- package/scripts/write-backpressure-test.mjs +147 -0
- package/src/cli.mjs +5 -5
- package/src/lib/keychain-cjs.cjs +114 -25
- package/src/repl.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +62 -25
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +50 -23
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +15 -4
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +136 -27
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +104 -20
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +96 -75
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +40 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +35 -4
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +49 -9
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +14 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +9 -4
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +53 -13
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +145 -23
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +20 -4
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +316 -63
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/store.mjs +46 -1
- package/src/runtime/agent/orchestrator/stall-policy.mjs +6 -13
- package/src/runtime/memory/lib/trace-store.mjs +66 -4
- package/src/runtime/shared/buffered-appender.mjs +83 -4
- package/src/runtime/shared/memory-snapshot.mjs +45 -36
- package/src/runtime/shared/process-lifecycle.mjs +166 -45
- package/src/runtime/shared/process-shutdown.mjs +2 -1
- package/src/session-runtime/model-route-api.mjs +4 -1
- package/src/session-runtime/native-search.mjs +4 -2
- package/src/session-runtime/provider-auth-api.mjs +8 -1
- package/src/session-runtime/provider-models.mjs +8 -3
- package/src/session-runtime/resource-api.mjs +2 -1
- package/src/session-runtime/runtime-core.mjs +32 -0
- package/src/session-runtime/session-turn-api.mjs +1 -0
- package/src/session-runtime/warmup-schedulers.mjs +5 -1
- package/src/standalone/agent-tool.mjs +19 -1
- package/src/tui/App.jsx +10 -4
- package/src/tui/app/text-layout.mjs +9 -1
- package/src/tui/app/transcript-window.mjs +146 -60
- package/src/tui/app/use-mouse-input.mjs +16 -8
- package/src/tui/app/use-transcript-scroll.mjs +71 -19
- package/src/tui/app/use-transcript-window.mjs +21 -10
- package/src/tui/components/PromptInput.jsx +13 -6
- package/src/tui/dist/index.mjs +1094 -232
- package/src/tui/engine/context-state.mjs +31 -31
- package/src/tui/engine/frame-batched-store.mjs +75 -0
- package/src/tui/engine/session-api-ext.mjs +6 -1
- package/src/tui/engine/session-api.mjs +9 -3
- package/src/tui/engine/session-flow.mjs +54 -27
- package/src/tui/engine/turn.mjs +44 -3
- package/src/tui/engine.mjs +515 -36
- package/src/tui/input-editing.mjs +33 -13
- package/src/tui/markdown/measure-rendered-rows.mjs +117 -5
- package/vendor/ink/build/ink.js +8 -16
|
@@ -2,14 +2,10 @@
|
|
|
2
2
|
* src/tui/engine/context-state.mjs - route/context/agent-status derivations.
|
|
3
3
|
*
|
|
4
4
|
* Extracted from engine.mjs unchanged. These read the live runtime + store
|
|
5
|
-
* snapshot
|
|
6
|
-
*
|
|
7
|
-
* immutable-emit contract is preserved by their callers, which follow up with
|
|
8
|
-
* a set({ stats: { ...state.stats }, ...routeState() }). getState() must return
|
|
9
|
-
* the live (latest) store object; getPendingSessionReset() gates the stats
|
|
10
|
-
* sync exactly as the old inline `pendingSessionReset` closure did.
|
|
5
|
+
* snapshot. The two sync helpers stage immutable draft patches through
|
|
6
|
+
* updateState; callers still follow with set(...) to schedule publication.
|
|
11
7
|
*/
|
|
12
|
-
export function createContextState({ runtime, getState, getPendingSessionReset }) {
|
|
8
|
+
export function createContextState({ runtime, getState, updateState, getPendingSessionReset }) {
|
|
13
9
|
const autoClearState = () => runtime.getAutoClear?.() || runtime.autoClear || { enabled: true, idleMs: 60 * 60 * 1000, custom: false, providerDefault: 60 * 60 * 1000, provider: null, minContextPercent: 10 };
|
|
14
10
|
const AGENT_STATUS_CACHE_MS = 250;
|
|
15
11
|
let agentStatusCache = null;
|
|
@@ -68,7 +64,6 @@ export function createContextState({ runtime, getState, getPendingSessionReset }
|
|
|
68
64
|
function syncContextDisplayFields(ctx = null) {
|
|
69
65
|
const status = ctx || runtime.contextStatus?.() || null;
|
|
70
66
|
if (!status) return;
|
|
71
|
-
const state = getState();
|
|
72
67
|
const displayWindow = Number(status.contextWindow || 0);
|
|
73
68
|
const compactBoundary = Number(status.compaction?.boundaryTokens || 0);
|
|
74
69
|
// Prefer the resolved trigger (boundary - buffer): the statusline uses it
|
|
@@ -80,9 +75,11 @@ export function createContextState({ runtime, getState, getPendingSessionReset }
|
|
|
80
75
|
|| runtime.session?.autoCompactTokenLimit
|
|
81
76
|
|| 0,
|
|
82
77
|
);
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
if (
|
|
78
|
+
const patch = {};
|
|
79
|
+
if (displayWindow > 0) patch.displayContextWindow = displayWindow;
|
|
80
|
+
if (compactBoundary > 0) patch.compactBoundaryTokens = compactBoundary;
|
|
81
|
+
if (autoCompact > 0) patch.autoCompactTokenLimit = autoCompact;
|
|
82
|
+
if (Object.keys(patch).length > 0) updateState(patch);
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
const syncContextStats = ({ allowEstimated = false } = {}) => {
|
|
@@ -91,17 +88,19 @@ export function createContextState({ runtime, getState, getPendingSessionReset }
|
|
|
91
88
|
if (!ctx) return null;
|
|
92
89
|
syncContextDisplayFields(ctx);
|
|
93
90
|
const state = getState();
|
|
94
|
-
const
|
|
91
|
+
const stats = { ...state.stats };
|
|
92
|
+
const hasProviderUsage = Number(stats.latestPromptTokens || stats.latestInputTokens || stats.inputTokens || 0) > 0;
|
|
95
93
|
const hasApiContextUsage = Number(ctx?.lastApiRequestTokens ?? ctx?.usage?.lastContextTokens ?? 0) > 0;
|
|
96
94
|
const hasTurnActivity = state.busy === true
|
|
97
95
|
|| state.spinner != null
|
|
98
96
|
|| state.thinking != null;
|
|
99
97
|
const isFreshSession = !hasProviderUsage && !hasApiContextUsage && !hasTurnActivity;
|
|
100
98
|
if (isFreshSession) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
stats.currentEstimatedContextTokens = 0;
|
|
100
|
+
stats.currentContextTokens = 0;
|
|
101
|
+
stats.currentContextSource = null;
|
|
102
|
+
stats.currentContextUpdatedAt = Date.now();
|
|
103
|
+
updateState({ stats });
|
|
105
104
|
return ctx;
|
|
106
105
|
}
|
|
107
106
|
const estimatedTokens = Math.max(0, Number(ctx.currentEstimatedTokens ?? ctx.usedTokens ?? 0));
|
|
@@ -114,32 +113,33 @@ export function createContextState({ runtime, getState, getPendingSessionReset }
|
|
|
114
113
|
);
|
|
115
114
|
if (!allowEstimated && !hasProviderUsage && usedSource !== 'last_api_request') return ctx;
|
|
116
115
|
if (shouldPublishEstimate) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
stats.currentEstimatedContextTokens = estimatedTokens;
|
|
117
|
+
stats.currentContextSource = 'estimated';
|
|
118
|
+
stats.currentContextTokens = 0;
|
|
120
119
|
} else if (allowEstimated && (hasProviderUsage || hasApiContextUsage || hasTurnActivity)) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const publishedSource = String(
|
|
120
|
+
stats.currentEstimatedContextTokens = estimatedTokens;
|
|
121
|
+
stats.currentContextSource = usedSource || (estimatedTokens > 0 ? 'estimated' : null);
|
|
122
|
+
const publishedSource = String(stats.currentContextSource || '').toLowerCase();
|
|
124
123
|
if (publishedSource === 'last_api_request') {
|
|
125
124
|
const apiUsed = Math.max(0, Number(ctx.lastApiRequestTokens ?? usedTokens ?? 0));
|
|
126
|
-
|
|
125
|
+
stats.currentContextTokens = apiUsed;
|
|
127
126
|
} else if (publishedSource === 'estimated') {
|
|
128
|
-
|
|
127
|
+
stats.currentContextTokens = 0;
|
|
129
128
|
} else {
|
|
130
|
-
|
|
129
|
+
stats.currentContextTokens = usedTokens > 0 ? usedTokens : 0;
|
|
131
130
|
}
|
|
132
131
|
} else {
|
|
133
|
-
|
|
132
|
+
stats.currentEstimatedContextTokens = 0;
|
|
134
133
|
if (usedSource === 'last_api_request' && Number(ctx.lastApiRequestTokens ?? usedTokens ?? 0) > 0) {
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
stats.currentContextTokens = Math.max(0, Number(ctx.lastApiRequestTokens ?? usedTokens ?? 0));
|
|
135
|
+
stats.currentContextSource = 'last_api_request';
|
|
137
136
|
} else {
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
stats.currentContextTokens = 0;
|
|
138
|
+
stats.currentContextSource = null;
|
|
140
139
|
}
|
|
141
140
|
}
|
|
142
|
-
|
|
141
|
+
stats.currentContextUpdatedAt = Date.now();
|
|
142
|
+
updateState({ stats });
|
|
143
143
|
return ctx;
|
|
144
144
|
};
|
|
145
145
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coalesces external-store publications to one terminal frame snapshot.
|
|
3
|
+
* Internal draft mutations remain synchronous. The public snapshot, listener
|
|
4
|
+
* delivery, and structureRevision commit are swapped atomically at flush.
|
|
5
|
+
*/
|
|
6
|
+
export function createFrameBatchedStorePublisher({
|
|
7
|
+
getState,
|
|
8
|
+
publishState,
|
|
9
|
+
listeners,
|
|
10
|
+
isDisposed = () => false,
|
|
11
|
+
frameMs = 16,
|
|
12
|
+
setTimer = setTimeout,
|
|
13
|
+
clearTimer = clearTimeout,
|
|
14
|
+
enqueueMicrotask = queueMicrotask,
|
|
15
|
+
}) {
|
|
16
|
+
let timer = null;
|
|
17
|
+
let emitPending = false;
|
|
18
|
+
let structureChangePending = false;
|
|
19
|
+
let immediatePending = false;
|
|
20
|
+
|
|
21
|
+
const flush = () => {
|
|
22
|
+
if (timer !== null) {
|
|
23
|
+
clearTimer(timer);
|
|
24
|
+
timer = null;
|
|
25
|
+
}
|
|
26
|
+
immediatePending = false;
|
|
27
|
+
if (!emitPending || isDisposed()) return false;
|
|
28
|
+
emitPending = false;
|
|
29
|
+
const current = getState();
|
|
30
|
+
let next = current;
|
|
31
|
+
if (structureChangePending) {
|
|
32
|
+
structureChangePending = false;
|
|
33
|
+
next = {
|
|
34
|
+
...current,
|
|
35
|
+
structureRevision: (Number(current.structureRevision) || 0) + 1,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
publishState(next);
|
|
39
|
+
for (const listener of listeners) listener();
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const emit = () => {
|
|
44
|
+
emitPending = true;
|
|
45
|
+
if (timer !== null || isDisposed()) return;
|
|
46
|
+
timer = setTimer(flush, frameMs);
|
|
47
|
+
timer?.unref?.();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const markStructureChange = () => {
|
|
51
|
+
structureChangePending = true;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Let the current synchronous mutation chain finish, then publish without
|
|
55
|
+
// waiting for the frame timer (input echo / terminal turn boundaries).
|
|
56
|
+
const flushImmediate = () => {
|
|
57
|
+
if (!emitPending || immediatePending || isDisposed()) return false;
|
|
58
|
+
immediatePending = true;
|
|
59
|
+
enqueueMicrotask(flush);
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const dispose = () => {
|
|
64
|
+
// Disposal is itself an immediate boundary: publish the final pending
|
|
65
|
+
// snapshot once, while subscribers are still present, then disarm.
|
|
66
|
+
if (emitPending && !isDisposed()) flush();
|
|
67
|
+
else if (timer !== null) clearTimer(timer);
|
|
68
|
+
timer = null;
|
|
69
|
+
emitPending = false;
|
|
70
|
+
structureChangePending = false;
|
|
71
|
+
immediatePending = false;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return { emit, flush, flushImmediate, markStructureChange, dispose };
|
|
75
|
+
}
|
|
@@ -10,7 +10,7 @@ import { getVoiceStatus, toggleVoice } from '../lib/voice-setup.mjs';
|
|
|
10
10
|
|
|
11
11
|
export function createEngineApiB(bag) {
|
|
12
12
|
const {
|
|
13
|
-
runtime, nextId, flags, lifecycle, listeners, getState, set, replaceItems, pushNotice, removeNotice, setProgressHint, clearToastTimers, routeState, syncContextStats, finishToolApproval, denyAllToolApprovals, restoreLeadSteeringFromDisk, resetStats, clearUiActivityBeforeContextSync, resetTuiForPendingSessionReset, snapshotTuiBeforeSessionReset, restoreTuiAfterFailedSessionReset, resetStatsAndSyncContext,
|
|
13
|
+
runtime, nextId, flags, lifecycle, listeners, getState, set, disposeEmit, replaceItems, pushNotice, removeNotice, setProgressHint, clearToastTimers, disposeTranscriptSpill, routeState, syncContextStats, finishToolApproval, denyAllToolApprovals, restoreLeadSteeringFromDisk, resetStats, clearUiActivityBeforeContextSync, resetTuiForPendingSessionReset, snapshotTuiBeforeSessionReset, restoreTuiAfterFailedSessionReset, commitTuiSessionReset, resetStatsAndSyncContext,
|
|
14
14
|
} = bag;
|
|
15
15
|
return {
|
|
16
16
|
resolveToolApproval: (id, decision = {}) => {
|
|
@@ -363,6 +363,7 @@ export function createEngineApiB(bag) {
|
|
|
363
363
|
flags.pendingSessionReset = false;
|
|
364
364
|
resetStatsAndSyncContext();
|
|
365
365
|
set({ items: replaceItems([]), toasts: [], queued: [], thinking: null, spinner: null, lastTurn: null, ...routeState(), stats: { ...getState().stats } });
|
|
366
|
+
commitTuiSessionReset(rollbackSnapshot);
|
|
366
367
|
flags.lastUserActivityAt = Date.now();
|
|
367
368
|
return true;
|
|
368
369
|
} catch (error) {
|
|
@@ -400,6 +401,7 @@ export function createEngineApiB(bag) {
|
|
|
400
401
|
...routeState(),
|
|
401
402
|
stats: { ...getState().stats },
|
|
402
403
|
});
|
|
404
|
+
commitTuiSessionReset(rollbackSnapshot);
|
|
403
405
|
return true;
|
|
404
406
|
} catch (error) {
|
|
405
407
|
restoreTuiAfterFailedSessionReset(rollbackSnapshot);
|
|
@@ -432,6 +434,7 @@ export function createEngineApiB(bag) {
|
|
|
432
434
|
flags.pendingSessionReset = false;
|
|
433
435
|
resetStatsAndSyncContext();
|
|
434
436
|
set({ items: replaceItems([]), toasts: [], queued: [], thinking: null, spinner: null, lastTurn: null, ...routeState(), stats: { ...getState().stats } });
|
|
437
|
+
commitTuiSessionReset(rollbackSnapshot);
|
|
435
438
|
return true;
|
|
436
439
|
} catch (error) {
|
|
437
440
|
restoreTuiAfterFailedSessionReset(rollbackSnapshot);
|
|
@@ -506,8 +509,10 @@ export function createEngineApiB(bag) {
|
|
|
506
509
|
|
|
507
510
|
dispose: async (reason = 'cli-react-exit', options = {}) => {
|
|
508
511
|
if (flags.disposed) return;
|
|
512
|
+
disposeEmit?.();
|
|
509
513
|
flags.disposed = true;
|
|
510
514
|
clearToastTimers();
|
|
515
|
+
disposeTranscriptSpill?.();
|
|
511
516
|
try { clearInterval(lifecycle.runtimePulseTimer); } catch {}
|
|
512
517
|
try { lifecycle.unsubscribeRuntimeNotifications?.(); } catch {}
|
|
513
518
|
lifecycle.unsubscribeRuntimeNotifications = null;
|
|
@@ -24,11 +24,13 @@ export function createEngineApi(bag) {
|
|
|
24
24
|
|
|
25
25
|
export function createEngineApiA(bag) {
|
|
26
26
|
const {
|
|
27
|
-
runtime, nextId, flags, pending, listeners, getState, set, pushItem, patchItem, replaceItems, settleStreamingTail, clearStreamingTail, pushNotice, autoClearState, agentStatusState, routeState, syncContextStats, denyAllToolApprovals, updateAgentJobCard, requeueEntriesFront, enqueue, autoClearBeforeSubmit, restoreQueued, resetStatsAndSyncContext, drain, flushDeferredExecutionPendingResumeKick, discardExecutionPendingResume,
|
|
27
|
+
runtime, nextId, flags, pending, listeners, getState, getPublishedState = getState, set, flushEmitImmediate, pushItem, patchItem, replaceItems, restoreOlderTranscript, restoreNewerTranscript, settleStreamingTail, clearStreamingTail, pushNotice, autoClearState, agentStatusState, routeState, syncContextStats, denyAllToolApprovals, updateAgentJobCard, requeueEntriesFront, enqueue, autoClearBeforeSubmit, restoreQueued, resetStatsAndSyncContext, drain, flushDeferredExecutionPendingResumeKick, discardExecutionPendingResume,
|
|
28
28
|
} = bag;
|
|
29
29
|
return {
|
|
30
|
-
getState: () =>
|
|
30
|
+
getState: () => getPublishedState(),
|
|
31
31
|
patchItem,
|
|
32
|
+
restoreOlderTranscript,
|
|
33
|
+
restoreNewerTranscript,
|
|
32
34
|
subscribe: (listener) => {
|
|
33
35
|
listeners.add(listener);
|
|
34
36
|
return () => listeners.delete(listener);
|
|
@@ -592,7 +594,10 @@ export function createEngineApiA(bag) {
|
|
|
592
594
|
if (idSet.size > 0) {
|
|
593
595
|
const items = getState().items.filter((item) => !idSet.has(item?.id));
|
|
594
596
|
if (items.length !== getState().items.length) {
|
|
595
|
-
patch.items = replaceItems(items
|
|
597
|
+
patch.items = replaceItems(items, {
|
|
598
|
+
preserveSpill: true,
|
|
599
|
+
preserveTranscriptView: true,
|
|
600
|
+
});
|
|
596
601
|
}
|
|
597
602
|
}
|
|
598
603
|
set(patch);
|
|
@@ -637,6 +642,7 @@ export function createEngineApiA(bag) {
|
|
|
637
642
|
flags.drainEpoch = (Number(flags.drainEpoch) || 0) + 1;
|
|
638
643
|
if (flags.draining) flags.draining = false;
|
|
639
644
|
pushNotice('Interrupt did not settle — input restored.', 'warn', { transcript: true });
|
|
645
|
+
flushEmitImmediate?.();
|
|
640
646
|
if (pending.length > 0 && typeof drain === 'function') void drain();
|
|
641
647
|
// busy→false here bypasses the normal turn-end + drain-finally flushes,
|
|
642
648
|
// so re-arm any deferred completion kick explicitly (idempotent).
|
|
@@ -9,7 +9,8 @@ import { appendTuiSteeringPersist, dropTuiSteeringPersist, drainTuiSteeringPersi
|
|
|
9
9
|
|
|
10
10
|
export function createSessionFlow(bag) {
|
|
11
11
|
const {
|
|
12
|
-
runtime, nextId, tuiDebug, flags, pending, pendingNotificationKeys, displayedExecutionNotificationKeys, clearExecutionDedupState, clearToastTimers, getState, set, pushItem, replaceItems, pushNotice, pushUserOrSyntheticItem, autoClearState, agentStatusState, routeState, syncContextStats, flushDeferredExecutionPendingResumeKick,
|
|
12
|
+
runtime, nextId, tuiDebug, flags, pending, pendingNotificationKeys, displayedExecutionNotificationKeys, clearExecutionDedupState, clearToastTimers, getState, set, flushEmitImmediate, pushItem, replaceItems, pushNotice, pushUserOrSyntheticItem, autoClearState, agentStatusState, routeState, syncContextStats, flushDeferredExecutionPendingResumeKick,
|
|
13
|
+
snapshotTranscriptSpill, restoreTranscriptSpill, releaseTranscriptSpill,
|
|
13
14
|
} = bag;
|
|
14
15
|
|
|
15
16
|
// Upper bound on the awaited compacting clear. requireCompactSuccess makes
|
|
@@ -272,7 +273,10 @@ export function createSessionFlow(bag) {
|
|
|
272
273
|
if (getState().busy && shouldMirrorSteeringEntry(entry)) {
|
|
273
274
|
appendTuiSteeringPersist(leadSessionId(), entry);
|
|
274
275
|
}
|
|
275
|
-
if (isQueuedEntryVisible(entry))
|
|
276
|
+
if (isQueuedEntryVisible(entry)) {
|
|
277
|
+
set({ queued: [...getState().queued, entry] });
|
|
278
|
+
if (isQueuedEntryEditable(entry)) flushEmitImmediate?.();
|
|
279
|
+
}
|
|
276
280
|
if (getState().busy) tuiDebug(`busy-queue enqueue mode=${entry.mode} pending=${pending.length}`);
|
|
277
281
|
void drain();
|
|
278
282
|
return true;
|
|
@@ -495,19 +499,23 @@ export function createSessionFlow(bag) {
|
|
|
495
499
|
}
|
|
496
500
|
|
|
497
501
|
const resetStats = () => {
|
|
498
|
-
|
|
499
|
-
|
|
502
|
+
const stats = createSessionStats();
|
|
503
|
+
set({ stats });
|
|
504
|
+
return stats;
|
|
500
505
|
};
|
|
501
506
|
const clearUiActivityBeforeContextSync = () => {
|
|
502
507
|
clearToastTimers();
|
|
503
508
|
resetAllStreamingMarkdownStablePrefixes();
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
509
|
+
const items = replaceItems([]);
|
|
510
|
+
set({
|
|
511
|
+
items,
|
|
512
|
+
toasts: [],
|
|
513
|
+
queued: [],
|
|
514
|
+
thinking: null,
|
|
515
|
+
spinner: null,
|
|
516
|
+
lastTurn: null,
|
|
517
|
+
busy: false,
|
|
518
|
+
});
|
|
511
519
|
pendingNotificationKeys.clear();
|
|
512
520
|
displayedExecutionNotificationKeys.clear();
|
|
513
521
|
clearExecutionDedupState?.();
|
|
@@ -545,16 +553,26 @@ export function createSessionFlow(bag) {
|
|
|
545
553
|
flags.pendingSessionReset = true;
|
|
546
554
|
clearUiActivityBeforeContextSync();
|
|
547
555
|
resetStats();
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
556
|
+
set({
|
|
557
|
+
stats: {
|
|
558
|
+
...getState().stats,
|
|
559
|
+
currentContextTokens: 0,
|
|
560
|
+
currentEstimatedContextTokens: 0,
|
|
561
|
+
currentContextSource: null,
|
|
562
|
+
currentContextUpdatedAt: Date.now(),
|
|
563
|
+
},
|
|
564
|
+
displayContextWindow: 0,
|
|
565
|
+
compactBoundaryTokens: 0,
|
|
566
|
+
autoCompactTokenLimit: 0,
|
|
567
|
+
});
|
|
555
568
|
};
|
|
556
569
|
const snapshotTuiBeforeSessionReset = () => ({
|
|
557
570
|
items: getState().items.slice(),
|
|
571
|
+
transcriptViewItems: Array.isArray(getState().transcriptViewItems)
|
|
572
|
+
? getState().transcriptViewItems.slice()
|
|
573
|
+
: null,
|
|
574
|
+
transcriptViewRevision: getState().transcriptViewRevision,
|
|
575
|
+
transcriptSpill: snapshotTranscriptSpill?.() || null,
|
|
558
576
|
toasts: getState().toasts.slice(),
|
|
559
577
|
queued: getState().queued.slice(),
|
|
560
578
|
thinking: getState().thinking,
|
|
@@ -567,14 +585,20 @@ export function createSessionFlow(bag) {
|
|
|
567
585
|
const restoreTuiAfterFailedSessionReset = (snapshot) => {
|
|
568
586
|
if (!snapshot) return;
|
|
569
587
|
flags.pendingSessionReset = false;
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
588
|
+
restoreTranscriptSpill?.(snapshot.transcriptSpill);
|
|
589
|
+
const items = replaceItems(snapshot.items, { preserveSpill: true });
|
|
590
|
+
set({
|
|
591
|
+
items,
|
|
592
|
+
transcriptViewItems: snapshot.transcriptViewItems,
|
|
593
|
+
transcriptViewRevision: snapshot.transcriptViewRevision,
|
|
594
|
+
toasts: snapshot.toasts.slice(),
|
|
595
|
+
queued: snapshot.queued.slice(),
|
|
596
|
+
thinking: snapshot.thinking,
|
|
597
|
+
spinner: snapshot.spinner,
|
|
598
|
+
lastTurn: snapshot.lastTurn,
|
|
599
|
+
busy: snapshot.busy,
|
|
600
|
+
stats: { ...snapshot.stats },
|
|
601
|
+
});
|
|
578
602
|
syncContextStats({ allowEstimated: true });
|
|
579
603
|
set({
|
|
580
604
|
items: getState().items,
|
|
@@ -589,11 +613,14 @@ export function createSessionFlow(bag) {
|
|
|
589
613
|
...agentStatusState(),
|
|
590
614
|
});
|
|
591
615
|
};
|
|
616
|
+
const commitTuiSessionReset = (snapshot) => {
|
|
617
|
+
releaseTranscriptSpill?.(snapshot?.transcriptSpill);
|
|
618
|
+
};
|
|
592
619
|
const resetStatsAndSyncContext = () => {
|
|
593
620
|
resetStats();
|
|
594
621
|
syncContextStats({ allowEstimated: true });
|
|
595
622
|
return getState().stats;
|
|
596
623
|
};
|
|
597
624
|
|
|
598
|
-
return { leadSessionId, shouldMirrorSteeringEntry, commitSteeringQueueEntries, makeQueueEntry, removeQueuedEntries, requeueEntriesFront, dequeueQueueBatch, drain, enqueue, drainPendingSteering, restoreLeadSteeringFromDisk, autoClearBeforeSubmit, performSessionClear, restoreQueued, resetStats, clearUiActivityBeforeContextSync, resetTuiForPendingSessionReset, snapshotTuiBeforeSessionReset, restoreTuiAfterFailedSessionReset, resetStatsAndSyncContext };
|
|
625
|
+
return { leadSessionId, shouldMirrorSteeringEntry, commitSteeringQueueEntries, makeQueueEntry, removeQueuedEntries, requeueEntriesFront, dequeueQueueBatch, drain, enqueue, drainPendingSteering, restoreLeadSteeringFromDisk, autoClearBeforeSubmit, performSessionClear, restoreQueued, resetStats, clearUiActivityBeforeContextSync, resetTuiForPendingSessionReset, snapshotTuiBeforeSessionReset, restoreTuiAfterFailedSessionReset, commitTuiSessionReset, resetStatsAndSyncContext };
|
|
599
626
|
}
|
package/src/tui/engine/turn.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { aggregateRawResult, aggregateBucketForCategory, aggregateSummaries, ass
|
|
|
12
12
|
|
|
13
13
|
export function createRunTurn(bag) {
|
|
14
14
|
const {
|
|
15
|
-
runtime, nextId, tuiDebug, LEAD_TURN_TIMEOUT_MS, flags, pending, itemIndexById, getState, set, pushItem, patchItem, replaceItems, updateStreamingTail: updateStreamingTailFromStore, settleStreamingTail: settleStreamingTailFromStore, clearStreamingTail: clearStreamingTailFromStore, pushNotice, pushUserOrSyntheticItem, markToolCallActive, markToolCallDone, clearActiveToolSummary, agentStatusState, routeState, syncContextStats, denyAllToolApprovals, requestToolApproval, patchToolCardResult, flushToolResults, flushDeferredExecutionPendingResumeKick, drain, drainPendingSteering,
|
|
15
|
+
runtime, nextId, tuiDebug, LEAD_TURN_TIMEOUT_MS, flags, pending, itemIndexById, getState, set, flushEmit, flushEmitImmediate, pushItem, appendItems, patchItem, replaceItems, updateStreamingTail: updateStreamingTailFromStore, settleStreamingTail: settleStreamingTailFromStore, clearStreamingTail: clearStreamingTailFromStore, pushNotice, pushUserOrSyntheticItem, markToolCallActive, markToolCallDone, clearActiveToolSummary, agentStatusState, routeState, syncContextStats, denyAllToolApprovals, requestToolApproval, patchToolCardResult, flushToolResults, flushDeferredExecutionPendingResumeKick, drain, drainPendingSteering,
|
|
16
16
|
} = bag;
|
|
17
17
|
// Small fallbacks keep isolated createRunTurn harnesses source-compatible;
|
|
18
18
|
// the real engine supplies atomic implementations that also maintain revision.
|
|
@@ -179,6 +179,7 @@ export function createRunTurn(bag) {
|
|
|
179
179
|
// drain-finally flush, so a deferred completion kick would never re-arm.
|
|
180
180
|
// Fire it explicitly (idempotent: guarded by deferred flag + !busy).
|
|
181
181
|
flushDeferredExecutionPendingResumeKick();
|
|
182
|
+
flushEmitImmediate?.();
|
|
182
183
|
}, 5_000);
|
|
183
184
|
watchdogGraceTimer.unref?.();
|
|
184
185
|
}, delay);
|
|
@@ -354,6 +355,10 @@ export function createRunTurn(bag) {
|
|
|
354
355
|
const appendItemsBatch = (newItems, extra = {}) => {
|
|
355
356
|
if (!isCurrentTurn()) return;
|
|
356
357
|
if (!newItems || !newItems.length) { set(extra); return; }
|
|
358
|
+
if (appendItems) {
|
|
359
|
+
appendItems(newItems, extra);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
357
362
|
const base = getState().items.length;
|
|
358
363
|
const items = [...getState().items, ...newItems];
|
|
359
364
|
for (let i = 0; i < newItems.length; i++) {
|
|
@@ -1073,6 +1078,37 @@ export function createRunTurn(bag) {
|
|
|
1073
1078
|
if (thinkingLastEndedAt) _pendingThinkingLastEndedAt = thinkingLastEndedAt;
|
|
1074
1079
|
scheduleStreamFlush();
|
|
1075
1080
|
},
|
|
1081
|
+
onTextReset: ({ chars } = {}) => {
|
|
1082
|
+
if (!isCurrentTurn()) return false;
|
|
1083
|
+
const count = Math.max(0, Number(chars) || 0);
|
|
1084
|
+
if (!count) return false;
|
|
1085
|
+
flushStreamBatch();
|
|
1086
|
+
assistantText = assistantText.slice(0, Math.max(0, assistantText.length - count));
|
|
1087
|
+
currentAssistantText = currentAssistantText.slice(
|
|
1088
|
+
0,
|
|
1089
|
+
Math.max(0, currentAssistantText.length - count),
|
|
1090
|
+
);
|
|
1091
|
+
_streamScanLen = 0;
|
|
1092
|
+
_lastNewlineIdx = -1;
|
|
1093
|
+
_emittedNewlineIdx = -2;
|
|
1094
|
+
_emittedVisibleText = '';
|
|
1095
|
+
if (currentAssistantId) {
|
|
1096
|
+
if (currentAssistantText) {
|
|
1097
|
+
set({
|
|
1098
|
+
streamingTail: {
|
|
1099
|
+
kind: 'assistant',
|
|
1100
|
+
id: currentAssistantId,
|
|
1101
|
+
text: currentAssistantText,
|
|
1102
|
+
streaming: true,
|
|
1103
|
+
},
|
|
1104
|
+
});
|
|
1105
|
+
} else {
|
|
1106
|
+
clearStreamingTail(currentAssistantId);
|
|
1107
|
+
currentAssistantId = null;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return true;
|
|
1111
|
+
},
|
|
1076
1112
|
onAssistantText: (text) => {
|
|
1077
1113
|
// Mid-turn assistant text that precedes a tool call. Providers that
|
|
1078
1114
|
// stream via onTextDelta already accumulated it into assistantText;
|
|
@@ -1113,7 +1149,9 @@ export function createRunTurn(bag) {
|
|
|
1113
1149
|
},
|
|
1114
1150
|
onUsageDelta: (delta) => {
|
|
1115
1151
|
if (!markTurnProgress('usage-delta')) return;
|
|
1116
|
-
|
|
1152
|
+
const stats = { ...getState().stats };
|
|
1153
|
+
applyUsageDelta(stats, delta);
|
|
1154
|
+
set({ stats });
|
|
1117
1155
|
syncContextStats({ allowEstimated: true });
|
|
1118
1156
|
const currentTurnInput = Math.max(0, getState().stats.inputTokens - inputBaseline);
|
|
1119
1157
|
const currentTurnOutput = Math.max(0, getState().stats.outputTokens - outputBaseline);
|
|
@@ -1269,7 +1307,7 @@ export function createRunTurn(bag) {
|
|
|
1269
1307
|
&& !assistantOutput
|
|
1270
1308
|
&& !producedTranscriptItem;
|
|
1271
1309
|
if (!isNoOpTurn) {
|
|
1272
|
-
getState().stats
|
|
1310
|
+
set({ stats: { ...getState().stats, turns: (getState().stats.turns || 0) + 1 } });
|
|
1273
1311
|
}
|
|
1274
1312
|
// Pin the post-think summary into the transcript right after this turn's
|
|
1275
1313
|
// output so it scrolls up with the answer and stays in the scrollback,
|
|
@@ -1295,6 +1333,9 @@ export function createRunTurn(bag) {
|
|
|
1295
1333
|
// Shared UI getState(): a stale unwind must not wipe a newer turn's live
|
|
1296
1334
|
// tool-summary line (same epoch rule as the shared-getState() block above).
|
|
1297
1335
|
if (flags.leadTurnEpoch === turnEpoch) clearActiveToolSummary();
|
|
1336
|
+
// Turn completion is latency-sensitive and must publish busy=false plus all
|
|
1337
|
+
// terminal transcript/card mutations as one final snapshot.
|
|
1338
|
+
flushEmit?.();
|
|
1298
1339
|
_publishedThinkingActive = false; // turn teardown cleared getState().thinking
|
|
1299
1340
|
const finalStatus = cancelled ? 'cancelled' : (failed ? 'failed' : 'done');
|
|
1300
1341
|
tuiDebug(`runTurn end turn=${turnIndex} status=${finalStatus} elapsedMs=${Date.now() - startedAt}${watchdogTripped ? ' watchdogTripped=1' : ''} pending=${pending.length}`);
|