mixdog 0.7.17 → 0.8.0

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 (836) hide show
  1. package/README.md +37 -331
  2. package/package.json +67 -99
  3. package/scripts/boot-smoke.mjs +94 -0
  4. package/scripts/build-tui.mjs +52 -0
  5. package/scripts/compact-smoke.mjs +199 -0
  6. package/scripts/lead-workflow-smoke.mjs +598 -0
  7. package/scripts/live-worker-smoke.mjs +239 -0
  8. package/scripts/output-style-smoke.mjs +101 -0
  9. package/scripts/smoke-loop-report.mjs +221 -0
  10. package/scripts/smoke-loop.mjs +201 -0
  11. package/scripts/smoke.mjs +113 -0
  12. package/scripts/tool-failures.mjs +143 -0
  13. package/scripts/tool-smoke.mjs +456 -0
  14. package/src/agents/debugger/AGENT.md +3 -0
  15. package/src/agents/debugger/agent.json +6 -0
  16. package/src/agents/explore/AGENT.md +4 -0
  17. package/src/agents/explore/agent.json +6 -0
  18. package/src/agents/heavy-worker/AGENT.md +3 -0
  19. package/src/agents/heavy-worker/agent.json +6 -0
  20. package/src/agents/maintainer/AGENT.md +3 -0
  21. package/src/agents/maintainer/agent.json +6 -0
  22. package/src/agents/reviewer/AGENT.md +3 -0
  23. package/src/agents/reviewer/agent.json +6 -0
  24. package/src/agents/scheduler-task.md +3 -0
  25. package/src/agents/web-researcher/AGENT.md +3 -0
  26. package/src/agents/web-researcher/agent.json +6 -0
  27. package/src/agents/webhook-handler.md +3 -0
  28. package/src/agents/worker/AGENT.md +3 -0
  29. package/src/agents/worker/agent.json +6 -0
  30. package/src/app.mjs +90 -0
  31. package/src/cli.mjs +11 -0
  32. package/src/defaults/hidden-roles.json +72 -0
  33. package/src/defaults/mixdog-config.template.json +15 -0
  34. package/src/hooks/lib/permission-evaluator.cjs +488 -0
  35. package/src/hooks/lib/settings-loader.cjs +112 -0
  36. package/src/lib/keychain-cjs.cjs +332 -0
  37. package/src/lib/plugin-paths.cjs +28 -0
  38. package/src/lib/rules-builder.cjs +315 -0
  39. package/src/mixdog-session-runtime.mjs +3704 -0
  40. package/src/output-styles/default.md +38 -0
  41. package/src/output-styles/extreme-simple.md +17 -0
  42. package/src/output-styles/simple.md +17 -0
  43. package/src/repl.mjs +322 -0
  44. package/src/rules/bridge/00-common.md +5 -0
  45. package/src/rules/bridge/20-skip-protocol.md +11 -0
  46. package/src/rules/bridge/30-explorer.md +4 -0
  47. package/src/rules/bridge/40-cycle1-agent.md +28 -0
  48. package/src/rules/bridge/41-cycle2-agent.md +59 -0
  49. package/src/rules/lead/00-tool-lead.md +5 -0
  50. package/src/rules/lead/01-general.md +5 -0
  51. package/src/rules/lead/02-channels.md +3 -0
  52. package/src/rules/lead/04-workflow.md +12 -0
  53. package/src/rules/shared/00-language.md +3 -0
  54. package/src/rules/shared/01-tool.md +3 -0
  55. package/src/runtime/agent/orchestrator/bridge-trace.mjs +814 -0
  56. package/src/runtime/agent/orchestrator/cache-mtime.mjs +60 -0
  57. package/src/runtime/agent/orchestrator/config.mjs +446 -0
  58. package/src/runtime/agent/orchestrator/context/collect.mjs +796 -0
  59. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +417 -0
  60. package/src/runtime/agent/orchestrator/internal-roles.mjs +188 -0
  61. package/src/runtime/agent/orchestrator/internal-tools.mjs +88 -0
  62. package/src/runtime/agent/orchestrator/mcp/client.mjs +345 -0
  63. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2104 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +784 -0
  65. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +341 -0
  66. package/src/runtime/agent/orchestrator/providers/gemini.mjs +1679 -0
  67. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +959 -0
  68. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +213 -0
  69. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +38 -0
  70. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +471 -0
  71. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +615 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +808 -0
  73. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +1719 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +2587 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +1953 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +136 -0
  77. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +317 -0
  78. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +109 -0
  79. package/src/runtime/agent/orchestrator/providers/registry.mjs +247 -0
  80. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +332 -0
  81. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +11 -0
  82. package/src/runtime/agent/orchestrator/providers/trace-utils.mjs +50 -0
  83. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  84. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +318 -0
  85. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +367 -0
  86. package/src/runtime/agent/orchestrator/session/compact.mjs +882 -0
  87. package/src/runtime/agent/orchestrator/session/context-utils.mjs +233 -0
  88. package/src/runtime/agent/orchestrator/session/loop.mjs +2320 -0
  89. package/src/runtime/agent/orchestrator/session/manager.mjs +2960 -0
  90. package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
  91. package/src/runtime/agent/orchestrator/session/store.mjs +663 -0
  92. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  93. package/src/runtime/agent/orchestrator/smart-bridge/bridge-llm.mjs +339 -0
  94. package/src/runtime/agent/orchestrator/smart-bridge/cache-strategy.mjs +419 -0
  95. package/src/runtime/agent/orchestrator/stall-policy.mjs +227 -0
  96. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +235 -0
  97. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +723 -0
  98. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +389 -0
  99. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +637 -0
  100. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +165 -0
  101. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +104 -0
  102. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +194 -0
  103. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +596 -0
  104. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +110 -0
  105. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +153 -0
  106. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  107. package/src/runtime/agent/orchestrator/tools/builtin/read-open.mjs +189 -0
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +731 -0
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +168 -0
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +465 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +160 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +982 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1087 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +231 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +223 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin.mjs +478 -0
  118. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  119. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4102 -0
  120. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  121. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +154 -0
  122. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +26 -0
  123. package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +143 -0
  124. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +18 -0
  125. package/src/runtime/agent/orchestrator/tools/patch.mjs +2772 -0
  126. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +114 -0
  127. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +880 -0
  128. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +312 -0
  129. package/src/runtime/channels/backends/discord.mjs +781 -0
  130. package/src/runtime/channels/data/voice-runtime-manifest.json +138 -0
  131. package/src/runtime/channels/index.mjs +3309 -0
  132. package/src/runtime/channels/lib/config.mjs +285 -0
  133. package/src/runtime/channels/lib/drop-trace.mjs +71 -0
  134. package/src/runtime/channels/lib/event-pipeline.mjs +81 -0
  135. package/src/runtime/channels/lib/holidays.mjs +138 -0
  136. package/src/runtime/channels/lib/hook-pipe-server.mjs +671 -0
  137. package/src/runtime/channels/lib/output-forwarder.mjs +765 -0
  138. package/src/runtime/channels/lib/runtime-paths.mjs +497 -0
  139. package/src/runtime/channels/lib/scheduler.mjs +710 -0
  140. package/src/runtime/channels/lib/session-discovery.mjs +102 -0
  141. package/src/runtime/channels/lib/state-file.mjs +68 -0
  142. package/src/runtime/channels/lib/status-snapshot.mjs +224 -0
  143. package/src/runtime/channels/lib/tool-format.mjs +124 -0
  144. package/src/runtime/channels/lib/transcript-discovery.mjs +195 -0
  145. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +734 -0
  146. package/src/runtime/channels/lib/webhook.mjs +1288 -0
  147. package/src/runtime/channels/tool-defs.mjs +177 -0
  148. package/src/runtime/lib/keychain-cjs.cjs +289 -0
  149. package/src/runtime/memory/data/runtime-manifest.json +40 -0
  150. package/src/runtime/memory/index.mjs +3600 -0
  151. package/src/runtime/memory/lib/core-memory-store.mjs +336 -0
  152. package/src/runtime/memory/lib/embedding-provider.mjs +275 -0
  153. package/src/runtime/memory/lib/embedding-worker.mjs +331 -0
  154. package/src/runtime/memory/lib/memory-cycle-requests.mjs +276 -0
  155. package/src/runtime/memory/lib/memory-cycle1.mjs +783 -0
  156. package/src/runtime/memory/lib/memory-cycle2.mjs +1389 -0
  157. package/src/runtime/memory/lib/memory-cycle3.mjs +646 -0
  158. package/src/runtime/memory/lib/memory-embed.mjs +300 -0
  159. package/src/runtime/memory/lib/memory-ops-policy.mjs +149 -0
  160. package/src/runtime/memory/lib/memory-recall-store.mjs +644 -0
  161. package/src/runtime/memory/lib/memory.mjs +418 -0
  162. package/src/runtime/memory/lib/pg/adapter.mjs +314 -0
  163. package/src/runtime/memory/lib/pg/process.mjs +366 -0
  164. package/src/runtime/memory/lib/pg/supervisor.mjs +495 -0
  165. package/src/runtime/memory/lib/runtime-fetcher.mjs +464 -0
  166. package/src/runtime/memory/lib/trace-store.mjs +734 -0
  167. package/src/runtime/memory/tool-defs.mjs +79 -0
  168. package/src/runtime/search/index.mjs +925 -0
  169. package/src/runtime/search/lib/config.mjs +61 -0
  170. package/src/runtime/search/lib/web-tools.mjs +1278 -0
  171. package/src/runtime/search/tool-defs.mjs +64 -0
  172. package/src/runtime/shared/atomic-file.mjs +435 -0
  173. package/src/runtime/shared/background-tasks.mjs +376 -0
  174. package/src/runtime/shared/child-guardian.mjs +98 -0
  175. package/src/runtime/shared/config.mjs +393 -0
  176. package/src/runtime/shared/err-text.mjs +121 -0
  177. package/src/runtime/shared/launcher-control.mjs +259 -0
  178. package/src/runtime/shared/llm/cost.mjs +66 -0
  179. package/src/runtime/shared/llm/http-agent.mjs +129 -0
  180. package/src/runtime/shared/open-url.mjs +37 -0
  181. package/src/runtime/shared/plugin-paths.mjs +25 -0
  182. package/src/runtime/shared/process-shutdown.mjs +147 -0
  183. package/src/runtime/shared/schedules-store.mjs +70 -0
  184. package/src/runtime/shared/tool-execution-contract.mjs +104 -0
  185. package/src/runtime/shared/tool-surface.mjs +950 -0
  186. package/src/runtime/shared/user-cwd.mjs +221 -0
  187. package/src/runtime/shared/user-data-guard.mjs +232 -0
  188. package/src/runtime/shared/workspace-router.mjs +259 -0
  189. package/src/standalone/bridge-tool.mjs +1414 -0
  190. package/src/standalone/channel-admin.mjs +366 -0
  191. package/src/standalone/channel-worker-preload.cjs +3 -0
  192. package/src/standalone/channel-worker.mjs +353 -0
  193. package/src/standalone/explore-tool.mjs +233 -0
  194. package/src/standalone/hook-bus.mjs +246 -0
  195. package/src/standalone/plugin-admin.mjs +247 -0
  196. package/src/standalone/provider-admin.mjs +338 -0
  197. package/src/standalone/seeds.mjs +94 -0
  198. package/src/standalone/usage-dashboard.mjs +510 -0
  199. package/src/tui/App.jsx +5438 -0
  200. package/src/tui/components/AnsiText.jsx +199 -0
  201. package/src/tui/components/ContextPanel.jsx +217 -0
  202. package/src/tui/components/Markdown.jsx +205 -0
  203. package/src/tui/components/MarkdownTable.jsx +204 -0
  204. package/src/tui/components/Message.jsx +103 -0
  205. package/src/tui/components/Picker.jsx +317 -0
  206. package/src/tui/components/PromptInput.jsx +584 -0
  207. package/src/tui/components/QueuedCommands.jsx +47 -0
  208. package/src/tui/components/SlashCommandPalette.jsx +114 -0
  209. package/src/tui/components/Spinner.jsx +317 -0
  210. package/src/tui/components/StatusLine.jsx +87 -0
  211. package/src/tui/components/TextEntryPanel.jsx +323 -0
  212. package/src/tui/components/ToolExecution.jsx +772 -0
  213. package/src/tui/components/TurnDone.jsx +78 -0
  214. package/src/tui/components/UsagePanel.jsx +331 -0
  215. package/src/tui/dist/index.mjs +12359 -0
  216. package/src/tui/engine.mjs +2410 -0
  217. package/src/tui/figures.mjs +50 -0
  218. package/src/tui/hooks/useEngine.mjs +16 -0
  219. package/src/tui/index.jsx +254 -0
  220. package/src/tui/input-editing.mjs +242 -0
  221. package/src/tui/markdown/format-token.mjs +194 -0
  222. package/src/tui/paste-attachments.mjs +198 -0
  223. package/src/tui/runtime/shared/process-shutdown.mjs +1 -0
  224. package/src/tui/spinner-verbs.mjs +45 -0
  225. package/src/tui/theme.mjs +67 -0
  226. package/src/tui/time-format.mjs +53 -0
  227. package/src/ui/ansi.mjs +115 -0
  228. package/src/ui/markdown.mjs +195 -0
  229. package/src/ui/statusline.mjs +730 -0
  230. package/src/ui/tool-card.mjs +101 -0
  231. package/src/vendor/statusline/bin/statusline-lib.mjs +805 -0
  232. package/src/vendor/statusline/bin/statusline-route.mjs +596 -0
  233. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +285 -0
  234. package/src/vendor/statusline/src/gateway/claude-current.mjs +320 -0
  235. package/src/vendor/statusline/src/gateway/route-meta.mjs +753 -0
  236. package/src/vendor/statusline/src/gateway/session-routes.mjs +244 -0
  237. package/src/workflows/default/WORKFLOW.md +7 -0
  238. package/src/workflows/default/workflow.json +14 -0
  239. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  240. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  241. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  242. package/vendor/ink/build/colorize.d.ts +3 -0
  243. package/vendor/ink/build/colorize.js +48 -0
  244. package/vendor/ink/build/colorize.js.map +1 -0
  245. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  246. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  247. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  248. package/vendor/ink/build/components/AnimationContext.d.ts +9 -0
  249. package/vendor/ink/build/components/AnimationContext.js +13 -0
  250. package/vendor/ink/build/components/AnimationContext.js.map +1 -0
  251. package/vendor/ink/build/components/App.d.ts +24 -0
  252. package/vendor/ink/build/components/App.js +554 -0
  253. package/vendor/ink/build/components/App.js.map +1 -0
  254. package/vendor/ink/build/components/AppContext.d.ts +80 -0
  255. package/vendor/ink/build/components/AppContext.js +25 -0
  256. package/vendor/ink/build/components/AppContext.js.map +1 -0
  257. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  258. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  259. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  260. package/vendor/ink/build/components/Box.d.ts +130 -0
  261. package/vendor/ink/build/components/Box.js +34 -0
  262. package/vendor/ink/build/components/Box.js.map +1 -0
  263. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  264. package/vendor/ink/build/components/CursorContext.js +8 -0
  265. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  266. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  267. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  268. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  269. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  270. package/vendor/ink/build/components/ErrorOverview.js +90 -0
  271. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  272. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  273. package/vendor/ink/build/components/FocusContext.js +17 -0
  274. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  275. package/vendor/ink/build/components/Newline.d.ts +13 -0
  276. package/vendor/ink/build/components/Newline.js +8 -0
  277. package/vendor/ink/build/components/Newline.js.map +1 -0
  278. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  279. package/vendor/ink/build/components/Spacer.js +11 -0
  280. package/vendor/ink/build/components/Spacer.js.map +1 -0
  281. package/vendor/ink/build/components/Static.d.ts +24 -0
  282. package/vendor/ink/build/components/Static.js +28 -0
  283. package/vendor/ink/build/components/Static.js.map +1 -0
  284. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  285. package/vendor/ink/build/components/StderrContext.js +13 -0
  286. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  287. package/vendor/ink/build/components/StdinContext.d.ts +28 -0
  288. package/vendor/ink/build/components/StdinContext.js +20 -0
  289. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  290. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  291. package/vendor/ink/build/components/StdoutContext.js +13 -0
  292. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  293. package/vendor/ink/build/components/Text.d.ts +55 -0
  294. package/vendor/ink/build/components/Text.js +50 -0
  295. package/vendor/ink/build/components/Text.js.map +1 -0
  296. package/vendor/ink/build/components/Transform.d.ts +16 -0
  297. package/vendor/ink/build/components/Transform.js +15 -0
  298. package/vendor/ink/build/components/Transform.js.map +1 -0
  299. package/vendor/ink/build/cursor-helpers.d.ts +39 -0
  300. package/vendor/ink/build/cursor-helpers.js +62 -0
  301. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  302. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  303. package/vendor/ink/build/devtools-window-polyfill.js +68 -0
  304. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  305. package/vendor/ink/build/devtools.d.ts +1 -0
  306. package/vendor/ink/build/devtools.js +36 -0
  307. package/vendor/ink/build/devtools.js.map +1 -0
  308. package/vendor/ink/build/dom.d.ts +62 -0
  309. package/vendor/ink/build/dom.js +143 -0
  310. package/vendor/ink/build/dom.js.map +1 -0
  311. package/vendor/ink/build/get-max-width.d.ts +3 -0
  312. package/vendor/ink/build/get-max-width.js +10 -0
  313. package/vendor/ink/build/get-max-width.js.map +1 -0
  314. package/vendor/ink/build/hooks/use-animation.d.ts +49 -0
  315. package/vendor/ink/build/hooks/use-animation.js +87 -0
  316. package/vendor/ink/build/hooks/use-animation.js.map +1 -0
  317. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  318. package/vendor/ink/build/hooks/use-app.js +8 -0
  319. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  320. package/vendor/ink/build/hooks/use-box-metrics.d.ts +59 -0
  321. package/vendor/ink/build/hooks/use-box-metrics.js +81 -0
  322. package/vendor/ink/build/hooks/use-box-metrics.js.map +1 -0
  323. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  324. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  325. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  326. package/vendor/ink/build/hooks/use-focus-manager.d.ts +43 -0
  327. package/vendor/ink/build/hooks/use-focus-manager.js +18 -0
  328. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  329. package/vendor/ink/build/hooks/use-focus.d.ts +30 -0
  330. package/vendor/ink/build/hooks/use-focus.js +43 -0
  331. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  332. package/vendor/ink/build/hooks/use-input.d.ts +132 -0
  333. package/vendor/ink/build/hooks/use-input.js +126 -0
  334. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  335. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +6 -0
  336. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +12 -0
  337. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  338. package/vendor/ink/build/hooks/use-paste.d.ts +35 -0
  339. package/vendor/ink/build/hooks/use-paste.js +62 -0
  340. package/vendor/ink/build/hooks/use-paste.js.map +1 -0
  341. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  342. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  343. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  344. package/vendor/ink/build/hooks/use-stdin.d.ts +7 -0
  345. package/vendor/ink/build/hooks/use-stdin.js +9 -0
  346. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  347. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  348. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  349. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  350. package/vendor/ink/build/hooks/use-window-size.d.ts +18 -0
  351. package/vendor/ink/build/hooks/use-window-size.js +22 -0
  352. package/vendor/ink/build/hooks/use-window-size.js.map +1 -0
  353. package/vendor/ink/build/index.d.ts +42 -0
  354. package/vendor/ink/build/index.js +24 -0
  355. package/vendor/ink/build/index.js.map +1 -0
  356. package/vendor/ink/build/ink.d.ts +146 -0
  357. package/vendor/ink/build/ink.js +1022 -0
  358. package/vendor/ink/build/ink.js.map +1 -0
  359. package/vendor/ink/build/input-parser.d.ts +10 -0
  360. package/vendor/ink/build/input-parser.js +194 -0
  361. package/vendor/ink/build/input-parser.js.map +1 -0
  362. package/vendor/ink/build/instances.d.ts +3 -0
  363. package/vendor/ink/build/instances.js +8 -0
  364. package/vendor/ink/build/instances.js.map +1 -0
  365. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  366. package/vendor/ink/build/kitty-keyboard.js +32 -0
  367. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  368. package/vendor/ink/build/log-update.d.ts +20 -0
  369. package/vendor/ink/build/log-update.js +261 -0
  370. package/vendor/ink/build/log-update.js.map +1 -0
  371. package/vendor/ink/build/measure-element.d.ts +20 -0
  372. package/vendor/ink/build/measure-element.js +13 -0
  373. package/vendor/ink/build/measure-element.js.map +1 -0
  374. package/vendor/ink/build/measure-text.d.ts +6 -0
  375. package/vendor/ink/build/measure-text.js +21 -0
  376. package/vendor/ink/build/measure-text.js.map +1 -0
  377. package/vendor/ink/build/output.d.ts +35 -0
  378. package/vendor/ink/build/output.js +328 -0
  379. package/vendor/ink/build/output.js.map +1 -0
  380. package/vendor/ink/build/parse-keypress.d.ts +20 -0
  381. package/vendor/ink/build/parse-keypress.js +495 -0
  382. package/vendor/ink/build/parse-keypress.js.map +1 -0
  383. package/vendor/ink/build/reconciler.d.ts +4 -0
  384. package/vendor/ink/build/reconciler.js +306 -0
  385. package/vendor/ink/build/reconciler.js.map +1 -0
  386. package/vendor/ink/build/render-background.d.ts +4 -0
  387. package/vendor/ink/build/render-background.js +25 -0
  388. package/vendor/ink/build/render-background.js.map +1 -0
  389. package/vendor/ink/build/render-border.d.ts +4 -0
  390. package/vendor/ink/build/render-border.js +84 -0
  391. package/vendor/ink/build/render-border.js.map +1 -0
  392. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  393. package/vendor/ink/build/render-node-to-output.js +162 -0
  394. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  395. package/vendor/ink/build/render-to-string.d.ts +38 -0
  396. package/vendor/ink/build/render-to-string.js +116 -0
  397. package/vendor/ink/build/render-to-string.js.map +1 -0
  398. package/vendor/ink/build/render.d.ts +176 -0
  399. package/vendor/ink/build/render.js +71 -0
  400. package/vendor/ink/build/render.js.map +1 -0
  401. package/vendor/ink/build/renderer.d.ts +8 -0
  402. package/vendor/ink/build/renderer.js +64 -0
  403. package/vendor/ink/build/renderer.js.map +1 -0
  404. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  405. package/vendor/ink/build/sanitize-ansi.js +27 -0
  406. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  407. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  408. package/vendor/ink/build/squash-text-nodes.js +36 -0
  409. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  410. package/vendor/ink/build/styles.d.ts +302 -0
  411. package/vendor/ink/build/styles.js +303 -0
  412. package/vendor/ink/build/styles.js.map +1 -0
  413. package/vendor/ink/build/utils.d.ts +9 -0
  414. package/vendor/ink/build/utils.js +19 -0
  415. package/vendor/ink/build/utils.js.map +1 -0
  416. package/vendor/ink/build/wrap-text.d.ts +3 -0
  417. package/vendor/ink/build/wrap-text.js +38 -0
  418. package/vendor/ink/build/wrap-text.js.map +1 -0
  419. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  420. package/vendor/ink/build/write-synchronized.js +9 -0
  421. package/vendor/ink/build/write-synchronized.js.map +1 -0
  422. package/vendor/ink/license +10 -0
  423. package/vendor/ink/package.json +137 -0
  424. package/.claude-plugin/marketplace.json +0 -34
  425. package/.claude-plugin/plugin.json +0 -20
  426. package/.gitattributes +0 -34
  427. package/.mcp.json +0 -14
  428. package/ARCHITECTURE.md +0 -77
  429. package/CHANGELOG.md +0 -30
  430. package/CONTRIBUTING.md +0 -45
  431. package/DATA-FLOW.md +0 -79
  432. package/LICENSE +0 -21
  433. package/SECURITY.md +0 -138
  434. package/UNINSTALL.md +0 -112
  435. package/agents/maintenance.md +0 -5
  436. package/agents/memory-classification.md +0 -30
  437. package/agents/scheduler-task.md +0 -18
  438. package/agents/webhook-handler.md +0 -27
  439. package/agents/worker.md +0 -24
  440. package/bin/bridge +0 -133
  441. package/bin/statusline-launcher.mjs +0 -82
  442. package/bin/statusline-lib.mjs +0 -558
  443. package/bin/statusline.mjs +0 -615
  444. package/bun.lock +0 -927
  445. package/commands/config.md +0 -16
  446. package/commands/doctor.md +0 -13
  447. package/commands/setup.md +0 -17
  448. package/defaults/hidden-roles.json +0 -68
  449. package/defaults/memory-chunk-prompt.md +0 -63
  450. package/defaults/mixdog-config.template.json +0 -27
  451. package/defaults/user-workflow.json +0 -8
  452. package/defaults/user-workflow.md +0 -17
  453. package/hooks/hooks.json +0 -73
  454. package/hooks/lib/active-instance.cjs +0 -77
  455. package/hooks/lib/permission-evaluator.cjs +0 -411
  456. package/hooks/lib/permission-route.cjs +0 -63
  457. package/hooks/lib/settings-loader.cjs +0 -117
  458. package/hooks/post-tool-use.cjs +0 -84
  459. package/hooks/pre-mcp-sandbox.cjs +0 -158
  460. package/hooks/pre-tool-subagent.cjs +0 -258
  461. package/hooks/session-start.cjs +0 -1479
  462. package/hooks/shim-launcher.cjs +0 -65
  463. package/hooks/turn-timer.cjs +0 -82
  464. package/lib/claude-md-writer.cjs +0 -386
  465. package/lib/keychain-cjs.cjs +0 -290
  466. package/lib/plugin-paths.cjs +0 -69
  467. package/lib/rules-builder.cjs +0 -241
  468. package/native/README.md +0 -117
  469. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  470. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  471. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  472. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  473. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  474. package/prompts/code-review.txt +0 -16
  475. package/prompts/security-audit.txt +0 -17
  476. package/rules/bridge/00-common.md +0 -39
  477. package/rules/bridge/20-skip-protocol.md +0 -18
  478. package/rules/bridge/30-explorer.md +0 -33
  479. package/rules/bridge/40-cycle1-agent.md +0 -52
  480. package/rules/bridge/41-cycle2-agent.md +0 -62
  481. package/rules/lead/00-tool-lead.md +0 -61
  482. package/rules/lead/01-general.md +0 -23
  483. package/rules/lead/02-channels.md +0 -49
  484. package/rules/lead/03-team.md +0 -27
  485. package/rules/lead/04-workflow.md +0 -20
  486. package/rules/shared/00-language.md +0 -14
  487. package/rules/shared/01-tool.md +0 -138
  488. package/scripts/bootstrap.mjs +0 -130
  489. package/scripts/bridge-unify-smoke.mjs +0 -308
  490. package/scripts/build-runtime-linux.sh +0 -348
  491. package/scripts/build-runtime-macos.sh +0 -217
  492. package/scripts/build-runtime-windows.ps1 +0 -242
  493. package/scripts/builtin-utils-smoke.mjs +0 -398
  494. package/scripts/bump.mjs +0 -80
  495. package/scripts/check-json.mjs +0 -45
  496. package/scripts/check-syntax-changed.mjs +0 -102
  497. package/scripts/check-syntax.mjs +0 -58
  498. package/scripts/code-graph-batch.test.mjs +0 -33
  499. package/scripts/config-preserve-smoke.mjs +0 -180
  500. package/scripts/doctor.mjs +0 -489
  501. package/scripts/edit-normalize-fuzz.mjs +0 -130
  502. package/scripts/edit-normalize-smoke.mjs +0 -401
  503. package/scripts/edit-operation-smoke.mjs +0 -369
  504. package/scripts/edit2-smoke.mjs +0 -63
  505. package/scripts/ensure-deps.mjs +0 -259
  506. package/scripts/fuzzy-e2e.mjs +0 -28
  507. package/scripts/fuzzy-smoke.mjs +0 -26
  508. package/scripts/generate-runtime-manifest.mjs +0 -166
  509. package/scripts/guard-smoke.mjs +0 -66
  510. package/scripts/hidden-role-schema-smoke.mjs +0 -162
  511. package/scripts/hook-routing-smoke.mjs +0 -29
  512. package/scripts/inject-input.ps1 +0 -204
  513. package/scripts/io-complex-smoke.mjs +0 -667
  514. package/scripts/io-explore-bench.mjs +0 -424
  515. package/scripts/io-guardrails-smoke.mjs +0 -205
  516. package/scripts/io-mini-bench-baseline.json +0 -11
  517. package/scripts/io-mini-bench.mjs +0 -216
  518. package/scripts/io-route-harness.mjs +0 -933
  519. package/scripts/io-telemetry-report.mjs +0 -691
  520. package/scripts/mutation-bench.mjs +0 -564
  521. package/scripts/mutation-io-smoke.mjs +0 -1097
  522. package/scripts/native-patch-bridge-smoke.mjs +0 -288
  523. package/scripts/native-patch-smoke.mjs +0 -304
  524. package/scripts/openai-oauth-catalog-smoke.mjs +0 -53
  525. package/scripts/patch-interior-context-smoke.mjs +0 -49
  526. package/scripts/patch-newline-utf8-smoke.mjs +0 -157
  527. package/scripts/perf-hook-smoke.mjs +0 -71
  528. package/scripts/permission-eval-smoke.mjs +0 -443
  529. package/scripts/prep-patch.mjs +0 -53
  530. package/scripts/prep-shim.mjs +0 -96
  531. package/scripts/provider-cache-smoke.mjs +0 -687
  532. package/scripts/report-runtime-health.mjs +0 -132
  533. package/scripts/resolve-bun.mjs +0 -60
  534. package/scripts/run-mcp.mjs +0 -1448
  535. package/scripts/salvage-v4a-shatter.test.mjs +0 -58
  536. package/scripts/scoped-cache-io-smoke.mjs +0 -103
  537. package/scripts/shell-policy-round3-smoke.mjs +0 -46
  538. package/scripts/smoke-runtime-negative.ps1 +0 -100
  539. package/scripts/smoke-runtime-negative.sh +0 -95
  540. package/scripts/stall-policy-smoke.mjs +0 -50
  541. package/scripts/start-memory-worker.mjs +0 -23
  542. package/scripts/statusline-launcher-smoke.mjs +0 -82
  543. package/scripts/stress-atomic-write.mjs +0 -1028
  544. package/scripts/test-fault-inject.mjs +0 -164
  545. package/scripts/test-large-file.mjs +0 -174
  546. package/scripts/tool-edge-smoke.mjs +0 -209
  547. package/scripts/uninstall.mjs +0 -201
  548. package/scripts/webhook-selfheal-smoke.mjs +0 -27
  549. package/scripts/write-overwrite-guard-smoke.mjs +0 -56
  550. package/server-main.mjs +0 -3109
  551. package/server.mjs +0 -468
  552. package/setup/config-merge.mjs +0 -246
  553. package/setup/install.mjs +0 -574
  554. package/setup/launch-core.mjs +0 -617
  555. package/setup/launch.mjs +0 -101
  556. package/setup/locate-claude.mjs +0 -56
  557. package/setup/mixdog-cli.mjs +0 -122
  558. package/setup/setup-server.mjs +0 -3305
  559. package/setup/setup.html +0 -3740
  560. package/setup/tui.mjs +0 -325
  561. package/skills/retro-skill-proposer/SKILL.md +0 -92
  562. package/skills/schedule-add/SKILL.md +0 -77
  563. package/skills/setup/SKILL.md +0 -356
  564. package/skills/webhook-add/SKILL.md +0 -81
  565. package/src/agent/bridge-stall-watchdog.mjs +0 -337
  566. package/src/agent/index.mjs +0 -2138
  567. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +0 -1010
  568. package/src/agent/orchestrator/bridge-retry.mjs +0 -220
  569. package/src/agent/orchestrator/bridge-trace.mjs +0 -583
  570. package/src/agent/orchestrator/cache-mtime.mjs +0 -58
  571. package/src/agent/orchestrator/config.mjs +0 -405
  572. package/src/agent/orchestrator/context/collect.mjs +0 -651
  573. package/src/agent/orchestrator/dispatch-persist.mjs +0 -549
  574. package/src/agent/orchestrator/drain-registry.mjs +0 -50
  575. package/src/agent/orchestrator/explore-validator.mjs +0 -8
  576. package/src/agent/orchestrator/internal-roles.mjs +0 -118
  577. package/src/agent/orchestrator/internal-tools.mjs +0 -88
  578. package/src/agent/orchestrator/jobs.mjs +0 -116
  579. package/src/agent/orchestrator/mcp/client.mjs +0 -364
  580. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +0 -1881
  581. package/src/agent/orchestrator/providers/anthropic.mjs +0 -594
  582. package/src/agent/orchestrator/providers/gemini.mjs +0 -1530
  583. package/src/agent/orchestrator/providers/grok-oauth.mjs +0 -779
  584. package/src/agent/orchestrator/providers/model-catalog.mjs +0 -374
  585. package/src/agent/orchestrator/providers/openai-compat-stream.mjs +0 -366
  586. package/src/agent/orchestrator/providers/openai-compat.mjs +0 -1511
  587. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1891
  588. package/src/agent/orchestrator/providers/openai-oauth.mjs +0 -1456
  589. package/src/agent/orchestrator/providers/openai-ws.mjs +0 -127
  590. package/src/agent/orchestrator/providers/registry.mjs +0 -192
  591. package/src/agent/orchestrator/providers/retry-classifier.mjs +0 -325
  592. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +0 -142
  593. package/src/agent/orchestrator/session/cache/read-cache.mjs +0 -319
  594. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +0 -361
  595. package/src/agent/orchestrator/session/loop.mjs +0 -1478
  596. package/src/agent/orchestrator/session/manager.mjs +0 -1991
  597. package/src/agent/orchestrator/session/result-classification.mjs +0 -65
  598. package/src/agent/orchestrator/session/store.mjs +0 -632
  599. package/src/agent/orchestrator/session/stream-watchdog.mjs +0 -130
  600. package/src/agent/orchestrator/session/tool-result-offload.mjs +0 -166
  601. package/src/agent/orchestrator/session/trim.mjs +0 -491
  602. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +0 -115
  603. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +0 -331
  604. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +0 -150
  605. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +0 -228
  606. package/src/agent/orchestrator/smart-bridge/index.mjs +0 -215
  607. package/src/agent/orchestrator/smart-bridge/profiles.mjs +0 -37
  608. package/src/agent/orchestrator/smart-bridge/registry.mjs +0 -348
  609. package/src/agent/orchestrator/stall-policy.mjs +0 -201
  610. package/src/agent/orchestrator/tool-loop-guard.mjs +0 -75
  611. package/src/agent/orchestrator/tools/bash-session.mjs +0 -722
  612. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +0 -455
  613. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -480
  614. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +0 -256
  615. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +0 -58
  616. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +0 -240
  617. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +0 -113
  618. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +0 -74
  619. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +0 -242
  620. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +0 -211
  621. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +0 -1364
  622. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +0 -126
  623. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +0 -141
  624. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +0 -194
  625. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +0 -60
  626. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +0 -168
  627. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +0 -173
  628. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +0 -48
  629. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -99
  630. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +0 -193
  631. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +0 -597
  632. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  633. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +0 -300
  634. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +0 -152
  635. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +0 -118
  636. package/src/agent/orchestrator/tools/builtin/read-open.mjs +0 -190
  637. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +0 -728
  638. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +0 -173
  639. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +0 -602
  640. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +0 -196
  641. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +0 -422
  642. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +0 -158
  643. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +0 -869
  644. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +0 -962
  645. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +0 -223
  646. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +0 -206
  647. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +0 -401
  648. package/src/agent/orchestrator/tools/builtin.mjs +0 -503
  649. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +0 -24
  650. package/src/agent/orchestrator/tools/code-graph.mjs +0 -4095
  651. package/src/agent/orchestrator/tools/cwd-tool.mjs +0 -298
  652. package/src/agent/orchestrator/tools/destructive-warning.mjs +0 -323
  653. package/src/agent/orchestrator/tools/edit-normalize.mjs +0 -603
  654. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +0 -154
  655. package/src/agent/orchestrator/tools/graph-manifest.json +0 -26
  656. package/src/agent/orchestrator/tools/host-input.mjs +0 -204
  657. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +0 -143
  658. package/src/agent/orchestrator/tools/patch-manifest.json +0 -26
  659. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +0 -20
  660. package/src/agent/orchestrator/tools/patch.mjs +0 -2754
  661. package/src/agent/orchestrator/tools/progress-message.mjs +0 -118
  662. package/src/agent/orchestrator/tools/shell-command.mjs +0 -865
  663. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +0 -7
  664. package/src/agent/orchestrator/tools/shell-snapshot.mjs +0 -313
  665. package/src/agent/orchestrator/workflow-store.mjs +0 -93
  666. package/src/agent/tool-defs.mjs +0 -103
  667. package/src/channels/backends/discord.mjs +0 -784
  668. package/src/channels/data/voice-runtime-manifest.json +0 -138
  669. package/src/channels/index.mjs +0 -3268
  670. package/src/channels/lib/config.mjs +0 -292
  671. package/src/channels/lib/drop-trace.mjs +0 -71
  672. package/src/channels/lib/event-pipeline.mjs +0 -81
  673. package/src/channels/lib/holidays.mjs +0 -138
  674. package/src/channels/lib/hook-pipe-server.mjs +0 -822
  675. package/src/channels/lib/output-forwarder.mjs +0 -765
  676. package/src/channels/lib/runtime-paths.mjs +0 -517
  677. package/src/channels/lib/scheduler.mjs +0 -723
  678. package/src/channels/lib/session-discovery.mjs +0 -103
  679. package/src/channels/lib/state-file.mjs +0 -68
  680. package/src/channels/lib/status-snapshot.mjs +0 -219
  681. package/src/channels/lib/tool-format.mjs +0 -140
  682. package/src/channels/lib/transcript-discovery.mjs +0 -195
  683. package/src/channels/lib/voice-runtime-fetcher.mjs +0 -734
  684. package/src/channels/lib/webhook.mjs +0 -1318
  685. package/src/channels/tool-defs.mjs +0 -170
  686. package/src/daemon/host.mjs +0 -118
  687. package/src/daemon/mcp-transport.mjs +0 -47
  688. package/src/daemon/session.mjs +0 -100
  689. package/src/daemon/thin-client.mjs +0 -71
  690. package/src/daemon/transport.mjs +0 -163
  691. package/src/memory/data/runtime-manifest.json +0 -40
  692. package/src/memory/index.mjs +0 -3332
  693. package/src/memory/lib/core-memory-store.mjs +0 -330
  694. package/src/memory/lib/embedding-provider.mjs +0 -269
  695. package/src/memory/lib/embedding-worker.mjs +0 -323
  696. package/src/memory/lib/memory-cycle1.mjs +0 -645
  697. package/src/memory/lib/memory-cycle2.mjs +0 -1284
  698. package/src/memory/lib/memory-cycle3.mjs +0 -540
  699. package/src/memory/lib/memory-embed.mjs +0 -299
  700. package/src/memory/lib/memory-ops-policy.mjs +0 -190
  701. package/src/memory/lib/memory-recall-store.mjs +0 -638
  702. package/src/memory/lib/memory.mjs +0 -412
  703. package/src/memory/lib/pg/adapter.mjs +0 -308
  704. package/src/memory/lib/pg/process.mjs +0 -360
  705. package/src/memory/lib/pg/supervisor.mjs +0 -396
  706. package/src/memory/lib/runtime-fetcher.mjs +0 -458
  707. package/src/memory/lib/trace-store.mjs +0 -728
  708. package/src/memory/tool-defs.mjs +0 -79
  709. package/src/search/index.mjs +0 -1173
  710. package/src/search/lib/backends/anthropic-oauth.mjs +0 -98
  711. package/src/search/lib/backends/exa.mjs +0 -50
  712. package/src/search/lib/backends/firecrawl.mjs +0 -61
  713. package/src/search/lib/backends/gemini-api.mjs +0 -83
  714. package/src/search/lib/backends/grok-oauth.mjs +0 -86
  715. package/src/search/lib/backends/index.mjs +0 -150
  716. package/src/search/lib/backends/openai-api.mjs +0 -144
  717. package/src/search/lib/backends/openai-oauth.mjs +0 -102
  718. package/src/search/lib/backends/openai-web-search.mjs +0 -76
  719. package/src/search/lib/backends/tavily.mjs +0 -55
  720. package/src/search/lib/backends/xai-api.mjs +0 -113
  721. package/src/search/lib/config.mjs +0 -192
  722. package/src/search/lib/provider-usage.mjs +0 -67
  723. package/src/search/lib/providers.mjs +0 -47
  724. package/src/search/lib/search-intent.mjs +0 -109
  725. package/src/search/lib/setup-handler.mjs +0 -261
  726. package/src/search/lib/web-tools.mjs +0 -1219
  727. package/src/search/tool-defs.mjs +0 -83
  728. package/src/setup/defender-exclusion.mjs +0 -183
  729. package/src/shared/atomic-file.mjs +0 -436
  730. package/src/shared/config.mjs +0 -372
  731. package/src/shared/daemon-recycle.mjs +0 -108
  732. package/src/shared/disable-claude-builtins.mjs +0 -91
  733. package/src/shared/err-text.mjs +0 -12
  734. package/src/shared/llm/cost.mjs +0 -66
  735. package/src/shared/llm/http-agent.mjs +0 -123
  736. package/src/shared/open-url.mjs +0 -62
  737. package/src/shared/plugin-paths.mjs +0 -58
  738. package/src/shared/schedules-store.mjs +0 -70
  739. package/src/shared/seed.mjs +0 -136
  740. package/src/shared/user-cwd.mjs +0 -225
  741. package/src/shared/user-data-guard.mjs +0 -244
  742. package/src/status/aggregator.mjs +0 -584
  743. package/src/status/server.mjs +0 -413
  744. package/tools.json +0 -1653
  745. /package/{defaults → src/defaults}/cycle3-review-prompt.md +0 -0
  746. /package/{defaults → src/defaults}/memory-promote-prompt.md +0 -0
  747. /package/{hooks → src/hooks}/lib/permission-rules.cjs +0 -0
  748. /package/{lib → src/lib}/config-cjs.cjs +0 -0
  749. /package/{lib → src/lib}/hook-pipe-path.cjs +0 -0
  750. /package/{lib → src/lib}/mixdog-debug.cjs +0 -0
  751. /package/{lib → src/lib}/text-utils.cjs +0 -0
  752. /package/{rules → src/rules}/bridge/42-cycle3-agent.md +0 -0
  753. /package/src/{agent → runtime/agent}/orchestrator/activity-bus.mjs +0 -0
  754. /package/src/{agent → runtime/agent}/orchestrator/providers/anthropic-betas.mjs +0 -0
  755. /package/src/{agent → runtime/agent}/orchestrator/session/abort-lookup.mjs +0 -0
  756. /package/src/{agent → runtime/agent}/orchestrator/session/cache/post-edit-marks.mjs +0 -0
  757. /package/src/{agent → runtime/agent}/orchestrator/session/cache/scoped-cache-outcome.mjs +0 -0
  758. /package/src/{agent → runtime/agent}/orchestrator/session/cache/util.mjs +0 -0
  759. /package/src/{agent → runtime/agent}/orchestrator/session/read-dedup.mjs +0 -0
  760. /package/src/{agent → runtime/agent}/orchestrator/session/save-session-worker.mjs +0 -0
  761. /package/src/{agent → runtime/agent}/orchestrator/smart-bridge/session-builder.mjs +0 -0
  762. /package/src/{agent → runtime/agent}/orchestrator/tools/bash-policy-scan.mjs +0 -0
  763. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/advisory-lock.mjs +0 -0
  764. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/atomic-write.mjs +0 -0
  765. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/binary-file.mjs +0 -0
  766. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cache-layers.mjs +0 -0
  767. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cwd-utils.mjs +0 -0
  768. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/device-paths.mjs +0 -0
  769. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -0
  770. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diff-utils.mjs +0 -0
  771. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/fs-reachability.mjs +0 -0
  772. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/grep-formatting.mjs +0 -0
  773. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/hash-utils.mjs +0 -0
  774. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/list-formatting.mjs +0 -0
  775. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/open-config-tool.mjs +0 -0
  776. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-locks.mjs +0 -0
  777. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-utils.mjs +0 -0
  778. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-args.mjs +0 -0
  779. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-batch.mjs +0 -0
  780. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-constants.mjs +0 -0
  781. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image-resize.mjs +0 -0
  782. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image.mjs +0 -0
  783. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-lines.mjs +0 -0
  784. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-mode-tool.mjs +0 -0
  785. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-range-index.mjs +0 -0
  786. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-ranges.mjs +0 -0
  787. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-special-files.mjs +0 -0
  788. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-tool.mjs +0 -0
  789. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-windows.mjs +0 -0
  790. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-analysis.mjs +0 -0
  791. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-output.mjs +0 -0
  792. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-helpers.mjs +0 -0
  793. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-validation.mjs +0 -0
  794. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/text-stats.mjs +0 -0
  795. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/windows-roots.mjs +0 -0
  796. /package/src/{agent → runtime/agent}/orchestrator/tools/code-graph-prewarm-worker.mjs +0 -0
  797. /package/src/{agent → runtime/agent}/orchestrator/tools/env-scrub.mjs +0 -0
  798. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-content-cache.mjs +0 -0
  799. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-planner.mjs +0 -0
  800. /package/src/{agent → runtime/agent}/orchestrator/tools/next-call-utils.mjs +0 -0
  801. /package/src/{agent → runtime/agent}/orchestrator/tools/result-compression.mjs +0 -0
  802. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-exec-policy.mjs +0 -0
  803. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy-danger-target.mjs +0 -0
  804. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy.mjs +0 -0
  805. /package/src/{channels → runtime/channels}/lib/cli-worker-host.mjs +0 -0
  806. /package/src/{channels → runtime/channels}/lib/config-lock.mjs +0 -0
  807. /package/src/{channels → runtime/channels}/lib/event-queue.mjs +0 -0
  808. /package/src/{channels → runtime/channels}/lib/executor.mjs +0 -0
  809. /package/src/{channels → runtime/channels}/lib/format.mjs +0 -0
  810. /package/src/{channels → runtime/channels}/lib/interaction-workflows.mjs +0 -0
  811. /package/src/{channels → runtime/channels}/lib/memory-client.mjs +0 -0
  812. /package/src/{channels → runtime/channels}/lib/session-control.mjs +0 -0
  813. /package/src/{channels → runtime/channels}/lib/settings.mjs +0 -0
  814. /package/src/{channels → runtime/channels}/lib/whisper-server.mjs +0 -0
  815. /package/src/{memory → runtime/memory}/lib/agent-ipc.mjs +0 -0
  816. /package/src/{memory → runtime/memory}/lib/bridge-trace-queries.mjs +0 -0
  817. /package/src/{memory → runtime/memory}/lib/llm-worker-host.mjs +0 -0
  818. /package/src/{memory → runtime/memory}/lib/memory-cycle.mjs +0 -0
  819. /package/src/{memory → runtime/memory}/lib/memory-extraction.mjs +0 -0
  820. /package/src/{memory → runtime/memory}/lib/memory-maintenance-store.mjs +0 -0
  821. /package/src/{memory → runtime/memory}/lib/memory-recall-id-patch.mjs +0 -0
  822. /package/src/{memory → runtime/memory}/lib/memory-recall-read-query.mjs +0 -0
  823. /package/src/{memory → runtime/memory}/lib/memory-recall-scope-filter.mjs +0 -0
  824. /package/src/{memory → runtime/memory}/lib/memory-retrievers.mjs +0 -0
  825. /package/src/{memory → runtime/memory}/lib/memory-score.mjs +0 -0
  826. /package/src/{memory → runtime/memory}/lib/memory-text-utils.mjs +0 -0
  827. /package/src/{memory → runtime/memory}/lib/model-profile.mjs +0 -0
  828. /package/src/{memory → runtime/memory}/lib/project-id-resolver.mjs +0 -0
  829. /package/src/{search → runtime/search}/lib/cache.mjs +0 -0
  830. /package/src/{search → runtime/search}/lib/formatter.mjs +0 -0
  831. /package/src/{search → runtime/search}/lib/state.mjs +0 -0
  832. /package/src/{shared → runtime/shared}/abort-controller.mjs +0 -0
  833. /package/src/{shared → runtime/shared}/llm/index.mjs +0 -0
  834. /package/src/{shared → runtime/shared}/llm/pid-cleanup.mjs +0 -0
  835. /package/src/{shared → runtime/shared}/llm/usage-log.mjs +0 -0
  836. /package/src/{shared → runtime/shared}/wsl.mjs +0 -0
@@ -1,1448 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * MCP server launcher for mixdog (bun-only) — proxy supervisor.
4
- *
5
- * Boot sequence:
6
- * 1. Resolve the shared data directory via plugin-paths.cjs.
7
- * 2. Copy package.json + bun.lock there and run `bun install --frozen-lockfile`
8
- * into <dataDir>/node_modules/ (only when the lockfile / dep-keys change).
9
- * 3. Symlink pluginRoot/node_modules → dataDir/node_modules so all plugin
10
- * code resolves deps from the shared install.
11
- * 4. Spawn server.mjs with bun and proxy MCP stdio between Claude Code and
12
- * the child. The proxy caches the client's initialize/initialized so a
13
- * child kill (dev-sync --restart, crash) can be silently re-handshaken
14
- * against a fresh child without forcing the client to reconnect.
15
- *
16
- * Single-runtime path: any failure throws — no node fallback.
17
- */
18
- import { fileURLToPath } from 'url';
19
- import { createRequire } from 'module';
20
- import { dirname, join } from 'path';
21
- import * as fs from 'fs';
22
- import { randomUUID } from 'crypto';
23
- import { execSync, spawn } from 'child_process';
24
- import * as os from 'os';
25
- import {
26
- ensureRuntimeDeps,
27
- hasRequiredDeps,
28
- renameWithRetrySync,
29
- RENAME_RETRY_CODES,
30
- sleepSync,
31
- } from './ensure-deps.mjs';
32
-
33
- // Stable per-terminal session id for this proxy supervisor's lifetime. The
34
- // child server.mjs is respawned on crash / dev-sync restart, but THIS
35
- // supervisor process survives, so a once-minted id stays constant across
36
- // child reconnects. thin-client.mjs advertises it on the daemon control
37
- // frame; a constant id keeps the daemon's bySession map pinned to the LIVE
38
- // connection instead of minting a fresh bootstrap UUID per reconnect — which
39
- // stranded detached worker results on stale (dead) connections. Honor an
40
- // upstream-provided id if one already exists.
41
- const STABLE_TERMINAL_SESSION_ID = process.env.MIXDOG_SESSION_ID || randomUUID();
42
-
43
- const __dirname = dirname(fileURLToPath(import.meta.url));
44
- const __localRoot = join(__dirname, '..');
45
-
46
- // Read installed_plugins.json each boot so dev-sync --restart picks up new code
47
- // without forcing client reconnect. Falls back to own cache dir on any error.
48
- function _resolveLatestPluginRoot() {
49
- try {
50
- const manifestPath = join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json');
51
- const data = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
52
- if (!data || typeof data !== 'object' || !data.plugins) {
53
- process.stderr.write('[run-mcp] WARN: installed_plugins.json has unexpected shape — using fallback\n')
54
- return __localRoot
55
- }
56
- const entry = data?.plugins?.['mixdog@trib-plugin']?.[0];
57
- if (entry?.installPath) {
58
- const latest = entry.installPath.replace(/\\/g, '/');
59
- if (fs.existsSync(latest)) {
60
- return latest
61
- }
62
- }
63
- } catch {}
64
- process.stderr.write('[run-mcp] manifest-lock-fallback: manifest read failed — using boot pluginRoot as-is\n')
65
- return __localRoot;
66
- }
67
- const pluginRoot = _resolveLatestPluginRoot();
68
- if (pluginRoot !== __localRoot) {
69
- process.stderr.write(`[run-mcp] supervisor proxying to latest cache: ${pluginRoot} (own=${__localRoot})\n`);
70
- }
71
- const serverPath = join(pluginRoot, 'server.mjs');
72
- const pluginPkg = join(pluginRoot, 'package.json');
73
- const pluginNm = join(pluginRoot, 'node_modules');
74
-
75
- process.stderr.write(`[boot-time] tag=run-mcp-entry tMs=${Date.now()}\n`);
76
-
77
- // Surface plugin.json/package.json version drift at boot — warn-only.
78
- try {
79
- const pluginVer = JSON.parse(fs.readFileSync(join(pluginRoot, '.claude-plugin', 'plugin.json'), 'utf8')).version;
80
- const packageVer = JSON.parse(fs.readFileSync(pluginPkg, 'utf8')).version;
81
- if (pluginVer && packageVer && pluginVer !== packageVer) {
82
- process.stderr.write(
83
- `[run-mcp] WARN: version mismatch — plugin.json=${pluginVer} package.json=${packageVer}\n`
84
- + ` Update package.json/.claude-plugin/plugin.json so both fields match.\n`,
85
- );
86
- }
87
- } catch { /* missing manifest — not run-mcp's concern */ }
88
- // Note: the supervisor cache-version advert (read by dev-sync) is written
89
- // by server.mjs at child boot, NOT here. Keeping advert/diagnostic writes
90
- // out of run-mcp.mjs means future updates to that logic land via
91
- // child-only restart and never sever the stdio bridge to Claude Code.
92
- // server.mjs reads MIXDOG_SUPERVISOR_PID + MIXDOG_SUPERVISOR_CACHE_DIR
93
- // from its env (set in spawnChild below) to identify the supervisor.
94
-
95
- // ── Lightweight JSON-RPC line scanner ────────────────────────────────────────
96
- // Extracts `id` and `method` from a JSON-RPC line without a full JSON.parse.
97
- // Returns { id, method } (each may be undefined), or null on scan failure.
98
-
99
- // Returns true when the line must be fully parsed (initialize / negative-id).
100
- function _lineNeedsFullParse(line) {
101
- if (/"id"\s*:\s*-/.test(line)) return true; // internal negative-id
102
- if (/"method"\s*:\s*"initializ/.test(line)) return true; // initialize / initialized
103
- return false;
104
- }
105
-
106
- const _JSON_STRING_RE = '"(?:\\\\.|[^"\\\\])*"';
107
- const _JSON_NUMBER_RE = '-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?';
108
- const _JSON_STRING_ONLY_RE = new RegExp(`^${_JSON_STRING_RE}$`);
109
-
110
- function _parseJsonRpcScalar(raw) {
111
- if (raw === 'null') return null;
112
- if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(raw)) return Number(raw);
113
- if (raw && raw[0] === '"') {
114
- try { return JSON.parse(raw); } catch { return undefined; }
115
- }
116
- return undefined;
117
- }
118
-
119
- function _skipJsonWs(s, i) {
120
- while (i < s.length && /\s/.test(s[i])) i++;
121
- return i;
122
- }
123
-
124
- function _readJsonStringLiteral(s, i) {
125
- if (s[i] !== '"') return null;
126
- let escaped = false;
127
- for (let j = i + 1; j < s.length; j++) {
128
- const ch = s[j];
129
- if (escaped) { escaped = false; continue; }
130
- if (ch === '\\') { escaped = true; continue; }
131
- if (ch === '"') return { raw: s.slice(i, j + 1), end: j + 1 };
132
- }
133
- return null;
134
- }
135
-
136
- function _skipJsonValue(s, i) {
137
- i = _skipJsonWs(s, i);
138
- const ch = s[i];
139
- if (ch === '"') return _readJsonStringLiteral(s, i)?.end ?? -1;
140
- if (ch === '{' || ch === '[') {
141
- const close = ch === '{' ? '}' : ']';
142
- const open = ch;
143
- let depth = 0;
144
- let inString = false;
145
- let escaped = false;
146
- for (let j = i; j < s.length; j++) {
147
- const c = s[j];
148
- if (inString) {
149
- if (escaped) { escaped = false; continue; }
150
- if (c === '\\') { escaped = true; continue; }
151
- if (c === '"') inString = false;
152
- continue;
153
- }
154
- if (c === '"') { inString = true; continue; }
155
- if (c === open) depth++;
156
- else if (c === close) {
157
- depth--;
158
- if (depth === 0) return j + 1;
159
- }
160
- }
161
- return -1;
162
- }
163
- while (i < s.length && s[i] !== ',' && s[i] !== '}' && s[i] !== ']') i++;
164
- return i;
165
- }
166
-
167
- // Cheap extraction of `id` + `method` for the common single-message JSON-RPC
168
- // hot path. Batch payloads are rare and still use JSON.parse so per-item
169
- // errors stay exact. Non-RPC/noise lines return null and are quarantined.
170
- function _scanIdMethod(line) {
171
- try {
172
- const s = String(line || '').trim();
173
- if (!s) return null;
174
- if (s[0] === '[') {
175
- const obj = JSON.parse(s);
176
- if (!Array.isArray(obj)) return null;
177
- return obj.map(item => {
178
- if (!item || typeof item !== 'object' || Array.isArray(item)) {
179
- return { id: null, _malformed: true };
180
- }
181
- return { id: item.id, method: item.method, _malformed: false };
182
- });
183
- }
184
- if (s[0] !== '{' || s[s.length - 1] !== '}') return null;
185
- let id;
186
- let method;
187
- let sawId = false;
188
- let sawMethod = false;
189
- let i = 1;
190
- while (i < s.length - 1) {
191
- i = _skipJsonWs(s, i);
192
- if (s[i] === ',') { i++; continue; }
193
- if (s[i] === '}') break;
194
- const keyLit = _readJsonStringLiteral(s, i);
195
- if (!keyLit) return null;
196
- let key;
197
- try { key = JSON.parse(keyLit.raw); } catch { return null; }
198
- i = _skipJsonWs(s, keyLit.end);
199
- if (s[i] !== ':') return null;
200
- i = _skipJsonWs(s, i + 1);
201
- const valueStart = i;
202
- const valueEnd = _skipJsonValue(s, valueStart);
203
- if (valueEnd < 0) return null;
204
- if (key === 'id') {
205
- const raw = s.slice(valueStart, valueEnd).trim();
206
- id = _parseJsonRpcScalar(raw);
207
- if (id === undefined) return null;
208
- sawId = true;
209
- } else if (key === 'method') {
210
- const raw = s.slice(valueStart, valueEnd).trim();
211
- if (!_JSON_STRING_ONLY_RE.test(raw)) return null;
212
- try { method = JSON.parse(raw); } catch { return null; }
213
- sawMethod = true;
214
- }
215
- i = valueEnd;
216
- }
217
- if (!sawId && !sawMethod) return null;
218
- return { id: sawId ? id : undefined, method: sawMethod ? method : undefined, _malformed: false };
219
- } catch {
220
- // Return null so handleChildLine's `if (scanned === null)` branch
221
- // catches non-JSON noise and quarantines it via supLog instead of
222
- // forwarding to the client. Previously this returned a malformed
223
- // sentinel that fell through to writeToClient, leaking non-JSON
224
- // bytes into the JSON-RPC frame stream (the "all tools hang"
225
- // regression vector).
226
- return null;
227
- }
228
- }
229
-
230
- function ensureNmSymlink(linkPath, targetPath) {
231
- const linkType = process.platform === 'win32' ? 'junction' : 'dir';
232
- // EPERM/EBUSY here is almost always a transient AV / indexer lock on the
233
- // freshly-created junction. Retry with bounded backoff (~750ms) before
234
- // giving up so a healthy boot doesn't have to wait for the next start.
235
- const trySymlink = () => {
236
- for (let attempt = 0; attempt < 5; attempt++) {
237
- try { fs.symlinkSync(targetPath, linkPath, linkType); return; }
238
- catch (e) {
239
- if ((e.code === 'EBUSY' || e.code === 'EPERM') && attempt < 4) {
240
- const end = Date.now() + 50 * (attempt + 1);
241
- while (Date.now() < end) {}
242
- continue;
243
- }
244
- if (e.code === 'EBUSY' || e.code === 'EPERM') {
245
- process.stderr.write(`[run-mcp] WARN: symlinkSync ${e.code} (${linkPath}) after retries — next boot retry\n`);
246
- return;
247
- }
248
- throw e;
249
- }
250
- }
251
- };
252
- let stat;
253
- try { stat = fs.lstatSync(linkPath); } catch { stat = null; }
254
- if (stat === null) { trySymlink(); return; }
255
- if (stat.isSymbolicLink()) {
256
- try {
257
- const current = fs.readlinkSync(linkPath);
258
- if (current === targetPath) return;
259
- } catch {}
260
- try { fs.unlinkSync(linkPath); }
261
- catch (e) {
262
- if (e.code === 'EBUSY' || e.code === 'EPERM') {
263
- process.stderr.write(`[run-mcp] WARN: unlinkSync ${e.code} (${linkPath}) — next boot retry\n`);
264
- return;
265
- }
266
- throw e;
267
- }
268
- trySymlink();
269
- return;
270
- }
271
- try {
272
- fs.rmSync(linkPath, { recursive: true, force: true });
273
- } catch (e) {
274
- if (e.code === 'EBUSY' || e.code === 'EPERM') {
275
- process.stderr.write(`[run-mcp] WARN: cache node_modules locked by live process (${e.code}), skipping symlink replacement — next boot retry\n`);
276
- return;
277
- }
278
- throw e;
279
- }
280
- trySymlink();
281
- }
282
-
283
- const require = createRequire(import.meta.url);
284
- const { resolvePluginData } = require('../lib/plugin-paths.cjs');
285
- const dataDir = resolvePluginData();
286
-
287
- fs.mkdirSync(dataDir, { recursive: true });
288
-
289
- // ── Supervisor self-cleanup on stdio loss ──────────────────────────────────
290
- // Lifecycle invariant: this supervisor is owned by exactly one Claude Code
291
- // MCP client (the process that spawned us). When that client tears down its
292
- // end of stdio — IDE quit, mcp server toggle, restart — our stdin closes.
293
- // Without a handler we'd linger forever (the comment near killChild
294
- // historically defended this on grounds of "transient stdin events", but
295
- // stdio close is not transient: it's the OS reporting EOF). Lingering
296
- // supervisors accumulate as zombies and confuse Claude Code's routing layer
297
- // on the next reconnect (it spawns a new supervisor; the old one stays
298
- // alive answering nothing).
299
- //
300
- // Multi-session safety: each Claude Code session spawns its own supervisor
301
- // with its own stdio. EOF on our stdin only signals OUR client going away.
302
- // Nothing here touches another session's supervisor — they have their own
303
- // pipe and their own EOF.
304
- //
305
- // Light diagnostic lock: record our PID in supervisor.lock for ps-style
306
- // visibility, but never kill a PID found there. Stale entries are harmless;
307
- // the stdin-EOF handler is the actual liveness mechanism.
308
- const SUPERVISOR_LOCK = join(dataDir, 'supervisor.lock');
309
- try {
310
- fs.writeFileSync(SUPERVISOR_LOCK, String(process.pid));
311
- const _releaseSupervisorLock = () => {
312
- try {
313
- const recorded = parseInt(fs.readFileSync(SUPERVISOR_LOCK, 'utf8').trim(), 10);
314
- // Only unlink if the lock still names us — another supervisor may have
315
- // overwritten it (multi-session, restart). Don't clobber theirs.
316
- if (recorded === process.pid) fs.unlinkSync(SUPERVISOR_LOCK);
317
- } catch {}
318
- };
319
- process.on('exit', _releaseSupervisorLock);
320
- // SIGINT/SIGTERM are handled cooperatively by killChild (registered
321
- // later in this file). killChild gracefully shuts down the child and
322
- // then calls process.exit(code), which fires the 'exit' listener
323
- // above to release the lock. Do NOT register a separate signal
324
- // handler here that calls process.exit(0) — it short-circuits the
325
- // killChild listener and orphans the MCP child. SIGHUP has no
326
- // killChild listener, so handle it terminally here.
327
- try {
328
- process.on('SIGHUP', () => process.exit(0));
329
- } catch { /* SIGHUP unsupported on Windows — ignore */ }
330
- } catch (e) {
331
- process.stderr.write(`[run-mcp] supervisor lock write failed: ${e?.message || e}\n`);
332
- }
333
-
334
- // Install runtime deps into a DEDICATED <dataDir>/.deps/ subdir — NEVER the
335
- // data root, which holds user data (mixdog-config.json, user-workflow.*,
336
- // roles/). Running `bun install` with cwd=dataDir would wipe that state.
337
- ensureRuntimeDeps({
338
- dataDir,
339
- pluginRoot,
340
- bunPath: process.env.BUN_EXEC_PATH,
341
- });
342
-
343
- const sharedNm = join(dataDir, '.deps', 'node_modules');
344
-
345
- ensureNmSymlink(pluginNm, sharedNm);
346
-
347
- const probe = join(pluginNm, '@modelcontextprotocol', 'sdk', 'package.json');
348
- if (!fs.existsSync(probe)) {
349
- // Probe failed: node_modules may be stale or install failed.
350
- // If any required dep is present the env may still be usable — warn and continue.
351
- // If ALL required deps are missing (fresh env + install failure), abort with guidance.
352
- const anyPresent = hasRequiredDeps(sharedNm) || hasRequiredDeps(pluginNm);
353
- if (anyPresent) {
354
- process.stderr.write(
355
- `[run-mcp] WARN: @modelcontextprotocol/sdk not found at expected path after install — ` +
356
- `continuing with available node_modules\n`
357
- );
358
- } else {
359
- process.stderr.write(
360
- `[run-mcp] ERROR: node_modules is incomplete and bun install did not succeed.\n` +
361
- ` Run \`bun install\` manually in ${pluginRoot} and retry.\n`
362
- );
363
- process.exit(1);
364
- }
365
- }
366
-
367
- const isWin = process.platform === 'win32';
368
-
369
- // Proxy supervisor: parses NDJSON JSON-RPC, caches initialize so child kills
370
- // are silent to the client; in-flight requests get a retry-able error on child death.
371
-
372
- const CRASH_WINDOW_MS = 10_000;
373
- const CRASH_MAX_RESTARTS = 5;
374
- const CRASH_BACKOFF_MS = 500;
375
- // Dev-sync respawn gate. dev-sync writes this lock (pid + ts) BEFORE killing
376
- // the daemon/child and removes it AFTER the marketplace→cache copy completes.
377
- // The respawn path below waits while the lock is present so the fresh child
378
- // loads post-sync code instead of racing the copy and getting SIGTERMed (the
379
- // "one wasted respawn" race). Hard mtime staleness cutoff so a crashed
380
- // dev-sync can never deadlock respawns. Poll cadence mirrors the kill-delay
381
- // granularity used elsewhere.
382
- const DEV_SYNC_LOCK = join(dataDir, 'dev-sync-cache-write.lock');
383
- const DEV_SYNC_GATE_POLL_MS = 100;
384
- const DEV_SYNC_GATE_STALE_MS = 30_000;
385
- // Hung-dev-sync escape hatch: even a live PID stops gating past this age, so a
386
- // wedged dev-sync can never deadlock respawns forever.
387
- const DEV_SYNC_GATE_HARD_CAP_MS = 5 * 60_000;
388
- // True while dev-sync is mid cache-copy. runSync() is synchronous (spawnSync,
389
- // bun install) so the lock mtime cannot heartbeat during long work — decide by
390
- // PID LIVENESS: a live lock owner keeps gating (up to the 5min hard cap),
391
- // regardless of mtime age. If the PID is dead or the lock is unparseable, fall
392
- // back to the 30s mtime cutoff so a crashed dev-sync still clears.
393
- function devSyncCacheWriteInProgress() {
394
- let st;
395
- try {
396
- st = fs.statSync(DEV_SYNC_LOCK);
397
- } catch {
398
- return false; // missing lock → not syncing
399
- }
400
- const mtimeFresh = (Date.now() - st.mtimeMs) <= DEV_SYNC_GATE_STALE_MS;
401
- let pid = null;
402
- let ts = null;
403
- try {
404
- const parsed = JSON.parse(fs.readFileSync(DEV_SYNC_LOCK, 'utf8'));
405
- pid = Number(parsed?.pid) || null;
406
- ts = Number(parsed?.ts) || null;
407
- } catch {
408
- return mtimeFresh; // unparseable lock → mtime cutoff fallback
409
- }
410
- if (!pid) return mtimeFresh;
411
- let alive;
412
- try {
413
- process.kill(pid, 0); // liveness probe (works on Windows too)
414
- alive = true;
415
- } catch (err) {
416
- alive = err && err.code === 'EPERM'; // ESRCH = dead; EPERM = alive (foreign owner)
417
- }
418
- // Dead owner = crashed dev-sync; its copy is never going to finish, so
419
- // unblock IMMEDIATELY rather than burning up to 30s of mtime cutoff. The
420
- // mtime fallback above stays only for locks with no readable pid.
421
- if (!alive) return false;
422
- // Live owner: keep gating unless the lock is older than the hard cap.
423
- const age = Date.now() - (ts ?? st.mtimeMs);
424
- return age <= DEV_SYNC_GATE_HARD_CAP_MS;
425
- }
426
- // child stderr ring-buffer cap. 16 KB carries the last progress lines plus
427
- // any final throw/abort stack without flooding supervisor.log on a runaway
428
- // error loop.
429
- const STDERR_TAIL_BYTES = 16 * 1024;
430
- // Inbound frame guardrail. JSON-RPC lines are newline-terminated; an
431
- // unterminated line that grows past this cap signals a runaway producer
432
- // (corrupted child stdout, hostile client stdin). 4 MB comfortably fits
433
- // any legitimate tool result while preventing unbounded memory growth.
434
- const MAX_LINE_BYTES = 4 * 1024 * 1024;
435
-
436
- let proc = null;
437
- let shuttingDown = false;
438
- let respawnTimer = null;
439
- const recentRestarts = [];
440
-
441
- // Handshake-readiness gate. A spawned child accepts stdin immediately but may
442
- // be stuck in module-init (singleton lock contention, malformed cache, etc.)
443
- // and never emit a response. Without this gate the supervisor forwarded
444
- // requests into a black hole and the MCP layer either timed out or hung
445
- // indefinitely. Invariant: forward only initialize-class traffic until the
446
- // child has produced ≥1 stdout line; reply retry-able to anything else.
447
- let childHasResponded = false;
448
- // Respawn-orphan guard. When a child is replaced (crash/dev-sync), the client
449
- // must re-fetch tools — but ONLY once the NEW child can answer tools/list.
450
- // Firing notifications/tools/list_changed before the child has responded races
451
- // the client's follow-up tools/list into the closed handshake gate, where it
452
- // gets a -32603 "retry" the client may never re-issue — leaving the session
453
- // with an empty tool list ("connected but no tools"). Deferred until
454
- // childHasResponded flips true post-respawn (see handleChildLine).
455
- let announceListChangedOnReady = false;
456
- let cachedInitRequest = null; // { id, params } from client's first initialize
457
- let cachedInitDone = false; // initialized notification observed from client
458
- // One-shot latch: flips true the instant the client's initialize response is
459
- // forwarded and never resets. It selects replayInitToChild's mode (real-id vs
460
- // swallow) and tells handleChildGone whether the pending initialize may be
461
- // flushed. Until it is true the client has NOT seen its initialize result, so
462
- // that request must survive a child death and be re-driven, not errored.
463
- let clientInitAnswered = false;
464
- let internalIdSeq = -1; // negative ids reserved for supervisor-internal requests
465
- const pendingFromClient = new Map(); // request id (from client) → { method }
466
- const pendingInternal = new Set(); // internal ids (init replay) — drop responses
467
- let stdinBuf = '';
468
- let stdoutBuf = '';
469
- let childStderrBuf = '';
470
- let currentChildPluginRoot = pluginRoot;
471
-
472
- // Supervisor diagnostic log. Distinct from mcp-debug.log (which is the
473
- // child's own log via server-main.mjs:LOG_FILE). Captures transport-level
474
- // events that previously had no audit trail: quarantined non-JSON lines,
475
- // write errors, backpressure drain pauses. First place to inspect when
476
- // "all tools hang" — supervisor stays alive even when the JSON-RPC stream
477
- // to the client is wedged.
478
- const SUPERVISOR_LOG = join(dataDir, 'supervisor.log');
479
- const SUPERVISOR_LOG_SCOPED = join(dataDir, `supervisor.${process.pid}.log`);
480
- const SUPERVISOR_CONTEXT = `lead=${process.pid} supervisor=${process.pid}`;
481
- function _rotateSupervisorLog(file) {
482
- try {
483
- const st = fs.statSync(file);
484
- if (st.size > 10 * 1024 * 1024) fs.renameSync(file, file + '.1');
485
- } catch {}
486
- }
487
- _rotateSupervisorLog(SUPERVISOR_LOG);
488
- _rotateSupervisorLog(SUPERVISOR_LOG_SCOPED);
489
- // R14: sanitize a single log field — strip ANSI escapes and escape control
490
- // chars (CR, lone C0/C1) so attacker-controlled bytes from the child's stderr
491
- // can't forge new log lines, hide payloads with \r overwrites, or smuggle
492
- // ANSI sequences into operator terminals tailing supervisor.log. Keep \t and
493
- // \n: callers either pass single-line msgs or pre-split on \n.
494
- function sanitizeLogField(text) {
495
- if (text == null) return '';
496
- let s = String(text);
497
- s = s.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, (m) => '\\x1b' + m.slice(1));
498
- s = s.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, (m) => '\\x1b' + m.slice(1));
499
- s = s.replace(/\x1b[@-_]/g, (m) => '\\x1b' + m.slice(1));
500
- s = s.replace(/\r/g, '\\r');
501
- s = s.replace(/[\x00-\x08\x0B-\x1F\x7F-\x9F]/g, (c) => {
502
- const code = c.charCodeAt(0);
503
- return '\\x' + code.toString(16).padStart(2, '0');
504
- });
505
- return s;
506
- }
507
- function supLog(msg) {
508
- const line = `[${new Date().toISOString()}] [${SUPERVISOR_CONTEXT} child=${proc?.pid ?? '-'}] ${sanitizeLogField(msg)}\n`;
509
- try { fs.appendFileSync(SUPERVISOR_LOG, line); } catch {}
510
- try { fs.appendFileSync(SUPERVISOR_LOG_SCOPED, line); } catch {}
511
- }
512
-
513
- function _envPositiveInt(name, fallback) {
514
- const n = Number(process.env[name]);
515
- return Number.isFinite(n) && n > 0 ? n : fallback;
516
- }
517
-
518
- const CLIENT_QUEUE_MAX_CHARS = _envPositiveInt('MIXDOG_SUPERVISOR_CLIENT_QUEUE_MAX_CHARS', 8 * 1024 * 1024);
519
- const CHILD_QUEUE_MAX_CHARS = _envPositiveInt('MIXDOG_SUPERVISOR_CHILD_QUEUE_MAX_CHARS', 4 * 1024 * 1024);
520
- const BACKPRESSURE_STALL_MS = _envPositiveInt('MIXDOG_SUPERVISOR_BACKPRESSURE_STALL_MS', 60_000);
521
-
522
- // Liveness watchdog. A client request can sit in pendingFromClient forever
523
- // when the child is ALIVE but the response path is wedged — a half-open daemon
524
- // pipe, or a dead-but-not-closed socket after an ungraceful multi-terminal
525
- // teardown. handleChildGone only fires on child PROCESS death and
526
- // flushPendingClientErrors only on supervisor death; neither covers
527
- // "alive but mute", so those requests never get answered → Claude Code's
528
- // silent hang ("tool call, no response"). We probe with an MCP `ping`, which
529
- // round-trips the SAME path as a tools/call: a healthy child's event loop
530
- // answers in well under a second even while a genuinely long tool runs async,
531
- // so this never aborts a slow-but-healthy call — only a dead path misses
532
- // repeated pings. After STALL_MAX_MISSES consecutive misses we SIGTERM the
533
- // child (NOT killChild, which tears down the whole supervisor) so
534
- // handleChildGone flushes pending with a retry error and respawns a fresh
535
- // thin client that re-attaches to the shared daemon.
536
- const STALL_PROBE_AFTER_MS = _envPositiveInt('MIXDOG_SUPERVISOR_STALL_PROBE_MS', 30_000);
537
- const PING_TIMEOUT_MS = _envPositiveInt('MIXDOG_SUPERVISOR_PING_TIMEOUT_MS', 10_000);
538
- const STALL_MAX_MISSES = _envPositiveInt('MIXDOG_SUPERVISOR_STALL_MAX_MISSES', 2);
539
- let _livenessPingId = null; // internal id of the in-flight liveness ping
540
- let _livenessPingSentAt = 0; // when it was written to the child
541
- let _livenessMisses = 0; // consecutive unanswered pings
542
- let _livenessQuietUntil = 0; // suppress re-probe until here after a good pong
543
-
544
- function _fatalSupervisor(reason) {
545
- const msg = `[supervisor-fatal] ${reason}`;
546
- try { process.stderr.write(msg + '\n'); } catch {}
547
- supLog(msg);
548
- flushPendingClientErrors(`fatal: ${reason}`);
549
- try { proc?.kill('SIGTERM'); } catch {}
550
- process.exit(1);
551
- }
552
-
553
- // Backpressure-aware writers. process.stdout.write / proc.stdin.write both
554
- // return false when the stream's internal buffer crosses its high-water
555
- // mark. Previously the return value was ignored, so the supervisor kept
556
- // piling writes onto an already-pressured pipe. On Windows pipes this can
557
- // stall the event loop when the peer (Claude Code or the child) falls
558
- // behind reading — manifesting as "every tool hangs for many minutes,
559
- // then suddenly all responses arrive" because the queue eventually drains
560
- // in one burst. Track drain state and queue further writes until the
561
- // 'drain' event fires, so we never push past a known backpressure
562
- // boundary. Order is preserved because the queue is a single string.
563
- let _clientQueue = '';
564
- let _clientDraining = false;
565
- let _clientDrainTimer = null;
566
- function writeToClient(line) {
567
- if (_clientQueue.length + line.length + 1 > CLIENT_QUEUE_MAX_CHARS) {
568
- _fatalSupervisor(`client queue overflow queued=${_clientQueue.length} incoming=${line.length} max=${CLIENT_QUEUE_MAX_CHARS}`);
569
- return;
570
- }
571
- _clientQueue += line + '\n';
572
- _flushClient();
573
- }
574
- function _flushClient() {
575
- if (_clientDraining || !_clientQueue) return;
576
- const chunk = _clientQueue;
577
- _clientQueue = '';
578
- let writeOk;
579
- try { writeOk = process.stdout.write(chunk); }
580
- catch (e) { supLog(`[client-write-error] ${e && e.message || e}`); return; }
581
- if (writeOk === false) {
582
- _clientDraining = true;
583
- const pausedAt = Date.now();
584
- _clientDrainTimer = setTimeout(() => {
585
- _fatalSupervisor(`client backpressure stuck after ${BACKPRESSURE_STALL_MS}ms queued=${_clientQueue.length}b`);
586
- }, BACKPRESSURE_STALL_MS);
587
- _clientDrainTimer.unref?.();
588
- process.stdout.once('drain', () => {
589
- _clientDraining = false;
590
- if (_clientDrainTimer) { clearTimeout(_clientDrainTimer); _clientDrainTimer = null; }
591
- const dur = Date.now() - pausedAt;
592
- // Only record meaningful stalls. Fast pause/drain cycles (<100ms)
593
- // happen normally under burst writes and aren't useful in the audit
594
- // trail; the sync appendFileSync per event was a non-trivial tax.
595
- if (dur >= 100) supLog(`[client-backpressure] paused/drained ${dur}ms queued=${_clientQueue.length}b`);
596
- _flushClient();
597
- });
598
- }
599
- }
600
-
601
- let _childQueue = '';
602
- let _childDraining = false;
603
- let _childDrainTimer = null;
604
- function writeToChild(line) {
605
- if (!proc || !proc.stdin || !proc.stdin.writable) return false;
606
- if (_childQueue.length + line.length + 1 > CHILD_QUEUE_MAX_CHARS) {
607
- supLog(`[child-queue-overflow] queued=${_childQueue.length} incoming=${line.length} max=${CHILD_QUEUE_MAX_CHARS}; killing child`);
608
- _childQueue = '';
609
- try { proc.kill('SIGTERM'); } catch {}
610
- return false;
611
- }
612
- _childQueue += line + '\n';
613
- _flushChild();
614
- return true;
615
- }
616
- function _flushChild() {
617
- if (_childDraining || !_childQueue) return;
618
- if (!proc || !proc.stdin || !proc.stdin.writable) {
619
- // Child gone — handleChildGone will surface retry errors to the
620
- // client. Drop queued writes here so a stale partial line cannot
621
- // concatenate with the new child's first stdin chunk after respawn.
622
- if (_childQueue) supLog(`[child-write-dropped] proc unavailable, dropped=${_childQueue.length}b`);
623
- _childQueue = '';
624
- return;
625
- }
626
- const chunk = _childQueue;
627
- _childQueue = '';
628
- let writeOk;
629
- try { writeOk = proc.stdin.write(chunk); }
630
- catch (e) { supLog(`[child-write-error] ${e && e.message || e}`); return; }
631
- if (writeOk === false) {
632
- _childDraining = true;
633
- const pausedAt = Date.now();
634
- _childDrainTimer = setTimeout(() => {
635
- supLog(`[child-backpressure-stuck] after ${BACKPRESSURE_STALL_MS}ms queued=${_childQueue.length}b; killing child`);
636
- try { proc?.kill('SIGTERM'); } catch {}
637
- }, BACKPRESSURE_STALL_MS);
638
- _childDrainTimer.unref?.();
639
- proc.stdin.once('drain', () => {
640
- _childDraining = false;
641
- if (_childDrainTimer) { clearTimeout(_childDrainTimer); _childDrainTimer = null; }
642
- const dur = Date.now() - pausedAt;
643
- if (dur >= 100) supLog(`[child-backpressure] paused/drained ${dur}ms queued=${_childQueue.length}b`);
644
- _flushChild();
645
- });
646
- }
647
- }
648
-
649
- function sendErrorToClient(id, code, message) {
650
- // Only skip for notifications (no id field at all). JSON-RPC allows id:null.
651
- if (id === undefined) return;
652
- writeToClient(JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } }));
653
- }
654
-
655
- // Invariant: if the SUPERVISOR itself goes down (uncaught exception, fatal
656
- // rejection, fatal backpressure) while client tool calls are still
657
- // outstanding, every outstanding request MUST receive a terminal JSON-RPC
658
- // error — never silence. Claude Code does NOT auto-reconnect a stdio MCP
659
- // server, so without this the client waits on a dead supervisor until a manual
660
- // /mcp reconnect (the "silent hang"). handleChildGone covers CHILD death; this
661
- // covers the supervisor's own death paths, which previously exited without
662
- // answering pending ids. Frames go DIRECT to stdout (bypassing the
663
- // backpressure queue) because the process is exiting and the async queue may
664
- // never drain; best-effort under try/catch since a broken pipe can't be
665
- // helped. Distinct "supervisor ..." tag (vs handleChildGone's "mcp child ...")
666
- // keeps the two death classes separable in logs.
667
- function flushPendingClientErrors(tag) {
668
- if (pendingFromClient.size === 0) return;
669
- const _n = pendingFromClient.size;
670
- for (const [id] of pendingFromClient) {
671
- if (id === undefined) continue;
672
- const frame = JSON.stringify({ jsonrpc: '2.0', id, error: { code: -32603, message: `[run-mcp] supervisor ${tag}; retry` } });
673
- try { process.stdout.write(frame + '\n'); } catch {}
674
- }
675
- pendingFromClient.clear();
676
- try { supLog(`[supervisor-flush-pending] tag=${tag} flushed=${_n}`); } catch {}
677
- }
678
-
679
- function replayInitToChild() {
680
- if (!cachedInitRequest) return;
681
- // Mode select by invariant — has the client's initialize been answered yet?
682
- // • Already answered (steady-state respawn): the client is fully
683
- // initialized and must NOT see a second result. Replay under an internal
684
- // negative id and swallow the response.
685
- // • Not yet (first-boot / handshake-time crash): replay under the client's
686
- // OWN id. Its request is still pending (preserved across the child-gone
687
- // flush in handleChildGone), so the new child's initialize response flows
688
- // back through the normal forward path — the client sees one clean result
689
- // instead of the -32603 that previously killed the connection and forced
690
- // a manual /mcp reconnect.
691
- if (clientInitAnswered) {
692
- const internalId = internalIdSeq--;
693
- pendingInternal.add(internalId);
694
- writeToChild(JSON.stringify({
695
- jsonrpc: '2.0',
696
- id: internalId,
697
- method: 'initialize',
698
- params: cachedInitRequest.params,
699
- }));
700
- } else {
701
- writeToChild(JSON.stringify({
702
- jsonrpc: '2.0',
703
- id: cachedInitRequest.id,
704
- method: 'initialize',
705
- params: cachedInitRequest.params,
706
- }));
707
- }
708
- if (cachedInitDone) {
709
- // Notification — no id, no response expected.
710
- writeToChild(JSON.stringify({
711
- jsonrpc: '2.0',
712
- method: 'notifications/initialized',
713
- }));
714
- }
715
- }
716
-
717
- function handleClientLine(line) {
718
- if (!line.trim()) return;
719
- // Fast-path: skip full JSON.parse on every tool call; only parse when the
720
- // supervisor needs the full payload (initialize/initialized or negative-id).
721
- const needsFullParse = _lineNeedsFullParse(line);
722
- let msg = needsFullParse ? null : _scanIdMethod(line);
723
- if (needsFullParse || msg === null) {
724
- try { msg = JSON.parse(line); } catch {
725
- // Non-JSON line from client stdin. Forwarding to the child would
726
- // corrupt its JSON-RPC parser and drop subsequent valid requests
727
- // until the parser realigns. Quarantine to supervisor.log and drop.
728
- supLog(`[client-stdin-noise] ${line.slice(0, 500)}`);
729
- return;
730
- }
731
- }
732
- if (msg && typeof msg === 'object') {
733
- const items = Array.isArray(msg) ? msg : [msg];
734
- for (const item of items) {
735
- if (!item || typeof item !== 'object') continue;
736
- if (item.method === 'initialize') {
737
- cachedInitRequest = { id: item.id, params: item.params };
738
- } else if (item.method === 'notifications/initialized' || item.method === 'initialized') {
739
- cachedInitDone = true;
740
- }
741
- if (item.id !== undefined && item.method) {
742
- pendingFromClient.set(item.id, { method: item.method, ts: Date.now() });
743
- }
744
- }
745
- }
746
- // Handshake gate: hold back non-init traffic until child proves liveness
747
- // with a response. Init/initialized are always forwarded since they are
748
- // the only payload that can advance the gate.
749
- if (!childHasResponded && msg && typeof msg === 'object') {
750
- const _isInit = (it) => it && typeof it === 'object'
751
- && (it.method === 'initialize'
752
- || it.method === 'notifications/initialized'
753
- || it.method === 'initialized');
754
- const items = Array.isArray(msg) ? msg : [msg];
755
- const allInit = items.every(_isInit);
756
- if (!allInit) {
757
- for (const item of items) {
758
- if (!_isInit(item) && item && item.id !== undefined) {
759
- sendErrorToClient(item.id, -32603, '[run-mcp] mcp child handshake pending; retry');
760
- pendingFromClient.delete(item.id);
761
- }
762
- }
763
- return;
764
- }
765
- }
766
- if (!writeToChild(line)) {
767
- // Child not yet ready (e.g. mid-respawn). For requests with an id, surface
768
- // a retry-able error; notifications are dropped (clients re-emit on
769
- // demand — list_changed will re-trigger).
770
- if (Array.isArray(msg)) {
771
- // Batch: send per-item errors and clean up pendingFromClient.
772
- for (const item of msg) {
773
- if (!item || typeof item !== 'object' || Array.isArray(item)) {
774
- // Non-object batch item — spec requires id:null -32600.
775
- sendErrorToClient(null, -32600, '[run-mcp] Invalid Request: batch item is not an object');
776
- continue;
777
- }
778
- const hasValidMethod = typeof item.method === 'string' && item.method.length > 0;
779
- if (item.id !== undefined || !hasValidMethod) {
780
- const id = item.id !== undefined ? item.id : null;
781
- const code = hasValidMethod ? -32603 : -32600;
782
- const message = hasValidMethod
783
- ? '[run-mcp] mcp child unavailable; retry'
784
- : '[run-mcp] Invalid Request: missing or invalid method';
785
- sendErrorToClient(id, code, message);
786
- pendingFromClient.delete(item.id);
787
- }
788
- }
789
- } else if (msg && msg.id !== undefined && msg.method) {
790
- sendErrorToClient(msg.id, -32603, '[run-mcp] mcp child unavailable; retry');
791
- pendingFromClient.delete(msg.id);
792
- }
793
- }
794
- }
795
-
796
- function handleChildLine(line) {
797
- if (!line.trim()) return;
798
- // Fast-path: only internal negative-id replies need full parse; everything
799
- // else is forwarded after a lightweight id scan.
800
- const scanned = _lineNeedsFullParse(line)
801
- ? (() => { try { return JSON.parse(line); } catch { return null; } })()
802
- : _scanIdMethod(line);
803
- if (scanned === null) {
804
- // Non-JSON noise must NOT flip childHasResponded — if it did, runtime
805
- // warnings during module init would prematurely open the handshake
806
- // gate and let regular tool requests reach a child that hadn't yet
807
- // replied to MCP `initialize` ("all tools hang" regression).
808
- } else if (!childHasResponded) {
809
- // Valid JSON response — child has completed module-init.
810
- childHasResponded = true;
811
- // A respawn deferred its tools/list_changed until the child could serve
812
- // tools/list. The gate is now open — announce so the client re-fetches
813
- // into a child that will actually answer (not the closed-gate -32603).
814
- // Gate on cachedInitDone: tools/list_changed is only valid AFTER the
815
- // client has completed MCP initialization (notifications/initialized).
816
- // A respawn that lands mid-handshake (before init completes) must NOT
817
- // emit it — doing so would drive the client to tools/list before init
818
- // finishes. In that pre-init case the client's own initialize→tools/list
819
- // flow already covers tool discovery, so dropping the announce is safe.
820
- if (announceListChangedOnReady) {
821
- announceListChangedOnReady = false;
822
- if (cachedInitDone) {
823
- writeToClient(JSON.stringify({
824
- jsonrpc: '2.0',
825
- method: 'notifications/tools/list_changed',
826
- }));
827
- }
828
- }
829
- }
830
- if (scanned === null) {
831
- // Non-JSON line from the child stdout. Worker stdout used to be
832
- // inherited (server-main.mjs stdio idx 1) so a bun runtime warning
833
- // or dependency stdout write could leak here and corrupt the
834
- // JSON-RPC frame stream the client sees. Worker stdout is now
835
- // /dev/null but server-main.mjs itself or future regressions could
836
- // still emit a non-JSON line — quarantine instead of forwarding so
837
- // the client parser never sees a malformed frame.
838
- supLog(`[child-stdout-noise] ${line.slice(0, 500)}`);
839
- return;
840
- }
841
- if (Array.isArray(scanned)) {
842
- const internalIds = new Set();
843
- for (const item of scanned) {
844
- if (item && item.id !== undefined) {
845
- if (pendingInternal.has(item.id)) { internalIds.add(item.id); pendingInternal.delete(item.id); _maybeResolveLivenessPong(item.id); }
846
- else {
847
- pendingFromClient.delete(item.id);
848
- // Same latch as the scalar path below — keep the invariant consistent
849
- // even if the client's initialize ever returns inside a batch, so a
850
- // later respawn swallows its replay instead of re-driving the real id.
851
- if (cachedInitRequest && item.id === cachedInitRequest.id) clientInitAnswered = true;
852
- }
853
- }
854
- }
855
- if (internalIds.size) {
856
- // A batch carrying an internal reply (init replay / liveness pong) must
857
- // not surface those negative ids to the client. The thin-client emits one
858
- // object per line so a mixed batch isn't expected — strip defensively and
859
- // forward only genuine client replies (swallow if none remain).
860
- const forward = scanned.filter((item) => !(item && item.id !== undefined && internalIds.has(item.id)));
861
- if (forward.length === 0) return;
862
- writeToClient(JSON.stringify(forward));
863
- return;
864
- }
865
- } else if (scanned.id !== undefined) {
866
- if (pendingInternal.has(scanned.id)) {
867
- // Supervisor-internal reply (initialize replay or liveness ping pong) —
868
- // swallow it: the client never issued this id. A liveness pong also
869
- // clears the stall probe so a slow-but-healthy call is not recycled.
870
- pendingInternal.delete(scanned.id);
871
- _maybeResolveLivenessPong(scanned.id);
872
- return;
873
- }
874
- if (!pendingFromClient.has(scanned.id)) {
875
- // Unknown id — neither an internal replay nor an outstanding client
876
- // request. Forwarding it would let a stale/rogue child line surface
877
- // as a spurious response. Drop with a supLog anchor instead.
878
- supLog(`[child-stdout-unknown-id] ${line.slice(0, 500)}`);
879
- return;
880
- }
881
- pendingFromClient.delete(scanned.id);
882
- // The client's initialize is satisfied the instant its response is
883
- // forwarded. Latch it so the next respawn swallows its replay (steady
884
- // state) and so handleChildGone is free to error this id on a later death.
885
- if (cachedInitRequest && scanned.id === cachedInitRequest.id) clientInitAnswered = true;
886
- }
887
- writeToClient(line);
888
- }
889
-
890
- function drainBuffer(buf, onLine) {
891
- let lastIndex = 0;
892
- let idx;
893
- while ((idx = buf.indexOf('\n', lastIndex)) !== -1) {
894
- const line = buf.slice(lastIndex, idx).replace(/\r$/, '');
895
- lastIndex = idx + 1;
896
- onLine(line);
897
- }
898
- return lastIndex === 0 ? buf : buf.slice(lastIndex);
899
- }
900
-
901
- // Shared child-gone cleanup. Every path that leaves `proc` non-runnable
902
- // (normal exit, crash, spawn-error) must invalidate pending requests,
903
- // reset stdoutBuf (stale partial line from the dead child must not
904
- // concatenate with the new child's first response), and schedule a
905
- // respawn. Invariant: `proc` is never left as an orphaned handle
906
- // without a recovery path.
907
- function handleChildGone(why) {
908
- if (proc === null) return;
909
- proc = null;
910
- if (shuttingDown) {
911
- process.exit(why.exitCode ?? 0);
912
- return;
913
- }
914
- // Exit-cause diagnostics: drain the child stderr ring buffer NOW (before
915
- // anything else can overwrite it) so post-mortem analysis has the last
916
- // bytes the dying child emitted — progress lines, native error, throw stack.
917
- // Cleared after capture so the next child boots with a fresh buffer.
918
- const _stderrTail = childStderrBuf;
919
- childStderrBuf = '';
920
- if (_stderrTail) {
921
- const _trimmed = _stderrTail.slice(-STDERR_TAIL_BYTES);
922
- // R14: prefix EACH physical line so an attacker can't forge a fake
923
- // supervisor.log entry by emitting bytes like "\n[timestamp] [...] evil"
924
- // from the child's stderr — every embedded line now starts with the
925
- // marker, and per-line sanitize strips ANSI / escapes lone CR + C0/C1.
926
- const _prefixed = _trimmed
927
- .split(/\r?\n/)
928
- .map((ln) => '[stderr-tail] ' + sanitizeLogField(ln))
929
- .join('\n');
930
- supLog(`[child-stderr-tail exitCode=${why.exitCode ?? 'n/a'} signal=${why.signal ?? 'n/a'} bytes=${_trimmed.length}]\n${_prefixed}`);
931
- } else {
932
- supLog(`[child-stderr-tail exitCode=${why.exitCode ?? 'n/a'} signal=${why.signal ?? 'n/a'} bytes=0] (empty)`);
933
- }
934
- const _pendingClientAtGone = pendingFromClient.size;
935
- const _pendingInternalAtGone = pendingInternal.size;
936
- // First-boot recovery invariant: the client's initialize must receive exactly
937
- // one success response, from whichever child completes the handshake. If it
938
- // has not been answered yet (clientInitAnswered=false), erroring it here makes
939
- // the client mark the MCP server failed — it never re-issues initialize on its
940
- // own, and the replay (internal id) never reaches it. That is the "startup
941
- // fails, /mcp fixes it" symptom. Keep that single id pending across the flush;
942
- // replayInitToChild re-drives it under the client's own id against the fresh
943
- // child so the success response flows straight back. shuttingDown never
944
- // preserves (the supervisor is exiting; nothing will replay).
945
- const _preserveInitId = (!shuttingDown && !clientInitAnswered && cachedInitRequest)
946
- ? cachedInitRequest.id
947
- : undefined;
948
- const _preservedInit = _preserveInitId !== undefined
949
- ? pendingFromClient.get(_preserveInitId)
950
- : undefined;
951
- for (const [id] of pendingFromClient) {
952
- if (id === _preserveInitId) continue;
953
- sendErrorToClient(id, -32603, `[run-mcp] mcp child ${why.tag}; retry`);
954
- }
955
- pendingFromClient.clear();
956
- if (_preserveInitId !== undefined && _preservedInit !== undefined) {
957
- pendingFromClient.set(_preserveInitId, _preservedInit);
958
- }
959
- pendingInternal.clear();
960
- // Fresh child = fresh response path; discard any in-flight liveness probe.
961
- _livenessPingId = null;
962
- _livenessMisses = 0;
963
- _livenessQuietUntil = 0;
964
- stdoutBuf = '';
965
- // Drop any stdin queue tied to the dead proc.stdin handle. The new
966
- // child gets a fresh writable stream from spawnChild; replaying queued
967
- // lines into it could break ordering (init replay must come first) or
968
- // leak requests the client already received an error for above.
969
- if (_childQueue) supLog(`[child-write-dropped] child gone, dropped=${_childQueue.length}b`);
970
- _childQueue = '';
971
- _childDraining = false;
972
- if (_childDrainTimer) { clearTimeout(_childDrainTimer); _childDrainTimer = null; }
973
-
974
- const now = Date.now();
975
- recentRestarts.push(now);
976
- while (recentRestarts.length && now - recentRestarts[0] > CRASH_WINDOW_MS) {
977
- recentRestarts.shift();
978
- }
979
- const crashLoop = recentRestarts.length > CRASH_MAX_RESTARTS;
980
- if (crashLoop) {
981
- // Don't tear down the supervisor — staying alive lets a follow-up
982
- // dev-sync replace the broken child without losing the MCP stdio
983
- // session. Surface the diagnostic and back off; new client requests
984
- // will get a retry-able error until a clean child boots.
985
- const _crashMsg = `[run-mcp] child crash loop (${recentRestarts.length} ${why.tag} in ${CRASH_WINDOW_MS}ms) — backing off ${CRASH_BACKOFF_MS * 4}ms; supervisor stays up`;
986
- process.stderr.write(_crashMsg + '\n');
987
- supLog(_crashMsg);
988
- } else {
989
- const _respawnMsg = `[run-mcp] ${why.log} — respawning (#${recentRestarts.length}); pendingClient=${_pendingClientAtGone} pendingInternal=${_pendingInternalAtGone} shuttingDown=${shuttingDown}`;
990
- process.stderr.write(_respawnMsg + '\n');
991
- supLog(_respawnMsg);
992
- }
993
- const delay = crashLoop ? CRASH_BACKOFF_MS * 4 : CRASH_BACKOFF_MS;
994
- // Gate the respawn behind the dev-sync cache-write lock. When dev-sync kills
995
- // the child to deploy fresh code it holds DEV_SYNC_LOCK across the
996
- // marketplace→cache copy; respawning before the copy finishes loads STALE
997
- // code that dev-sync then SIGTERMs (the wasted-respawn race this gate fixes).
998
- // While the lock is present (and not stale) we re-poll every
999
- // DEV_SYNC_GATE_POLL_MS instead of spawning; devSyncCacheWriteInProgress's
1000
- // mtime staleness cutoff guarantees a crashed dev-sync can never deadlock.
1001
- const doRespawn = () => {
1002
- if (shuttingDown) return;
1003
- if (devSyncCacheWriteInProgress()) {
1004
- respawnTimer = setTimeout(doRespawn, DEV_SYNC_GATE_POLL_MS);
1005
- return;
1006
- }
1007
- spawnChild();
1008
- if (cachedInitRequest) {
1009
- replayInitToChild();
1010
- } else if (crashLoop) {
1011
- process.stderr.write('[run-mcp] WARN: crash-loop respawn before initialize landed — skipping init replay\n');
1012
- }
1013
- // Defer the tools/list_changed announcement until the fresh child proves
1014
- // it can respond (handleChildLine flips childHasResponded → fires it).
1015
- // Announcing now would race the client's tools/list into the closed
1016
- // handshake gate and risk a permanently-empty tool list on reconnect.
1017
- announceListChangedOnReady = true;
1018
- };
1019
- respawnTimer = setTimeout(doRespawn, delay);
1020
- }
1021
-
1022
- function spawnChild() {
1023
- // Re-resolve pluginRoot on EVERY child spawn so dev-sync --restart
1024
- // (kills only child) picks up the new cache path. Boot-time pluginRoot
1025
- // is used for one-shot install / symlink / version warn; everything
1026
- // child-facing must come from the live manifest each spawn.
1027
- const childPluginRoot = _resolveLatestPluginRoot();
1028
- currentChildPluginRoot = childPluginRoot;
1029
- const childServerPath = join(childPluginRoot, 'server.mjs');
1030
- if (childPluginRoot !== pluginRoot) {
1031
- process.stderr.write(`[run-mcp] child spawn path refreshed: ${childPluginRoot} (boot=${pluginRoot})\n`);
1032
- }
1033
- // Reset the readiness gate every spawn — respawned child must re-prove
1034
- // it can respond before it inherits "ready" from the previous instance.
1035
- childHasResponded = false;
1036
- // Reset the stdout parse buffer too. A partial JSON line left in the
1037
- // buffer by the previous child must not concatenate with the new
1038
- // child's first response and corrupt JSON.parse.
1039
- stdoutBuf = '';
1040
- process.stderr.write(`[boot-time] tag=run-mcp-spawn-server tMs=${Date.now()}\n`);
1041
- proc = spawn(process.env.BUN_EXEC_PATH || process.execPath, [childServerPath], {
1042
- cwd: childPluginRoot,
1043
- // child stderr piped (not inherited) so supervisor can ring-buffer the
1044
- // tail and surface it on unexpected exit. handleChildGone reads the
1045
- // last STDERR_TAIL_BYTES on death to anchor exit-cause diagnostics
1046
- // (e.g. crash loop, native crash, unhandledRejection final line).
1047
- stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
1048
- // The supervisor itself can be console-less (hidden respawn via
1049
- // launch.mjs); without CREATE_NO_WINDOW each child respawn allocates a
1050
- // visible console that flashes on screen.
1051
- windowsHide: true,
1052
- env: {
1053
- ...process.env,
1054
- UV_THREADPOOL_SIZE: '2',
1055
- CLAUDE_PLUGIN_ROOT: childPluginRoot,
1056
- CLAUDE_PLUGIN_DATA: dataDir,
1057
- MIXDOG_SUPERVISOR_CONTROL_FD: '3',
1058
- // Identity passed to the child so server.mjs can write the supervisor
1059
- // advert (consumed by dev-sync's cleanupOldCacheVersions). Owning the
1060
- // write site in server.mjs keeps run-mcp.mjs change-free for future
1061
- // advert tweaks → no stdio-severing full-restart needed.
1062
- MIXDOG_SUPERVISOR_PID: String(process.pid),
1063
- MIXDOG_SUPERVISOR_CACHE_DIR: __localRoot,
1064
- // Stable routing id (see STABLE_TERMINAL_SESSION_ID): pins the daemon's
1065
- // bySession map to this terminal's LIVE connection across child
1066
- // reconnects so detached worker results are never delivered to a stale
1067
- // connection.
1068
- MIXDOG_SESSION_ID: STABLE_TERMINAL_SESSION_ID,
1069
- },
1070
- ...(isWin ? { windowsHide: true } : {}),
1071
- });
1072
-
1073
- if (isWin && proc.pid) {
1074
- try {
1075
- const ps = `$p = Get-Process -Id ${Number(proc.pid)} -ErrorAction SilentlyContinue; if ($p) { $p.PriorityClass = 'BelowNormal' }`;
1076
- const encoded = Buffer.from(ps, 'utf16le').toString('base64');
1077
- execSync(`powershell.exe -NoProfile -EncodedCommand ${encoded}`, {
1078
- stdio: 'ignore',
1079
- windowsHide: true,
1080
- timeout: 3000,
1081
- });
1082
- } catch {}
1083
- }
1084
-
1085
- proc.stdout.setEncoding('utf8');
1086
- proc.stdout.on('data', (chunk) => {
1087
- stdoutBuf += chunk;
1088
- stdoutBuf = drainBuffer(stdoutBuf, handleChildLine);
1089
- // Unbounded-line guard: if the residual unterminated tail exceeds the
1090
- // cap, the child is producing a frame too large to be legitimate (or
1091
- // never emitting a newline). Kill the child so handleChildGone can
1092
- // respawn it cleanly, and drop the corrupted buffer.
1093
- if (Buffer.byteLength(stdoutBuf, 'utf8') > MAX_LINE_BYTES) {
1094
- supLog(`[child-stdout-overflow] bytes=${Buffer.byteLength(stdoutBuf, 'utf8')} cap=${MAX_LINE_BYTES} — killing child`);
1095
- stdoutBuf = '';
1096
- try { proc?.kill(); } catch {}
1097
- }
1098
- });
1099
-
1100
- // child stderr ring buffer — capped at STDERR_TAIL_BYTES; older bytes
1101
- // are dropped from the head. Each chunk is mirrored to supervisor's own
1102
- // stderr so the user-visible inherit-equivalent passthrough is preserved.
1103
- childStderrBuf = '';
1104
- proc.stderr.setEncoding('utf8');
1105
- proc.stderr.on('data', (chunk) => {
1106
- try { process.stderr.write(chunk); } catch {}
1107
- childStderrBuf += chunk;
1108
- if (childStderrBuf.length > STDERR_TAIL_BYTES) {
1109
- childStderrBuf = childStderrBuf.slice(-STDERR_TAIL_BYTES);
1110
- }
1111
- });
1112
-
1113
- proc.on('exit', (code, signal) => {
1114
- handleChildGone({
1115
- tag: `exit code=${code}`,
1116
- log: `child exit code=${code} signal=${signal}`,
1117
- exitCode: code || 0,
1118
- signal,
1119
- });
1120
- });
1121
-
1122
- proc.on('error', (err) => {
1123
- handleChildGone({
1124
- tag: 'spawn failed',
1125
- log: `child spawn error: ${err && err.message}`,
1126
- exitCode: 1,
1127
- signal: null,
1128
- });
1129
- });
1130
-
1131
- // Async write failures to a dying child's stdin (EPIPE/EOF) surface as a
1132
- // stream 'error' event, NOT via the synchronous try/catch in _flushChild.
1133
- // Without this handler the supervisor crashes (uncaught) during dev-sync
1134
- // full-restart when a queued client line is flushed to the just-killed
1135
- // child. handleChildGone (exit/error) owns respawn; here we only swallow.
1136
- proc.stdin.on('error', (err) => {
1137
- supLog(`[child-stdin-error] ${err && err.message || err}`);
1138
- });
1139
- }
1140
-
1141
- function killChild(fast = false) {
1142
- supLog(`[supervisor-killChild] entered shuttingDown=${shuttingDown} fast=${fast}`);
1143
- if (shuttingDown) return;
1144
- shuttingDown = true;
1145
- clearTimeout(respawnTimer);
1146
- respawnTimer = null;
1147
- if (!proc) {
1148
- process.exit(0);
1149
- return;
1150
- }
1151
- // Graceful shutdown: write "shutdown\n" to fd-3 control pipe → child detects the command and
1152
- // shuts down gracefully. fd-3 is dedicated to lifecycle control and independent of MCP stdio
1153
- // transport — so transient stdin events from the MCP host can never trigger shutdown.
1154
- // fast=true (stdin-EOF/dev-sync path): replacement child is identical, no flush owed —
1155
- // shrink the two-children respawn window. Full timeout retained for SIGTERM.
1156
- const GRACEFUL_TIMEOUT_MS = fast ? 2000 : 10000;
1157
- const pid = proc.pid;
1158
- try {
1159
- const ctrlFd = proc.stdio && proc.stdio[3];
1160
- if (ctrlFd && typeof ctrlFd.end === 'function') {
1161
- ctrlFd.end('shutdown\n');
1162
- process.stderr.write(`[run-mcp] sent shutdown to control fd (pid=${pid}) — signalling graceful shutdown\n`);
1163
- } else {
1164
- process.stderr.write(`[run-mcp] WARN: control fd unavailable (pid=${pid}) — falling back to SIGTERM\n`);
1165
- try { proc.kill('SIGTERM'); } catch {}
1166
- }
1167
- } catch (e) {
1168
- process.stderr.write(`[run-mcp] control fd write failed (pid=${pid}): ${e && e.message}\n`);
1169
- }
1170
- // Also send SIGINT (Ctrl+C simulation) on non-Windows; on Windows skip (no reliable delivery)
1171
- if (!isWin) {
1172
- try { proc.kill('SIGINT'); } catch {}
1173
- }
1174
- // Wait up to GRACEFUL_TIMEOUT_MS for clean exit; force-kill only if timeout expires.
1175
- let exited = false;
1176
- const forceTimer = setTimeout(() => {
1177
- if (exited) return;
1178
- process.stderr.write(`[run-mcp] child did not exit within ${GRACEFUL_TIMEOUT_MS}ms — forcing kill (pid=${pid}) path=force\n`);
1179
- try {
1180
- if (isWin && pid) {
1181
- execSync(`taskkill /F /T /PID ${pid}`, { stdio: 'ignore', windowsHide: true, timeout: 5000 });
1182
- } else {
1183
- proc.kill('SIGKILL');
1184
- }
1185
- } catch {}
1186
- }, GRACEFUL_TIMEOUT_MS);
1187
- proc.once('exit', (code, signal) => {
1188
- exited = true;
1189
- clearTimeout(forceTimer);
1190
- process.stderr.write(`[run-mcp] child exited cleanly (pid=${pid} code=${code} signal=${signal}) path=graceful\n`);
1191
- process.exit(code || 0);
1192
- });
1193
- // process.exit is called by the proc 'exit' handler above once the child terminates.
1194
- }
1195
-
1196
- process.on('SIGTERM', killChild);
1197
- process.on('SIGINT', killChild);
1198
- // stdin EOF = our MCP client closed its end of the pipe (IDE quit, mcp
1199
- // server toggled off, Claude Code restart). The historical fear of
1200
- // "transient stdin events" doesn't apply: stdio close is a hard OS EOF,
1201
- // not a wobble. Letting the supervisor linger past EOF is exactly what
1202
- // produces zombie supervisors across reconnects — the new client spawns
1203
- // a fresh supervisor while the old one keeps running, holding no client,
1204
- // answering nothing. Hook EOF into the existing graceful-shutdown path so
1205
- // the child gets the proper shutdown signal too.
1206
- process.stdin.once('end', () => {
1207
- process.stderr.write('[run-mcp] stdin EOF — client disconnected; initiating graceful shutdown\n');
1208
- try { killChild(true); } catch { process.exit(0); }
1209
- });
1210
- process.stdin.once('close', () => {
1211
- process.stderr.write('[run-mcp] stdin closed — initiating graceful shutdown\n');
1212
- try { killChild(true); } catch { process.exit(0); }
1213
- });
1214
- let _HEARTBEAT_FILE = null;
1215
- process.on('exit', (code) => {
1216
- try { supLog(`[supervisor-exit] code=${code} shuttingDown=${shuttingDown}`); } catch {}
1217
- try { if (_HEARTBEAT_FILE) fs.unlinkSync(_HEARTBEAT_FILE); } catch {}
1218
- });
1219
- process.on('uncaughtException', (err) => {
1220
- try { supLog(`[supervisor-uncaught] ${err?.stack || err?.message || err}`); } catch {}
1221
- flushPendingClientErrors('uncaught exception');
1222
- try { killChild(); } catch {}
1223
- process.exit(1);
1224
- });
1225
- function _isSupervisorFatal(err) {
1226
- const code = err?.code;
1227
- return code === 'EPIPE' || code === 'EADDRINUSE' || code === 'ENOMEM';
1228
- }
1229
- process.on('unhandledRejection', (reason) => {
1230
- try { supLog(`[supervisor-unhandled-rejection] ${reason?.stack || reason?.message || reason}`); } catch {}
1231
- if (_isSupervisorFatal(reason)) {
1232
- try { supLog(`[supervisor-unhandled-rejection-fatal] code=${reason?.code} — exiting code=1`); } catch {}
1233
- flushPendingClientErrors('fatal rejection');
1234
- try { killChild(); } catch {}
1235
- process.exit(1);
1236
- }
1237
- });
1238
-
1239
- const _HEARTBEAT_MS = 5000;
1240
- const _HEARTBEAT_DIR = join(os.tmpdir(), 'mixdog');
1241
- _HEARTBEAT_FILE = join(_HEARTBEAT_DIR, `supervisor-heartbeat.${process.pid}.json`);
1242
- const _HEARTBEAT_INDEX_FILE = join(_HEARTBEAT_DIR, 'supervisor-heartbeats.json');
1243
- const _HEARTBEAT_INDEX_LOCK = `${_HEARTBEAT_INDEX_FILE}.lock`;
1244
- let _heartbeatWarnedMultiAt = 0;
1245
- function _heartbeatPidAlive(pid) {
1246
- if (!Number.isFinite(pid) || pid <= 0) return false;
1247
- if (pid === process.pid) return true;
1248
- try {
1249
- process.kill(pid, 0);
1250
- return true;
1251
- } catch (err) {
1252
- return err?.code === 'EPERM';
1253
- }
1254
- }
1255
- function _writeJsonAtomic(file, value) {
1256
- const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
1257
- fs.writeFileSync(tmp, JSON.stringify(value));
1258
- try { return renameWithRetrySync(tmp, file); }
1259
- catch (err) {
1260
- try { fs.unlinkSync(tmp); } catch {}
1261
- throw err;
1262
- }
1263
- }
1264
- function _readJsonSafe(file) {
1265
- try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
1266
- }
1267
- function _withHeartbeatIndexLock(fn) {
1268
- const deadline = Date.now() + 8000;
1269
- while (Date.now() < deadline) {
1270
- let fd = null;
1271
- try {
1272
- fd = fs.openSync(_HEARTBEAT_INDEX_LOCK, 'wx');
1273
- try { fs.writeSync(fd, `${process.pid} ${Date.now()}\n`); } catch {}
1274
- try { return fn(); }
1275
- finally {
1276
- try { if (fd !== null) fs.closeSync(fd); } catch {}
1277
- try { fs.unlinkSync(_HEARTBEAT_INDEX_LOCK); } catch {}
1278
- }
1279
- } catch (err) {
1280
- try { if (fd !== null) fs.closeSync(fd); } catch {}
1281
- if (!RENAME_RETRY_CODES.has(err?.code)) throw err;
1282
- try {
1283
- const st = fs.statSync(_HEARTBEAT_INDEX_LOCK);
1284
- if (Date.now() - st.mtimeMs > _HEARTBEAT_MS * 3) {
1285
- try { fs.unlinkSync(_HEARTBEAT_INDEX_LOCK); } catch {}
1286
- continue;
1287
- }
1288
- } catch {}
1289
- sleepSync(25 + Math.floor(Math.random() * 35));
1290
- }
1291
- }
1292
- return false;
1293
- }
1294
- function _writeSupervisorHeartbeat() {
1295
- try {
1296
- fs.mkdirSync(_HEARTBEAT_DIR, { recursive: true });
1297
- const now = Date.now();
1298
- const payload = {
1299
- pid: process.pid,
1300
- ownerLeadPid: process.pid,
1301
- childPid: proc?.pid ?? null,
1302
- pendingClientCount: pendingFromClient.size,
1303
- pendingInternalCount: pendingInternal.size,
1304
- pendingClientMethods: [...pendingFromClient.values()].map(v => v?.method || 'unknown').slice(0, 8),
1305
- ts: now,
1306
- cacheDir: __localRoot,
1307
- pluginRoot: currentChildPluginRoot,
1308
- dataDir,
1309
- ppid: process.ppid,
1310
- };
1311
- _writeJsonAtomic(_HEARTBEAT_FILE, payload);
1312
-
1313
- const supervisors = [];
1314
- for (const ent of fs.readdirSync(_HEARTBEAT_DIR, { withFileTypes: true })) {
1315
- if (!ent.isFile()) continue;
1316
- if (!/^supervisor-heartbeat\.\d+\.json$/.test(ent.name)) continue;
1317
- const file = join(_HEARTBEAT_DIR, ent.name);
1318
- const entry = _readJsonSafe(file);
1319
- const pid = Number(entry?.ownerLeadPid ?? entry?.pid);
1320
- const fresh = Number.isFinite(entry?.ts) && now - Number(entry.ts) <= _HEARTBEAT_MS * 6;
1321
- if (!_heartbeatPidAlive(pid) || !fresh) {
1322
- try { fs.unlinkSync(file); } catch {}
1323
- continue;
1324
- }
1325
- supervisors.push({ ...entry, pid, ownerLeadPid: pid });
1326
- }
1327
- supervisors.sort((a, b) => Number(a.pid) - Number(b.pid));
1328
- _withHeartbeatIndexLock(() => _writeJsonAtomic(_HEARTBEAT_INDEX_FILE, { updatedAt: now, supervisors }));
1329
- if (supervisors.length > 1 && now - _heartbeatWarnedMultiAt > 60000) {
1330
- _heartbeatWarnedMultiAt = now;
1331
- const pids = supervisors.map(s => s.pid).join(',');
1332
- const msg = `[heartbeat] multi-supervisor active count=${supervisors.length} pids=${pids}`;
1333
- supLog(msg);
1334
- try { process.stderr.write(`[run-mcp] ${msg}\n`); } catch {}
1335
- }
1336
- } catch (e) { supLog(`[heartbeat-error] ${e?.message || e}`); }
1337
- }
1338
- const _heartbeatTimer = setInterval(_writeSupervisorHeartbeat, _HEARTBEAT_MS);
1339
- _heartbeatTimer.unref?.();
1340
- _writeSupervisorHeartbeat();
1341
-
1342
- // Liveness pong handler. Returns true when `id` is the in-flight liveness
1343
- // ping's reply: the response path is proven healthy, so reset the miss
1344
- // counter and back off re-probing for one STALL_PROBE_AFTER_MS window (a
1345
- // genuinely long tool keeps the call pending but the path is fine).
1346
- function _maybeResolveLivenessPong(id) {
1347
- if (_livenessPingId === null || id !== _livenessPingId) return false;
1348
- _livenessPingId = null;
1349
- _livenessMisses = 0;
1350
- _livenessQuietUntil = Date.now() + STALL_PROBE_AFTER_MS;
1351
- return true;
1352
- }
1353
-
1354
- // Record one unanswered/failed liveness probe. On STALL_MAX_MISSES in a row the
1355
- // response path is dead: SIGTERM the child ONLY (not killChild, which exits the
1356
- // supervisor and severs the unrecoverable stdio bridge) so proc 'exit' →
1357
- // handleChildGone flushes pending with a retry error and respawns a fresh thin
1358
- // client. Both miss sources — an unanswered pong AND an unwritable child stdin
1359
- // while the process lingers alive — funnel here so neither can hang pending
1360
- // calls forever.
1361
- function _recordLivenessMiss(reason) {
1362
- _livenessMisses += 1;
1363
- supLog(`[liveness] ${reason} — miss ${_livenessMisses}/${STALL_MAX_MISSES} (pendingClient=${pendingFromClient.size})`);
1364
- if (_livenessMisses < STALL_MAX_MISSES) return;
1365
- const _n = pendingFromClient.size;
1366
- _livenessMisses = 0;
1367
- const m = `[liveness] response path dead (${STALL_MAX_MISSES} missed pings) — recycling child to unblock ${_n} pending client call(s)`;
1368
- supLog(m);
1369
- try { process.stderr.write(`[run-mcp] ${m}\n`); } catch {}
1370
- try { proc?.kill('SIGTERM'); } catch {}
1371
- }
1372
-
1373
- // Stall watchdog tick (shares the heartbeat cadence). Invariant: a live child
1374
- // answers an MCP `ping` promptly. When a client call has been pending past
1375
- // STALL_PROBE_AFTER_MS, send one ping down the same path; if it goes
1376
- // unanswered STALL_MAX_MISSES times in a row, the response path is dead —
1377
- // SIGTERM the child so handleChildGone flushes pending (retry error) and
1378
- // respawns. Never aborts a healthy call: async tools don't block the child's
1379
- // event loop, so the pong still round-trips while the tool runs.
1380
- function _livenessTick() {
1381
- if (shuttingDown) return;
1382
- const now = Date.now();
1383
- // Resolve an outstanding ping verdict first.
1384
- if (_livenessPingId !== null) {
1385
- if (now - _livenessPingSentAt < PING_TIMEOUT_MS) return; // still waiting
1386
- pendingInternal.delete(_livenessPingId);
1387
- _livenessPingId = null;
1388
- _recordLivenessMiss(`ping unanswered after ${now - _livenessPingSentAt}ms`);
1389
- return;
1390
- }
1391
- // Arm a probe only when a client call has genuinely waited too long.
1392
- if (pendingFromClient.size === 0) { _livenessMisses = 0; return; }
1393
- if (!proc || !childHasResponded || _childDraining || _clientDraining) return;
1394
- if (now < _livenessQuietUntil) return;
1395
- let oldest = Infinity;
1396
- for (const v of pendingFromClient.values()) {
1397
- const t = Number(v?.ts);
1398
- if (Number.isFinite(t) && t < oldest) oldest = t;
1399
- }
1400
- if (!Number.isFinite(oldest) || now - oldest < STALL_PROBE_AFTER_MS) return;
1401
- const id = internalIdSeq--;
1402
- pendingInternal.add(id);
1403
- _livenessPingId = id;
1404
- _livenessPingSentAt = now;
1405
- const ok = writeToChild(JSON.stringify({ jsonrpc: '2.0', id, method: 'ping' }));
1406
- if (ok) {
1407
- supLog(`[liveness] probing child — oldest pending client call ${now - oldest}ms (pendingClient=${pendingFromClient.size})`);
1408
- } else {
1409
- // Write path itself unusable (child stdin gone/non-writable while the
1410
- // process lingers): count it as a miss so repeated failures recycle.
1411
- pendingInternal.delete(id);
1412
- _livenessPingId = null;
1413
- _recordLivenessMiss('ping write rejected');
1414
- }
1415
- }
1416
- const _livenessTimer = setInterval(_livenessTick, _HEARTBEAT_MS);
1417
- _livenessTimer.unref?.();
1418
-
1419
- process.stdin.setEncoding('utf8');
1420
- process.stdin.on('data', (chunk) => {
1421
- stdinBuf += chunk;
1422
- stdinBuf = drainBuffer(stdinBuf, handleClientLine);
1423
- // Unbounded-line guard: a client never legitimately sends a single
1424
- // JSON-RPC frame larger than the cap. Drop the buffer (cannot kill the
1425
- // client) and surface an anchor in supervisor.log.
1426
- if (Buffer.byteLength(stdinBuf, 'utf8') > MAX_LINE_BYTES) {
1427
- supLog(`[client-stdin-overflow] bytes=${Buffer.byteLength(stdinBuf, 'utf8')} cap=${MAX_LINE_BYTES} — dropping buffer`);
1428
- stdinBuf = '';
1429
- }
1430
- });
1431
- spawnChild();
1432
-
1433
- // Parent (Claude Code) death watchdog — replaces the old stdin-EOF
1434
- // lifecycle signal that was prone to transient close during boot.
1435
- // process.kill(pid, 0) probes liveness without sending a signal.
1436
- const initialPpid = process.ppid;
1437
- if (initialPpid && initialPpid !== 1) {
1438
- const parentWatch = setInterval(() => {
1439
- try {
1440
- process.kill(initialPpid, 0);
1441
- } catch {
1442
- process.stderr.write(`[run-mcp] parent pid=${initialPpid} no longer alive — initiating graceful shutdown\n`);
1443
- clearInterval(parentWatch);
1444
- killChild();
1445
- }
1446
- }, 5000);
1447
- parentWatch.unref();
1448
- }