mixdog 0.9.75 → 0.9.78
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 +8 -3
- package/scripts/prune-embedding-runtime.mjs +159 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +3 -1
- package/src/runtime/memory/lib/embedding-provider.mjs +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixdog",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.78",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone mixdog coding-agent CLI/TUI workspace.",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"!scripts/smoke-loop*.mjs",
|
|
34
34
|
"!scripts/*-bench.mjs",
|
|
35
35
|
"!scripts/*bench-*.mjs",
|
|
36
|
+
"!scripts/verify-embedding-runtime.mjs",
|
|
36
37
|
"!scripts/*.ps1",
|
|
37
38
|
"!scripts/*.jsx",
|
|
38
39
|
"src/",
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
"vendor/"
|
|
41
42
|
],
|
|
42
43
|
"scripts": {
|
|
44
|
+
"postinstall": "node scripts/prune-embedding-runtime.mjs",
|
|
43
45
|
"prepack": "npm run build:tui",
|
|
44
46
|
"prepublishOnly": "node -e \"if(!process.env.CI){console.error('local npm publish is disabled — run npm run release:patch');process.exit(1)}\"",
|
|
45
47
|
"start": "node src/cli.mjs",
|
|
@@ -71,7 +73,10 @@
|
|
|
71
73
|
"test:anthropic-oauth-race": "node --test scripts/anthropic-oauth-refresh-race-test.mjs",
|
|
72
74
|
"test:grok-oauth-race": "node --test scripts/grok-oauth-refresh-race-test.mjs",
|
|
73
75
|
"test:atomiclock": "node --test scripts/atomic-lock-tryonce-test.mjs",
|
|
74
|
-
"test:memory-routing": "node --test scripts/memory-cycle-routing-test.mjs scripts/maintenance-default-routes-test.mjs scripts/embedding-worker-exit-test.mjs",
|
|
76
|
+
"test:memory-routing": "node --test scripts/memory-cycle-routing-test.mjs scripts/maintenance-default-routes-test.mjs scripts/embedding-worker-exit-test.mjs scripts/embedding-runtime-prune-test.mjs",
|
|
77
|
+
"test:embedding-runtime": "node --test scripts/embedding-runtime-prune-test.mjs && node scripts/verify-embedding-runtime.mjs",
|
|
78
|
+
"test:embedding-runtime:core": "node --test scripts/embedding-runtime-prune-test.mjs && node scripts/verify-embedding-runtime.mjs --core",
|
|
79
|
+
"test:embedding-runtime:warmup": "node scripts/verify-embedding-runtime.mjs --warmup",
|
|
75
80
|
"test:code-graph-dispatch": "node --test scripts/code-graph-dispatch-test.mjs",
|
|
76
81
|
"test:code-graph-clean-cache": "node --test scripts/code-graph-dispatch-test.mjs",
|
|
77
82
|
"test:tui-queue": "node --test scripts/submit-commandbusy-race-test.mjs scripts/steering-drain-buckets-test.mjs scripts/abort-recovery-test.mjs scripts/execution-pending-resume-kick-test.mjs scripts/execution-resume-esc-integration-test.mjs",
|
|
@@ -79,7 +84,7 @@
|
|
|
79
84
|
"test:tui-streaming-window": "node --test scripts/streaming-tail-window-test.mjs",
|
|
80
85
|
"test:tui-ambiguous-width": "node --test scripts/tui-ambiguous-width-test.mjs",
|
|
81
86
|
"test:release-assets": "node --check scripts/verify-release-assets.mjs && node --check scripts/verify-release-assets-test.mjs && node --test scripts/verify-release-assets-test.mjs",
|
|
82
|
-
"test:release-focused": "npm run test:release-assets && npm run test:tool-contracts && npm run test:placeholder && npm run smoke:patch && npm run test:patch-binary-cache && npm run test:providers && npm run test:deferred-tools && npm run smoke:compact && node --test scripts/code-graph-root-federation-test.mjs scripts/code-graph-aggregate-cwd-test.mjs && npm run test:code-graph-dispatch && node --test scripts/code-graph-disk-hit-test.mjs && npm run test:shellhardening && node --test scripts/windows-hide-spawn-options-test.mjs && npm run test:session && npm run test:workflow-editor && node --test scripts/tui-transcript-perf-test.mjs",
|
|
87
|
+
"test:release-focused": "npm run test:release-assets && npm run test:tool-contracts && npm run test:placeholder && npm run smoke:patch && npm run test:patch-binary-cache && npm run test:providers && npm run test:deferred-tools && npm run smoke:compact && node --test scripts/code-graph-root-federation-test.mjs scripts/code-graph-aggregate-cwd-test.mjs && npm run test:code-graph-dispatch && node --test scripts/code-graph-disk-hit-test.mjs && npm run test:shellhardening && node --test scripts/windows-hide-spawn-options-test.mjs && npm run test:session && npm run test:workflow-editor && npm run test:embedding-runtime && node --test scripts/tui-transcript-perf-test.mjs",
|
|
83
88
|
"test:native-edit-wire": "node --test scripts/native-edit-wire-test.mjs",
|
|
84
89
|
"test:patch-binary-cache": "node --test scripts/patch-binary-cache-test.mjs",
|
|
85
90
|
"test:session": "node --test scripts/session-orphan-sweep-test.mjs scripts/interrupted-turn-history-test.mjs scripts/session-heartbeat-lifecycle-test.mjs scripts/remote-transition-order-test.mjs",
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { access, readdir, readFile, rm } from 'node:fs/promises'
|
|
4
|
+
import { dirname, join, resolve } from 'node:path'
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const SUPPORTED_TARGETS = new Map([
|
|
8
|
+
['win32', new Set(['x64', 'arm64'])],
|
|
9
|
+
['darwin', new Set(['x64', 'arm64'])],
|
|
10
|
+
['linux', new Set(['x64', 'arm64'])],
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
async function exists(path) {
|
|
14
|
+
try {
|
|
15
|
+
await access(path)
|
|
16
|
+
return true
|
|
17
|
+
} catch {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function removeChildrenExcept(directory, keep) {
|
|
23
|
+
let entries
|
|
24
|
+
try {
|
|
25
|
+
entries = await readdir(directory, { withFileTypes: true })
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error?.code === 'ENOENT') return
|
|
28
|
+
throw error
|
|
29
|
+
}
|
|
30
|
+
await Promise.all(entries
|
|
31
|
+
.filter((entry) => !keep.has(entry.name))
|
|
32
|
+
.map((entry) => rm(join(directory, entry.name), { recursive: true, force: true })))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function packageName(directory) {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(await readFile(join(directory, 'package.json'), 'utf8')).name
|
|
38
|
+
} catch {
|
|
39
|
+
return ''
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function findPackageRoot(entry, expectedName) {
|
|
44
|
+
let current = dirname(entry)
|
|
45
|
+
for (;;) {
|
|
46
|
+
if (await packageName(current) === expectedName) return current
|
|
47
|
+
const parent = dirname(current)
|
|
48
|
+
if (parent === current) break
|
|
49
|
+
current = parent
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`Unable to locate ${expectedName} from ${entry}`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function embeddingRuntimeTarget(options = {}) {
|
|
55
|
+
const platform = String(
|
|
56
|
+
options.platform
|
|
57
|
+
|| process.env.MIXDOG_EMBED_TARGET_PLATFORM
|
|
58
|
+
|| process.platform,
|
|
59
|
+
).trim()
|
|
60
|
+
const arch = String(
|
|
61
|
+
options.arch
|
|
62
|
+
|| process.env.MIXDOG_EMBED_TARGET_ARCH
|
|
63
|
+
|| process.arch,
|
|
64
|
+
).trim()
|
|
65
|
+
if (!SUPPORTED_TARGETS.get(platform)?.has(arch)) {
|
|
66
|
+
throw new Error(`Unsupported embedding runtime target: ${platform}-${arch}`)
|
|
67
|
+
}
|
|
68
|
+
return { platform, arch, key: `${platform}-${arch}` }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function pruneEmbeddingRuntime(packageRoot, options = {}) {
|
|
72
|
+
const root = resolve(packageRoot)
|
|
73
|
+
const target = embeddingRuntimeTarget(options)
|
|
74
|
+
const nodeModules = join(root, 'node_modules')
|
|
75
|
+
const transformerRoot = join(nodeModules, '@huggingface', 'transformers')
|
|
76
|
+
const transformerPackage = join(transformerRoot, 'package.json')
|
|
77
|
+
if (!(await exists(transformerPackage))) {
|
|
78
|
+
throw new Error(`Embedding runtime is incomplete: missing ${transformerPackage}`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const transformerEntry = join(transformerRoot, 'dist', 'transformers.node.cjs')
|
|
82
|
+
const transformerImport = join(transformerRoot, 'dist', 'transformers.node.mjs')
|
|
83
|
+
for (const required of [transformerEntry, transformerImport]) {
|
|
84
|
+
if (!(await exists(required))) {
|
|
85
|
+
throw new Error(`Embedding runtime is incomplete: missing ${required}`)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const ortCandidates = [
|
|
90
|
+
join(transformerRoot, 'node_modules', 'onnxruntime-node'),
|
|
91
|
+
join(nodeModules, 'onnxruntime-node'),
|
|
92
|
+
]
|
|
93
|
+
const ortRoot = (await Promise.all(ortCandidates.map(async (candidate) => (
|
|
94
|
+
await exists(join(candidate, 'package.json')) ? candidate : null
|
|
95
|
+
)))).find(Boolean)
|
|
96
|
+
if (!ortRoot) throw new Error('Embedding runtime is incomplete: onnxruntime-node is unavailable')
|
|
97
|
+
|
|
98
|
+
const targetBinaryDir = join(ortRoot, 'bin', 'napi-v3', target.platform, target.arch)
|
|
99
|
+
if (!(await exists(join(targetBinaryDir, 'onnxruntime_binding.node')))) {
|
|
100
|
+
throw new Error(`Embedding runtime is incomplete: missing ${target.key} ONNX binding`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Transformers' Node conditional export bundles all JS implementation code.
|
|
104
|
+
// Browser bundles, WASM, maps, source and types are not runtime inputs.
|
|
105
|
+
await removeChildrenExcept(transformerRoot, new Set([
|
|
106
|
+
'package.json',
|
|
107
|
+
'LICENSE',
|
|
108
|
+
'dist',
|
|
109
|
+
'node_modules',
|
|
110
|
+
]))
|
|
111
|
+
await removeChildrenExcept(join(transformerRoot, 'dist'), new Set([
|
|
112
|
+
'transformers.node.cjs',
|
|
113
|
+
'transformers.node.mjs',
|
|
114
|
+
]))
|
|
115
|
+
|
|
116
|
+
// onnxruntime-node resolves exactly:
|
|
117
|
+
// bin/napi-v3/${process.platform}/${process.arch}/onnxruntime_binding.node.
|
|
118
|
+
// Keep one native payload and remove every foreign OS/architecture.
|
|
119
|
+
const napiRoot = join(ortRoot, 'bin', 'napi-v3')
|
|
120
|
+
await removeChildrenExcept(napiRoot, new Set([target.platform]))
|
|
121
|
+
await removeChildrenExcept(join(napiRoot, target.platform), new Set([target.arch]))
|
|
122
|
+
await removeChildrenExcept(ortRoot, new Set([
|
|
123
|
+
'package.json',
|
|
124
|
+
'dist',
|
|
125
|
+
'bin',
|
|
126
|
+
'node_modules',
|
|
127
|
+
]))
|
|
128
|
+
|
|
129
|
+
// The Node bundle marks onnxruntime-web as an ignored webpack external. Keep
|
|
130
|
+
// only its manifest so npm's dependency inventory remains coherent.
|
|
131
|
+
for (const webRoot of [
|
|
132
|
+
join(nodeModules, 'onnxruntime-web'),
|
|
133
|
+
join(transformerRoot, 'node_modules', 'onnxruntime-web'),
|
|
134
|
+
]) {
|
|
135
|
+
if (await exists(join(webRoot, 'package.json'))) {
|
|
136
|
+
await removeChildrenExcept(webRoot, new Set(['package.json', 'LICENSE']))
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
...target,
|
|
142
|
+
transformerRoot,
|
|
143
|
+
ortRoot,
|
|
144
|
+
targetBinaryDir,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : ''
|
|
149
|
+
if (invokedPath === import.meta.url) {
|
|
150
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
151
|
+
pruneEmbeddingRuntime(packageRoot)
|
|
152
|
+
.then(({ key }) => {
|
|
153
|
+
process.stdout.write(`Embedding runtime pruned for ${key}.\n`)
|
|
154
|
+
})
|
|
155
|
+
.catch((error) => {
|
|
156
|
+
process.stderr.write(`Embedding runtime prune failed: ${error?.message || error}\n`)
|
|
157
|
+
process.exitCode = 1
|
|
158
|
+
})
|
|
159
|
+
}
|
|
@@ -848,7 +848,17 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
|
|
|
848
848
|
activeSession.messages = finalized.messages;
|
|
849
849
|
if (!finalized.responsePreserved) {
|
|
850
850
|
releasePendingMessages(sessionId, _turnPendingEntries);
|
|
851
|
-
|
|
851
|
+
if (finalized.userTurnPreserved) {
|
|
852
|
+
// Non-user abort (app quit / engine dispose /
|
|
853
|
+
// watchdog): the just-sent user turn stays in
|
|
854
|
+
// history, so the session-start meta it carries
|
|
855
|
+
// remains consumed, and the opaque provider
|
|
856
|
+
// continuation no longer matches — force full
|
|
857
|
+
// transcript replay on the next send.
|
|
858
|
+
activeSession.providerState = undefined;
|
|
859
|
+
} else {
|
|
860
|
+
activeSession.sessionStartMetaInjected = _sessionStartMetaInjectedBeforeTurn;
|
|
861
|
+
}
|
|
852
862
|
} else {
|
|
853
863
|
recordPendingMessageDelivery(activeSession, _turnPendingEntries);
|
|
854
864
|
// The opaque provider continuation now points at a
|
|
@@ -10,6 +10,16 @@ const INTERRUPT_MESSAGE_FOR_TOOL_USE = '[Request interrupted by user for tool us
|
|
|
10
10
|
const STREAMING_INTERRUPTED_TOOL_RESULT = 'Interrupted by user';
|
|
11
11
|
const TOOL_USE_REJECT_RESULT = "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.";
|
|
12
12
|
|
|
13
|
+
// Abort reasons that represent an EXPLICIT user cancellation of this turn.
|
|
14
|
+
// Only these rewind a not-yet-answered user turn out of history — the TUI/
|
|
15
|
+
// desktop restore the prompt into the input box on user cancel, so keeping
|
|
16
|
+
// the message would duplicate it on resubmit. Any other closeReason (engine
|
|
17
|
+
// shutdown `cli-react-exit`, watchdog, idle-sweep, runner-crash, …) must
|
|
18
|
+
// PRESERVE the just-sent user turn: nothing restores the prompt there, and
|
|
19
|
+
// rewinding erases the user's message from the persisted transcript (the
|
|
20
|
+
// exact loss seen when the desktop app quits mid-turn).
|
|
21
|
+
const USER_CANCEL_ABORT_REASONS = new Set(['cli-abort', 'user-cancel', 'turn-abort']);
|
|
22
|
+
|
|
13
23
|
function assistantToolCallIds(message) {
|
|
14
24
|
if (!message || message.role !== 'assistant') return [];
|
|
15
25
|
const ids = [];
|
|
@@ -60,9 +70,21 @@ function finalizeInterruptedTurn({
|
|
|
60
70
|
const preserveResponse = responseStarted
|
|
61
71
|
&& !isInternalRuntimeNotificationText(currentUserContent);
|
|
62
72
|
if (!preserveResponse) {
|
|
73
|
+
// Null/unknown reasons keep the legacy rewind (status quo for wrapped
|
|
74
|
+
// aborts without a closeReason enum); named non-user reasons preserve.
|
|
75
|
+
const userCancelled = abortReason == null
|
|
76
|
+
|| USER_CANCEL_ABORT_REASONS.has(abortReason);
|
|
77
|
+
if (!userCancelled) {
|
|
78
|
+
return {
|
|
79
|
+
messages,
|
|
80
|
+
responsePreserved: false,
|
|
81
|
+
userTurnPreserved: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
63
84
|
return {
|
|
64
85
|
messages: rewindProvisionalUserTurn(messages, currentUserContent),
|
|
65
86
|
responsePreserved: false,
|
|
87
|
+
userTurnPreserved: false,
|
|
66
88
|
};
|
|
67
89
|
}
|
|
68
90
|
|
|
@@ -132,7 +154,7 @@ function finalizeInterruptedTurn({
|
|
|
132
154
|
: INTERRUPT_MESSAGE,
|
|
133
155
|
});
|
|
134
156
|
}
|
|
135
|
-
return { messages: pairedMessages, responsePreserved: true };
|
|
157
|
+
return { messages: pairedMessages, responsePreserved: true, userTurnPreserved: true };
|
|
136
158
|
}
|
|
137
159
|
|
|
138
160
|
export function createTurnInterruptionTracker() {
|
|
@@ -45,7 +45,7 @@ import { markCodeGraphDirtyPaths, drainCodeGraphCache } from './code-graph-state
|
|
|
45
45
|
import { maybeRewriteWmicProcessCommand } from './shell-policy.mjs';
|
|
46
46
|
import { _maybeEncodePowerShellCommand } from './shell-command.mjs';
|
|
47
47
|
import { _captureTrackedMtimes, _trackedDriftNoteAfter, getDedupedDestructiveWarnings } from './builtin/bash-tool.mjs';
|
|
48
|
-
import { scrubLoaderVars, scrubProviderSecrets } from './env-scrub.mjs';
|
|
48
|
+
import { scrubLoaderVars, scrubProviderSecrets, scrubRuntimeRootVars } from './env-scrub.mjs';
|
|
49
49
|
import { checkExecPolicyMessage } from './bash-policy-scan.mjs';
|
|
50
50
|
import { startChildGuardian } from '../../../shared/child-guardian.mjs';
|
|
51
51
|
import { resourceAdmission } from '../../../shared/resource-admission.mjs';
|
|
@@ -342,6 +342,8 @@ function buildBashEnv() {
|
|
|
342
342
|
scrubProviderSecrets(env);
|
|
343
343
|
// R11 loader/execution scrub (NODE_OPTIONS, LD_PRELOAD, DYLD_*, …).
|
|
344
344
|
scrubLoaderVars(env);
|
|
345
|
+
// Runtime-root isolation — see env-scrub.mjs scrubRuntimeRootVars.
|
|
346
|
+
scrubRuntimeRootVars(env);
|
|
345
347
|
return env;
|
|
346
348
|
}
|
|
347
349
|
|
|
@@ -44,7 +44,7 @@ import { normalizeOutputPath } from './path-utils.mjs';
|
|
|
44
44
|
import { normalizeErrorMessage } from './path-diagnostics.mjs';
|
|
45
45
|
import { invalidateBuiltinResultCache } from './cache-layers.mjs';
|
|
46
46
|
import { resolveOptionalCwd } from './cwd-utils.mjs';
|
|
47
|
-
import { scrubLoaderVars, scrubProviderSecrets } from '../env-scrub.mjs';
|
|
47
|
+
import { scrubLoaderVars, scrubProviderSecrets, scrubRuntimeRootVars } from '../env-scrub.mjs';
|
|
48
48
|
import { resolveSessionCwd, stateFilePath, wrapPowerShellWithCwdProbe, wrapBashWithCwdProbe } from '../shell-state.mjs';
|
|
49
49
|
import { resourceAdmission } from '../../../../shared/resource-admission.mjs';
|
|
50
50
|
|
|
@@ -407,6 +407,7 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
407
407
|
// R5/R11: same scrub as background/persistent spawn sites (env-scrub.mjs).
|
|
408
408
|
scrubProviderSecrets(spawnEnv);
|
|
409
409
|
scrubLoaderVars(spawnEnv);
|
|
410
|
+
scrubRuntimeRootVars(spawnEnv);
|
|
410
411
|
let wrappedCommand;
|
|
411
412
|
// PowerShell UTF-8 prefix is PS-only: the Windows Git Bash path
|
|
412
413
|
// (shellType==='posix') must NOT receive it. Snapshot wrapper stays
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
3
|
import { existsSync, readFileSync, statSync, unlinkSync, watch as fsWatch, writeFileSync } from 'fs';
|
|
4
4
|
import { basename } from 'path';
|
|
5
|
-
import { scrubLoaderVars, scrubProviderSecrets } from '../env-scrub.mjs';
|
|
5
|
+
import { scrubLoaderVars, scrubProviderSecrets, scrubRuntimeRootVars } from '../env-scrub.mjs';
|
|
6
6
|
import {
|
|
7
7
|
normalizeToolNotifyContext,
|
|
8
8
|
notifyToolCompletion,
|
|
@@ -169,7 +169,7 @@ export async function _startBackgroundShellJobImpl({
|
|
|
169
169
|
try {
|
|
170
170
|
child = spawnFn(shell, [wrappedTempPath], {
|
|
171
171
|
cwd: workDir,
|
|
172
|
-
env: scrubLoaderVars(scrubProviderSecrets({ ...spawnEnv })),
|
|
172
|
+
env: scrubRuntimeRootVars(scrubLoaderVars(scrubProviderSecrets({ ...spawnEnv }))),
|
|
173
173
|
stdio: 'ignore',
|
|
174
174
|
...detachedSpawnOpts,
|
|
175
175
|
});
|
|
@@ -398,7 +398,7 @@ async function startBackgroundPowerShellJob({
|
|
|
398
398
|
try {
|
|
399
399
|
child = spawnFn(shell, wrapperArgs, {
|
|
400
400
|
cwd: workDir,
|
|
401
|
-
env: scrubLoaderVars(scrubProviderSecrets({ ...spawnEnv })),
|
|
401
|
+
env: scrubRuntimeRootVars(scrubLoaderVars(scrubProviderSecrets({ ...spawnEnv }))),
|
|
402
402
|
detached: false,
|
|
403
403
|
stdio: 'ignore',
|
|
404
404
|
windowsHide: true,
|
|
@@ -98,3 +98,17 @@ export function scrubProviderSecrets(env) {
|
|
|
98
98
|
}
|
|
99
99
|
return env;
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
// Runtime-root isolation: MIXDOG_ROOT is a runtime-internal alias for THIS
|
|
103
|
+
// process's install/resource root (a packaged desktop host points it at
|
|
104
|
+
// resources/runtime.asar/...). Model-spawned shells must never inherit it —
|
|
105
|
+
// a dev-tree test run inside such a shell would resolve defaults/agents.json
|
|
106
|
+
// and friends from the INSTALLED app instead of the repo under test (plain
|
|
107
|
+
// node can't even read asar paths → ENOENT). Internal worker spawns that need
|
|
108
|
+
// the root re-set it explicitly and are unaffected: only shell spawn sites
|
|
109
|
+
// call this.
|
|
110
|
+
export function scrubRuntimeRootVars(env) {
|
|
111
|
+
if (!env || typeof env !== 'object') return env;
|
|
112
|
+
delete env.MIXDOG_ROOT;
|
|
113
|
+
return env;
|
|
114
|
+
}
|
|
@@ -18,7 +18,7 @@ import { join } from 'node:path';
|
|
|
18
18
|
import { homedir } from 'node:os';
|
|
19
19
|
import { randomUUID } from 'node:crypto';
|
|
20
20
|
import { getPluginData } from '../config.mjs';
|
|
21
|
-
import { scrubLoaderVars, scrubProviderSecrets } from './env-scrub.mjs';
|
|
21
|
+
import { scrubLoaderVars, scrubProviderSecrets, scrubRuntimeRootVars } from './env-scrub.mjs';
|
|
22
22
|
|
|
23
23
|
const SNAPSHOT_TIMEOUT_MS = 10_000;
|
|
24
24
|
|
|
@@ -191,6 +191,8 @@ function _runSnapshot(shellPath, snapshotPath, configFileExists) {
|
|
|
191
191
|
// provider/cloud tokens before exposing the env to that script.
|
|
192
192
|
// Shared with bash-session and shell-jobs via env-scrub.mjs.
|
|
193
193
|
scrubProviderSecrets(e);
|
|
194
|
+
// Runtime-root isolation — see env-scrub.mjs scrubRuntimeRootVars.
|
|
195
|
+
scrubRuntimeRootVars(e);
|
|
194
196
|
return e;
|
|
195
197
|
})(),
|
|
196
198
|
windowsHide: true,
|
|
@@ -306,3 +306,17 @@ export async function embedTexts(texts) {
|
|
|
306
306
|
return cached ? [...cached] : []
|
|
307
307
|
})
|
|
308
308
|
}
|
|
309
|
+
|
|
310
|
+
export async function shutdownEmbeddingProvider() {
|
|
311
|
+
const activeWorker = worker
|
|
312
|
+
worker = null
|
|
313
|
+
_warmupPromise = null
|
|
314
|
+
_modelReady = false
|
|
315
|
+
cachedDims = null
|
|
316
|
+
queryEmbeddingCache.clear()
|
|
317
|
+
if (!activeWorker) return
|
|
318
|
+
const shutdownError = new Error('embedding provider shut down')
|
|
319
|
+
for (const [, pending] of _pending) pending.reject(shutdownError)
|
|
320
|
+
_pending.clear()
|
|
321
|
+
await activeWorker.terminate()
|
|
322
|
+
}
|