mixdog 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. package/.claude-plugin/marketplace.json +31 -0
  2. package/.claude-plugin/plugin.json +20 -0
  3. package/.gitattributes +34 -0
  4. package/.mcp.json +14 -0
  5. package/ARCHITECTURE.md +77 -0
  6. package/CHANGELOG.md +7 -0
  7. package/CONTRIBUTING.md +45 -0
  8. package/DATA-FLOW.md +79 -0
  9. package/LICENSE +21 -0
  10. package/README.md +389 -0
  11. package/SECURITY.md +138 -0
  12. package/UNINSTALL.md +112 -0
  13. package/agents/maintenance.md +5 -0
  14. package/agents/memory-classification.md +30 -0
  15. package/agents/scheduler-task.md +18 -0
  16. package/agents/webhook-handler.md +27 -0
  17. package/agents/worker.md +24 -0
  18. package/bin/bridge +133 -0
  19. package/bin/statusline-launcher.mjs +78 -0
  20. package/bin/statusline-lib.mjs +550 -0
  21. package/bin/statusline.mjs +607 -0
  22. package/bun.lock +802 -0
  23. package/commands/config.md +16 -0
  24. package/commands/doctor.md +13 -0
  25. package/commands/setup.md +17 -0
  26. package/defaults/cycle3-review-prompt.md +90 -0
  27. package/defaults/hidden-roles.json +65 -0
  28. package/defaults/memory-chunk-prompt.md +63 -0
  29. package/defaults/memory-promote-prompt.md +135 -0
  30. package/defaults/mixdog-config.template.json +27 -0
  31. package/defaults/user-workflow.json +8 -0
  32. package/defaults/user-workflow.md +12 -0
  33. package/hooks/hooks.json +73 -0
  34. package/hooks/lib/active-instance.cjs +77 -0
  35. package/hooks/lib/permission-evaluator.cjs +411 -0
  36. package/hooks/lib/permission-route.cjs +63 -0
  37. package/hooks/lib/permission-rules.cjs +170 -0
  38. package/hooks/lib/settings-loader.cjs +116 -0
  39. package/hooks/post-tool-use.cjs +84 -0
  40. package/hooks/pre-mcp-sandbox.cjs +158 -0
  41. package/hooks/pre-tool-subagent.cjs +253 -0
  42. package/hooks/session-start.cjs +1372 -0
  43. package/hooks/turn-timer.cjs +82 -0
  44. package/lib/claude-md-writer.cjs +386 -0
  45. package/lib/config-cjs.cjs +61 -0
  46. package/lib/hook-pipe-path.cjs +10 -0
  47. package/lib/keychain-cjs.cjs +263 -0
  48. package/lib/plugin-paths.cjs +61 -0
  49. package/lib/rules-builder.cjs +241 -0
  50. package/lib/text-utils.cjs +61 -0
  51. package/native/README.md +117 -0
  52. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  53. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  54. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  55. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  56. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  57. package/package.json +107 -0
  58. package/prompts/code-review.txt +16 -0
  59. package/prompts/security-audit.txt +17 -0
  60. package/rules/bridge/00-common.md +39 -0
  61. package/rules/bridge/20-skip-protocol.md +18 -0
  62. package/rules/bridge/30-explorer.md +33 -0
  63. package/rules/bridge/40-cycle1-agent.md +52 -0
  64. package/rules/bridge/41-cycle2-agent.md +62 -0
  65. package/rules/bridge/42-cycle3-agent.md +44 -0
  66. package/rules/lead/00-tool-lead.md +61 -0
  67. package/rules/lead/01-general.md +23 -0
  68. package/rules/lead/02-channels.md +49 -0
  69. package/rules/lead/03-team.md +27 -0
  70. package/rules/lead/04-workflow.md +20 -0
  71. package/rules/shared/00-language.md +14 -0
  72. package/rules/shared/01-tool.md +138 -0
  73. package/scripts/bootstrap.mjs +184 -0
  74. package/scripts/bridge-unify-smoke.mjs +308 -0
  75. package/scripts/build-runtime-linux.sh +348 -0
  76. package/scripts/build-runtime-macos.sh +217 -0
  77. package/scripts/build-runtime-windows.ps1 +242 -0
  78. package/scripts/builtin-utils-smoke.mjs +392 -0
  79. package/scripts/check-json.mjs +45 -0
  80. package/scripts/check-syntax-changed.mjs +102 -0
  81. package/scripts/check-syntax.mjs +58 -0
  82. package/scripts/code-graph-batch.test.mjs +33 -0
  83. package/scripts/config-preserve-smoke.mjs +180 -0
  84. package/scripts/doctor.mjs +484 -0
  85. package/scripts/edit-normalize-fuzz.mjs +130 -0
  86. package/scripts/edit-normalize-smoke.mjs +401 -0
  87. package/scripts/edit-operation-smoke.mjs +369 -0
  88. package/scripts/edit2-smoke.mjs +63 -0
  89. package/scripts/fuzzy-e2e.mjs +28 -0
  90. package/scripts/fuzzy-smoke.mjs +26 -0
  91. package/scripts/generate-runtime-manifest.mjs +166 -0
  92. package/scripts/guard-smoke.mjs +66 -0
  93. package/scripts/hidden-role-schema-smoke.mjs +162 -0
  94. package/scripts/hook-routing-smoke.mjs +29 -0
  95. package/scripts/inject-input.ps1 +204 -0
  96. package/scripts/io-complex-smoke.mjs +667 -0
  97. package/scripts/io-explore-bench.mjs +424 -0
  98. package/scripts/io-guardrails-smoke.mjs +205 -0
  99. package/scripts/io-mini-bench-baseline.json +11 -0
  100. package/scripts/io-mini-bench.mjs +216 -0
  101. package/scripts/io-route-harness.mjs +933 -0
  102. package/scripts/io-telemetry-report.mjs +691 -0
  103. package/scripts/mutation-bench.mjs +564 -0
  104. package/scripts/mutation-io-smoke.mjs +1081 -0
  105. package/scripts/native-patch-bridge-smoke.mjs +288 -0
  106. package/scripts/native-patch-smoke.mjs +304 -0
  107. package/scripts/patch-interior-context-smoke.mjs +49 -0
  108. package/scripts/patch-newline-utf8-smoke.mjs +157 -0
  109. package/scripts/perf-hook-smoke.mjs +71 -0
  110. package/scripts/permission-eval-smoke.mjs +426 -0
  111. package/scripts/prep-patch.mjs +53 -0
  112. package/scripts/prep-shim.mjs +96 -0
  113. package/scripts/provider-cache-smoke.mjs +687 -0
  114. package/scripts/report-runtime-health.mjs +132 -0
  115. package/scripts/run-mcp.mjs +1547 -0
  116. package/scripts/salvage-v4a-shatter.test.mjs +58 -0
  117. package/scripts/scoped-cache-io-smoke.mjs +103 -0
  118. package/scripts/shell-policy-round3-smoke.mjs +46 -0
  119. package/scripts/smoke-runtime-negative.ps1 +100 -0
  120. package/scripts/smoke-runtime-negative.sh +95 -0
  121. package/scripts/stall-policy-smoke.mjs +50 -0
  122. package/scripts/start-memory-worker.mjs +23 -0
  123. package/scripts/statusline-launcher-smoke.mjs +82 -0
  124. package/scripts/stress-atomic-write.mjs +1028 -0
  125. package/scripts/test-config-rmw-restore.mjs +122 -0
  126. package/scripts/test-fault-inject.mjs +164 -0
  127. package/scripts/test-large-file.mjs +174 -0
  128. package/scripts/tool-edge-smoke.mjs +209 -0
  129. package/scripts/uninstall.mjs +201 -0
  130. package/scripts/webhook-selfheal-smoke.mjs +29 -0
  131. package/scripts/write-overwrite-guard-smoke.mjs +56 -0
  132. package/server-main.mjs +3055 -0
  133. package/server.mjs +468 -0
  134. package/setup/config-merge.mjs +254 -0
  135. package/setup/install.mjs +120 -0
  136. package/setup/launch-core.mjs +507 -0
  137. package/setup/launch.mjs +101 -0
  138. package/setup/setup-server.mjs +3206 -0
  139. package/setup/setup.html +3693 -0
  140. package/skills/retro-skill-proposer/SKILL.md +92 -0
  141. package/skills/schedule-add/SKILL.md +77 -0
  142. package/skills/setup/SKILL.md +346 -0
  143. package/skills/webhook-add/SKILL.md +81 -0
  144. package/src/agent/bridge-stall-watchdog.mjs +337 -0
  145. package/src/agent/index.mjs +2138 -0
  146. package/src/agent/orchestrator/activity-bus.mjs +38 -0
  147. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
  148. package/src/agent/orchestrator/bridge-retry.mjs +220 -0
  149. package/src/agent/orchestrator/bridge-trace.mjs +583 -0
  150. package/src/agent/orchestrator/cache-mtime.mjs +58 -0
  151. package/src/agent/orchestrator/config.mjs +358 -0
  152. package/src/agent/orchestrator/context/collect.mjs +651 -0
  153. package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
  154. package/src/agent/orchestrator/drain-registry.mjs +50 -0
  155. package/src/agent/orchestrator/explore-validator.mjs +8 -0
  156. package/src/agent/orchestrator/internal-roles.mjs +118 -0
  157. package/src/agent/orchestrator/internal-tools.mjs +88 -0
  158. package/src/agent/orchestrator/jobs.mjs +116 -0
  159. package/src/agent/orchestrator/mcp/client.mjs +364 -0
  160. package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
  161. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
  162. package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
  163. package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
  164. package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
  165. package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
  166. package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
  167. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
  168. package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
  169. package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
  170. package/src/agent/orchestrator/providers/registry.mjs +192 -0
  171. package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
  172. package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
  173. package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
  174. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  175. package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
  176. package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
  177. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
  178. package/src/agent/orchestrator/session/cache/util.mjs +49 -0
  179. package/src/agent/orchestrator/session/loop.mjs +1478 -0
  180. package/src/agent/orchestrator/session/manager.mjs +1975 -0
  181. package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
  182. package/src/agent/orchestrator/session/result-classification.mjs +65 -0
  183. package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
  184. package/src/agent/orchestrator/session/store.mjs +624 -0
  185. package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
  186. package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  187. package/src/agent/orchestrator/session/trim.mjs +491 -0
  188. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
  189. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
  190. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
  191. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
  192. package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
  193. package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
  194. package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
  195. package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
  196. package/src/agent/orchestrator/stall-policy.mjs +195 -0
  197. package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
  198. package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
  199. package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
  200. package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
  201. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
  202. package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
  203. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
  204. package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
  205. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
  206. package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
  207. package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
  208. package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
  209. package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
  210. package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
  211. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
  212. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
  213. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
  214. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
  215. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
  216. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
  217. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
  218. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
  219. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
  220. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
  221. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
  222. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
  223. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
  224. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
  225. package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
  226. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
  227. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
  228. package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
  229. package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
  230. package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
  231. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
  232. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
  233. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
  234. package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
  235. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
  236. package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
  237. package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
  238. package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
  239. package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
  240. package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
  241. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  242. package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
  243. package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
  244. package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
  245. package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
  246. package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
  247. package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
  248. package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
  249. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
  250. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
  251. package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
  252. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  253. package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
  254. package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
  255. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
  256. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
  257. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
  258. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
  259. package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
  260. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
  261. package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
  262. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
  263. package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
  264. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
  265. package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
  266. package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
  267. package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
  268. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
  269. package/src/agent/orchestrator/tools/builtin.mjs +500 -0
  270. package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
  271. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  272. package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
  273. package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
  274. package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  275. package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
  276. package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
  277. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
  278. package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
  279. package/src/agent/orchestrator/tools/host-input.mjs +204 -0
  280. package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
  281. package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
  282. package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
  283. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
  284. package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
  285. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
  286. package/src/agent/orchestrator/tools/patch.mjs +2754 -0
  287. package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
  288. package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
  289. package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
  290. package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
  291. package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
  292. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
  293. package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
  294. package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
  295. package/src/agent/orchestrator/workflow-store.mjs +93 -0
  296. package/src/agent/tool-defs.mjs +103 -0
  297. package/src/channels/backends/discord.mjs +784 -0
  298. package/src/channels/data/voice-runtime-manifest.json +138 -0
  299. package/src/channels/index.mjs +3229 -0
  300. package/src/channels/lib/cli-worker-host.mjs +12 -0
  301. package/src/channels/lib/config-lock.mjs +13 -0
  302. package/src/channels/lib/config.mjs +292 -0
  303. package/src/channels/lib/drop-trace.mjs +71 -0
  304. package/src/channels/lib/event-pipeline.mjs +81 -0
  305. package/src/channels/lib/event-queue.mjs +345 -0
  306. package/src/channels/lib/executor.mjs +168 -0
  307. package/src/channels/lib/format.mjs +188 -0
  308. package/src/channels/lib/holidays.mjs +138 -0
  309. package/src/channels/lib/hook-pipe-server.mjs +802 -0
  310. package/src/channels/lib/interaction-workflows.mjs +184 -0
  311. package/src/channels/lib/memory-client.mjs +149 -0
  312. package/src/channels/lib/output-forwarder.mjs +765 -0
  313. package/src/channels/lib/runtime-paths.mjs +479 -0
  314. package/src/channels/lib/scheduler.mjs +723 -0
  315. package/src/channels/lib/session-control.mjs +36 -0
  316. package/src/channels/lib/session-discovery.mjs +103 -0
  317. package/src/channels/lib/settings.mjs +11 -0
  318. package/src/channels/lib/state-file.mjs +68 -0
  319. package/src/channels/lib/status-snapshot.mjs +219 -0
  320. package/src/channels/lib/tool-format.mjs +140 -0
  321. package/src/channels/lib/transcript-discovery.mjs +195 -0
  322. package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
  323. package/src/channels/lib/webhook.mjs +1179 -0
  324. package/src/channels/lib/whisper-server.mjs +477 -0
  325. package/src/channels/tool-defs.mjs +170 -0
  326. package/src/daemon/host.mjs +118 -0
  327. package/src/daemon/mcp-transport.mjs +47 -0
  328. package/src/daemon/session.mjs +100 -0
  329. package/src/daemon/thin-client.mjs +71 -0
  330. package/src/daemon/transport.mjs +163 -0
  331. package/src/memory/data/runtime-manifest.json +40 -0
  332. package/src/memory/index.mjs +3305 -0
  333. package/src/memory/lib/agent-ipc.mjs +93 -0
  334. package/src/memory/lib/bridge-trace-queries.mjs +120 -0
  335. package/src/memory/lib/core-memory-store.mjs +330 -0
  336. package/src/memory/lib/embedding-provider.mjs +269 -0
  337. package/src/memory/lib/embedding-worker.mjs +323 -0
  338. package/src/memory/lib/llm-worker-host.mjs +17 -0
  339. package/src/memory/lib/memory-cycle.mjs +11 -0
  340. package/src/memory/lib/memory-cycle1.mjs +641 -0
  341. package/src/memory/lib/memory-cycle2.mjs +1284 -0
  342. package/src/memory/lib/memory-cycle3.mjs +540 -0
  343. package/src/memory/lib/memory-embed.mjs +299 -0
  344. package/src/memory/lib/memory-extraction.mjs +5 -0
  345. package/src/memory/lib/memory-maintenance-store.mjs +32 -0
  346. package/src/memory/lib/memory-ops-policy.mjs +190 -0
  347. package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
  348. package/src/memory/lib/memory-recall-read-query.mjs +7 -0
  349. package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
  350. package/src/memory/lib/memory-recall-store.mjs +621 -0
  351. package/src/memory/lib/memory-retrievers.mjs +112 -0
  352. package/src/memory/lib/memory-score.mjs +71 -0
  353. package/src/memory/lib/memory-text-utils.mjs +58 -0
  354. package/src/memory/lib/memory.mjs +412 -0
  355. package/src/memory/lib/model-profile.mjs +85 -0
  356. package/src/memory/lib/pg/adapter.mjs +308 -0
  357. package/src/memory/lib/pg/process.mjs +360 -0
  358. package/src/memory/lib/pg/supervisor.mjs +396 -0
  359. package/src/memory/lib/project-id-resolver.mjs +86 -0
  360. package/src/memory/lib/runtime-fetcher.mjs +442 -0
  361. package/src/memory/lib/trace-store.mjs +728 -0
  362. package/src/memory/tool-defs.mjs +79 -0
  363. package/src/search/index.mjs +1173 -0
  364. package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
  365. package/src/search/lib/backends/exa.mjs +50 -0
  366. package/src/search/lib/backends/firecrawl.mjs +61 -0
  367. package/src/search/lib/backends/gemini-api.mjs +83 -0
  368. package/src/search/lib/backends/grok-oauth.mjs +86 -0
  369. package/src/search/lib/backends/index.mjs +150 -0
  370. package/src/search/lib/backends/openai-api.mjs +144 -0
  371. package/src/search/lib/backends/openai-oauth.mjs +98 -0
  372. package/src/search/lib/backends/openai-web-search.mjs +76 -0
  373. package/src/search/lib/backends/tavily.mjs +55 -0
  374. package/src/search/lib/backends/xai-api.mjs +113 -0
  375. package/src/search/lib/cache.mjs +131 -0
  376. package/src/search/lib/config.mjs +192 -0
  377. package/src/search/lib/formatter.mjs +115 -0
  378. package/src/search/lib/provider-usage.mjs +67 -0
  379. package/src/search/lib/providers.mjs +47 -0
  380. package/src/search/lib/search-intent.mjs +109 -0
  381. package/src/search/lib/setup-handler.mjs +261 -0
  382. package/src/search/lib/state.mjs +201 -0
  383. package/src/search/lib/web-tools.mjs +1207 -0
  384. package/src/search/tool-defs.mjs +83 -0
  385. package/src/setup/defender-exclusion.mjs +183 -0
  386. package/src/shared/abort-controller.mjs +15 -0
  387. package/src/shared/atomic-file.mjs +420 -0
  388. package/src/shared/config.mjs +350 -0
  389. package/src/shared/daemon-recycle.mjs +108 -0
  390. package/src/shared/disable-claude-builtins.mjs +88 -0
  391. package/src/shared/err-text.mjs +12 -0
  392. package/src/shared/llm/cost.mjs +66 -0
  393. package/src/shared/llm/http-agent.mjs +123 -0
  394. package/src/shared/llm/index.mjs +41 -0
  395. package/src/shared/llm/pid-cleanup.mjs +27 -0
  396. package/src/shared/llm/usage-log.mjs +47 -0
  397. package/src/shared/plugin-paths.mjs +58 -0
  398. package/src/shared/schedules-store.mjs +70 -0
  399. package/src/shared/seed.mjs +119 -0
  400. package/src/shared/user-cwd.mjs +213 -0
  401. package/src/shared/user-data-guard.mjs +238 -0
  402. package/src/status/aggregator.mjs +584 -0
  403. package/src/status/server.mjs +413 -0
  404. package/tools.json +1653 -0
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Internal hidden roles — plugin-managed, user-untouchable.
3
+ *
4
+ * Unlike user-workflow.json roles (which the user defines and edits freely),
5
+ * these roles are NEVER exposed to callers of the `bridge` MCP tool. They are
6
+ * invoked only by internal MCP handlers (explore / recall / search) and carry
7
+ * their own system prompt + tool-set policy.
8
+ *
9
+ * Lookup order (bridge-llm.resolvePresetName):
10
+ * 1. explicit preset arg
11
+ * 2. opts.preset
12
+ * 3. hidden-role registry (defaults/hidden-roles.json) ← plugin-internal
13
+ * 4. user-workflow.json[role] ← user-owned
14
+ *
15
+ * Role definitions live in defaults/hidden-roles.json. Editing that file is a
16
+ * plugin-code change; users cannot break the dispatch path by touching their
17
+ * workflow JSON.
18
+ *
19
+ * The preset names refer to entries seeded in mixdog-config.json (agent.presets)
20
+ * via DEFAULT_PRESETS (see config.mjs). If the user deletes the referenced preset
21
+ * from their config the hidden roles degrade gracefully — `resolvePresetName`
22
+ * returns a name, but session creation will fail with a clear "preset not
23
+ * found" error rather than silently mis-dispatching.
24
+ *
25
+ * Kind classification:
26
+ * - 'retrieval' : short-lived MCP-invoked hidden retrieval roles (explore).
27
+ * BP2 is cache-aligned with public `worker` (collect.mjs).
28
+ * - 'maintenance' : background-trigger hidden roles (memory cycle, recap, scheduler,
29
+ * webhook). Receive only their own self section in BP2.
30
+ *
31
+ * Permission classification:
32
+ * - 'read' : read-only — write/edit/bash blocked at loop.mjs runtime
33
+ * guard with the same error string public read-only roles see.
34
+ * - 'read-write' : full tool surface — used by hidden roles that legitimately
35
+ * mutate state (scheduler-task launches commands,
36
+ * webhook-handler persists payloads). bridge-llm honors this
37
+ * declaratively instead of forcing every hidden role to 'read'.
38
+ *
39
+ * Tool schema profile:
40
+ * - 'unified' : shared bridge tool schema for provider cache reuse.
41
+ * - 'llm-only' : no tools exposed; pure transform/classifier roles.
42
+ */
43
+
44
+ import { fileURLToPath } from 'url'
45
+ import { readFileSync } from 'fs'
46
+ import { join, dirname } from 'path'
47
+
48
+ // Load hidden-role definitions from defaults/hidden-roles.json at module
49
+ // initialisation. CLAUDE_PLUGIN_ROOT points to the plugin root directory
50
+ // (same pattern used by bridge-llm.mjs pluginRoot()). Falls back to a
51
+ // path derived from import.meta.url (3 levels up from src/agent/orchestrator/)
52
+ // so tests and standalone scripts work without the env var.
53
+ function _loadHiddenRoles() {
54
+ const root = process.env.CLAUDE_PLUGIN_ROOT
55
+ || join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
56
+ try {
57
+ const raw = JSON.parse(readFileSync(join(root, 'defaults', 'hidden-roles.json'), 'utf8'))
58
+ const map = Object.create(null)
59
+ for (const entry of (raw.roles || [])) {
60
+ if (entry && entry.name) map[entry.name] = Object.freeze({ ...entry })
61
+ }
62
+ return Object.freeze(map)
63
+ } catch (err) {
64
+ // Fail loudly — a missing or malformed hidden-roles.json breaks dispatch.
65
+ throw new Error(`[internal-roles] failed to load defaults/hidden-roles.json: ${err.message}`)
66
+ }
67
+ }
68
+
69
+ const _HIDDEN_ROLES = _loadHiddenRoles()
70
+
71
+ /**
72
+ * Return the hidden-role definition, or null if the name is not internal.
73
+ */
74
+ export function getHiddenRole(name) {
75
+ if (!name) return null
76
+ return _HIDDEN_ROLES[name] || null
77
+ }
78
+
79
+ /**
80
+ * Resolve permission stamped on a bridge session. Hidden roles declared
81
+ * `permission: 'read'` are read-locked — caller opts cannot upgrade them.
82
+ */
83
+ export function resolveBridgeSessionPermission(role, callerPermission) {
84
+ const hidden = getHiddenRole(role)
85
+ if (hidden && hidden.permission === 'read') return 'read'
86
+ if (callerPermission != null && callerPermission !== '') return callerPermission
87
+ if (hidden?.permission) return hidden.permission
88
+ return null
89
+ }
90
+
91
+ /**
92
+ * Boolean check — useful for branching inside bridge-llm / session-manager.
93
+ */
94
+ export function isHiddenRole(name) {
95
+ if (!name) return false
96
+ return Object.prototype.hasOwnProperty.call(_HIDDEN_ROLES, name)
97
+ }
98
+
99
+ /**
100
+ * List all hidden role names. Used by diagnostics / setup UI guards to ensure
101
+ * a user-defined role doesn't collide with an internal one.
102
+ */
103
+ export function listHiddenRoleNames() {
104
+ return Object.keys(_HIDDEN_ROLES)
105
+ }
106
+
107
+ /**
108
+ * List hidden role names matching a given kind ('retrieval' | 'maintenance').
109
+ * Consumed by collect.mjs to drive BP2 cache shard classification dynamically
110
+ * instead of hard-coding role-name sets.
111
+ */
112
+ export function listHiddenRolesByKind(kind) {
113
+ const out = []
114
+ for (const [name, def] of Object.entries(_HIDDEN_ROLES)) {
115
+ if (def.kind === kind) out.push(name)
116
+ }
117
+ return out
118
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Internal tool registry — in-process tools exposed to external LLMs via bridge.
3
+ *
4
+ * Populated by agent/index.mjs.handleToolCall when the server injects a
5
+ * context carrying { toolExecutor, internalTools }. The executor dispatches
6
+ * to the plugin's existing module router (worker IPC for memory/channels,
7
+ * in-process loadModule for search). No MCP loopback, no HTTP hop.
8
+ *
9
+ * Orchestrator modules (session/manager.mjs, session/loop.mjs) import from
10
+ * here instead of going through mcp/client.mjs for internal tools.
11
+ *
12
+ * Permission enforcement: bridge-worker tool calls (including internal tools)
13
+ * are gated by _checkWorkerPermission() in session/loop.mjs BEFORE reaching
14
+ * executeTool() → executeInternalTool(). No duplicate check is needed here;
15
+ * the loop is the single enforcement point for all dispatch paths.
16
+ */
17
+
18
+ let _executor = null;
19
+ let _tools = [];
20
+ let _names = new Set();
21
+
22
+ let _bootReady = false;
23
+ let _bootResolver = null;
24
+ const _bootPromise = new Promise((r) => { _bootResolver = r; });
25
+ export function markBootReady() { if (_bootReady) return; _bootReady = true; _bootResolver(); }
26
+ export async function awaitBootReady(timeoutMs = 2000) { if (_bootReady) return; await Promise.race([_bootPromise, new Promise((r) => setTimeout(r, timeoutMs))]); }
27
+
28
+ // Per-tool executor overrides. Populated by addInternalTools() for extra
29
+ // internal tools that bypass the main dispatch (tools.json + dispatchTool).
30
+ const _overrides = new Map();
31
+
32
+ export function setInternalToolsProvider({ executor, tools }) {
33
+ if (typeof executor !== 'function') throw new Error('internal-tools: executor must be a function');
34
+ _executor = executor;
35
+ const base = Array.isArray(tools) ? [...tools] : [];
36
+ // Re-registration (handleToolCall idempotent fallback) must preserve any
37
+ // override-backed tools previously registered via addInternalTools.
38
+ if (_overrides.size > 0) {
39
+ const baseNames = new Set(base.map(t => t?.name).filter(Boolean));
40
+ for (const [name] of _overrides) {
41
+ if (baseNames.has(name)) continue;
42
+ const existing = _tools.find(t => t?.name === name);
43
+ if (existing) base.push(existing);
44
+ }
45
+ }
46
+ _tools = base;
47
+ _names = new Set(_tools.map(t => t?.name).filter(Boolean));
48
+ }
49
+
50
+ /**
51
+ * Register additional tools that aren't declared in tools.json — each comes
52
+ * with its own executor.
53
+ *
54
+ * Re-registration is idempotent; later calls overwrite earlier entries with
55
+ * the same name.
56
+ */
57
+ export function getInternalTools() {
58
+ return _tools;
59
+ }
60
+
61
+ export function isInternalTool(name) {
62
+ return _names.has(name);
63
+ }
64
+
65
+ export async function executeInternalTool(name, args, callerCtx = {}) {
66
+ if (!_names.has(name)) throw new Error(`internal-tools: "${name}" is not registered`);
67
+ const override = _overrides.get(name);
68
+ if (override) {
69
+ const result = await override(args ?? {}, callerCtx);
70
+ return _normalize(result);
71
+ }
72
+ if (!_executor) throw new Error(`internal-tools: executor not initialized (tool=${name})`);
73
+ const result = await _executor(name, args ?? {}, callerCtx);
74
+ return _normalize(result);
75
+ }
76
+
77
+ // Mirror executeMcpTool's shape normalization so the session loop sees a
78
+ // plain string either way. Worker/module handlers return the MCP-shaped
79
+ // `{ content: [{type:'text', text}] }` envelope directly.
80
+ function _normalize(result) {
81
+ if (result && typeof result === 'object' && Array.isArray(result.content)) {
82
+ return result.content
83
+ .map((c) => (c?.type === 'text' ? c.text || '' : JSON.stringify(c)))
84
+ .join('\n');
85
+ }
86
+ if (typeof result === 'string') return result;
87
+ return JSON.stringify(result);
88
+ }
@@ -0,0 +1,116 @@
1
+ import { join } from 'path';
2
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
3
+ import { randomUUID } from 'crypto';
4
+ import { getPluginData } from './config.mjs';
5
+ import { writeJsonAtomicSync } from '../../shared/atomic-file.mjs';
6
+ function getJobsDir() {
7
+ const dir = join(getPluginData(), 'jobs');
8
+ if (!existsSync(dir))
9
+ mkdirSync(dir, { recursive: true });
10
+ return dir;
11
+ }
12
+ function stateFilePath() {
13
+ return join(getJobsDir(), 'state.json');
14
+ }
15
+ function jobFilePath(jobId) {
16
+ return join(getJobsDir(), `${jobId}.json`);
17
+ }
18
+ function _readStateFromDisk() {
19
+ const p = stateFilePath();
20
+ if (!existsSync(p))
21
+ return [];
22
+ try {
23
+ return JSON.parse(readFileSync(p, 'utf-8'));
24
+ }
25
+ catch {
26
+ return [];
27
+ }
28
+ }
29
+
30
+ // In-memory cache + 200ms debounce so a running→stage→done flurry writes once.
31
+ let _stateCache = null;
32
+ let _stateFlushTimer = null;
33
+ function _flushState() {
34
+ _stateFlushTimer = null;
35
+ if (_stateCache === null) return;
36
+ const p = stateFilePath();
37
+ try {
38
+ writeJsonAtomicSync(p, _stateCache, { lock: true });
39
+ } catch { /* best-effort */ }
40
+ }
41
+ function writeState(state) {
42
+ _stateCache = state;
43
+ if (_stateFlushTimer) return; // coalesce: latest _stateCache written on flush
44
+ _stateFlushTimer = setTimeout(_flushState, 200);
45
+ }
46
+
47
+ export function drainJobs() {
48
+ if (_stateFlushTimer === null || _stateCache === null) return;
49
+ clearTimeout(_stateFlushTimer);
50
+ _flushState();
51
+ }
52
+ // SIGTERM/SIGINT drain runs through drain-registry.mjs; bare 'exit' hook
53
+ // stays as an idempotent backup for cases where the registry already ran.
54
+ process.on('exit', drainJobs);
55
+
56
+ // Module-level mutex: serializes all state R/M/W in this process.
57
+ let _stateLock = Promise.resolve();
58
+
59
+ export function createJob(sessionId, prompt, context, { scopeKey, lane } = {}) {
60
+ const jobId = `job_${randomUUID()}`;
61
+ const now = new Date().toISOString();
62
+ const detail = {
63
+ jobId,
64
+ sessionId,
65
+ status: 'running',
66
+ scopeKey: scopeKey || null,
67
+ lane: lane || null,
68
+ request: { prompt, context },
69
+ startedAt: now,
70
+ };
71
+ // Write detail file first (unique path — no contention).
72
+ writeFileSync(jobFilePath(jobId), JSON.stringify(detail, null, 2));
73
+ // Mutate _stateCache synchronously so immediate same-process listJobs() sees the new job.
74
+ const newEntry = { jobId, sessionId, status: 'running', startedAt: now, lane: lane || null };
75
+ if (_stateCache !== null) {
76
+ _stateCache.push(newEntry);
77
+ } else {
78
+ _stateCache = [..._readStateFromDisk(), newEntry];
79
+ }
80
+ // Serialize disk persistence only (cache already updated above).
81
+ _stateLock = _stateLock.then(() => {
82
+ try {
83
+ writeState(_stateCache ?? []);
84
+ } catch { /* best-effort */ }
85
+ });
86
+ return jobId;
87
+ }
88
+ export function completeJob(jobId, result, failed = false) {
89
+ const now = new Date().toISOString();
90
+ const status = failed ? 'failed' : 'completed';
91
+ // Mutate _stateCache synchronously so immediate same-process reads see updated status.
92
+ if (_stateCache === null) _stateCache = _readStateFromDisk();
93
+ const syncEntry = _stateCache.find(j => j.jobId === jobId);
94
+ if (syncEntry) {
95
+ syncEntry.status = status;
96
+ syncEntry.finishedAt = now;
97
+ }
98
+ // Serialize disk persistence only (cache already updated above).
99
+ _stateLock = _stateLock.then(() => {
100
+ try {
101
+ writeState(_stateCache ?? []);
102
+ } catch { /* best-effort */ }
103
+ });
104
+ // Update detail file
105
+ const detailPath = jobFilePath(jobId);
106
+ if (existsSync(detailPath)) {
107
+ try {
108
+ const detail = JSON.parse(readFileSync(detailPath, 'utf-8'));
109
+ detail.status = status;
110
+ detail.result = result;
111
+ detail.finishedAt = now;
112
+ writeFileSync(detailPath, JSON.stringify(detail, null, 2));
113
+ }
114
+ catch { /* ignore corrupt file */ }
115
+ }
116
+ }
@@ -0,0 +1,364 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4
+ import { readFileSync, existsSync, readdirSync, mkdirSync, writeFileSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { tmpdir, homedir } from 'os';
7
+ import { randomUUID } from 'crypto';
8
+ import { DEFAULT_MARKETPLACE } from '../../../shared/plugin-paths.mjs';
9
+ import { smartReadTruncate } from '../tools/builtin/read-formatting.mjs';
10
+ // --- Types ---
11
+ /** Known auto-detect targets: port file path relative to tmpdir.
12
+ * Note: `mixdog` used to self-loopback via active-instance.json's
13
+ * httpPort, but that path went through channels' owner HTTP server which
14
+ * only exposes a subset of tools. The plugin's own tools are now injected
15
+ * in-process through agent's toolExecutor (see orchestrator/internal-tools),
16
+ * so this registry is for genuinely external port-based MCP targets only. */
17
+ const AUTO_DETECT_PORTS = {
18
+ 'mixdog-memory': { dir: 'mixdog', file: 'active-instance.json', portField: 'memory_port', endpoint: '/mcp' },
19
+ };
20
+ const DEFAULT_MCP_CALL_TIMEOUT_MS = 0;
21
+ // --- State ---
22
+ const servers = new Map();
23
+ // Memo for mcpToolHasField(name, field) — keyed by `${toolName}|${field}`.
24
+ // The lookup (regex parse + servers Map get + tools.find + schema property
25
+ // inspection) runs on every MCP tool invocation but its result only changes
26
+ // when the servers/tools registry is (re)built. Cleared at every registry
27
+ // mutation point (connectServer / disconnectAll) so a stale positive or
28
+ // negative can never survive a tools-list change.
29
+ const _mcpToolFieldMemo = new Map();
30
+ function _invalidateMcpToolFieldMemo() {
31
+ _mcpToolFieldMemo.clear();
32
+ }
33
+ // --- Public API ---
34
+ /**
35
+ * Connect to MCP servers defined in config.
36
+ * Supports stdio (child process) and http (Streamable HTTP) transports.
37
+ */
38
+ export async function connectMcpServers(config) {
39
+ const failures = [];
40
+ for (const [name, cfg] of Object.entries(config)) {
41
+ try {
42
+ await connectServer(name, cfg);
43
+ }
44
+ catch (err) {
45
+ const msg = err instanceof Error ? err.message : String(err);
46
+ process.stderr.write(`[mcp-client] Failed to connect "${name}": ${msg}\n`);
47
+ failures.push({ name, msg });
48
+ }
49
+ }
50
+ if (failures.length > 0) {
51
+ const detail = failures.map(f => `${f.name}: ${f.msg}`).join('; ');
52
+ throw new Error(`[mcp-client] ${failures.length} MCP server(s) failed to connect — ${detail}`);
53
+ }
54
+ }
55
+ /**
56
+ * Get all tool definitions from connected MCP servers.
57
+ * Tool names are prefixed: `mcp__{serverName}__{toolName}`
58
+ */
59
+ export function getMcpTools() {
60
+ const tools = [];
61
+ for (const server of servers.values()) {
62
+ tools.push(...server.tools);
63
+ }
64
+ return tools;
65
+ }
66
+ /**
67
+ * Execute an MCP tool call.
68
+ * Name format: `mcp__{serverName}__{toolName}`
69
+ */
70
+ export async function executeMcpTool(name, args) {
71
+ // Parse: mcp__{server}__{tool}
72
+ const match = name.match(/^mcp__(.+?)__(.+)$/);
73
+ if (!match)
74
+ throw new Error(`Not an MCP tool name: ${name}`);
75
+ const [, serverName, toolName] = match;
76
+ const server = servers.get(serverName);
77
+ if (!server)
78
+ throw new Error(`MCP server "${serverName}" not connected`);
79
+ let result;
80
+ try {
81
+ result = await _callToolWithTimeout(server, toolName, args);
82
+ } catch (firstErr) {
83
+ const firstMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
84
+ if (isMcpToolCallTimeoutError(firstErr)) {
85
+ process.stderr.write(`[mcp-client] Tool call timed out; skipping reconnect retry for "${serverName}/${toolName}".\n`);
86
+ throw firstErr;
87
+ }
88
+ process.stderr.write(`[mcp-client] Tool call failed, attempting reconnect...\n`);
89
+ await new Promise(r => setTimeout(r, 500));
90
+ try {
91
+ await server.client.close();
92
+ } catch { /* ignore close error */ }
93
+ try {
94
+ await connectServer(serverName, server.cfg);
95
+ } catch (reconnectErr) {
96
+ const reconnectMsg = reconnectErr instanceof Error ? reconnectErr.message : String(reconnectErr);
97
+ throw new Error(`Tool call failed: ${firstMsg}; reconnect also failed: ${reconnectMsg}`);
98
+ }
99
+ const retryServer = servers.get(serverName);
100
+ if (!retryServer) {
101
+ throw new Error(`Tool call failed: ${firstMsg}; reconnect succeeded but server "${serverName}" entry is missing from registry`);
102
+ }
103
+ try {
104
+ result = await _callToolWithTimeout(retryServer, toolName, args);
105
+ } catch (retryErr) {
106
+ const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
107
+ throw new Error(`Tool call failed: ${firstMsg}; retry after reconnect also failed: ${retryMsg}`);
108
+ }
109
+ }
110
+ const content = result.content;
111
+ let text;
112
+ if (Array.isArray(content)) {
113
+ text = content
114
+ .map((c) => (c.type === 'text' ? c.text || '' : JSON.stringify(c)))
115
+ .join('\n');
116
+ } else {
117
+ text = typeof content === 'string' ? content : JSON.stringify(content);
118
+ }
119
+ return capMcpOutput(text);
120
+ }
121
+
122
+ // MCP per-tool-call timeout. Disabled by default: external MCP tools can be
123
+ // long-running, and replaying an arbitrary tool after a timeout can duplicate
124
+ // side effects. Operators may opt in with MIXDOG_MCP_CALL_TIMEOUT_MS or a
125
+ // per-server timeoutMs/callTimeoutMs config value. On expiry we close the
126
+ // transport so the next dispatch reconnects fresh, but we do not retry the
127
+ // timed-out call automatically.
128
+ export function resolveMcpCallTimeoutMs(cfg = {}, env = process.env) {
129
+ const raw = cfg?.timeoutMs ?? cfg?.timeout_ms ?? cfg?.callTimeoutMs ?? cfg?.call_timeout_ms
130
+ ?? env?.MIXDOG_MCP_CALL_TIMEOUT_MS;
131
+ if (raw == null || raw === '' || raw === false) return DEFAULT_MCP_CALL_TIMEOUT_MS;
132
+ if (typeof raw === 'string' && /^(0|off|none|false)$/i.test(raw.trim())) return 0;
133
+ const parsed = Number(raw);
134
+ if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MCP_CALL_TIMEOUT_MS;
135
+ return Math.round(parsed);
136
+ }
137
+
138
+ export function isMcpToolCallTimeoutError(err) {
139
+ return err?.code === 'EMCPTOOLTIMEOUT';
140
+ }
141
+
142
+ async function _callToolWithTimeout(server, toolName, args) {
143
+ let timer;
144
+ const timeoutMs = resolveMcpCallTimeoutMs(server?.cfg);
145
+ if (!(timeoutMs > 0)) {
146
+ return server.client.callTool({ name: toolName, arguments: args });
147
+ }
148
+ const timeout = new Promise((_, rej) => {
149
+ timer = setTimeout(() => {
150
+ try { server.client.close().catch(() => {}); } catch { /* ignore */ }
151
+ const err = new Error(`MCP tool call timed out after ${timeoutMs}ms (server="${server.name}", tool="${toolName}")`);
152
+ err.code = 'EMCPTOOLTIMEOUT';
153
+ err.serverName = server.name;
154
+ err.toolName = toolName;
155
+ err.timeoutMs = timeoutMs;
156
+ rej(err);
157
+ }, timeoutMs);
158
+ if (timer.unref) timer.unref();
159
+ });
160
+ try {
161
+ return await Promise.race([
162
+ server.client.callTool({ name: toolName, arguments: args }),
163
+ timeout,
164
+ ]);
165
+ } finally {
166
+ if (timer) clearTimeout(timer);
167
+ }
168
+ }
169
+
170
+ function countTextLines(text) {
171
+ const s = String(text ?? '');
172
+ if (s.length === 0) return 0;
173
+ let lines = 1;
174
+ for (let i = 0; i < s.length; i += 1) {
175
+ if (s.charCodeAt(i) === 10) lines += 1;
176
+ }
177
+ return lines;
178
+ }
179
+
180
+ function capMcpOutput(content) {
181
+ const s = typeof content === 'string' ? content : String(content ?? '');
182
+ const bodyBytes = Buffer.byteLength(s, 'utf8');
183
+ const bodyLines = countTextLines(s);
184
+ const { text, truncated } = smartReadTruncate(s, bodyLines, bodyBytes);
185
+ if (!truncated) return text;
186
+ // Spill the full body to a tmp file so the caller can recover content
187
+ // elided by the head/tail cap (parity with the prior head-only spill).
188
+ let spillPath = null;
189
+ try {
190
+ const dir = join(tmpdir(), 'mixdog-mcp-output');
191
+ mkdirSync(dir, { recursive: true });
192
+ spillPath = join(dir, `mcp-${Date.now()}-${randomUUID().slice(0, 8)}.txt`);
193
+ writeFileSync(spillPath, s, 'utf-8');
194
+ } catch { /* spill best-effort */ }
195
+ const spillNote = spillPath
196
+ ? `\n\n... [full output spilled to ${spillPath}] ...`
197
+ : '';
198
+ return `${text}${spillNote}`;
199
+ }
200
+ /**
201
+ * Check if a tool name is an MCP tool.
202
+ */
203
+ export function isMcpTool(name) {
204
+ return name.startsWith('mcp__');
205
+ }
206
+ /**
207
+ * Check whether the inputSchema for an MCP tool declares the given top-level
208
+ * property. Used to decide if the orchestrator should auto-inject context
209
+ * (e.g. cwd) into the args before dispatch — schemas that don't declare the
210
+ * field would reject the unknown argument.
211
+ */
212
+ export function mcpToolHasField(name, field) {
213
+ const memoKey = `${name}|${field}`;
214
+ const memoized = _mcpToolFieldMemo.get(memoKey);
215
+ if (memoized !== undefined) return memoized;
216
+ const match = name.match(/^mcp__(.+?)__(.+)$/);
217
+ if (!match) { _mcpToolFieldMemo.set(memoKey, false); return false; }
218
+ const [, serverName] = match;
219
+ const server = servers.get(serverName);
220
+ if (!server) { _mcpToolFieldMemo.set(memoKey, false); return false; }
221
+ const tool = server.tools.find((t) => t.name === name);
222
+ if (!tool) { _mcpToolFieldMemo.set(memoKey, false); return false; }
223
+ const props = tool.inputSchema?.properties;
224
+ const result = Boolean(props && Object.prototype.hasOwnProperty.call(props, field));
225
+ _mcpToolFieldMemo.set(memoKey, result);
226
+ return result;
227
+ }
228
+ /**
229
+ * Disconnect all MCP servers.
230
+ */
231
+ export const drainMcpClients = disconnectAll;
232
+ export async function disconnectAll() {
233
+ for (const [name, server] of servers) {
234
+ try {
235
+ await server.client.close();
236
+ }
237
+ catch { /* ignore */ }
238
+ servers.delete(name);
239
+ }
240
+ _invalidateMcpToolFieldMemo();
241
+ }
242
+ /**
243
+ * Load MCP server configs from a JSON file.
244
+ * Supports both `{ mcpServers: { ... } }` and flat `{ name: { ... } }` format.
245
+ */
246
+
247
+ function mcpConfigCommon(c) {
248
+ const timeout = c?.timeoutMs ?? c?.timeout_ms ?? c?.callTimeoutMs ?? c?.call_timeout_ms;
249
+ if (timeout == null) return {};
250
+ return { timeoutMs: resolveMcpCallTimeoutMs({ timeoutMs: timeout }, {}) };
251
+ }
252
+ // --- Internal ---
253
+ function resolvePluginCacheScript(pluginName, script) {
254
+ const cacheBase = join(homedir(), '.claude', 'plugins', 'cache', DEFAULT_MARKETPLACE, pluginName);
255
+ if (existsSync(cacheBase)) {
256
+ const versions = readdirSync(cacheBase).filter(d => /^\d+\.\d+\.\d+/.test(d)).sort((a, b) => {
257
+ const pa = a.split('.').map(Number), pb = b.split('.').map(Number);
258
+ return (pa[0] - pb[0]) || (pa[1] - pb[1]) || (pa[2] - pb[2]);
259
+ });
260
+ for (let i = versions.length - 1; i >= 0; i--) {
261
+ const version = versions[i];
262
+ const dir = join(cacheBase, version);
263
+ const scriptPath = join(dir, script);
264
+ if (existsSync(scriptPath)) {
265
+ return { dir, scriptPath, source: `pluginCache:${pluginName}@${version}` };
266
+ }
267
+ }
268
+ }
269
+ const marketplaceDir = join(homedir(), '.claude', 'plugins', 'marketplaces', DEFAULT_MARKETPLACE, 'external_plugins', pluginName);
270
+ const marketplaceScript = join(marketplaceDir, script);
271
+ if (existsSync(marketplaceScript)) {
272
+ return { dir: marketplaceDir, scriptPath: marketplaceScript, source: `marketplace:${pluginName}` };
273
+ }
274
+ return null;
275
+ }
276
+
277
+ async function connectServer(name, cfg) {
278
+ const client = new Client({ name: `mixdog-agent/${name}`, version: '1.0.0' });
279
+ let transport;
280
+ // pluginCache: resolve latest cached plugin version as stdio transport
281
+ if (cfg.pluginCache) {
282
+ const script = cfg.script || 'scripts/run-mcp.mjs';
283
+ const resolved = resolvePluginCacheScript(cfg.pluginCache, script);
284
+ if (!resolved) throw new Error(`Script not found for pluginCache "${cfg.pluginCache}" (${script})`);
285
+ transport = new StdioClientTransport({
286
+ command: 'node',
287
+ args: [resolved.scriptPath],
288
+ cwd: resolved.dir,
289
+ env: {
290
+ ...process.env,
291
+ CLAUDE_PLUGIN_ROOT: resolved.dir,
292
+ CLAUDE_PLUGIN_DATA: join(homedir(), '.claude', 'plugins', 'data', `${cfg.pluginCache}-${DEFAULT_MARKETPLACE}`),
293
+ },
294
+ });
295
+ process.stderr.write(`[mcp-client] Connecting "${name}" via ${resolved.source}\n`);
296
+ }
297
+ // Auto-detect: read port from a running service's port file
298
+ else if (cfg.autoDetect) {
299
+ const spec = AUTO_DETECT_PORTS[cfg.autoDetect];
300
+ if (!spec)
301
+ throw new Error(`Unknown autoDetect target: "${cfg.autoDetect}"`);
302
+ const portFile = spec.dir === 'mixdog' && process.env.MIXDOG_RUNTIME_ROOT
303
+ ? join(process.env.MIXDOG_RUNTIME_ROOT, spec.file)
304
+ : join(tmpdir(), spec.dir, spec.file);
305
+ if (!existsSync(portFile)) {
306
+ throw new Error(`autoDetect server "${name}": port file missing (${portFile})`);
307
+ }
308
+ let port;
309
+ const raw = readFileSync(portFile, 'utf-8').trim();
310
+ if (spec.portField) {
311
+ try {
312
+ const json = JSON.parse(raw);
313
+ const v = json[spec.portField];
314
+ port = (typeof v === 'number' && Number.isFinite(v)) ? v : Number(v);
315
+ if (!Number.isFinite(port)) {
316
+ throw new Error(`autoDetect server "${name}": portField "${spec.portField}" is not numeric in ${portFile}`);
317
+ }
318
+ } catch (jsonErr) {
319
+ if (jsonErr instanceof Error && jsonErr.message.startsWith('autoDetect server')) throw jsonErr;
320
+ throw new Error(`autoDetect server "${name}": invalid JSON in port file ${portFile}`);
321
+ }
322
+ }
323
+ else {
324
+ port = parseInt(raw, 10);
325
+ }
326
+ if (!Number.isFinite(port) || port < 1 || port > 65535) {
327
+ throw new Error(`autoDetect server "${name}": invalid port value in ${portFile}`);
328
+ }
329
+ const url = `http://127.0.0.1:${port}${spec.endpoint}`;
330
+ transport = new StreamableHTTPClientTransport(new URL(url));
331
+ process.stderr.write(`[mcp-client] Connecting "${name}" via autoDetect HTTP: ${url}\n`);
332
+ }
333
+ else if (cfg.transport === 'http' && cfg.url) {
334
+ transport = new StreamableHTTPClientTransport(new URL(cfg.url));
335
+ process.stderr.write(`[mcp-client] Connecting "${name}" via HTTP: ${cfg.url}\n`);
336
+ }
337
+ else if (cfg.command) {
338
+ transport = new StdioClientTransport({
339
+ command: cfg.command,
340
+ args: cfg.args,
341
+ cwd: cfg.cwd,
342
+ env: { ...process.env, ...cfg.env },
343
+ });
344
+ }
345
+ else {
346
+ throw new Error(`Invalid config for "${name}": need autoDetect, url (http), or command (stdio)`);
347
+ }
348
+ await client.connect(transport);
349
+ const toolsResult = await client.listTools();
350
+ if (!toolsResult || !Array.isArray(toolsResult.tools)) {
351
+ throw new Error(`[mcp-client] ListTools returned invalid shape for "${name}": missing or non-array tools field`);
352
+ }
353
+ const tools = toolsResult.tools.map((t) => ({
354
+ name: `mcp__${name}__${t.name}`,
355
+ description: t.description || '',
356
+ inputSchema: (t.inputSchema || { type: 'object', properties: {} }),
357
+ ...(t.annotations && typeof t.annotations === 'object' ? { annotations: t.annotations } : {}),
358
+ }));
359
+ const mode = cfg.pluginCache ? `pluginCache(${cfg.pluginCache})` : cfg.autoDetect ? `autoDetect(${cfg.autoDetect})` : cfg.transport || 'stdio';
360
+ const toolNames = tools.map(t => t.name);
361
+ servers.set(name, { name, client, transport, tools, cfg });
362
+ _invalidateMcpToolFieldMemo();
363
+ process.stderr.write(`[mcp] connected: ${tools.length} tools — ${toolNames.join(', ')}\n`);
364
+ }