mixdog 0.9.76 → 0.9.79
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixdog",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.79",
|
|
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 scripts/memory-pg-recovery-test.mjs && node scripts/verify-embedding-runtime.mjs",
|
|
78
|
+
"test:embedding-runtime:core": "node --test scripts/embedding-runtime-prune-test.mjs scripts/memory-pg-recovery-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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -77,18 +77,11 @@ function readPostmasterInfo(pgdataDir) {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function pgIsReady(runtimeDir, env, port) {
|
|
81
|
-
const probe = spawnSync(pgBin(runtimeDir, 'pg_isready'), ['-h', '127.0.0.1', '-p', String(port)], {
|
|
82
|
-
env, stdio: 'pipe', timeout: 3_000, windowsHide: true,
|
|
83
|
-
})
|
|
84
|
-
return probe.status === 0
|
|
85
|
-
}
|
|
86
|
-
|
|
87
80
|
async function awaitExistingPostmaster({ runtimeDir, pgdataDir, env, waitMs }) {
|
|
88
81
|
const deadline = Date.now() + Math.max(0, Number(waitMs) || 0)
|
|
89
82
|
let info = readPostmasterInfo(pgdataDir)
|
|
90
83
|
while (info.pid && info.port) {
|
|
91
|
-
if (
|
|
84
|
+
if (await healthcheckPg({ port: info.port })) return { state: 'ready', ...info }
|
|
92
85
|
if (!isPidAlive(info.pid)) return { state: 'dead', ...info }
|
|
93
86
|
if (Date.now() >= deadline) return { state: 'alive-not-ready', ...info }
|
|
94
87
|
await delay(250)
|
|
@@ -185,10 +178,6 @@ export async function startPg({
|
|
|
185
178
|
}) {
|
|
186
179
|
mkdirSync(pgdataDir, { recursive: true })
|
|
187
180
|
|
|
188
|
-
if (process.platform === 'darwin') {
|
|
189
|
-
try { writeFileSync(join(pgdataDir, '.metadata_never_index'), '') } catch {}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
181
|
// Idempotent v2 conf reconcile — runs before attach/init. Returns true if
|
|
193
182
|
// the block was just appended (so the attach path can trigger pg_ctl reload).
|
|
194
183
|
// Fresh-init path falls through to confAppend below which already includes v2.
|
|
@@ -282,6 +271,12 @@ export async function startPg({
|
|
|
282
271
|
)
|
|
283
272
|
}
|
|
284
273
|
}
|
|
274
|
+
// PostgreSQL requires a completely empty target on first init. Creating the
|
|
275
|
+
// Spotlight marker before initdb makes every fresh macOS cluster fail with
|
|
276
|
+
// "directory exists but is not empty"; add it only after initialization.
|
|
277
|
+
if (process.platform === 'darwin') {
|
|
278
|
+
try { writeFileSync(join(pgdataDir, '.metadata_never_index'), '') } catch {}
|
|
279
|
+
}
|
|
285
280
|
|
|
286
281
|
// Choose a free port (guards against stale postmaster from prior crash).
|
|
287
282
|
const port = await findFreePort(preferredPort)
|
|
@@ -350,7 +345,7 @@ export async function startPg({
|
|
|
350
345
|
const deadline = Date.now() + 30_000
|
|
351
346
|
// eslint-disable-next-line no-constant-condition
|
|
352
347
|
while (true) {
|
|
353
|
-
if (
|
|
348
|
+
if (await healthcheckPg({ port })) {
|
|
354
349
|
const pid = await confirmPid()
|
|
355
350
|
// pid confirmed with matching port → ready. Otherwise keep polling until
|
|
356
351
|
// the cap (postmaster.pid not yet written or port mismatch).
|