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,98 @@
1
+ /**
2
+ * OpenAI OAuth (Codex backend) search backend.
3
+ *
4
+ * Reuses agent.providers.openai-oauth credentials (ChatGPT Pro bearer).
5
+ * Calls Codex WebSocket endpoint via sendViaWebSocket with web_search server
6
+ * tool. Model is config-driven (search.models.openai default 'gpt-5.4-mini').
7
+ */
8
+ import { OpenAIOAuthProvider, ensureLatestCodexModel } from '../../../agent/orchestrator/providers/openai-oauth.mjs'
9
+ import {
10
+ OPENAI_SEARCH_SYSTEM_INSTRUCTIONS,
11
+ buildOpenAISearchPrompt,
12
+ buildOpenAIWebSearchTool,
13
+ citationsFromText,
14
+ citationsFromWebSearchCalls,
15
+ } from './openai-web-search.mjs'
16
+
17
+ const SEARCH_POOL_KEY = 'mixdog-search-openai-oauth'
18
+ const SEARCH_CACHE_KEY = 'mixdog-codex-search'
19
+
20
+ // Reuse one provider instance across searches so its in-memory token cache
21
+ // survives between calls — avoids re-loading credentials on every search.
22
+ // ensureAuth() still re-validates via mtime + expiry per use.
23
+ let _sharedProvider = null
24
+ function getSharedProvider() {
25
+ if (!_sharedProvider) _sharedProvider = new OpenAIOAuthProvider({})
26
+ return _sharedProvider
27
+ }
28
+
29
+ export async function searchViaOpenAIOAuth({
30
+ query,
31
+ model,
32
+ effort,
33
+ fast = false,
34
+ site,
35
+ type = 'web',
36
+ maxResults = 5,
37
+ locale,
38
+ contextSize = 'low',
39
+ warnings = [],
40
+ signal,
41
+ }) {
42
+ const t0 = Date.now()
43
+ const provider = getSharedProvider()
44
+ const useModel = model || await ensureLatestCodexModel(provider)
45
+ await provider.ensureAuth({ reason: 'search' })
46
+ const tokens = provider.tokens
47
+ if (!tokens?.access_token) throw new Error('[search:openai-oauth] no access_token available')
48
+
49
+ // Match the bridge invariant: poolKey is the local socket bucket, cacheKey is
50
+ // the server-side prompt-cache shard. Keep them stable and distinct.
51
+ const poolKey = SEARCH_POOL_KEY
52
+ const cacheKey = SEARCH_CACHE_KEY
53
+ // Effort defaults to 'low' when caller doesn't specify — preserves the
54
+ // prior hard-coded behavior so older configs without modelOptions still
55
+ // route through the same low-latency path.
56
+ const body = {
57
+ model: useModel,
58
+ instructions: OPENAI_SEARCH_SYSTEM_INSTRUCTIONS,
59
+ input: [{ type: 'message', role: 'user', content: [{ type: 'input_text', text: buildOpenAISearchPrompt(query, maxResults) }] }],
60
+ store: false,
61
+ stream: true,
62
+ prompt_cache_key: cacheKey,
63
+ reasoning: { effort: effort || 'low' },
64
+ text: { verbosity: 'low' },
65
+ tool_choice: 'auto',
66
+ parallel_tool_calls: false,
67
+ tools: [buildOpenAIWebSearchTool({ site, type, locale, contextSize })],
68
+ }
69
+ if (fast === true) body.service_tier = 'priority'
70
+ // Route through provider.send() (not sendViaWebSocket directly) so the search
71
+ // request inherits the 401/403 force-refresh retry + HTTP/SSE fallback. A
72
+ // stale token or unhealthy WebSocket then recovers instead of hard-failing.
73
+ // _prebuiltBody bypasses buildRequestBody — the web_search server-tool body
74
+ // shape it can't express is shipped verbatim. poolKey/cacheKey map onto
75
+ // sessionId/providerCacheKey to preserve the prior socket/cache sharding.
76
+ const result = await provider.send(null, useModel, null, {
77
+ _prebuiltBody: body,
78
+ sessionId: poolKey,
79
+ providerCacheKey: cacheKey,
80
+ iteration: 0,
81
+ signal,
82
+ })
83
+ const answer = String(result?.content || '').trim()
84
+ let citations = Array.isArray(result?.citations) ? result.citations.slice(0, maxResults) : []
85
+ if (!citations.length) citations = citationsFromText(answer, maxResults, 'openai-oauth')
86
+ if (!citations.length) citations = citationsFromWebSearchCalls(result?.webSearchCalls, maxResults, 'openai-oauth')
87
+ return {
88
+ backend: 'openai-oauth',
89
+ model: useModel,
90
+ query,
91
+ answer,
92
+ citations,
93
+ durationMs: Date.now() - t0,
94
+ usage: result?.usage || null,
95
+ webSearchCalls: result?.webSearchCalls || [],
96
+ warnings,
97
+ }
98
+ }
@@ -0,0 +1,76 @@
1
+ export const OPENAI_SEARCH_SYSTEM_INSTRUCTIONS = 'You can use the web_search server tool to look up live information. Reply concisely with the answer and citations.'
2
+
3
+ export function buildOpenAIWebSearchTool({ site, type = 'web', locale, contextSize = 'low' } = {}) {
4
+ const tool = {
5
+ type: 'web_search',
6
+ external_web_access: true,
7
+ search_context_size: contextSize || 'low',
8
+ }
9
+ if (site) tool.filters = { allowed_domains: [site] }
10
+ if (locale?.country || locale?.city || locale?.region || locale?.timezone) {
11
+ tool.user_location = {
12
+ type: 'approximate',
13
+ ...(locale.country ? { country: locale.country } : {}),
14
+ ...(locale.city ? { city: locale.city } : {}),
15
+ ...(locale.region ? { region: locale.region } : {}),
16
+ ...(locale.timezone ? { timezone: locale.timezone } : {}),
17
+ }
18
+ }
19
+ if (type === 'images') tool.search_content_types = ['text', 'image']
20
+ return tool
21
+ }
22
+
23
+ export function buildOpenAISearchPrompt(query, maxResults = 5) {
24
+ return [
25
+ String(query),
26
+ '',
27
+ `Return a concise answer and cite at most ${maxResults} source URLs.`,
28
+ 'Put source URLs as plain full URLs if annotations are not available.',
29
+ ].join('\n')
30
+ }
31
+
32
+ export function citationsFromText(text, maxResults = 5, source = 'openai-oauth') {
33
+ const urls = String(text || '').match(/https?:\/\/[^\s<>()\[\]{}"`']+/g) || []
34
+ const seen = new Set()
35
+ const citations = []
36
+ for (const raw of urls) {
37
+ let parsed
38
+ try {
39
+ parsed = new URL(raw.replace(/[.,;:!?]+$/g, ''))
40
+ } catch {
41
+ continue
42
+ }
43
+ const url = parsed.toString()
44
+ if (seen.has(url)) continue
45
+ seen.add(url)
46
+ citations.push({ title: parsed.hostname, url, snippet: '', source })
47
+ if (citations.length >= maxResults) break
48
+ }
49
+ return citations
50
+ }
51
+
52
+ export function citationsFromWebSearchCalls(calls, maxResults = 5, source = 'openai-oauth') {
53
+ const seen = new Set()
54
+ const citations = []
55
+ for (const call of Array.isArray(calls) ? calls : []) {
56
+ const action = call?.action || {}
57
+ const entries = [
58
+ ...(action.url ? [action.url] : []),
59
+ ...(Array.isArray(action.urls) ? action.urls : []),
60
+ ]
61
+ for (const raw of entries) {
62
+ let parsed
63
+ try {
64
+ parsed = new URL(String(raw))
65
+ } catch {
66
+ continue
67
+ }
68
+ const url = parsed.toString()
69
+ if (seen.has(url)) continue
70
+ seen.add(url)
71
+ citations.push({ title: parsed.hostname, url, snippet: '', source })
72
+ if (citations.length >= maxResults) return citations
73
+ }
74
+ }
75
+ return citations
76
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Tavily search backend.
3
+ *
4
+ * Uses search.credentials.tavily.apiKey. POST /search with include_answer:true
5
+ * for built-in AI synthesis. RAG-specialized index.
6
+ */
7
+ import { providerHttpError } from '../state.mjs'
8
+ import { getSearchApiKey } from '../../../shared/config.mjs'
9
+ import { tavilyCountryName } from '../search-intent.mjs'
10
+
11
+ const URL = 'https://api.tavily.com/search'
12
+
13
+ export async function searchViaTavily({ query, limit = 5, site, type = 'web', locale, signal }) {
14
+ const t0 = Date.now()
15
+ const key = getSearchApiKey('tavily')
16
+ if (!key) throw new Error('[search:tavily] no api key — register via mixdog-search setup -> search-keys')
17
+ const topic = type === 'news' ? 'news' : 'general'
18
+ const body = {
19
+ api_key: key,
20
+ query: String(query),
21
+ topic,
22
+ search_depth: 'basic',
23
+ max_results: limit,
24
+ include_answer: true,
25
+ include_raw_content: false,
26
+ ...(site ? { include_domains: [site] } : {}),
27
+ }
28
+ if (topic === 'general' && locale?.country) body.country = tavilyCountryName(locale.country)
29
+
30
+ const res = await fetch(URL, {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json' },
33
+ body: JSON.stringify(body),
34
+ signal,
35
+ })
36
+ if (res.status !== 200) {
37
+ const text = await res.text()
38
+ throw providerHttpError('tavily', res.status, text)
39
+ }
40
+ const j = await res.json()
41
+ const citations = (j?.results || []).slice(0, limit).map(h => ({
42
+ title: h.title || '',
43
+ url: h.url || '',
44
+ snippet: (h.content || '').slice(0, 240),
45
+ publishedDate: h.published_date || null,
46
+ source: 'tavily',
47
+ }))
48
+ return {
49
+ backend: 'tavily',
50
+ query,
51
+ answer: (j?.answer || '').trim(),
52
+ citations,
53
+ durationMs: Date.now() - t0,
54
+ }
55
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * xAI API key search backend.
3
+ *
4
+ * API key via getAgentApiKey('xai') (env/keychain/setup). Calls Responses API + web_search
5
+ * (Agent Tools API) — Live Search (Chat Completions) is deprecated.
6
+ */
7
+ import { providerHttpError } from '../state.mjs'
8
+ import { getAgentApiKey } from '../../../shared/config.mjs'
9
+ import { resolveLatestGrokModel } from '../../../agent/orchestrator/providers/grok-oauth.mjs'
10
+
11
+ const URL = 'https://api.x.ai/v1/responses'
12
+ const MODELS_URL = 'https://api.x.ai/v1/models'
13
+
14
+ // Match resolveLatestGrokModel() in grok-oauth.mjs (do not write OAuth shared cache).
15
+ const NON_CHAT_MODEL_RE = /imagine|image|video/i
16
+ const PROXY_EXACT_MODELS = new Set(['grok-build'])
17
+ function _isProxyOnlyModel(model) {
18
+ const m = String(model || '')
19
+ return /^grok-composer/i.test(m) || PROXY_EXACT_MODELS.has(m)
20
+ }
21
+
22
+ function _normalizeGrokApiModel(m) {
23
+ const id = m?.id
24
+ if (!id) return null
25
+ return {
26
+ id,
27
+ name: id,
28
+ display: id,
29
+ provider: 'grok-oauth',
30
+ family: 'grok',
31
+ tier: 'version',
32
+ latest: false,
33
+ contextWindow: m?.context_window || 0,
34
+ created: typeof m?.created === 'number' ? m.created : null,
35
+ }
36
+ }
37
+
38
+ function _pickLatestChatModelId(models) {
39
+ let best = null
40
+ for (const m of models) {
41
+ if (!m?.id || NON_CHAT_MODEL_RE.test(m.id) || _isProxyOnlyModel(m.id) || !(Number(m.created) > 0)) continue
42
+ if (!best || Number(m.created) > Number(best.created)) best = m
43
+ }
44
+ return best?.id || null
45
+ }
46
+
47
+ async function _fetchGrokCatalogModels(apiKey, signal) {
48
+ const res = await fetch(MODELS_URL, {
49
+ method: 'GET',
50
+ headers: { Authorization: `Bearer ${apiKey}` },
51
+ redirect: 'error',
52
+ signal,
53
+ })
54
+ if (!res.ok) {
55
+ const text = await res.text().catch(() => '')
56
+ throw new Error(`[search:xai-api] model catalog fetch failed (${res.status}${text ? `: ${text.slice(0, 200)}` : ''})`)
57
+ }
58
+ const data = await res.json()
59
+ if (!Array.isArray(data?.data)) {
60
+ throw new Error('[search:xai-api] unexpected /models response shape (no data[])')
61
+ }
62
+ return data.data.map(_normalizeGrokApiModel).filter(Boolean)
63
+ }
64
+
65
+ async function ensureLatestGrokModelForApiKey(apiKey, signal) {
66
+ let m = resolveLatestGrokModel()
67
+ if (m) return m
68
+ const models = await _fetchGrokCatalogModels(apiKey, signal)
69
+ m = _pickLatestChatModelId(models)
70
+ if (m) return m
71
+ throw new Error(
72
+ '[search:xai-api] model catalog unavailable after warmup — set search.models.xai to an explicit model id',
73
+ )
74
+ }
75
+
76
+ export async function searchViaXAIApi({ query, model, maxResults = 5, warnings = [], signal }) {
77
+ const t0 = Date.now()
78
+ const key = getAgentApiKey('xai')
79
+ if (!key) throw new Error('[search:xai-api] no api key — set XAI_API_KEY or the xAI provider key in setup')
80
+ const useModel = model || await ensureLatestGrokModelForApiKey(key, signal)
81
+
82
+ const res = await fetch(URL, {
83
+ method: 'POST',
84
+ headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
85
+ body: JSON.stringify({
86
+ model: useModel,
87
+ input: String(query),
88
+ tools: [{ type: 'web_search' }],
89
+ }),
90
+ signal,
91
+ })
92
+ if (res.status !== 200) {
93
+ const text = await res.text()
94
+ throw providerHttpError('xai-api', res.status, text)
95
+ }
96
+ const j = await res.json()
97
+ const items = j?.output || []
98
+ const msg = items.find(it => it.type === 'message')?.content || []
99
+ const tb = msg.find(c => c.type === 'output_text')
100
+ const annotations = (tb?.annotations || [])
101
+ .filter(a => a?.url)
102
+ .map(a => ({ title: a.title || '', url: a.url || '', snippet: '', source: 'xai-api' }))
103
+ return {
104
+ backend: 'xai-api',
105
+ model: useModel,
106
+ query,
107
+ answer: (tb?.text || '').trim(),
108
+ citations: annotations.slice(0, maxResults),
109
+ durationMs: Date.now() - t0,
110
+ usage: j?.usage || null,
111
+ warnings,
112
+ }
113
+ }
@@ -0,0 +1,131 @@
1
+ import crypto from 'crypto'
2
+ import { CACHE_PATH, readJson, writeJson } from './config.mjs'
3
+
4
+ const DEFAULT_CACHE_STATE = {
5
+ entries: {},
6
+ }
7
+
8
+ const FLUSH_DELAY_MS = 5000
9
+
10
+ let cacheDirty = false
11
+ let cacheFlushTimer = null
12
+ let activeCacheState = null
13
+ let lastCacheFlushWarnAt = 0
14
+
15
+ function nowMs() {
16
+ return Date.now()
17
+ }
18
+
19
+ // 5s debounce so a single search invocation that touches the cache multiple
20
+ // times (lookup + insert + prune) coalesces into one writeJson roundtrip.
21
+ // Without this, callers like crawl/batch that don't explicitly flush would
22
+ // either spam fsync (immediate write per mutation) or silently drop dirty
23
+ // state on crash (bare dirty flag).
24
+ function scheduleCacheFlush(state) {
25
+ cacheDirty = true
26
+ activeCacheState = state
27
+ if (cacheFlushTimer) return
28
+ cacheFlushTimer = setTimeout(() => {
29
+ cacheFlushTimer = null
30
+ flushCacheState()
31
+ }, FLUSH_DELAY_MS)
32
+ if (cacheFlushTimer.unref) cacheFlushTimer.unref()
33
+ }
34
+
35
+ function flushCacheState() {
36
+ if (cacheFlushTimer) {
37
+ clearTimeout(cacheFlushTimer)
38
+ cacheFlushTimer = null
39
+ }
40
+ if (cacheDirty && activeCacheState) {
41
+ try {
42
+ writeJson(CACHE_PATH, activeCacheState)
43
+ cacheDirty = false
44
+ } catch (err) {
45
+ // Cache state is best-effort. Windows AV/indexer can hold the
46
+ // destination open. Keep the dirty state and retry quietly.
47
+ const now = Date.now()
48
+ if (now - lastCacheFlushWarnAt > 60000) {
49
+ lastCacheFlushWarnAt = now
50
+ process.stderr.write(`[search-cache] flushCacheState delayed: ${err?.code || err?.message || err}\n`)
51
+ }
52
+ if (!cacheFlushTimer) {
53
+ cacheFlushTimer = setTimeout(() => {
54
+ cacheFlushTimer = null
55
+ flushCacheState()
56
+ }, FLUSH_DELAY_MS * 2)
57
+ if (cacheFlushTimer.unref) cacheFlushTimer.unref()
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ process.on('exit', flushCacheState)
64
+
65
+ export { flushCacheState }
66
+
67
+ let _instance = null
68
+
69
+ export function loadCacheState() {
70
+ if (_instance) return _instance
71
+ const state = readJson(CACHE_PATH, DEFAULT_CACHE_STATE)
72
+ if (!state.entries || typeof state.entries !== 'object') {
73
+ state.entries = {}
74
+ }
75
+ _instance = state
76
+ activeCacheState = state
77
+ pruneExpiredEntries(state)
78
+ return state
79
+ }
80
+
81
+ export function buildCacheKey(namespace, payload) {
82
+ const hash = crypto
83
+ .createHash('sha256')
84
+ .update(JSON.stringify(payload))
85
+ .digest('hex')
86
+ return `${namespace}:${hash}`
87
+ }
88
+
89
+ export function getCachedEntry(state, key) {
90
+ const entry = state.entries[key]
91
+ if (!entry) return null
92
+ if (entry.expiresAt && entry.expiresAt <= nowMs()) {
93
+ delete state.entries[key]
94
+ scheduleCacheFlush(state)
95
+ return null
96
+ }
97
+ return entry
98
+ }
99
+
100
+ export function setCachedEntry(state, key, payload, ttlMs) {
101
+ const cachedAt = nowMs()
102
+ state.entries[key] = {
103
+ cachedAt,
104
+ expiresAt: cachedAt + ttlMs,
105
+ payload,
106
+ }
107
+ scheduleCacheFlush(state)
108
+ return state.entries[key]
109
+ }
110
+
111
+ export function buildCacheMeta(entry, hit) {
112
+ return {
113
+ hit,
114
+ cachedAt: entry ? new Date(entry.cachedAt).toISOString() : null,
115
+ expiresAt: entry ? new Date(entry.expiresAt).toISOString() : null,
116
+ }
117
+ }
118
+
119
+ function pruneExpiredEntries(state) {
120
+ const current = nowMs()
121
+ let dirty = false
122
+ for (const [key, entry] of Object.entries(state.entries)) {
123
+ if (entry?.expiresAt && entry.expiresAt <= current) {
124
+ delete state.entries[key]
125
+ dirty = true
126
+ }
127
+ }
128
+ if (dirty) {
129
+ scheduleCacheFlush(state)
130
+ }
131
+ }
@@ -0,0 +1,192 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { fileURLToPath } from 'url'
4
+ import { resolvePluginData } from '../../shared/plugin-paths.mjs'
5
+ import { renameWithRetrySync, writeJsonAtomicSync } from '../../shared/atomic-file.mjs'
6
+ import { readSection, updateSection, stripGeneratedMarker, CONFIG_PATH as MIXDOG_CONFIG_PATH, getSearchApiKey } from '../../shared/config.mjs'
7
+
8
+ const currentDir = path.dirname(fileURLToPath(import.meta.url))
9
+ // src/search/lib/config.mjs → plugin root is three levels up (src/search/lib → src/search → src → plugin root).
10
+ export const PLUGIN_ROOT = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(currentDir, '..', '..', '..')
11
+
12
+ // Unified mode: search shares the plugin data dir with the rest of mixdog.
13
+ const SHARED_DATA_DIR = resolvePluginData()
14
+ if (!SHARED_DATA_DIR) throw new Error('[search-config] resolvePluginData() returned falsy — plugin data dir not configured')
15
+ export const DATA_DIR = SHARED_DATA_DIR
16
+ export const USAGE_PATH = path.join(DATA_DIR, 'usage.local.json')
17
+ export const CACHE_PATH = path.join(DATA_DIR, 'cache.local.json')
18
+
19
+ // Per-provider default models. Single source of truth for any site that
20
+ // needs a fallback when the user config lacks an explicit override.
21
+ export const DEFAULT_MODELS = {
22
+ openai: 'gpt-5.5',
23
+ gemini: 'gemini-3-flash-preview',
24
+ xai: 'grok-4.3',
25
+ }
26
+
27
+ export const DEFAULT_CONFIG = {
28
+ // Single active search backend. Switching here changes which backend the
29
+ // `search` MCP tool calls — does not require credentials for the unused
30
+ // backends. No priority chain / fallback — the selected provider is the
31
+ // only one tried; on failure the call throws.
32
+ provider: 'anthropic-oauth',
33
+ // Per-provider model override (only used by providers that take a model arg).
34
+ // Anthropic OAuth is haiku-fixed (third-party policy + practical quota).
35
+ models: {},
36
+ rawSearch: {
37
+ maxResults: 10,
38
+ credentials: {
39
+ firecrawl: {
40
+ apiKey: '',
41
+ },
42
+ tavily: {
43
+ apiKey: '',
44
+ },
45
+ exa: {
46
+ apiKey: '',
47
+ },
48
+ },
49
+ },
50
+ requestTimeoutMs: 120000,
51
+ crawl: {
52
+ maxPages: 10,
53
+ maxDepth: 2,
54
+ sameDomainOnly: true,
55
+ },
56
+ }
57
+
58
+ export function ensureDir(dirPath) {
59
+ fs.mkdirSync(dirPath, { recursive: true })
60
+ }
61
+
62
+ export function ensureDataDir() {
63
+ ensureDir(DATA_DIR)
64
+ }
65
+
66
+ export function readJson(filePath, fallback) {
67
+ try {
68
+ const raw = fs.readFileSync(filePath, 'utf8')
69
+ try {
70
+ return JSON.parse(raw)
71
+ } catch (parseErr) {
72
+ try { renameWithRetrySync(filePath, filePath + '.corrupt.' + Date.now()) } catch {}
73
+ process.stderr.write(`[search-config] corrupt JSON backed up: ${filePath}\n`)
74
+ throw parseErr
75
+ }
76
+ } catch (err) {
77
+ if (err.code === 'ENOENT') return fallback
78
+ throw err
79
+ }
80
+ }
81
+
82
+ export function writeJson(filePath, value) {
83
+ ensureDir(path.dirname(filePath))
84
+ writeJsonAtomicSync(filePath, value, { lock: true, fsyncDir: true })
85
+ }
86
+
87
+ function hasKeys(value) {
88
+ return !!value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 0
89
+ }
90
+
91
+ export function saveConfig(config) {
92
+ updateSection('search', () => stripGeneratedMarker(config) || {})
93
+ }
94
+
95
+ function finiteInt(value, { min, max, def }) {
96
+ const n = typeof value === 'number' ? value : Number(value)
97
+ if (!Number.isFinite(n)) return def
98
+ const i = Math.trunc(n)
99
+ if (i < min) return min
100
+ if (i > max) return max
101
+ return i
102
+ }
103
+
104
+ function strictBool(value, def) {
105
+ if (value === true) return true
106
+ if (value === false) return false
107
+ return def
108
+ }
109
+
110
+ export function loadConfig() {
111
+ ensureDataDir()
112
+ let config = readSection('search')
113
+ if (!hasKeys(config)) {
114
+ saveConfig(DEFAULT_CONFIG)
115
+ config = DEFAULT_CONFIG
116
+ process.stderr.write(
117
+ `mixdog-search: default config created in ${MIXDOG_CONFIG_PATH} (section: search)\n` +
118
+ ' use /setup to change provider priority and crawl defaults.\n',
119
+ )
120
+ }
121
+ const merged = {
122
+ ...DEFAULT_CONFIG,
123
+ ...config,
124
+ rawSearch: {
125
+ ...DEFAULT_CONFIG.rawSearch,
126
+ ...(config?.rawSearch || {}),
127
+ credentials: {
128
+ ...DEFAULT_CONFIG.rawSearch.credentials,
129
+ ...(config?.rawSearch?.credentials || {}),
130
+ },
131
+ },
132
+ crawl: {
133
+ ...DEFAULT_CONFIG.crawl,
134
+ ...(config?.crawl || {}),
135
+ },
136
+ }
137
+ merged.requestTimeoutMs = finiteInt(merged.requestTimeoutMs, {
138
+ min: 1000,
139
+ max: 300000,
140
+ def: DEFAULT_CONFIG.requestTimeoutMs,
141
+ })
142
+ merged.crawl.maxPages = finiteInt(merged.crawl.maxPages, {
143
+ min: 1,
144
+ max: 200,
145
+ def: DEFAULT_CONFIG.crawl.maxPages,
146
+ })
147
+ merged.crawl.maxDepth = finiteInt(merged.crawl.maxDepth, {
148
+ min: 0,
149
+ max: 5,
150
+ def: DEFAULT_CONFIG.crawl.maxDepth,
151
+ })
152
+ merged.crawl.sameDomainOnly = strictBool(merged.crawl.sameDomainOnly, true)
153
+ return merged
154
+ }
155
+
156
+ export function getRawSearchMaxResults(config) {
157
+ return config.rawSearch?.maxResults || DEFAULT_CONFIG.rawSearch.maxResults
158
+ }
159
+
160
+ export function getRawProviderApiKey(_config, provider) {
161
+ return getSearchApiKey(provider)
162
+ }
163
+
164
+ export function getRawProviderCredentialSource(config, provider, env = process.env) {
165
+ if (getRawProviderApiKey(config, provider)) {
166
+ return 'config'
167
+ }
168
+
169
+ const envKeyByProvider = {
170
+ firecrawl: 'FIRECRAWL_API_KEY',
171
+ tavily: 'TAVILY_API_KEY',
172
+ 'xai-api': ['XAI_API_KEY', 'GROK_API_KEY'],
173
+ }
174
+
175
+ const envKey = envKeyByProvider[provider]
176
+ if (envKey) {
177
+ const keys = Array.isArray(envKey) ? envKey : [envKey]
178
+ if (keys.some(k => env?.[k])) {
179
+ return 'env'
180
+ }
181
+ }
182
+
183
+ return null
184
+ }
185
+
186
+ export function getRequestTimeoutMs(config) {
187
+ return config.requestTimeoutMs || DEFAULT_CONFIG.requestTimeoutMs
188
+ }
189
+
190
+ export function getFirecrawlApiKey(config) {
191
+ return getRawProviderApiKey(config, 'firecrawl') || ''
192
+ }