mixdog 0.7.1

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.
Files changed (404) hide show
  1. package/.claude-plugin/marketplace.json +31 -0
  2. package/.claude-plugin/plugin.json +20 -0
  3. package/.gitattributes +34 -0
  4. package/.mcp.json +14 -0
  5. package/ARCHITECTURE.md +77 -0
  6. package/CHANGELOG.md +7 -0
  7. package/CONTRIBUTING.md +45 -0
  8. package/DATA-FLOW.md +79 -0
  9. package/LICENSE +21 -0
  10. package/README.md +389 -0
  11. package/SECURITY.md +138 -0
  12. package/UNINSTALL.md +112 -0
  13. package/agents/maintenance.md +5 -0
  14. package/agents/memory-classification.md +30 -0
  15. package/agents/scheduler-task.md +18 -0
  16. package/agents/webhook-handler.md +27 -0
  17. package/agents/worker.md +24 -0
  18. package/bin/bridge +133 -0
  19. package/bin/statusline-launcher.mjs +78 -0
  20. package/bin/statusline-lib.mjs +550 -0
  21. package/bin/statusline.mjs +607 -0
  22. package/bun.lock +802 -0
  23. package/commands/config.md +16 -0
  24. package/commands/doctor.md +13 -0
  25. package/commands/setup.md +17 -0
  26. package/defaults/cycle3-review-prompt.md +90 -0
  27. package/defaults/hidden-roles.json +65 -0
  28. package/defaults/memory-chunk-prompt.md +63 -0
  29. package/defaults/memory-promote-prompt.md +135 -0
  30. package/defaults/mixdog-config.template.json +27 -0
  31. package/defaults/user-workflow.json +8 -0
  32. package/defaults/user-workflow.md +12 -0
  33. package/hooks/hooks.json +73 -0
  34. package/hooks/lib/active-instance.cjs +77 -0
  35. package/hooks/lib/permission-evaluator.cjs +411 -0
  36. package/hooks/lib/permission-route.cjs +63 -0
  37. package/hooks/lib/permission-rules.cjs +170 -0
  38. package/hooks/lib/settings-loader.cjs +116 -0
  39. package/hooks/post-tool-use.cjs +84 -0
  40. package/hooks/pre-mcp-sandbox.cjs +158 -0
  41. package/hooks/pre-tool-subagent.cjs +253 -0
  42. package/hooks/session-start.cjs +1372 -0
  43. package/hooks/turn-timer.cjs +82 -0
  44. package/lib/claude-md-writer.cjs +386 -0
  45. package/lib/config-cjs.cjs +61 -0
  46. package/lib/hook-pipe-path.cjs +10 -0
  47. package/lib/keychain-cjs.cjs +263 -0
  48. package/lib/plugin-paths.cjs +61 -0
  49. package/lib/rules-builder.cjs +241 -0
  50. package/lib/text-utils.cjs +61 -0
  51. package/native/README.md +117 -0
  52. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  53. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  54. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  55. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  56. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  57. package/package.json +107 -0
  58. package/prompts/code-review.txt +16 -0
  59. package/prompts/security-audit.txt +17 -0
  60. package/rules/bridge/00-common.md +39 -0
  61. package/rules/bridge/20-skip-protocol.md +18 -0
  62. package/rules/bridge/30-explorer.md +33 -0
  63. package/rules/bridge/40-cycle1-agent.md +52 -0
  64. package/rules/bridge/41-cycle2-agent.md +62 -0
  65. package/rules/bridge/42-cycle3-agent.md +44 -0
  66. package/rules/lead/00-tool-lead.md +61 -0
  67. package/rules/lead/01-general.md +23 -0
  68. package/rules/lead/02-channels.md +49 -0
  69. package/rules/lead/03-team.md +27 -0
  70. package/rules/lead/04-workflow.md +20 -0
  71. package/rules/shared/00-language.md +14 -0
  72. package/rules/shared/01-tool.md +138 -0
  73. package/scripts/bootstrap.mjs +184 -0
  74. package/scripts/bridge-unify-smoke.mjs +308 -0
  75. package/scripts/build-runtime-linux.sh +348 -0
  76. package/scripts/build-runtime-macos.sh +217 -0
  77. package/scripts/build-runtime-windows.ps1 +242 -0
  78. package/scripts/builtin-utils-smoke.mjs +392 -0
  79. package/scripts/check-json.mjs +45 -0
  80. package/scripts/check-syntax-changed.mjs +102 -0
  81. package/scripts/check-syntax.mjs +58 -0
  82. package/scripts/code-graph-batch.test.mjs +33 -0
  83. package/scripts/config-preserve-smoke.mjs +180 -0
  84. package/scripts/doctor.mjs +484 -0
  85. package/scripts/edit-normalize-fuzz.mjs +130 -0
  86. package/scripts/edit-normalize-smoke.mjs +401 -0
  87. package/scripts/edit-operation-smoke.mjs +369 -0
  88. package/scripts/edit2-smoke.mjs +63 -0
  89. package/scripts/fuzzy-e2e.mjs +28 -0
  90. package/scripts/fuzzy-smoke.mjs +26 -0
  91. package/scripts/generate-runtime-manifest.mjs +166 -0
  92. package/scripts/guard-smoke.mjs +66 -0
  93. package/scripts/hidden-role-schema-smoke.mjs +162 -0
  94. package/scripts/hook-routing-smoke.mjs +29 -0
  95. package/scripts/inject-input.ps1 +204 -0
  96. package/scripts/io-complex-smoke.mjs +667 -0
  97. package/scripts/io-explore-bench.mjs +424 -0
  98. package/scripts/io-guardrails-smoke.mjs +205 -0
  99. package/scripts/io-mini-bench-baseline.json +11 -0
  100. package/scripts/io-mini-bench.mjs +216 -0
  101. package/scripts/io-route-harness.mjs +933 -0
  102. package/scripts/io-telemetry-report.mjs +691 -0
  103. package/scripts/mutation-bench.mjs +564 -0
  104. package/scripts/mutation-io-smoke.mjs +1081 -0
  105. package/scripts/native-patch-bridge-smoke.mjs +288 -0
  106. package/scripts/native-patch-smoke.mjs +304 -0
  107. package/scripts/patch-interior-context-smoke.mjs +49 -0
  108. package/scripts/patch-newline-utf8-smoke.mjs +157 -0
  109. package/scripts/perf-hook-smoke.mjs +71 -0
  110. package/scripts/permission-eval-smoke.mjs +426 -0
  111. package/scripts/prep-patch.mjs +53 -0
  112. package/scripts/prep-shim.mjs +96 -0
  113. package/scripts/provider-cache-smoke.mjs +687 -0
  114. package/scripts/report-runtime-health.mjs +132 -0
  115. package/scripts/run-mcp.mjs +1547 -0
  116. package/scripts/salvage-v4a-shatter.test.mjs +58 -0
  117. package/scripts/scoped-cache-io-smoke.mjs +103 -0
  118. package/scripts/shell-policy-round3-smoke.mjs +46 -0
  119. package/scripts/smoke-runtime-negative.ps1 +100 -0
  120. package/scripts/smoke-runtime-negative.sh +95 -0
  121. package/scripts/stall-policy-smoke.mjs +50 -0
  122. package/scripts/start-memory-worker.mjs +23 -0
  123. package/scripts/statusline-launcher-smoke.mjs +82 -0
  124. package/scripts/stress-atomic-write.mjs +1028 -0
  125. package/scripts/test-config-rmw-restore.mjs +122 -0
  126. package/scripts/test-fault-inject.mjs +164 -0
  127. package/scripts/test-large-file.mjs +174 -0
  128. package/scripts/tool-edge-smoke.mjs +209 -0
  129. package/scripts/uninstall.mjs +201 -0
  130. package/scripts/webhook-selfheal-smoke.mjs +29 -0
  131. package/scripts/write-overwrite-guard-smoke.mjs +56 -0
  132. package/server-main.mjs +3055 -0
  133. package/server.mjs +468 -0
  134. package/setup/config-merge.mjs +254 -0
  135. package/setup/install.mjs +120 -0
  136. package/setup/launch-core.mjs +507 -0
  137. package/setup/launch.mjs +101 -0
  138. package/setup/setup-server.mjs +3206 -0
  139. package/setup/setup.html +3693 -0
  140. package/skills/retro-skill-proposer/SKILL.md +92 -0
  141. package/skills/schedule-add/SKILL.md +77 -0
  142. package/skills/setup/SKILL.md +346 -0
  143. package/skills/webhook-add/SKILL.md +81 -0
  144. package/src/agent/bridge-stall-watchdog.mjs +337 -0
  145. package/src/agent/index.mjs +2138 -0
  146. package/src/agent/orchestrator/activity-bus.mjs +38 -0
  147. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
  148. package/src/agent/orchestrator/bridge-retry.mjs +220 -0
  149. package/src/agent/orchestrator/bridge-trace.mjs +583 -0
  150. package/src/agent/orchestrator/cache-mtime.mjs +58 -0
  151. package/src/agent/orchestrator/config.mjs +358 -0
  152. package/src/agent/orchestrator/context/collect.mjs +651 -0
  153. package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
  154. package/src/agent/orchestrator/drain-registry.mjs +50 -0
  155. package/src/agent/orchestrator/explore-validator.mjs +8 -0
  156. package/src/agent/orchestrator/internal-roles.mjs +118 -0
  157. package/src/agent/orchestrator/internal-tools.mjs +88 -0
  158. package/src/agent/orchestrator/jobs.mjs +116 -0
  159. package/src/agent/orchestrator/mcp/client.mjs +364 -0
  160. package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
  161. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
  162. package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
  163. package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
  164. package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
  165. package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
  166. package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
  167. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
  168. package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
  169. package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
  170. package/src/agent/orchestrator/providers/registry.mjs +192 -0
  171. package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
  172. package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
  173. package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
  174. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  175. package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
  176. package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
  177. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
  178. package/src/agent/orchestrator/session/cache/util.mjs +49 -0
  179. package/src/agent/orchestrator/session/loop.mjs +1478 -0
  180. package/src/agent/orchestrator/session/manager.mjs +1975 -0
  181. package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
  182. package/src/agent/orchestrator/session/result-classification.mjs +65 -0
  183. package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
  184. package/src/agent/orchestrator/session/store.mjs +624 -0
  185. package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
  186. package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  187. package/src/agent/orchestrator/session/trim.mjs +491 -0
  188. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
  189. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
  190. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
  191. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
  192. package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
  193. package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
  194. package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
  195. package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
  196. package/src/agent/orchestrator/stall-policy.mjs +195 -0
  197. package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
  198. package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
  199. package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
  200. package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
  201. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
  202. package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
  203. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
  204. package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
  205. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
  206. package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
  207. package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
  208. package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
  209. package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
  210. package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
  211. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
  212. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
  213. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
  214. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
  215. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
  216. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
  217. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
  218. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
  219. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
  220. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
  221. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
  222. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
  223. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
  224. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
  225. package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
  226. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
  227. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
  228. package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
  229. package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
  230. package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
  231. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
  232. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
  233. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
  234. package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
  235. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
  236. package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
  237. package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
  238. package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
  239. package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
  240. package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
  241. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  242. package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
  243. package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
  244. package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
  245. package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
  246. package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
  247. package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
  248. package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
  249. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
  250. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
  251. package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
  252. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  253. package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
  254. package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
  255. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
  256. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
  257. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
  258. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
  259. package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
  260. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
  261. package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
  262. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
  263. package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
  264. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
  265. package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
  266. package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
  267. package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
  268. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
  269. package/src/agent/orchestrator/tools/builtin.mjs +500 -0
  270. package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
  271. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  272. package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
  273. package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
  274. package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  275. package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
  276. package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
  277. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
  278. package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
  279. package/src/agent/orchestrator/tools/host-input.mjs +204 -0
  280. package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
  281. package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
  282. package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
  283. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
  284. package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
  285. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
  286. package/src/agent/orchestrator/tools/patch.mjs +2754 -0
  287. package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
  288. package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
  289. package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
  290. package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
  291. package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
  292. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
  293. package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
  294. package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
  295. package/src/agent/orchestrator/workflow-store.mjs +93 -0
  296. package/src/agent/tool-defs.mjs +103 -0
  297. package/src/channels/backends/discord.mjs +784 -0
  298. package/src/channels/data/voice-runtime-manifest.json +138 -0
  299. package/src/channels/index.mjs +3229 -0
  300. package/src/channels/lib/cli-worker-host.mjs +12 -0
  301. package/src/channels/lib/config-lock.mjs +13 -0
  302. package/src/channels/lib/config.mjs +292 -0
  303. package/src/channels/lib/drop-trace.mjs +71 -0
  304. package/src/channels/lib/event-pipeline.mjs +81 -0
  305. package/src/channels/lib/event-queue.mjs +345 -0
  306. package/src/channels/lib/executor.mjs +168 -0
  307. package/src/channels/lib/format.mjs +188 -0
  308. package/src/channels/lib/holidays.mjs +138 -0
  309. package/src/channels/lib/hook-pipe-server.mjs +802 -0
  310. package/src/channels/lib/interaction-workflows.mjs +184 -0
  311. package/src/channels/lib/memory-client.mjs +149 -0
  312. package/src/channels/lib/output-forwarder.mjs +765 -0
  313. package/src/channels/lib/runtime-paths.mjs +479 -0
  314. package/src/channels/lib/scheduler.mjs +723 -0
  315. package/src/channels/lib/session-control.mjs +36 -0
  316. package/src/channels/lib/session-discovery.mjs +103 -0
  317. package/src/channels/lib/settings.mjs +11 -0
  318. package/src/channels/lib/state-file.mjs +68 -0
  319. package/src/channels/lib/status-snapshot.mjs +219 -0
  320. package/src/channels/lib/tool-format.mjs +140 -0
  321. package/src/channels/lib/transcript-discovery.mjs +195 -0
  322. package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
  323. package/src/channels/lib/webhook.mjs +1179 -0
  324. package/src/channels/lib/whisper-server.mjs +477 -0
  325. package/src/channels/tool-defs.mjs +170 -0
  326. package/src/daemon/host.mjs +118 -0
  327. package/src/daemon/mcp-transport.mjs +47 -0
  328. package/src/daemon/session.mjs +100 -0
  329. package/src/daemon/thin-client.mjs +71 -0
  330. package/src/daemon/transport.mjs +163 -0
  331. package/src/memory/data/runtime-manifest.json +40 -0
  332. package/src/memory/index.mjs +3305 -0
  333. package/src/memory/lib/agent-ipc.mjs +93 -0
  334. package/src/memory/lib/bridge-trace-queries.mjs +120 -0
  335. package/src/memory/lib/core-memory-store.mjs +330 -0
  336. package/src/memory/lib/embedding-provider.mjs +269 -0
  337. package/src/memory/lib/embedding-worker.mjs +323 -0
  338. package/src/memory/lib/llm-worker-host.mjs +17 -0
  339. package/src/memory/lib/memory-cycle.mjs +11 -0
  340. package/src/memory/lib/memory-cycle1.mjs +641 -0
  341. package/src/memory/lib/memory-cycle2.mjs +1284 -0
  342. package/src/memory/lib/memory-cycle3.mjs +540 -0
  343. package/src/memory/lib/memory-embed.mjs +299 -0
  344. package/src/memory/lib/memory-extraction.mjs +5 -0
  345. package/src/memory/lib/memory-maintenance-store.mjs +32 -0
  346. package/src/memory/lib/memory-ops-policy.mjs +190 -0
  347. package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
  348. package/src/memory/lib/memory-recall-read-query.mjs +7 -0
  349. package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
  350. package/src/memory/lib/memory-recall-store.mjs +621 -0
  351. package/src/memory/lib/memory-retrievers.mjs +112 -0
  352. package/src/memory/lib/memory-score.mjs +71 -0
  353. package/src/memory/lib/memory-text-utils.mjs +58 -0
  354. package/src/memory/lib/memory.mjs +412 -0
  355. package/src/memory/lib/model-profile.mjs +85 -0
  356. package/src/memory/lib/pg/adapter.mjs +308 -0
  357. package/src/memory/lib/pg/process.mjs +360 -0
  358. package/src/memory/lib/pg/supervisor.mjs +396 -0
  359. package/src/memory/lib/project-id-resolver.mjs +86 -0
  360. package/src/memory/lib/runtime-fetcher.mjs +442 -0
  361. package/src/memory/lib/trace-store.mjs +728 -0
  362. package/src/memory/tool-defs.mjs +79 -0
  363. package/src/search/index.mjs +1173 -0
  364. package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
  365. package/src/search/lib/backends/exa.mjs +50 -0
  366. package/src/search/lib/backends/firecrawl.mjs +61 -0
  367. package/src/search/lib/backends/gemini-api.mjs +83 -0
  368. package/src/search/lib/backends/grok-oauth.mjs +86 -0
  369. package/src/search/lib/backends/index.mjs +150 -0
  370. package/src/search/lib/backends/openai-api.mjs +144 -0
  371. package/src/search/lib/backends/openai-oauth.mjs +98 -0
  372. package/src/search/lib/backends/openai-web-search.mjs +76 -0
  373. package/src/search/lib/backends/tavily.mjs +55 -0
  374. package/src/search/lib/backends/xai-api.mjs +113 -0
  375. package/src/search/lib/cache.mjs +131 -0
  376. package/src/search/lib/config.mjs +192 -0
  377. package/src/search/lib/formatter.mjs +115 -0
  378. package/src/search/lib/provider-usage.mjs +67 -0
  379. package/src/search/lib/providers.mjs +47 -0
  380. package/src/search/lib/search-intent.mjs +109 -0
  381. package/src/search/lib/setup-handler.mjs +261 -0
  382. package/src/search/lib/state.mjs +201 -0
  383. package/src/search/lib/web-tools.mjs +1207 -0
  384. package/src/search/tool-defs.mjs +83 -0
  385. package/src/setup/defender-exclusion.mjs +183 -0
  386. package/src/shared/abort-controller.mjs +15 -0
  387. package/src/shared/atomic-file.mjs +420 -0
  388. package/src/shared/config.mjs +350 -0
  389. package/src/shared/daemon-recycle.mjs +108 -0
  390. package/src/shared/disable-claude-builtins.mjs +88 -0
  391. package/src/shared/err-text.mjs +12 -0
  392. package/src/shared/llm/cost.mjs +66 -0
  393. package/src/shared/llm/http-agent.mjs +123 -0
  394. package/src/shared/llm/index.mjs +41 -0
  395. package/src/shared/llm/pid-cleanup.mjs +27 -0
  396. package/src/shared/llm/usage-log.mjs +47 -0
  397. package/src/shared/plugin-paths.mjs +58 -0
  398. package/src/shared/schedules-store.mjs +70 -0
  399. package/src/shared/seed.mjs +119 -0
  400. package/src/shared/user-cwd.mjs +213 -0
  401. package/src/shared/user-data-guard.mjs +238 -0
  402. package/src/status/aggregator.mjs +584 -0
  403. package/src/status/server.mjs +413 -0
  404. package/tools.json +1653 -0
@@ -0,0 +1,442 @@
1
+ // runtime-fetcher.mjs — P1 runtime fetcher for mixdog 0.4.0
2
+ // runtime-fetcher.mjs
3
+ // REQUIRES: `tar` (bsdtar-compatible) on PATH.
4
+ // On Windows, bsdtar ships with Windows 10 1803+ as %SystemRoot%\System32\tar.exe.
5
+ // If tar is missing, ensureRuntime() throws with an actionable error message.
6
+ //
7
+ // Downloads and verifies a prebuilt native PG runtime from the mixdog GitHub
8
+ // release manifest.
9
+ //
10
+ // Layout: <dataDir>/runtime/runtime-{ver}/ + <dataDir>/runtime/active-version
11
+ // Atomic swap: write active-version.tmp then rename → active-version.
12
+ // GC: removes stale runtime-* dirs and staging-* dirs on every ensureRuntime call.
13
+ //
14
+ // Public API: ensureRuntime(dataDir) → { runtimeDir, pgBinDir, libDir, sharePath, version }
15
+
16
+ import { createHash } from 'crypto'
17
+ import {
18
+ chmodSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync,
19
+ readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync,
20
+ } from 'fs'
21
+ import { readFile } from 'fs/promises'
22
+ import { join, resolve } from 'path'
23
+ import { fileURLToPath } from 'url'
24
+ import { pipeline } from 'stream/promises'
25
+ import { spawnSync } from 'child_process'
26
+ import { renameWithRetrySync, withFileLockSync, writeFileAtomicSync, writeJsonAtomicSync } from '../../shared/atomic-file.mjs'
27
+
28
+ // Bundled fallback manifest shipped alongside the plugin. fileURLToPath required
29
+ // for cross-platform path resolution (URL.pathname returns /C:/... on Windows).
30
+ const BUNDLED_MANIFEST_PATH = fileURLToPath(new URL('../data/runtime-manifest.json', import.meta.url))
31
+
32
+ // GitHub raw URL fallback — used only when no cached or bundled manifest exists.
33
+ const MANIFEST_URL = 'https://raw.githubusercontent.com/trib-plugin/mixdog/main/src/memory/data/runtime-manifest.json'
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Platform key
37
+ // ---------------------------------------------------------------------------
38
+
39
+ function platformKey() {
40
+ const os = process.platform === 'win32' ? 'win32' : process.platform
41
+ return `${os}-${process.arch}`
42
+ }
43
+
44
+ // Fail-closed asset validation. A selected manifest asset is usable only if it
45
+ // is not explicitly marked unsupported AND carries a real downloadable payload:
46
+ // non-empty url, a well-formed 64-hex sha256, and a positive integer size.
47
+ // Placeholder / TBD entries fail every payload check and are rejected.
48
+ function isUsableAsset(asset) {
49
+ if (!asset || typeof asset !== 'object') return false
50
+ if (asset.unsupported === true) return false
51
+ if (typeof asset.url !== 'string' || asset.url.length === 0) return false
52
+ if (typeof asset.sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(asset.sha256)) return false
53
+ if (!Number.isInteger(asset.size) || asset.size <= 0) return false
54
+ return true
55
+ }
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Manifest resolution
59
+ // ---------------------------------------------------------------------------
60
+
61
+ async function loadManifest(dataDir) {
62
+ const runtimeManifestPath = join(dataDir, 'runtime', 'manifest.json')
63
+ if (existsSync(runtimeManifestPath)) {
64
+ try { return JSON.parse(readFileSync(runtimeManifestPath, 'utf8')) } catch {}
65
+ }
66
+ if (existsSync(BUNDLED_MANIFEST_PATH)) {
67
+ return JSON.parse(readFileSync(BUNDLED_MANIFEST_PATH, 'utf8'))
68
+ }
69
+ const res = await fetch(MANIFEST_URL, { signal: AbortSignal.timeout(30_000) })
70
+ if (!res.ok) throw new Error(`[runtime-fetcher] manifest fetch failed: ${res.status} ${res.statusText}`)
71
+ const manifest = await res.json()
72
+ mkdirSync(join(dataDir, 'runtime'), { recursive: true })
73
+ writeJsonAtomicSync(runtimeManifestPath, manifest, { lock: true, fsyncDir: true })
74
+ return manifest
75
+ }
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // SHA-256 verification
79
+ // ---------------------------------------------------------------------------
80
+
81
+ async function sha256File(filePath) {
82
+ const data = await readFile(filePath)
83
+ return createHash('sha256').update(data).digest('hex')
84
+ }
85
+
86
+ async function verifySha256(filePath, expected) {
87
+ const actual = await sha256File(filePath)
88
+ if (actual !== expected) {
89
+ throw new Error(`[runtime-fetcher] sha256 mismatch for ${filePath}: expected ${expected}, got ${actual}`)
90
+ }
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Active-runtime validation (pointer-file layout)
95
+ // ---------------------------------------------------------------------------
96
+
97
+ function activeVersionPath(runtimeDir) {
98
+ return join(runtimeDir, 'active-version')
99
+ }
100
+
101
+ function readActiveVersion(runtimeDir) {
102
+ try { return readFileSync(activeVersionPath(runtimeDir), 'utf8').trim() } catch { return null }
103
+ }
104
+
105
+ function runtimeVerDir(runtimeDir, ver) {
106
+ return join(runtimeDir, `runtime-${ver}`)
107
+ }
108
+
109
+ function runtimePaths(verDir) {
110
+ return {
111
+ pgBinDir: join(verDir, 'bin'),
112
+ libDir: join(verDir, 'lib'),
113
+ sharePath: join(verDir, 'share'),
114
+ }
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Download with retry
119
+ // ---------------------------------------------------------------------------
120
+
121
+ async function downloadWithRetry(url, destPath) {
122
+ // 4 total attempts: 1 initial + 3 retries; waits between attempts: 1s, 3s, 9s.
123
+ const delays = [1000, 3000, 9000]
124
+ let lastErr
125
+ for (let attempt = 0; attempt < 4; attempt++) {
126
+ try {
127
+ const res = await fetch(url, { signal: AbortSignal.timeout(180_000) })
128
+ if (res.status >= 400 && res.status < 500) {
129
+ // 4xx: terminal — do not retry.
130
+ throw new Error(`[runtime-fetcher] asset download HTTP ${res.status} (terminal) — ${url}`)
131
+ }
132
+ if (!res.ok) {
133
+ throw new Error(`[runtime-fetcher] asset download HTTP ${res.status} — ${url}`)
134
+ }
135
+ const out = createWriteStream(destPath)
136
+ await pipeline(res.body, out)
137
+ return // success
138
+ } catch (err) {
139
+ lastErr = err
140
+ // Terminal 4xx: do not retry.
141
+ if (err.message.includes('(terminal)')) throw err
142
+ if (attempt < 3) {
143
+ process.stderr.write(`[runtime-fetcher] download attempt ${attempt + 1} failed (${err.message}), retrying in ${delays[attempt]}ms…\n`)
144
+ await new Promise(r => setTimeout(r, delays[attempt]))
145
+ }
146
+ }
147
+ }
148
+ throw lastErr
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // Tar entry path validation + extraction
153
+ // ---------------------------------------------------------------------------
154
+
155
+ function extractTarGz(tarPath, destDir, stagingBase) {
156
+ mkdirSync(destDir, { recursive: true })
157
+
158
+ // List entries first and validate — reject any that escape staging.
159
+ const listResult = spawnSync('tar', ['-tzf', tarPath], { stdio: 'pipe', windowsHide: true })
160
+ if (listResult.status !== 0) {
161
+ throw new Error(`[runtime-fetcher] tar list failed: ${listResult.stderr?.toString() || 'unknown'}`)
162
+ }
163
+ const entries = (listResult.stdout?.toString() || '').split('\n').filter(Boolean)
164
+ const resolvedBase = resolve(stagingBase)
165
+ for (const entry of entries) {
166
+ // Reject absolute paths and traversal sequences.
167
+ if (entry.startsWith('/') || entry.includes('..')) {
168
+ throw new Error(`[runtime-fetcher] tar entry path validation failed (unsafe entry): ${entry}`)
169
+ }
170
+ const resolved = resolve(join(stagingBase, entry))
171
+ if (!resolved.startsWith(resolvedBase)) {
172
+ throw new Error(`[runtime-fetcher] tar entry escapes staging dir: ${entry}`)
173
+ }
174
+ }
175
+
176
+ const r = spawnSync('tar', ['-xzf', tarPath, '-C', destDir], { stdio: 'pipe', windowsHide: true })
177
+ if (r.status !== 0) {
178
+ throw new Error(`[runtime-fetcher] tar extraction failed: ${r.stderr?.toString() || 'unknown error'}`)
179
+ }
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Unix exec-bit normalization
184
+ // ---------------------------------------------------------------------------
185
+
186
+ function normalizeBinExecBit(verDir) {
187
+ if (process.platform === 'win32') return
188
+ const binDir = join(verDir, 'bin')
189
+ if (!existsSync(binDir)) return
190
+ try {
191
+ const entries = readdirSync(binDir)
192
+ for (const f of entries) {
193
+ try { chmodSync(join(binDir, f), 0o755) } catch {}
194
+ }
195
+ } catch {}
196
+ }
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // Staging cross-process lock — protects an in-progress download/extract/swap
200
+ // from being GC'd by a concurrently-booting sibling process.
201
+ //
202
+ // Each staging-* dir is guarded by a sibling O_EXCL lockfile `<staging>.lock`
203
+ // stamped with `<pid> <epoch-ms>`. The lockfile is held (fd open) for the whole
204
+ // download→extract→swap lifetime. GC only removes a staging dir whose guarding
205
+ // lock is NOT live (file missing, owner pid dead, or lock older than
206
+ // STAGING_LOCK_STALE_MS), so a concurrent boot mid-extract is never wiped.
207
+ // ---------------------------------------------------------------------------
208
+
209
+ // Generous staleness budget: a cold download + extract on a slow link can take
210
+ // minutes. A shorter window would let a sibling reclaim a still-live staging.
211
+ const STAGING_LOCK_STALE_MS = 600_000
212
+
213
+ function stagingLockPath(stagingDir) {
214
+ return `${stagingDir}.lock`
215
+ }
216
+
217
+ function _readStagingLockPid(lockPath) {
218
+ try {
219
+ const raw = readFileSync(lockPath, 'utf8')
220
+ const pid = Number.parseInt(String(raw).trim().split(/\s+/)[0], 10)
221
+ return Number.isFinite(pid) && pid > 0 ? pid : null
222
+ } catch {
223
+ return null
224
+ }
225
+ }
226
+
227
+ function _stagingLockPidAlive(pid) {
228
+ if (pid === null) return false
229
+ if (pid === process.pid) return true
230
+ try {
231
+ process.kill(pid, 0)
232
+ return true // signal delivered → owner exists
233
+ } catch (err) {
234
+ // ESRCH = no such process → owner is gone. Any other error (e.g. EPERM:
235
+ // exists but unsignalable) → treat as alive so we never steal from a live
236
+ // holder.
237
+ return err?.code !== 'ESRCH'
238
+ }
239
+ }
240
+
241
+ // A staging dir is protected iff its lockfile exists, names a live owner pid
242
+ // (or an unparseable-but-fresh stamp), and is not older than the stale budget.
243
+ function _stagingLockIsLive(lockPath) {
244
+ let st
245
+ try { st = statSync(lockPath) } catch { return false } // no lock → unprotected
246
+ const pid = _readStagingLockPid(lockPath)
247
+ if (pid !== null && !_stagingLockPidAlive(pid)) return false // owner dead
248
+ if (Date.now() - st.mtimeMs > STAGING_LOCK_STALE_MS) return false // abandoned
249
+ return true
250
+ }
251
+
252
+ function acquireStagingLock(stagingDir) {
253
+ const lockPath = stagingLockPath(stagingDir)
254
+ const fd = openSync(lockPath, 'wx') // O_EXCL: fails if a sibling already owns it
255
+ try { writeFileSync(fd, `${process.pid} ${Date.now()}\n`, 'utf8') } catch {}
256
+ return { fd, lockPath }
257
+ }
258
+
259
+ function releaseStagingLock(lock) {
260
+ if (!lock) return
261
+ try { closeSync(lock.fd) } catch {}
262
+ // Only unlink if we still own the stamp; a stolen+replaced lock must not be
263
+ // destroyed out from under its new owner.
264
+ try {
265
+ if (_readStagingLockPid(lock.lockPath) === process.pid) unlinkSync(lock.lockPath)
266
+ } catch {}
267
+ }
268
+
269
+ // ---------------------------------------------------------------------------
270
+ // GC — remove stale runtime-* and staging-* dirs
271
+ // ---------------------------------------------------------------------------
272
+
273
+ function gcRuntimeDir(runtimeDir, keepVer) {
274
+ try {
275
+ const entries = readdirSync(runtimeDir)
276
+ const entrySet = new Set(entries)
277
+ for (const name of entries) {
278
+ if (name.startsWith('staging-')) {
279
+ if (name.endsWith('.lock')) {
280
+ // Orphan lockfile (crash after rename-away, or a failed unlink in
281
+ // releaseStagingLock) whose matching staging dir is gone. Lockfiles
282
+ // beside a live dir are handled by the dir branch below.
283
+ const dirName = name.slice(0, -'.lock'.length)
284
+ if (entrySet.has(dirName)) continue
285
+ const lockPath = join(runtimeDir, name)
286
+ // Reap only a provably-dead/stale orphan; never one whose owner pid is
287
+ // live AND fresh (_stagingLockIsLive covers both conditions).
288
+ if (_stagingLockIsLive(lockPath)) continue
289
+ try { unlinkSync(lockPath) } catch {}
290
+ continue
291
+ }
292
+ const dir = join(runtimeDir, name)
293
+ const lockPath = stagingLockPath(dir)
294
+ // Never wipe a staging dir guarded by a LIVE lock — that is a sibling
295
+ // process mid-extract/swap.
296
+ if (_stagingLockIsLive(lockPath)) continue
297
+ try { rmSync(dir, { recursive: true, force: true }) } catch {}
298
+ // Reap the now-orphaned (dead/stale) lockfile too.
299
+ try { if (existsSync(lockPath)) unlinkSync(lockPath) } catch {}
300
+ } else if (name.startsWith('runtime-') && name !== `runtime-${keepVer}`) {
301
+ try { rmSync(join(runtimeDir, name), { recursive: true, force: true }) } catch {}
302
+ }
303
+ }
304
+ } catch {}
305
+ }
306
+
307
+ // ---------------------------------------------------------------------------
308
+ // ensureRuntime — public API
309
+ // ---------------------------------------------------------------------------
310
+
311
+ const runtimeCache = new Map()
312
+
313
+ // One-shot tar availability probe; result cached after first call.
314
+ let _tarProbed = false
315
+ function probeTar() {
316
+ if (_tarProbed) return
317
+ const r = spawnSync('tar', ['--version'], { stdio: 'pipe', windowsHide: true })
318
+ if (r.status !== 0 || r.error) {
319
+ throw new Error(
320
+ '[runtime-fetcher] `tar` not found or not executable. ' +
321
+ 'On Windows, bsdtar (tar.exe) is required (available since Windows 10 1803). ' +
322
+ 'Ensure tar.exe is on PATH (typically %SystemRoot%\\System32\\tar.exe).'
323
+ )
324
+ }
325
+ _tarProbed = true
326
+ }
327
+
328
+ export async function ensureRuntime(dataDir) {
329
+ const key = resolve(dataDir)
330
+ if (runtimeCache.has(key)) return runtimeCache.get(key)
331
+
332
+ const runtimeBaseDir = join(key, 'runtime')
333
+ mkdirSync(runtimeBaseDir, { recursive: true })
334
+
335
+ // Entry GC: always clean staging-* (partial extracts from prior crashes), but
336
+ // preserve runtime-${currentVer} so a sibling child's just-completed swap is
337
+ // not wiped. multi-process race protection.
338
+ gcRuntimeDir(runtimeBaseDir, readActiveVersion(runtimeBaseDir))
339
+
340
+ const manifest = await loadManifest(key)
341
+ const pkey = platformKey()
342
+ const asset = manifest.assets?.[pkey]
343
+ if (!asset) {
344
+ throw new Error(
345
+ `[runtime-fetcher] no asset for platform ${pkey} in manifest. ` +
346
+ `Available: ${Object.keys(manifest.assets || {}).join(', ')}`
347
+ )
348
+ }
349
+ if (!isUsableAsset(asset)) {
350
+ throw new Error(`unsupported platform/arch: no validated runtime asset for ${pkey}`)
351
+ }
352
+
353
+ const { url, sha256, size } = asset
354
+ const version = `pg${manifest.pg?.major}.${manifest.pg?.minor}+pgvector-${manifest.pgvector?.version}`
355
+
356
+ // Fast path: active-version pointer exists and matches expected sha256.
357
+ const currentVer = readActiveVersion(runtimeBaseDir)
358
+ if (currentVer === version) {
359
+ const verDir = runtimeVerDir(runtimeBaseDir, version)
360
+ if (existsSync(join(verDir, '.version-sha256'))) {
361
+ const stored = readFileSync(join(verDir, '.version-sha256'), 'utf8').trim()
362
+ if (stored === sha256) {
363
+ const result = { runtimeDir: verDir, ...runtimePaths(verDir), version }
364
+ runtimeCache.set(key, result)
365
+ return result
366
+ }
367
+ }
368
+ }
369
+
370
+ // tar is only required for the download/extract path. Probe here (not at
371
+ // function entry) so a machine without tar can still reuse an
372
+ // already-extracted, sha-matching cached runtime via the fast path above.
373
+ probeTar()
374
+
375
+ process.stderr.write(`[runtime-fetcher] downloading runtime ${version} for ${pkey} (~${size} bytes) …\n`)
376
+
377
+ // Unique staging suffix prevents two siblings from colliding on one dir and
378
+ // on its guarding lockfile (the O_EXCL acquire below would otherwise have one
379
+ // process fail to obtain the lock for its own staging dir).
380
+ const stagingTag = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`
381
+ const stagingDir = join(runtimeBaseDir, `staging-${stagingTag}`)
382
+ const tarPath = join(runtimeBaseDir, `runtime-${pkey}-${stagingTag}.tar.gz`)
383
+
384
+ const verDir = runtimeVerDir(runtimeBaseDir, version)
385
+ const avPath = activeVersionPath(runtimeBaseDir)
386
+
387
+ // Cross-process lock: hold the staging lockfile (O_EXCL) for the WHOLE
388
+ // download → extract → swap lifetime. GC in any concurrently-booting sibling
389
+ // treats a live lock as "in progress" and will not wipe this staging dir.
390
+ const stagingLock = acquireStagingLock(stagingDir)
391
+ try {
392
+ let downloadOk = false
393
+ try {
394
+ await downloadWithRetry(url, tarPath)
395
+ await verifySha256(tarPath, sha256)
396
+ downloadOk = true
397
+ extractTarGz(tarPath, stagingDir, stagingDir)
398
+ } finally {
399
+ try { rmSync(tarPath, { force: true }) } catch {}
400
+ }
401
+
402
+ if (!downloadOk) {
403
+ try { rmSync(stagingDir, { recursive: true, force: true }) } catch {}
404
+ throw new Error(`[runtime-fetcher] download or verify failed for ${version}`)
405
+ }
406
+
407
+ // Stamp sha256 inside staging dir.
408
+ writeFileSync(join(stagingDir, '.version-sha256'), sha256)
409
+ normalizeBinExecBit(stagingDir)
410
+
411
+ // Atomic swap:
412
+ // 1. Rename staging → runtime-{ver}
413
+ // 2. Write active-version.tmp → rename to active-version
414
+ // Stale dirs cleaned up by GC after.
415
+ try {
416
+ // If a prior runtime-{ver} dir exists (interrupted earlier run), remove it.
417
+ if (existsSync(verDir)) {
418
+ rmSync(verDir, { recursive: true, force: true })
419
+ }
420
+ renameWithRetrySync(stagingDir, verDir)
421
+ writeFileAtomicSync(avPath, version, { fsyncDir: true })
422
+ } catch (swapErr) {
423
+ process.stderr.write(`[runtime-fetcher] atomic swap failed: ${swapErr.message}\n`)
424
+ // Attempt to leave things in a recoverable state: if verDir landed but
425
+ // active-version didn't update, next call will re-download.
426
+ throw swapErr
427
+ }
428
+
429
+ // GC: remove stale runtime-* dirs (anything that isn't runtime-{version}).
430
+ // Still under the lock so the just-swapped staging lockfile reap below sees
431
+ // a consistent view.
432
+ gcRuntimeDir(runtimeBaseDir, version)
433
+ } finally {
434
+ releaseStagingLock(stagingLock)
435
+ }
436
+
437
+ process.stderr.write(`[runtime-fetcher] runtime ready at ${verDir}\n`)
438
+
439
+ const result = { runtimeDir: verDir, ...runtimePaths(verDir), version }
440
+ runtimeCache.set(key, result)
441
+ return result
442
+ }