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,507 @@
1
+ // launch-core.mjs — importable config-UI launch core.
2
+ //
3
+ // Extracted from launch.mjs so the same alive-probe → stale-server
4
+ // detection → spawn → requestOpen sequence can run BOTH from the CLI
5
+ // shim (launch.mjs, which keeps the Windows console-hide / self-respawn
6
+ // mitigations for direct `bun launch.mjs` invocation) AND from inside the
7
+ // resident MCP server (the `open_config` tool). When the MCP server — a
8
+ // long-lived background process — spawns setup-server with windowsHide:true,
9
+ // no console window is created at all, so the conhost flash the CLI path
10
+ // fights with FFI/wscript shims simply never happens.
11
+ //
12
+ // Import-safe: no top-level side effects, no env mutation at module load.
13
+ // All paths/ports are module constants; per-call state (plugin root/data)
14
+ // is derived inside launchConfigUi().
15
+
16
+ import { spawn, execSync } from 'child_process';
17
+ import { openSync, closeSync, readFileSync, writeSync, writeFileSync, appendFileSync } from 'fs';
18
+ import { join, dirname } from 'path';
19
+ import { fileURLToPath } from 'url';
20
+ import http from 'http';
21
+ import net from 'net';
22
+ import { tmpdir } from 'os';
23
+ import { resolvePluginData } from '../src/shared/plugin-paths.mjs';
24
+
25
+ const __dirname = dirname(fileURLToPath(import.meta.url));
26
+ const server = join(__dirname, 'setup-server.mjs');
27
+ // Reuse the launcher's own runtime (bun.exe from the CLI shebang, or the
28
+ // bun that runs the MCP server). Spawning setup-server by absolute path lets
29
+ // us drop shell:true on Windows; the cmd.exe wrapper was swallowing the
30
+ // child's stderr and turning real bun startup errors into an empty log.
31
+ const CHILD_INTERPRETER = process.execPath;
32
+ export const PORT = 3458;
33
+ export const CONFIG_UI_URL = `http://localhost:${PORT}`;
34
+
35
+ function ping(timeoutMs = 1500) {
36
+ return new Promise(resolve => {
37
+ // setup-server listens on 127.0.0.1 only. agent:false disables Node's
38
+ // default keepAlive socket pool; Connection: close prevents any keepalive
39
+ // negotiation that could leave half-open sockets between probes.
40
+ const req = http.get({
41
+ host: '127.0.0.1', port: PORT, path: '/',
42
+ agent: false, headers: { Connection: 'close' },
43
+ }, res => { res.resume(); resolve(true); });
44
+ req.on('error', () => resolve(false));
45
+ req.setTimeout(timeoutMs, () => { req.destroy(); resolve(false); });
46
+ });
47
+ }
48
+
49
+ // TCP-level liveness probe used as a fallback when HTTP ping times out but
50
+ // the server is actually accepting connections. Distinguishes "server is
51
+ // listening but its handler is momentarily slow" from "server is dead" so
52
+ // the launcher doesn't false-trigger spawnServerWithLog and crash on the
53
+ // port collision.
54
+ function tcpOpen(timeoutMs = 300) {
55
+ return new Promise(resolve => {
56
+ const s = net.connect({ host: '127.0.0.1', port: PORT });
57
+ s.setTimeout(timeoutMs);
58
+ s.once('connect', () => { s.destroy(); resolve(true); });
59
+ s.once('timeout', () => { s.destroy(); resolve(false); });
60
+ s.once('error', () => resolve(false));
61
+ });
62
+ }
63
+
64
+ // GET /api/plugin-path. Returns the absolute plugin root the running
65
+ // setup-server identifies as, or null on any failure. Used to detect a
66
+ // stale or wrong-plugin server still bound to PORT before we hand it the
67
+ // /open call — otherwise a different version's setup window can pop up.
68
+ function fetchPluginPath(timeoutMs = 1500) {
69
+ return new Promise(resolve => {
70
+ const req = http.get(`http://127.0.0.1:${PORT}/api/plugin-path`, res => {
71
+ let body = '';
72
+ res.setEncoding('utf8');
73
+ res.on('data', chunk => { body += chunk; });
74
+ res.on('end', () => {
75
+ try {
76
+ const json = JSON.parse(body);
77
+ resolve(typeof json?.path === 'string' ? json.path : null);
78
+ } catch {
79
+ resolve(null);
80
+ }
81
+ });
82
+ });
83
+ req.on('error', () => resolve(null));
84
+ req.setTimeout(timeoutMs, () => { req.destroy(); resolve(null); });
85
+ });
86
+ }
87
+
88
+ // Watchdog disabled — returning 0 keeps setup-server alive after the MCP
89
+ // host exits (until the user closes the chrome window). The previous
90
+ // execSync('powershell …') ancestor walk was a major conhost-flash source
91
+ // on every /mixdog:config invocation; removing it eliminated that flash.
92
+ function findAncestorPid() {
93
+ return 0;
94
+ }
95
+
96
+ function requestOpen() {
97
+ return new Promise(resolve => {
98
+ const req = http.get(`http://127.0.0.1:${PORT}/open`, res => {
99
+ let body = '';
100
+ res.setEncoding('utf8');
101
+ res.on('data', chunk => { body += chunk; });
102
+ res.on('end', () => {
103
+ try {
104
+ const json = JSON.parse(body);
105
+ resolve(json.ok === true);
106
+ } catch {
107
+ resolve(res.statusCode >= 200 && res.statusCode < 300);
108
+ }
109
+ });
110
+ });
111
+ req.on('error', () => resolve(false));
112
+ // /open now holds the in-process open mutex until the spawned browser is
113
+ // discoverable or its wscript launcher's bounded focus loop (~6s) exits,
114
+ // so the response can legitimately take several seconds. Allow headroom
115
+ // above that bound so a slow-but-successful open is not reported as a
116
+ // failure (which would throw a spurious LaunchError).
117
+ req.setTimeout(12000, () => { req.destroy(); resolve(false); });
118
+ });
119
+ }
120
+
121
+ function sleep(ms) {
122
+ return new Promise(resolve => setTimeout(resolve, ms));
123
+ }
124
+
125
+ async function waitForServer(timeoutMs = 4000, shouldStop = () => false) {
126
+ const deadline = Date.now() + timeoutMs;
127
+ let ready = false;
128
+ while (Date.now() < deadline) {
129
+ ready = await ping(500);
130
+ if (ready) break;
131
+ if (shouldStop()) break;
132
+ await sleep(250);
133
+ }
134
+ return ready && !shouldStop();
135
+ }
136
+
137
+ function openLaunchLog() {
138
+ const path = join(tmpdir(), `mixdog-setup-launch-${process.pid}.log`);
139
+ const fd = openSync(path, 'a');
140
+ writeSync(fd, [
141
+ '',
142
+ `[${new Date().toISOString()}] setup-server launch`,
143
+ `launcherPid=${process.pid}`,
144
+ `execPath=${process.execPath}`,
145
+ `interpreter=${CHILD_INTERPRETER}`,
146
+ `server=${server}`,
147
+ `cwd=${dirname(__dirname)}`,
148
+ '--- child stdout/stderr ---',
149
+ ].join('\n') + '\n');
150
+ return { path, fd };
151
+ }
152
+
153
+ function writeLog(fd, message) {
154
+ try {
155
+ if (typeof fd === 'number') writeSync(fd, message);
156
+ } catch {}
157
+ }
158
+
159
+ function closeLog(fd) {
160
+ try {
161
+ if (typeof fd === 'number') closeSync(fd);
162
+ } catch {}
163
+ }
164
+
165
+ function readLog(path) {
166
+ try { return readFileSync(path, 'utf8').trim(); } catch { return ''; }
167
+ }
168
+
169
+ // In the importable core a launch failure THROWS (with the same diagnostic
170
+ // text the CLI used to print to stderr). The CLI shim catches and converts
171
+ // it back to a stderr write + process.exit(1); the MCP tool surfaces it as
172
+ // an error result.
173
+ class LaunchError extends Error {}
174
+
175
+ function cmdSetLine(name, value) {
176
+ return `set "${name}=${String(value ?? '').replace(/"/g, '')}"`;
177
+ }
178
+
179
+ function cmdQuote(value) {
180
+ return `"${String(value).replace(/"/g, '""')}"`;
181
+ }
182
+
183
+ function spawnWscriptHidden(vbsPath) {
184
+ return new Promise(resolve => {
185
+ let child;
186
+ try {
187
+ child = spawn('wscript.exe', ['//B', '//NoLogo', vbsPath], {
188
+ stdio: 'ignore',
189
+ windowsHide: true,
190
+ });
191
+ } catch (error) {
192
+ resolve({ ok: false, error: error?.message || String(error) });
193
+ return;
194
+ }
195
+ child.once('error', error => resolve({ ok: false, error: error?.message || String(error) }));
196
+ child.once('exit', code => resolve({ ok: code === 0, code }));
197
+ });
198
+ }
199
+
200
+ // Spawn setup-server with a launch log, wait for readiness, and throw a
201
+ // LaunchError on failure. Shared by the cold-start (!alive) path and the
202
+ // stale-server takeover path so both use one implementation.
203
+ async function spawnServerWithLog(pluginRoot, pluginData, { openOnStart = true } = {}) {
204
+ const launchLog = openLaunchLog();
205
+ let spawnError = null;
206
+ let childExit = null;
207
+ let child;
208
+
209
+ if (process.platform === 'win32') {
210
+ closeLog(launchLog.fd);
211
+ const stamp = `${process.pid}-${Date.now()}`;
212
+ const cmdPath = join(tmpdir(), `mixdog-setup-server-${stamp}.cmd`);
213
+ const vbsPath = join(tmpdir(), `mixdog-setup-server-${stamp}.vbs`);
214
+ const batchLines = [
215
+ '@echo off',
216
+ 'setlocal',
217
+ cmdSetLine('CLAUDE_PLUGIN_ROOT', pluginRoot),
218
+ cmdSetLine('CLAUDE_PLUGIN_DATA', pluginData),
219
+ cmdSetLine('MIXDOG_SETUP_OPEN_ON_START', openOnStart ? '1' : '0'),
220
+ cmdSetLine('MIXDOG_SETUP_PARENT_PID', String(findAncestorPid() || '')),
221
+ process.env.USERPROFILE ? cmdSetLine('USERPROFILE', process.env.USERPROFILE) : '',
222
+ process.env.APPDATA ? cmdSetLine('APPDATA', process.env.APPDATA) : '',
223
+ process.env.LOCALAPPDATA ? cmdSetLine('LOCALAPPDATA', process.env.LOCALAPPDATA) : '',
224
+ process.env.PATH ? cmdSetLine('PATH', process.env.PATH) : '',
225
+ `cd /d ${cmdQuote(pluginRoot)}`,
226
+ `echo [${new Date().toISOString()}] hidden setup-server cmd start>>${cmdQuote(launchLog.path)}`,
227
+ `${cmdQuote(CHILD_INTERPRETER)} ${cmdQuote(server)}>>${cmdQuote(launchLog.path)} 2>&1`,
228
+ 'set "mixdogExit=%ERRORLEVEL%"',
229
+ 'del "%~f0" >NUL 2>&1',
230
+ 'exit /b %mixdogExit%',
231
+ ].filter(Boolean);
232
+ writeFileSync(cmdPath, batchLines.join('\r\n'), 'utf8');
233
+
234
+ const escVbs = s => String(s).replace(/"/g, '""');
235
+ const cmdLine = `cmd.exe /d /c ""${cmdPath}""`;
236
+ const vbsLines = [
237
+ 'Option Explicit',
238
+ 'Const HIDDEN_WINDOW = 0',
239
+ 'Dim Wmi, Startup, cmdLine, cwd, pid, rc',
240
+ 'Set Wmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2")',
241
+ 'Set Startup = Wmi.Get("Win32_ProcessStartup").SpawnInstance_',
242
+ 'Startup.ShowWindow = HIDDEN_WINDOW',
243
+ `cmdLine = "${escVbs(cmdLine)}"`,
244
+ `cwd = "${escVbs(pluginRoot)}"`,
245
+ 'rc = Wmi.Get("Win32_Process").Create(cmdLine, cwd, Startup, pid)',
246
+ 'If rc <> 0 Then WScript.Quit rc',
247
+ ];
248
+ writeFileSync(vbsPath, vbsLines.join('\r\n'), 'utf8');
249
+
250
+ const launched = await spawnWscriptHidden(vbsPath);
251
+ if (!launched.ok) {
252
+ throw new LaunchError(
253
+ `Failed to spawn hidden setup-server for ${CONFIG_UI_URL}\n` +
254
+ `Launch log: ${launchLog.path}\n` +
255
+ `wscript result: ${launched.error || `exit ${launched.code}`}\n`,
256
+ );
257
+ }
258
+ if (!await waitForServer(15000)) {
259
+ const captured = readLog(launchLog.path);
260
+ throw new LaunchError(
261
+ `setup-server did not become ready at ${CONFIG_UI_URL}/ within 15000ms (hidden WMI launch).\n` +
262
+ `Launch log: ${launchLog.path}\n` +
263
+ (captured ? `--- setup-server launch log ---\n${captured}\n--- end setup-server launch log ---\n` : 'No setup-server output was captured.\n'),
264
+ );
265
+ }
266
+ return;
267
+ }
268
+
269
+ try {
270
+ child = spawn(CHILD_INTERPRETER, [server], {
271
+ detached: true,
272
+ // Console-less parents (hidden wscript/PowerShell chain) would
273
+ // otherwise hand the detached console child a fresh visible window.
274
+ windowsHide: true,
275
+ // Use a real file descriptor, not parent pipes/stdio. This keeps the
276
+ // detached child independent after unref() while preserving first-start
277
+ // stdout/stderr for diagnostics. shell:false (default) on every
278
+ // platform — cmd.exe wrapping was eating the child's stderr.
279
+ stdio: ['ignore', launchLog.fd, launchLog.fd],
280
+ cwd: pluginRoot,
281
+ env: {
282
+ ...process.env,
283
+ CLAUDE_PLUGIN_ROOT: pluginRoot,
284
+ CLAUDE_PLUGIN_DATA: pluginData,
285
+ MIXDOG_SETUP_OPEN_ON_START: openOnStart ? '1' : '0',
286
+ MIXDOG_SETUP_PARENT_PID: String(findAncestorPid() || ''),
287
+ },
288
+ windowsHide: true,
289
+ });
290
+ } catch (error) {
291
+ closeLog(launchLog.fd);
292
+ throw new LaunchError(
293
+ `Failed to spawn setup-server for ${CONFIG_UI_URL}\n` +
294
+ `Launch log: ${launchLog.path}\n` +
295
+ `${error?.stack || error?.message || String(error)}\n`,
296
+ );
297
+ }
298
+
299
+ child.once('error', error => {
300
+ spawnError = error;
301
+ writeLog(launchLog.fd, `\n[${new Date().toISOString()}] child error: ${error?.stack || error?.message || String(error)}\n`);
302
+ });
303
+ child.once('exit', (code, signal) => {
304
+ childExit = { code, signal };
305
+ writeLog(launchLog.fd, `\n[${new Date().toISOString()}] child exit: code=${code} signal=${signal}\n`);
306
+ });
307
+ child.unref();
308
+
309
+ if (!await waitForServer(15000, () => Boolean(spawnError || childExit))) {
310
+ closeLog(launchLog.fd);
311
+ const captured = readLog(launchLog.path);
312
+ const status = spawnError
313
+ ? `spawn error: ${spawnError?.stack || spawnError?.message || String(spawnError)}`
314
+ : childExit
315
+ ? `child exit: code=${childExit.code} signal=${childExit.signal}`
316
+ : 'child did not report an error or exit before readiness timeout';
317
+ throw new LaunchError(
318
+ `setup-server did not become ready at ${CONFIG_UI_URL}/ within 15000ms (${status}).\n` +
319
+ `Launch log: ${launchLog.path}\n` +
320
+ (captured ? `--- setup-server launch log ---\n${captured}\n--- end setup-server launch log ---\n` : 'No setup-server output was captured.\n'),
321
+ );
322
+ }
323
+
324
+ closeLog(launchLog.fd);
325
+ }
326
+
327
+ // Probe for an already-running setup-server. A live server answers the HTTP
328
+ // ping immediately; if its event loop is momentarily blocked inside a sync
329
+ // spawn (chrome /open path), the kernel still completes the TCP accept, so the
330
+ // tcpOpen fallback detects it. A short window therefore suffices.
331
+ function probeOnce() {
332
+ return new Promise(resolve => {
333
+ const req = http.get({
334
+ host: '127.0.0.1', port: PORT, path: '/',
335
+ agent: false, headers: { Connection: 'close' },
336
+ }, res => { res.resume(); resolve('up'); });
337
+ req.on('error', e => resolve(e?.code === 'ECONNREFUSED' ? 'refused' : 'timeout'));
338
+ req.setTimeout(800, () => { req.destroy(); resolve('timeout'); });
339
+ });
340
+ }
341
+
342
+ async function aliveProbe() {
343
+ const deadline = Date.now() + 1500;
344
+ while (Date.now() < deadline) {
345
+ const state = await probeOnce();
346
+ if (state === 'up') return true;
347
+ if (state === 'refused') return false;
348
+ await sleep(150);
349
+ }
350
+ return false;
351
+ }
352
+
353
+ // Resolve / start the resident setup-server and open (or re-open) the config
354
+ // UI window, returning the UI URL on success. Throws LaunchError with a
355
+ // diagnostic message on failure.
356
+ //
357
+ // Options:
358
+ // pluginRoot — override the plugin root (defaults to CLAUDE_PLUGIN_ROOT or
359
+ // the directory above setup/).
360
+ // trace — optional (msg:string)=>void hook for diagnostics; defaults
361
+ // to a tmp trace-log writer (matches the CLI's behaviour).
362
+ // prewarm — when true, only ensure the server is alive (spawned
363
+ // window-free, MIXDOG_SETUP_OPEN_ON_START unset). Never opens
364
+ // or re-focuses a window; a no-op when the server already runs.
365
+ export async function launchConfigUi(options = {}) {
366
+ const pluginRoot = options.pluginRoot || process.env.CLAUDE_PLUGIN_ROOT || dirname(__dirname);
367
+ const prewarm = options.prewarm === true;
368
+ // Slash-command shells expand ${CLAUDE_PLUGIN_ROOT} into argv but do not
369
+ // export it, so the spawned setup-server inherits a stripped env. Re-derive
370
+ // both ROOT and DATA so plugin-paths and the stricter channels-lib guard
371
+ // (which checks CLAUDE_PLUGIN_DATA directly) both succeed in the child.
372
+ process.env.CLAUDE_PLUGIN_ROOT = pluginRoot;
373
+ const pluginData = process.env.CLAUDE_PLUGIN_DATA || resolvePluginData();
374
+ process.env.CLAUDE_PLUGIN_DATA = pluginData;
375
+
376
+ const traceFn = typeof options.trace === 'function'
377
+ ? options.trace
378
+ : (() => {
379
+ const traceLogPath = join(tmpdir(), `mixdog-launch-trace-${process.pid}.log`);
380
+ return msg => {
381
+ try { appendFileSync(traceLogPath, `[${new Date().toISOString()}] ${msg}\n`); } catch {}
382
+ };
383
+ })();
384
+ const __trace = msg => { try { traceFn(msg); } catch {} };
385
+ __trace(`launchConfigUi entry parentPid=${process.ppid} pluginRoot=${pluginRoot}`);
386
+
387
+ const alive = await aliveProbe();
388
+ __trace(`aliveProbe result alive=${alive}`);
389
+
390
+ // Pre-warm mode: ensure the server is booted window-free, then return.
391
+ // Already alive → cheap no-op (no requestOpen, no window). Not alive →
392
+ // spawn with openOnStart:false so setup-server boots without opening a
393
+ // browser window. No stale-server takeover: pre-warm is best-effort and
394
+ // must never kill another install's server.
395
+ if (prewarm) {
396
+ if (alive) {
397
+ __trace(`prewarm: server already alive → no-op`);
398
+ return CONFIG_UI_URL;
399
+ }
400
+ __trace(`prewarm: !alive → spawnServerWithLog (window-free)`);
401
+ await spawnServerWithLog(pluginRoot, pluginData, { openOnStart: false });
402
+ __trace(`prewarm: spawnServerWithLog completed`);
403
+ return CONFIG_UI_URL;
404
+ }
405
+
406
+ if (!alive) {
407
+ // Boot window-free; the unconditional requestOpen below owns opening the
408
+ // window. Spawning with openOnStart:1 here would double-open and races a
409
+ // concurrent every-session prewarm that may bind the port first (in which
410
+ // case our own openOnStart child never gets to open anything).
411
+ __trace(`branch: !alive → spawnServerWithLog (window-free)`);
412
+ await spawnServerWithLog(pluginRoot, pluginData, { openOnStart: false });
413
+ __trace(`spawnServerWithLog completed`);
414
+ }
415
+
416
+ // Confirm the live server belongs to THIS plugin install before reusing it
417
+ // (a stale or different-version setup-server would otherwise own the
418
+ // window). After a !alive spawn above this simply re-confirms our own server.
419
+ const remoteRoot = await fetchPluginPath();
420
+ __trace(`fetchPluginPath returned remoteRoot=${remoteRoot}`);
421
+ if (remoteRoot) {
422
+ // Normalize: trailing slash strip + backslash → forward slash + lowercase
423
+ // so `C:/x` and `C:\x` compare equal (lowercase only on Windows).
424
+ const normalize = p => { const s = p.replace(/[\\/]+$/, '').replace(/\\/g, '/'); return process.platform === 'win32' ? s.toLowerCase() : s; };
425
+ const expected = normalize(pluginRoot);
426
+ const actual = normalize(remoteRoot);
427
+ __trace(`compare expected=${expected} actual=${actual} mismatch=${expected !== actual}`);
428
+ if (expected !== actual) {
429
+ // Invariant: single live plugin install owns PORT. The occupying server
430
+ // belongs to a different install — resolve its PID, terminate it, poll
431
+ // until the port is free, then take ownership via spawnServerWithLog().
432
+ let stalePid = null;
433
+ let pidResolveError = null;
434
+ let killError = null;
435
+ try {
436
+ if (process.platform === 'win32') {
437
+ const out = execSync(
438
+ `powershell -NoProfile -Command "(Get-NetTCPConnection -LocalPort ${PORT} -ErrorAction SilentlyContinue | Select-Object -First 1).OwningProcess"`,
439
+ { encoding: 'utf8', timeout: 3000, windowsHide: true },
440
+ ).trim();
441
+ const n = parseInt(out, 10);
442
+ if (Number.isFinite(n) && n > 0) stalePid = n;
443
+ } else {
444
+ const out = execSync(`lsof -ti :${PORT}`, { encoding: 'utf8', timeout: 3000 }).trim();
445
+ const n = parseInt(out.split('\n')[0], 10);
446
+ if (Number.isFinite(n) && n > 0) stalePid = n;
447
+ }
448
+ } catch (e) { pidResolveError = e; }
449
+ if (stalePid !== null) {
450
+ try {
451
+ if (process.platform === 'win32') {
452
+ // /T kills the whole process tree — the setup-server spawns child
453
+ // browser/server descendants that survive a PID-only Stop-Process,
454
+ // leaking processes and holding the port.
455
+ execSync(`taskkill /T /F /PID ${stalePid}`, { timeout: 3000, windowsHide: true });
456
+ } else {
457
+ // setup-server is launched detached (its own process-group leader),
458
+ // so the port-owning PID equals the group id. Negative PID signals
459
+ // the whole group, reaping descendants a bare kill would orphan.
460
+ try { execSync(`kill -KILL -${stalePid}`, { timeout: 3000 }); }
461
+ catch { execSync(`kill -KILL ${stalePid}`, { timeout: 3000 }); }
462
+ }
463
+ } catch (e) { killError = e; }
464
+ }
465
+ // Poll until port is free (250ms ticks, 3000ms deadline).
466
+ const killDeadline = Date.now() + 3000;
467
+ let portFree = false;
468
+ while (Date.now() < killDeadline) {
469
+ await sleep(250);
470
+ portFree = !(await ping(300));
471
+ if (portFree) break;
472
+ }
473
+ if (!portFree) {
474
+ const errorDetails = [
475
+ pidResolveError && ` PID resolution failure: ${pidResolveError?.message || String(pidResolveError)}`,
476
+ killError && ` kill failure: ${killError?.message || String(killError)}`,
477
+ ].filter(Boolean).join('\n');
478
+ throw new LaunchError(
479
+ `Port ${PORT} is in use by a different mixdog plugin instance and could not be reclaimed (kill of PID ${stalePid ?? 'unknown'} failed or timed out).\n` +
480
+ ` expected plugin root: ${pluginRoot}\n` +
481
+ ` actual plugin root: ${remoteRoot}\n` +
482
+ (errorDetails ? errorDetails + '\n' : '') +
483
+ `Stop the other setup-server (or change PORT) and retry.\n`,
484
+ );
485
+ }
486
+ __trace(`takeover: killed stalePid=${stalePid}, port freed, spawning new server`);
487
+ await spawnServerWithLog(pluginRoot, pluginData, { openOnStart: false });
488
+ __trace(`takeover: spawnServerWithLog completed`);
489
+ }
490
+ }
491
+ // Invariant: a non-prewarm launch ALWAYS opens the window through the
492
+ // idempotent /open endpoint once the server is ready — regardless of which
493
+ // process (this launcher, a concurrent every-session prewarm, or a
494
+ // pre-existing server) actually booted it. Relying on the spawned child's
495
+ // MIXDOG_SETUP_OPEN_ON_START would silently no-op whenever a window-free
496
+ // prewarm server bound the port first.
497
+ __trace(`branch: requestOpen`);
498
+ const opened = await requestOpen();
499
+ __trace(`requestOpen returned ${opened}`);
500
+ if (!opened) {
501
+ throw new LaunchError(`Failed to open config UI window for ${CONFIG_UI_URL}\n`);
502
+ }
503
+ __trace(`launchConfigUi success`);
504
+ return CONFIG_UI_URL;
505
+ }
506
+
507
+ export { LaunchError, tcpOpen };
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env bun
2
+ // launch.mjs — CLI shim for the config-UI launcher.
3
+ //
4
+ // The launch SEQUENCE (alive probe → stale-server detection → spawn →
5
+ // requestOpen) now lives in launch-core.mjs so the resident MCP server can
6
+ // import and run it window-free via the `open_config` tool. This file keeps
7
+ // only the bits that exist BECAUSE the CLI is invoked through a
8
+ // console-subsystem bun.exe: the FFI console-hide and the hidden-wscript
9
+ // self-respawn. Both collapse the conhost flash that the slash command's
10
+ // shell-out produces. Neither is needed (or wanted) inside the MCP server,
11
+ // which spawns setup-server with windowsHide:true and never flashes at all.
12
+ import { spawn } from 'child_process';
13
+ import { writeFileSync } from 'fs';
14
+ import { join, dirname } from 'path';
15
+ import { fileURLToPath } from 'url';
16
+ import { tmpdir } from 'os';
17
+
18
+ // Hide our own console window as the very first thing on Windows. Claude Code
19
+ // runs this script via a console-subsystem bun.exe, which flashes an empty
20
+ // terminal for ~0.5s before the self-respawn below hands off to a hidden
21
+ // child. Hiding the console here collapses that flash to bun's startup time.
22
+ // SAFETY GUARD: only hide when this console belongs to us ALONE.
23
+ // GetConsoleProcessList returning a single PID equal to ours means a dedicated
24
+ // console (the flash case). A shared console — Claude Code's own terminal with
25
+ // our stdio piped into it — reports the host process too; hiding that would
26
+ // make the user's working terminal vanish, so we must never touch it. stdout
27
+ // capture is unaffected (Claude Code reads the pipe, not the window).
28
+ if (process.platform === 'win32') {
29
+ try {
30
+ const { dlopen, FFIType, ptr } = await import('bun:ffi');
31
+ const k32 = dlopen('kernel32.dll', {
32
+ GetConsoleWindow: { args: [], returns: FFIType.ptr },
33
+ GetConsoleProcessList: { args: [FFIType.ptr, FFIType.u32], returns: FFIType.u32 },
34
+ });
35
+ const u32 = dlopen('user32.dll', {
36
+ ShowWindow: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
37
+ });
38
+ const hwnd = k32.symbols.GetConsoleWindow();
39
+ if (hwnd) {
40
+ const list = new Uint32Array(16);
41
+ const count = k32.symbols.GetConsoleProcessList(ptr(list), list.length);
42
+ if (count === 1 && list[0] === process.pid) {
43
+ u32.symbols.ShowWindow(hwnd, 0); // SW_HIDE
44
+ }
45
+ }
46
+ } catch { /* FFI unavailable/failed — leave the console alone; at worst the brief flash remains */ }
47
+ }
48
+
49
+ // Windows self-respawn under hidden wscript. Without this, the bun console
50
+ // flashes briefly between the slash command's shell-out and the detached
51
+ // setup-server spawn (two flashes total). Self-respawn shrinks it to one
52
+ // flash: parent exits immediately, hidden child re-runs this script with
53
+ // stdio detached so setup-server boot work happens entirely off-screen. The
54
+ // --hidden argv flag prevents recursion.
55
+ if (process.platform === 'win32' && !process.argv.includes('--hidden')) {
56
+ // wscript.exe is a GUI host (no console of its own), so spawning it does
57
+ // not flash a conhost window. Use Shell.Application.ShellExecute (NOT
58
+ // WScript.Shell.Run — Wsh.Run silently no-ops when launching bun.exe with
59
+ // an embedded-quote command line; ShellExecute keeps app and args as
60
+ // separate parameters) with show=0 (SW_HIDE) for the hidden child bun.
61
+ const __scriptPath = fileURLToPath(import.meta.url);
62
+ const __bunExe = process.execPath;
63
+ const __escVbs = s => String(s).replace(/"/g, '""');
64
+ // Forward --prewarm into the hidden child so the re-run boots the server
65
+ // window-free instead of opening the config window.
66
+ const __prewarmArg = process.argv.includes('--prewarm') ? ' --prewarm' : '';
67
+ const __argsStr = `"${__scriptPath}" --hidden${__prewarmArg}`;
68
+ const __vbsLines = [
69
+ 'Set Shell = CreateObject("Shell.Application")',
70
+ `Shell.ShellExecute "${__escVbs(__bunExe)}", "${__escVbs(__argsStr)}", "", "open", 0`,
71
+ ];
72
+ const __vbsPath = join(tmpdir(), `mixdog-launch-respawn-${process.pid}-${Date.now()}.vbs`);
73
+ writeFileSync(__vbsPath, __vbsLines.join('\r\n'), 'utf8');
74
+ const __child = spawn('wscript.exe', ['//B', '//NoLogo', __vbsPath], {
75
+ stdio: 'ignore', windowsHide: true, detached: true,
76
+ });
77
+ __child.unref();
78
+ // Brief wait so wscript finishes parsing the .vbs and dispatches
79
+ // ShellExecute before this parent exits. Without it, parent termination
80
+ // races wscript startup and the hidden child never spawns.
81
+ await new Promise(resolve => setTimeout(resolve, 500));
82
+ process.stdout.write(`Config UI: http://localhost:3458\n`, () => process.exit(0));
83
+ }
84
+
85
+ // Delegate the actual launch sequence to the shared core. Reuse the trace-log
86
+ // behaviour the inlined implementation had by letting launchConfigUi default
87
+ // its own tmp trace writer. A LaunchError is converted back to the legacy
88
+ // stderr-write + exit(1) contract the slash command relied on.
89
+ const { launchConfigUi, LaunchError, CONFIG_UI_URL } = await import(
90
+ join(dirname(fileURLToPath(import.meta.url)), 'launch-core.mjs'),
91
+ );
92
+
93
+ try {
94
+ const prewarm = process.argv.includes('--prewarm');
95
+ const url = await launchConfigUi({ prewarm });
96
+ process.stdout.write(`Config UI: ${url}\n`, () => process.exit(0));
97
+ } catch (err) {
98
+ const message = err instanceof LaunchError ? err.message : `${err?.stack || err?.message || String(err)}\n`;
99
+ await new Promise(resolve => process.stderr.write(message, resolve));
100
+ process.exit(1);
101
+ }