mixdog 0.7.17 → 0.8.0

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