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,802 @@
1
+ // Hook IPC daemon — Windows named-pipe server consumed by mixdog-shim.exe.
2
+ //
3
+ // Replaces the per-spawn cold-start cost of `bun hooks/*.cjs` (≈86ms) with a
4
+ // single long-lived listener inside the channels worker. The shim is a tiny
5
+ // Rust .exe (~111KB, ~5-10ms cold) that connects, writes one JSON line, reads
6
+ // one JSON line back, and exits.
7
+ //
8
+ // Protocol (line-delimited JSON):
9
+ // client → server : <Claude-Code hook payload>\n
10
+ // server → client : <decision-json or "null">\n
11
+ //
12
+ // Each connection is handled independently. Long-running handlers (Discord
13
+ // permission polling, up to 2 minutes) do not block other connections.
14
+ //
15
+ // Failure model: dispatch errors emit "null" (fail-open). The shim itself
16
+ // also fails open when the pipe is unreachable.
17
+
18
+ import { createServer, createConnection } from 'node:net'
19
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync, readFileSync } from 'node:fs'
20
+ import { join, resolve as pathResolve } from 'node:path'
21
+ import { homedir, tmpdir } from 'node:os'
22
+ import { randomBytes } from 'node:crypto'
23
+ import { request as httpsRequest } from 'node:https'
24
+ import { createRequire } from 'node:module'
25
+
26
+ const moduleRequire = createRequire(import.meta.url)
27
+
28
+ // IPC transport path. Windows uses a named pipe (`\\.\pipe\…`); Unix uses a
29
+ // Unix domain socket under XDG_RUNTIME_DIR (or /tmp as fallback). Node's
30
+ // net.createServer().listen() accepts both transparently.
31
+ const PIPE_PATH = moduleRequire('../../../lib/hook-pipe-path.cjs')()
32
+
33
+ const RUNTIME_ROOT = join(tmpdir(), 'mixdog')
34
+ const SIGNAL_CONSUMER_MARKER = join(RUNTIME_ROOT, '.tool-exec-consumer')
35
+ const SUBAGENT_SIGNAL_CONSUMER_MARKER = join(RUNTIME_ROOT, '.tool-exec-subagent-consumer')
36
+ const SIGNAL_RE_GENERIC = /^tool-exec-\d+-[0-9a-f]+\.signal$/
37
+ const SIGNAL_RE_CAPTURE = /^tool-exec-(\d+)-[0-9a-f]+\.signal$/
38
+ const SWEEP_MARKER = join(RUNTIME_ROOT, '.tool-exec-sweep')
39
+ const SWEEP_INTERVAL_MS = 30_000
40
+ const SIGNAL_TTL_MS = 60_000
41
+ // Marketplace installs use two naming shapes for the MCP server name —
42
+ // `plugin_mixdog_mixdog__` (legacy / mixdog marketplace) and
43
+ // `plugin_mixdog_trib-plugin__` (trib-plugin marketplace). PreToolUse
44
+ // sandbox checks must recognise both or sandbox evaluation silently
45
+ // misses MCP tool names from the other install layout.
46
+ const MCP_PREFIXES = [
47
+ 'mcp__plugin_mixdog_mixdog__',
48
+ 'mcp__plugin_mixdog_trib-plugin__',
49
+ ]
50
+ const NATIVE_FILE_LOOKUP_TOOLS = new Set(['Read', 'Grep', 'Glob', 'Search', 'LS'])
51
+ function isMcpToolName(name) {
52
+ if (!name) return false
53
+ return MCP_PREFIXES.some(p => name.startsWith(p))
54
+ }
55
+
56
+ const POLL_INTERVAL_MS = 2000
57
+ const SUBAGENT_TIMEOUT_MS = 120_000
58
+ const DEFAULT_DISPATCH_TIMEOUT_MS = 15_000
59
+ const SESSION_START_MEMORY_DISPATCH_TIMEOUT_MS = 125_000
60
+ const SESSION_START_TRACE_ENABLED =
61
+ process.env.MIXDOG_DEBUG_SESSION_START === '1' ||
62
+ process.env.MIXDOG_DEBUG_SESSION_START === 'true'
63
+
64
+ let _started = false
65
+ let _server = null
66
+ let _subagentSignalConsumers = 0
67
+
68
+ function refreshSubagentSignalConsumerMarker() {
69
+ try {
70
+ if (_subagentSignalConsumers > 0) {
71
+ try { mkdirSync(RUNTIME_ROOT, { recursive: true }) } catch {}
72
+ writeFileSync(SUBAGENT_SIGNAL_CONSUMER_MARKER, String(Date.now()))
73
+ } else {
74
+ try { unlinkSync(SUBAGENT_SIGNAL_CONSUMER_MARKER) } catch {}
75
+ }
76
+ } catch {}
77
+ }
78
+
79
+ function formatError(err) {
80
+ const msg = (err && (err.stack || err.message)) || err
81
+ return String(msg || 'unknown').replace(/\s+/g, ' ').slice(0, 2000)
82
+ }
83
+
84
+ function traceSessionStart(message) {
85
+ if (!SESSION_START_TRACE_ENABLED) return
86
+ const line = `[${new Date().toISOString()}] [hook-pipe][session-start] ${message}\n`
87
+ try { process.stderr.write(line) } catch {}
88
+ try {
89
+ const dataDir = process.env.CLAUDE_PLUGIN_DATA ||
90
+ join(homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin')
91
+ mkdirSync(dataDir, { recursive: true })
92
+ appendFileSync(join(dataDir, 'session-start.log'), line)
93
+ } catch {}
94
+ }
95
+
96
+ function dispatchTimeoutMsForPayload(payload) {
97
+ const event = payload?.hook_event_name || payload?.hookEventName || ''
98
+ if (event !== 'SessionStart') return DEFAULT_DISPATCH_TIMEOUT_MS
99
+ const argsArr = payload?._args || []
100
+ const partArg = argsArr.find(a => a.startsWith('--part='))
101
+ const part = partArg ? partArg.slice('--part='.length) : ''
102
+ return (part === 'core' || part === 'recap')
103
+ ? SESSION_START_MEMORY_DISPATCH_TIMEOUT_MS
104
+ : DEFAULT_DISPATCH_TIMEOUT_MS
105
+ }
106
+
107
+ // ── post-tool-use handler ────────────────────────────────────────────────────
108
+
109
+ function sweepStaleSignalsThrottled(now = Date.now()) {
110
+ try {
111
+ let lastSweep = 0
112
+ try { lastSweep = statSync(SWEEP_MARKER).mtimeMs } catch {}
113
+ if (now - lastSweep < SWEEP_INTERVAL_MS) return
114
+ try { writeFileSync(SWEEP_MARKER, String(now)) } catch {}
115
+ const entries = readdirSync(RUNTIME_ROOT)
116
+ for (const name of entries) {
117
+ if (!SIGNAL_RE_GENERIC.test(name)) continue
118
+ const p = join(RUNTIME_ROOT, name)
119
+ try {
120
+ const st = statSync(p)
121
+ if (now - st.mtimeMs > SIGNAL_TTL_MS) unlinkSync(p)
122
+ } catch {}
123
+ }
124
+ } catch {}
125
+ }
126
+
127
+ function handlePostToolUse(payload) {
128
+ const toolName = payload?.tool_name || payload?.toolName || ''
129
+ if (!toolName) return null
130
+ if (_subagentSignalConsumers <= 0 &&
131
+ !existsSync(SIGNAL_CONSUMER_MARKER) &&
132
+ !existsSync(SUBAGENT_SIGNAL_CONSUMER_MARKER)) {
133
+ return null
134
+ }
135
+ const filePath = payload?.tool_input?.file_path || payload?.toolInput?.file_path || ''
136
+ const toolUseId = payload?.tool_use_id || payload?.toolUseId || ''
137
+
138
+ try { if (!existsSync(RUNTIME_ROOT)) mkdirSync(RUNTIME_ROOT, { recursive: true }) } catch {}
139
+ sweepStaleSignalsThrottled()
140
+
141
+ try {
142
+ const rand = randomBytes(4).toString('hex')
143
+ const signalFile = join(RUNTIME_ROOT, `tool-exec-${Date.now()}-${rand}.signal`)
144
+ writeFileSync(signalFile, JSON.stringify({ toolName, filePath, toolUseId, ts: Date.now() }))
145
+ } catch (err) {
146
+ process.stderr.write(`[hook-pipe] post-tool-use signal write failed: ${err?.message || err}\n`)
147
+ }
148
+ return null
149
+ }
150
+
151
+ // ── pre-mcp-sandbox handler ──────────────────────────────────────────────────
152
+
153
+ function handlePreMcpSandbox(payload) {
154
+ const toolName = payload?.tool_name || payload?.toolName || ''
155
+ if (!isMcpToolName(toolName)) return null
156
+
157
+ const toolInput = payload?.tool_input ?? payload?.toolInput ?? {}
158
+
159
+ let userCwdRaw = payload?.cwd || ''
160
+ if (!userCwdRaw) {
161
+ const dataDir = process.env.CLAUDE_PLUGIN_DATA || ''
162
+ if (dataDir) {
163
+ try { userCwdRaw = readFileSync(join(dataDir, 'user-cwd.txt'), 'utf8').trim() } catch {}
164
+ }
165
+ }
166
+ if (!userCwdRaw) userCwdRaw = process.cwd()
167
+
168
+ const userCwd = pathResolve(userCwdRaw)
169
+ const projectDir = payload?.projectDir || payload?.project_dir ||
170
+ process.env.CLAUDE_PROJECT_DIR || userCwd
171
+ const permissionMode = payload?.permissionMode || payload?.permission_mode || undefined
172
+
173
+ let settingsPerms, evaluatePermission
174
+ try {
175
+ const settingsLoader = moduleRequire('../../../hooks/lib/settings-loader.cjs')
176
+ settingsPerms = settingsLoader.loadPermissions(projectDir)
177
+ } catch (err) {
178
+ process.stderr.write(`[hook-pipe] pre-mcp-sandbox settings-loader unavailable: ${err?.message || err}\n`)
179
+ return null
180
+ }
181
+ const effectiveMode = permissionMode || settingsPerms.defaultMode
182
+
183
+ // Bypass fast-path: bypassPermissions/auto are full-allow by design; when no
184
+ // user deny rules are set, skip the evaluator (the owner opted into bypass).
185
+ if ((effectiveMode === 'bypassPermissions' || effectiveMode === 'auto') &&
186
+ (!settingsPerms.deny || settingsPerms.deny.length === 0)) {
187
+ return null
188
+ }
189
+
190
+ try {
191
+ const ev = moduleRequire('../../../hooks/lib/permission-evaluator.cjs')
192
+ evaluatePermission = ev.evaluatePermission
193
+ } catch (err) {
194
+ process.stderr.write(`[hook-pipe] pre-mcp-sandbox evaluator unavailable: ${err?.message || err}\n`)
195
+ return null
196
+ }
197
+
198
+ const evalResult = evaluatePermission({ toolName, toolInput, permissionMode, projectDir, userCwd, permissions: settingsPerms })
199
+ const { decision, reason, updatedInput } = evalResult
200
+
201
+ if (effectiveMode === 'bypassPermissions' || effectiveMode === 'auto') {
202
+ if (decision === 'deny') return makeDecision('deny', reason)
203
+ return null
204
+ }
205
+ if (decision === 'allow') return null
206
+ if (decision === 'ask') return makeDecision('ask', reason, updatedInput)
207
+ return makeDecision('deny', reason)
208
+ }
209
+
210
+ function handleNativeFileLookup(payload) {
211
+ const toolName = payload?.tool_name || payload?.toolName || ''
212
+ if (!NATIVE_FILE_LOOKUP_TOOLS.has(toolName)) return null
213
+ return makeDecision(
214
+ 'deny',
215
+ `Native ${toolName} is disabled by Mixdog. Use the Mixdog MCP read/grep/glob/list tools instead.`
216
+ )
217
+ }
218
+
219
+ function makeDecision(decision, reason, updatedInput) {
220
+ const out = {
221
+ hookSpecificOutput: {
222
+ hookEventName: 'PreToolUse',
223
+ permissionDecision: decision,
224
+ permissionDecisionReason: reason,
225
+ },
226
+ }
227
+ if (updatedInput !== undefined) out.hookSpecificOutput.updatedInput = updatedInput
228
+ return out
229
+ }
230
+
231
+ // ── pre-tool-subagent handler (Discord permission flow, async) ───────────────
232
+
233
+ function sanitize(value) {
234
+ return String(value).replace(/[^a-zA-Z0-9._-]/g, '_')
235
+ }
236
+
237
+ function readDiscordConfig() {
238
+ try {
239
+ const { readSection } = moduleRequire('../../../lib/config-cjs.cjs')
240
+ return readSection('channels')
241
+ } catch { return {} }
242
+ }
243
+
244
+ function isProtectedPath(filePath, cwd) {
245
+ if (!filePath) return false
246
+ const norm = pathResolve(filePath).replace(/\\/g, '/').toLowerCase()
247
+ const cwdNorm = (cwd || process.cwd()).replace(/\\/g, '/').toLowerCase()
248
+ const insideCwd = cwdNorm && (norm === cwdNorm || norm.startsWith(cwdNorm.endsWith('/') ? cwdNorm : cwdNorm + '/'))
249
+ return !insideCwd
250
+ }
251
+
252
+ function findAndClaimSignal(toolName, filePath, toolUseId, hookStartedAt) {
253
+ let entries
254
+ try { entries = readdirSync(RUNTIME_ROOT) } catch { return null }
255
+ for (const name of entries) {
256
+ const m = SIGNAL_RE_CAPTURE.exec(name)
257
+ if (!m) continue
258
+ const ts = Number(m[1])
259
+ if (!Number.isFinite(ts) || ts < hookStartedAt) continue
260
+ const p = join(RUNTIME_ROOT, name)
261
+ let raw
262
+ try { raw = readFileSync(p, 'utf8') } catch { continue }
263
+ let parsed
264
+ try { parsed = JSON.parse(raw) } catch { continue }
265
+ if (parsed?.toolName !== toolName) continue
266
+ if (parsed?.filePath !== filePath) continue
267
+ if (toolUseId && parsed?.toolUseId !== toolUseId) continue
268
+ try { unlinkSync(p) } catch {}
269
+ return p
270
+ }
271
+ return null
272
+ }
273
+
274
+ function discordApi(method, apiPath, token, body) {
275
+ return new Promise((resolve, reject) => {
276
+ const data = body ? JSON.stringify(body) : ''
277
+ const headers = { 'Authorization': 'Bot ' + token, 'Content-Type': 'application/json' }
278
+ if (data) headers['Content-Length'] = Buffer.byteLength(data)
279
+ const req = httpsRequest({ hostname: 'discord.com', path: apiPath, method, headers },
280
+ res => { let out = ''; res.on('data', d => { out += d }); res.on('end', () => { try { resolve(JSON.parse(out)) } catch { resolve({}) } }) })
281
+ req.setTimeout(10_000, () => req.destroy())
282
+ req.on('error', reject)
283
+ if (data) req.write(data)
284
+ req.end()
285
+ })
286
+ }
287
+
288
+ const sleep = (ms) => new Promise(r => setTimeout(r, ms))
289
+
290
+ async function handlePreToolSubagent(payload) {
291
+ if (process.env.MIXDOG_CHANNELS_NO_CONNECT) return null
292
+
293
+ const isSidechain = payload.isSidechain ?? payload.is_sidechain
294
+ const agentIdRaw = payload.agentId ?? payload.agent_id
295
+ const toolInput = payload.tool_input ?? payload.toolInput ?? {}
296
+
297
+ const isSubagent = isSidechain === true || Boolean(agentIdRaw)
298
+ if (!isSubagent) return null
299
+
300
+ const toolName = payload.tool_name || payload.toolName || ''
301
+ if (toolName !== 'Edit' && toolName !== 'Write' && toolName !== 'MultiEdit') return null
302
+
303
+ const mode = payload.permissionMode || payload.permission_mode || payload.mode
304
+ if (mode === 'bypassPermissions' || mode === 'acceptEdits' || mode === 'auto') return null
305
+
306
+ const hookCwd = payload.cwd || toolInput.cwd || process.cwd()
307
+ const filePath = toolInput.file_path || ''
308
+ const resolvedPath = filePath ? pathResolve(hookCwd, filePath) : ''
309
+ if (!isProtectedPath(resolvedPath, hookCwd)) return null
310
+
311
+ try { mkdirSync(RUNTIME_ROOT, { recursive: true }) } catch {}
312
+ const toolUseId = payload.tool_use_id ?? payload.toolUseId ?? ''
313
+
314
+ let routeMod
315
+ try {
316
+ routeMod = moduleRequire('../../../hooks/lib/permission-route.cjs')
317
+ } catch (err) {
318
+ process.stderr.write(`[hook-pipe] pre-tool-subagent permission-route unavailable: ${err?.message || err}\n`)
319
+ return null
320
+ }
321
+ const route = routeMod.shouldRoutePermissionToDiscord()
322
+ if (route.route !== 'discord') {
323
+ process.stderr.write(`[hook-pipe] pre-tool-subagent discord-route=off agent=${agentIdRaw || 'unknown'} tool=${toolName} reason=${route.reason || 'inactive'}\n`)
324
+ return null
325
+ }
326
+ process.stderr.write(`[hook-pipe] pre-tool-subagent discord-route=on agent=${agentIdRaw || 'unknown'} tool=${toolName}\n`)
327
+
328
+ let getDiscordToken
329
+ try {
330
+ ({ getDiscordToken } = moduleRequire('../../../lib/config-cjs.cjs'))
331
+ } catch (err) {
332
+ process.stderr.write(`[hook-pipe] pre-tool-subagent config-cjs unavailable: ${err?.message || err}\n`)
333
+ return null
334
+ }
335
+ const cfg = readDiscordConfig()
336
+ const token = getDiscordToken()
337
+ if (!token) return null
338
+ const agentId = agentIdRaw || 'unknown'
339
+ const mainCh = cfg && cfg.channelsConfig && cfg.channelsConfig.main
340
+ const channelId = mainCh && (typeof mainCh === 'string' ? null : mainCh.channelId)
341
+ if (!channelId) return null
342
+
343
+ const uuid = randomBytes(16).toString('hex')
344
+ const permissionInstanceIds = route.permissionInstanceIds || [route.permissionInstanceId].filter(Boolean)
345
+ if (!permissionInstanceIds.length) return null
346
+ const resultFiles = permissionInstanceIds.map((id) => join(RUNTIME_ROOT, `perm-${sanitize(id)}-${uuid}.result`))
347
+ _subagentSignalConsumers += 1
348
+ refreshSubagentSignalConsumerMarker()
349
+
350
+ const releaseSubagentConsumer = () => {
351
+ _subagentSignalConsumers = Math.max(0, _subagentSignalConsumers - 1)
352
+ refreshSubagentSignalConsumerMarker()
353
+ }
354
+
355
+ let detail = ''
356
+ if (toolName === 'Edit') {
357
+ detail = filePath + '\n' + (toolInput.old_string || '').substring(0, 200)
358
+ } else {
359
+ detail = filePath
360
+ }
361
+ const content = `🔐 **Sub-agent Permission**\nAgent: \`${agentId}\`\nTool: \`${toolName}\`\n\`\`\`\n${detail}\n\`\`\``
362
+
363
+ const body = {
364
+ content,
365
+ components: [{
366
+ type: 1,
367
+ components: [
368
+ { type: 2, style: 3, label: 'Allow', custom_id: 'perm-' + uuid + '-allow' },
369
+ { type: 2, style: 1, label: 'Session Allow', custom_id: 'perm-' + uuid + '-session' },
370
+ { type: 2, style: 4, label: 'Deny', custom_id: 'perm-' + uuid + '-deny' },
371
+ ]
372
+ }]
373
+ }
374
+
375
+ let msgResult
376
+ try {
377
+ msgResult = await discordApi('POST', '/api/v10/channels/' + channelId + '/messages', token, body)
378
+ } catch (err) {
379
+ process.stderr.write(`[hook-pipe] discord POST failed: ${err?.message || err}\n`)
380
+ releaseSubagentConsumer()
381
+ return null
382
+ }
383
+ const messageId = msgResult?.id
384
+ if (!messageId) {
385
+ releaseSubagentConsumer()
386
+ return null
387
+ }
388
+
389
+ const hookStartedAt = Date.now()
390
+
391
+ const patchAndFinish = async (suffix, decisionJson) => {
392
+ try {
393
+ await discordApi('PATCH', '/api/v10/channels/' + channelId + '/messages/' + messageId, token, {
394
+ content: content + suffix,
395
+ components: []
396
+ })
397
+ } catch {}
398
+ for (const resultFile of resultFiles) {
399
+ try { unlinkSync(resultFile) } catch {}
400
+ }
401
+ releaseSubagentConsumer()
402
+ return decisionJson || null
403
+ }
404
+
405
+ const startTime = Date.now()
406
+ while (Date.now() - startTime < SUBAGENT_TIMEOUT_MS) {
407
+ await sleep(POLL_INTERVAL_MS)
408
+
409
+ const resultFile = resultFiles.find((file) => existsSync(file))
410
+ if (resultFile) {
411
+ let decision
412
+ try {
413
+ const result = readFileSync(resultFile, 'utf8').trim()
414
+ if (result === 'allow' || result === 'session') {
415
+ decision = makeDecision('allow')
416
+ } else {
417
+ decision = makeDecision('deny', 'Denied from Discord')
418
+ }
419
+ } catch {
420
+ decision = makeDecision('deny', 'Failed to read result')
421
+ }
422
+ return patchAndFinish('', decision)
423
+ }
424
+
425
+ const claimed = findAndClaimSignal(toolName, filePath, toolUseId, hookStartedAt)
426
+ if (claimed) {
427
+ return patchAndFinish('\n\n↩️ Resolved from terminal.', null)
428
+ }
429
+ }
430
+
431
+ return patchAndFinish(
432
+ '\n\n⚠️ Auto-denied due to timeout.',
433
+ makeDecision('deny', 'Timeout')
434
+ )
435
+ }
436
+
437
+ // ── statusline handler (via dynamic ESM import) ──────────────────────────────
438
+
439
+ let _statusLineMod = null
440
+ let _statusLineLoadPromise = null
441
+
442
+ async function ensureStatusLineMod() {
443
+ if (_statusLineMod) return _statusLineMod
444
+ if (_statusLineLoadPromise) return _statusLineLoadPromise
445
+ _statusLineLoadPromise = import('../../../bin/statusline-lib.mjs')
446
+ .then(mod => { _statusLineMod = mod; return mod })
447
+ .catch(err => {
448
+ process.stderr.write(`[hook-pipe] statusline-lib import failed: ${err?.message || err}\n`)
449
+ _statusLineLoadPromise = null
450
+ return null
451
+ })
452
+ return _statusLineLoadPromise
453
+ }
454
+
455
+ async function handleStatusLine(payload) {
456
+ const mod = await ensureStatusLineMod()
457
+ if (!mod || typeof mod.renderStatusLine !== 'function') return null
458
+ try {
459
+ return await mod.renderStatusLine(JSON.stringify(payload || {}))
460
+ } catch (err) {
461
+ process.stderr.write(`[hook-pipe] statusline render failed: ${err?.message || err}\n`)
462
+ return null
463
+ }
464
+ }
465
+
466
+ // ── SessionStart: rules/core/recap handlers (via require'd cjs) ──────────────
467
+ //
468
+ // session-start.cjs accesses fd 0 at the top level — we gate that behind
469
+ // MIXDOG_SKIP_TOP_STDIN so it doesn't consume the daemon's MCP stdio pipe.
470
+ // Each SessionStart slot gets a fresh CJS module instance. That keeps the
471
+ // module-globals (_event, PART, _emitSink) isolated, so rules/core/recap can
472
+ // run concurrently without a daemon-wide lock.
473
+ function loadSessionStartMod() {
474
+ const prev = process.env.MIXDOG_SKIP_TOP_STDIN
475
+ process.env.MIXDOG_SKIP_TOP_STDIN = '1'
476
+ const moduleId = moduleRequire.resolve('../../../hooks/session-start.cjs')
477
+ delete moduleRequire.cache[moduleId]
478
+ traceSessionStart('fresh require start path=../../../hooks/session-start.cjs')
479
+ try {
480
+ const mod = moduleRequire(moduleId)
481
+ delete moduleRequire.cache[moduleId]
482
+ traceSessionStart(`fresh require ok exports=${Object.keys(mod || {}).join(',')}`)
483
+ return mod
484
+ } catch (err) {
485
+ process.stderr.write(`[hook-pipe] session-start.cjs require failed: ${err?.message || err}\n`)
486
+ traceSessionStart(`require failed err=${formatError(err)}`)
487
+ return null
488
+ } finally {
489
+ if (prev === undefined) delete process.env.MIXDOG_SKIP_TOP_STDIN
490
+ else process.env.MIXDOG_SKIP_TOP_STDIN = prev
491
+ }
492
+ }
493
+
494
+ async function handleSessionStartPart(args, payload) {
495
+ if (payload?.isSidechain || payload?.is_sidechain) {
496
+ traceSessionStart(`skip reason=sidechain source=${payload?.source || ''}`)
497
+ return null
498
+ }
499
+ if (payload?.agentId || payload?.agent_id) {
500
+ traceSessionStart(`skip reason=agent source=${payload?.source || ''} agent=${payload?.agentId || payload?.agent_id || ''}`)
501
+ return null
502
+ }
503
+ if (payload?.kind && payload.kind !== 'interactive') {
504
+ traceSessionStart(`skip reason=kind source=${payload?.source || ''} kind=${payload.kind}`)
505
+ return null
506
+ }
507
+
508
+ const partArg = (args || []).find(a => a.startsWith('--part='))
509
+ const part = partArg ? partArg.slice('--part='.length) : null
510
+ if (!part || (part !== 'rules' && part !== 'core' && part !== 'recap')) {
511
+ traceSessionStart(`skip reason=invalid-part source=${payload?.source || ''} args=${JSON.stringify(args || [])}`)
512
+ return null
513
+ }
514
+
515
+ const mod = loadSessionStartMod()
516
+ if (!mod) {
517
+ traceSessionStart(`skip reason=require-null part=${part} source=${payload?.source || ''}`)
518
+ return null
519
+ }
520
+
521
+ let buf = ''
522
+ let failed = false
523
+ const t0 = Date.now()
524
+ try {
525
+ traceSessionStart(
526
+ `run start part=${part} source=${payload?.source || ''} cwd=${payload?.cwd || ''} ` +
527
+ `sessionId=${payload?.session_id || payload?.sessionId || ''}`
528
+ )
529
+ try { mod.setEvent(payload || {}) } catch (err) {
530
+ failed = true
531
+ traceSessionStart(`setEvent failed part=${part} err=${formatError(err)}`)
532
+ }
533
+ try {
534
+ if (typeof mod.setPart === 'function') mod.setPart(part)
535
+ else traceSessionStart(`setPart unavailable part=${part}`)
536
+ } catch (err) {
537
+ failed = true
538
+ traceSessionStart(`setPart failed part=${part} err=${formatError(err)}`)
539
+ }
540
+ try { mod.setEmitSink(s => { buf += String(s) }) } catch (err) {
541
+ failed = true
542
+ traceSessionStart(`setEmitSink failed part=${part} err=${formatError(err)}`)
543
+ }
544
+ if (part === 'rules') await mod.runRulesPart()
545
+ else if (part === 'core') await mod.runCorePart()
546
+ else if (part === 'recap') await mod.runRecapPart()
547
+ } catch (err) {
548
+ failed = true
549
+ process.stderr.write(`[hook-pipe] session-start ${part} failed: ${err?.message || err}\n`)
550
+ traceSessionStart(`run failed part=${part} err=${formatError(err)}`)
551
+ } finally {
552
+ try { mod.setEmitSink(null) } catch (err) {
553
+ failed = true
554
+ traceSessionStart(`clearEmitSink failed part=${part} err=${formatError(err)}`)
555
+ }
556
+ traceSessionStart(
557
+ `run done part=${part} source=${payload?.source || ''} ` +
558
+ `bytes=${Buffer.byteLength(buf, 'utf8')} elapsed=${Date.now() - t0}ms failed=${failed}`
559
+ )
560
+ }
561
+ return buf || null
562
+ }
563
+
564
+ // ── SessionStart: clear-active-session handler ───────────────────────────────
565
+
566
+ function handleSessionStartClear() {
567
+ // Clear the active orchestrator session pointer so each Claude Code session
568
+ // starts fresh. Stored sessions on disk are NOT deleted — only the pointer.
569
+ try {
570
+ const dataDir = process.env.CLAUDE_PLUGIN_DATA || ''
571
+ if (!dataDir) return null
572
+ const target = join(dataDir, 'active-session.txt')
573
+ try { unlinkSync(target) } catch {}
574
+ } catch (err) {
575
+ process.stderr.write(`[hook-pipe] session-start clear failed: ${err?.message || err}\n`)
576
+ }
577
+ return null
578
+ }
579
+
580
+ // ── dispatch ─────────────────────────────────────────────────────────────────
581
+
582
+ async function dispatch(payload) {
583
+ const event = payload?.hook_event_name || payload?.hookEventName || ''
584
+ const tool = payload?.tool_name || payload?.toolName || ''
585
+ const argsArr = payload?._args || []
586
+
587
+ // CLI-arg-driven routing (statusline + future entry points without a
588
+ // hook_event_name field).
589
+ const kindArg = argsArr.find(a => a.startsWith('--kind='))
590
+ if (kindArg) {
591
+ const kind = kindArg.slice('--kind='.length)
592
+ if (kind === 'statusline') return await handleStatusLine(payload)
593
+ }
594
+
595
+ try {
596
+ if (event === 'PreToolUse') {
597
+ if (NATIVE_FILE_LOOKUP_TOOLS.has(tool)) {
598
+ return handleNativeFileLookup(payload)
599
+ }
600
+ if (tool === 'Edit' || tool === 'Write' || tool === 'MultiEdit') {
601
+ return await handlePreToolSubagent(payload)
602
+ }
603
+ if (isMcpToolName(tool)) {
604
+ return handlePreMcpSandbox(payload)
605
+ }
606
+ } else if (event === 'PostToolUse') {
607
+ return handlePostToolUse(payload)
608
+ } else if (event === 'SessionStart') {
609
+ const argsArr = payload?._args || []
610
+ const hasPart = argsArr.some(a => a.startsWith('--part='))
611
+ if (hasPart) {
612
+ return await handleSessionStartPart(argsArr, payload)
613
+ }
614
+ // No --part: clear-active-session entry.
615
+ return handleSessionStartClear()
616
+ }
617
+ } catch (err) {
618
+ process.stderr.write(`[hook-pipe] dispatch error: ${err?.message || err}\n`)
619
+ }
620
+ return null
621
+ }
622
+
623
+ // ── server ───────────────────────────────────────────────────────────────────
624
+
625
+ export function startHookPipeServer() {
626
+ if (_server) return _server
627
+
628
+ _server = createServer((socket) => {
629
+ let buf = ''
630
+ let handled = false
631
+ // Resource guards: a connection that never sends a newline-terminated
632
+ // payload would otherwise grow buf unbounded and hold the socket open
633
+ // forever. Cap the buffered bytes and idle-close a stalled connection.
634
+ const MAX_BUF_BYTES = 1 << 20 // 1 MiB
635
+ const IDLE_TIMEOUT_MS = 30_000
636
+ socket.setTimeout(IDLE_TIMEOUT_MS, () => {
637
+ if (!handled) { try { socket.destroy() } catch {} }
638
+ })
639
+ socket.on('data', async (chunk) => {
640
+ if (handled) return
641
+ buf += chunk.toString('utf8')
642
+ if (Buffer.byteLength(buf, 'utf8') > MAX_BUF_BYTES) {
643
+ handled = true
644
+ process.stderr.write(`[hook-pipe] payload exceeded ${MAX_BUF_BYTES} bytes without newline; dropping connection\n`)
645
+ try { socket.destroy() } catch {}
646
+ return
647
+ }
648
+ const firstNl = buf.indexOf('\n')
649
+ if (firstNl < 0) return
650
+ const firstLine = buf.slice(0, firstNl)
651
+
652
+ // Optional `args=` prefix line. When present, the actual payload is the
653
+ // second line; otherwise the first line IS the payload.
654
+ let args = []
655
+ let payloadLine
656
+ if (firstLine.startsWith('args=')) {
657
+ const secondNl = buf.indexOf('\n', firstNl + 1)
658
+ if (secondNl < 0) return // wait for more
659
+ args = firstLine.slice(5).split(' ').filter(Boolean)
660
+ payloadLine = buf.slice(firstNl + 1, secondNl)
661
+ } else {
662
+ payloadLine = firstLine
663
+ }
664
+
665
+ handled = true
666
+ let payload = null
667
+ try { payload = payloadLine ? JSON.parse(payloadLine) : null } catch {}
668
+ if (payload && args.length > 0) payload._args = args
669
+
670
+ // Per-request deadline: a hung handler would otherwise hold the hook
671
+ // client waiting for EOF forever, stalling Claude Code's hook step. Race
672
+ // dispatch against a real timer; on timeout, write the no-op fallback and
673
+ // end the socket so the client unblocks.
674
+ const dispatchTimeoutMs = payload ? dispatchTimeoutMsForPayload(payload) : DEFAULT_DISPATCH_TIMEOUT_MS
675
+ let timedOut = false
676
+ let deadlineTimer = null
677
+ let reply = null
678
+ try {
679
+ if (payload) {
680
+ reply = await new Promise((resolve, reject) => {
681
+ deadlineTimer = setTimeout(() => {
682
+ timedOut = true
683
+ reject(new Error(`dispatch exceeded ${dispatchTimeoutMs}ms`))
684
+ }, dispatchTimeoutMs)
685
+ dispatch(payload).then(resolve, reject)
686
+ })
687
+ }
688
+ } catch (err) {
689
+ if (timedOut) {
690
+ process.stderr.write(`[hook-pipe] dispatch timed out after ${dispatchTimeoutMs}ms; writing no-op fallback\n`)
691
+ try { socket.write('null\n') } catch {}
692
+ try { socket.end() } catch {}
693
+ return
694
+ }
695
+ process.stderr.write(`[hook-pipe] handler threw: ${err?.message || err}\n`)
696
+ } finally {
697
+ if (deadlineTimer) { clearTimeout(deadlineTimer); deadlineTimer = null }
698
+ }
699
+
700
+ // Response shape:
701
+ // • object → JSON-stringified single line (legacy decision protocol)
702
+ // • string → raw text (multi-line session-start / statusline output)
703
+ // • null/undefined → "null" (no-op marker)
704
+ let out
705
+ if (reply == null) out = 'null'
706
+ else if (typeof reply === 'string') out = reply
707
+ else out = JSON.stringify(reply)
708
+
709
+ try { socket.write(out) } catch {}
710
+ if (!out.endsWith('\n')) { try { socket.write('\n') } catch {} }
711
+ try { socket.end() } catch {}
712
+ })
713
+ socket.on('error', () => {})
714
+ })
715
+ _server.on('error', (err) => {
716
+ const msg = String(err?.message || err || '')
717
+ if (err?.code === 'EADDRINUSE' || msg.includes('EADDRINUSE') || msg.includes('Failed to listen')) {
718
+ process.stderr.write(`[hook-pipe] ${PIPE_PATH} already owned by a peer daemon; standby for hook IPC\n`)
719
+ _server = null
720
+ _started = false
721
+ return
722
+ }
723
+ process.stderr.write(`[hook-pipe] server error: ${err?.message || err}\n`)
724
+ })
725
+
726
+ const beginListen = () => {
727
+ try {
728
+ _server.listen(PIPE_PATH, () => {
729
+ _started = true
730
+ process.stderr.write(`[hook-pipe] listening on ${PIPE_PATH}\n`)
731
+ })
732
+ } catch (err) {
733
+ process.stderr.write(`[hook-pipe] listen failed: ${err?.message || err}\n`)
734
+ _server = null
735
+ }
736
+ }
737
+
738
+ if (process.platform === 'win32') {
739
+ // Windows named pipes refuse a second listener with EADDRINUSE on their
740
+ // own, so no pre-listen probe is needed.
741
+ beginListen()
742
+ } else {
743
+ // Unix: a leftover socket file from a crashed prior daemon would make
744
+ // listen() fail with EADDRINUSE. But blindly unlinking would also steal
745
+ // the socket from a live sibling daemon, leaving it orphaned. Probe the
746
+ // path first — only unlink when nothing answers.
747
+ probeUnixSocketAlive(PIPE_PATH).then((alive) => {
748
+ if (alive) {
749
+ process.stderr.write(
750
+ `[hook-pipe] another mixdog daemon is already listening on ${PIPE_PATH}; refusing to start a second instance\n`
751
+ )
752
+ _server = null
753
+ return
754
+ }
755
+ try { unlinkSync(PIPE_PATH) } catch {}
756
+ beginListen()
757
+ })
758
+ }
759
+ return _server
760
+ }
761
+
762
+ // Best-effort liveness check for a Unix socket path. Resolves true when
763
+ // something is listening (connect succeeds), false when the path is dead
764
+ // (ECONNREFUSED) or absent (ENOENT). Other errors / timeout resolve true so
765
+ // we err on the side of NOT stealing a possibly-live peer's socket.
766
+ function probeUnixSocketAlive(socketPath) {
767
+ return new Promise((resolve) => {
768
+ let done = false
769
+ const finish = (alive) => {
770
+ if (done) return
771
+ done = true
772
+ try { client.destroy() } catch {}
773
+ clearTimeout(timer)
774
+ resolve(alive)
775
+ }
776
+ let client
777
+ try {
778
+ client = createConnection(socketPath)
779
+ } catch {
780
+ resolve(false)
781
+ return
782
+ }
783
+ const timer = setTimeout(() => finish(true), 300)
784
+ client.once('connect', () => finish(true))
785
+ client.once('error', (err) => {
786
+ const code = err && err.code
787
+ finish(!(code === 'ECONNREFUSED' || code === 'ENOENT'))
788
+ })
789
+ })
790
+ }
791
+
792
+ export function stopHookPipeServer() {
793
+ if (_server) {
794
+ try { _server.close() } catch {}
795
+ _server = null
796
+ _started = false
797
+ }
798
+ }
799
+
800
+ export function isHookPipeServerStarted() {
801
+ return _started
802
+ }