mixdog 0.7.18 → 0.8.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 (847) hide show
  1. package/README.md +37 -331
  2. package/package.json +67 -99
  3. package/scripts/boot-smoke.mjs +94 -0
  4. package/scripts/build-tui.mjs +52 -0
  5. package/scripts/compact-smoke.mjs +199 -0
  6. package/scripts/lead-workflow-smoke.mjs +598 -0
  7. package/scripts/live-worker-smoke.mjs +239 -0
  8. package/scripts/output-style-smoke.mjs +101 -0
  9. package/scripts/session-context-bench.mjs +172 -0
  10. package/scripts/smoke-loop-report.mjs +221 -0
  11. package/scripts/smoke-loop.mjs +201 -0
  12. package/scripts/smoke.mjs +113 -0
  13. package/scripts/tool-failures.mjs +143 -0
  14. package/scripts/tool-smoke.mjs +452 -0
  15. package/src/agents/debugger/AGENT.md +3 -0
  16. package/src/agents/debugger/agent.json +6 -0
  17. package/src/agents/explore/AGENT.md +4 -0
  18. package/src/agents/explore/agent.json +6 -0
  19. package/src/agents/heavy-worker/AGENT.md +3 -0
  20. package/src/agents/heavy-worker/agent.json +6 -0
  21. package/src/agents/maintainer/AGENT.md +3 -0
  22. package/src/agents/maintainer/agent.json +6 -0
  23. package/src/agents/reviewer/AGENT.md +3 -0
  24. package/src/agents/reviewer/agent.json +6 -0
  25. package/src/agents/scheduler-task.md +3 -0
  26. package/src/agents/web-researcher/AGENT.md +3 -0
  27. package/src/agents/web-researcher/agent.json +6 -0
  28. package/src/agents/webhook-handler.md +3 -0
  29. package/src/agents/worker/AGENT.md +3 -0
  30. package/src/agents/worker/agent.json +6 -0
  31. package/src/app.mjs +90 -0
  32. package/src/cli.mjs +11 -0
  33. package/src/defaults/hidden-roles.json +72 -0
  34. package/src/defaults/mixdog-config.template.json +15 -0
  35. package/src/hooks/lib/permission-evaluator.cjs +488 -0
  36. package/src/hooks/lib/settings-loader.cjs +112 -0
  37. package/src/lib/keychain-cjs.cjs +332 -0
  38. package/src/lib/plugin-paths.cjs +28 -0
  39. package/src/lib/rules-builder.cjs +315 -0
  40. package/src/mixdog-session-runtime.mjs +3813 -0
  41. package/src/output-styles/default.md +38 -0
  42. package/src/output-styles/extreme-simple.md +17 -0
  43. package/src/output-styles/simple.md +17 -0
  44. package/src/repl.mjs +330 -0
  45. package/src/rules/bridge/00-common.md +5 -0
  46. package/src/rules/bridge/20-skip-protocol.md +11 -0
  47. package/src/rules/bridge/30-explorer.md +4 -0
  48. package/src/rules/bridge/40-cycle1-agent.md +28 -0
  49. package/src/rules/bridge/41-cycle2-agent.md +59 -0
  50. package/src/rules/lead/00-tool-lead.md +5 -0
  51. package/src/rules/lead/01-general.md +5 -0
  52. package/src/rules/lead/02-channels.md +3 -0
  53. package/src/rules/lead/04-workflow.md +12 -0
  54. package/src/rules/shared/00-language.md +3 -0
  55. package/src/rules/shared/01-tool.md +3 -0
  56. package/src/runtime/agent/orchestrator/bridge-trace.mjs +814 -0
  57. package/src/runtime/agent/orchestrator/cache-mtime.mjs +60 -0
  58. package/src/runtime/agent/orchestrator/config.mjs +446 -0
  59. package/src/runtime/agent/orchestrator/context/collect.mjs +796 -0
  60. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +417 -0
  61. package/src/runtime/agent/orchestrator/internal-roles.mjs +188 -0
  62. package/src/runtime/agent/orchestrator/internal-tools.mjs +88 -0
  63. package/src/runtime/agent/orchestrator/mcp/client.mjs +345 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2104 -0
  65. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +784 -0
  66. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +341 -0
  67. package/src/runtime/agent/orchestrator/providers/gemini.mjs +1679 -0
  68. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +959 -0
  69. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +213 -0
  70. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +38 -0
  71. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +471 -0
  72. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +615 -0
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +808 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +1719 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +2587 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +1953 -0
  77. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +136 -0
  78. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +317 -0
  79. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +109 -0
  80. package/src/runtime/agent/orchestrator/providers/registry.mjs +247 -0
  81. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +332 -0
  82. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +11 -0
  83. package/src/runtime/agent/orchestrator/providers/trace-utils.mjs +50 -0
  84. package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
  85. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  86. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +318 -0
  87. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +367 -0
  88. package/src/runtime/agent/orchestrator/session/compact.mjs +882 -0
  89. package/src/runtime/agent/orchestrator/session/context-utils.mjs +233 -0
  90. package/src/runtime/agent/orchestrator/session/loop.mjs +2269 -0
  91. package/src/runtime/agent/orchestrator/session/manager.mjs +2972 -0
  92. package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
  93. package/src/runtime/agent/orchestrator/session/store.mjs +870 -0
  94. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  95. package/src/runtime/agent/orchestrator/smart-bridge/bridge-llm.mjs +339 -0
  96. package/src/runtime/agent/orchestrator/smart-bridge/cache-strategy.mjs +419 -0
  97. package/src/runtime/agent/orchestrator/stall-policy.mjs +227 -0
  98. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +235 -0
  99. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +723 -0
  100. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
  101. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +389 -0
  102. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +637 -0
  103. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +165 -0
  104. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +104 -0
  105. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +194 -0
  106. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +596 -0
  107. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +110 -0
  108. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +153 -0
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-open.mjs +189 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +731 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +168 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +465 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +160 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +982 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1087 -0
  118. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +231 -0
  119. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +223 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin.mjs +478 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  122. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4102 -0
  123. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +154 -0
  125. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +26 -0
  126. package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +143 -0
  127. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +18 -0
  128. package/src/runtime/agent/orchestrator/tools/patch.mjs +2772 -0
  129. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +114 -0
  130. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +880 -0
  131. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +312 -0
  132. package/src/runtime/channels/backends/discord.mjs +781 -0
  133. package/src/runtime/channels/data/voice-runtime-manifest.json +138 -0
  134. package/src/runtime/channels/index.mjs +3309 -0
  135. package/src/runtime/channels/lib/config.mjs +285 -0
  136. package/src/runtime/channels/lib/drop-trace.mjs +71 -0
  137. package/src/runtime/channels/lib/event-pipeline.mjs +81 -0
  138. package/src/runtime/channels/lib/holidays.mjs +138 -0
  139. package/src/runtime/channels/lib/hook-pipe-server.mjs +671 -0
  140. package/src/runtime/channels/lib/output-forwarder.mjs +765 -0
  141. package/src/runtime/channels/lib/runtime-paths.mjs +497 -0
  142. package/src/runtime/channels/lib/scheduler.mjs +710 -0
  143. package/src/runtime/channels/lib/session-discovery.mjs +102 -0
  144. package/src/runtime/channels/lib/state-file.mjs +68 -0
  145. package/src/runtime/channels/lib/status-snapshot.mjs +224 -0
  146. package/src/runtime/channels/lib/tool-format.mjs +122 -0
  147. package/src/runtime/channels/lib/transcript-discovery.mjs +195 -0
  148. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +734 -0
  149. package/src/runtime/channels/lib/webhook.mjs +1288 -0
  150. package/src/runtime/channels/tool-defs.mjs +177 -0
  151. package/src/runtime/lib/keychain-cjs.cjs +289 -0
  152. package/src/runtime/memory/data/runtime-manifest.json +40 -0
  153. package/src/runtime/memory/index.mjs +3706 -0
  154. package/src/runtime/memory/lib/core-memory-store.mjs +336 -0
  155. package/src/runtime/memory/lib/embedding-provider.mjs +275 -0
  156. package/src/runtime/memory/lib/embedding-worker.mjs +331 -0
  157. package/src/runtime/memory/lib/memory-cycle-requests.mjs +276 -0
  158. package/src/runtime/memory/lib/memory-cycle1.mjs +783 -0
  159. package/src/runtime/memory/lib/memory-cycle2.mjs +1389 -0
  160. package/src/runtime/memory/lib/memory-cycle3.mjs +646 -0
  161. package/src/runtime/memory/lib/memory-embed.mjs +300 -0
  162. package/src/runtime/memory/lib/memory-ops-policy.mjs +149 -0
  163. package/src/runtime/memory/lib/memory-recall-store.mjs +644 -0
  164. package/src/runtime/memory/lib/memory.mjs +418 -0
  165. package/src/runtime/memory/lib/pg/adapter.mjs +328 -0
  166. package/src/runtime/memory/lib/pg/process.mjs +366 -0
  167. package/src/runtime/memory/lib/pg/supervisor.mjs +495 -0
  168. package/src/runtime/memory/lib/runtime-fetcher.mjs +464 -0
  169. package/src/runtime/memory/lib/trace-store.mjs +734 -0
  170. package/src/runtime/memory/tool-defs.mjs +79 -0
  171. package/src/runtime/search/index.mjs +925 -0
  172. package/src/runtime/search/lib/config.mjs +61 -0
  173. package/src/runtime/search/lib/web-tools.mjs +1278 -0
  174. package/src/runtime/search/tool-defs.mjs +64 -0
  175. package/src/runtime/shared/atomic-file.mjs +435 -0
  176. package/src/runtime/shared/background-tasks.mjs +376 -0
  177. package/src/runtime/shared/child-guardian.mjs +98 -0
  178. package/src/runtime/shared/config.mjs +393 -0
  179. package/src/runtime/shared/err-text.mjs +121 -0
  180. package/src/runtime/shared/launcher-control.mjs +259 -0
  181. package/src/runtime/shared/llm/http-agent.mjs +129 -0
  182. package/src/runtime/shared/open-url.mjs +37 -0
  183. package/src/runtime/shared/plugin-paths.mjs +25 -0
  184. package/src/runtime/shared/process-shutdown.mjs +147 -0
  185. package/src/runtime/shared/schedules-store.mjs +70 -0
  186. package/src/runtime/shared/tool-execution-contract.mjs +104 -0
  187. package/src/runtime/shared/tool-surface.mjs +947 -0
  188. package/src/runtime/shared/user-cwd.mjs +221 -0
  189. package/src/runtime/shared/user-data-guard.mjs +232 -0
  190. package/src/runtime/shared/workspace-router.mjs +259 -0
  191. package/src/standalone/bridge-tool.mjs +1414 -0
  192. package/src/standalone/channel-admin.mjs +366 -0
  193. package/src/standalone/channel-worker-preload.cjs +3 -0
  194. package/src/standalone/channel-worker.mjs +353 -0
  195. package/src/standalone/explore-tool.mjs +233 -0
  196. package/src/standalone/hook-bus.mjs +246 -0
  197. package/src/standalone/plugin-admin.mjs +247 -0
  198. package/src/standalone/provider-admin.mjs +338 -0
  199. package/src/standalone/seeds.mjs +94 -0
  200. package/src/standalone/usage-dashboard.mjs +510 -0
  201. package/src/tui/App.jsx +5446 -0
  202. package/src/tui/components/AnsiText.jsx +199 -0
  203. package/src/tui/components/ContextPanel.jsx +265 -0
  204. package/src/tui/components/Markdown.jsx +205 -0
  205. package/src/tui/components/MarkdownTable.jsx +204 -0
  206. package/src/tui/components/Message.jsx +103 -0
  207. package/src/tui/components/Picker.jsx +317 -0
  208. package/src/tui/components/PromptInput.jsx +584 -0
  209. package/src/tui/components/QueuedCommands.jsx +47 -0
  210. package/src/tui/components/SlashCommandPalette.jsx +114 -0
  211. package/src/tui/components/Spinner.jsx +317 -0
  212. package/src/tui/components/StatusLine.jsx +87 -0
  213. package/src/tui/components/TextEntryPanel.jsx +323 -0
  214. package/src/tui/components/ToolExecution.jsx +791 -0
  215. package/src/tui/components/TurnDone.jsx +78 -0
  216. package/src/tui/components/UsagePanel.jsx +331 -0
  217. package/src/tui/dist/index.mjs +12420 -0
  218. package/src/tui/engine.mjs +2410 -0
  219. package/src/tui/figures.mjs +50 -0
  220. package/src/tui/hooks/useEngine.mjs +16 -0
  221. package/src/tui/index.jsx +254 -0
  222. package/src/tui/input-editing.mjs +242 -0
  223. package/src/tui/markdown/format-token.mjs +194 -0
  224. package/src/tui/paste-attachments.mjs +198 -0
  225. package/src/tui/runtime/shared/process-shutdown.mjs +1 -0
  226. package/src/tui/spinner-verbs.mjs +45 -0
  227. package/src/tui/theme.mjs +67 -0
  228. package/src/tui/time-format.mjs +53 -0
  229. package/src/ui/ansi.mjs +115 -0
  230. package/src/ui/markdown.mjs +195 -0
  231. package/src/ui/statusline.mjs +730 -0
  232. package/src/ui/tool-card.mjs +99 -0
  233. package/src/vendor/statusline/bin/statusline-lib.mjs +805 -0
  234. package/src/vendor/statusline/bin/statusline-route.mjs +596 -0
  235. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +285 -0
  236. package/src/vendor/statusline/src/gateway/claude-current.mjs +320 -0
  237. package/src/vendor/statusline/src/gateway/route-meta.mjs +753 -0
  238. package/src/vendor/statusline/src/gateway/session-routes.mjs +244 -0
  239. package/src/workflows/default/WORKFLOW.md +7 -0
  240. package/src/workflows/default/workflow.json +14 -0
  241. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  242. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  243. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  244. package/vendor/ink/build/colorize.d.ts +3 -0
  245. package/vendor/ink/build/colorize.js +48 -0
  246. package/vendor/ink/build/colorize.js.map +1 -0
  247. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  248. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  249. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  250. package/vendor/ink/build/components/AnimationContext.d.ts +9 -0
  251. package/vendor/ink/build/components/AnimationContext.js +13 -0
  252. package/vendor/ink/build/components/AnimationContext.js.map +1 -0
  253. package/vendor/ink/build/components/App.d.ts +24 -0
  254. package/vendor/ink/build/components/App.js +554 -0
  255. package/vendor/ink/build/components/App.js.map +1 -0
  256. package/vendor/ink/build/components/AppContext.d.ts +80 -0
  257. package/vendor/ink/build/components/AppContext.js +25 -0
  258. package/vendor/ink/build/components/AppContext.js.map +1 -0
  259. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  260. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  261. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  262. package/vendor/ink/build/components/Box.d.ts +130 -0
  263. package/vendor/ink/build/components/Box.js +34 -0
  264. package/vendor/ink/build/components/Box.js.map +1 -0
  265. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  266. package/vendor/ink/build/components/CursorContext.js +8 -0
  267. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  268. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  269. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  270. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  271. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  272. package/vendor/ink/build/components/ErrorOverview.js +90 -0
  273. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  274. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  275. package/vendor/ink/build/components/FocusContext.js +17 -0
  276. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  277. package/vendor/ink/build/components/Newline.d.ts +13 -0
  278. package/vendor/ink/build/components/Newline.js +8 -0
  279. package/vendor/ink/build/components/Newline.js.map +1 -0
  280. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  281. package/vendor/ink/build/components/Spacer.js +11 -0
  282. package/vendor/ink/build/components/Spacer.js.map +1 -0
  283. package/vendor/ink/build/components/Static.d.ts +24 -0
  284. package/vendor/ink/build/components/Static.js +28 -0
  285. package/vendor/ink/build/components/Static.js.map +1 -0
  286. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  287. package/vendor/ink/build/components/StderrContext.js +13 -0
  288. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  289. package/vendor/ink/build/components/StdinContext.d.ts +28 -0
  290. package/vendor/ink/build/components/StdinContext.js +20 -0
  291. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  292. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  293. package/vendor/ink/build/components/StdoutContext.js +13 -0
  294. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  295. package/vendor/ink/build/components/Text.d.ts +55 -0
  296. package/vendor/ink/build/components/Text.js +50 -0
  297. package/vendor/ink/build/components/Text.js.map +1 -0
  298. package/vendor/ink/build/components/Transform.d.ts +16 -0
  299. package/vendor/ink/build/components/Transform.js +15 -0
  300. package/vendor/ink/build/components/Transform.js.map +1 -0
  301. package/vendor/ink/build/cursor-helpers.d.ts +39 -0
  302. package/vendor/ink/build/cursor-helpers.js +62 -0
  303. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  304. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  305. package/vendor/ink/build/devtools-window-polyfill.js +68 -0
  306. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  307. package/vendor/ink/build/devtools.d.ts +1 -0
  308. package/vendor/ink/build/devtools.js +36 -0
  309. package/vendor/ink/build/devtools.js.map +1 -0
  310. package/vendor/ink/build/dom.d.ts +62 -0
  311. package/vendor/ink/build/dom.js +143 -0
  312. package/vendor/ink/build/dom.js.map +1 -0
  313. package/vendor/ink/build/get-max-width.d.ts +3 -0
  314. package/vendor/ink/build/get-max-width.js +10 -0
  315. package/vendor/ink/build/get-max-width.js.map +1 -0
  316. package/vendor/ink/build/hooks/use-animation.d.ts +49 -0
  317. package/vendor/ink/build/hooks/use-animation.js +87 -0
  318. package/vendor/ink/build/hooks/use-animation.js.map +1 -0
  319. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  320. package/vendor/ink/build/hooks/use-app.js +8 -0
  321. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  322. package/vendor/ink/build/hooks/use-box-metrics.d.ts +59 -0
  323. package/vendor/ink/build/hooks/use-box-metrics.js +81 -0
  324. package/vendor/ink/build/hooks/use-box-metrics.js.map +1 -0
  325. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  326. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  327. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  328. package/vendor/ink/build/hooks/use-focus-manager.d.ts +43 -0
  329. package/vendor/ink/build/hooks/use-focus-manager.js +18 -0
  330. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  331. package/vendor/ink/build/hooks/use-focus.d.ts +30 -0
  332. package/vendor/ink/build/hooks/use-focus.js +43 -0
  333. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  334. package/vendor/ink/build/hooks/use-input.d.ts +132 -0
  335. package/vendor/ink/build/hooks/use-input.js +126 -0
  336. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  337. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +6 -0
  338. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +12 -0
  339. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  340. package/vendor/ink/build/hooks/use-paste.d.ts +35 -0
  341. package/vendor/ink/build/hooks/use-paste.js +62 -0
  342. package/vendor/ink/build/hooks/use-paste.js.map +1 -0
  343. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  344. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  345. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  346. package/vendor/ink/build/hooks/use-stdin.d.ts +7 -0
  347. package/vendor/ink/build/hooks/use-stdin.js +9 -0
  348. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  349. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  350. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  351. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  352. package/vendor/ink/build/hooks/use-window-size.d.ts +18 -0
  353. package/vendor/ink/build/hooks/use-window-size.js +22 -0
  354. package/vendor/ink/build/hooks/use-window-size.js.map +1 -0
  355. package/vendor/ink/build/index.d.ts +42 -0
  356. package/vendor/ink/build/index.js +24 -0
  357. package/vendor/ink/build/index.js.map +1 -0
  358. package/vendor/ink/build/ink.d.ts +146 -0
  359. package/vendor/ink/build/ink.js +1022 -0
  360. package/vendor/ink/build/ink.js.map +1 -0
  361. package/vendor/ink/build/input-parser.d.ts +10 -0
  362. package/vendor/ink/build/input-parser.js +194 -0
  363. package/vendor/ink/build/input-parser.js.map +1 -0
  364. package/vendor/ink/build/instances.d.ts +3 -0
  365. package/vendor/ink/build/instances.js +8 -0
  366. package/vendor/ink/build/instances.js.map +1 -0
  367. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  368. package/vendor/ink/build/kitty-keyboard.js +32 -0
  369. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  370. package/vendor/ink/build/log-update.d.ts +20 -0
  371. package/vendor/ink/build/log-update.js +261 -0
  372. package/vendor/ink/build/log-update.js.map +1 -0
  373. package/vendor/ink/build/measure-element.d.ts +20 -0
  374. package/vendor/ink/build/measure-element.js +13 -0
  375. package/vendor/ink/build/measure-element.js.map +1 -0
  376. package/vendor/ink/build/measure-text.d.ts +6 -0
  377. package/vendor/ink/build/measure-text.js +21 -0
  378. package/vendor/ink/build/measure-text.js.map +1 -0
  379. package/vendor/ink/build/output.d.ts +35 -0
  380. package/vendor/ink/build/output.js +328 -0
  381. package/vendor/ink/build/output.js.map +1 -0
  382. package/vendor/ink/build/parse-keypress.d.ts +20 -0
  383. package/vendor/ink/build/parse-keypress.js +495 -0
  384. package/vendor/ink/build/parse-keypress.js.map +1 -0
  385. package/vendor/ink/build/reconciler.d.ts +4 -0
  386. package/vendor/ink/build/reconciler.js +306 -0
  387. package/vendor/ink/build/reconciler.js.map +1 -0
  388. package/vendor/ink/build/render-background.d.ts +4 -0
  389. package/vendor/ink/build/render-background.js +25 -0
  390. package/vendor/ink/build/render-background.js.map +1 -0
  391. package/vendor/ink/build/render-border.d.ts +4 -0
  392. package/vendor/ink/build/render-border.js +84 -0
  393. package/vendor/ink/build/render-border.js.map +1 -0
  394. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  395. package/vendor/ink/build/render-node-to-output.js +162 -0
  396. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  397. package/vendor/ink/build/render-to-string.d.ts +38 -0
  398. package/vendor/ink/build/render-to-string.js +116 -0
  399. package/vendor/ink/build/render-to-string.js.map +1 -0
  400. package/vendor/ink/build/render.d.ts +176 -0
  401. package/vendor/ink/build/render.js +71 -0
  402. package/vendor/ink/build/render.js.map +1 -0
  403. package/vendor/ink/build/renderer.d.ts +8 -0
  404. package/vendor/ink/build/renderer.js +64 -0
  405. package/vendor/ink/build/renderer.js.map +1 -0
  406. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  407. package/vendor/ink/build/sanitize-ansi.js +27 -0
  408. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  409. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  410. package/vendor/ink/build/squash-text-nodes.js +36 -0
  411. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  412. package/vendor/ink/build/styles.d.ts +302 -0
  413. package/vendor/ink/build/styles.js +303 -0
  414. package/vendor/ink/build/styles.js.map +1 -0
  415. package/vendor/ink/build/utils.d.ts +9 -0
  416. package/vendor/ink/build/utils.js +19 -0
  417. package/vendor/ink/build/utils.js.map +1 -0
  418. package/vendor/ink/build/wrap-text.d.ts +3 -0
  419. package/vendor/ink/build/wrap-text.js +38 -0
  420. package/vendor/ink/build/wrap-text.js.map +1 -0
  421. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  422. package/vendor/ink/build/write-synchronized.js +9 -0
  423. package/vendor/ink/build/write-synchronized.js.map +1 -0
  424. package/vendor/ink/license +10 -0
  425. package/vendor/ink/package.json +137 -0
  426. package/.claude-plugin/marketplace.json +0 -34
  427. package/.claude-plugin/plugin.json +0 -20
  428. package/.gitattributes +0 -34
  429. package/.mcp.json +0 -14
  430. package/ARCHITECTURE.md +0 -77
  431. package/CHANGELOG.md +0 -30
  432. package/CONTRIBUTING.md +0 -45
  433. package/DATA-FLOW.md +0 -79
  434. package/LICENSE +0 -21
  435. package/SECURITY.md +0 -138
  436. package/UNINSTALL.md +0 -115
  437. package/agents/maintenance.md +0 -5
  438. package/agents/memory-classification.md +0 -30
  439. package/agents/scheduler-task.md +0 -18
  440. package/agents/webhook-handler.md +0 -27
  441. package/agents/worker.md +0 -24
  442. package/bin/bridge +0 -133
  443. package/bin/statusline-launcher.mjs +0 -82
  444. package/bin/statusline-lib.mjs +0 -581
  445. package/bin/statusline-route.mjs +0 -273
  446. package/bin/statusline.mjs +0 -638
  447. package/bun.lock +0 -927
  448. package/commands/config.md +0 -16
  449. package/commands/doctor.md +0 -13
  450. package/commands/model.md +0 -61
  451. package/commands/setup.md +0 -17
  452. package/defaults/hidden-roles.json +0 -68
  453. package/defaults/memory-chunk-prompt.md +0 -63
  454. package/defaults/mixdog-config.template.json +0 -27
  455. package/defaults/user-workflow.json +0 -8
  456. package/defaults/user-workflow.md +0 -17
  457. package/hooks/hooks.json +0 -73
  458. package/hooks/lib/active-instance.cjs +0 -77
  459. package/hooks/lib/permission-evaluator.cjs +0 -411
  460. package/hooks/lib/permission-route.cjs +0 -63
  461. package/hooks/lib/settings-loader.cjs +0 -117
  462. package/hooks/post-tool-use.cjs +0 -84
  463. package/hooks/pre-mcp-sandbox.cjs +0 -158
  464. package/hooks/pre-tool-subagent.cjs +0 -258
  465. package/hooks/session-start.cjs +0 -1493
  466. package/hooks/shim-launcher.cjs +0 -65
  467. package/hooks/turn-timer.cjs +0 -82
  468. package/lib/claude-md-writer.cjs +0 -386
  469. package/lib/keychain-cjs.cjs +0 -290
  470. package/lib/plugin-paths.cjs +0 -69
  471. package/lib/rules-builder.cjs +0 -241
  472. package/native/README.md +0 -117
  473. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  474. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  475. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  476. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  477. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  478. package/prompts/code-review.txt +0 -16
  479. package/prompts/security-audit.txt +0 -17
  480. package/rules/bridge/00-common.md +0 -39
  481. package/rules/bridge/20-skip-protocol.md +0 -18
  482. package/rules/bridge/30-explorer.md +0 -33
  483. package/rules/bridge/40-cycle1-agent.md +0 -52
  484. package/rules/bridge/41-cycle2-agent.md +0 -62
  485. package/rules/lead/00-tool-lead.md +0 -61
  486. package/rules/lead/01-general.md +0 -26
  487. package/rules/lead/02-channels.md +0 -49
  488. package/rules/lead/03-team.md +0 -27
  489. package/rules/lead/04-workflow.md +0 -20
  490. package/rules/shared/00-language.md +0 -14
  491. package/rules/shared/01-tool.md +0 -138
  492. package/scripts/bootstrap.mjs +0 -130
  493. package/scripts/bridge-unify-smoke.mjs +0 -308
  494. package/scripts/build-runtime-linux.sh +0 -348
  495. package/scripts/build-runtime-macos.sh +0 -217
  496. package/scripts/build-runtime-windows.ps1 +0 -242
  497. package/scripts/builtin-utils-smoke.mjs +0 -398
  498. package/scripts/bump.mjs +0 -80
  499. package/scripts/check-json.mjs +0 -45
  500. package/scripts/check-syntax-changed.mjs +0 -102
  501. package/scripts/check-syntax.mjs +0 -58
  502. package/scripts/code-graph-batch.test.mjs +0 -33
  503. package/scripts/config-preserve-smoke.mjs +0 -180
  504. package/scripts/doctor.mjs +0 -489
  505. package/scripts/edit-normalize-fuzz.mjs +0 -130
  506. package/scripts/edit-normalize-smoke.mjs +0 -401
  507. package/scripts/edit-operation-smoke.mjs +0 -369
  508. package/scripts/edit2-smoke.mjs +0 -63
  509. package/scripts/ensure-deps.mjs +0 -259
  510. package/scripts/fuzzy-e2e.mjs +0 -28
  511. package/scripts/fuzzy-smoke.mjs +0 -26
  512. package/scripts/gateway-model.mjs +0 -596
  513. package/scripts/generate-runtime-manifest.mjs +0 -166
  514. package/scripts/guard-smoke.mjs +0 -66
  515. package/scripts/hidden-role-schema-smoke.mjs +0 -162
  516. package/scripts/hook-routing-smoke.mjs +0 -29
  517. package/scripts/inject-input.ps1 +0 -204
  518. package/scripts/io-complex-smoke.mjs +0 -667
  519. package/scripts/io-explore-bench.mjs +0 -424
  520. package/scripts/io-guardrails-smoke.mjs +0 -205
  521. package/scripts/io-mini-bench-baseline.json +0 -11
  522. package/scripts/io-mini-bench.mjs +0 -216
  523. package/scripts/io-route-harness.mjs +0 -933
  524. package/scripts/io-telemetry-report.mjs +0 -691
  525. package/scripts/lib/gateway-inventory.mjs +0 -178
  526. package/scripts/lib/gateway-settings.mjs +0 -78
  527. package/scripts/mutation-bench.mjs +0 -564
  528. package/scripts/mutation-io-smoke.mjs +0 -1097
  529. package/scripts/native-patch-bridge-smoke.mjs +0 -288
  530. package/scripts/native-patch-smoke.mjs +0 -304
  531. package/scripts/openai-oauth-catalog-smoke.mjs +0 -53
  532. package/scripts/patch-interior-context-smoke.mjs +0 -49
  533. package/scripts/patch-newline-utf8-smoke.mjs +0 -157
  534. package/scripts/perf-hook-smoke.mjs +0 -71
  535. package/scripts/permission-eval-smoke.mjs +0 -443
  536. package/scripts/prep-patch.mjs +0 -53
  537. package/scripts/prep-shim.mjs +0 -96
  538. package/scripts/provider-cache-smoke.mjs +0 -687
  539. package/scripts/report-runtime-health.mjs +0 -132
  540. package/scripts/resolve-bun.mjs +0 -60
  541. package/scripts/run-mcp.mjs +0 -1473
  542. package/scripts/salvage-v4a-shatter.test.mjs +0 -58
  543. package/scripts/scoped-cache-io-smoke.mjs +0 -103
  544. package/scripts/shell-policy-round3-smoke.mjs +0 -46
  545. package/scripts/smoke-runtime-negative.ps1 +0 -100
  546. package/scripts/smoke-runtime-negative.sh +0 -95
  547. package/scripts/stall-policy-smoke.mjs +0 -50
  548. package/scripts/start-memory-worker.mjs +0 -23
  549. package/scripts/statusline-launcher-smoke.mjs +0 -235
  550. package/scripts/stress-atomic-write.mjs +0 -1028
  551. package/scripts/test-fault-inject.mjs +0 -164
  552. package/scripts/test-large-file.mjs +0 -174
  553. package/scripts/tool-edge-smoke.mjs +0 -209
  554. package/scripts/uninstall.mjs +0 -238
  555. package/scripts/webhook-selfheal-smoke.mjs +0 -27
  556. package/scripts/write-overwrite-guard-smoke.mjs +0 -56
  557. package/server-main.mjs +0 -3350
  558. package/server.mjs +0 -468
  559. package/setup/config-merge.mjs +0 -246
  560. package/setup/install.mjs +0 -574
  561. package/setup/launch-core.mjs +0 -617
  562. package/setup/launch.mjs +0 -101
  563. package/setup/locate-claude.mjs +0 -56
  564. package/setup/mixdog-cli.mjs +0 -122
  565. package/setup/setup-server.mjs +0 -3305
  566. package/setup/setup.html +0 -3740
  567. package/setup/tui.mjs +0 -325
  568. package/skills/retro-skill-proposer/SKILL.md +0 -92
  569. package/skills/schedule-add/SKILL.md +0 -77
  570. package/skills/setup/SKILL.md +0 -356
  571. package/skills/webhook-add/SKILL.md +0 -81
  572. package/src/agent/bridge-stall-watchdog.mjs +0 -337
  573. package/src/agent/index.mjs +0 -2229
  574. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +0 -1010
  575. package/src/agent/orchestrator/bridge-retry.mjs +0 -220
  576. package/src/agent/orchestrator/bridge-trace.mjs +0 -601
  577. package/src/agent/orchestrator/cache-mtime.mjs +0 -58
  578. package/src/agent/orchestrator/config.mjs +0 -405
  579. package/src/agent/orchestrator/context/collect.mjs +0 -651
  580. package/src/agent/orchestrator/dispatch-persist.mjs +0 -549
  581. package/src/agent/orchestrator/drain-registry.mjs +0 -50
  582. package/src/agent/orchestrator/explore-validator.mjs +0 -8
  583. package/src/agent/orchestrator/internal-roles.mjs +0 -118
  584. package/src/agent/orchestrator/internal-tools.mjs +0 -88
  585. package/src/agent/orchestrator/jobs.mjs +0 -116
  586. package/src/agent/orchestrator/mcp/client.mjs +0 -364
  587. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +0 -1884
  588. package/src/agent/orchestrator/providers/anthropic.mjs +0 -598
  589. package/src/agent/orchestrator/providers/gemini.mjs +0 -1530
  590. package/src/agent/orchestrator/providers/grok-oauth.mjs +0 -779
  591. package/src/agent/orchestrator/providers/model-catalog.mjs +0 -374
  592. package/src/agent/orchestrator/providers/openai-compat-stream.mjs +0 -366
  593. package/src/agent/orchestrator/providers/openai-compat.mjs +0 -1511
  594. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1891
  595. package/src/agent/orchestrator/providers/openai-oauth.mjs +0 -1456
  596. package/src/agent/orchestrator/providers/openai-ws.mjs +0 -127
  597. package/src/agent/orchestrator/providers/registry.mjs +0 -192
  598. package/src/agent/orchestrator/providers/retry-classifier.mjs +0 -325
  599. package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +0 -42
  600. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +0 -142
  601. package/src/agent/orchestrator/session/cache/read-cache.mjs +0 -319
  602. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +0 -361
  603. package/src/agent/orchestrator/session/loop.mjs +0 -1619
  604. package/src/agent/orchestrator/session/manager.mjs +0 -1991
  605. package/src/agent/orchestrator/session/result-classification.mjs +0 -65
  606. package/src/agent/orchestrator/session/store.mjs +0 -632
  607. package/src/agent/orchestrator/session/stream-watchdog.mjs +0 -130
  608. package/src/agent/orchestrator/session/tool-result-offload.mjs +0 -166
  609. package/src/agent/orchestrator/session/trim.mjs +0 -491
  610. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +0 -115
  611. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +0 -331
  612. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +0 -150
  613. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +0 -228
  614. package/src/agent/orchestrator/smart-bridge/index.mjs +0 -215
  615. package/src/agent/orchestrator/smart-bridge/profiles.mjs +0 -37
  616. package/src/agent/orchestrator/smart-bridge/registry.mjs +0 -348
  617. package/src/agent/orchestrator/stall-policy.mjs +0 -201
  618. package/src/agent/orchestrator/tool-loop-guard.mjs +0 -75
  619. package/src/agent/orchestrator/tools/bash-session.mjs +0 -722
  620. package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +0 -171
  621. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +0 -511
  622. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -480
  623. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +0 -256
  624. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +0 -58
  625. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +0 -240
  626. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +0 -113
  627. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +0 -74
  628. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +0 -242
  629. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +0 -211
  630. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +0 -1364
  631. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +0 -126
  632. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +0 -141
  633. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +0 -194
  634. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +0 -60
  635. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +0 -168
  636. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +0 -173
  637. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +0 -48
  638. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -99
  639. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +0 -193
  640. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +0 -597
  641. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  642. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +0 -300
  643. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +0 -152
  644. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +0 -118
  645. package/src/agent/orchestrator/tools/builtin/read-open.mjs +0 -190
  646. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +0 -728
  647. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +0 -173
  648. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +0 -602
  649. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +0 -196
  650. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +0 -422
  651. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +0 -158
  652. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +0 -869
  653. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +0 -962
  654. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +0 -223
  655. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +0 -206
  656. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +0 -401
  657. package/src/agent/orchestrator/tools/builtin.mjs +0 -503
  658. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +0 -24
  659. package/src/agent/orchestrator/tools/code-graph.mjs +0 -4095
  660. package/src/agent/orchestrator/tools/cwd-tool.mjs +0 -298
  661. package/src/agent/orchestrator/tools/destructive-warning.mjs +0 -323
  662. package/src/agent/orchestrator/tools/edit-normalize.mjs +0 -603
  663. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +0 -154
  664. package/src/agent/orchestrator/tools/graph-manifest.json +0 -26
  665. package/src/agent/orchestrator/tools/host-input.mjs +0 -204
  666. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +0 -143
  667. package/src/agent/orchestrator/tools/patch-manifest.json +0 -26
  668. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +0 -20
  669. package/src/agent/orchestrator/tools/patch.mjs +0 -2754
  670. package/src/agent/orchestrator/tools/progress-message.mjs +0 -118
  671. package/src/agent/orchestrator/tools/shell-command.mjs +0 -865
  672. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +0 -7
  673. package/src/agent/orchestrator/tools/shell-snapshot.mjs +0 -313
  674. package/src/agent/orchestrator/workflow-store.mjs +0 -93
  675. package/src/agent/tool-defs.mjs +0 -110
  676. package/src/channels/backends/discord.mjs +0 -784
  677. package/src/channels/data/voice-runtime-manifest.json +0 -138
  678. package/src/channels/index.mjs +0 -3268
  679. package/src/channels/lib/config.mjs +0 -292
  680. package/src/channels/lib/drop-trace.mjs +0 -71
  681. package/src/channels/lib/event-pipeline.mjs +0 -81
  682. package/src/channels/lib/holidays.mjs +0 -138
  683. package/src/channels/lib/hook-pipe-server.mjs +0 -822
  684. package/src/channels/lib/output-forwarder.mjs +0 -765
  685. package/src/channels/lib/runtime-paths.mjs +0 -552
  686. package/src/channels/lib/scheduler.mjs +0 -723
  687. package/src/channels/lib/session-discovery.mjs +0 -103
  688. package/src/channels/lib/state-file.mjs +0 -68
  689. package/src/channels/lib/status-snapshot.mjs +0 -219
  690. package/src/channels/lib/tool-format.mjs +0 -140
  691. package/src/channels/lib/transcript-discovery.mjs +0 -195
  692. package/src/channels/lib/voice-runtime-fetcher.mjs +0 -734
  693. package/src/channels/lib/webhook.mjs +0 -1318
  694. package/src/channels/tool-defs.mjs +0 -170
  695. package/src/daemon/host.mjs +0 -118
  696. package/src/daemon/mcp-transport.mjs +0 -47
  697. package/src/daemon/session.mjs +0 -100
  698. package/src/daemon/thin-client.mjs +0 -71
  699. package/src/daemon/transport.mjs +0 -163
  700. package/src/gateway/claude-current.mjs +0 -255
  701. package/src/gateway/oauth-usage.mjs +0 -598
  702. package/src/gateway/route-meta.mjs +0 -629
  703. package/src/gateway/server.mjs +0 -713
  704. package/src/memory/data/runtime-manifest.json +0 -40
  705. package/src/memory/index.mjs +0 -3332
  706. package/src/memory/lib/core-memory-store.mjs +0 -330
  707. package/src/memory/lib/embedding-provider.mjs +0 -269
  708. package/src/memory/lib/embedding-worker.mjs +0 -323
  709. package/src/memory/lib/memory-cycle1.mjs +0 -645
  710. package/src/memory/lib/memory-cycle2.mjs +0 -1284
  711. package/src/memory/lib/memory-cycle3.mjs +0 -540
  712. package/src/memory/lib/memory-embed.mjs +0 -299
  713. package/src/memory/lib/memory-ops-policy.mjs +0 -190
  714. package/src/memory/lib/memory-recall-store.mjs +0 -638
  715. package/src/memory/lib/memory.mjs +0 -412
  716. package/src/memory/lib/pg/adapter.mjs +0 -308
  717. package/src/memory/lib/pg/process.mjs +0 -360
  718. package/src/memory/lib/pg/supervisor.mjs +0 -396
  719. package/src/memory/lib/runtime-fetcher.mjs +0 -458
  720. package/src/memory/lib/trace-store.mjs +0 -728
  721. package/src/memory/tool-defs.mjs +0 -79
  722. package/src/search/index.mjs +0 -1173
  723. package/src/search/lib/backends/anthropic-oauth.mjs +0 -98
  724. package/src/search/lib/backends/exa.mjs +0 -50
  725. package/src/search/lib/backends/firecrawl.mjs +0 -61
  726. package/src/search/lib/backends/gemini-api.mjs +0 -83
  727. package/src/search/lib/backends/grok-oauth.mjs +0 -86
  728. package/src/search/lib/backends/index.mjs +0 -150
  729. package/src/search/lib/backends/openai-api.mjs +0 -144
  730. package/src/search/lib/backends/openai-oauth.mjs +0 -102
  731. package/src/search/lib/backends/openai-web-search.mjs +0 -76
  732. package/src/search/lib/backends/tavily.mjs +0 -55
  733. package/src/search/lib/backends/xai-api.mjs +0 -113
  734. package/src/search/lib/config.mjs +0 -192
  735. package/src/search/lib/provider-usage.mjs +0 -67
  736. package/src/search/lib/providers.mjs +0 -47
  737. package/src/search/lib/search-intent.mjs +0 -109
  738. package/src/search/lib/setup-handler.mjs +0 -261
  739. package/src/search/lib/web-tools.mjs +0 -1219
  740. package/src/search/tool-defs.mjs +0 -83
  741. package/src/setup/defender-exclusion.mjs +0 -183
  742. package/src/shared/atomic-file.mjs +0 -436
  743. package/src/shared/config.mjs +0 -372
  744. package/src/shared/daemon-recycle.mjs +0 -108
  745. package/src/shared/disable-claude-builtins.mjs +0 -91
  746. package/src/shared/err-text.mjs +0 -12
  747. package/src/shared/llm/http-agent.mjs +0 -123
  748. package/src/shared/open-url.mjs +0 -62
  749. package/src/shared/plugin-paths.mjs +0 -58
  750. package/src/shared/schedules-store.mjs +0 -70
  751. package/src/shared/seed.mjs +0 -161
  752. package/src/shared/user-cwd.mjs +0 -225
  753. package/src/shared/user-data-guard.mjs +0 -244
  754. package/src/status/aggregator.mjs +0 -584
  755. package/src/status/server.mjs +0 -413
  756. package/tools.json +0 -1671
  757. /package/{defaults → src/defaults}/cycle3-review-prompt.md +0 -0
  758. /package/{defaults → src/defaults}/memory-promote-prompt.md +0 -0
  759. /package/{hooks → src/hooks}/lib/permission-rules.cjs +0 -0
  760. /package/{lib → src/lib}/config-cjs.cjs +0 -0
  761. /package/{lib → src/lib}/hook-pipe-path.cjs +0 -0
  762. /package/{lib → src/lib}/mixdog-debug.cjs +0 -0
  763. /package/{lib → src/lib}/text-utils.cjs +0 -0
  764. /package/{rules → src/rules}/bridge/42-cycle3-agent.md +0 -0
  765. /package/src/{agent → runtime/agent}/orchestrator/activity-bus.mjs +0 -0
  766. /package/src/{agent → runtime/agent}/orchestrator/providers/anthropic-betas.mjs +0 -0
  767. /package/src/{agent → runtime/agent}/orchestrator/session/abort-lookup.mjs +0 -0
  768. /package/src/{agent → runtime/agent}/orchestrator/session/cache/scoped-cache-outcome.mjs +0 -0
  769. /package/src/{agent → runtime/agent}/orchestrator/session/cache/util.mjs +0 -0
  770. /package/src/{agent → runtime/agent}/orchestrator/session/read-dedup.mjs +0 -0
  771. /package/src/{agent → runtime/agent}/orchestrator/session/save-session-worker.mjs +0 -0
  772. /package/src/{agent → runtime/agent}/orchestrator/smart-bridge/session-builder.mjs +0 -0
  773. /package/src/{agent → runtime/agent}/orchestrator/tools/bash-policy-scan.mjs +0 -0
  774. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/atomic-write.mjs +0 -0
  775. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/binary-file.mjs +0 -0
  776. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cache-layers.mjs +0 -0
  777. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cwd-utils.mjs +0 -0
  778. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/device-paths.mjs +0 -0
  779. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -0
  780. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diff-utils.mjs +0 -0
  781. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/fs-reachability.mjs +0 -0
  782. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/grep-formatting.mjs +0 -0
  783. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/hash-utils.mjs +0 -0
  784. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/list-formatting.mjs +0 -0
  785. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/open-config-tool.mjs +0 -0
  786. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-locks.mjs +0 -0
  787. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-utils.mjs +0 -0
  788. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-args.mjs +0 -0
  789. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-batch.mjs +0 -0
  790. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-constants.mjs +0 -0
  791. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image-resize.mjs +0 -0
  792. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image.mjs +0 -0
  793. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-lines.mjs +0 -0
  794. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-mode-tool.mjs +0 -0
  795. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-range-index.mjs +0 -0
  796. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-ranges.mjs +0 -0
  797. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-special-files.mjs +0 -0
  798. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-tool.mjs +0 -0
  799. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-windows.mjs +0 -0
  800. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-analysis.mjs +0 -0
  801. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-output.mjs +0 -0
  802. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-helpers.mjs +0 -0
  803. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-validation.mjs +0 -0
  804. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/text-stats.mjs +0 -0
  805. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/windows-roots.mjs +0 -0
  806. /package/src/{agent → runtime/agent}/orchestrator/tools/code-graph-prewarm-worker.mjs +0 -0
  807. /package/src/{agent → runtime/agent}/orchestrator/tools/env-scrub.mjs +0 -0
  808. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-content-cache.mjs +0 -0
  809. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-planner.mjs +0 -0
  810. /package/src/{agent → runtime/agent}/orchestrator/tools/next-call-utils.mjs +0 -0
  811. /package/src/{agent → runtime/agent}/orchestrator/tools/result-compression.mjs +0 -0
  812. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-exec-policy.mjs +0 -0
  813. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy-danger-target.mjs +0 -0
  814. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy.mjs +0 -0
  815. /package/src/{channels → runtime/channels}/lib/cli-worker-host.mjs +0 -0
  816. /package/src/{channels → runtime/channels}/lib/config-lock.mjs +0 -0
  817. /package/src/{channels → runtime/channels}/lib/event-queue.mjs +0 -0
  818. /package/src/{channels → runtime/channels}/lib/executor.mjs +0 -0
  819. /package/src/{channels → runtime/channels}/lib/format.mjs +0 -0
  820. /package/src/{channels → runtime/channels}/lib/interaction-workflows.mjs +0 -0
  821. /package/src/{channels → runtime/channels}/lib/memory-client.mjs +0 -0
  822. /package/src/{channels → runtime/channels}/lib/session-control.mjs +0 -0
  823. /package/src/{channels → runtime/channels}/lib/settings.mjs +0 -0
  824. /package/src/{channels → runtime/channels}/lib/whisper-server.mjs +0 -0
  825. /package/src/{memory → runtime/memory}/lib/agent-ipc.mjs +0 -0
  826. /package/src/{memory → runtime/memory}/lib/bridge-trace-queries.mjs +0 -0
  827. /package/src/{memory → runtime/memory}/lib/llm-worker-host.mjs +0 -0
  828. /package/src/{memory → runtime/memory}/lib/memory-cycle.mjs +0 -0
  829. /package/src/{memory → runtime/memory}/lib/memory-extraction.mjs +0 -0
  830. /package/src/{memory → runtime/memory}/lib/memory-maintenance-store.mjs +0 -0
  831. /package/src/{memory → runtime/memory}/lib/memory-recall-id-patch.mjs +0 -0
  832. /package/src/{memory → runtime/memory}/lib/memory-recall-read-query.mjs +0 -0
  833. /package/src/{memory → runtime/memory}/lib/memory-recall-scope-filter.mjs +0 -0
  834. /package/src/{memory → runtime/memory}/lib/memory-retrievers.mjs +0 -0
  835. /package/src/{memory → runtime/memory}/lib/memory-score.mjs +0 -0
  836. /package/src/{memory → runtime/memory}/lib/memory-text-utils.mjs +0 -0
  837. /package/src/{memory → runtime/memory}/lib/model-profile.mjs +0 -0
  838. /package/src/{memory → runtime/memory}/lib/project-id-resolver.mjs +0 -0
  839. /package/src/{search → runtime/search}/lib/cache.mjs +0 -0
  840. /package/src/{search → runtime/search}/lib/formatter.mjs +0 -0
  841. /package/src/{search → runtime/search}/lib/state.mjs +0 -0
  842. /package/src/{shared → runtime/shared}/abort-controller.mjs +0 -0
  843. /package/src/{shared → runtime/shared}/llm/cost.mjs +0 -0
  844. /package/src/{shared → runtime/shared}/llm/index.mjs +0 -0
  845. /package/src/{shared → runtime/shared}/llm/pid-cleanup.mjs +0 -0
  846. /package/src/{shared → runtime/shared}/llm/usage-log.mjs +0 -0
  847. /package/src/{shared → runtime/shared}/wsl.mjs +0 -0
@@ -1,1219 +0,0 @@
1
- import fs, { readFileSync } from 'fs'
2
- import dns from 'dns'
3
- import net from 'net'
4
- import { Agent, fetch as undiciFetch } from 'undici'
5
-
6
- import { JSDOM } from 'jsdom'
7
- import puppeteer from 'puppeteer-core'
8
- import { Readability } from '@mozilla/readability'
9
- import { isWSL } from '../../shared/wsl.mjs'
10
-
11
-
12
- const PKG_VERSION = (() => { try { return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version } catch { return '0.0.1' } })()
13
- import {
14
- noteProviderFailure,
15
- noteProviderSuccess,
16
- rankScrapeExtractors,
17
- classifyProviderError,
18
- } from './state.mjs'
19
-
20
- const DEFAULT_EXTRACTORS = ['readability', 'puppeteer']
21
-
22
- const COMMON_BROWSER_PATHS = (() => {
23
- const platform = process.platform
24
- if (platform === 'win32') {
25
- // Derive install roots from the environment so non-C: installs and the
26
- // per-user %LOCALAPPDATA% Chrome install are covered. Fall back to the
27
- // canonical C: paths (well-known locations, not guessed defaults) when an
28
- // env var is unset.
29
- const localAppData = process.env.LOCALAPPDATA
30
- const programFiles = process.env.PROGRAMFILES || 'C:/Program Files'
31
- const programFilesX86 = process.env['PROGRAMFILES(X86)'] || 'C:/Program Files (x86)'
32
- return [
33
- `${programFiles}/Google/Chrome/Application/chrome.exe`,
34
- `${programFilesX86}/Google/Chrome/Application/chrome.exe`,
35
- localAppData && `${localAppData}/Google/Chrome/Application/chrome.exe`,
36
- `${programFiles}/Microsoft/Edge/Application/msedge.exe`,
37
- `${programFilesX86}/Microsoft/Edge/Application/msedge.exe`,
38
- localAppData && `${localAppData}/Microsoft/Edge/Application/msedge.exe`,
39
- ].filter(Boolean)
40
- }
41
- if (platform === 'linux') {
42
- // Native-Linux Chromium/Chrome binaries first. The /mnt/c Windows .exe
43
- // entries are reachable from WSL's filesystem but puppeteer-core CANNOT
44
- // drive a Windows GUI browser as a Linux child process (CDP over a pipe to
45
- // a Win32 binary launched from the Linux ABI does not work), so advertising
46
- // puppeteer-available off a Windows .exe yields launch failures at runtime.
47
- // Only offer the Windows .exe fallbacks on plain Linux (Wine/dual-mount
48
- // edge cases), never under WSL.
49
- const linuxNative = [
50
- '/usr/bin/google-chrome',
51
- '/usr/bin/google-chrome-stable',
52
- '/usr/bin/chromium',
53
- '/usr/bin/chromium-browser',
54
- '/snap/bin/chromium',
55
- '/usr/bin/microsoft-edge',
56
- ]
57
- if (isWSL()) return linuxNative
58
- return [
59
- ...linuxNative,
60
- '/mnt/c/Program Files/Google/Chrome/Application/chrome.exe',
61
- '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe',
62
- '/mnt/c/Program Files/Microsoft/Edge/Application/msedge.exe',
63
- '/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
64
- ]
65
- }
66
- return [
67
- '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
68
- '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
69
- '/Applications/Chromium.app/Contents/MacOS/Chromium',
70
- '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
71
- ]
72
- })()
73
-
74
- export function getScrapeCapabilities() {
75
- const browserAvailable = Boolean(
76
- (process.env.PUPPETEER_EXECUTABLE_PATH && fs.existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) ||
77
- COMMON_BROWSER_PATHS.some(item => fs.existsSync(item)),
78
- )
79
-
80
- return {
81
- readability: true,
82
- puppeteer: browserAvailable,
83
- }
84
- }
85
-
86
- function normalizeUrl(url) {
87
- const parsed = new URL(url)
88
- parsed.hash = ''
89
- return parsed.toString()
90
- }
91
-
92
- function assertPrivateIpv4(hostname) {
93
- const ipv4Match = hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
94
- if (!ipv4Match) return
95
- const [, a, b] = ipv4Match.map(Number)
96
- if (a === 127 || a === 10 || a === 0 ||
97
- (a === 172 && b >= 16 && b <= 31) ||
98
- (a === 192 && b === 168) ||
99
- (a === 169 && b === 254) ||
100
- (a === 100 && b >= 64 && b <= 127) ||
101
- (a === 198 && b >= 18 && b <= 19) ||
102
- (a >= 224 && a <= 239) ||
103
- (a >= 240)) {
104
- throw new Error(`Blocked request to private address: ${hostname}`)
105
- }
106
- }
107
-
108
- // Detect IPv4-mapped IPv6 (::ffff:/96) in BOTH dotted and hex forms and
109
- // return the embedded IPv4 as a dotted-quad string, or null when the input
110
- // is not an IPv4-mapped address. WHATWG URL canonicalises `[::ffff:127.0.0.1]`
111
- // to `[::ffff:7f00:1]`, so the hex form must be handled or assertPublicUrl /
112
- // _validateIpv6 will miss mapped loopback / private addresses.
113
- function _mappedIpv4FromIpv6(bare) {
114
- const lower = bare.toLowerCase()
115
- // Dotted form: ::ffff:a.b.c.d
116
- const dotted = lower.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/)
117
- if (dotted) return dotted[1]
118
- // Hex form: ::ffff:HHHH:LLLL — low 32 bits of the /96 prefix carry the IPv4.
119
- const hex = lower.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/)
120
- if (hex) {
121
- const high = parseInt(hex[1], 16)
122
- const low = parseInt(hex[2], 16)
123
- if (Number.isFinite(high) && Number.isFinite(low) && high <= 0xffff && low <= 0xffff) {
124
- const a = (high >> 8) & 0xff
125
- const b = high & 0xff
126
- const c = (low >> 8) & 0xff
127
- const d = low & 0xff
128
- return `${a}.${b}.${c}.${d}`
129
- }
130
- }
131
- return null
132
- }
133
-
134
- export function assertPublicUrl(url) {
135
- const parsed = new URL(url)
136
-
137
- // Block dangerous protocols
138
- const blockedProtocols = ['file:', 'ftp:', 'data:', 'javascript:']
139
- if (blockedProtocols.includes(parsed.protocol)) {
140
- throw new Error(`Blocked non-HTTP protocol: ${parsed.protocol}`)
141
- }
142
- if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
143
- throw new Error(`Blocked non-HTTP protocol: ${parsed.protocol}`)
144
- }
145
-
146
- const hostname = parsed.hostname.toLowerCase()
147
-
148
- // Reject userinfo (user:pass@host) — credential-injection / SSRF vector
149
- if (parsed.username || parsed.password) {
150
- throw new Error(`Blocked URL with userinfo credentials: ${hostname}`)
151
- }
152
-
153
- // Localhost
154
- if (hostname === 'localhost') {
155
- throw new Error(`Blocked request to private address: ${hostname}`)
156
- }
157
-
158
- // IPv4 private/reserved ranges
159
- assertPrivateIpv4(hostname)
160
-
161
- // Strip brackets for IPv6 analysis (URL parser stores IPv6 without brackets in .hostname)
162
- const bare = hostname.startsWith('[') ? hostname.slice(1, -1) : hostname
163
-
164
- // IPv6 loopback
165
- if (bare === '::1') {
166
- throw new Error(`Blocked request to private address: ${hostname}`)
167
- }
168
-
169
- // IPv6 unspecified (::)
170
- if (bare === '::') {
171
- throw new Error(`Blocked request to private address: ${hostname}`)
172
- }
173
-
174
- // IPv6 multicast (ff00::/8)
175
- if (/^ff/i.test(bare)) {
176
- throw new Error(`Blocked request to private address: ${hostname}`)
177
- }
178
-
179
- // IPv4-mapped IPv6 — ::ffff:a.b.c.d
180
- // Cover both dotted (::ffff:127.0.0.1) and hex (::ffff:7f00:1) forms —
181
- // WHATWG URL canonicalises bracketed mapped literals to the hex shape.
182
- const mappedIpv4 = _mappedIpv4FromIpv6(bare)
183
- if (mappedIpv4) {
184
- assertPrivateIpv4(mappedIpv4)
185
- }
186
-
187
- // IPv6 private (fc00::/7 — starts with fc or fd)
188
- if (/^f[cd]/i.test(bare)) {
189
- throw new Error(`Blocked request to private address: ${hostname}`)
190
- }
191
-
192
- // IPv6 link-local (fe80::/10 — starts with fe8, fe9, fea, feb)
193
- if (/^fe[89ab]/i.test(bare)) {
194
- throw new Error(`Blocked request to private address: ${hostname}`)
195
- }
196
- }
197
-
198
- function _validateIpv6(ip) {
199
- const lower = ip.toLowerCase()
200
- if (lower === '::1') {
201
- throw new Error(`Blocked request to private address: ${ip}`)
202
- }
203
- if (lower === '::') {
204
- throw new Error(`Blocked request to private address: ${ip}`)
205
- }
206
- if (/^ff/i.test(lower)) {
207
- throw new Error(`Blocked request to private address: ${ip}`)
208
- }
209
- if (/^f[cd]/i.test(lower)) {
210
- throw new Error(`Blocked request to private address: ${ip}`)
211
- }
212
- if (/^fe[89ab]/i.test(lower)) {
213
- throw new Error(`Blocked request to private address: ${ip}`)
214
- }
215
- // Cover both dotted and hex IPv4-mapped IPv6 forms — resolver output and
216
- // WHATWG-canonicalised URL hostnames may arrive as `::ffff:7f00:1`.
217
- const mappedIpv4 = _mappedIpv4FromIpv6(lower)
218
- if (mappedIpv4) {
219
- assertPrivateIpv4(mappedIpv4)
220
- }
221
- }
222
-
223
- // Resolve hostname once, validate EVERY returned address (so a DNS round-robin
224
- // can't smuggle a private IP behind a public one), and return the de-duped
225
- // `{address, family}` list. The caller pins the real connection to one of
226
- // these addresses so a second uncontrolled resolution (DNS rebinding / TOCTOU)
227
- // cannot flip the IP between validation and connect.
228
- // Race a DNS promise against an abort signal so a hung resolver cannot
229
- // outlive the request's timeout budget. The signal is the same one that
230
- // bounds the outbound fetch (AbortSignal.timeout / requestTimeoutMs), so
231
- // DNS is bounded by the same deadline as the connection.
232
- function _abortRace(promise, signal, label) {
233
- if (!signal) return promise
234
- if (signal.aborted) return Promise.reject(signal.reason || new Error(`${label} aborted`))
235
- return new Promise((resolve, reject) => {
236
- const onAbort = () => reject(signal.reason || new Error(`${label} aborted`))
237
- signal.addEventListener('abort', onAbort, { once: true })
238
- promise.then(
239
- (value) => { signal.removeEventListener('abort', onAbort); resolve(value) },
240
- (err) => { signal.removeEventListener('abort', onAbort); reject(err) },
241
- )
242
- })
243
- }
244
-
245
- export async function resolveAndValidate(hostname, { signal } = {}) {
246
- // Literal IPs bypass DNS entirely — validate directly.
247
- if (net.isIP(hostname)) {
248
- if (net.isIPv4(hostname)) {
249
- assertPrivateIpv4(hostname)
250
- return [{ address: hostname, family: 4 }]
251
- }
252
- _validateIpv6(hostname)
253
- return [{ address: hostname, family: 6 }]
254
- }
255
-
256
- const addresses = []
257
- const seen = new Set()
258
- const push = (address, family) => {
259
- const key = `${family}:${address}`
260
- if (seen.has(key)) return
261
- seen.add(key)
262
- addresses.push({ address, family })
263
- }
264
-
265
- // dns.lookup mirrors what the platform resolver will hand to the connector;
266
- // resolve4/resolve6 catch entries the stub resolver returns even when the
267
- // OS lookup table would omit them.
268
- let lookupAddrs = []
269
- try {
270
- lookupAddrs = await _abortRace(dns.promises.lookup(hostname, { all: true }), signal, 'dns.lookup')
271
- } catch (err) {
272
- if (err.code !== 'ENODATA' && err.code !== 'ENOTFOUND') throw err
273
- }
274
- for (const entry of lookupAddrs) {
275
- if (entry.family === 4) assertPrivateIpv4(entry.address)
276
- else _validateIpv6(entry.address)
277
- push(entry.address, entry.family)
278
- }
279
-
280
- let v4Addrs = []
281
- try {
282
- v4Addrs = await _abortRace(dns.promises.resolve4(hostname), signal, 'dns.resolve4')
283
- } catch (err) {
284
- if (err.code !== 'ENODATA' && err.code !== 'ENOTFOUND') throw err
285
- }
286
- for (const ip of v4Addrs) {
287
- assertPrivateIpv4(ip)
288
- push(ip, 4)
289
- }
290
-
291
- let v6Addrs = []
292
- try {
293
- v6Addrs = await _abortRace(dns.promises.resolve6(hostname), signal, 'dns.resolve6')
294
- } catch (err) {
295
- if (err.code !== 'ENODATA' && err.code !== 'ENOTFOUND') throw err
296
- }
297
- for (const ip of v6Addrs) {
298
- _validateIpv6(ip)
299
- push(ip, 6)
300
- }
301
-
302
- return addresses
303
- }
304
-
305
- export async function assertResolvedIps(hostname) {
306
- // Backward-compatible wrapper: callers that only need validation (e.g. the
307
- // Puppeteer request interceptor, which cannot pin Chromium's connect) still
308
- // get the same throw-on-private behaviour.
309
- // Fail closed: an empty result (no DNS records, all lookups returned
310
- // ENODATA/ENOTFOUND) must NOT be treated as success — the Puppeteer path
311
- // would otherwise hand the raw hostname to Chromium for a second,
312
- // unvalidated resolution.
313
- // Callers pass `new URL(...).hostname`, which on Node/Bun keeps the
314
- // brackets around IPv6 literals (e.g. `[2606:4700::1111]`). Strip them
315
- // here so resolveAndValidate's net.isIP() path recognises the literal
316
- // instead of falling through to a doomed DNS lookup on `[..]`.
317
- const bare = _bareHost(hostname)
318
- const addresses = await resolveAndValidate(bare)
319
- if (!addresses || addresses.length === 0) {
320
- throw new Error(`DNS returned no addresses for ${hostname}`)
321
- }
322
- }
323
-
324
- // Bare hostname helper that strips IPv6 brackets — undici / WHATWG URL stores
325
- // IPv6 hostnames with the brackets included.
326
- function _bareHost(hostname) {
327
- return hostname.startsWith('[') ? hostname.slice(1, -1) : hostname
328
- }
329
-
330
- // SSRF-hardened fetch: resolves the host ONCE, validates every returned
331
- // address, then connects to a single pre-validated IP via a per-request
332
- // undici Agent whose `connect.lookup` returns that IP only. This closes the
333
- // validate-then-fetch TOCTOU / DNS-rebinding window because the connector
334
- // never performs a second resolution against the live DNS — the Host header
335
- // (undici fills from the URL) and TLS SNI (likewise) are unaffected, so
336
- // virtual hosts and HTTPS certificate validation keep working against
337
- // legitimate public sites.
338
- export async function pinnedFetch(url, options = {}) {
339
- const parsed = new URL(url)
340
- const host = _bareHost(parsed.hostname)
341
- // Bound the validating DNS lookups by the request's own abort signal so a
342
- // hung resolver cannot outlive the fetch timeout.
343
- const addresses = await resolveAndValidate(host, { signal: options.signal })
344
- if (addresses.length === 0) {
345
- throw new Error(`DNS returned no addresses for ${host}`)
346
- }
347
- // Deterministic: pin to the first validated address. Every entry in
348
- // `addresses` already passed assertPrivateIpv4 / IPv6 checks, so picking any
349
- // index is safe — first-match keeps behaviour stable across calls.
350
- const pinned = addresses[0]
351
- const dispatcher = new Agent({
352
- connect: {
353
- // Custom lookup invoked by undici's connector. We ignore the requested
354
- // hostname argument and unconditionally hand back the pre-validated IP,
355
- // so DNS rebinding cannot flip the address between assert and connect.
356
- lookup: (_hostname, opts, cb) => {
357
- if (opts && opts.all) {
358
- cb(null, [{ address: pinned.address, family: pinned.family }])
359
- } else {
360
- cb(null, pinned.address, pinned.family)
361
- }
362
- },
363
- },
364
- })
365
- // The per-request Agent owns a dedicated connection pool. If it is never
366
- // closed it leaks the kept-alive socket until GC. Destroy it once the body
367
- // is fully consumed, cancelled, or the request errors — wrapping the body
368
- // stream so the dispatcher outlives streaming reads but is always reclaimed.
369
- let response
370
- try {
371
- response = await undiciFetch(url, { ...options, dispatcher })
372
- } catch (err) {
373
- dispatcher.destroy().catch(() => {})
374
- throw err
375
- }
376
- let cleaned = false
377
- const cleanup = () => { if (!cleaned) { cleaned = true; dispatcher.destroy().catch(() => {}) } }
378
- // If there's no body to stream, the response is already complete.
379
- if (!response.body) {
380
- cleanup()
381
- return response
382
- }
383
- // Wrap the body in a ReadableStream that pulls from the original reader and
384
- // destroys the dispatcher when the stream ends, errors, or the consumer
385
- // cancels it. ReadableStream's underlying-source pull/cancel callbacks are
386
- // reliably invoked, so the per-request Agent is always reclaimed instead of
387
- // leaking its kept-alive socket until GC.
388
- const reader = response.body.getReader()
389
- const monitored = new ReadableStream({
390
- async pull(controller) {
391
- try {
392
- const { done, value } = await reader.read()
393
- if (done) {
394
- controller.close()
395
- cleanup()
396
- return
397
- }
398
- controller.enqueue(value)
399
- } catch (err) {
400
- controller.error(err)
401
- cleanup()
402
- }
403
- },
404
- cancel(reason) {
405
- reader.cancel(reason).catch(() => {})
406
- cleanup()
407
- },
408
- })
409
- return new Response(monitored, {
410
- status: response.status,
411
- statusText: response.statusText,
412
- headers: response.headers,
413
- })
414
- }
415
-
416
- function withTimeout(controller, timeoutMs) {
417
- return setTimeout(() => controller.abort(), timeoutMs)
418
- }
419
-
420
- function buildHeaders() {
421
- return {
422
- 'User-Agent': `mixdog-search/${PKG_VERSION}`,
423
- }
424
- }
425
-
426
- function buildContentPayload(url, title, content, extractor, extra = {}) {
427
- // Whitespace-normalize extracted text so blank-line runs from page layout
428
- // don't eat the caller's maxLength window. Per-line interior spacing is
429
- // preserved (code blocks / <pre> stay intact) — only trailing spaces and
430
- // 3+ consecutive newlines are collapsed.
431
- const normalized = (content || '')
432
- .replace(/[ \t]+\n/g, '\n')
433
- .replace(/\n{3,}/g, '\n\n')
434
- .trim()
435
- if (!normalized) {
436
- throw new Error(`${extractor} returned empty content`)
437
- }
438
- return {
439
- url,
440
- title: (title || '').trim(),
441
- content: normalized,
442
- excerpt: normalized.slice(0, 240),
443
- extractor,
444
- ...extra,
445
- }
446
- }
447
-
448
- function extractReadableArticle(url, html) {
449
- const dom = new JSDOM(html, { url })
450
- try {
451
- const doc = dom.window.document
452
- // <head> social/preview images: Readability + textContent strip every tag,
453
- // so og:image / twitter:image never survive text extraction. Capture them
454
- // here and prepend as labelled lines so callers get the image URL without a
455
- // second (native) fetch — closes the readability-drops-meta gap.
456
- const metaImg = (sel) => doc.querySelector(sel)?.getAttribute('content')?.trim() || ''
457
- const ogImage = metaImg('meta[property="og:image"]') || metaImg('meta[name="og:image"]') || metaImg('meta[property="og:image:url"]')
458
- const twImage = metaImg('meta[name="twitter:image"]') || metaImg('meta[property="twitter:image"]') || metaImg('meta[name="twitter:image:src"]')
459
- const _imgLines = []
460
- if (ogImage) _imgLines.push(`og:image: ${ogImage}`)
461
- if (twImage && twImage !== ogImage) _imgLines.push(`twitter:image: ${twImage}`)
462
- const imgPrefix = _imgLines.length ? `${_imgLines.join('\n')}\n\n` : ''
463
- const reader = new Readability(doc)
464
- const article = reader.parse()
465
- if (article?.textContent?.trim()) {
466
- return buildContentPayload(
467
- url,
468
- article.title || doc.title || '',
469
- imgPrefix + article.textContent,
470
- 'readability',
471
- )
472
- }
473
-
474
- // Readability failed to find an article; fall back to the raw body text.
475
- // body.textContent concatenates script/style/template content and chrome
476
- // (nav/header/footer/aside) verbatim, which floods the result with noise.
477
- // Drop those non-content elements first so the fallback yields readable
478
- // prose rather than inlined JS/CSS and boilerplate.
479
- const body = dom.window.document.body
480
- let bodyText = ''
481
- if (body) {
482
- for (const node of body.querySelectorAll('script, style, noscript, template, nav, header, footer, aside, [hidden], [aria-hidden="true"]')) {
483
- node.remove()
484
- }
485
- bodyText = body.textContent?.trim() || ''
486
- }
487
- if (!bodyText) {
488
- throw new Error('readability returned no readable body')
489
- }
490
-
491
- return buildContentPayload(
492
- url,
493
- doc.title || '',
494
- imgPrefix + bodyText,
495
- 'dom-text',
496
- )
497
- } finally {
498
- dom.window.close()
499
- }
500
- }
501
-
502
- const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308])
503
- const MAX_REDIRECTS = 5
504
- // Hard cap on response body size (10 MB) to prevent memory DoS from a
505
- // hostile / misconfigured URL returning a huge body. Applied in two places:
506
- // 1. Content-Length pre-check (cheap reject before reading bytes).
507
- // 2. Streaming byte counter (covers chunked transfer / missing header).
508
- const MAX_BODY_BYTES = 10 * 1024 * 1024
509
-
510
- /** HTTP-path policy failures must not fall through to the Puppeteer extractor. */
511
- export function isFatalHttpPathPolicyError(error) {
512
- const msg = error instanceof Error ? error.message : String(error)
513
- if (/response body too large|page content too large|Content-Length=.*> cap=/i.test(msg)) return true
514
- if (/Blocked non-text content-type/i.test(msg)) return true
515
- if (/cross-host redirect blocked/i.test(msg)) return true
516
- if (/Blocked request to private|Blocked non-HTTP|Blocked URL with userinfo/i.test(msg)) return true
517
- if (/DNS returned no addresses/i.test(msg)) return true
518
- if (/Too many redirects/i.test(msg)) return true
519
- return false
520
- }
521
-
522
- async function readBodyWithCap(response, maxBytes) {
523
- // Reject non-text content-types early; decode by content-type charset.
524
- const contentType = (response.headers.get('content-type') || '').toLowerCase()
525
- if (contentType) {
526
- const isText = contentType.includes('text/') || contentType.includes('/html') ||
527
- contentType.includes('/xml') || contentType.includes('/json') ||
528
- contentType.includes('javascript') || contentType.includes('application/x-www-form-urlencoded')
529
- if (!isText) {
530
- // Cancel body before throwing so the underlying socket isn't held
531
- // until GC — fetchHtml's caller would otherwise leak the connection.
532
- try { await response.body?.cancel() } catch {}
533
- throw new Error(`Blocked non-text content-type: ${contentType.split(';')[0].trim()}`)
534
- }
535
- }
536
- const charsetMatch = contentType.match(/charset=([\w-]+)/i)
537
- const charset = charsetMatch ? charsetMatch[1] : 'utf-8'
538
-
539
- const contentLength = Number(response.headers.get('content-length') || 0)
540
- if (contentLength > maxBytes) {
541
- try { await response.body?.cancel() } catch {}
542
- throw new Error(`response body too large: Content-Length=${contentLength} > cap=${maxBytes}`)
543
- }
544
- const reader = response.body?.getReader?.()
545
- if (!reader) {
546
- // Fallback for environments without a readable stream — post-check length.
547
- const text = await response.text()
548
- if (text.length > maxBytes) {
549
- // response.text() already drained the body, but guard symmetrically.
550
- try { await response.body?.cancel() } catch {}
551
- throw new Error(`response body too large: ${text.length} bytes > cap=${maxBytes}`)
552
- }
553
- return text
554
- }
555
- const chunks = []
556
- let total = 0
557
- try {
558
- while (true) {
559
- const { done, value } = await reader.read()
560
- if (done) break
561
- total += value.byteLength
562
- if (total > maxBytes) {
563
- try { await reader.cancel() } catch {}
564
- throw new Error(`response body too large: received ${total}+ bytes > cap=${maxBytes}`)
565
- }
566
- chunks.push(value)
567
- }
568
- } finally {
569
- try { reader.releaseLock() } catch {}
570
- }
571
- const decoder = new TextDecoder(charset, { fatal: false })
572
- let text = ''
573
- for (const chunk of chunks) text += decoder.decode(chunk, { stream: true })
574
- text += decoder.decode()
575
- return text
576
- }
577
-
578
- /** Binary-safe body reader for CDP Fetch fulfillment (no text-only filter). */
579
- async function readBodyBytesWithCap(response, maxBytes) {
580
- const contentLength = Number(response.headers.get('content-length') || 0)
581
- if (contentLength > maxBytes) {
582
- try { await response.body?.cancel() } catch {}
583
- throw new Error(`response body too large: Content-Length=${contentLength} > cap=${maxBytes}`)
584
- }
585
- const reader = response.body?.getReader?.()
586
- if (!reader) {
587
- const buf = Buffer.from(await response.arrayBuffer())
588
- if (buf.byteLength > maxBytes) {
589
- try { await response.body?.cancel() } catch {}
590
- throw new Error(`response body too large: ${buf.byteLength} bytes > cap=${maxBytes}`)
591
- }
592
- return buf
593
- }
594
- const chunks = []
595
- let total = 0
596
- try {
597
- while (true) {
598
- const { done, value } = await reader.read()
599
- if (done) break
600
- total += value.byteLength
601
- if (total > maxBytes) {
602
- try { await reader.cancel() } catch {}
603
- throw new Error(`response body too large: received ${total}+ bytes > cap=${maxBytes}`)
604
- }
605
- chunks.push(value)
606
- }
607
- } finally {
608
- try { reader.releaseLock() } catch {}
609
- }
610
- return Buffer.concat(chunks.map((c) => Buffer.from(c)))
611
- }
612
-
613
- const CDP_FORBIDDEN_RESPONSE_HEADERS = new Set([
614
- 'content-length',
615
- 'transfer-encoding',
616
- // undici decodes gzip/br/deflate; body passed to fulfillRequest is plain bytes
617
- 'content-encoding',
618
- ])
619
-
620
- function headersToCdpPairs(headers) {
621
- const out = []
622
- headers.forEach((value, name) => {
623
- const lower = name.toLowerCase()
624
- if (CDP_FORBIDDEN_RESPONSE_HEADERS.has(lower)) return
625
- out.push({ name, value })
626
- })
627
- return out
628
- }
629
-
630
- /**
631
- * Pinned fetch for a paused Chromium request: validate each hop, follow redirects,
632
- * return bytes for Fetch.fulfillRequest. Chromium never performs its own DNS/connect.
633
- */
634
- async function fetchPinnedForPausedRequest(url, { signal, method = 'GET', headers = {}, body } = {}) {
635
- const upperMethod = (method || 'GET').toUpperCase()
636
- let currentUrl = url
637
- for (let hops = 0; ; hops++) {
638
- assertPublicUrl(currentUrl)
639
- const response = await pinnedFetch(currentUrl, {
640
- signal,
641
- method: upperMethod,
642
- headers,
643
- body: hops === 0 ? body : undefined,
644
- redirect: 'manual',
645
- })
646
- if (REDIRECT_STATUSES.has(response.status)) {
647
- try { await response.body?.cancel() } catch {}
648
- if (hops >= MAX_REDIRECTS) {
649
- throw new Error(`Too many redirects (max ${MAX_REDIRECTS})`)
650
- }
651
- const location = response.headers.get('location')
652
- if (!location) {
653
- throw new Error(`Redirect ${response.status} without Location header`)
654
- }
655
- currentUrl = new URL(location, currentUrl).toString()
656
- continue
657
- }
658
- const body = await readBodyBytesWithCap(response, MAX_BODY_BYTES)
659
- return {
660
- status: response.status,
661
- responseHeaders: headersToCdpPairs(response.headers),
662
- body,
663
- }
664
- }
665
- }
666
-
667
- async function fetchHtml(url, timeoutMs, signal) {
668
- const controller = new AbortController()
669
- const timer = withTimeout(controller, timeoutMs)
670
- // Propagate an external (tool-call) abort into the local timeout controller
671
- // so a cancelled web_fetch tears down the in-flight request promptly.
672
- let onExternalAbort
673
- if (signal) {
674
- if (signal.aborted) controller.abort(signal.reason)
675
- else {
676
- onExternalAbort = () => controller.abort(signal.reason)
677
- signal.addEventListener('abort', onExternalAbort, { once: true })
678
- }
679
- }
680
- const originalHost = new URL(url).hostname.replace(/^www\./, '')
681
- try {
682
- let currentUrl = url
683
- for (let hops = 0; ; hops++) {
684
- // pinnedFetch resolves+validates the host once and forces the
685
- // connection to the validated IP — closes the validate-then-fetch
686
- // TOCTOU / DNS-rebinding window that bare `fetch` left open.
687
- const response = await pinnedFetch(currentUrl, {
688
- signal: controller.signal,
689
- headers: buildHeaders(),
690
- redirect: 'manual',
691
- })
692
- if (REDIRECT_STATUSES.has(response.status)) {
693
- // Drain the redirect response body so the socket isn't held until GC.
694
- try { await response.body?.cancel() } catch {}
695
- if (hops >= MAX_REDIRECTS) {
696
- throw new Error(`Too many redirects (max ${MAX_REDIRECTS})`)
697
- }
698
- const location = response.headers.get('location')
699
- if (!location) {
700
- throw new Error(`Redirect ${response.status} without Location header`)
701
- }
702
- const nextUrl = new URL(location, currentUrl).toString()
703
- assertPublicUrl(nextUrl)
704
- const nextHost = new URL(nextUrl).hostname.replace(/^www\./, '')
705
- if (nextHost !== originalHost) {
706
- throw new Error(`cross-host redirect blocked (redirected_to: ${nextUrl})`)
707
- }
708
- currentUrl = nextUrl
709
- continue
710
- }
711
- if (!response.ok) {
712
- // Drain the error response body before propagating.
713
- try { await response.body?.cancel() } catch {}
714
- const err = new Error(`HTTP ${response.status}`)
715
- err.status = response.status
716
- throw err
717
- }
718
- return await readBodyWithCap(response, MAX_BODY_BYTES)
719
- }
720
- } finally {
721
- clearTimeout(timer)
722
- if (onExternalAbort) signal.removeEventListener('abort', onExternalAbort)
723
- }
724
- }
725
-
726
- // Parse a short-delay <meta http-equiv="refresh" content="N; url=..."> from
727
- // the document head. Browsers treat these as redirects, but fetchHtml only
728
- // follows HTTP-level (3xx) redirects — without this, a stub page like
729
- // tree-sitter.github.io (tiny body + meta refresh) is returned as the
730
- // "article". Long-delay refreshes (>5s) are page auto-reloads, not
731
- // redirects, and are deliberately NOT followed.
732
- function _metaRefreshTarget(html, baseUrl) {
733
- const head = String(html || '').slice(0, 8192)
734
- const tags = head.match(/<meta\b[^>]*>/gi) || []
735
- for (const tag of tags) {
736
- if (!/http-equiv\s*=\s*["']?refresh\b/i.test(tag)) continue
737
- // Quote-aware capture: the attribute value may NEST the other quote kind
738
- // (content="0; url='...'"), so a combined ["'] char class would cut the
739
- // capture at the inner quote. Match each quote style to its own closer.
740
- const m = /content\s*=\s*"([^"]*)"/i.exec(tag)
741
- || /content\s*=\s*'([^']*)'/i.exec(tag)
742
- || /content\s*=\s*([^\s>]+)/i.exec(tag)
743
- if (!m) continue
744
- const cm = /^\s*(\d+(?:\.\d+)?)\s*[;,]\s*url\s*=\s*['"]?([^'"]+?)['"]?\s*$/i.exec(m[1])
745
- if (!cm) continue
746
- const delay = Number(cm[1])
747
- if (!Number.isFinite(delay) || delay > 5) continue
748
- try {
749
- const resolved = new URL(cm[2].trim(), baseUrl)
750
- if (resolved.protocol !== 'http:' && resolved.protocol !== 'https:') continue
751
- if (resolved.href === baseUrl) continue
752
- return resolved.href
753
- } catch { continue }
754
- }
755
- return null
756
- }
757
-
758
- async function scrapeWithReadability(url, timeoutMs, signal) {
759
- let currentUrl = url
760
- let html = await fetchHtml(currentUrl, timeoutMs, signal)
761
- // Bounded meta-refresh chase: each hop re-enters fetchHtml, so the
762
- // SSRF/public-URL validation applies to every target.
763
- for (let hop = 0; hop < 3; hop += 1) {
764
- const target = _metaRefreshTarget(html, currentUrl)
765
- if (!target) break
766
- currentUrl = target
767
- html = await fetchHtml(currentUrl, timeoutMs, signal)
768
- }
769
- return extractReadableArticle(currentUrl, html)
770
- }
771
-
772
- function resolveBrowserLaunchOptions() {
773
- if (process.env.PUPPETEER_EXECUTABLE_PATH && fs.existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) {
774
- return { executablePath: process.env.PUPPETEER_EXECUTABLE_PATH }
775
- }
776
-
777
- for (const executablePath of COMMON_BROWSER_PATHS) {
778
- if (fs.existsSync(executablePath)) {
779
- return { executablePath }
780
- }
781
- }
782
-
783
- return { channel: 'chrome' }
784
- }
785
-
786
- function puppeteerNoSandboxEnabled() {
787
- const raw = (process.env.PUPPETEER_NO_SANDBOX || process.env.MIXDOG_PUPPETEER_NO_SANDBOX || '').trim().toLowerCase()
788
- return raw === '1' || raw === 'true' || raw === 'yes'
789
- }
790
-
791
- function buildPuppeteerLaunchArgs() {
792
- const args = ['--disable-dev-shm-usage']
793
- if (puppeteerNoSandboxEnabled()) args.push('--no-sandbox')
794
- return args
795
- }
796
-
797
- const PUPPETEER_POOL_MAX_PAGES = Math.max(1, Number(process.env.PUPPETEER_POOL_MAX_PAGES) || 3)
798
- const PUPPETEER_POOL_IDLE_MS = Math.max(5_000, Number(process.env.PUPPETEER_POOL_IDLE_MS) || 60_000)
799
-
800
- let _poolBrowser = null
801
- let _poolLaunching = null
802
- let _poolActive = 0
803
- let _poolLastActivity = Date.now()
804
- let _poolIdleTimer = null
805
- const _poolWaiters = []
806
-
807
- function _notifyPoolWaiter() {
808
- const next = _poolWaiters.shift()
809
- if (next) next()
810
- }
811
-
812
- async function _acquirePoolSlot() {
813
- while (_poolActive >= PUPPETEER_POOL_MAX_PAGES) {
814
- await new Promise((resolve) => _poolWaiters.push(resolve))
815
- }
816
- _poolActive++
817
- _poolLastActivity = Date.now()
818
- if (_poolIdleTimer) {
819
- clearTimeout(_poolIdleTimer)
820
- _poolIdleTimer = null
821
- }
822
- }
823
-
824
- function _releasePoolSlot() {
825
- _poolActive = Math.max(0, _poolActive - 1)
826
- _poolLastActivity = Date.now()
827
- _notifyPoolWaiter()
828
- if (_poolActive === 0 && _poolBrowser) {
829
- _poolIdleTimer = setTimeout(() => {
830
- if (_poolActive === 0 && _poolBrowser) {
831
- const b = _poolBrowser
832
- _poolBrowser = null
833
- closeBrowserBounded(b).catch(() => {})
834
- }
835
- }, PUPPETEER_POOL_IDLE_MS)
836
- }
837
- }
838
-
839
- async function _getPoolBrowser() {
840
- if (_poolBrowser && _poolBrowser.isConnected?.() === false) {
841
- _poolBrowser = null
842
- }
843
- if (_poolBrowser) return _poolBrowser
844
- if (!_poolLaunching) {
845
- _poolLaunching = puppeteer.launch({
846
- headless: true,
847
- ...resolveBrowserLaunchOptions(),
848
- args: buildPuppeteerLaunchArgs(),
849
- }).then((browser) => {
850
- _poolBrowser = browser
851
- browser.on('disconnected', () => {
852
- if (_poolBrowser === browser) _poolBrowser = null
853
- })
854
- return browser
855
- }).finally(() => {
856
- _poolLaunching = null
857
- })
858
- }
859
- return _poolLaunching
860
- }
861
-
862
- // SSRF + DNS pin: CDP Fetch pauses every request; Node pinnedFetch connects to
863
- // the validated IP and Fetch.fulfillRequest returns the body so Chromium never
864
- // performs its own DNS for response bytes. Redirects and subresources each
865
- // re-enter requestPaused and are validated again (fail-closed on block).
866
- async function installPuppeteerSsrfGate(_page, cdp, signal) {
867
- await cdp.send('Fetch.enable', {
868
- handleAuthRequests: false,
869
- patterns: [{ urlPattern: '*', requestStage: 'Request' }],
870
- })
871
- cdp.on('Fetch.requestPaused', (event) => {
872
- void (async () => {
873
- const { requestId, request } = event
874
- try {
875
- const reqUrl = request?.url
876
- if (!reqUrl) {
877
- await cdp.send('Fetch.failRequest', { requestId, errorReason: 'Failed' })
878
- return
879
- }
880
- const reqHeaders = { ...buildHeaders() }
881
- if (Array.isArray(request.headers)) {
882
- for (const entry of request.headers) {
883
- if (entry?.name) reqHeaders[entry.name] = entry.value ?? ''
884
- }
885
- } else if (request.headers && typeof request.headers === 'object') {
886
- for (const [name, value] of Object.entries(request.headers)) {
887
- reqHeaders[name] = value
888
- }
889
- }
890
- const fetchOpts = {
891
- signal,
892
- method: request.method || 'GET',
893
- headers: reqHeaders,
894
- }
895
- if (request.postData) fetchOpts.body = request.postData
896
- const result = await fetchPinnedForPausedRequest(reqUrl, fetchOpts)
897
- await cdp.send('Fetch.fulfillRequest', {
898
- requestId,
899
- responseCode: result.status,
900
- responseHeaders: result.responseHeaders,
901
- body: result.body.toString('base64'),
902
- })
903
- } catch {
904
- try {
905
- await cdp.send('Fetch.failRequest', { requestId, errorReason: 'Failed' })
906
- } catch {}
907
- }
908
- })()
909
- })
910
- }
911
-
912
- // Bounded browser teardown: browser.close() can hang if the Chromium process
913
- // is wedged, which would leak the process and pin the timeout budget. Race the
914
- // graceful close against a deadline and fall back to killing the OS process so
915
- // the browser is always reclaimed.
916
- async function closeBrowserBounded(browser, timeoutMs = 5000) {
917
- if (!browser) return
918
- let timer
919
- try {
920
- await Promise.race([
921
- browser.close().catch(() => {}),
922
- new Promise((resolve) => { timer = setTimeout(resolve, timeoutMs) }),
923
- ])
924
- } finally {
925
- if (timer) clearTimeout(timer)
926
- try {
927
- const proc = browser.process?.()
928
- if (proc && proc.exitCode === null && !proc.killed) proc.kill('SIGKILL')
929
- } catch {}
930
- }
931
- }
932
-
933
- async function withPuppeteerPage(signal, fn) {
934
- await _acquirePoolSlot()
935
- let browser
936
- let context
937
- let page
938
- let cdp
939
- let onExternalAbort
940
- try {
941
- try {
942
- browser = await _getPoolBrowser()
943
- } catch (error) {
944
- throw new Error(`puppeteer launch failed: ${error instanceof Error ? error.message : String(error)}`)
945
- }
946
- if (signal?.aborted) throw signal.reason || new Error('aborted')
947
- if (signal) {
948
- onExternalAbort = () => { closeBrowserBounded(browser) }
949
- signal.addEventListener('abort', onExternalAbort, { once: true })
950
- }
951
- context = await browser.createBrowserContext()
952
- page = await context.newPage()
953
- cdp = await page.createCDPSession()
954
- await installPuppeteerSsrfGate(page, cdp, signal)
955
- return await fn(page)
956
- } finally {
957
- if (onExternalAbort && signal) signal.removeEventListener('abort', onExternalAbort)
958
- try { await page?.close() } catch {}
959
- try { await context?.close() } catch {}
960
- _releasePoolSlot()
961
- }
962
- }
963
-
964
- async function scrapeWithPuppeteer(url, timeoutMs, signal) {
965
- return withPuppeteerPage(signal, async (page) => {
966
- const resp = await page.goto(url, {
967
- waitUntil: 'networkidle2',
968
- timeout: timeoutMs,
969
- })
970
- if (!resp || !resp.ok()) {
971
- const status = resp?.status?.() ?? 'unknown'
972
- const err = new Error(`HTTP ${status}`)
973
- err.status = typeof status === 'number' ? status : undefined
974
- throw err
975
- }
976
- const finalUrl = page.url()
977
- assertPublicUrl(finalUrl)
978
- await assertResolvedIps(new URL(finalUrl).hostname)
979
- const html = await page.content()
980
- const htmlBytes = Buffer.byteLength(html, 'utf8')
981
- if (htmlBytes > MAX_BODY_BYTES) {
982
- throw new Error(`puppeteer page content too large: ${htmlBytes} bytes > cap=${MAX_BODY_BYTES}`)
983
- }
984
- try {
985
- return {
986
- ...extractReadableArticle(url, html),
987
- extractor: 'puppeteer',
988
- }
989
- } catch {
990
- const bodyText = await page.evaluate(() => document.body?.innerText || '')
991
- return buildContentPayload(url, await page.title(), bodyText, 'puppeteer')
992
- }
993
- })
994
- }
995
-
996
- async function tryExtractor(extractor, url, timeoutMs, signal) {
997
- switch (extractor) {
998
- case 'readability':
999
- return scrapeWithReadability(url, timeoutMs, signal)
1000
- case 'puppeteer':
1001
- return scrapeWithPuppeteer(url, timeoutMs, signal)
1002
- default:
1003
- throw new Error(`Unknown extractor: ${extractor}`)
1004
- }
1005
- }
1006
-
1007
- function filterLinks(rawLinks, baseUrl, { limit = 50, sameDomainOnly = true, search }) {
1008
- const originHost = new URL(baseUrl).host
1009
- const items = []
1010
- const seen = new Set()
1011
-
1012
- for (const rawLink of rawLinks) {
1013
- const href = rawLink?.href
1014
- if (!href) continue
1015
-
1016
- let absolute
1017
- try {
1018
- absolute = normalizeUrl(new URL(href, baseUrl).toString())
1019
- } catch {
1020
- continue
1021
- }
1022
-
1023
- if (sameDomainOnly && new URL(absolute).host !== originHost) {
1024
- continue
1025
- }
1026
-
1027
- const text = (rawLink.text || '').trim()
1028
- if (search && !absolute.includes(search) && !text.includes(search)) {
1029
- continue
1030
- }
1031
-
1032
- if (seen.has(absolute)) continue
1033
- seen.add(absolute)
1034
- items.push({ url: absolute, text })
1035
- if (items.length >= limit) break
1036
- }
1037
-
1038
- return items
1039
- }
1040
-
1041
- function extractLinksFromHtml(baseUrl, html, options) {
1042
- const dom = new JSDOM(html, { url: baseUrl })
1043
- try {
1044
- const links = Array.from(dom.window.document.querySelectorAll('a[href]')).map(link => ({
1045
- href: link.getAttribute('href'),
1046
- text: link.textContent || '',
1047
- }))
1048
- return filterLinks(links, baseUrl, options)
1049
- } finally {
1050
- dom.window.close()
1051
- }
1052
- }
1053
-
1054
- async function mapWithHttp(url, options, timeoutMs, signal) {
1055
- const html = await fetchHtml(url, timeoutMs, signal)
1056
- return extractLinksFromHtml(url, html, options)
1057
- }
1058
-
1059
- async function mapWithPuppeteer(url, options, timeoutMs, signal) {
1060
- return withPuppeteerPage(signal, async (page) => {
1061
- await page.goto(url, {
1062
- waitUntil: 'networkidle2',
1063
- timeout: timeoutMs,
1064
- })
1065
- const finalUrl = page.url()
1066
- assertPublicUrl(finalUrl)
1067
- await assertResolvedIps(new URL(finalUrl).hostname)
1068
- const links = await page.$$eval('a[href]', nodes => nodes.map(node => ({
1069
- href: node.getAttribute('href'),
1070
- text: node.textContent || '',
1071
- })))
1072
- return filterLinks(links, url, options)
1073
- })
1074
- }
1075
-
1076
- export async function scrapeUrl(url, timeoutMs, usageState, signal) {
1077
- const normalizedUrl = normalizeUrl(url)
1078
- const host = new URL(normalizedUrl).host
1079
- const extractors = rankScrapeExtractors(host, usageState, DEFAULT_EXTRACTORS)
1080
- const failures = []
1081
-
1082
- for (const extractor of extractors) {
1083
- if (extractor === 'puppeteer') {
1084
- try {
1085
- await fetchHtml(normalizedUrl, timeoutMs, signal)
1086
- } catch (error) {
1087
- if (isFatalHttpPathPolicyError(error)) {
1088
- const message = error instanceof Error ? error.message : String(error)
1089
- failures.push({ extractor: 'http-policy', error: message })
1090
- const err = error instanceof Error ? error : new Error(message)
1091
- err.failures = failures
1092
- throw err
1093
- }
1094
- }
1095
- }
1096
- try {
1097
- const page = await tryExtractor(extractor, normalizedUrl, timeoutMs, signal)
1098
- noteProviderSuccess(usageState, extractor)
1099
- return {
1100
- ...page,
1101
- triedExtractors: extractors,
1102
- failures,
1103
- }
1104
- } catch (error) {
1105
- const message = error instanceof Error ? error.message : String(error)
1106
- failures.push({ extractor, error: message })
1107
- if (extractor === 'readability' && isFatalHttpPathPolicyError(error)) {
1108
- const err = error instanceof Error ? error : new Error(message)
1109
- err.failures = failures
1110
- throw err
1111
- }
1112
- const errorKind = classifyProviderError(error)
1113
- noteProviderFailure(usageState, extractor, message, errorKind)
1114
- }
1115
- }
1116
-
1117
- throw new Error(`All extractors failed for ${normalizedUrl}: ${failures.map(item => `${item.extractor}: ${item.error}`).join(' | ')}`)
1118
- }
1119
-
1120
- export async function scrapeUrls(urls, timeoutMs, usageState, signal) {
1121
- for (const url of urls) assertPublicUrl(url)
1122
- const settled = await Promise.allSettled(urls.map(url => scrapeUrl(url, timeoutMs, usageState, signal)))
1123
- return settled.map((result, index) => {
1124
- if (result.status === 'fulfilled') {
1125
- return result.value
1126
- }
1127
- return {
1128
- url: urls[index],
1129
- error: result.reason instanceof Error ? result.reason.message : String(result.reason),
1130
- }
1131
- })
1132
- }
1133
-
1134
- async function mapSite(url, { limit = 50, sameDomainOnly = true, search }, timeoutMs, signal) {
1135
- assertPublicUrl(url)
1136
- const options = { limit, sameDomainOnly, search }
1137
- try {
1138
- const links = await mapWithHttp(url, options, timeoutMs, signal)
1139
- if (links.length > 0) {
1140
- return links
1141
- }
1142
- } catch (error) {
1143
- if (isFatalHttpPathPolicyError(error)) throw error
1144
- }
1145
-
1146
- return mapWithPuppeteer(url, options, timeoutMs, signal)
1147
- }
1148
-
1149
- export async function crawlSite(
1150
- startUrl,
1151
- { maxPages = 10, maxDepth = 1, sameDomainOnly = true },
1152
- timeoutMs,
1153
- usageState,
1154
- signal,
1155
- ) {
1156
- assertPublicUrl(startUrl)
1157
- const visited = new Set()
1158
- const queue = [{ url: normalizeUrl(startUrl), depth: 0 }]
1159
- const pages = []
1160
-
1161
- while (queue.length > 0 && pages.length < maxPages) {
1162
- const current = queue.shift()
1163
- if (!current || visited.has(current.url)) continue
1164
- visited.add(current.url)
1165
-
1166
- try {
1167
- const page = await scrapeUrl(current.url, timeoutMs, usageState, signal)
1168
- pages.push({
1169
- url: current.url,
1170
- depth: current.depth,
1171
- title: page.title,
1172
- excerpt: page.excerpt,
1173
- extractor: page.extractor,
1174
- })
1175
- } catch (error) {
1176
- pages.push({
1177
- url: current.url,
1178
- depth: current.depth,
1179
- error: error instanceof Error ? error.message : String(error),
1180
- })
1181
- continue
1182
- }
1183
-
1184
- if (current.depth >= maxDepth) {
1185
- continue
1186
- }
1187
-
1188
- let links = []
1189
- try {
1190
- links = await mapSite(
1191
- current.url,
1192
- {
1193
- limit: maxPages,
1194
- sameDomainOnly,
1195
- },
1196
- timeoutMs,
1197
- signal,
1198
- )
1199
- } catch {
1200
- links = []
1201
- }
1202
-
1203
- for (const link of links) {
1204
- if (!visited.has(link.url)) {
1205
- try {
1206
- assertPublicUrl(link.url)
1207
- } catch {
1208
- continue
1209
- }
1210
- queue.push({
1211
- url: link.url,
1212
- depth: current.depth + 1,
1213
- })
1214
- }
1215
- }
1216
- }
1217
-
1218
- return pages
1219
- }