mixdog 0.9.46 → 0.9.49
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 +14 -4
- package/scripts/agent-parallel-smoke.mjs +4 -1
- package/scripts/agent-route-batch-test.mjs +40 -0
- package/scripts/code-graph-aggregate-cwd-test.mjs +154 -0
- package/scripts/code-graph-description-contract.mjs +185 -0
- package/scripts/code-graph-disk-hit-test.mjs +55 -0
- package/scripts/code-graph-dispatch-test.mjs +96 -0
- package/scripts/compact-pressure-test.mjs +40 -0
- package/scripts/deferred-tool-loading-test.mjs +233 -0
- package/scripts/execution-completion-dedup-test.mjs +48 -0
- package/scripts/explore-prompt-policy-test.mjs +88 -3
- package/scripts/live-worker-smoke.mjs +68 -16
- package/scripts/memory-core-input-test.mjs +33 -13
- package/scripts/native-edit-wire-test.mjs +152 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +294 -16
- package/scripts/patch-binary-cache-test.mjs +181 -0
- package/scripts/prompt-immediate-render-test.mjs +89 -0
- package/scripts/provider-toolcall-test.mjs +280 -15
- package/scripts/shell-failure-diagnostics-test.mjs +211 -0
- package/scripts/statusline-quota-hysteresis-test.mjs +26 -1
- package/scripts/streaming-tail-window-test.mjs +29 -0
- package/scripts/tool-failures.mjs +21 -3
- package/scripts/tool-smoke.mjs +263 -38
- package/scripts/tool-tui-presentation-test.mjs +17 -1
- package/scripts/tui-perf-run.ps1 +26 -0
- package/scripts/tui-transcript-perf-test.mjs +7 -1
- package/scripts/verify-release-assets-test.mjs +647 -0
- package/scripts/verify-release-assets.mjs +293 -0
- package/scripts/windows-hide-spawn-options-test.mjs +19 -0
- package/src/cli.mjs +1 -0
- package/src/defaults/cycle3-review-prompt.md +10 -4
- package/src/defaults/memory-promote-prompt.md +4 -3
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +28 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +34 -34
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +45 -38
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +36 -11
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +13 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +30 -16
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +25 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +69 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +12 -12
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +12 -7
- package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +77 -18
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +99 -24
- package/src/runtime/memory/lib/memory-action-handlers.mjs +21 -11
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +13 -5
- package/src/runtime/memory/lib/memory-cycle2.mjs +8 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +41 -6
- package/src/runtime/memory/lib/query-handlers.mjs +49 -6
- package/src/runtime/memory/lib/recall-format.mjs +21 -6
- package/src/runtime/memory/tool-defs.mjs +2 -3
- package/src/session-runtime/context-status.mjs +25 -16
- package/src/session-runtime/model-route-api.mjs +2 -0
- package/src/session-runtime/runtime-core.mjs +2 -2
- package/src/session-runtime/session-turn-api.mjs +4 -1
- package/src/session-runtime/tool-catalog.mjs +113 -19
- package/src/session-runtime/tool-defs.mjs +1 -0
- package/src/standalone/agent-tool/helpers.mjs +25 -6
- package/src/standalone/agent-tool.mjs +75 -41
- package/src/tui/App.jsx +4 -0
- package/src/tui/app/input-parsers.mjs +8 -9
- package/src/tui/components/Markdown.jsx +6 -1
- package/src/tui/components/Message.jsx +11 -2
- package/src/tui/components/PromptInput.jsx +19 -21
- package/src/tui/components/Spinner.jsx +4 -4
- package/src/tui/components/StatusLine.jsx +6 -6
- package/src/tui/components/TranscriptItem.jsx +2 -2
- package/src/tui/components/prompt-input/immediate-render.mjs +47 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +14 -9
- package/src/tui/dist/index.mjs +130 -45
- package/src/tui/engine/agent-job-feed.mjs +21 -2
- package/src/tui/engine/turn.mjs +12 -1
- package/src/tui/markdown/measure-rendered-rows.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +20 -0
- package/src/ui/statusline.mjs +5 -5
- package/src/vendor/statusline/src/gateway/session-routes.mjs +22 -10
|
@@ -181,6 +181,32 @@ export function formatShellToolFailure(message) {
|
|
|
181
181
|
return `Error: [shell-tool-failed] ${text}`;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
export function _shellFailureStatus(result, timeout) {
|
|
185
|
+
// Prefer the signal reported by the process. `killed` is only a fallback
|
|
186
|
+
// for platforms (notably taskkill on Windows) that close without one.
|
|
187
|
+
const signal = result.signal || (result.killed ? 'SIGKILL' : null);
|
|
188
|
+
const exitCode = signal ? null : result.exitCode;
|
|
189
|
+
const shellToolFailed = result.failurePhase === 'tool' || !!result.outputCaptureError;
|
|
190
|
+
const killCause = result.killCause || null;
|
|
191
|
+
const causeDetail = killCause ? ` cause: ${killCause}` : '';
|
|
192
|
+
const signalDetail = signal ? ` signal: ${signal}` : '';
|
|
193
|
+
const timeoutHint = result.timedOut
|
|
194
|
+
? ` — command killed after ${timeout} ms; if it legitimately needs longer, retry with a larger timeout`
|
|
195
|
+
: '';
|
|
196
|
+
const statusDetail = shellToolFailed
|
|
197
|
+
? `[${result.outputCaptureError ? 'output capture failed' : (result.failureReason || 'tool failed')}${causeDetail}${signalDetail}]`
|
|
198
|
+
: (result.timedOut
|
|
199
|
+
? `[timeout: ${timeout}ms${signalDetail || ' signal: unknown'}${causeDetail}]${timeoutHint}`
|
|
200
|
+
: (signal
|
|
201
|
+
? `[signal: ${signal}${causeDetail}]`
|
|
202
|
+
: (exitCode !== 0 && exitCode !== null ? `[exit code: ${exitCode}]` : '')));
|
|
203
|
+
return { signal, exitCode, shellToolFailed, statusDetail };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function _composeShellFailure(statusMarker, errorPrefix, warningBlock, payload) {
|
|
207
|
+
return `${errorPrefix}${statusMarker}${warningBlock ? `\n${warningBlock}` : ''}\n\n${payload}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
184
210
|
export async function executeBashTool(args, workDir, options = {}) {
|
|
185
211
|
const requestedCwd = args.cwd ?? args.workdir;
|
|
186
212
|
const cwdResult = resolveOptionalCwd(requestedCwd, workDir);
|
|
@@ -539,12 +565,9 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
539
565
|
}
|
|
540
566
|
const stdout = stripAnsi(result.stdout || '');
|
|
541
567
|
const stderr = stripAnsi(result.stderr || '');
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
: (result.killed ? 'SIGKILL' : (result.signal || null));
|
|
545
|
-
const exitCode = signal ? null : result.exitCode;
|
|
568
|
+
const failureStatus = _shellFailureStatus(result, timeout);
|
|
569
|
+
const { signal, exitCode, shellToolFailed } = failureStatus;
|
|
546
570
|
const benignExitOne = _isBenignSearchExitOne(command, exitCode, signal, stderr);
|
|
547
|
-
const shellToolFailed = result.failurePhase === 'tool' || !!result.outputCaptureError;
|
|
548
571
|
const shellRunFailed = !shellToolFailed && (!!signal || (exitCode !== 0 && exitCode !== null && !benignExitOne));
|
|
549
572
|
const isReallyErrored = shellToolFailed || shellRunFailed;
|
|
550
573
|
const _driftNote = '';
|
|
@@ -556,16 +579,7 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
556
579
|
// Timeout marker carries an inline recovery hint so the caller can
|
|
557
580
|
// act in one round (increase ceiling or detach) instead of repeating
|
|
558
581
|
// the same command and hitting the same wall.
|
|
559
|
-
const
|
|
560
|
-
? ` — command killed after ${timeout} ms; if it legitimately needs longer, retry with a larger timeout`
|
|
561
|
-
: '';
|
|
562
|
-
const statusDetail = shellToolFailed
|
|
563
|
-
? `[${result.outputCaptureError ? 'output capture failed' : (result.failureReason || 'tool failed')}]`
|
|
564
|
-
: (result.timedOut
|
|
565
|
-
? `[timeout: ${timeout}ms signal: ${signal || 'SIGTERM'}]${timeoutHint}`
|
|
566
|
-
: (signal
|
|
567
|
-
? `[signal: ${signal}]`
|
|
568
|
-
: (shellRunFailed ? `[exit code: ${exitCode}]` : '')));
|
|
582
|
+
const statusDetail = failureStatus.statusDetail;
|
|
569
583
|
const statusMarker = shellToolFailed
|
|
570
584
|
? `[shell-tool-failed] ${statusDetail}`
|
|
571
585
|
: (shellRunFailed ? `[shell-run-failed] ${statusDetail}` : '');
|
|
@@ -602,7 +616,7 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
602
616
|
wmicRewrite?.note || '',
|
|
603
617
|
].filter(Boolean).join('\n');
|
|
604
618
|
const payload = `${body}${stderrBlock}${spillBlock}${_driftNote}`;
|
|
605
|
-
if (statusMarker) return _prependDestructiveWarning(command,
|
|
619
|
+
if (statusMarker) return _prependDestructiveWarning(command, _composeShellFailure(statusMarker, errorPrefix, warningBlock, payload));
|
|
606
620
|
return _prependDestructiveWarning(command, warningBlock ? `${warningBlock}\n${payload}` : payload);
|
|
607
621
|
}
|
|
608
622
|
finally {
|
|
@@ -155,12 +155,23 @@ export function _postCodeGraphWorkerSuccess(graph, postMessage, drainCache = dra
|
|
|
155
155
|
postMessage({ ok: true, signature: graph.signature, graph });
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
// Exported for the focused worker-protocol regression test.
|
|
159
|
+
export function _codeGraphWorkerFailure(message) {
|
|
160
|
+
const error = typeof message?.error === 'string' ? message.error.trim() : '';
|
|
161
|
+
return new Error(error || 'code-graph prewarm worker failed');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Exported for the focused best-effort prewarm regression test.
|
|
165
|
+
export function _prewarmCodeGraph(cwd, build = buildCodeGraphAsync) {
|
|
159
166
|
if (!cwd) return;
|
|
160
167
|
// Reuse the buildCodeGraphAsync single-flight path. Fire-and-forget, and
|
|
161
168
|
// best-effort: skip a fresh worker spawn when the child-spawn gate is busy
|
|
162
169
|
// so this warm never queues ahead of real code_graph/find queries.
|
|
163
|
-
|
|
170
|
+
build(cwd, null, { bestEffort: true }).catch(() => { /* best-effort */ });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function prewarmCodeGraph(cwd) {
|
|
174
|
+
_prewarmCodeGraph(cwd);
|
|
164
175
|
}
|
|
165
176
|
|
|
166
177
|
export function prewarmCodeGraphSymbols(cwd, symbols, { language = null } = {}) {
|
|
@@ -248,7 +259,7 @@ export async function buildCodeGraphAsync(cwd, signal = null, { bestEffort = fal
|
|
|
248
259
|
}
|
|
249
260
|
}
|
|
250
261
|
|
|
251
|
-
function _spawnCodeGraphWorker(
|
|
262
|
+
export function _spawnCodeGraphWorker(
|
|
252
263
|
cwd,
|
|
253
264
|
graphCwd,
|
|
254
265
|
genAtStart,
|
|
@@ -256,6 +267,12 @@ function _spawnCodeGraphWorker(
|
|
|
256
267
|
preAcquiredRelease = null,
|
|
257
268
|
manifest = null,
|
|
258
269
|
signature = null,
|
|
270
|
+
{
|
|
271
|
+
createWorker = (url, options) => new Worker(url, options),
|
|
272
|
+
getGeneration = _getCodeGraphGen,
|
|
273
|
+
setMemoryCache = _setCodeGraphCache,
|
|
274
|
+
setDiskCache = _setDiskCodeGraphEntry,
|
|
275
|
+
} = {},
|
|
259
276
|
) {
|
|
260
277
|
let _worker = null;
|
|
261
278
|
return new Promise((resolve, reject) => {
|
|
@@ -281,7 +298,7 @@ function _spawnCodeGraphWorker(
|
|
|
281
298
|
if (signal?.aborted) { settle(new Error('aborted')); return; }
|
|
282
299
|
const workerUrl = new URL('../code-graph-prewarm-worker.mjs', import.meta.url);
|
|
283
300
|
try {
|
|
284
|
-
_worker =
|
|
301
|
+
_worker = createWorker(workerUrl, {
|
|
285
302
|
workerData: { cwd, manifest, signature },
|
|
286
303
|
execArgv: [],
|
|
287
304
|
});
|
|
@@ -305,14 +322,14 @@ function _spawnCodeGraphWorker(
|
|
|
305
322
|
w.once('message', (msg) => {
|
|
306
323
|
try {
|
|
307
324
|
if (msg && msg.ok && msg.graph && typeof msg.signature === 'string') {
|
|
308
|
-
const genStillCurrent =
|
|
325
|
+
const genStillCurrent = getGeneration(graphCwd) === genAtStart;
|
|
309
326
|
if (genStillCurrent) {
|
|
310
|
-
|
|
311
|
-
|
|
327
|
+
setMemoryCache(graphCwd, { ts: Date.now(), signature: msg.signature, graph: msg.graph });
|
|
328
|
+
setDiskCache(graphCwd, msg.graph);
|
|
312
329
|
}
|
|
313
330
|
settle(genStillCurrent ? msg.graph : new Error('code-graph build invalidated during prewarm'));
|
|
314
331
|
} else {
|
|
315
|
-
settle(
|
|
332
|
+
settle(_codeGraphWorkerFailure(msg));
|
|
316
333
|
}
|
|
317
334
|
} catch (e) { settle(e instanceof Error ? e : new Error(String(e))); }
|
|
318
335
|
});
|
|
@@ -50,6 +50,7 @@ function _collectGraphSymbolList(args) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const CODE_GRAPH_FILE_BATCH_CAP = 20;
|
|
53
|
+
const _AGGREGATE_FILE_WILDCARD_RE = /[*?[\]{}]/;
|
|
53
54
|
|
|
54
55
|
// Absorb: file/files arriving as a JSON-stringified array
|
|
55
56
|
// (file:"[\"a.mjs\",\"b.mjs\"]") — parse to a real array so the graph lookup
|
|
@@ -74,21 +75,21 @@ function _normalizeGraphFileArgs(args) {
|
|
|
74
75
|
if (fileArr) { out.files = Array.isArray(out.files) ? [...fileArr, ...out.files] : fileArr; delete out.file; }
|
|
75
76
|
if (filesArr) out.files = filesArr;
|
|
76
77
|
// Collapse a lone entry back to the single-file field for the fast path.
|
|
77
|
-
if (Array.isArray(out.files) && out.files.length === 1 && !out.file) {
|
|
78
|
+
if (Array.isArray(out.files) && out.files.length === 1 && !out.file && !filesArr) {
|
|
78
79
|
out.file = out.files[0];
|
|
79
80
|
delete out.files;
|
|
80
81
|
}
|
|
81
82
|
return out;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
function _collectGraphFileList(args) {
|
|
85
|
+
function _collectGraphFileList(args, { cap = true } = {}) {
|
|
85
86
|
const split = (s) => String(s || '').split(/,+/).map((t) => t.trim()).filter(Boolean);
|
|
86
87
|
const list = [...new Set([
|
|
87
88
|
...(Array.isArray(args?.files) ? args.files.map((f) => String(f || '').trim()).filter(Boolean) : []),
|
|
88
89
|
...(typeof args?.files === 'string' ? split(args.files) : []),
|
|
89
90
|
...(typeof args?.file === 'string' && args.file.trim() ? [args.file.trim()] : []),
|
|
90
91
|
])];
|
|
91
|
-
if (list.length > CODE_GRAPH_FILE_BATCH_CAP) {
|
|
92
|
+
if (cap && list.length > CODE_GRAPH_FILE_BATCH_CAP) {
|
|
92
93
|
const capped = list.slice(0, CODE_GRAPH_FILE_BATCH_CAP);
|
|
93
94
|
capped._capped = true;
|
|
94
95
|
return capped;
|
|
@@ -96,6 +97,57 @@ function _collectGraphFileList(args) {
|
|
|
96
97
|
return list;
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
function _hasAggregateFileArgs(args) {
|
|
101
|
+
return (Array.isArray(args?.files) && args.files.some((f) => String(f || '').trim()))
|
|
102
|
+
|| (typeof args?.files === 'string' && args.files.trim());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// An invalid caller cwd may be recovered for an explicit files aggregate only
|
|
106
|
+
// when every supplied anchor points at the same detectable project. Do not use
|
|
107
|
+
// the batch cap here: an omitted anchor could belong to another project.
|
|
108
|
+
function _resolveAggregateFileProjectRoot(args, baseCwd) {
|
|
109
|
+
if (!_hasAggregateFileArgs(args)) return null;
|
|
110
|
+
// Comma-delimited strings are parsed for normal batch dispatch, but are not
|
|
111
|
+
// unambiguous enough to select a project root. JSON array strings have
|
|
112
|
+
// already been normalized to an actual array above.
|
|
113
|
+
if (typeof args?.files === 'string' && args.files.includes(',')) return null;
|
|
114
|
+
const files = _collectGraphFileList(args, { cap: false });
|
|
115
|
+
// Recovery cannot silently discard anchors that normal batch dispatch caps.
|
|
116
|
+
if (files.length > CODE_GRAPH_FILE_BATCH_CAP) return null;
|
|
117
|
+
const roots = new Set();
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
// Never infer a root from a glob-shaped anchor, including a literal file
|
|
120
|
+
// whose name contains a glob metacharacter.
|
|
121
|
+
if (_AGGREGATE_FILE_WILDCARD_RE.test(file)) return null;
|
|
122
|
+
const abs = isAbsolute(file) ? pathResolve(file) : pathResolve(baseCwd, file);
|
|
123
|
+
if (!existsSync(abs)) return null;
|
|
124
|
+
let isDirectory = false;
|
|
125
|
+
try { isDirectory = statSync(abs).isDirectory(); } catch { return null; }
|
|
126
|
+
const root = isDirectory ? _findDirProjectRoot(abs) : _resolveFileProjectRoot(abs);
|
|
127
|
+
if (!root) return null;
|
|
128
|
+
roots.add(pathResolve(root));
|
|
129
|
+
if (roots.size > 1) return null;
|
|
130
|
+
}
|
|
131
|
+
return roots.size === 1 ? [...roots][0] : null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Aggregate recovery resolves relative anchors against the caller's original
|
|
135
|
+
// cwd. Keep those resolved paths when dispatching under the recovered root;
|
|
136
|
+
// otherwise codeGraph resolves them a second time below that root.
|
|
137
|
+
function _absolutizeAggregateFileArgs(args, baseCwd) {
|
|
138
|
+
const absolutize = (file) => {
|
|
139
|
+
const trimmed = String(file || '').trim();
|
|
140
|
+
return trimmed && !isAbsolute(trimmed) ? pathResolve(baseCwd, trimmed) : file;
|
|
141
|
+
};
|
|
142
|
+
return {
|
|
143
|
+
...args,
|
|
144
|
+
file: typeof args?.file === 'string' ? absolutize(args.file) : args?.file,
|
|
145
|
+
files: Array.isArray(args?.files)
|
|
146
|
+
? args.files.map(absolutize)
|
|
147
|
+
: (typeof args?.files === 'string' ? absolutize(args.files) : args?.files),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
99
151
|
async function codeGraph(args, cwd, signal = null, options = {}) {
|
|
100
152
|
let mode = String(args?.mode || '').trim();
|
|
101
153
|
if (!mode) throw new Error('code_graph: "mode" is required');
|
|
@@ -539,8 +591,21 @@ export async function executeCodeGraphTool(name, args, cwd, signal = null, optio
|
|
|
539
591
|
delete args.file;
|
|
540
592
|
}
|
|
541
593
|
const fileArg = (args && typeof args.file === 'string' && args.file.trim()) ? args.file.trim() : '';
|
|
594
|
+
const hasAggregateFileArgs = _hasAggregateFileArgs(args);
|
|
542
595
|
let effectiveCwd = baseCwd;
|
|
543
|
-
|
|
596
|
+
const baseProjectRoot = _findDirProjectRoot(baseCwd);
|
|
597
|
+
if (hasAggregateFileArgs && !baseProjectRoot) {
|
|
598
|
+
const aggregateRoot = _resolveAggregateFileProjectRoot(args, baseCwd);
|
|
599
|
+
if (!aggregateRoot) {
|
|
600
|
+
throw new Error(
|
|
601
|
+
`${name}: cwd '${baseCwd}' is not inside a project and aggregate file anchors do not all `
|
|
602
|
+
+ `exist under exactly one detectable project root. Refusing to index an arbitrary tree.`,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
effectiveCwd = aggregateRoot;
|
|
606
|
+
args = _absolutizeAggregateFileArgs(args, baseCwd);
|
|
607
|
+
}
|
|
608
|
+
if (fileArg && !hasAggregateFileArgs) {
|
|
544
609
|
const abs = isAbsolute(fileArg) ? pathResolve(fileArg) : pathResolve(baseCwd, fileArg);
|
|
545
610
|
if (!existsSync(abs)) {
|
|
546
611
|
const elsewhere = findFileByBasename(pathResolve(baseCwd), abs);
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// seeds _codeGraphCache directly. Graph is structuredClone-transferred —
|
|
10
10
|
// Maps/Sets of plain objects only, runtime caches are empty Maps that
|
|
11
11
|
// survive the clone.
|
|
12
|
-
// - On any failure: postMessage({ ok: false }). Main thread propagates
|
|
13
|
-
//
|
|
12
|
+
// - On any failure: postMessage({ ok: false, error }). Main thread propagates
|
|
13
|
+
// the worker error — find_symbol / code_graph tools throw. No sync fallback.
|
|
14
14
|
import { parentPort, workerData } from 'node:worker_threads';
|
|
15
15
|
import { _buildCodeGraph, _postCodeGraphWorkerSuccess } from './code-graph.mjs';
|
|
16
16
|
|
|
@@ -37,6 +37,7 @@ try {
|
|
|
37
37
|
parentPort.postMessage({ ok: false });
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
} catch {
|
|
41
|
-
|
|
40
|
+
} catch (error) {
|
|
41
|
+
const message = (error instanceof Error ? error.message : String(error)).trim();
|
|
42
|
+
parentPort.postMessage(message ? { ok: false, error: message } : { ok: false });
|
|
42
43
|
}
|
|
@@ -3,7 +3,7 @@ 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 code structure/flow over verified source files only. IDs→graph; literal/zero→grep. Exact identifiers
|
|
6
|
+
description: 'Repo code structure/flow over verified source files only. IDs→graph; literal/zero→grep. File modes take files[]; symbol modes find_symbol/symbol_search/search/references/callers/callees take symbols[]. Exact identifiers use find_symbol/references/callers/callees and keywords use symbol_search/search. Unsupported target arrays are omitted, never silently mixed. Batch targets per mode.',
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"_comment": "
|
|
2
|
+
"version": "0.1.1",
|
|
3
|
+
"_comment": "Synced from immutable graph-v release assets.",
|
|
4
4
|
"assets": {
|
|
5
5
|
"darwin-arm64": {
|
|
6
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/v0.
|
|
7
|
-
"sha256": "
|
|
6
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.1/mixdog-graph-darwin-arm64",
|
|
7
|
+
"sha256": "3fb76a5e3064547b68e7bcab6ce2a855ce615a732157d836cb561c5d7c8d86b6"
|
|
8
8
|
},
|
|
9
9
|
"darwin-x64": {
|
|
10
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/v0.
|
|
11
|
-
"sha256": "
|
|
10
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.1/mixdog-graph-darwin-x64",
|
|
11
|
+
"sha256": "64eeffc17984a76b0fbf7d85f9d84cb5e1bb949a251053feb260f70481235bb1"
|
|
12
12
|
},
|
|
13
13
|
"linux-arm64": {
|
|
14
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/v0.
|
|
15
|
-
"sha256": "
|
|
14
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.1/mixdog-graph-linux-arm64",
|
|
15
|
+
"sha256": "15d5cbb7575b3e0671c6f5fbef3460c5c9353f17968a80faba737fcc15b49c33"
|
|
16
16
|
},
|
|
17
17
|
"linux-x64": {
|
|
18
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/v0.
|
|
19
|
-
"sha256": "
|
|
18
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.1/mixdog-graph-linux-x64",
|
|
19
|
+
"sha256": "5079544c09ae2311295cef21978fda3c1c06f93beaaf666cfe1f68971d5b27ae"
|
|
20
20
|
},
|
|
21
21
|
"win32-x64": {
|
|
22
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/v0.
|
|
23
|
-
"sha256": "
|
|
22
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.1/mixdog-graph-win32-x64.exe",
|
|
23
|
+
"sha256": "acfbcd02b96154f4fb342a44dec1a4808752944c5941bbb7983d84ed64464830"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -70,30 +70,35 @@ function nativePatchPersistent() {
|
|
|
70
70
|
return /^(1|true|yes|server|persistent)$/i.test(nativePatchMode());
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
export function nativePatchBinPath() {
|
|
73
|
+
export function nativePatchBinPath(options = {}) {
|
|
74
74
|
if (process.env.MIXDOG_PATCH_NATIVE_BIN) return process.env.MIXDOG_PATCH_NATIVE_BIN;
|
|
75
75
|
// Local cargo build first, then a fetched/cached prebuilt; absence is
|
|
76
76
|
// a hard error at dispatch (no JS fallback in native-only mode).
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
const defaultBin = options.defaultBin || NATIVE_PATCH_DEFAULT_BIN;
|
|
78
|
+
if (existsSync(defaultBin)) return defaultBin;
|
|
79
|
+
const dataDir = options.dataDir || getPluginData();
|
|
80
|
+
return findCachedPatchBinary(dataDir, options.fetcherOptions) || defaultBin;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
export async function ensureNativePatchBinaryAvailable() {
|
|
83
|
+
export async function ensureNativePatchBinaryAvailable(options = {}) {
|
|
82
84
|
if (!nativePatchEnabled()) {
|
|
83
85
|
throw new Error('apply_patch: native engine disabled via MIXDOG_PATCH_NATIVE; set it to "auto" or "1" to apply patches.');
|
|
84
86
|
}
|
|
85
|
-
const current = nativePatchBinPath();
|
|
87
|
+
const current = nativePatchBinPath(options);
|
|
86
88
|
if (existsSync(current)) return current;
|
|
87
89
|
if (process.env.MIXDOG_PATCH_NATIVE_BIN) {
|
|
88
90
|
throw new Error(`apply_patch: native patch binary not found at MIXDOG_PATCH_NATIVE_BIN=${current}.`);
|
|
89
91
|
}
|
|
90
92
|
try {
|
|
91
|
-
const fetched = await ensurePatchBinary(
|
|
93
|
+
const fetched = await ensurePatchBinary(
|
|
94
|
+
options.dataDir || getPluginData(),
|
|
95
|
+
options.fetcherOptions,
|
|
96
|
+
);
|
|
92
97
|
if (fetched && existsSync(fetched)) return fetched;
|
|
93
98
|
} catch (err) {
|
|
94
99
|
throw new Error(`apply_patch: native patch binary unavailable — ${err?.message || String(err)}`);
|
|
95
100
|
}
|
|
96
|
-
const resolved = nativePatchBinPath();
|
|
101
|
+
const resolved = nativePatchBinPath(options);
|
|
97
102
|
if (existsSync(resolved)) return resolved;
|
|
98
103
|
throw new Error(`apply_patch: native patch binary not found at ${resolved}.`);
|
|
99
104
|
}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// ensurePatchBinary(dataDir) -> absolute path to the verified binary.
|
|
8
8
|
// Throws on no-asset / download / verify failure.
|
|
9
9
|
// findCachedPatchBinary(dataDir) -> path | null (sync, no network).
|
|
10
|
+
// Both accept an optional dependency object used by deterministic tests:
|
|
11
|
+
// { bundledManifest, download }.
|
|
10
12
|
|
|
11
13
|
import { createHash } from 'node:crypto';
|
|
12
14
|
import {
|
|
@@ -34,16 +36,68 @@ function patchBinDir(dataDir) {
|
|
|
34
36
|
return join(dataDir, 'patch-bin');
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
function readJson(path) {
|
|
40
|
+
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function manifestVersion(manifest) {
|
|
44
|
+
const value = String(manifest?.version || '');
|
|
45
|
+
if (!/^\d+\.\d+\.\d+$/.test(value)) return null;
|
|
46
|
+
return value.split('.').map(Number);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function compareManifestVersions(a, b) {
|
|
50
|
+
const av = manifestVersion(a);
|
|
51
|
+
const bv = manifestVersion(b);
|
|
52
|
+
if (!av || !bv) return null;
|
|
53
|
+
for (let i = 0; i < 3; i++) {
|
|
54
|
+
if (av[i] !== bv[i]) return av[i] > bv[i] ? 1 : -1;
|
|
55
|
+
}
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validSha256(value) {
|
|
60
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function validCachedUpgrade(manifest, pkey) {
|
|
64
|
+
if (!manifestVersion(manifest)) return false;
|
|
65
|
+
const asset = manifest.assets?.[pkey];
|
|
66
|
+
if (!asset || !validSha256(asset.sha256) || typeof asset.url !== 'string') return false;
|
|
67
|
+
const expectedUrl = `https://github.com/tribgames/mixdog/releases/download/patch-v${manifest.version}`
|
|
68
|
+
+ `/mixdog-patch-${pkey}${binSuffix()}`;
|
|
69
|
+
return asset.url === expectedUrl;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function bundledManifest(options) {
|
|
73
|
+
if (options.bundledManifest) return options.bundledManifest;
|
|
74
|
+
return existsSync(BUNDLED_MANIFEST_PATH) ? readJson(BUNDLED_MANIFEST_PATH) : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function selectLocalManifest(dataDir, options = {}) {
|
|
78
|
+
const bundled = bundledManifest(options);
|
|
38
79
|
const cached = join(patchBinDir(dataDir), 'manifest.json');
|
|
39
|
-
|
|
40
|
-
|
|
80
|
+
const cachedManifest = existsSync(cached) ? readJson(cached) : null;
|
|
81
|
+
if (bundled) {
|
|
82
|
+
// The installed manifest is the minimum policy. A cache may advance it,
|
|
83
|
+
// but only with a strict newer semver and a trusted, fully hashed asset.
|
|
84
|
+
if (compareManifestVersions(cachedManifest, bundled) === 1
|
|
85
|
+
&& validCachedUpgrade(cachedManifest, platformKey())) {
|
|
86
|
+
return cachedManifest;
|
|
87
|
+
}
|
|
88
|
+
return bundled;
|
|
41
89
|
}
|
|
42
|
-
|
|
43
|
-
|
|
90
|
+
return validCachedUpgrade(cachedManifest, platformKey()) ? cachedManifest : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function loadManifest(dataDir, options = {}) {
|
|
94
|
+
const local = selectLocalManifest(dataDir, options);
|
|
95
|
+
if (local) return local;
|
|
96
|
+
const fetchFn = options.fetch || fetch;
|
|
97
|
+
const res = await fetchFn(MANIFEST_URL, { signal: AbortSignal.timeout(30_000) });
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
throw new Error(`[patch-fetcher] manifest fetch failed: ${res.status} ${res.statusText}`);
|
|
44
100
|
}
|
|
45
|
-
const res = await fetch(MANIFEST_URL, { signal: AbortSignal.timeout(30_000) });
|
|
46
|
-
if (!res.ok) throw new Error(`[patch-fetcher] manifest fetch failed: ${res.status} ${res.statusText}`);
|
|
47
101
|
return res.json();
|
|
48
102
|
}
|
|
49
103
|
|
|
@@ -86,13 +140,18 @@ function gcPatchBin(dir, keepFile) {
|
|
|
86
140
|
} catch { /* dir may not exist yet */ }
|
|
87
141
|
}
|
|
88
142
|
|
|
89
|
-
export function findCachedPatchBinary(dataDir) {
|
|
143
|
+
export function findCachedPatchBinary(dataDir, options = {}) {
|
|
90
144
|
try {
|
|
91
145
|
const dir = patchBinDir(dataDir);
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
);
|
|
95
|
-
|
|
146
|
+
const manifest = selectLocalManifest(dataDir, options);
|
|
147
|
+
const asset = manifest?.assets?.[platformKey()];
|
|
148
|
+
const version = manifestVersion(manifest);
|
|
149
|
+
if (!version || !validSha256(asset?.sha256)) return null;
|
|
150
|
+
const fileName = `mixdog-patch-${manifest.version}${binSuffix()}`;
|
|
151
|
+
const hit = join(dir, fileName);
|
|
152
|
+
if (!existsSync(hit)) return null;
|
|
153
|
+
const actual = createHash('sha256').update(readFileSync(hit)).digest('hex');
|
|
154
|
+
return actual === asset.sha256.toLowerCase() ? hit : null;
|
|
96
155
|
} catch {
|
|
97
156
|
return null;
|
|
98
157
|
}
|
|
@@ -100,13 +159,13 @@ export function findCachedPatchBinary(dataDir) {
|
|
|
100
159
|
|
|
101
160
|
let _inflight = null;
|
|
102
161
|
|
|
103
|
-
export function ensurePatchBinary(dataDir) {
|
|
162
|
+
export function ensurePatchBinary(dataDir, options = {}) {
|
|
104
163
|
if (_inflight) return _inflight;
|
|
105
164
|
_inflight = (async () => {
|
|
106
|
-
const manifest = await loadManifest(dataDir);
|
|
165
|
+
const manifest = await loadManifest(dataDir, options);
|
|
107
166
|
const pkey = platformKey();
|
|
108
167
|
const asset = manifest.assets?.[pkey];
|
|
109
|
-
if (!asset || !asset.url || !asset.sha256) {
|
|
168
|
+
if (!asset || !asset.url || !validSha256(asset.sha256) || !manifestVersion(manifest)) {
|
|
110
169
|
// Unsupported platform/arch (e.g. win32-arm64): the manifest has no
|
|
111
170
|
// downloadable asset for this {os}-{arch}. apply_patch is native-only
|
|
112
171
|
// (no JS apply fallback), so this is terminal — surface a single clear,
|
|
@@ -125,12 +184,12 @@ export function ensurePatchBinary(dataDir) {
|
|
|
125
184
|
const fileName = `mixdog-patch-${version}${binSuffix()}`;
|
|
126
185
|
const destPath = join(dir, fileName);
|
|
127
186
|
if (existsSync(destPath)) {
|
|
128
|
-
try { if (await sha256File(destPath) === asset.sha256) return destPath; } catch { /* re-download */ }
|
|
187
|
+
try { if (await sha256File(destPath) === asset.sha256.toLowerCase()) return destPath; } catch { /* re-download */ }
|
|
129
188
|
}
|
|
130
189
|
const tmpPath = `${destPath}.tmp-${process.pid}-${Date.now()}`;
|
|
131
|
-
await downloadWithRetry(asset.url, tmpPath);
|
|
190
|
+
await (options.download || downloadWithRetry)(asset.url, tmpPath);
|
|
132
191
|
const actual = await sha256File(tmpPath);
|
|
133
|
-
if (actual !== asset.sha256) {
|
|
192
|
+
if (actual !== asset.sha256.toLowerCase()) {
|
|
134
193
|
try { rmSync(tmpPath, { force: true }); } catch { /* best-effort */ }
|
|
135
194
|
throw new Error(`[patch-fetcher] sha256 mismatch for ${pkey}: expected ${asset.sha256}, got ${actual}`);
|
|
136
195
|
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.7.
|
|
2
|
+
"version": "0.7.20",
|
|
3
3
|
"_comment": "Rewritten by .github/workflows/patch-release.yml on each tagged release. assets maps platformKey (process.platform-process.arch, e.g. win32-x64, linux-x64, darwin-arm64) to { url, sha256 } of the mixdog-patch binary on the GitHub release. A local cargo build under native/mixdog-patch/target/release always takes precedence; otherwise the binary is fetched per this manifest into the data dir (apply is native-only — no JS apply engine).",
|
|
4
4
|
"assets": {
|
|
5
5
|
"darwin-arm64": {
|
|
6
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.
|
|
7
|
-
"sha256": "
|
|
6
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.20/mixdog-patch-darwin-arm64",
|
|
7
|
+
"sha256": "12b9b3ad3365fcde21c83c1b42236f6bd70dbe411d79e5c0f7c3c5743f1a3185"
|
|
8
8
|
},
|
|
9
9
|
"darwin-x64": {
|
|
10
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.
|
|
11
|
-
"sha256": "
|
|
10
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.20/mixdog-patch-darwin-x64",
|
|
11
|
+
"sha256": "ff5de6da5fec3abbf617c0c3b1ead8e724d4cb0e9948277253889c83bc27a570"
|
|
12
12
|
},
|
|
13
13
|
"linux-arm64": {
|
|
14
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.
|
|
15
|
-
"sha256": "
|
|
14
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.20/mixdog-patch-linux-arm64",
|
|
15
|
+
"sha256": "76d7cf54df7face32fb20862425ce25ad44e9800440132dea3f0ad387c92f07e"
|
|
16
16
|
},
|
|
17
17
|
"linux-x64": {
|
|
18
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.
|
|
19
|
-
"sha256": "
|
|
18
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.20/mixdog-patch-linux-x64",
|
|
19
|
+
"sha256": "43f6610beadf25eca752a09775fcb8858818ad2d9f7b8cf6a00deaa47571f3f0"
|
|
20
20
|
},
|
|
21
21
|
"win32-x64": {
|
|
22
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.
|
|
23
|
-
"sha256": "
|
|
22
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.20/mixdog-patch-win32-x64.exe",
|
|
23
|
+
"sha256": "4d5d2242f9662aeb968c24df571a498008d9c504fd31214fe9dac31aac669fdd"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|