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