mixdog 0.9.16 → 0.9.17
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/bench/cache-probe-tasks.json +1 -1
- package/scripts/bench/lead-review-tasks-r3.json +1 -1
- package/scripts/bench/r4-mixed-tasks.json +1 -1
- package/scripts/bench/review-tasks.json +1 -1
- package/scripts/build-runtime-windows.ps1 +242 -242
- package/scripts/provider-toolcall-test.mjs +79 -2
- package/scripts/recall-usecase-cases.json +1 -1
- package/scripts/smoke-runtime-negative.ps1 +106 -106
- package/scripts/tool-efficiency-diag.mjs +1 -1
- package/src/mixdog-session-runtime.mjs +12 -0
- package/src/rules/lead/02-channels.md +3 -3
- 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/lib/runtime-paths.mjs +8 -19
- 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/tui/app/transcript-window.mjs +115 -6
- package/src/tui/app/use-transcript-window.mjs +58 -7
- package/src/tui/components/StatusLine.jsx +1 -1
- package/src/tui/dist/index.mjs +373 -81
- package/src/tui/engine/tui-steering-persist.mjs +66 -35
- package/src/tui/engine.mjs +6 -4
- 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 {
|
|
@@ -11007,14 +11113,13 @@ function estimateTranscriptItemRowsCached(item, columns, toolOutputExpanded) {
|
|
|
11007
11113
|
if (!item) return Math.max(1, Math.ceil(estimateTranscriptItemRows(item, columns, toolOutputExpanded)));
|
|
11008
11114
|
if (shouldSuppressFullyFailedToolItem(item)) return 0;
|
|
11009
11115
|
if (item.kind === "assistant" && item.streaming) {
|
|
11010
|
-
const estimate = streamingEstimateRows(item, columns, toolOutputExpanded);
|
|
11011
11116
|
const toolExpanded2 = toolOutputExpanded ? 1 : 0;
|
|
11012
11117
|
const idEntry = streamingMeasuredRowsById.get(item.id);
|
|
11013
11118
|
if (idEntry && idEntry.rows > 0 && idEntry.columns === columns && idEntry.toolExpanded === toolExpanded2) {
|
|
11014
|
-
return
|
|
11119
|
+
return idEntry.rows;
|
|
11015
11120
|
}
|
|
11016
11121
|
if (idEntry) streamingMeasuredRowsById.delete(item.id);
|
|
11017
|
-
return
|
|
11122
|
+
return streamingEstimateRows(item, columns, toolOutputExpanded);
|
|
11018
11123
|
}
|
|
11019
11124
|
if (item.kind === "assistant" && streamingMeasuredRowsById.has(item.id)) {
|
|
11020
11125
|
streamingMeasuredRowsById.delete(item.id);
|
|
@@ -11047,6 +11152,61 @@ function buildTranscriptRowIndex(items, {
|
|
|
11047
11152
|
}
|
|
11048
11153
|
return { rows, prefixRows, totalRows: prefixRows[allItems.length] || 0 };
|
|
11049
11154
|
}
|
|
11155
|
+
function trailingStreamingItem(allItems) {
|
|
11156
|
+
const last = allItems.length > 0 ? allItems[allItems.length - 1] : null;
|
|
11157
|
+
return last && last.kind === "assistant" && last.streaming ? last : null;
|
|
11158
|
+
}
|
|
11159
|
+
function buildTranscriptRowIndexIncremental(items, {
|
|
11160
|
+
columns = 80,
|
|
11161
|
+
toolOutputExpanded = false,
|
|
11162
|
+
suppressMeasuredRowHeights = false,
|
|
11163
|
+
measuredRowsVersion = 0,
|
|
11164
|
+
cacheRef = null,
|
|
11165
|
+
prefixSig = null
|
|
11166
|
+
} = {}) {
|
|
11167
|
+
const allItems = Array.isArray(items) ? items : [];
|
|
11168
|
+
const tail = trailingStreamingItem(allItems);
|
|
11169
|
+
const holder = cacheRef || { current: null };
|
|
11170
|
+
if (!tail) {
|
|
11171
|
+
holder.current = null;
|
|
11172
|
+
return buildTranscriptRowIndex(allItems, {
|
|
11173
|
+
columns,
|
|
11174
|
+
toolOutputExpanded,
|
|
11175
|
+
suppressMeasuredRowHeights
|
|
11176
|
+
});
|
|
11177
|
+
}
|
|
11178
|
+
const prefixLen = allItems.length - 1;
|
|
11179
|
+
const cache = holder.current;
|
|
11180
|
+
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) {
|
|
11181
|
+
{
|
|
11182
|
+
const tailMeasured = suppressMeasuredRowHeights ? null : measuredTranscriptRows(tail, columns, toolOutputExpanded);
|
|
11183
|
+
const tailRows = tailMeasured != null ? tailMeasured : estimateTranscriptItemRowsCached(tail, columns, toolOutputExpanded);
|
|
11184
|
+
const rows = cache.prefixRowsArr.slice();
|
|
11185
|
+
rows.push(tailRows);
|
|
11186
|
+
const prefixRows = cache.prefixPrefixRows.slice();
|
|
11187
|
+
prefixRows.push(cache.prefixTotal + tailRows);
|
|
11188
|
+
return { rows, prefixRows, totalRows: prefixRows[allItems.length] || 0 };
|
|
11189
|
+
}
|
|
11190
|
+
}
|
|
11191
|
+
const full = buildTranscriptRowIndex(allItems, {
|
|
11192
|
+
columns,
|
|
11193
|
+
toolOutputExpanded,
|
|
11194
|
+
suppressMeasuredRowHeights
|
|
11195
|
+
});
|
|
11196
|
+
holder.current = {
|
|
11197
|
+
columns,
|
|
11198
|
+
toolExpanded: toolOutputExpanded ? 1 : 0,
|
|
11199
|
+
suppress: suppressMeasuredRowHeights,
|
|
11200
|
+
version: measuredRowsVersion,
|
|
11201
|
+
prefixLen,
|
|
11202
|
+
prefixSig,
|
|
11203
|
+
tailId: tail.id,
|
|
11204
|
+
prefixRowsArr: full.rows.slice(0, prefixLen),
|
|
11205
|
+
prefixPrefixRows: full.prefixRows.slice(0, prefixLen + 1),
|
|
11206
|
+
prefixTotal: full.prefixRows[prefixLen] || 0
|
|
11207
|
+
};
|
|
11208
|
+
return full;
|
|
11209
|
+
}
|
|
11050
11210
|
var transcriptSigPartCache = /* @__PURE__ */ new WeakMap();
|
|
11051
11211
|
function transcriptStructureSignature(items, columns, toolOutputExpanded) {
|
|
11052
11212
|
const list = Array.isArray(items) ? items : [];
|
|
@@ -11880,6 +12040,7 @@ function useTranscriptWindow({
|
|
|
11880
12040
|
}) {
|
|
11881
12041
|
const transcriptTotalRowsRef = useRef8(0);
|
|
11882
12042
|
const preservedScrollDeltaRef = useRef8(0);
|
|
12043
|
+
const incrementalRowIndexCacheRef = useRef8(null);
|
|
11883
12044
|
const prevViewportGeomRef = useRef8({ contentHeight: 0, floatingPanelRows: 0 });
|
|
11884
12045
|
const transcriptItemElsRef = useRef8(/* @__PURE__ */ new Map());
|
|
11885
12046
|
const transcriptMeasureRefCache = useRef8(/* @__PURE__ */ new Map());
|
|
@@ -11907,10 +12068,21 @@ function useTranscriptWindow({
|
|
|
11907
12068
|
}
|
|
11908
12069
|
return fn;
|
|
11909
12070
|
}, []);
|
|
11910
|
-
const
|
|
11911
|
-
()
|
|
11912
|
-
|
|
12071
|
+
const streamingTailItem = useMemo(() => {
|
|
12072
|
+
const last = (items || []).length > 0 ? items[items.length - 1] : null;
|
|
12073
|
+
return last && last.kind === "assistant" && last.streaming ? last : null;
|
|
12074
|
+
}, [items]);
|
|
12075
|
+
const prefixLen = streamingTailItem ? (items || []).length - 1 : (items || []).length;
|
|
12076
|
+
const prefixSig = useMemo(
|
|
12077
|
+
() => transcriptStructureSignature(
|
|
12078
|
+
streamingTailItem ? (items || []).slice(0, prefixLen) : items || [],
|
|
12079
|
+
frameColumns,
|
|
12080
|
+
toolOutputExpanded
|
|
12081
|
+
),
|
|
12082
|
+
[items, streamingTailItem, prefixLen, frameColumns, toolOutputExpanded]
|
|
11913
12083
|
);
|
|
12084
|
+
const tailSig = streamingTailItem ? `a${streamingTailItem.id}:${estimateTranscriptItemRowsCached(streamingTailItem, frameColumns, toolOutputExpanded)}` : "_";
|
|
12085
|
+
const transcriptStructureSig = `${prefixSig}#${tailSig}`;
|
|
11914
12086
|
const transcriptStreamingActive = (items || []).some(
|
|
11915
12087
|
(item) => item?.kind === "assistant" && item?.streaming
|
|
11916
12088
|
);
|
|
@@ -11921,12 +12093,15 @@ function useTranscriptWindow({
|
|
|
11921
12093
|
const hasStreamingReadingAnchor = !!transcriptAnchorRef.current || transcriptAnchorDirtyRef.current;
|
|
11922
12094
|
const bottomPinnedForMeasure = transcriptPinnedForStreaming;
|
|
11923
12095
|
const suppressMeasuredRowHeights = bottomPinnedForMeasure || transcriptStreamingActive && (scrolledUpForStreamingMeasure && hasStreamingReadingAnchor || scrolledUpForStreamingMeasure && prevEstimateGeometry && !transcriptPinnedForStreaming);
|
|
11924
|
-
const transcriptRowIndex = useMemo(() =>
|
|
12096
|
+
const transcriptRowIndex = useMemo(() => buildTranscriptRowIndexIncremental(items, {
|
|
11925
12097
|
columns: frameColumns,
|
|
11926
12098
|
toolOutputExpanded,
|
|
11927
|
-
suppressMeasuredRowHeights
|
|
11928
|
-
|
|
11929
|
-
|
|
12099
|
+
suppressMeasuredRowHeights,
|
|
12100
|
+
measuredRowsVersion,
|
|
12101
|
+
cacheRef: incrementalRowIndexCacheRef,
|
|
12102
|
+
prefixSig
|
|
12103
|
+
// 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
|
|
12104
|
+
}), [prefixSig, tailSig, measuredRowsVersion, suppressMeasuredRowHeights]);
|
|
11930
12105
|
const hasReadingAnchor = !!transcriptAnchorRef.current && !transcriptAnchorDirtyRef.current;
|
|
11931
12106
|
const scrolledUpRows = Math.max(0, Number(scrollTargetRef.current) || 0);
|
|
11932
12107
|
const scrolledUp = scrolledUpRows > transcriptBottomSlackRows;
|
|
@@ -22149,6 +22324,13 @@ import { randomBytes as randomBytes3 } from "crypto";
|
|
|
22149
22324
|
import { join as join7 } from "path";
|
|
22150
22325
|
var PENDING_MESSAGES_FILE = "session-pending-messages.json";
|
|
22151
22326
|
var PENDING_MESSAGES_MODE = 384;
|
|
22327
|
+
var _persistChain = Promise.resolve();
|
|
22328
|
+
function _serialize(task) {
|
|
22329
|
+
const run = _persistChain.then(task, task);
|
|
22330
|
+
_persistChain = run.catch(() => {
|
|
22331
|
+
});
|
|
22332
|
+
return run;
|
|
22333
|
+
}
|
|
22152
22334
|
function pendingMessagesPath() {
|
|
22153
22335
|
return join7(resolvePluginData(), PENDING_MESSAGES_FILE);
|
|
22154
22336
|
}
|
|
@@ -22216,59 +22398,67 @@ function removePersistRow(q, entry) {
|
|
|
22216
22398
|
}
|
|
22217
22399
|
function appendTuiSteeringPersist(leadSessionId, entry) {
|
|
22218
22400
|
const text = entryPersistText(entry);
|
|
22219
|
-
if (!text) return;
|
|
22401
|
+
if (!text) return Promise.resolve();
|
|
22220
22402
|
const key = tuiSteeringSessionKey(leadSessionId);
|
|
22221
|
-
if (!key) return;
|
|
22403
|
+
if (!key) return Promise.resolve();
|
|
22222
22404
|
if (!entry.steeringPersistId) entry.steeringPersistId = newSteeringPersistId();
|
|
22223
22405
|
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) {
|
|
22406
|
+
return _serialize(async () => {
|
|
22236
22407
|
try {
|
|
22237
|
-
|
|
22408
|
+
await updateJsonAtomic(pendingMessagesPath(), (raw) => {
|
|
22409
|
+
const next = normalizePendingStore(raw);
|
|
22410
|
+
const q = Array.isArray(next.sessions[key]) ? next.sessions[key] : [];
|
|
22411
|
+
q.push(record);
|
|
22412
|
+
next.sessions[key] = q;
|
|
22413
|
+
const now = Date.now();
|
|
22414
|
+
next.updatedAt = now;
|
|
22415
|
+
touchPendingSessionEntry(next, key, now);
|
|
22416
|
+
return next;
|
|
22417
|
+
}, { compact: true, lock: true, mode: PENDING_MESSAGES_MODE, fsync: false });
|
|
22418
|
+
} catch (err) {
|
|
22419
|
+
try {
|
|
22420
|
+
process.stderr.write(`[tui] steering-queue append failed sessionId=${leadSessionId}: ${err?.message || err}
|
|
22238
22421
|
`);
|
|
22239
|
-
|
|
22422
|
+
} catch {
|
|
22423
|
+
}
|
|
22240
22424
|
}
|
|
22241
|
-
}
|
|
22425
|
+
});
|
|
22242
22426
|
}
|
|
22243
22427
|
function dropTuiSteeringPersist(leadSessionId, entries) {
|
|
22244
22428
|
const key = tuiSteeringSessionKey(leadSessionId);
|
|
22245
|
-
if (!key) return;
|
|
22429
|
+
if (!key) return Promise.resolve();
|
|
22246
22430
|
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) {
|
|
22431
|
+
if (batch.length === 0) return Promise.resolve();
|
|
22432
|
+
return _serialize(async () => {
|
|
22266
22433
|
try {
|
|
22267
|
-
|
|
22434
|
+
await updateJsonAtomic(pendingMessagesPath(), (raw) => {
|
|
22435
|
+
const next = normalizePendingStore(raw);
|
|
22436
|
+
const q = Array.isArray(next.sessions[key]) ? next.sessions[key].slice() : [];
|
|
22437
|
+
if (q.length === 0) return void 0;
|
|
22438
|
+
for (const entry of batch) {
|
|
22439
|
+
removePersistRow(q, entry);
|
|
22440
|
+
}
|
|
22441
|
+
if (q.length === 0) {
|
|
22442
|
+
delete next.sessions[key];
|
|
22443
|
+
if (next.sessionTouchedAt) delete next.sessionTouchedAt[key];
|
|
22444
|
+
} else {
|
|
22445
|
+
next.sessions[key] = q;
|
|
22446
|
+
}
|
|
22447
|
+
next.updatedAt = Date.now();
|
|
22448
|
+
return next;
|
|
22449
|
+
}, { compact: true, lock: true, mode: PENDING_MESSAGES_MODE, fsync: false });
|
|
22450
|
+
} catch (err) {
|
|
22451
|
+
try {
|
|
22452
|
+
process.stderr.write(`[tui] steering-queue drop failed sessionId=${leadSessionId}: ${err?.message || err}
|
|
22268
22453
|
`);
|
|
22269
|
-
|
|
22454
|
+
} catch {
|
|
22455
|
+
}
|
|
22270
22456
|
}
|
|
22271
|
-
}
|
|
22457
|
+
});
|
|
22458
|
+
}
|
|
22459
|
+
function flushTuiSteeringPersist() {
|
|
22460
|
+
return _persistChain.catch(() => {
|
|
22461
|
+
});
|
|
22272
22462
|
}
|
|
22273
22463
|
function drainedRowToRestore(row) {
|
|
22274
22464
|
if (typeof row === "string") {
|
|
@@ -22281,27 +22471,29 @@ function drainedRowToRestore(row) {
|
|
|
22281
22471
|
}
|
|
22282
22472
|
function drainTuiSteeringPersist(leadSessionId) {
|
|
22283
22473
|
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) {
|
|
22474
|
+
if (!key) return Promise.resolve([]);
|
|
22475
|
+
return _serialize(async () => {
|
|
22476
|
+
let drained = [];
|
|
22298
22477
|
try {
|
|
22299
|
-
|
|
22478
|
+
await updateJsonAtomic(pendingMessagesPath(), (raw) => {
|
|
22479
|
+
const next = normalizePendingStore(raw);
|
|
22480
|
+
const q = Array.isArray(next.sessions[key]) ? next.sessions[key] : [];
|
|
22481
|
+
drained = q.map(drainedRowToRestore).filter(Boolean);
|
|
22482
|
+
if (drained.length === 0) return void 0;
|
|
22483
|
+
delete next.sessions[key];
|
|
22484
|
+
if (next.sessionTouchedAt) delete next.sessionTouchedAt[key];
|
|
22485
|
+
next.updatedAt = Date.now();
|
|
22486
|
+
return next;
|
|
22487
|
+
}, { compact: true, lock: true, mode: PENDING_MESSAGES_MODE, fsync: false });
|
|
22488
|
+
} catch (err) {
|
|
22489
|
+
try {
|
|
22490
|
+
process.stderr.write(`[tui] steering-queue drain failed sessionId=${leadSessionId}: ${err?.message || err}
|
|
22300
22491
|
`);
|
|
22301
|
-
|
|
22492
|
+
} catch {
|
|
22493
|
+
}
|
|
22302
22494
|
}
|
|
22303
|
-
|
|
22304
|
-
|
|
22495
|
+
return drained;
|
|
22496
|
+
});
|
|
22305
22497
|
}
|
|
22306
22498
|
|
|
22307
22499
|
// src/tui/engine.mjs
|
|
@@ -23638,8 +23830,8 @@ async function createEngineSession({
|
|
|
23638
23830
|
commitSteeringQueueEntries(batch);
|
|
23639
23831
|
return out;
|
|
23640
23832
|
}
|
|
23641
|
-
function restoreLeadSteeringFromDisk() {
|
|
23642
|
-
const rows = drainTuiSteeringPersist(leadSessionId());
|
|
23833
|
+
async function restoreLeadSteeringFromDisk() {
|
|
23834
|
+
const rows = await drainTuiSteeringPersist(leadSessionId());
|
|
23643
23835
|
if (!rows.length) return;
|
|
23644
23836
|
const restored = [];
|
|
23645
23837
|
for (const row of rows) {
|
|
@@ -23820,7 +24012,7 @@ async function createEngineSession({
|
|
|
23820
24012
|
syncContextStats({ allowEstimated: true });
|
|
23821
24013
|
return state.stats;
|
|
23822
24014
|
};
|
|
23823
|
-
restoreLeadSteeringFromDisk();
|
|
24015
|
+
void restoreLeadSteeringFromDisk();
|
|
23824
24016
|
return {
|
|
23825
24017
|
getState: () => state,
|
|
23826
24018
|
patchItem,
|
|
@@ -24743,7 +24935,7 @@ async function createEngineSession({
|
|
|
24743
24935
|
...routeState(),
|
|
24744
24936
|
stats: { ...state.stats }
|
|
24745
24937
|
});
|
|
24746
|
-
restoreLeadSteeringFromDisk();
|
|
24938
|
+
void restoreLeadSteeringFromDisk();
|
|
24747
24939
|
return true;
|
|
24748
24940
|
} finally {
|
|
24749
24941
|
set({ commandBusy: false, commandStatus: null });
|
|
@@ -24768,6 +24960,7 @@ async function createEngineSession({
|
|
|
24768
24960
|
}
|
|
24769
24961
|
unsubscribeRemoteState = null;
|
|
24770
24962
|
denyAllToolApprovals("runtime closing");
|
|
24963
|
+
await flushTuiSteeringPersist();
|
|
24771
24964
|
await runtime.close(reason, options);
|
|
24772
24965
|
listeners.clear();
|
|
24773
24966
|
}
|
|
@@ -24953,7 +25146,7 @@ function touchUiHeartbeat(instanceId) {
|
|
|
24953
25146
|
if (!curRaw) return void 0;
|
|
24954
25147
|
if (instanceId && curRaw.instanceId !== instanceId) return void 0;
|
|
24955
25148
|
return { ...curRaw, ui_heartbeat_at: Date.now(), updatedAt: Date.now() };
|
|
24956
|
-
}, { compact: true, fsync: false, fsyncDir: false, renameFallback: "truncate" });
|
|
25149
|
+
}, { compact: true, fsync: false, fsyncDir: false, renameFallback: "truncate", timeoutMs: 0 });
|
|
24957
25150
|
} catch {
|
|
24958
25151
|
}
|
|
24959
25152
|
}
|
|
@@ -24975,36 +25168,129 @@ var EXIT_HARD_DELAY_MS = positiveIntEnv2("MIXDOG_TUI_HARD_EXIT_DELAY_MS", 500);
|
|
|
24975
25168
|
var EXIT_HARD_ENABLED = !/^(0|false|no|off)$/i.test(String(process.env.MIXDOG_TUI_HARD_EXIT || "1"));
|
|
24976
25169
|
var EXIT_DEBUG_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_EXIT_DEBUG || ""));
|
|
24977
25170
|
var PERF_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_PERF || ""));
|
|
25171
|
+
var LOOP_PROBE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_LOOP_PROBE || ""));
|
|
25172
|
+
var LOOP_PROBE_INTERVAL_MS = 1e3;
|
|
25173
|
+
var LOOP_PROBE_DRIFT_THRESHOLD_MS = 200;
|
|
24978
25174
|
function positiveIntEnv2(name, fallback) {
|
|
24979
25175
|
const value = Number(process.env[name]);
|
|
24980
25176
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
24981
25177
|
}
|
|
25178
|
+
function installTuiLoopProbe() {
|
|
25179
|
+
if (!LOOP_PROBE_ENABLED) return () => {
|
|
25180
|
+
};
|
|
25181
|
+
let last = performance3.now();
|
|
25182
|
+
const timer = setInterval(() => {
|
|
25183
|
+
const now = performance3.now();
|
|
25184
|
+
const drift = now - last - LOOP_PROBE_INTERVAL_MS;
|
|
25185
|
+
last = now;
|
|
25186
|
+
if (drift > LOOP_PROBE_DRIFT_THRESHOLD_MS) {
|
|
25187
|
+
try {
|
|
25188
|
+
process.stderr.write(`[mixdog-loop] stall=${drift.toFixed(0)}ms
|
|
25189
|
+
`);
|
|
25190
|
+
} catch {
|
|
25191
|
+
}
|
|
25192
|
+
}
|
|
25193
|
+
}, LOOP_PROBE_INTERVAL_MS);
|
|
25194
|
+
timer.unref?.();
|
|
25195
|
+
return () => {
|
|
25196
|
+
try {
|
|
25197
|
+
clearInterval(timer);
|
|
25198
|
+
} catch {
|
|
25199
|
+
}
|
|
25200
|
+
};
|
|
25201
|
+
}
|
|
24982
25202
|
var PERF_REPORT_EVERY = positiveIntEnv2("MIXDOG_TUI_PERF_EVERY", 60);
|
|
25203
|
+
var PERF_STALL_INTERVAL_MS = positiveIntEnv2("MIXDOG_TUI_PERF_STALL_INTERVAL_MS", 100);
|
|
25204
|
+
var PERF_STALL_THRESHOLD_MS = positiveIntEnv2("MIXDOG_TUI_PERF_STALL_MS", 80);
|
|
25205
|
+
var PERF_RENDER_GAP_MS = positiveIntEnv2("MIXDOG_TUI_PERF_RENDER_GAP_MS", 120);
|
|
25206
|
+
function perfLog(event, fields = {}) {
|
|
25207
|
+
if (!PERF_ENABLED) return;
|
|
25208
|
+
const elapsedMs = performance3.now() - BOOT_PROFILE_START2;
|
|
25209
|
+
const parts = [`[mixdog-perf] +${elapsedMs.toFixed(1)}ms`, event];
|
|
25210
|
+
for (const [key, value] of Object.entries(fields || {})) {
|
|
25211
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
25212
|
+
parts.push(`${key}=${String(value).replace(/\s+/g, "_")}`);
|
|
25213
|
+
}
|
|
25214
|
+
try {
|
|
25215
|
+
process.stderr.write(`${parts.join(" ")}
|
|
25216
|
+
`);
|
|
25217
|
+
} catch {
|
|
25218
|
+
}
|
|
25219
|
+
}
|
|
25220
|
+
function installTuiPerfProbe() {
|
|
25221
|
+
if (!PERF_ENABLED) return () => {
|
|
25222
|
+
};
|
|
25223
|
+
let last = performance3.now();
|
|
25224
|
+
let lastCpu = process.cpuUsage();
|
|
25225
|
+
perfLog("probe:start", {
|
|
25226
|
+
intervalMs: PERF_STALL_INTERVAL_MS,
|
|
25227
|
+
stallMs: PERF_STALL_THRESHOLD_MS,
|
|
25228
|
+
renderGapMs: PERF_RENDER_GAP_MS
|
|
25229
|
+
});
|
|
25230
|
+
const timer = setInterval(() => {
|
|
25231
|
+
const now = performance3.now();
|
|
25232
|
+
const elapsed = now - last;
|
|
25233
|
+
const lagMs = elapsed - PERF_STALL_INTERVAL_MS;
|
|
25234
|
+
const cpu = process.cpuUsage(lastCpu);
|
|
25235
|
+
last = now;
|
|
25236
|
+
lastCpu = process.cpuUsage();
|
|
25237
|
+
if (lagMs < PERF_STALL_THRESHOLD_MS) return;
|
|
25238
|
+
const mem = process.memoryUsage();
|
|
25239
|
+
perfLog("event-loop-stall", {
|
|
25240
|
+
lagMs: lagMs.toFixed(1),
|
|
25241
|
+
elapsedMs: elapsed.toFixed(1),
|
|
25242
|
+
cpuUserMs: (cpu.user / 1e3).toFixed(1),
|
|
25243
|
+
cpuSystemMs: (cpu.system / 1e3).toFixed(1),
|
|
25244
|
+
rssMb: (mem.rss / 1048576).toFixed(1),
|
|
25245
|
+
heapMb: (mem.heapUsed / 1048576).toFixed(1)
|
|
25246
|
+
});
|
|
25247
|
+
}, PERF_STALL_INTERVAL_MS);
|
|
25248
|
+
timer.unref?.();
|
|
25249
|
+
return () => {
|
|
25250
|
+
try {
|
|
25251
|
+
clearInterval(timer);
|
|
25252
|
+
} catch {
|
|
25253
|
+
}
|
|
25254
|
+
};
|
|
25255
|
+
}
|
|
24983
25256
|
function makeRenderProfiler() {
|
|
24984
25257
|
if (!PERF_ENABLED) return void 0;
|
|
24985
25258
|
let count = 0;
|
|
24986
25259
|
let sum = 0;
|
|
24987
25260
|
let max = 0;
|
|
24988
25261
|
let slow = 0;
|
|
25262
|
+
let maxGap = 0;
|
|
25263
|
+
let lastFrameAt = 0;
|
|
24989
25264
|
return ({ renderTime } = {}) => {
|
|
25265
|
+
const now = performance3.now();
|
|
24990
25266
|
const ms = Number(renderTime) || 0;
|
|
25267
|
+
const gap = lastFrameAt ? now - lastFrameAt : 0;
|
|
25268
|
+
lastFrameAt = now;
|
|
24991
25269
|
count += 1;
|
|
24992
25270
|
sum += ms;
|
|
24993
25271
|
if (ms > max) max = ms;
|
|
25272
|
+
if (gap > maxGap) maxGap = gap;
|
|
24994
25273
|
if (ms >= 16) slow += 1;
|
|
25274
|
+
if (gap >= PERF_RENDER_GAP_MS) {
|
|
25275
|
+
perfLog("render-gap", {
|
|
25276
|
+
gapMs: gap.toFixed(1),
|
|
25277
|
+
renderMs: ms.toFixed(2)
|
|
25278
|
+
});
|
|
25279
|
+
}
|
|
24995
25280
|
if (count >= PERF_REPORT_EVERY) {
|
|
24996
25281
|
const avg = sum / count;
|
|
24997
|
-
|
|
24998
|
-
|
|
24999
|
-
|
|
25000
|
-
|
|
25001
|
-
)
|
|
25002
|
-
|
|
25003
|
-
}
|
|
25282
|
+
perfLog("render-summary", {
|
|
25283
|
+
frames: count,
|
|
25284
|
+
avgMs: avg.toFixed(2),
|
|
25285
|
+
maxMs: max.toFixed(2),
|
|
25286
|
+
maxGapMs: maxGap.toFixed(1),
|
|
25287
|
+
slow16: slow
|
|
25288
|
+
});
|
|
25004
25289
|
count = 0;
|
|
25005
25290
|
sum = 0;
|
|
25006
25291
|
max = 0;
|
|
25007
25292
|
slow = 0;
|
|
25293
|
+
maxGap = 0;
|
|
25008
25294
|
}
|
|
25009
25295
|
};
|
|
25010
25296
|
}
|
|
@@ -25219,6 +25505,8 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
|
|
|
25219
25505
|
return 1;
|
|
25220
25506
|
}
|
|
25221
25507
|
const restoreStderr = installTuiStderrGuard();
|
|
25508
|
+
const stopPerfProbe = installTuiPerfProbe();
|
|
25509
|
+
const stopLoopProbe = installTuiLoopProbe();
|
|
25222
25510
|
const restorePrimedInput = () => drainStdin(process.stdin);
|
|
25223
25511
|
let restored = false;
|
|
25224
25512
|
const restoreTerminal = () => {
|
|
@@ -25250,6 +25538,8 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
|
|
|
25250
25538
|
bootProfile2("store:ready", { ms: (performance3.now() - startedAt).toFixed(1) });
|
|
25251
25539
|
} catch (error) {
|
|
25252
25540
|
splash.stop();
|
|
25541
|
+
stopPerfProbe();
|
|
25542
|
+
stopLoopProbe();
|
|
25253
25543
|
restoreTerminal();
|
|
25254
25544
|
try {
|
|
25255
25545
|
process.off("exit", restoreTerminal);
|
|
@@ -25334,6 +25624,8 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
|
|
|
25334
25624
|
}
|
|
25335
25625
|
await waitUntilExit();
|
|
25336
25626
|
} finally {
|
|
25627
|
+
stopPerfProbe();
|
|
25628
|
+
stopLoopProbe();
|
|
25337
25629
|
clearInterval(uiHeartbeatTimer);
|
|
25338
25630
|
for (const [stream, event, handler] of stdioDeathListeners.splice(0)) {
|
|
25339
25631
|
try {
|