mixdog 0.9.16 → 0.9.18
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/atomic-lock-tryonce-test.mjs +66 -0
- package/scripts/provider-toolcall-test.mjs +79 -2
- package/src/mixdog-session-runtime.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +33 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +32 -18
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +142 -3
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +108 -30
- package/src/runtime/agent/orchestrator/session/store.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +22 -12
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +15 -15
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +10 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +8 -2
- package/src/runtime/channels/backends/discord-attachments.mjs +2 -2
- package/src/runtime/channels/backends/discord-gateway.mjs +12 -2
- package/src/runtime/channels/backends/discord.mjs +97 -7
- package/src/runtime/channels/index.mjs +150 -23
- package/src/runtime/channels/lib/crash-log.mjs +21 -3
- package/src/runtime/channels/lib/output-forwarder.mjs +118 -7
- package/src/runtime/channels/lib/runtime-paths.mjs +21 -19
- package/src/runtime/channels/lib/voice-transcription.mjs +4 -2
- package/src/runtime/memory/index.mjs +37 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +3 -0
- package/src/runtime/memory/lib/ko-morph.mjs +1 -0
- package/src/runtime/shared/atomic-file.mjs +110 -0
- package/src/runtime/shared/transcript-writer.mjs +46 -4
- package/src/session-runtime/provider-models.mjs +47 -8
- package/src/standalone/channel-worker.mjs +14 -2
- package/src/tui/app/transcript-window.mjs +137 -6
- package/src/tui/app/use-transcript-window.mjs +67 -10
- package/src/tui/components/StatusLine.jsx +1 -1
- package/src/tui/dist/index.mjs +436 -93
- package/src/tui/engine/tui-steering-persist.mjs +66 -35
- package/src/tui/engine.mjs +66 -14
- package/src/tui/index.jsx +97 -6
- package/src/ui/statusline-segments.mjs +54 -36
- package/src/ui/statusline.mjs +141 -95
package/src/tui/dist/index.mjs
CHANGED
|
@@ -3516,7 +3516,7 @@ function hasActiveStatuslineTools(activeTools = null) {
|
|
|
3516
3516
|
return e || s;
|
|
3517
3517
|
}
|
|
3518
3518
|
function hasActiveStatuslineWork(line, agentWorkers = [], agentJobs = [], activeTools = null) {
|
|
3519
|
-
return hasRunningStatuslineWorkers(agentWorkers, agentJobs) || hasActiveStatuslineTools(activeTools) || /\bRunning \d+ (?:Agents?|Shells?)\b/.test(stripAnsi(line)) || /\b(?:Exploring|Searching)\b/.test(stripAnsi(line));
|
|
3519
|
+
return hasRunningStatuslineWorkers(agentWorkers, agentJobs) || hasActiveStatuslineTools(activeTools) || /\bRunning \d+ (?:Agents?|Shells?)\b/.test(stripAnsi(line)) || /\b(?:Exploring|Searching|Memory)\b/.test(stripAnsi(line));
|
|
3520
3520
|
}
|
|
3521
3521
|
function bootFullRenderEligible(mountAtMs, line, agentWorkers = [], agentJobs = [], activeTools = null) {
|
|
3522
3522
|
const elapsed = Date.now() - mountAtMs;
|
|
@@ -7538,6 +7538,12 @@ function withFileLockSync(lockPath, fn, opts = {}) {
|
|
|
7538
7538
|
} catch (err) {
|
|
7539
7539
|
lastErr = err;
|
|
7540
7540
|
if (!LOCK_WAIT_CODES.has(err?.code)) throw err;
|
|
7541
|
+
if (timeoutMs <= 0) {
|
|
7542
|
+
const contErr = new Error(`atomic lock contended (try-once): ${lockPath}`);
|
|
7543
|
+
contErr.code = "ELOCKCONTENDED";
|
|
7544
|
+
contErr.cause = err;
|
|
7545
|
+
throw contErr;
|
|
7546
|
+
}
|
|
7541
7547
|
try {
|
|
7542
7548
|
const st = statSync3(lockPath);
|
|
7543
7549
|
if (Date.now() - st.mtimeMs > staleMs) {
|
|
@@ -7694,6 +7700,91 @@ function _releaseReclaimGuard(reclaim) {
|
|
|
7694
7700
|
}
|
|
7695
7701
|
}
|
|
7696
7702
|
}
|
|
7703
|
+
function _asyncSleep(ms) {
|
|
7704
|
+
return new Promise((resolve5) => {
|
|
7705
|
+
setTimeout(resolve5, Math.max(1, Number(ms) || 1));
|
|
7706
|
+
});
|
|
7707
|
+
}
|
|
7708
|
+
async function withFileLock(lockPath, fn, opts = {}) {
|
|
7709
|
+
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : DEFAULT_LOCK_TIMEOUT_MS;
|
|
7710
|
+
const staleMs = Number.isFinite(opts.staleMs) ? opts.staleMs : 3e4;
|
|
7711
|
+
const deadline = Date.now() + timeoutMs;
|
|
7712
|
+
mkdirSync2(dirname3(lockPath), { recursive: true });
|
|
7713
|
+
let attempt = 0;
|
|
7714
|
+
let lastErr = null;
|
|
7715
|
+
while (true) {
|
|
7716
|
+
let fd;
|
|
7717
|
+
try {
|
|
7718
|
+
fd = openSync(lockPath, "wx");
|
|
7719
|
+
} catch (err) {
|
|
7720
|
+
lastErr = err;
|
|
7721
|
+
if (!LOCK_WAIT_CODES.has(err?.code)) throw err;
|
|
7722
|
+
if (timeoutMs <= 0) {
|
|
7723
|
+
const contErr = new Error(`atomic lock contended (try-once): ${lockPath}`);
|
|
7724
|
+
contErr.code = "ELOCKCONTENDED";
|
|
7725
|
+
contErr.cause = err;
|
|
7726
|
+
throw contErr;
|
|
7727
|
+
}
|
|
7728
|
+
try {
|
|
7729
|
+
const st = statSync3(lockPath);
|
|
7730
|
+
if (Date.now() - st.mtimeMs > staleMs) {
|
|
7731
|
+
const deadPid = _readLockOwnerPid(lockPath);
|
|
7732
|
+
if (deadPid !== null && _pidIsDead(deadPid)) {
|
|
7733
|
+
const reclaim = _tryAcquireReclaimGuard(lockPath, staleMs);
|
|
7734
|
+
if (reclaim !== null) {
|
|
7735
|
+
let reclaimed = false;
|
|
7736
|
+
try {
|
|
7737
|
+
const currentSt = statSync3(lockPath);
|
|
7738
|
+
if (Date.now() - currentSt.mtimeMs > staleMs) {
|
|
7739
|
+
const currentPid = _readLockOwnerPid(lockPath);
|
|
7740
|
+
if (currentPid === deadPid && _pidIsDead(currentPid)) {
|
|
7741
|
+
try {
|
|
7742
|
+
unlinkSync2(lockPath);
|
|
7743
|
+
reclaimed = true;
|
|
7744
|
+
} catch {
|
|
7745
|
+
}
|
|
7746
|
+
}
|
|
7747
|
+
}
|
|
7748
|
+
} finally {
|
|
7749
|
+
_releaseReclaimGuard(reclaim);
|
|
7750
|
+
}
|
|
7751
|
+
if (reclaimed) continue;
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7754
|
+
}
|
|
7755
|
+
} catch {
|
|
7756
|
+
}
|
|
7757
|
+
if (Date.now() >= deadline) break;
|
|
7758
|
+
const base = DEFAULT_BACKOFFS_MS[Math.min(attempt, DEFAULT_BACKOFFS_MS.length - 1)];
|
|
7759
|
+
const jitter = Math.floor(Math.random() * Math.min(75, Math.max(1, base)));
|
|
7760
|
+
await _asyncSleep(Math.min(Math.max(1, deadline - Date.now()), base + jitter));
|
|
7761
|
+
attempt += 1;
|
|
7762
|
+
continue;
|
|
7763
|
+
}
|
|
7764
|
+
try {
|
|
7765
|
+
writeFileSync2(fd, `${process.pid} ${Date.now()}
|
|
7766
|
+
`, "utf8");
|
|
7767
|
+
} catch {
|
|
7768
|
+
}
|
|
7769
|
+
try {
|
|
7770
|
+
if (opts.secret === true) _enforceOwnerOnlyAclWin32(lockPath);
|
|
7771
|
+
return await fn();
|
|
7772
|
+
} finally {
|
|
7773
|
+
try {
|
|
7774
|
+
closeSync(fd);
|
|
7775
|
+
} catch {
|
|
7776
|
+
}
|
|
7777
|
+
try {
|
|
7778
|
+
if (_lockOwnedByPid(lockPath, process.pid)) unlinkSync2(lockPath);
|
|
7779
|
+
} catch {
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
const timeoutErr = new Error(`atomic lock timeout after ${timeoutMs}ms: ${lockPath}`);
|
|
7784
|
+
timeoutErr.code = "ELOCKTIMEOUT";
|
|
7785
|
+
timeoutErr.cause = lastErr;
|
|
7786
|
+
throw timeoutErr;
|
|
7787
|
+
}
|
|
7697
7788
|
var _cachedUserSid = null;
|
|
7698
7789
|
function _resolveCurrentUserPrincipal() {
|
|
7699
7790
|
const systemRoot = process.env.SystemRoot || process.env.windir;
|
|
@@ -7854,6 +7945,21 @@ function updateJsonAtomicSync(filePath, mutator, opts = {}) {
|
|
|
7854
7945
|
return next;
|
|
7855
7946
|
}, opts);
|
|
7856
7947
|
}
|
|
7948
|
+
async function updateJsonAtomic(filePath, mutator, opts = {}) {
|
|
7949
|
+
const { lock: _lock, ...writeOpts } = opts;
|
|
7950
|
+
return withFileLock(`${filePath}.lock`, () => {
|
|
7951
|
+
let cur = null;
|
|
7952
|
+
try {
|
|
7953
|
+
cur = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
7954
|
+
} catch {
|
|
7955
|
+
cur = null;
|
|
7956
|
+
}
|
|
7957
|
+
const next = mutator(cur);
|
|
7958
|
+
if (next === void 0) return cur;
|
|
7959
|
+
writeJsonAtomicSync(filePath, next, { ...writeOpts, lock: false });
|
|
7960
|
+
return next;
|
|
7961
|
+
}, opts);
|
|
7962
|
+
}
|
|
7857
7963
|
|
|
7858
7964
|
// src/runtime/shared/user-data-guard.mjs
|
|
7859
7965
|
import {
|
|
@@ -10994,6 +11100,10 @@ function measuredTranscriptRows(item, columns, toolOutputExpanded) {
|
|
|
10994
11100
|
return entry.rows;
|
|
10995
11101
|
}
|
|
10996
11102
|
var STREAMING_ROW_QUANTUM = 1;
|
|
11103
|
+
var streamingBottomPinned = false;
|
|
11104
|
+
function setStreamingBottomPinned(pinned) {
|
|
11105
|
+
streamingBottomPinned = !!pinned;
|
|
11106
|
+
}
|
|
10997
11107
|
function assistantTextForStreamingRowEstimate(text) {
|
|
10998
11108
|
return streamingLayoutText(text);
|
|
10999
11109
|
}
|
|
@@ -11007,14 +11117,16 @@ function estimateTranscriptItemRowsCached(item, columns, toolOutputExpanded) {
|
|
|
11007
11117
|
if (!item) return Math.max(1, Math.ceil(estimateTranscriptItemRows(item, columns, toolOutputExpanded)));
|
|
11008
11118
|
if (shouldSuppressFullyFailedToolItem(item)) return 0;
|
|
11009
11119
|
if (item.kind === "assistant" && item.streaming) {
|
|
11010
|
-
const estimate = streamingEstimateRows(item, columns, toolOutputExpanded);
|
|
11011
11120
|
const toolExpanded2 = toolOutputExpanded ? 1 : 0;
|
|
11012
11121
|
const idEntry = streamingMeasuredRowsById.get(item.id);
|
|
11013
11122
|
if (idEntry && idEntry.rows > 0 && idEntry.columns === columns && idEntry.toolExpanded === toolExpanded2) {
|
|
11014
|
-
|
|
11123
|
+
if (streamingBottomPinned) {
|
|
11124
|
+
return Math.max(idEntry.rows, streamingEstimateRows(item, columns, toolOutputExpanded));
|
|
11125
|
+
}
|
|
11126
|
+
return idEntry.rows;
|
|
11015
11127
|
}
|
|
11016
11128
|
if (idEntry) streamingMeasuredRowsById.delete(item.id);
|
|
11017
|
-
return
|
|
11129
|
+
return streamingEstimateRows(item, columns, toolOutputExpanded);
|
|
11018
11130
|
}
|
|
11019
11131
|
if (item.kind === "assistant" && streamingMeasuredRowsById.has(item.id)) {
|
|
11020
11132
|
streamingMeasuredRowsById.delete(item.id);
|
|
@@ -11047,6 +11159,61 @@ function buildTranscriptRowIndex(items, {
|
|
|
11047
11159
|
}
|
|
11048
11160
|
return { rows, prefixRows, totalRows: prefixRows[allItems.length] || 0 };
|
|
11049
11161
|
}
|
|
11162
|
+
function trailingStreamingItem(allItems) {
|
|
11163
|
+
const last = allItems.length > 0 ? allItems[allItems.length - 1] : null;
|
|
11164
|
+
return last && last.kind === "assistant" && last.streaming ? last : null;
|
|
11165
|
+
}
|
|
11166
|
+
function buildTranscriptRowIndexIncremental(items, {
|
|
11167
|
+
columns = 80,
|
|
11168
|
+
toolOutputExpanded = false,
|
|
11169
|
+
suppressMeasuredRowHeights = false,
|
|
11170
|
+
measuredRowsVersion = 0,
|
|
11171
|
+
cacheRef = null,
|
|
11172
|
+
prefixSig = null
|
|
11173
|
+
} = {}) {
|
|
11174
|
+
const allItems = Array.isArray(items) ? items : [];
|
|
11175
|
+
const tail = trailingStreamingItem(allItems);
|
|
11176
|
+
const holder = cacheRef || { current: null };
|
|
11177
|
+
if (!tail) {
|
|
11178
|
+
holder.current = null;
|
|
11179
|
+
return buildTranscriptRowIndex(allItems, {
|
|
11180
|
+
columns,
|
|
11181
|
+
toolOutputExpanded,
|
|
11182
|
+
suppressMeasuredRowHeights
|
|
11183
|
+
});
|
|
11184
|
+
}
|
|
11185
|
+
const prefixLen = allItems.length - 1;
|
|
11186
|
+
const cache = holder.current;
|
|
11187
|
+
if (cache && cache.columns === columns && cache.toolExpanded === (toolOutputExpanded ? 1 : 0) && cache.suppress === suppressMeasuredRowHeights && cache.version === measuredRowsVersion && cache.prefixLen === prefixLen && prefixSig != null && cache.prefixSig === prefixSig && cache.tailId === tail.id) {
|
|
11188
|
+
{
|
|
11189
|
+
const tailMeasured = suppressMeasuredRowHeights ? null : measuredTranscriptRows(tail, columns, toolOutputExpanded);
|
|
11190
|
+
const tailRows = tailMeasured != null ? tailMeasured : estimateTranscriptItemRowsCached(tail, columns, toolOutputExpanded);
|
|
11191
|
+
const rows = cache.prefixRowsArr.slice();
|
|
11192
|
+
rows.push(tailRows);
|
|
11193
|
+
const prefixRows = cache.prefixPrefixRows.slice();
|
|
11194
|
+
prefixRows.push(cache.prefixTotal + tailRows);
|
|
11195
|
+
return { rows, prefixRows, totalRows: prefixRows[allItems.length] || 0 };
|
|
11196
|
+
}
|
|
11197
|
+
}
|
|
11198
|
+
const full = buildTranscriptRowIndex(allItems, {
|
|
11199
|
+
columns,
|
|
11200
|
+
toolOutputExpanded,
|
|
11201
|
+
suppressMeasuredRowHeights
|
|
11202
|
+
});
|
|
11203
|
+
holder.current = {
|
|
11204
|
+
columns,
|
|
11205
|
+
toolExpanded: toolOutputExpanded ? 1 : 0,
|
|
11206
|
+
suppress: suppressMeasuredRowHeights,
|
|
11207
|
+
version: measuredRowsVersion,
|
|
11208
|
+
prefixLen,
|
|
11209
|
+
prefixSig,
|
|
11210
|
+
tailId: tail.id,
|
|
11211
|
+
prefixRowsArr: full.rows.slice(0, prefixLen),
|
|
11212
|
+
prefixPrefixRows: full.prefixRows.slice(0, prefixLen + 1),
|
|
11213
|
+
prefixTotal: full.prefixRows[prefixLen] || 0
|
|
11214
|
+
};
|
|
11215
|
+
return full;
|
|
11216
|
+
}
|
|
11050
11217
|
var transcriptSigPartCache = /* @__PURE__ */ new WeakMap();
|
|
11051
11218
|
function transcriptStructureSignature(items, columns, toolOutputExpanded) {
|
|
11052
11219
|
const list = Array.isArray(items) ? items : [];
|
|
@@ -11880,6 +12047,7 @@ function useTranscriptWindow({
|
|
|
11880
12047
|
}) {
|
|
11881
12048
|
const transcriptTotalRowsRef = useRef8(0);
|
|
11882
12049
|
const preservedScrollDeltaRef = useRef8(0);
|
|
12050
|
+
const incrementalRowIndexCacheRef = useRef8(null);
|
|
11883
12051
|
const prevViewportGeomRef = useRef8({ contentHeight: 0, floatingPanelRows: 0 });
|
|
11884
12052
|
const transcriptItemElsRef = useRef8(/* @__PURE__ */ new Map());
|
|
11885
12053
|
const transcriptMeasureRefCache = useRef8(/* @__PURE__ */ new Map());
|
|
@@ -11907,26 +12075,41 @@ function useTranscriptWindow({
|
|
|
11907
12075
|
}
|
|
11908
12076
|
return fn;
|
|
11909
12077
|
}, []);
|
|
11910
|
-
const
|
|
11911
|
-
()
|
|
11912
|
-
|
|
12078
|
+
const streamingTailItem = useMemo(() => {
|
|
12079
|
+
const last = (items || []).length > 0 ? items[items.length - 1] : null;
|
|
12080
|
+
return last && last.kind === "assistant" && last.streaming ? last : null;
|
|
12081
|
+
}, [items]);
|
|
12082
|
+
const prefixLen = streamingTailItem ? (items || []).length - 1 : (items || []).length;
|
|
12083
|
+
const prefixSig = useMemo(
|
|
12084
|
+
() => transcriptStructureSignature(
|
|
12085
|
+
streamingTailItem ? (items || []).slice(0, prefixLen) : items || [],
|
|
12086
|
+
frameColumns,
|
|
12087
|
+
toolOutputExpanded
|
|
12088
|
+
),
|
|
12089
|
+
[items, streamingTailItem, prefixLen, frameColumns, toolOutputExpanded]
|
|
11913
12090
|
);
|
|
12091
|
+
const scrolledUpRowsForPin = Math.max(0, Number(scrollTargetRef.current) || 0);
|
|
12092
|
+
const transcriptPinnedForStreaming = followingRef.current || scrolledUpRowsForPin <= transcriptBottomSlackRows;
|
|
12093
|
+
setStreamingBottomPinned(transcriptPinnedForStreaming);
|
|
12094
|
+
const tailSig = streamingTailItem ? `a${streamingTailItem.id}:${estimateTranscriptItemRowsCached(streamingTailItem, frameColumns, toolOutputExpanded)}` : "_";
|
|
12095
|
+
const transcriptStructureSig = `${prefixSig}#${tailSig}`;
|
|
11914
12096
|
const transcriptStreamingActive = (items || []).some(
|
|
11915
12097
|
(item) => item?.kind === "assistant" && item?.streaming
|
|
11916
12098
|
);
|
|
11917
|
-
const scrolledUpRowsForPin = Math.max(0, Number(scrollTargetRef.current) || 0);
|
|
11918
|
-
const transcriptPinnedForStreaming = followingRef.current || scrolledUpRowsForPin <= transcriptBottomSlackRows;
|
|
11919
12099
|
const scrolledUpForStreamingMeasure = scrolledUpRowsForPin > transcriptBottomSlackRows;
|
|
11920
12100
|
const prevEstimateGeometry = transcriptGeomRef.current?.suppressMeasuredRowHeights === true;
|
|
11921
12101
|
const hasStreamingReadingAnchor = !!transcriptAnchorRef.current || transcriptAnchorDirtyRef.current;
|
|
11922
12102
|
const bottomPinnedForMeasure = transcriptPinnedForStreaming;
|
|
11923
12103
|
const suppressMeasuredRowHeights = bottomPinnedForMeasure || transcriptStreamingActive && (scrolledUpForStreamingMeasure && hasStreamingReadingAnchor || scrolledUpForStreamingMeasure && prevEstimateGeometry && !transcriptPinnedForStreaming);
|
|
11924
|
-
const transcriptRowIndex = useMemo(() =>
|
|
12104
|
+
const transcriptRowIndex = useMemo(() => buildTranscriptRowIndexIncremental(items, {
|
|
11925
12105
|
columns: frameColumns,
|
|
11926
12106
|
toolOutputExpanded,
|
|
11927
|
-
suppressMeasuredRowHeights
|
|
11928
|
-
|
|
11929
|
-
|
|
12107
|
+
suppressMeasuredRowHeights,
|
|
12108
|
+
measuredRowsVersion,
|
|
12109
|
+
cacheRef: incrementalRowIndexCacheRef,
|
|
12110
|
+
prefixSig
|
|
12111
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentional: prefixSig/tailSig capture the relevant item changes (raw `items` dropped so a tail-only flush never re-enters the builder via array identity); measuredRowsVersion folds in app-level measured height corrections
|
|
12112
|
+
}), [prefixSig, tailSig, measuredRowsVersion, suppressMeasuredRowHeights]);
|
|
11930
12113
|
const hasReadingAnchor = !!transcriptAnchorRef.current && !transcriptAnchorDirtyRef.current;
|
|
11931
12114
|
const scrolledUpRows = Math.max(0, Number(scrollTargetRef.current) || 0);
|
|
11932
12115
|
const scrolledUp = scrolledUpRows > transcriptBottomSlackRows;
|
|
@@ -22149,6 +22332,13 @@ import { randomBytes as randomBytes3 } from "crypto";
|
|
|
22149
22332
|
import { join as join7 } from "path";
|
|
22150
22333
|
var PENDING_MESSAGES_FILE = "session-pending-messages.json";
|
|
22151
22334
|
var PENDING_MESSAGES_MODE = 384;
|
|
22335
|
+
var _persistChain = Promise.resolve();
|
|
22336
|
+
function _serialize(task) {
|
|
22337
|
+
const run = _persistChain.then(task, task);
|
|
22338
|
+
_persistChain = run.catch(() => {
|
|
22339
|
+
});
|
|
22340
|
+
return run;
|
|
22341
|
+
}
|
|
22152
22342
|
function pendingMessagesPath() {
|
|
22153
22343
|
return join7(resolvePluginData(), PENDING_MESSAGES_FILE);
|
|
22154
22344
|
}
|
|
@@ -22216,59 +22406,67 @@ function removePersistRow(q, entry) {
|
|
|
22216
22406
|
}
|
|
22217
22407
|
function appendTuiSteeringPersist(leadSessionId, entry) {
|
|
22218
22408
|
const text = entryPersistText(entry);
|
|
22219
|
-
if (!text) return;
|
|
22409
|
+
if (!text) return Promise.resolve();
|
|
22220
22410
|
const key = tuiSteeringSessionKey(leadSessionId);
|
|
22221
|
-
if (!key) return;
|
|
22411
|
+
if (!key) return Promise.resolve();
|
|
22222
22412
|
if (!entry.steeringPersistId) entry.steeringPersistId = newSteeringPersistId();
|
|
22223
22413
|
const record = { id: entry.steeringPersistId, text };
|
|
22224
|
-
|
|
22225
|
-
updateJsonAtomicSync(pendingMessagesPath(), (raw) => {
|
|
22226
|
-
const next = normalizePendingStore(raw);
|
|
22227
|
-
const q = Array.isArray(next.sessions[key]) ? next.sessions[key] : [];
|
|
22228
|
-
q.push(record);
|
|
22229
|
-
next.sessions[key] = q;
|
|
22230
|
-
const now = Date.now();
|
|
22231
|
-
next.updatedAt = now;
|
|
22232
|
-
touchPendingSessionEntry(next, key, now);
|
|
22233
|
-
return next;
|
|
22234
|
-
}, { compact: true, lock: true, mode: PENDING_MESSAGES_MODE, fsync: false });
|
|
22235
|
-
} catch (err) {
|
|
22414
|
+
return _serialize(async () => {
|
|
22236
22415
|
try {
|
|
22237
|
-
|
|
22416
|
+
await updateJsonAtomic(pendingMessagesPath(), (raw) => {
|
|
22417
|
+
const next = normalizePendingStore(raw);
|
|
22418
|
+
const q = Array.isArray(next.sessions[key]) ? next.sessions[key] : [];
|
|
22419
|
+
q.push(record);
|
|
22420
|
+
next.sessions[key] = q;
|
|
22421
|
+
const now = Date.now();
|
|
22422
|
+
next.updatedAt = now;
|
|
22423
|
+
touchPendingSessionEntry(next, key, now);
|
|
22424
|
+
return next;
|
|
22425
|
+
}, { compact: true, lock: true, mode: PENDING_MESSAGES_MODE, fsync: false });
|
|
22426
|
+
} catch (err) {
|
|
22427
|
+
try {
|
|
22428
|
+
process.stderr.write(`[tui] steering-queue append failed sessionId=${leadSessionId}: ${err?.message || err}
|
|
22238
22429
|
`);
|
|
22239
|
-
|
|
22430
|
+
} catch {
|
|
22431
|
+
}
|
|
22240
22432
|
}
|
|
22241
|
-
}
|
|
22433
|
+
});
|
|
22242
22434
|
}
|
|
22243
22435
|
function dropTuiSteeringPersist(leadSessionId, entries) {
|
|
22244
22436
|
const key = tuiSteeringSessionKey(leadSessionId);
|
|
22245
|
-
if (!key) return;
|
|
22437
|
+
if (!key) return Promise.resolve();
|
|
22246
22438
|
const batch = Array.isArray(entries) ? entries : [];
|
|
22247
|
-
if (batch.length === 0) return;
|
|
22248
|
-
|
|
22249
|
-
updateJsonAtomicSync(pendingMessagesPath(), (raw) => {
|
|
22250
|
-
const next = normalizePendingStore(raw);
|
|
22251
|
-
const q = Array.isArray(next.sessions[key]) ? next.sessions[key].slice() : [];
|
|
22252
|
-
if (q.length === 0) return void 0;
|
|
22253
|
-
for (const entry of batch) {
|
|
22254
|
-
removePersistRow(q, entry);
|
|
22255
|
-
}
|
|
22256
|
-
if (q.length === 0) {
|
|
22257
|
-
delete next.sessions[key];
|
|
22258
|
-
if (next.sessionTouchedAt) delete next.sessionTouchedAt[key];
|
|
22259
|
-
} else {
|
|
22260
|
-
next.sessions[key] = q;
|
|
22261
|
-
}
|
|
22262
|
-
next.updatedAt = Date.now();
|
|
22263
|
-
return next;
|
|
22264
|
-
}, { compact: true, lock: true, mode: PENDING_MESSAGES_MODE, fsync: false });
|
|
22265
|
-
} catch (err) {
|
|
22439
|
+
if (batch.length === 0) return Promise.resolve();
|
|
22440
|
+
return _serialize(async () => {
|
|
22266
22441
|
try {
|
|
22267
|
-
|
|
22442
|
+
await updateJsonAtomic(pendingMessagesPath(), (raw) => {
|
|
22443
|
+
const next = normalizePendingStore(raw);
|
|
22444
|
+
const q = Array.isArray(next.sessions[key]) ? next.sessions[key].slice() : [];
|
|
22445
|
+
if (q.length === 0) return void 0;
|
|
22446
|
+
for (const entry of batch) {
|
|
22447
|
+
removePersistRow(q, entry);
|
|
22448
|
+
}
|
|
22449
|
+
if (q.length === 0) {
|
|
22450
|
+
delete next.sessions[key];
|
|
22451
|
+
if (next.sessionTouchedAt) delete next.sessionTouchedAt[key];
|
|
22452
|
+
} else {
|
|
22453
|
+
next.sessions[key] = q;
|
|
22454
|
+
}
|
|
22455
|
+
next.updatedAt = Date.now();
|
|
22456
|
+
return next;
|
|
22457
|
+
}, { compact: true, lock: true, mode: PENDING_MESSAGES_MODE, fsync: false });
|
|
22458
|
+
} catch (err) {
|
|
22459
|
+
try {
|
|
22460
|
+
process.stderr.write(`[tui] steering-queue drop failed sessionId=${leadSessionId}: ${err?.message || err}
|
|
22268
22461
|
`);
|
|
22269
|
-
|
|
22462
|
+
} catch {
|
|
22463
|
+
}
|
|
22270
22464
|
}
|
|
22271
|
-
}
|
|
22465
|
+
});
|
|
22466
|
+
}
|
|
22467
|
+
function flushTuiSteeringPersist() {
|
|
22468
|
+
return _persistChain.catch(() => {
|
|
22469
|
+
});
|
|
22272
22470
|
}
|
|
22273
22471
|
function drainedRowToRestore(row) {
|
|
22274
22472
|
if (typeof row === "string") {
|
|
@@ -22281,27 +22479,29 @@ function drainedRowToRestore(row) {
|
|
|
22281
22479
|
}
|
|
22282
22480
|
function drainTuiSteeringPersist(leadSessionId) {
|
|
22283
22481
|
const key = tuiSteeringSessionKey(leadSessionId);
|
|
22284
|
-
if (!key) return [];
|
|
22285
|
-
|
|
22286
|
-
|
|
22287
|
-
updateJsonAtomicSync(pendingMessagesPath(), (raw) => {
|
|
22288
|
-
const next = normalizePendingStore(raw);
|
|
22289
|
-
const q = Array.isArray(next.sessions[key]) ? next.sessions[key] : [];
|
|
22290
|
-
drained = q.map(drainedRowToRestore).filter(Boolean);
|
|
22291
|
-
if (drained.length === 0) return void 0;
|
|
22292
|
-
delete next.sessions[key];
|
|
22293
|
-
if (next.sessionTouchedAt) delete next.sessionTouchedAt[key];
|
|
22294
|
-
next.updatedAt = Date.now();
|
|
22295
|
-
return next;
|
|
22296
|
-
}, { compact: true, lock: true, mode: PENDING_MESSAGES_MODE, fsync: false });
|
|
22297
|
-
} catch (err) {
|
|
22482
|
+
if (!key) return Promise.resolve([]);
|
|
22483
|
+
return _serialize(async () => {
|
|
22484
|
+
let drained = [];
|
|
22298
22485
|
try {
|
|
22299
|
-
|
|
22486
|
+
await updateJsonAtomic(pendingMessagesPath(), (raw) => {
|
|
22487
|
+
const next = normalizePendingStore(raw);
|
|
22488
|
+
const q = Array.isArray(next.sessions[key]) ? next.sessions[key] : [];
|
|
22489
|
+
drained = q.map(drainedRowToRestore).filter(Boolean);
|
|
22490
|
+
if (drained.length === 0) return void 0;
|
|
22491
|
+
delete next.sessions[key];
|
|
22492
|
+
if (next.sessionTouchedAt) delete next.sessionTouchedAt[key];
|
|
22493
|
+
next.updatedAt = Date.now();
|
|
22494
|
+
return next;
|
|
22495
|
+
}, { compact: true, lock: true, mode: PENDING_MESSAGES_MODE, fsync: false });
|
|
22496
|
+
} catch (err) {
|
|
22497
|
+
try {
|
|
22498
|
+
process.stderr.write(`[tui] steering-queue drain failed sessionId=${leadSessionId}: ${err?.message || err}
|
|
22300
22499
|
`);
|
|
22301
|
-
|
|
22500
|
+
} catch {
|
|
22501
|
+
}
|
|
22302
22502
|
}
|
|
22303
|
-
|
|
22304
|
-
|
|
22503
|
+
return drained;
|
|
22504
|
+
});
|
|
22305
22505
|
}
|
|
22306
22506
|
|
|
22307
22507
|
// src/tui/engine.mjs
|
|
@@ -22862,13 +23062,20 @@ async function createEngineSession({
|
|
|
22862
23062
|
seq: deferredSeqCounter++,
|
|
22863
23063
|
pushed: false,
|
|
22864
23064
|
timer: null,
|
|
22865
|
-
|
|
23065
|
+
// Mark the card visible and return its spec WITHOUT emitting, so a
|
|
23066
|
+
// batched turn-close flush can commit many specs in one set().
|
|
23067
|
+
materialize: () => {
|
|
22866
23068
|
card.pushed = true;
|
|
22867
|
-
if (!card.spec) return;
|
|
23069
|
+
if (!card.spec) return null;
|
|
22868
23070
|
card.spec.deferredDisplayReady = true;
|
|
23071
|
+
return card.spec;
|
|
23072
|
+
},
|
|
23073
|
+
push: () => {
|
|
23074
|
+
const spec = entry.materialize();
|
|
23075
|
+
if (!spec) return;
|
|
22869
23076
|
pushingFromDeferredEntry = true;
|
|
22870
23077
|
try {
|
|
22871
|
-
pushItem(
|
|
23078
|
+
pushItem(spec);
|
|
22872
23079
|
} finally {
|
|
22873
23080
|
pushingFromDeferredEntry = false;
|
|
22874
23081
|
}
|
|
@@ -22889,13 +23096,18 @@ async function createEngineSession({
|
|
|
22889
23096
|
seq: deferredSeqCounter++,
|
|
22890
23097
|
pushed: false,
|
|
22891
23098
|
timer: null,
|
|
22892
|
-
|
|
23099
|
+
materialize: () => {
|
|
22893
23100
|
aggregate.pushed = true;
|
|
22894
|
-
if (!aggregate.pendingSpec) return;
|
|
23101
|
+
if (!aggregate.pendingSpec) return null;
|
|
22895
23102
|
aggregate.pendingSpec.deferredDisplayReady = true;
|
|
23103
|
+
return aggregate.pendingSpec;
|
|
23104
|
+
},
|
|
23105
|
+
push: () => {
|
|
23106
|
+
const spec = entry.materialize();
|
|
23107
|
+
if (!spec) return;
|
|
22896
23108
|
pushingFromDeferredEntry = true;
|
|
22897
23109
|
try {
|
|
22898
|
-
pushItem(
|
|
23110
|
+
pushItem(spec);
|
|
22899
23111
|
} finally {
|
|
22900
23112
|
pushingFromDeferredEntry = false;
|
|
22901
23113
|
}
|
|
@@ -22919,6 +23131,35 @@ async function createEngineSession({
|
|
|
22919
23131
|
}
|
|
22920
23132
|
}
|
|
22921
23133
|
};
|
|
23134
|
+
const collectDeferredUpTo = (entry) => {
|
|
23135
|
+
const specs = [];
|
|
23136
|
+
if (!entry) return specs;
|
|
23137
|
+
for (const e of deferredEntries) {
|
|
23138
|
+
if (e.seq > entry.seq) break;
|
|
23139
|
+
if (e.pushed) continue;
|
|
23140
|
+
e.pushed = true;
|
|
23141
|
+
if (e.timer) {
|
|
23142
|
+
clearTimeout(e.timer);
|
|
23143
|
+
e.timer = null;
|
|
23144
|
+
}
|
|
23145
|
+
const spec = e.materialize?.();
|
|
23146
|
+
if (spec) specs.push(spec);
|
|
23147
|
+
}
|
|
23148
|
+
return specs;
|
|
23149
|
+
};
|
|
23150
|
+
const appendItemsBatch = (newItems, extra = {}) => {
|
|
23151
|
+
if (!newItems || !newItems.length) {
|
|
23152
|
+
set(extra);
|
|
23153
|
+
return;
|
|
23154
|
+
}
|
|
23155
|
+
const base = state.items.length;
|
|
23156
|
+
const items = [...state.items, ...newItems];
|
|
23157
|
+
for (let i = 0; i < newItems.length; i++) {
|
|
23158
|
+
const it = newItems[i];
|
|
23159
|
+
if (it?.id != null) itemIndexById.set(it.id, base + i);
|
|
23160
|
+
}
|
|
23161
|
+
set({ items, ...extra });
|
|
23162
|
+
};
|
|
22922
23163
|
const markPromptCommitted = () => {
|
|
22923
23164
|
if (activePromptRestore) {
|
|
22924
23165
|
if (!promptCommittedCallbackCalled && typeof activePromptRestore.onCommitted === "function") {
|
|
@@ -23421,13 +23662,14 @@ async function createEngineSession({
|
|
|
23421
23662
|
denyAllToolApprovals(cancelled ? "turn cancelled" : "turn finished");
|
|
23422
23663
|
clearWatchdog();
|
|
23423
23664
|
const isStaleUnwind = leadTurnEpoch !== turnEpoch;
|
|
23665
|
+
let closingItems = [];
|
|
23424
23666
|
if (deferredEntries.length) {
|
|
23425
23667
|
const last = deferredEntries[deferredEntries.length - 1];
|
|
23426
|
-
|
|
23668
|
+
closingItems = collectDeferredUpTo(last);
|
|
23427
23669
|
clearDeferredTimers();
|
|
23428
23670
|
}
|
|
23429
23671
|
flushDeferredBeforeImmediatePush = null;
|
|
23430
|
-
const producedTranscriptItem = state.items.length > itemsAtTurnStart;
|
|
23672
|
+
const producedTranscriptItem = state.items.length + closingItems.length > itemsAtTurnStart;
|
|
23431
23673
|
const reclaimed = cancelled && activePromptRestore?.reclaimed === true;
|
|
23432
23674
|
if (!isStaleUnwind) activePromptRestore = null;
|
|
23433
23675
|
closeThinkingSegment();
|
|
@@ -23441,15 +23683,16 @@ async function createEngineSession({
|
|
|
23441
23683
|
const assistantOutput = (currentAssistantText || assistantText || "").trim();
|
|
23442
23684
|
const isNoOpTurn = turnFinishedNormally && !cancelled && toolCards.length === 0 && !resultContent && !assistantOutput && !producedTranscriptItem;
|
|
23443
23685
|
if (isStaleUnwind) {
|
|
23686
|
+
if (closingItems.length) appendItemsBatch(closingItems);
|
|
23444
23687
|
tuiDebug(`runTurn STALE UNWIND turn=${turnIndex} \u2014 force-released; skipping shared-state writes`);
|
|
23445
23688
|
} else {
|
|
23446
23689
|
if (!isNoOpTurn) {
|
|
23447
23690
|
state.stats.turns = (state.stats.turns || 0) + 1;
|
|
23448
23691
|
}
|
|
23449
23692
|
if (!reclaimed && !isNoOpTurn) {
|
|
23450
|
-
|
|
23693
|
+
closingItems.push({ kind: "turndone", id: nextId(), elapsedMs, status: turnStatus, outputTokens: finalOutputTokens, thinkingElapsedMs, verb: pickDoneVerb(turnIndex) });
|
|
23451
23694
|
}
|
|
23452
|
-
|
|
23695
|
+
appendItemsBatch(closingItems, {
|
|
23453
23696
|
busy: false,
|
|
23454
23697
|
spinner: null,
|
|
23455
23698
|
thinking: null,
|
|
@@ -23638,8 +23881,8 @@ async function createEngineSession({
|
|
|
23638
23881
|
commitSteeringQueueEntries(batch);
|
|
23639
23882
|
return out;
|
|
23640
23883
|
}
|
|
23641
|
-
function restoreLeadSteeringFromDisk() {
|
|
23642
|
-
const rows = drainTuiSteeringPersist(leadSessionId());
|
|
23884
|
+
async function restoreLeadSteeringFromDisk() {
|
|
23885
|
+
const rows = await drainTuiSteeringPersist(leadSessionId());
|
|
23643
23886
|
if (!rows.length) return;
|
|
23644
23887
|
const restored = [];
|
|
23645
23888
|
for (const row of rows) {
|
|
@@ -23820,7 +24063,7 @@ async function createEngineSession({
|
|
|
23820
24063
|
syncContextStats({ allowEstimated: true });
|
|
23821
24064
|
return state.stats;
|
|
23822
24065
|
};
|
|
23823
|
-
restoreLeadSteeringFromDisk();
|
|
24066
|
+
void restoreLeadSteeringFromDisk();
|
|
23824
24067
|
return {
|
|
23825
24068
|
getState: () => state,
|
|
23826
24069
|
patchItem,
|
|
@@ -24743,7 +24986,7 @@ async function createEngineSession({
|
|
|
24743
24986
|
...routeState(),
|
|
24744
24987
|
stats: { ...state.stats }
|
|
24745
24988
|
});
|
|
24746
|
-
restoreLeadSteeringFromDisk();
|
|
24989
|
+
void restoreLeadSteeringFromDisk();
|
|
24747
24990
|
return true;
|
|
24748
24991
|
} finally {
|
|
24749
24992
|
set({ commandBusy: false, commandStatus: null });
|
|
@@ -24768,6 +25011,7 @@ async function createEngineSession({
|
|
|
24768
25011
|
}
|
|
24769
25012
|
unsubscribeRemoteState = null;
|
|
24770
25013
|
denyAllToolApprovals("runtime closing");
|
|
25014
|
+
await flushTuiSteeringPersist();
|
|
24771
25015
|
await runtime.close(reason, options);
|
|
24772
25016
|
listeners.clear();
|
|
24773
25017
|
}
|
|
@@ -24953,7 +25197,7 @@ function touchUiHeartbeat(instanceId) {
|
|
|
24953
25197
|
if (!curRaw) return void 0;
|
|
24954
25198
|
if (instanceId && curRaw.instanceId !== instanceId) return void 0;
|
|
24955
25199
|
return { ...curRaw, ui_heartbeat_at: Date.now(), updatedAt: Date.now() };
|
|
24956
|
-
}, { compact: true, fsync: false, fsyncDir: false, renameFallback: "truncate" });
|
|
25200
|
+
}, { compact: true, fsync: false, fsyncDir: false, renameFallback: "truncate", timeoutMs: 0 });
|
|
24957
25201
|
} catch {
|
|
24958
25202
|
}
|
|
24959
25203
|
}
|
|
@@ -24975,36 +25219,129 @@ var EXIT_HARD_DELAY_MS = positiveIntEnv2("MIXDOG_TUI_HARD_EXIT_DELAY_MS", 500);
|
|
|
24975
25219
|
var EXIT_HARD_ENABLED = !/^(0|false|no|off)$/i.test(String(process.env.MIXDOG_TUI_HARD_EXIT || "1"));
|
|
24976
25220
|
var EXIT_DEBUG_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_EXIT_DEBUG || ""));
|
|
24977
25221
|
var PERF_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_PERF || ""));
|
|
25222
|
+
var LOOP_PROBE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_LOOP_PROBE || ""));
|
|
25223
|
+
var LOOP_PROBE_INTERVAL_MS = 1e3;
|
|
25224
|
+
var LOOP_PROBE_DRIFT_THRESHOLD_MS = 200;
|
|
24978
25225
|
function positiveIntEnv2(name, fallback) {
|
|
24979
25226
|
const value = Number(process.env[name]);
|
|
24980
25227
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
24981
25228
|
}
|
|
25229
|
+
function installTuiLoopProbe() {
|
|
25230
|
+
if (!LOOP_PROBE_ENABLED) return () => {
|
|
25231
|
+
};
|
|
25232
|
+
let last = performance3.now();
|
|
25233
|
+
const timer = setInterval(() => {
|
|
25234
|
+
const now = performance3.now();
|
|
25235
|
+
const drift = now - last - LOOP_PROBE_INTERVAL_MS;
|
|
25236
|
+
last = now;
|
|
25237
|
+
if (drift > LOOP_PROBE_DRIFT_THRESHOLD_MS) {
|
|
25238
|
+
try {
|
|
25239
|
+
process.stderr.write(`[mixdog-loop] stall=${drift.toFixed(0)}ms
|
|
25240
|
+
`);
|
|
25241
|
+
} catch {
|
|
25242
|
+
}
|
|
25243
|
+
}
|
|
25244
|
+
}, LOOP_PROBE_INTERVAL_MS);
|
|
25245
|
+
timer.unref?.();
|
|
25246
|
+
return () => {
|
|
25247
|
+
try {
|
|
25248
|
+
clearInterval(timer);
|
|
25249
|
+
} catch {
|
|
25250
|
+
}
|
|
25251
|
+
};
|
|
25252
|
+
}
|
|
24982
25253
|
var PERF_REPORT_EVERY = positiveIntEnv2("MIXDOG_TUI_PERF_EVERY", 60);
|
|
25254
|
+
var PERF_STALL_INTERVAL_MS = positiveIntEnv2("MIXDOG_TUI_PERF_STALL_INTERVAL_MS", 100);
|
|
25255
|
+
var PERF_STALL_THRESHOLD_MS = positiveIntEnv2("MIXDOG_TUI_PERF_STALL_MS", 80);
|
|
25256
|
+
var PERF_RENDER_GAP_MS = positiveIntEnv2("MIXDOG_TUI_PERF_RENDER_GAP_MS", 120);
|
|
25257
|
+
function perfLog(event, fields = {}) {
|
|
25258
|
+
if (!PERF_ENABLED) return;
|
|
25259
|
+
const elapsedMs = performance3.now() - BOOT_PROFILE_START2;
|
|
25260
|
+
const parts = [`[mixdog-perf] +${elapsedMs.toFixed(1)}ms`, event];
|
|
25261
|
+
for (const [key, value] of Object.entries(fields || {})) {
|
|
25262
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
25263
|
+
parts.push(`${key}=${String(value).replace(/\s+/g, "_")}`);
|
|
25264
|
+
}
|
|
25265
|
+
try {
|
|
25266
|
+
process.stderr.write(`${parts.join(" ")}
|
|
25267
|
+
`);
|
|
25268
|
+
} catch {
|
|
25269
|
+
}
|
|
25270
|
+
}
|
|
25271
|
+
function installTuiPerfProbe() {
|
|
25272
|
+
if (!PERF_ENABLED) return () => {
|
|
25273
|
+
};
|
|
25274
|
+
let last = performance3.now();
|
|
25275
|
+
let lastCpu = process.cpuUsage();
|
|
25276
|
+
perfLog("probe:start", {
|
|
25277
|
+
intervalMs: PERF_STALL_INTERVAL_MS,
|
|
25278
|
+
stallMs: PERF_STALL_THRESHOLD_MS,
|
|
25279
|
+
renderGapMs: PERF_RENDER_GAP_MS
|
|
25280
|
+
});
|
|
25281
|
+
const timer = setInterval(() => {
|
|
25282
|
+
const now = performance3.now();
|
|
25283
|
+
const elapsed = now - last;
|
|
25284
|
+
const lagMs = elapsed - PERF_STALL_INTERVAL_MS;
|
|
25285
|
+
const cpu = process.cpuUsage(lastCpu);
|
|
25286
|
+
last = now;
|
|
25287
|
+
lastCpu = process.cpuUsage();
|
|
25288
|
+
if (lagMs < PERF_STALL_THRESHOLD_MS) return;
|
|
25289
|
+
const mem = process.memoryUsage();
|
|
25290
|
+
perfLog("event-loop-stall", {
|
|
25291
|
+
lagMs: lagMs.toFixed(1),
|
|
25292
|
+
elapsedMs: elapsed.toFixed(1),
|
|
25293
|
+
cpuUserMs: (cpu.user / 1e3).toFixed(1),
|
|
25294
|
+
cpuSystemMs: (cpu.system / 1e3).toFixed(1),
|
|
25295
|
+
rssMb: (mem.rss / 1048576).toFixed(1),
|
|
25296
|
+
heapMb: (mem.heapUsed / 1048576).toFixed(1)
|
|
25297
|
+
});
|
|
25298
|
+
}, PERF_STALL_INTERVAL_MS);
|
|
25299
|
+
timer.unref?.();
|
|
25300
|
+
return () => {
|
|
25301
|
+
try {
|
|
25302
|
+
clearInterval(timer);
|
|
25303
|
+
} catch {
|
|
25304
|
+
}
|
|
25305
|
+
};
|
|
25306
|
+
}
|
|
24983
25307
|
function makeRenderProfiler() {
|
|
24984
25308
|
if (!PERF_ENABLED) return void 0;
|
|
24985
25309
|
let count = 0;
|
|
24986
25310
|
let sum = 0;
|
|
24987
25311
|
let max = 0;
|
|
24988
25312
|
let slow = 0;
|
|
25313
|
+
let maxGap = 0;
|
|
25314
|
+
let lastFrameAt = 0;
|
|
24989
25315
|
return ({ renderTime } = {}) => {
|
|
25316
|
+
const now = performance3.now();
|
|
24990
25317
|
const ms = Number(renderTime) || 0;
|
|
25318
|
+
const gap = lastFrameAt ? now - lastFrameAt : 0;
|
|
25319
|
+
lastFrameAt = now;
|
|
24991
25320
|
count += 1;
|
|
24992
25321
|
sum += ms;
|
|
24993
25322
|
if (ms > max) max = ms;
|
|
25323
|
+
if (gap > maxGap) maxGap = gap;
|
|
24994
25324
|
if (ms >= 16) slow += 1;
|
|
25325
|
+
if (gap >= PERF_RENDER_GAP_MS) {
|
|
25326
|
+
perfLog("render-gap", {
|
|
25327
|
+
gapMs: gap.toFixed(1),
|
|
25328
|
+
renderMs: ms.toFixed(2)
|
|
25329
|
+
});
|
|
25330
|
+
}
|
|
24995
25331
|
if (count >= PERF_REPORT_EVERY) {
|
|
24996
25332
|
const avg = sum / count;
|
|
24997
|
-
|
|
24998
|
-
|
|
24999
|
-
|
|
25000
|
-
|
|
25001
|
-
)
|
|
25002
|
-
|
|
25003
|
-
}
|
|
25333
|
+
perfLog("render-summary", {
|
|
25334
|
+
frames: count,
|
|
25335
|
+
avgMs: avg.toFixed(2),
|
|
25336
|
+
maxMs: max.toFixed(2),
|
|
25337
|
+
maxGapMs: maxGap.toFixed(1),
|
|
25338
|
+
slow16: slow
|
|
25339
|
+
});
|
|
25004
25340
|
count = 0;
|
|
25005
25341
|
sum = 0;
|
|
25006
25342
|
max = 0;
|
|
25007
25343
|
slow = 0;
|
|
25344
|
+
maxGap = 0;
|
|
25008
25345
|
}
|
|
25009
25346
|
};
|
|
25010
25347
|
}
|
|
@@ -25219,6 +25556,8 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
|
|
|
25219
25556
|
return 1;
|
|
25220
25557
|
}
|
|
25221
25558
|
const restoreStderr = installTuiStderrGuard();
|
|
25559
|
+
const stopPerfProbe = installTuiPerfProbe();
|
|
25560
|
+
const stopLoopProbe = installTuiLoopProbe();
|
|
25222
25561
|
const restorePrimedInput = () => drainStdin(process.stdin);
|
|
25223
25562
|
let restored = false;
|
|
25224
25563
|
const restoreTerminal = () => {
|
|
@@ -25250,6 +25589,8 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
|
|
|
25250
25589
|
bootProfile2("store:ready", { ms: (performance3.now() - startedAt).toFixed(1) });
|
|
25251
25590
|
} catch (error) {
|
|
25252
25591
|
splash.stop();
|
|
25592
|
+
stopPerfProbe();
|
|
25593
|
+
stopLoopProbe();
|
|
25253
25594
|
restoreTerminal();
|
|
25254
25595
|
try {
|
|
25255
25596
|
process.off("exit", restoreTerminal);
|
|
@@ -25334,6 +25675,8 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
|
|
|
25334
25675
|
}
|
|
25335
25676
|
await waitUntilExit();
|
|
25336
25677
|
} finally {
|
|
25678
|
+
stopPerfProbe();
|
|
25679
|
+
stopLoopProbe();
|
|
25337
25680
|
clearInterval(uiHeartbeatTimer);
|
|
25338
25681
|
for (const [stream, event, handler] of stdioDeathListeners.splice(0)) {
|
|
25339
25682
|
try {
|