mixdog 0.9.38 → 0.9.40
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 +9 -4
- package/scripts/abort-recovery-test.mjs +43 -2
- package/scripts/agent-tag-reuse-smoke.mjs +146 -6
- package/scripts/agent-terminal-reap-test.mjs +127 -0
- package/scripts/agent-trace-io-test.mjs +69 -0
- package/scripts/code-graph-disk-hit-test.mjs +224 -0
- package/scripts/execution-completion-dedup-test.mjs +157 -0
- package/scripts/execution-pending-resume-kick-test.mjs +57 -2
- package/scripts/execution-resume-esc-integration-test.mjs +176 -0
- package/scripts/explore-bench.mjs +38 -2
- package/scripts/explore-prompt-policy-test.mjs +152 -11
- package/scripts/find-fuzzy-hidden-test.mjs +122 -0
- package/scripts/internal-comms-bench-test.mjs +226 -0
- package/scripts/internal-comms-bench.mjs +185 -58
- package/scripts/internal-comms-smoke.mjs +171 -23
- package/scripts/live-worker-smoke.mjs +38 -2
- package/scripts/memory-cycle-routing-test.mjs +111 -0
- package/scripts/memory-rule-contract-test.mjs +93 -0
- package/scripts/output-style-smoke.mjs +2 -2
- package/scripts/rg-runner-test.mjs +240 -0
- package/scripts/routing-corpus-test.mjs +349 -0
- package/scripts/routing-corpus.mjs +211 -32
- package/scripts/session-orphan-sweep-test.mjs +83 -0
- package/scripts/steering-drain-buckets-test.mjs +316 -2
- package/scripts/tool-smoke.mjs +21 -13
- package/scripts/tool-tui-presentation-test.mjs +202 -0
- package/scripts/tui-transcript-perf-test.mjs +279 -0
- package/src/agents/heavy-worker/AGENT.md +10 -7
- package/src/agents/reviewer/AGENT.md +6 -4
- package/src/agents/worker/AGENT.md +7 -5
- package/src/rules/agent/00-common.md +4 -4
- package/src/rules/agent/00-core.md +11 -14
- package/src/rules/agent/20-skip-protocol.md +3 -3
- package/src/rules/agent/30-explorer.md +50 -60
- package/src/rules/agent/40-cycle1-agent.md +15 -24
- package/src/rules/agent/41-cycle2-agent.md +33 -57
- package/src/rules/agent/42-cycle3-agent.md +28 -42
- package/src/rules/lead/01-general.md +7 -10
- package/src/rules/lead/lead-brief.md +11 -14
- package/src/rules/lead/lead-tool.md +6 -5
- package/src/rules/shared/01-tool.md +44 -45
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +44 -1
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +20 -5
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +21 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +17 -38
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +17 -8
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +270 -78
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/delivered-completions.mjs +123 -0
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +15 -2
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +67 -2
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +232 -43
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +43 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +27 -13
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +25 -20
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +118 -53
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +106 -29
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +151 -64
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +37 -13
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +84 -49
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +172 -23
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +3 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +68 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +17 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +24 -10
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -7
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -4
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +4 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +12 -2
- package/src/runtime/shared/buffered-appender.mjs +13 -2
- package/src/runtime/shared/tool-primitives.mjs +4 -1
- package/src/runtime/shared/tool-status.mjs +27 -0
- package/src/runtime/shared/tool-surface.mjs +6 -3
- package/src/session-runtime/config-helpers.mjs +14 -0
- package/src/session-runtime/context-status.mjs +1 -0
- package/src/session-runtime/effort.mjs +6 -2
- package/src/session-runtime/lifecycle-api.mjs +4 -0
- package/src/session-runtime/model-recency.mjs +5 -2
- package/src/session-runtime/runtime-core.mjs +36 -2
- package/src/session-runtime/session-turn-api.mjs +12 -0
- package/src/session-runtime/tool-catalog.mjs +34 -0
- package/src/standalone/agent-tool/notify.mjs +13 -0
- package/src/standalone/agent-tool.mjs +53 -70
- package/src/standalone/explore-tool.mjs +6 -7
- package/src/tui/App.jsx +33 -1
- package/src/tui/app/model-options.mjs +5 -3
- package/src/tui/app/model-picker.mjs +12 -24
- package/src/tui/app/transcript-window.mjs +10 -10
- package/src/tui/app/use-transcript-window.mjs +38 -56
- package/src/tui/components/ToolExecution.jsx +11 -6
- package/src/tui/components/TranscriptItem.jsx +1 -1
- package/src/tui/components/tool-execution/surface-detail.mjs +24 -10
- package/src/tui/components/tool-execution/text-format.mjs +10 -19
- package/src/tui/dist/index.mjs +866 -300
- package/src/tui/engine/agent-job-feed.mjs +216 -19
- package/src/tui/engine/agent-response-tail.mjs +68 -0
- package/src/tui/engine/notification-plan.mjs +16 -0
- package/src/tui/engine/session-api.mjs +14 -2
- package/src/tui/engine/session-flow.mjs +22 -2
- package/src/tui/engine/tool-card-results.mjs +64 -37
- package/src/tui/engine/tool-result-status.mjs +75 -21
- package/src/tui/engine/turn.mjs +172 -77
- package/src/tui/engine.mjs +199 -39
- package/src/tui/index.jsx +2 -2
- package/src/workflows/bench/WORKFLOW.md +25 -35
- package/src/workflows/default/WORKFLOW.md +38 -32
- package/src/workflows/solo/WORKFLOW.md +19 -22
- package/scripts/_jitter-fuzz.mjs +0 -44410
- package/scripts/_jitter-fuzz2.mjs +0 -44400
- package/scripts/_jitter-probe.mjs +0 -44397
- package/scripts/_jp2.mjs +0 -45614
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import {
|
|
8
|
-
readdirSync, readFileSync, statSync, existsSync, mkdirSync, renameSync, unlinkSync,
|
|
8
|
+
readdirSync, readFileSync, statSync, existsSync, mkdirSync, renameSync, unlinkSync, openSync, readSync, closeSync,
|
|
9
9
|
} from 'node:fs';
|
|
10
10
|
import { getPluginData } from '../../config.mjs';
|
|
11
11
|
import { writeJsonAtomicSync } from '../../../../shared/atomic-file.mjs';
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
CODE_GRAPH_DISK_DIR,
|
|
20
20
|
CODE_GRAPH_DISK_MAX_ENTRIES,
|
|
21
21
|
CODE_GRAPH_DISK_MAX_BYTES,
|
|
22
|
+
CODE_GRAPH_FAST_PATH_MAX_BYTES,
|
|
22
23
|
ORPHAN_TMP_MIN_AGE_MS,
|
|
23
24
|
RE_CACHE_TMP,
|
|
24
25
|
RE_MANIFEST_TMP,
|
|
@@ -83,6 +84,13 @@ function _migrateLegacyDiskCache() {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
// This intentionally does not call _loadDiskCodeGraphCache: the parent-side
|
|
88
|
+
// fast path uses it to leave a legacy blob's synchronous read/JSON.parse to
|
|
89
|
+
// the Worker, where normal one-shot migration still happens.
|
|
90
|
+
export function hasLegacyDiskCodeGraphCache() {
|
|
91
|
+
return existsSync(join(getPluginData(), CODE_GRAPH_DISK_FILE));
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
function _pruneDiskCodeGraphEntries(_now = Date.now()) {
|
|
87
95
|
for (const [cwd, entry] of _diskCodeGraphCache) {
|
|
88
96
|
if (!entry || typeof entry !== 'object') {
|
|
@@ -278,7 +286,10 @@ function _ensureCwdLoaded(cwd) {
|
|
|
278
286
|
} catch { /* skip corrupt per-cwd file */ }
|
|
279
287
|
}
|
|
280
288
|
|
|
281
|
-
function _persistDiskCodeGraphCacheNow(
|
|
289
|
+
export function _persistDiskCodeGraphCacheNow({
|
|
290
|
+
strict = false,
|
|
291
|
+
writeJson = writeJsonAtomicSync,
|
|
292
|
+
} = {}) {
|
|
282
293
|
try {
|
|
283
294
|
_loadDiskCodeGraphCache();
|
|
284
295
|
_pruneDiskCodeGraphEntries();
|
|
@@ -307,8 +318,15 @@ function _persistDiskCodeGraphCacheNow() {
|
|
|
307
318
|
// rebuildable — a busy lock just skips this flush; _scheduleDiskCodeGraphCacheFlush
|
|
308
319
|
// re-schedules on the next build. Exit drain still runs (unref'd timer
|
|
309
320
|
// cancelled + direct call).
|
|
310
|
-
|
|
311
|
-
|
|
321
|
+
writeJson(file, entry, { compact: true, lock: true, timeoutMs: 0 });
|
|
322
|
+
let bytes = null;
|
|
323
|
+
try { bytes = statSync(file).size; } catch { /* written entry may be unavailable */ }
|
|
324
|
+
manifest[cwd] = {
|
|
325
|
+
hash,
|
|
326
|
+
builtAt: entry.builtAt || Date.now(),
|
|
327
|
+
bytes: Number.isFinite(bytes) ? bytes : undefined,
|
|
328
|
+
maxFiles: Number.isFinite(entry.maxFiles) ? entry.maxFiles : undefined,
|
|
329
|
+
};
|
|
312
330
|
}
|
|
313
331
|
const pruned = _pruneCodeGraphManifestForBudget(manifest, dir);
|
|
314
332
|
manifest = pruned.manifest;
|
|
@@ -320,7 +338,7 @@ function _persistDiskCodeGraphCacheNow() {
|
|
|
320
338
|
}
|
|
321
339
|
|
|
322
340
|
const manifestFile = join(dir, 'manifest.json');
|
|
323
|
-
|
|
341
|
+
writeJson(manifestFile, manifest, { compact: true, lock: true, timeoutMs: 0 });
|
|
324
342
|
_diskManifest = manifest;
|
|
325
343
|
|
|
326
344
|
// Sweep orphan per-cwd files. validHashes now includes every hash in
|
|
@@ -329,6 +347,7 @@ function _persistDiskCodeGraphCacheNow() {
|
|
|
329
347
|
_sweepCodeGraphCacheDir(dir, validHashes, { sweepJson: true });
|
|
330
348
|
} catch (err) {
|
|
331
349
|
process.stderr.write(`[code-graph] disk cache persist failed (target: ${_codeGraphDiskDir()}): ${err?.message || err}\n`);
|
|
350
|
+
if (strict) throw err;
|
|
332
351
|
}
|
|
333
352
|
}
|
|
334
353
|
|
|
@@ -355,6 +374,16 @@ function drainCodeGraphCacheNow() {
|
|
|
355
374
|
}
|
|
356
375
|
registerCodeGraphDrain(drainCodeGraphCacheNow);
|
|
357
376
|
|
|
377
|
+
// Worker-only success fencing: unlike the exit/parent drain, a failed
|
|
378
|
+
// persistence must reject the Worker result instead of being log-only.
|
|
379
|
+
export function drainCodeGraphCacheStrict() {
|
|
380
|
+
if (_diskCodeGraphCacheFlushTimer) {
|
|
381
|
+
clearTimeout(_diskCodeGraphCacheFlushTimer);
|
|
382
|
+
_diskCodeGraphCacheFlushTimer = null;
|
|
383
|
+
}
|
|
384
|
+
_persistDiskCodeGraphCacheNow({ strict: true });
|
|
385
|
+
}
|
|
386
|
+
|
|
358
387
|
// Public: delegate to the state module's drain hook (which invokes the
|
|
359
388
|
// registered drainCodeGraphCacheNow). Preserves the original facade behavior.
|
|
360
389
|
export function drainCodeGraphCache() {
|
|
@@ -369,6 +398,40 @@ export function getDiskCodeGraphEntry(cwd) {
|
|
|
369
398
|
return _diskCodeGraphCache.get(key);
|
|
370
399
|
}
|
|
371
400
|
|
|
401
|
+
// Inspect only manifest metadata (or a stat fallback), never parse the entry.
|
|
402
|
+
// The main-thread fast path uses this to leave large/legacy entries to Worker
|
|
403
|
+
// isolation while still allowing small compatible entries to avoid Worker boot.
|
|
404
|
+
export function probeDiskCodeGraphEntry(cwd, maxBytes = CODE_GRAPH_FAST_PATH_MAX_BYTES) {
|
|
405
|
+
const key = _canonicalGraphCwd(cwd);
|
|
406
|
+
const meta = _diskManifest?.[key];
|
|
407
|
+
if (!meta || typeof meta !== 'object' || !_isCodeGraphCacheHash(meta.hash)) return null;
|
|
408
|
+
const file = join(_codeGraphDiskDir(), `${meta.hash}.json`);
|
|
409
|
+
let bytes = Number(meta.bytes);
|
|
410
|
+
if (!Number.isFinite(bytes) || bytes < 0) {
|
|
411
|
+
try { bytes = statSync(file).size; } catch { return null; }
|
|
412
|
+
}
|
|
413
|
+
let maxFiles = Number.isFinite(meta.maxFiles) ? meta.maxFiles : null;
|
|
414
|
+
// Legacy manifests have no per-entry metadata. Read only the compact JSON
|
|
415
|
+
// header (maxFiles precedes nodes) rather than parsing a potentially huge
|
|
416
|
+
// payload on the main thread.
|
|
417
|
+
if (maxFiles === null) {
|
|
418
|
+
let fd = null;
|
|
419
|
+
try {
|
|
420
|
+
fd = openSync(file, 'r');
|
|
421
|
+
const header = Buffer.allocUnsafe(4096);
|
|
422
|
+
const read = readSync(fd, header, 0, header.length, 0);
|
|
423
|
+
const match = /"maxFiles":(\d+)/.exec(header.toString('utf8', 0, read));
|
|
424
|
+
if (match) maxFiles = Number(match[1]);
|
|
425
|
+
} catch { /* leave legacy/corrupt metadata in the Worker path */ }
|
|
426
|
+
finally { if (fd !== null) try { closeSync(fd); } catch {} }
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
bytes,
|
|
430
|
+
maxFiles,
|
|
431
|
+
isFastPathEligible: bytes <= maxBytes,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
372
435
|
// Ensure the on-disk manifest/sweep boot pass has run (idempotent).
|
|
373
436
|
export function ensureDiskCodeGraphLoaded(now = Date.now()) {
|
|
374
437
|
_loadDiskCodeGraphCache(now);
|
|
@@ -103,8 +103,16 @@ async function codeGraph(args, cwd, signal = null, options = {}) {
|
|
|
103
103
|
// Name-only "symbols" calls (symbols[]/symbol without a file) are symbol
|
|
104
104
|
// lookups, not a file outline — absorb into symbol_search instead of
|
|
105
105
|
// erroring "file not found in graph: (missing file)".
|
|
106
|
-
if (mode === 'symbols'
|
|
107
|
-
&&
|
|
106
|
+
if (mode === 'symbols'
|
|
107
|
+
&& !String(args?.file || '').trim()
|
|
108
|
+
&& !String(args?.files || '').trim()
|
|
109
|
+
&& ((Array.isArray(args?.symbols) && args.symbols.length)
|
|
110
|
+
|| (typeof args?.symbols === 'string' && args.symbols.trim())
|
|
111
|
+
|| String(args?.symbol || '').trim())) {
|
|
112
|
+
if (!args.symbol && typeof args.symbols === 'string' && args.symbols.trim()) {
|
|
113
|
+
args = { ...args, symbol: args.symbols };
|
|
114
|
+
delete args.symbols;
|
|
115
|
+
}
|
|
108
116
|
mode = 'symbol_search';
|
|
109
117
|
}
|
|
110
118
|
|
|
@@ -523,8 +531,14 @@ export async function resolveSymbolReadSpan(cwd, { symbol, path = null, language
|
|
|
523
531
|
export async function executeCodeGraphTool(name, args, cwd, signal = null, options = {}) {
|
|
524
532
|
if (!cwd) throw new Error('find_symbol/code_graph requires cwd — caller did not provide a working directory');
|
|
525
533
|
args = _normalizeGraphFileArgs(args);
|
|
526
|
-
const fileArg = (args && typeof args.file === 'string' && args.file.trim()) ? args.file.trim() : '';
|
|
527
534
|
const baseCwd = (args && typeof args.cwd === 'string' && args.cwd.trim()) ? args.cwd.trim() : cwd;
|
|
535
|
+
const symbolMode = ['find_symbol', 'symbol_search', 'search', 'references', 'callers', 'callees', 'symbols'].includes(args?.mode);
|
|
536
|
+
if (symbolMode && typeof args?.file === 'string' && args.file.trim()
|
|
537
|
+
&& pathResolve(baseCwd, args.file.trim()) === pathResolve(baseCwd)) {
|
|
538
|
+
args = { ...args };
|
|
539
|
+
delete args.file;
|
|
540
|
+
}
|
|
541
|
+
const fileArg = (args && typeof args.file === 'string' && args.file.trim()) ? args.file.trim() : '';
|
|
528
542
|
let effectiveCwd = baseCwd;
|
|
529
543
|
if (fileArg) {
|
|
530
544
|
const abs = isAbsolute(fileArg) ? pathResolve(fileArg) : pathResolve(baseCwd, fileArg);
|
|
@@ -33,7 +33,7 @@ function _graphBinaryPath() {
|
|
|
33
33
|
try { return findCachedGraphBinary(getPluginData()); } catch { return null; }
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
async function _runGraphBinaryJsonl(absRoot, extraArgs, stdinLines = null) {
|
|
36
|
+
async function _runGraphBinaryJsonl(absRoot, extraArgs, stdinLines = null, signal = null) {
|
|
37
37
|
let binPath = _graphBinaryPath();
|
|
38
38
|
if (!binPath) {
|
|
39
39
|
// No local build or cached binary — fetch the prebuilt from the release
|
|
@@ -54,14 +54,10 @@ async function _runGraphBinaryJsonl(absRoot, extraArgs, stdinLines = null) {
|
|
|
54
54
|
|
|
55
55
|
// Inner spawn + promise — extracted so we can retry once on EAGAIN.
|
|
56
56
|
//
|
|
57
|
-
// child-spawn-gate is NOT acquired here.
|
|
58
|
-
//
|
|
59
|
-
// do not share module-level state with the main thread
|
|
60
|
-
// create
|
|
61
|
-
// main-thread rg gate. Instead the gate is held on the MAIN THREAD across the
|
|
62
|
-
// whole graph-build worker's lifetime (see buildCodeGraphAsync). The binary
|
|
63
|
-
// child is spawned exclusively from this worker path, so one main-side slot
|
|
64
|
-
// per worker correctly bounds native graph spawns against rg.
|
|
57
|
+
// child-spawn-gate is NOT acquired here. Worker builds and main-thread
|
|
58
|
+
// signature validation both hold their slot in buildCodeGraphAsync; worker
|
|
59
|
+
// threads do not share module-level state with the main thread, so acquiring
|
|
60
|
+
// here would create an independent semaphore that cannot coordinate with rg.
|
|
65
61
|
const _spawnOnce = () => new Promise((resolve, reject) => {
|
|
66
62
|
// When stdinLines is supplied (--files mode), stream one JSON object per
|
|
67
63
|
// line to the child's STDIN — the reused nodes' metadata — so Rust can
|
|
@@ -79,6 +75,7 @@ async function _runGraphBinaryJsonl(absRoot, extraArgs, stdinLines = null) {
|
|
|
79
75
|
const STDERR_CAP = 8 * 1024;
|
|
80
76
|
let settled = false;
|
|
81
77
|
let timedOut = false;
|
|
78
|
+
let aborted = false;
|
|
82
79
|
|
|
83
80
|
// ── timeout + kill helpers (mirrors rg-runner's _killRgProc/_escalateRgKill) ──
|
|
84
81
|
let timeoutTimer = null;
|
|
@@ -130,7 +127,12 @@ async function _runGraphBinaryJsonl(absRoot, extraArgs, stdinLines = null) {
|
|
|
130
127
|
clearTimeout(forceSettleTimer);
|
|
131
128
|
forceSettleTimer = null;
|
|
132
129
|
}
|
|
130
|
+
if (onAbort && signal) {
|
|
131
|
+
try { signal.removeEventListener('abort', onAbort); } catch { /* ignore */ }
|
|
132
|
+
onAbort = null;
|
|
133
|
+
}
|
|
133
134
|
};
|
|
135
|
+
let onAbort = null;
|
|
134
136
|
|
|
135
137
|
// Arm timeout — unref so it doesn't keep the process alive. On timeout we
|
|
136
138
|
// start SIGTERM→grace→force-kill but do NOT settle yet: the promise stays
|
|
@@ -157,6 +159,14 @@ async function _runGraphBinaryJsonl(absRoot, extraArgs, stdinLines = null) {
|
|
|
157
159
|
if (forceSettleTimer.unref) forceSettleTimer.unref();
|
|
158
160
|
}, timeoutMs);
|
|
159
161
|
if (timeoutTimer.unref) timeoutTimer.unref();
|
|
162
|
+
if (signal) {
|
|
163
|
+
onAbort = () => {
|
|
164
|
+
aborted = true;
|
|
165
|
+
_killProc();
|
|
166
|
+
};
|
|
167
|
+
if (signal.aborted) onAbort();
|
|
168
|
+
else signal.addEventListener('abort', onAbort, { once: true });
|
|
169
|
+
}
|
|
160
170
|
|
|
161
171
|
proc.stdout.on('data', (c) => chunks.push(c));
|
|
162
172
|
proc.stderr.on('data', (c) => {
|
|
@@ -186,6 +196,10 @@ async function _runGraphBinaryJsonl(absRoot, extraArgs, stdinLines = null) {
|
|
|
186
196
|
reject(new Error(`[code-graph] mixdog-graph timed out after ${timeoutMs}ms`));
|
|
187
197
|
return;
|
|
188
198
|
}
|
|
199
|
+
if (aborted) {
|
|
200
|
+
reject(new Error('aborted'));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
189
203
|
if (code !== 0) {
|
|
190
204
|
reject(new Error(`[code-graph] mixdog-graph exited ${code}: ${stderrText.trim().slice(0, 200)}`));
|
|
191
205
|
return;
|
|
@@ -215,7 +229,7 @@ async function _runGraphBinaryJsonl(absRoot, extraArgs, stdinLines = null) {
|
|
|
215
229
|
throw err;
|
|
216
230
|
}
|
|
217
231
|
}
|
|
218
|
-
export function _runGraphManifest(absRoot) { return _runGraphBinaryJsonl(absRoot, ['--manifest']); }
|
|
232
|
+
export function _runGraphManifest(absRoot, signal = null) { return _runGraphBinaryJsonl(absRoot, ['--manifest'], null, signal); }
|
|
219
233
|
export function _runGraphWalk(absRoot) { return _runGraphBinaryJsonl(absRoot, []); }
|
|
220
234
|
// --files (design A: full-graph resolution) full-parses only `rels` (argv) but
|
|
221
235
|
// resolves imports across the WHOLE tree. The reused nodes' metas are streamed
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// - On any failure: postMessage({ ok: false }). Main thread propagates
|
|
13
13
|
// an error — find_symbol / code_graph tools throw. No sync fallback.
|
|
14
14
|
import { parentPort, workerData } from 'node:worker_threads';
|
|
15
|
-
import { _buildCodeGraph } from './code-graph.mjs';
|
|
15
|
+
import { _buildCodeGraph, _postCodeGraphWorkerSuccess } from './code-graph.mjs';
|
|
16
16
|
|
|
17
17
|
const cwd = workerData && workerData.cwd ? workerData.cwd : null;
|
|
18
18
|
|
|
@@ -27,9 +27,12 @@ try {
|
|
|
27
27
|
if (!cwd) {
|
|
28
28
|
parentPort.postMessage({ ok: false });
|
|
29
29
|
} else {
|
|
30
|
-
const graph = await _buildCodeGraph(cwd
|
|
30
|
+
const graph = await _buildCodeGraph(cwd, {
|
|
31
|
+
manifest: Array.isArray(workerData.manifest) ? workerData.manifest : null,
|
|
32
|
+
signature: typeof workerData.signature === 'string' ? workerData.signature : null,
|
|
33
|
+
});
|
|
31
34
|
if (graph && typeof graph.signature === 'string') {
|
|
32
|
-
|
|
35
|
+
_postCodeGraphWorkerSuccess(graph, (message) => parentPort.postMessage(message));
|
|
33
36
|
} else {
|
|
34
37
|
parentPort.postMessage({ ok: false });
|
|
35
38
|
}
|
|
@@ -3,17 +3,14 @@ export const CODE_GRAPH_TOOL_DEFS = [
|
|
|
3
3
|
name: 'code_graph',
|
|
4
4
|
title: 'Code Graph',
|
|
5
5
|
annotations: { title: 'Code Graph', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: false, compressibleLossless: true },
|
|
6
|
-
description: 'Repo-local code structure/flow
|
|
6
|
+
description: 'Repo-local code structure/flow (not web): symbols/references/calls/deps Public files[] must be verified paths; verified source files only. IDs→graph; literal/zero→grep. Exact identifiers: find_symbol/references/callers/callees; keywords: symbol_search/search Unsupported target arrays omitted, never silently mixed. Legacy file/symbol/language compatible, hidden. Batch symbols[]/files[] by mode',
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
|
10
|
-
mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'symbol_search', 'search', 'references', 'callers'], description: 'Repo-local
|
|
11
|
-
files: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Verified source
|
|
12
|
-
symbols: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: '
|
|
13
|
-
symbol: { type: 'string', description: 'Singular alias for symbols.' },
|
|
14
|
-
file: { type: 'string', description: 'Singular alias for files.' },
|
|
10
|
+
mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'symbol_search', 'search', 'references', 'callers', 'callees'], description: 'Repo-local: file modes={overview,imports,dependents,related,impact}; symbols with files→files[] file outline; symbol modes={find_symbol,symbol_search,search,references,callers,callees}; fileless symbols→symbol_search keywords.' },
|
|
11
|
+
files: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Verified source files; supported targets only.' },
|
|
12
|
+
symbols: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Exact identifiers: find_symbol/references/callers/callees; keywords: symbol_search/search; multiple exact symbols use one symbols[] call; one call.' },
|
|
15
13
|
body: { type: 'boolean', description: 'Include body.' },
|
|
16
|
-
language: { type: 'string', description: 'Language hint.' },
|
|
17
14
|
limit: { type: 'number', minimum: 1, description: 'Max results.' },
|
|
18
15
|
depth: { type: 'number', minimum: 1, maximum: 5, description: 'Caller depth.' },
|
|
19
16
|
page: { type: 'number', minimum: 1, description: 'Caller page.' },
|
|
@@ -26,7 +26,7 @@ export const PATCH_TOOL_DEFS = [
|
|
|
26
26
|
name: 'apply_patch',
|
|
27
27
|
title: 'Mixdog Apply Patch',
|
|
28
28
|
annotations: { title: 'Mixdog Apply Patch', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false, compressible: false, compressibleLossless: true },
|
|
29
|
-
description: 'Apply file
|
|
29
|
+
description: 'Apply known file edits in one patch; sections run in listed order. Do not split a dependent edit across turns.',
|
|
30
30
|
freeformDescription: APPLY_PATCH_FREEFORM_DESCRIPTION,
|
|
31
31
|
freeform: {
|
|
32
32
|
type: 'grammar',
|
|
@@ -496,10 +496,10 @@ export async function sonnetCascade(candidates, rulesDigest, options = {}) {
|
|
|
496
496
|
`NO prose, NO preamble, NO meta-commentary. First character must be a digit.`,
|
|
497
497
|
].join('\n')
|
|
498
498
|
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
const preset = options.cascadePreset || '
|
|
499
|
+
// Keep the cascade on the same maintenance route as every other memory
|
|
500
|
+
// cycle call. An explicit override remains available for focused tests and
|
|
501
|
+
// controlled callers.
|
|
502
|
+
const preset = options.cascadePreset || resolveMaintenancePreset('memory')
|
|
503
503
|
const llmCall = typeof options?.callLlm === 'function' ? options.callLlm : callAgentDispatch
|
|
504
504
|
let raw
|
|
505
505
|
try {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// DB status/update/merge writers plus the cosine-similarity dedup pass.
|
|
3
3
|
// Facade (memory-cycle2.mjs) re-exports the public members unchanged.
|
|
4
4
|
import { deleteRootEmbedding, syncRootEmbedding } from './memory-embed.mjs'
|
|
5
|
+
import { resolveMaintenancePreset } from '../../shared/llm/index.mjs'
|
|
5
6
|
import { callAgentDispatch } from './agent-ipc.mjs'
|
|
6
7
|
import { __mixdogMemoryLog, throwIfAborted } from './memory-cycle2-shared.mjs'
|
|
7
8
|
|
|
@@ -318,7 +319,7 @@ async function _llmJudgePair(summaryA, summaryB, siblingContext = [], options =
|
|
|
318
319
|
agent: 'cycle2-agent',
|
|
319
320
|
taskType: 'maintenance',
|
|
320
321
|
mode: 'cycle2-phase_merge_judge',
|
|
321
|
-
preset: '
|
|
322
|
+
preset: options.preset || resolveMaintenancePreset('memory'),
|
|
322
323
|
timeout: 30000,
|
|
323
324
|
cwd: null,
|
|
324
325
|
}, prompt)
|
|
@@ -413,7 +414,7 @@ export async function runPhaseMerge(db, options = {}) {
|
|
|
413
414
|
// callAgentDispatch (IPC), which is unavailable in the standalone
|
|
414
415
|
// memory service and failed every judge call with
|
|
415
416
|
// `agent-ipc: IPC channel unavailable`.
|
|
416
|
-
{ signal, callLlm: options?.callLlm },
|
|
417
|
+
{ signal, preset: options?.preset, callLlm: options?.callLlm },
|
|
417
418
|
)
|
|
418
419
|
throwIfAborted(signal)
|
|
419
420
|
if (shouldMerge) await doMerge(pair.a, pair.b, pair.sim)
|
|
@@ -459,7 +460,7 @@ export async function runPhaseMerge(db, options = {}) {
|
|
|
459
460
|
String(row.entry_summary ?? ''),
|
|
460
461
|
String(row.core_summary ?? ''),
|
|
461
462
|
[],
|
|
462
|
-
{ signal, callLlm: options?.callLlm },
|
|
463
|
+
{ signal, preset: options?.preset, callLlm: options?.callLlm },
|
|
463
464
|
)
|
|
464
465
|
throwIfAborted(signal)
|
|
465
466
|
if (!verdictMerge) continue
|
|
@@ -51,6 +51,17 @@ async function invokeLlm(prompt, mode, preset, timeout, llmCall = callAgentDispa
|
|
|
51
51
|
}, prompt)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
export async function invokeCycle3Maintenance(prompt, options = {}) {
|
|
55
|
+
const preset = options.preset || resolveMaintenancePreset('memory')
|
|
56
|
+
return await invokeLlm(
|
|
57
|
+
prompt,
|
|
58
|
+
'cycle3-review',
|
|
59
|
+
preset,
|
|
60
|
+
Number(options.timeout ?? 600000),
|
|
61
|
+
options.callLlm,
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
54
65
|
function throwIfAborted(signal) {
|
|
55
66
|
if (signal?.aborted) throw signal.reason ?? new Error('aborted')
|
|
56
67
|
}
|
|
@@ -507,7 +518,6 @@ async function _runCycle3Impl(db, config, dataDir, options = {}) {
|
|
|
507
518
|
.replace('{{CORE_REVIEW}}', coreReview)
|
|
508
519
|
.replace('{{CURRENT_RULES}}', rulesDigest)
|
|
509
520
|
|
|
510
|
-
const preset = resolveMaintenancePreset('memory')
|
|
511
521
|
const timeout = Number(config?.cycle3?.timeout ?? 600000)
|
|
512
522
|
const mode = 'cycle3-review'
|
|
513
523
|
|
|
@@ -516,7 +526,7 @@ async function _runCycle3Impl(db, config, dataDir, options = {}) {
|
|
|
516
526
|
let raw
|
|
517
527
|
try {
|
|
518
528
|
throwIfAborted(signal)
|
|
519
|
-
raw = await
|
|
529
|
+
raw = await invokeCycle3Maintenance(prompt, { ...options, timeout, mode })
|
|
520
530
|
} catch (err) {
|
|
521
531
|
if (signal?.aborted) throw signal.reason ?? err
|
|
522
532
|
__mixdogMemoryLog(`[cycle3] LLM error: ${err.message}\n`)
|
|
@@ -38,6 +38,13 @@ function scheduleFlush(path, q) {
|
|
|
38
38
|
q.timer.unref?.();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function deleteIfIdle(path, q) {
|
|
42
|
+
if (!q.timer && !q.flushing && !q.inFlight && q.chunks.length === 0
|
|
43
|
+
&& queues.get(path) === q) {
|
|
44
|
+
queues.delete(path);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
41
48
|
function _flush(path, q) {
|
|
42
49
|
if (q.flushing) return;
|
|
43
50
|
if (q.chunks.length === 0) return;
|
|
@@ -70,7 +77,7 @@ function _flush(path, q) {
|
|
|
70
77
|
if (q.chunks.length > 0) {
|
|
71
78
|
if (q.bytes >= BUFFER_FLUSH_BYTES) _flush(path, q);
|
|
72
79
|
else scheduleFlush(path, q);
|
|
73
|
-
}
|
|
80
|
+
} else deleteIfIdle(path, q);
|
|
74
81
|
});
|
|
75
82
|
}
|
|
76
83
|
|
|
@@ -120,7 +127,10 @@ export function drainPathSync(path) {
|
|
|
120
127
|
}
|
|
121
128
|
q.inFlight = null;
|
|
122
129
|
}
|
|
123
|
-
if (q.chunks.length === 0)
|
|
130
|
+
if (q.chunks.length === 0) {
|
|
131
|
+
deleteIfIdle(path, q);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
124
134
|
const data = q.chunks.join('');
|
|
125
135
|
q.chunks = [];
|
|
126
136
|
q.bytes = 0;
|
|
@@ -129,6 +139,7 @@ export function drainPathSync(path) {
|
|
|
129
139
|
} catch {
|
|
130
140
|
// Best-effort exit drain; nothing to recover from here.
|
|
131
141
|
}
|
|
142
|
+
deleteIfIdle(path, q);
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
/**
|
|
@@ -250,10 +250,13 @@ export function bridgeAgentModelSummary(args) {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
export function summarizeLineWindow(a) {
|
|
253
|
+
const hasOffset = a.offset != null;
|
|
253
254
|
const offset = a.offset ?? a.start_line ?? a.startLine ?? a.line;
|
|
254
255
|
const limit = a.limit ?? a.line_count ?? a.lineCount ?? a.lines;
|
|
255
256
|
if (offset == null && limit == null) return '';
|
|
256
|
-
|
|
257
|
+
// `read` uses a zero-based offset, while legacy start_line/line inputs are
|
|
258
|
+
// already one-based. Surface the human line number, never a raw array index.
|
|
259
|
+
const start = Number(offset) + (hasOffset ? 1 : 0);
|
|
257
260
|
const count = Number(limit);
|
|
258
261
|
if (Number.isFinite(start) && Number.isFinite(count) && count > 0) {
|
|
259
262
|
return `lines ${start}-${Math.max(start, start + count - 1)}`;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared terminal-status parsing for tool result surfaces.
|
|
3
|
+
*
|
|
4
|
+
* A terminal result status describes the tool's reported outcome; it is not
|
|
5
|
+
* evidence that the tool invocation itself failed. Call-failure accounting is
|
|
6
|
+
* owned by the TUI engine's isError/toolKind envelope fields.
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeToolTerminalStatus(value) {
|
|
9
|
+
const raw = String(value || '').trim().toLowerCase();
|
|
10
|
+
if (!raw) return '';
|
|
11
|
+
if (/^(running|pending|queued|in_progress|in-progress)$/.test(raw)) return 'running';
|
|
12
|
+
if (/^(completed|complete|done|success|succeeded|ok)$/.test(raw)) return 'completed';
|
|
13
|
+
if (/^(failed|fail|error|errored|timeout|timed_out|killed)$/.test(raw)) return 'failed';
|
|
14
|
+
if (/^(cancelled|canceled|cancel)$/.test(raw)) return 'cancelled';
|
|
15
|
+
if (/^(denied|deny|refused|rejected)$/.test(raw)) return 'denied';
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function toolResultTerminalStatus(text) {
|
|
20
|
+
const body = String(text || '');
|
|
21
|
+
const tagged = body.match(/<status[^>]*>([\s\S]*?)<\/status>/i)?.[1]?.trim();
|
|
22
|
+
if (tagged) return normalizeToolTerminalStatus(tagged);
|
|
23
|
+
const bracketed = body.match(/^\[status:\s*([^\]]*)\]/mi)?.[1]?.trim();
|
|
24
|
+
if (bracketed) return normalizeToolTerminalStatus(bracketed);
|
|
25
|
+
const inline = body.match(/^(?:status|state):\s*([^\s·,;]+)/mi)?.[1]?.trim();
|
|
26
|
+
return normalizeToolTerminalStatus(inline);
|
|
27
|
+
}
|
|
@@ -452,7 +452,10 @@ export function toolWorkUnit(name, args = {}, category = '') {
|
|
|
452
452
|
case 'read_mcp_resource':
|
|
453
453
|
return unitDescriptor('Read', { count: queryCount(a, 'uri', 'uris') || 1, noun: 'resource' });
|
|
454
454
|
case 'apply_patch': {
|
|
455
|
-
const
|
|
455
|
+
const patchText = String(a.patch ?? '');
|
|
456
|
+
const creating = a.old_string === '' || /^\*\*\*\s+Add File:/mi.test(patchText);
|
|
457
|
+
const deleting = (!creating && a.new_string === '' && a.old_string != null)
|
|
458
|
+
|| /^\*\*\*\s+Delete File:/mi.test(patchText);
|
|
456
459
|
// A dry_run patch validates the diff WITHOUT writing any file, so the
|
|
457
460
|
// header must not claim "Editing/Edited" (which made a pure validation
|
|
458
461
|
// look like a real edit). Surface it as "Checking/Checked" instead.
|
|
@@ -466,8 +469,8 @@ export function toolWorkUnit(name, args = {}, category = '') {
|
|
|
466
469
|
}
|
|
467
470
|
return unitDescriptor('Patch', {
|
|
468
471
|
count: patchFileCount(a) || 1,
|
|
469
|
-
active: creating ? 'Creating' : 'Editing',
|
|
470
|
-
done: creating ? 'Created' : 'Edited',
|
|
472
|
+
active: creating ? 'Creating' : deleting ? 'Deleting' : 'Editing',
|
|
473
|
+
done: creating ? 'Created' : deleting ? 'Deleted' : 'Edited',
|
|
471
474
|
noun: 'file',
|
|
472
475
|
});
|
|
473
476
|
}
|
|
@@ -175,6 +175,20 @@ export function autoClearProviderDefaults(providerIdleMs = null) {
|
|
|
175
175
|
});
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// Agent terminal retention is deliberately narrower than Auto Clear's general
|
|
179
|
+
// resolution: listed providers use their provider cache lifetime. Unlisted
|
|
180
|
+
// providers use the Advanced "default" row, or the built-in default when no
|
|
181
|
+
// override is configured, so completed agents are still eventually reclaimed.
|
|
182
|
+
export function resolveAgentTerminalReapMs(config, provider) {
|
|
183
|
+
const key = clean(provider).toLowerCase();
|
|
184
|
+
const raw = config?.autoClear && typeof config.autoClear === 'object' ? config.autoClear : {};
|
|
185
|
+
const overrides = normalizeAutoClearProviderIdleMs(raw.providerIdleMs);
|
|
186
|
+
if (!key || key === 'default' || !Object.hasOwn(AUTO_CLEAR_PROVIDER_IDLE_MS, key)) {
|
|
187
|
+
return overrides.default ?? AUTO_CLEAR_PROVIDER_IDLE_MS.default;
|
|
188
|
+
}
|
|
189
|
+
return overrides[key] ?? AUTO_CLEAR_PROVIDER_IDLE_MS[key];
|
|
190
|
+
}
|
|
191
|
+
|
|
178
192
|
// Resolve the effective auto-clear idle window for a config + provider:
|
|
179
193
|
// an explicit user-set idleMs (config.autoClear.idleMs) always wins; else
|
|
180
194
|
// fall back to the provider's default; else the global 1h default.
|
|
@@ -100,6 +100,7 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
100
100
|
const lastContextTokens = Number(session?.lastContextTokens || 0);
|
|
101
101
|
const estimatedContextTokens = estimateTranscriptContextUsage(messages, tools, {
|
|
102
102
|
messageCount: messageSummary.count,
|
|
103
|
+
estimatedMessageTokens: messageSummary.estimatedTokens,
|
|
103
104
|
});
|
|
104
105
|
const compactAt = Number(session?.compaction?.lastChangedAt || session?.compaction?.lastCompactAt || 0);
|
|
105
106
|
const usageAt = Number(session?.lastContextTokensUpdatedAt || 0);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { clean } from './session-text.mjs';
|
|
3
3
|
|
|
4
4
|
export const TOOL_MODES = new Set(['full', 'readonly', 'lead']);
|
|
5
|
-
export const ALL_EFFORT_LEVELS = new Set(['none', 'low', 'medium', 'high', 'xhigh', 'max']);
|
|
5
|
+
export const ALL_EFFORT_LEVELS = new Set(['none', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra']);
|
|
6
6
|
export const EFFORT_LABELS = {
|
|
7
7
|
none: 'None',
|
|
8
8
|
low: 'Low',
|
|
@@ -10,11 +10,14 @@ export const EFFORT_LABELS = {
|
|
|
10
10
|
high: 'High',
|
|
11
11
|
xhigh: 'Extra High',
|
|
12
12
|
max: 'Max',
|
|
13
|
+
ultra: 'Ultra',
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export const EFFORT_OPTIONS_BY_PROVIDER = {
|
|
16
17
|
openai: ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
17
|
-
|
|
18
|
+
// gpt-5.6+ catalogs declare max/ultra; the openai-oauth transport folds
|
|
19
|
+
// ultra -> max on the wire (openai-oauth.mjs _normalizeReasoningEffort).
|
|
20
|
+
'openai-oauth': ['none', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
|
|
18
21
|
anthropic: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
19
22
|
'anthropic-oauth': ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
20
23
|
xai: ['none', 'low', 'medium', 'high'],
|
|
@@ -36,6 +39,7 @@ export const EFFORT_BY_FAMILY = {
|
|
|
36
39
|
grok: ['none', 'low', 'medium', 'high'],
|
|
37
40
|
};
|
|
38
41
|
export const EFFORT_FALLBACKS = {
|
|
42
|
+
ultra: ['ultra', 'max', 'xhigh', 'high', 'medium', 'low'],
|
|
39
43
|
max: ['max', 'xhigh', 'high', 'medium', 'low'],
|
|
40
44
|
xhigh: ['xhigh', 'high', 'medium', 'low'],
|
|
41
45
|
high: ['high', 'medium', 'low'],
|
|
@@ -30,6 +30,7 @@ export function createLifecycleApi(deps) {
|
|
|
30
30
|
invalidateContextStatusCache, invalidatePreSessionToolSurface,
|
|
31
31
|
applyResolvedCwd, resolveRoute, applyDeferredToolSurface, standaloneTools,
|
|
32
32
|
pushTranscriptRebind,
|
|
33
|
+
notificationListeners, remoteStateListeners,
|
|
33
34
|
} = deps;
|
|
34
35
|
return {
|
|
35
36
|
async close(reason = 'cli-exit', options = {}) {
|
|
@@ -132,6 +133,9 @@ export function createLifecycleApi(deps) {
|
|
|
132
133
|
ok = mgr.closeSession(session.id, reason, { tombstone });
|
|
133
134
|
setSession(null);
|
|
134
135
|
}
|
|
136
|
+
invalidateContextStatusCache();
|
|
137
|
+
notificationListeners?.clear?.();
|
|
138
|
+
remoteStateListeners?.clear?.();
|
|
135
139
|
const shellJobsStop = globalThis.__mixdogShellJobsRuntimeLoaded === true
|
|
136
140
|
? import('../runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs')
|
|
137
141
|
.then((mod) => mod?.shutdownShellJobs?.(reason, { sync: !detach }))
|
|
@@ -56,10 +56,13 @@ export function compareProviderModelRecency(a, b) {
|
|
|
56
56
|
}
|
|
57
57
|
const ta = providerModelReleaseTime(a);
|
|
58
58
|
const tb = providerModelReleaseTime(b);
|
|
59
|
-
if (ta !== tb) return tb - ta;
|
|
60
|
-
if (a.latest !== b.latest) return a.latest ? -1 : 1;
|
|
61
59
|
const versionDelta = compareProviderModelVersion(a, b);
|
|
60
|
+
// Release dates win only when both sides have one; sparse OAuth catalogs
|
|
61
|
+
// must not sink undated (often newest) models below dated/latest ones.
|
|
62
|
+
if (ta > 0 && tb > 0 && ta !== tb) return tb - ta;
|
|
62
63
|
if (versionDelta) return versionDelta;
|
|
64
|
+
if (a.latest !== b.latest) return a.latest ? -1 : 1;
|
|
65
|
+
if (ta !== tb) return tb - ta;
|
|
63
66
|
return clean(a.display || a.id).localeCompare(clean(b.display || b.id));
|
|
64
67
|
}
|
|
65
68
|
|