mixdog 0.7.18 → 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 (844) 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/http-agent.mjs +129 -0
  179. package/src/runtime/shared/open-url.mjs +37 -0
  180. package/src/runtime/shared/plugin-paths.mjs +25 -0
  181. package/src/runtime/shared/process-shutdown.mjs +147 -0
  182. package/src/runtime/shared/schedules-store.mjs +70 -0
  183. package/src/runtime/shared/tool-execution-contract.mjs +104 -0
  184. package/src/runtime/shared/tool-surface.mjs +950 -0
  185. package/src/runtime/shared/user-cwd.mjs +221 -0
  186. package/src/runtime/shared/user-data-guard.mjs +232 -0
  187. package/src/runtime/shared/workspace-router.mjs +259 -0
  188. package/src/standalone/bridge-tool.mjs +1414 -0
  189. package/src/standalone/channel-admin.mjs +366 -0
  190. package/src/standalone/channel-worker-preload.cjs +3 -0
  191. package/src/standalone/channel-worker.mjs +353 -0
  192. package/src/standalone/explore-tool.mjs +233 -0
  193. package/src/standalone/hook-bus.mjs +246 -0
  194. package/src/standalone/plugin-admin.mjs +247 -0
  195. package/src/standalone/provider-admin.mjs +338 -0
  196. package/src/standalone/seeds.mjs +94 -0
  197. package/src/standalone/usage-dashboard.mjs +510 -0
  198. package/src/tui/App.jsx +5438 -0
  199. package/src/tui/components/AnsiText.jsx +199 -0
  200. package/src/tui/components/ContextPanel.jsx +217 -0
  201. package/src/tui/components/Markdown.jsx +205 -0
  202. package/src/tui/components/MarkdownTable.jsx +204 -0
  203. package/src/tui/components/Message.jsx +103 -0
  204. package/src/tui/components/Picker.jsx +317 -0
  205. package/src/tui/components/PromptInput.jsx +584 -0
  206. package/src/tui/components/QueuedCommands.jsx +47 -0
  207. package/src/tui/components/SlashCommandPalette.jsx +114 -0
  208. package/src/tui/components/Spinner.jsx +317 -0
  209. package/src/tui/components/StatusLine.jsx +87 -0
  210. package/src/tui/components/TextEntryPanel.jsx +323 -0
  211. package/src/tui/components/ToolExecution.jsx +772 -0
  212. package/src/tui/components/TurnDone.jsx +78 -0
  213. package/src/tui/components/UsagePanel.jsx +331 -0
  214. package/src/tui/dist/index.mjs +12359 -0
  215. package/src/tui/engine.mjs +2410 -0
  216. package/src/tui/figures.mjs +50 -0
  217. package/src/tui/hooks/useEngine.mjs +16 -0
  218. package/src/tui/index.jsx +254 -0
  219. package/src/tui/input-editing.mjs +242 -0
  220. package/src/tui/markdown/format-token.mjs +194 -0
  221. package/src/tui/paste-attachments.mjs +198 -0
  222. package/src/tui/runtime/shared/process-shutdown.mjs +1 -0
  223. package/src/tui/spinner-verbs.mjs +45 -0
  224. package/src/tui/theme.mjs +67 -0
  225. package/src/tui/time-format.mjs +53 -0
  226. package/src/ui/ansi.mjs +115 -0
  227. package/src/ui/markdown.mjs +195 -0
  228. package/src/ui/statusline.mjs +730 -0
  229. package/src/ui/tool-card.mjs +101 -0
  230. package/src/vendor/statusline/bin/statusline-lib.mjs +805 -0
  231. package/src/vendor/statusline/bin/statusline-route.mjs +596 -0
  232. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +285 -0
  233. package/src/vendor/statusline/src/gateway/claude-current.mjs +320 -0
  234. package/src/vendor/statusline/src/gateway/route-meta.mjs +753 -0
  235. package/src/vendor/statusline/src/gateway/session-routes.mjs +244 -0
  236. package/src/workflows/default/WORKFLOW.md +7 -0
  237. package/src/workflows/default/workflow.json +14 -0
  238. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  239. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  240. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  241. package/vendor/ink/build/colorize.d.ts +3 -0
  242. package/vendor/ink/build/colorize.js +48 -0
  243. package/vendor/ink/build/colorize.js.map +1 -0
  244. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  245. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  246. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  247. package/vendor/ink/build/components/AnimationContext.d.ts +9 -0
  248. package/vendor/ink/build/components/AnimationContext.js +13 -0
  249. package/vendor/ink/build/components/AnimationContext.js.map +1 -0
  250. package/vendor/ink/build/components/App.d.ts +24 -0
  251. package/vendor/ink/build/components/App.js +554 -0
  252. package/vendor/ink/build/components/App.js.map +1 -0
  253. package/vendor/ink/build/components/AppContext.d.ts +80 -0
  254. package/vendor/ink/build/components/AppContext.js +25 -0
  255. package/vendor/ink/build/components/AppContext.js.map +1 -0
  256. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  257. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  258. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  259. package/vendor/ink/build/components/Box.d.ts +130 -0
  260. package/vendor/ink/build/components/Box.js +34 -0
  261. package/vendor/ink/build/components/Box.js.map +1 -0
  262. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  263. package/vendor/ink/build/components/CursorContext.js +8 -0
  264. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  265. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  266. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  267. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  268. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  269. package/vendor/ink/build/components/ErrorOverview.js +90 -0
  270. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  271. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  272. package/vendor/ink/build/components/FocusContext.js +17 -0
  273. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  274. package/vendor/ink/build/components/Newline.d.ts +13 -0
  275. package/vendor/ink/build/components/Newline.js +8 -0
  276. package/vendor/ink/build/components/Newline.js.map +1 -0
  277. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  278. package/vendor/ink/build/components/Spacer.js +11 -0
  279. package/vendor/ink/build/components/Spacer.js.map +1 -0
  280. package/vendor/ink/build/components/Static.d.ts +24 -0
  281. package/vendor/ink/build/components/Static.js +28 -0
  282. package/vendor/ink/build/components/Static.js.map +1 -0
  283. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  284. package/vendor/ink/build/components/StderrContext.js +13 -0
  285. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  286. package/vendor/ink/build/components/StdinContext.d.ts +28 -0
  287. package/vendor/ink/build/components/StdinContext.js +20 -0
  288. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  289. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  290. package/vendor/ink/build/components/StdoutContext.js +13 -0
  291. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  292. package/vendor/ink/build/components/Text.d.ts +55 -0
  293. package/vendor/ink/build/components/Text.js +50 -0
  294. package/vendor/ink/build/components/Text.js.map +1 -0
  295. package/vendor/ink/build/components/Transform.d.ts +16 -0
  296. package/vendor/ink/build/components/Transform.js +15 -0
  297. package/vendor/ink/build/components/Transform.js.map +1 -0
  298. package/vendor/ink/build/cursor-helpers.d.ts +39 -0
  299. package/vendor/ink/build/cursor-helpers.js +62 -0
  300. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  301. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  302. package/vendor/ink/build/devtools-window-polyfill.js +68 -0
  303. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  304. package/vendor/ink/build/devtools.d.ts +1 -0
  305. package/vendor/ink/build/devtools.js +36 -0
  306. package/vendor/ink/build/devtools.js.map +1 -0
  307. package/vendor/ink/build/dom.d.ts +62 -0
  308. package/vendor/ink/build/dom.js +143 -0
  309. package/vendor/ink/build/dom.js.map +1 -0
  310. package/vendor/ink/build/get-max-width.d.ts +3 -0
  311. package/vendor/ink/build/get-max-width.js +10 -0
  312. package/vendor/ink/build/get-max-width.js.map +1 -0
  313. package/vendor/ink/build/hooks/use-animation.d.ts +49 -0
  314. package/vendor/ink/build/hooks/use-animation.js +87 -0
  315. package/vendor/ink/build/hooks/use-animation.js.map +1 -0
  316. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  317. package/vendor/ink/build/hooks/use-app.js +8 -0
  318. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  319. package/vendor/ink/build/hooks/use-box-metrics.d.ts +59 -0
  320. package/vendor/ink/build/hooks/use-box-metrics.js +81 -0
  321. package/vendor/ink/build/hooks/use-box-metrics.js.map +1 -0
  322. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  323. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  324. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  325. package/vendor/ink/build/hooks/use-focus-manager.d.ts +43 -0
  326. package/vendor/ink/build/hooks/use-focus-manager.js +18 -0
  327. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  328. package/vendor/ink/build/hooks/use-focus.d.ts +30 -0
  329. package/vendor/ink/build/hooks/use-focus.js +43 -0
  330. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  331. package/vendor/ink/build/hooks/use-input.d.ts +132 -0
  332. package/vendor/ink/build/hooks/use-input.js +126 -0
  333. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  334. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +6 -0
  335. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +12 -0
  336. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  337. package/vendor/ink/build/hooks/use-paste.d.ts +35 -0
  338. package/vendor/ink/build/hooks/use-paste.js +62 -0
  339. package/vendor/ink/build/hooks/use-paste.js.map +1 -0
  340. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  341. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  342. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  343. package/vendor/ink/build/hooks/use-stdin.d.ts +7 -0
  344. package/vendor/ink/build/hooks/use-stdin.js +9 -0
  345. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  346. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  347. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  348. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  349. package/vendor/ink/build/hooks/use-window-size.d.ts +18 -0
  350. package/vendor/ink/build/hooks/use-window-size.js +22 -0
  351. package/vendor/ink/build/hooks/use-window-size.js.map +1 -0
  352. package/vendor/ink/build/index.d.ts +42 -0
  353. package/vendor/ink/build/index.js +24 -0
  354. package/vendor/ink/build/index.js.map +1 -0
  355. package/vendor/ink/build/ink.d.ts +146 -0
  356. package/vendor/ink/build/ink.js +1022 -0
  357. package/vendor/ink/build/ink.js.map +1 -0
  358. package/vendor/ink/build/input-parser.d.ts +10 -0
  359. package/vendor/ink/build/input-parser.js +194 -0
  360. package/vendor/ink/build/input-parser.js.map +1 -0
  361. package/vendor/ink/build/instances.d.ts +3 -0
  362. package/vendor/ink/build/instances.js +8 -0
  363. package/vendor/ink/build/instances.js.map +1 -0
  364. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  365. package/vendor/ink/build/kitty-keyboard.js +32 -0
  366. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  367. package/vendor/ink/build/log-update.d.ts +20 -0
  368. package/vendor/ink/build/log-update.js +261 -0
  369. package/vendor/ink/build/log-update.js.map +1 -0
  370. package/vendor/ink/build/measure-element.d.ts +20 -0
  371. package/vendor/ink/build/measure-element.js +13 -0
  372. package/vendor/ink/build/measure-element.js.map +1 -0
  373. package/vendor/ink/build/measure-text.d.ts +6 -0
  374. package/vendor/ink/build/measure-text.js +21 -0
  375. package/vendor/ink/build/measure-text.js.map +1 -0
  376. package/vendor/ink/build/output.d.ts +35 -0
  377. package/vendor/ink/build/output.js +328 -0
  378. package/vendor/ink/build/output.js.map +1 -0
  379. package/vendor/ink/build/parse-keypress.d.ts +20 -0
  380. package/vendor/ink/build/parse-keypress.js +495 -0
  381. package/vendor/ink/build/parse-keypress.js.map +1 -0
  382. package/vendor/ink/build/reconciler.d.ts +4 -0
  383. package/vendor/ink/build/reconciler.js +306 -0
  384. package/vendor/ink/build/reconciler.js.map +1 -0
  385. package/vendor/ink/build/render-background.d.ts +4 -0
  386. package/vendor/ink/build/render-background.js +25 -0
  387. package/vendor/ink/build/render-background.js.map +1 -0
  388. package/vendor/ink/build/render-border.d.ts +4 -0
  389. package/vendor/ink/build/render-border.js +84 -0
  390. package/vendor/ink/build/render-border.js.map +1 -0
  391. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  392. package/vendor/ink/build/render-node-to-output.js +162 -0
  393. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  394. package/vendor/ink/build/render-to-string.d.ts +38 -0
  395. package/vendor/ink/build/render-to-string.js +116 -0
  396. package/vendor/ink/build/render-to-string.js.map +1 -0
  397. package/vendor/ink/build/render.d.ts +176 -0
  398. package/vendor/ink/build/render.js +71 -0
  399. package/vendor/ink/build/render.js.map +1 -0
  400. package/vendor/ink/build/renderer.d.ts +8 -0
  401. package/vendor/ink/build/renderer.js +64 -0
  402. package/vendor/ink/build/renderer.js.map +1 -0
  403. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  404. package/vendor/ink/build/sanitize-ansi.js +27 -0
  405. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  406. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  407. package/vendor/ink/build/squash-text-nodes.js +36 -0
  408. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  409. package/vendor/ink/build/styles.d.ts +302 -0
  410. package/vendor/ink/build/styles.js +303 -0
  411. package/vendor/ink/build/styles.js.map +1 -0
  412. package/vendor/ink/build/utils.d.ts +9 -0
  413. package/vendor/ink/build/utils.js +19 -0
  414. package/vendor/ink/build/utils.js.map +1 -0
  415. package/vendor/ink/build/wrap-text.d.ts +3 -0
  416. package/vendor/ink/build/wrap-text.js +38 -0
  417. package/vendor/ink/build/wrap-text.js.map +1 -0
  418. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  419. package/vendor/ink/build/write-synchronized.js +9 -0
  420. package/vendor/ink/build/write-synchronized.js.map +1 -0
  421. package/vendor/ink/license +10 -0
  422. package/vendor/ink/package.json +137 -0
  423. package/.claude-plugin/marketplace.json +0 -34
  424. package/.claude-plugin/plugin.json +0 -20
  425. package/.gitattributes +0 -34
  426. package/.mcp.json +0 -14
  427. package/ARCHITECTURE.md +0 -77
  428. package/CHANGELOG.md +0 -30
  429. package/CONTRIBUTING.md +0 -45
  430. package/DATA-FLOW.md +0 -79
  431. package/LICENSE +0 -21
  432. package/SECURITY.md +0 -138
  433. package/UNINSTALL.md +0 -115
  434. package/agents/maintenance.md +0 -5
  435. package/agents/memory-classification.md +0 -30
  436. package/agents/scheduler-task.md +0 -18
  437. package/agents/webhook-handler.md +0 -27
  438. package/agents/worker.md +0 -24
  439. package/bin/bridge +0 -133
  440. package/bin/statusline-launcher.mjs +0 -82
  441. package/bin/statusline-lib.mjs +0 -581
  442. package/bin/statusline-route.mjs +0 -273
  443. package/bin/statusline.mjs +0 -638
  444. package/bun.lock +0 -927
  445. package/commands/config.md +0 -16
  446. package/commands/doctor.md +0 -13
  447. package/commands/model.md +0 -61
  448. package/commands/setup.md +0 -17
  449. package/defaults/hidden-roles.json +0 -68
  450. package/defaults/memory-chunk-prompt.md +0 -63
  451. package/defaults/mixdog-config.template.json +0 -27
  452. package/defaults/user-workflow.json +0 -8
  453. package/defaults/user-workflow.md +0 -17
  454. package/hooks/hooks.json +0 -73
  455. package/hooks/lib/active-instance.cjs +0 -77
  456. package/hooks/lib/permission-evaluator.cjs +0 -411
  457. package/hooks/lib/permission-route.cjs +0 -63
  458. package/hooks/lib/settings-loader.cjs +0 -117
  459. package/hooks/post-tool-use.cjs +0 -84
  460. package/hooks/pre-mcp-sandbox.cjs +0 -158
  461. package/hooks/pre-tool-subagent.cjs +0 -258
  462. package/hooks/session-start.cjs +0 -1493
  463. package/hooks/shim-launcher.cjs +0 -65
  464. package/hooks/turn-timer.cjs +0 -82
  465. package/lib/claude-md-writer.cjs +0 -386
  466. package/lib/keychain-cjs.cjs +0 -290
  467. package/lib/plugin-paths.cjs +0 -69
  468. package/lib/rules-builder.cjs +0 -241
  469. package/native/README.md +0 -117
  470. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  471. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  472. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  473. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  474. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  475. package/prompts/code-review.txt +0 -16
  476. package/prompts/security-audit.txt +0 -17
  477. package/rules/bridge/00-common.md +0 -39
  478. package/rules/bridge/20-skip-protocol.md +0 -18
  479. package/rules/bridge/30-explorer.md +0 -33
  480. package/rules/bridge/40-cycle1-agent.md +0 -52
  481. package/rules/bridge/41-cycle2-agent.md +0 -62
  482. package/rules/lead/00-tool-lead.md +0 -61
  483. package/rules/lead/01-general.md +0 -26
  484. package/rules/lead/02-channels.md +0 -49
  485. package/rules/lead/03-team.md +0 -27
  486. package/rules/lead/04-workflow.md +0 -20
  487. package/rules/shared/00-language.md +0 -14
  488. package/rules/shared/01-tool.md +0 -138
  489. package/scripts/bootstrap.mjs +0 -130
  490. package/scripts/bridge-unify-smoke.mjs +0 -308
  491. package/scripts/build-runtime-linux.sh +0 -348
  492. package/scripts/build-runtime-macos.sh +0 -217
  493. package/scripts/build-runtime-windows.ps1 +0 -242
  494. package/scripts/builtin-utils-smoke.mjs +0 -398
  495. package/scripts/bump.mjs +0 -80
  496. package/scripts/check-json.mjs +0 -45
  497. package/scripts/check-syntax-changed.mjs +0 -102
  498. package/scripts/check-syntax.mjs +0 -58
  499. package/scripts/code-graph-batch.test.mjs +0 -33
  500. package/scripts/config-preserve-smoke.mjs +0 -180
  501. package/scripts/doctor.mjs +0 -489
  502. package/scripts/edit-normalize-fuzz.mjs +0 -130
  503. package/scripts/edit-normalize-smoke.mjs +0 -401
  504. package/scripts/edit-operation-smoke.mjs +0 -369
  505. package/scripts/edit2-smoke.mjs +0 -63
  506. package/scripts/ensure-deps.mjs +0 -259
  507. package/scripts/fuzzy-e2e.mjs +0 -28
  508. package/scripts/fuzzy-smoke.mjs +0 -26
  509. package/scripts/gateway-model.mjs +0 -596
  510. package/scripts/generate-runtime-manifest.mjs +0 -166
  511. package/scripts/guard-smoke.mjs +0 -66
  512. package/scripts/hidden-role-schema-smoke.mjs +0 -162
  513. package/scripts/hook-routing-smoke.mjs +0 -29
  514. package/scripts/inject-input.ps1 +0 -204
  515. package/scripts/io-complex-smoke.mjs +0 -667
  516. package/scripts/io-explore-bench.mjs +0 -424
  517. package/scripts/io-guardrails-smoke.mjs +0 -205
  518. package/scripts/io-mini-bench-baseline.json +0 -11
  519. package/scripts/io-mini-bench.mjs +0 -216
  520. package/scripts/io-route-harness.mjs +0 -933
  521. package/scripts/io-telemetry-report.mjs +0 -691
  522. package/scripts/lib/gateway-inventory.mjs +0 -178
  523. package/scripts/lib/gateway-settings.mjs +0 -78
  524. package/scripts/mutation-bench.mjs +0 -564
  525. package/scripts/mutation-io-smoke.mjs +0 -1097
  526. package/scripts/native-patch-bridge-smoke.mjs +0 -288
  527. package/scripts/native-patch-smoke.mjs +0 -304
  528. package/scripts/openai-oauth-catalog-smoke.mjs +0 -53
  529. package/scripts/patch-interior-context-smoke.mjs +0 -49
  530. package/scripts/patch-newline-utf8-smoke.mjs +0 -157
  531. package/scripts/perf-hook-smoke.mjs +0 -71
  532. package/scripts/permission-eval-smoke.mjs +0 -443
  533. package/scripts/prep-patch.mjs +0 -53
  534. package/scripts/prep-shim.mjs +0 -96
  535. package/scripts/provider-cache-smoke.mjs +0 -687
  536. package/scripts/report-runtime-health.mjs +0 -132
  537. package/scripts/resolve-bun.mjs +0 -60
  538. package/scripts/run-mcp.mjs +0 -1473
  539. package/scripts/salvage-v4a-shatter.test.mjs +0 -58
  540. package/scripts/scoped-cache-io-smoke.mjs +0 -103
  541. package/scripts/shell-policy-round3-smoke.mjs +0 -46
  542. package/scripts/smoke-runtime-negative.ps1 +0 -100
  543. package/scripts/smoke-runtime-negative.sh +0 -95
  544. package/scripts/stall-policy-smoke.mjs +0 -50
  545. package/scripts/start-memory-worker.mjs +0 -23
  546. package/scripts/statusline-launcher-smoke.mjs +0 -235
  547. package/scripts/stress-atomic-write.mjs +0 -1028
  548. package/scripts/test-fault-inject.mjs +0 -164
  549. package/scripts/test-large-file.mjs +0 -174
  550. package/scripts/tool-edge-smoke.mjs +0 -209
  551. package/scripts/uninstall.mjs +0 -238
  552. package/scripts/webhook-selfheal-smoke.mjs +0 -27
  553. package/scripts/write-overwrite-guard-smoke.mjs +0 -56
  554. package/server-main.mjs +0 -3350
  555. package/server.mjs +0 -468
  556. package/setup/config-merge.mjs +0 -246
  557. package/setup/install.mjs +0 -574
  558. package/setup/launch-core.mjs +0 -617
  559. package/setup/launch.mjs +0 -101
  560. package/setup/locate-claude.mjs +0 -56
  561. package/setup/mixdog-cli.mjs +0 -122
  562. package/setup/setup-server.mjs +0 -3305
  563. package/setup/setup.html +0 -3740
  564. package/setup/tui.mjs +0 -325
  565. package/skills/retro-skill-proposer/SKILL.md +0 -92
  566. package/skills/schedule-add/SKILL.md +0 -77
  567. package/skills/setup/SKILL.md +0 -356
  568. package/skills/webhook-add/SKILL.md +0 -81
  569. package/src/agent/bridge-stall-watchdog.mjs +0 -337
  570. package/src/agent/index.mjs +0 -2229
  571. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +0 -1010
  572. package/src/agent/orchestrator/bridge-retry.mjs +0 -220
  573. package/src/agent/orchestrator/bridge-trace.mjs +0 -601
  574. package/src/agent/orchestrator/cache-mtime.mjs +0 -58
  575. package/src/agent/orchestrator/config.mjs +0 -405
  576. package/src/agent/orchestrator/context/collect.mjs +0 -651
  577. package/src/agent/orchestrator/dispatch-persist.mjs +0 -549
  578. package/src/agent/orchestrator/drain-registry.mjs +0 -50
  579. package/src/agent/orchestrator/explore-validator.mjs +0 -8
  580. package/src/agent/orchestrator/internal-roles.mjs +0 -118
  581. package/src/agent/orchestrator/internal-tools.mjs +0 -88
  582. package/src/agent/orchestrator/jobs.mjs +0 -116
  583. package/src/agent/orchestrator/mcp/client.mjs +0 -364
  584. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +0 -1884
  585. package/src/agent/orchestrator/providers/anthropic.mjs +0 -598
  586. package/src/agent/orchestrator/providers/gemini.mjs +0 -1530
  587. package/src/agent/orchestrator/providers/grok-oauth.mjs +0 -779
  588. package/src/agent/orchestrator/providers/model-catalog.mjs +0 -374
  589. package/src/agent/orchestrator/providers/openai-compat-stream.mjs +0 -366
  590. package/src/agent/orchestrator/providers/openai-compat.mjs +0 -1511
  591. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1891
  592. package/src/agent/orchestrator/providers/openai-oauth.mjs +0 -1456
  593. package/src/agent/orchestrator/providers/openai-ws.mjs +0 -127
  594. package/src/agent/orchestrator/providers/registry.mjs +0 -192
  595. package/src/agent/orchestrator/providers/retry-classifier.mjs +0 -325
  596. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +0 -142
  597. package/src/agent/orchestrator/session/cache/read-cache.mjs +0 -319
  598. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +0 -361
  599. package/src/agent/orchestrator/session/loop.mjs +0 -1619
  600. package/src/agent/orchestrator/session/manager.mjs +0 -1991
  601. package/src/agent/orchestrator/session/result-classification.mjs +0 -65
  602. package/src/agent/orchestrator/session/store.mjs +0 -632
  603. package/src/agent/orchestrator/session/stream-watchdog.mjs +0 -130
  604. package/src/agent/orchestrator/session/tool-result-offload.mjs +0 -166
  605. package/src/agent/orchestrator/session/trim.mjs +0 -491
  606. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +0 -115
  607. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +0 -331
  608. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +0 -150
  609. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +0 -228
  610. package/src/agent/orchestrator/smart-bridge/index.mjs +0 -215
  611. package/src/agent/orchestrator/smart-bridge/profiles.mjs +0 -37
  612. package/src/agent/orchestrator/smart-bridge/registry.mjs +0 -348
  613. package/src/agent/orchestrator/stall-policy.mjs +0 -201
  614. package/src/agent/orchestrator/tool-loop-guard.mjs +0 -75
  615. package/src/agent/orchestrator/tools/bash-session.mjs +0 -722
  616. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +0 -511
  617. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -480
  618. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +0 -256
  619. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +0 -58
  620. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +0 -240
  621. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +0 -113
  622. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +0 -74
  623. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +0 -242
  624. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +0 -211
  625. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +0 -1364
  626. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +0 -126
  627. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +0 -141
  628. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +0 -194
  629. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +0 -60
  630. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +0 -168
  631. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +0 -173
  632. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +0 -48
  633. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -99
  634. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +0 -193
  635. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +0 -597
  636. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  637. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +0 -300
  638. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +0 -152
  639. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +0 -118
  640. package/src/agent/orchestrator/tools/builtin/read-open.mjs +0 -190
  641. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +0 -728
  642. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +0 -173
  643. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +0 -602
  644. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +0 -196
  645. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +0 -422
  646. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +0 -158
  647. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +0 -869
  648. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +0 -962
  649. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +0 -223
  650. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +0 -206
  651. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +0 -401
  652. package/src/agent/orchestrator/tools/builtin.mjs +0 -503
  653. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +0 -24
  654. package/src/agent/orchestrator/tools/code-graph.mjs +0 -4095
  655. package/src/agent/orchestrator/tools/cwd-tool.mjs +0 -298
  656. package/src/agent/orchestrator/tools/destructive-warning.mjs +0 -323
  657. package/src/agent/orchestrator/tools/edit-normalize.mjs +0 -603
  658. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +0 -154
  659. package/src/agent/orchestrator/tools/graph-manifest.json +0 -26
  660. package/src/agent/orchestrator/tools/host-input.mjs +0 -204
  661. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +0 -143
  662. package/src/agent/orchestrator/tools/patch-manifest.json +0 -26
  663. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +0 -20
  664. package/src/agent/orchestrator/tools/patch.mjs +0 -2754
  665. package/src/agent/orchestrator/tools/progress-message.mjs +0 -118
  666. package/src/agent/orchestrator/tools/shell-command.mjs +0 -865
  667. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +0 -7
  668. package/src/agent/orchestrator/tools/shell-snapshot.mjs +0 -313
  669. package/src/agent/orchestrator/workflow-store.mjs +0 -93
  670. package/src/agent/tool-defs.mjs +0 -110
  671. package/src/channels/backends/discord.mjs +0 -784
  672. package/src/channels/data/voice-runtime-manifest.json +0 -138
  673. package/src/channels/index.mjs +0 -3268
  674. package/src/channels/lib/config.mjs +0 -292
  675. package/src/channels/lib/drop-trace.mjs +0 -71
  676. package/src/channels/lib/event-pipeline.mjs +0 -81
  677. package/src/channels/lib/holidays.mjs +0 -138
  678. package/src/channels/lib/hook-pipe-server.mjs +0 -822
  679. package/src/channels/lib/output-forwarder.mjs +0 -765
  680. package/src/channels/lib/runtime-paths.mjs +0 -552
  681. package/src/channels/lib/scheduler.mjs +0 -723
  682. package/src/channels/lib/session-discovery.mjs +0 -103
  683. package/src/channels/lib/state-file.mjs +0 -68
  684. package/src/channels/lib/status-snapshot.mjs +0 -219
  685. package/src/channels/lib/tool-format.mjs +0 -140
  686. package/src/channels/lib/transcript-discovery.mjs +0 -195
  687. package/src/channels/lib/voice-runtime-fetcher.mjs +0 -734
  688. package/src/channels/lib/webhook.mjs +0 -1318
  689. package/src/channels/tool-defs.mjs +0 -170
  690. package/src/daemon/host.mjs +0 -118
  691. package/src/daemon/mcp-transport.mjs +0 -47
  692. package/src/daemon/session.mjs +0 -100
  693. package/src/daemon/thin-client.mjs +0 -71
  694. package/src/daemon/transport.mjs +0 -163
  695. package/src/gateway/claude-current.mjs +0 -255
  696. package/src/gateway/oauth-usage.mjs +0 -598
  697. package/src/gateway/route-meta.mjs +0 -629
  698. package/src/gateway/server.mjs +0 -713
  699. package/src/memory/data/runtime-manifest.json +0 -40
  700. package/src/memory/index.mjs +0 -3332
  701. package/src/memory/lib/core-memory-store.mjs +0 -330
  702. package/src/memory/lib/embedding-provider.mjs +0 -269
  703. package/src/memory/lib/embedding-worker.mjs +0 -323
  704. package/src/memory/lib/memory-cycle1.mjs +0 -645
  705. package/src/memory/lib/memory-cycle2.mjs +0 -1284
  706. package/src/memory/lib/memory-cycle3.mjs +0 -540
  707. package/src/memory/lib/memory-embed.mjs +0 -299
  708. package/src/memory/lib/memory-ops-policy.mjs +0 -190
  709. package/src/memory/lib/memory-recall-store.mjs +0 -638
  710. package/src/memory/lib/memory.mjs +0 -412
  711. package/src/memory/lib/pg/adapter.mjs +0 -308
  712. package/src/memory/lib/pg/process.mjs +0 -360
  713. package/src/memory/lib/pg/supervisor.mjs +0 -396
  714. package/src/memory/lib/runtime-fetcher.mjs +0 -458
  715. package/src/memory/lib/trace-store.mjs +0 -728
  716. package/src/memory/tool-defs.mjs +0 -79
  717. package/src/search/index.mjs +0 -1173
  718. package/src/search/lib/backends/anthropic-oauth.mjs +0 -98
  719. package/src/search/lib/backends/exa.mjs +0 -50
  720. package/src/search/lib/backends/firecrawl.mjs +0 -61
  721. package/src/search/lib/backends/gemini-api.mjs +0 -83
  722. package/src/search/lib/backends/grok-oauth.mjs +0 -86
  723. package/src/search/lib/backends/index.mjs +0 -150
  724. package/src/search/lib/backends/openai-api.mjs +0 -144
  725. package/src/search/lib/backends/openai-oauth.mjs +0 -102
  726. package/src/search/lib/backends/openai-web-search.mjs +0 -76
  727. package/src/search/lib/backends/tavily.mjs +0 -55
  728. package/src/search/lib/backends/xai-api.mjs +0 -113
  729. package/src/search/lib/config.mjs +0 -192
  730. package/src/search/lib/provider-usage.mjs +0 -67
  731. package/src/search/lib/providers.mjs +0 -47
  732. package/src/search/lib/search-intent.mjs +0 -109
  733. package/src/search/lib/setup-handler.mjs +0 -261
  734. package/src/search/lib/web-tools.mjs +0 -1219
  735. package/src/search/tool-defs.mjs +0 -83
  736. package/src/setup/defender-exclusion.mjs +0 -183
  737. package/src/shared/atomic-file.mjs +0 -436
  738. package/src/shared/config.mjs +0 -372
  739. package/src/shared/daemon-recycle.mjs +0 -108
  740. package/src/shared/disable-claude-builtins.mjs +0 -91
  741. package/src/shared/err-text.mjs +0 -12
  742. package/src/shared/llm/http-agent.mjs +0 -123
  743. package/src/shared/open-url.mjs +0 -62
  744. package/src/shared/plugin-paths.mjs +0 -58
  745. package/src/shared/schedules-store.mjs +0 -70
  746. package/src/shared/seed.mjs +0 -161
  747. package/src/shared/user-cwd.mjs +0 -225
  748. package/src/shared/user-data-guard.mjs +0 -244
  749. package/src/status/aggregator.mjs +0 -584
  750. package/src/status/server.mjs +0 -413
  751. package/tools.json +0 -1671
  752. /package/{defaults → src/defaults}/cycle3-review-prompt.md +0 -0
  753. /package/{defaults → src/defaults}/memory-promote-prompt.md +0 -0
  754. /package/{hooks → src/hooks}/lib/permission-rules.cjs +0 -0
  755. /package/{lib → src/lib}/config-cjs.cjs +0 -0
  756. /package/{lib → src/lib}/hook-pipe-path.cjs +0 -0
  757. /package/{lib → src/lib}/mixdog-debug.cjs +0 -0
  758. /package/{lib → src/lib}/text-utils.cjs +0 -0
  759. /package/{rules → src/rules}/bridge/42-cycle3-agent.md +0 -0
  760. /package/src/{agent → runtime/agent}/orchestrator/activity-bus.mjs +0 -0
  761. /package/src/{agent → runtime/agent}/orchestrator/providers/anthropic-betas.mjs +0 -0
  762. /package/src/{agent → runtime/agent}/orchestrator/session/abort-lookup.mjs +0 -0
  763. /package/src/{agent → runtime/agent}/orchestrator/session/cache/post-edit-marks.mjs +0 -0
  764. /package/src/{agent → runtime/agent}/orchestrator/session/cache/scoped-cache-outcome.mjs +0 -0
  765. /package/src/{agent → runtime/agent}/orchestrator/session/cache/util.mjs +0 -0
  766. /package/src/{agent → runtime/agent}/orchestrator/session/read-dedup.mjs +0 -0
  767. /package/src/{agent → runtime/agent}/orchestrator/session/save-session-worker.mjs +0 -0
  768. /package/src/{agent → runtime/agent}/orchestrator/smart-bridge/session-builder.mjs +0 -0
  769. /package/src/{agent → runtime/agent}/orchestrator/tools/bash-policy-scan.mjs +0 -0
  770. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/advisory-lock.mjs +0 -0
  771. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/atomic-write.mjs +0 -0
  772. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/binary-file.mjs +0 -0
  773. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cache-layers.mjs +0 -0
  774. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cwd-utils.mjs +0 -0
  775. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/device-paths.mjs +0 -0
  776. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -0
  777. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diff-utils.mjs +0 -0
  778. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/fs-reachability.mjs +0 -0
  779. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/grep-formatting.mjs +0 -0
  780. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/hash-utils.mjs +0 -0
  781. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/list-formatting.mjs +0 -0
  782. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/open-config-tool.mjs +0 -0
  783. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-locks.mjs +0 -0
  784. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-utils.mjs +0 -0
  785. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-args.mjs +0 -0
  786. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-batch.mjs +0 -0
  787. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-constants.mjs +0 -0
  788. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image-resize.mjs +0 -0
  789. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image.mjs +0 -0
  790. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-lines.mjs +0 -0
  791. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-mode-tool.mjs +0 -0
  792. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-range-index.mjs +0 -0
  793. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-ranges.mjs +0 -0
  794. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-special-files.mjs +0 -0
  795. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-tool.mjs +0 -0
  796. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-windows.mjs +0 -0
  797. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-analysis.mjs +0 -0
  798. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-output.mjs +0 -0
  799. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-helpers.mjs +0 -0
  800. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-validation.mjs +0 -0
  801. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/text-stats.mjs +0 -0
  802. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/windows-roots.mjs +0 -0
  803. /package/src/{agent → runtime/agent}/orchestrator/tools/code-graph-prewarm-worker.mjs +0 -0
  804. /package/src/{agent → runtime/agent}/orchestrator/tools/env-scrub.mjs +0 -0
  805. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-content-cache.mjs +0 -0
  806. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-planner.mjs +0 -0
  807. /package/src/{agent → runtime/agent}/orchestrator/tools/next-call-utils.mjs +0 -0
  808. /package/src/{agent → runtime/agent}/orchestrator/tools/result-compression.mjs +0 -0
  809. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-exec-policy.mjs +0 -0
  810. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy-danger-target.mjs +0 -0
  811. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy.mjs +0 -0
  812. /package/src/{channels → runtime/channels}/lib/cli-worker-host.mjs +0 -0
  813. /package/src/{channels → runtime/channels}/lib/config-lock.mjs +0 -0
  814. /package/src/{channels → runtime/channels}/lib/event-queue.mjs +0 -0
  815. /package/src/{channels → runtime/channels}/lib/executor.mjs +0 -0
  816. /package/src/{channels → runtime/channels}/lib/format.mjs +0 -0
  817. /package/src/{channels → runtime/channels}/lib/interaction-workflows.mjs +0 -0
  818. /package/src/{channels → runtime/channels}/lib/memory-client.mjs +0 -0
  819. /package/src/{channels → runtime/channels}/lib/session-control.mjs +0 -0
  820. /package/src/{channels → runtime/channels}/lib/settings.mjs +0 -0
  821. /package/src/{channels → runtime/channels}/lib/whisper-server.mjs +0 -0
  822. /package/src/{memory → runtime/memory}/lib/agent-ipc.mjs +0 -0
  823. /package/src/{memory → runtime/memory}/lib/bridge-trace-queries.mjs +0 -0
  824. /package/src/{memory → runtime/memory}/lib/llm-worker-host.mjs +0 -0
  825. /package/src/{memory → runtime/memory}/lib/memory-cycle.mjs +0 -0
  826. /package/src/{memory → runtime/memory}/lib/memory-extraction.mjs +0 -0
  827. /package/src/{memory → runtime/memory}/lib/memory-maintenance-store.mjs +0 -0
  828. /package/src/{memory → runtime/memory}/lib/memory-recall-id-patch.mjs +0 -0
  829. /package/src/{memory → runtime/memory}/lib/memory-recall-read-query.mjs +0 -0
  830. /package/src/{memory → runtime/memory}/lib/memory-recall-scope-filter.mjs +0 -0
  831. /package/src/{memory → runtime/memory}/lib/memory-retrievers.mjs +0 -0
  832. /package/src/{memory → runtime/memory}/lib/memory-score.mjs +0 -0
  833. /package/src/{memory → runtime/memory}/lib/memory-text-utils.mjs +0 -0
  834. /package/src/{memory → runtime/memory}/lib/model-profile.mjs +0 -0
  835. /package/src/{memory → runtime/memory}/lib/project-id-resolver.mjs +0 -0
  836. /package/src/{search → runtime/search}/lib/cache.mjs +0 -0
  837. /package/src/{search → runtime/search}/lib/formatter.mjs +0 -0
  838. /package/src/{search → runtime/search}/lib/state.mjs +0 -0
  839. /package/src/{shared → runtime/shared}/abort-controller.mjs +0 -0
  840. /package/src/{shared → runtime/shared}/llm/cost.mjs +0 -0
  841. /package/src/{shared → runtime/shared}/llm/index.mjs +0 -0
  842. /package/src/{shared → runtime/shared}/llm/pid-cleanup.mjs +0 -0
  843. /package/src/{shared → runtime/shared}/llm/usage-log.mjs +0 -0
  844. /package/src/{shared → runtime/shared}/wsl.mjs +0 -0
@@ -1,1619 +0,0 @@
1
- import { classifyResultKind } from './result-classification.mjs';
2
- import { executeMcpTool, isMcpTool, mcpToolHasField } from '../mcp/client.mjs';
3
- import { canonicalizeBuiltinToolName, executeBuiltinTool, formatUnknownBuiltinToolMessage, isBuiltinTool } from '../tools/builtin.mjs';
4
- import { executeBashSessionTool } from '../tools/bash-session.mjs';
5
- import { executePatchTool } from '../tools/patch.mjs';
6
- import { executeCodeGraphTool, isCodeGraphTool } from '../tools/code-graph.mjs';
7
- import { executeInternalTool, isInternalTool } from '../internal-tools.mjs';
8
- import { collectSkillsCached, loadSkillContent } from '../context/collect.mjs';
9
- import { traceBridgeLoop, traceBridgeTool, traceBridgeTrim, estimateProviderPayloadBytes, messagePrefixHash } from '../bridge-trace.mjs';
10
- import { markSessionToolCall, updateSessionStage, SessionClosedError, getSessionAbortSignal } from './manager.mjs';
11
- import { trimMessages, estimateMessagesTokens, estimateRequestReserveTokens } from './trim.mjs';
12
- import { isContextOverflowError } from '../providers/retry-classifier.mjs';
13
- import { classifyBashFileLookupCommand, stripSoftWarns } from '../tool-loop-guard.mjs';
14
- import { maybeOffloadToolResult } from './tool-result-offload.mjs';
15
- import { tryReadCached, setReadCached, invalidatePathForSession, markPostEdit, consumePostEditMark, clearReadDedupSession, extractTouchedPathsFromPatch, tryScopedToolCached, setScopedToolCached, clearScopedToolsForSession, clearScopedToolsForSessionPaths, invalidatePrefetchCache } from './read-dedup.mjs';
16
- import { createScopedCacheOutcome } from './cache/scoped-cache-outcome.mjs';
17
- import { createHash } from 'crypto';
18
-
19
- // Tool-name classification for cross-turn read dedup.
20
- // Strips the MCP prefix so direct calls and MCP-wrapped calls share the
21
- // same cache.
22
- function _stripMcpPrefix(name) {
23
- return typeof name === 'string' && name.startsWith(MCP_TOOL_PREFIX)
24
- ? name.slice(MCP_TOOL_PREFIX.length) : name;
25
- }
26
- function _isReadTool(name) {
27
- return _stripMcpPrefix(name) === 'read';
28
- }
29
- function _isScalarWriteEditTool(name) {
30
- const n = _stripMcpPrefix(name);
31
- return n === 'write' || n === 'edit';
32
- }
33
- function _isMutationTool(name) {
34
- const n = _stripMcpPrefix(name);
35
- return n === 'apply_patch' || n === 'write' || n === 'edit';
36
- }
37
- const SCOPED_CACHEABLE_TOOLS = new Set([
38
- 'code_graph',
39
- 'grep',
40
- 'list',
41
- 'glob',
42
- ]);
43
- function _isScopedCacheableTool(name) {
44
- const n = _stripMcpPrefix(name);
45
- return SCOPED_CACHEABLE_TOOLS.has(n);
46
- }
47
- function _isBashTool(name) {
48
- const n = _stripMcpPrefix(name);
49
- return n === 'bash' || n === 'bash_session';
50
- }
51
-
52
- // classifyResultKind is imported from result-classification.mjs at the top of
53
- // this file; import it from there directly rather than via this module.
54
-
55
- // Canonical signature for intra-turn duplicate detection. Sorting keys
56
- // produces a stable hash regardless of arg-object key order. Anything
57
- // non-serializable falls back to String(args) — still deterministic for
58
- // the model's typical structured-arg shape.
59
- function _canonicalArgs(args) {
60
- if (args == null || typeof args !== 'object') {
61
- try { return JSON.stringify(args); } catch { return String(args); }
62
- }
63
- try {
64
- const keys = Object.keys(args).sort();
65
- const sorted = {};
66
- for (const k of keys) sorted[k] = args[k];
67
- return JSON.stringify(sorted);
68
- } catch { return String(args); }
69
- }
70
- function _intraTurnSig(name, args) {
71
- return createHash('sha256').update(`${name}:${_canonicalArgs(args)}`).digest('hex').slice(0, 16);
72
- }
73
-
74
- // Shared pre-dispatch deny — single source of truth for role/scope/permission
75
- // rejects. Called by BOTH the eager dispatch path (startEagerTool) and the
76
- // serial dispatch path (executeTool body). Returns null when the call is
77
- // allowed to proceed; otherwise returns the Error string the serial path
78
- // would emit. The eager caller ignores the message body and just treats
79
- // non-null as "do not start eager".
80
- //
81
- // Predicates are kept in the same order as the legacy serial branch so a
82
- // bridge-owned control-plane tool fails on _bridgeOwned+_controlPlaneTool
83
- // FIRST (not on permission/wrapper checks) — matches the prior wording.
84
- // Bridge workers are sandboxed to code/research tools. They must never reach
85
- // owner/host control surfaces: session management, the ENTIRE channels module
86
- // (Discord messaging, schedules, webhook/config, channel-bridge toggle,
87
- // command injection), or host input injection. Explicit name list (no imports)
88
- // keeps this hot-path gate dependency-free; add new owner/channel tools here.
89
- const WORKER_DENIED_TOOLS = new Set([
90
- // session control-plane — unified into the single `bridge` tool
91
- // (type=spawn|send|close|list). Denying the one name blocks all worker
92
- // session control. Legacy names kept for defense-in-depth against any
93
- // stale catalog entry that still advertises them.
94
- 'bridge', 'close_session', 'list_sessions', 'create_session',
95
- // channels module (owner/Discord-facing)
96
- 'reply', 'react', 'edit_message', 'download_attachment', 'fetch',
97
- 'schedule_status', 'trigger_schedule', 'schedule_control',
98
- 'activate_channel_bridge', 'reload_config', 'inject_command',
99
- // host input injection
100
- 'inject_input',
101
- ]);
102
- function _preDispatchDeny(call, toolKind, sessionRef) {
103
- const name = call?.name;
104
- if (typeof name !== 'string' || !name) return null;
105
- const _bridgeOwned = sessionRef?.scope?.startsWith?.('bridge:') || sessionRef?.owner === 'bridge';
106
- const _controlPlaneTool = WORKER_DENIED_TOOLS.has(name);
107
- if (_bridgeOwned && _controlPlaneTool) {
108
- return `Error: control-plane tool "${name}" is Lead-only and not available to bridge workers.`;
109
- }
110
- const noToolRole = sessionRef?.role === 'cycle1-agent' || sessionRef?.role === 'cycle2-agent';
111
- if (noToolRole) {
112
- return `Error: tool "${name}" is not available in role "${sessionRef.role}". Re-emit the answer as pipe-separated text per the role's output format (first character a digit, NO tool_use blocks, NO JSON, NO prose, NO apology).`;
113
- }
114
- if (isBlockedHiddenWrapperCall(name, sessionRef)) {
115
- return `Error: tool "${name}" is the wrapper your role (${sessionRef?.role || 'hidden'}) backs. Calling it would spawn another hidden agent of the same kind — use direct read/grep/glob/code_graph instead.`;
116
- }
117
- const effectivePermission = effectiveToolPermission(sessionRef);
118
- const permissionBlocked = isBlockedByPermission(name, toolKind, effectivePermission);
119
- if (permissionBlocked && effectivePermission === 'mcp') {
120
- return `Error: tool "${name}" is not available on this session (permission=mcp). Use MCP/internal retrieval tools only.`;
121
- }
122
- if (permissionBlocked && effectivePermission === 'read') {
123
- return `Error: tool "${name}" is not available on this session (permission=read). Use Mixdog MCP read/grep/glob/recall/search/explore instead.`;
124
- }
125
- if (permissionBlocked && effectivePermission && typeof effectivePermission === 'object') {
126
- return `Error: tool "${name}" is not permitted on this session by the role's allow/deny permission policy.`;
127
- }
128
- return null;
129
- }
130
- /** Exported for smoke tests — same runtime deny as the agent loop. */
131
- export function preDispatchDenyForSession(sessionRef, call, toolKind = 'builtin') {
132
- return _preDispatchDeny(call, toolKind, sessionRef);
133
- }
134
- import { compressToolResult, recordToolBatch } from '../tools/result-compression.mjs';
135
-
136
-
137
- import { isHiddenRole } from '../internal-roles.mjs';
138
- import { createRequire } from 'module';
139
- import { readFileSync as _readFileSync } from 'fs';
140
- import { fileURLToPath } from 'url';
141
- import { dirname, resolve as resolvePath, isAbsolute } from 'path';
142
- // Load the CJS permission evaluator. The hooks/ directory lives two levels
143
- // above src/agent/orchestrator/session/, so we walk up from __dirname.
144
- const _require = createRequire(import.meta.url);
145
- const _hooksLib = resolvePath(dirname(fileURLToPath(import.meta.url)), '../../../../hooks/lib/permission-evaluator.cjs');
146
- const { evaluatePermission: _evaluatePermission } = _require(_hooksLib);
147
- const MCP_TOOL_PREFIX = 'mcp__plugin_mixdog_mixdog__';
148
- const SAFETY_TRIM_PERCENT = 1.00;
149
- // Stricter budget used for the one-shot retry after a provider rejects a send
150
- // with a context-window-exceeded error. 0.60×contextWindow forces older
151
- // non-system / non-latest turns to drop hard so the retry fits even when the
152
- // pre-send estimate under-counted provider-side bytes (tool schemas, framing,
153
- // provider-internal token accounting). Used exactly once per send; see the
154
- // retry block around provider.send below.
155
- const OVERFLOW_RETRY_TRIM_PERCENT = 0.60;
156
-
157
- function estimateMessagesTokensSafe(messages) {
158
- try { return estimateMessagesTokens(messages); }
159
- catch { return null; }
160
- }
161
-
162
- class BridgeContextOverflowError extends Error {
163
- constructor({ stage, sessionId, provider, model, contextWindow, budgetTokens, reserveTokens, messageTokensEst }, cause) {
164
- const target = [provider, model].filter(Boolean).join('/') || 'target model';
165
- const causeMsg = cause && cause.message ? `: ${cause.message}` : '';
166
- super(
167
- `bridge context overflow (${target}, stage=${stage || 'trim'}): ` +
168
- `latest turn cannot fit target context budget=${budgetTokens ?? 'unknown'} ` +
169
- `reserve=${reserveTokens ?? 'unknown'} contextWindow=${contextWindow ?? 'unknown'} ` +
170
- `messageTokensEst=${messageTokensEst ?? 'unknown'}${causeMsg}`,
171
- );
172
- this.name = 'BridgeContextOverflowError';
173
- this.code = 'BRIDGE_CONTEXT_OVERFLOW';
174
- this.sessionId = sessionId || null;
175
- this.provider = provider || null;
176
- this.model = model || null;
177
- this.contextWindow = contextWindow ?? null;
178
- this.budgetTokens = budgetTokens ?? null;
179
- this.reserveTokens = reserveTokens ?? null;
180
- this.messageTokensEst = messageTokensEst ?? null;
181
- if (cause) this.cause = cause;
182
- }
183
- }
184
-
185
- function bridgeContextOverflowError({ stage, sessionId, sessionRef, model, budgetTokens, reserveTokens, messageTokensEst }, cause) {
186
- return new BridgeContextOverflowError({
187
- stage,
188
- sessionId,
189
- provider: sessionRef?.provider || null,
190
- model: sessionRef?.model || model || null,
191
- contextWindow: sessionRef?.contextWindow ?? null,
192
- budgetTokens,
193
- reserveTokens,
194
- messageTokensEst,
195
- }, cause);
196
- }
197
-
198
- // Cache-hit results always inline the cached body. The earlier size-gated
199
- // `[cache-hit-ref]` branch confused bridge workers whose context did not
200
- // contain the referenced prior tool_result, triggering shell-cat detours.
201
- // Hard iteration ceiling for every agent loop. Reset to 0 whenever the
202
- // transcript is compacted (see the trim block below): a long task that keeps
203
- // compacting can proceed past this count, while a tight NON-compacting loop
204
- // still stops here and returns the accumulated transcript.
205
- const MAX_LOOP_ITERATIONS = 200;
206
- // Consecutive identical-AND-failing tool calls (same name+args, error result)
207
- // tolerated across iterations before the loop refuses to re-execute and steers
208
- // the model to change approach. Distinct from the hard iteration cap above:
209
- // this catches tight deterministic-failure loops (e.g. a command that errors
210
- // the same way every time) far earlier than 100 iterations.
211
- const REPEAT_FAIL_LIMIT = 3;
212
- const _HIDDEN_ROLES_JSON = resolvePath(dirname(fileURLToPath(import.meta.url)), '../../../../defaults/hidden-roles.json');
213
- let _hiddenRolesCache = null;
214
- function _getHiddenRoles() {
215
- if (_hiddenRolesCache) return _hiddenRolesCache;
216
- try {
217
- _hiddenRolesCache = JSON.parse(_readFileSync(_HIDDEN_ROLES_JSON, 'utf8'));
218
- } catch { _hiddenRolesCache = { roles: [] }; }
219
- return _hiddenRolesCache;
220
- }
221
- // Transcript pairing guard. Anthropic 400-rejects when an assistant message
222
- // ends with tool_use blocks and the next message isn't tool results for
223
- // those exact ids. abort/timeout/error race in the loop body can leave a
224
- // dangling assistant tool_use at the tail (e.g. the structure_probe loop
225
- // running 12 deep then aborting between push-assistant and push-tool).
226
- // Strip any trailing assistant tool_use that has no matching tool result
227
- // so provider.send sees a valid transcript instead of leaking the 400 to
228
- // the user. Repair runs every iteration but is a no-op on healthy paths.
229
- function _ensureTranscriptPairing(msgs, sessionId) {
230
- // Walk backwards to find the last assistant message that emitted
231
- // tool_use, then validate that every id has a matching tool result
232
- // inside the CONTIGUOUS tool-message block immediately following it.
233
- // Earlier guard splice'd the entire tail — which silently deleted any
234
- // user prompt appended after the dangling assistant by manager.mjs:
235
- // when the guard fired with shape
236
- // [..., assistant{a,b}, tool{a}, user{new prompt}]
237
- // the splice removed user{new prompt} along with the orphan suffix.
238
- // Fix: remove only assistant + the contiguous tool block; preserve
239
- // anything past it (user / system / next assistant) untouched.
240
- let popped = 0;
241
- while (msgs.length > 0) {
242
- let lastAssistantIdx = -1;
243
- for (let i = msgs.length - 1; i >= 0; i--) {
244
- const m = msgs[i];
245
- if (m?.role === 'assistant' && Array.isArray(m.toolCalls) && m.toolCalls.length > 0) {
246
- lastAssistantIdx = i;
247
- break;
248
- }
249
- }
250
- if (lastAssistantIdx === -1) break;
251
- // Collect the contiguous tool messages directly after this assistant.
252
- // Anything past that block is unrelated (next user prompt, system
253
- // marker, etc.) and must survive the repair.
254
- let toolBlockEnd = lastAssistantIdx + 1;
255
- while (toolBlockEnd < msgs.length && msgs[toolBlockEnd]?.role === 'tool') {
256
- toolBlockEnd += 1;
257
- }
258
- const toolBlock = msgs.slice(lastAssistantIdx + 1, toolBlockEnd);
259
- const ids = msgs[lastAssistantIdx].toolCalls.map(c => c.id);
260
- const matched = ids.every(id => toolBlock.some(m => m.toolCallId === id));
261
- if (matched) break;
262
- const removed = toolBlockEnd - lastAssistantIdx;
263
- msgs.splice(lastAssistantIdx, removed);
264
- popped += removed;
265
- }
266
- // Second sweep — catch dangling tool results that survived the
267
- // contiguous-block splice. Anthropic strict spec requires every
268
- // tool result to sit in a contiguous block right after the
269
- // assistant whose toolCalls produced it; a `[..., assistant{a,b},
270
- // tool{a}, user, tool{b}]` shape leaves tool{b} orphaned even
271
- // after assistant + tool{a} are repaired by the loop above.
272
- // Walk back from each tool message to the nearest non-tool
273
- // ancestor; if it is not an assistant whose toolCalls include
274
- // this id, drop the orphan.
275
- for (let i = msgs.length - 1; i >= 0; i--) {
276
- const m = msgs[i];
277
- if (m?.role !== 'tool') continue;
278
- if (!m.toolCallId) {
279
- msgs.splice(i, 1);
280
- popped += 1;
281
- continue;
282
- }
283
- let prevIdx = i - 1;
284
- while (prevIdx >= 0 && msgs[prevIdx]?.role === 'tool') prevIdx--;
285
- const anchor = prevIdx >= 0 ? msgs[prevIdx] : null;
286
- const anchorOk = anchor?.role === 'assistant'
287
- && Array.isArray(anchor.toolCalls)
288
- && anchor.toolCalls.some(c => c.id === m.toolCallId);
289
- if (!anchorOk) {
290
- msgs.splice(i, 1);
291
- popped += 1;
292
- }
293
- }
294
- if (popped > 0 && sessionId) {
295
- try { process.stderr.write(`[transcript-repair] sess=${sessionId} popped=${popped} dangling assistant tool_use\n`); } catch {}
296
- }
297
- }
298
-
299
- // Write-class tools that a permission=read session must not execute. The
300
- // schema still advertises them to keep one unified shard; this runtime set
301
- // is the fail-safe reject at call time.
302
- const READ_BLOCKED_TOOLS = new Set([
303
- 'bash', 'bash_session',
304
- 'write',
305
- 'edit',
306
- 'apply_patch',
307
- ]);
308
- const MCP_ONLY_ALLOWED_KINDS = new Set(['mcp', 'internal', 'skill']);
309
- // Wrappers that hidden retrieval roles back. Hidden roles MUST NOT call
310
- // these or they spawn another hidden agent of the same kind — nested chain
311
- // + token burn. Block at call time; the role's rule prompt also says so.
312
- const RETRIEVAL_WRAPPERS = new Set(['recall', 'search', 'explore']);
313
- // Hidden roles that may call specific retrieval wrappers. Default policy
314
- // blocks all hidden→wrapper calls; roles listed here have a documented
315
- // need:
316
- // - scheduler-task / webhook-handler: state-changing agents whose
317
- // tasks routinely require both reach-back into past context
318
- // (`recall`) and fresh external info (`search`).
319
- const HIDDEN_ROLE_WRAPPER_ALLOWLIST = {
320
- 'scheduler-task': new Set(['recall', 'search']),
321
- 'webhook-handler': new Set(['recall', 'search']),
322
- };
323
- // Eager-dispatch: tools with readOnlyHint:true in their declaration are safe
324
- // to execute during SSE parsing so tool work overlaps with the rest of the
325
- // stream. Writes, bash, MCP and skills stay serial after send() returns.
326
- function isEagerDispatchable(name, tools) {
327
- if (!Array.isArray(tools)) return false;
328
- const def = tools.find(t => t?.name === name);
329
- return def?.annotations?.readOnlyHint === true;
330
- }
331
- // ── Bridge-worker permission enforcement ──────────────────────────────────────
332
- // Mirrors the PreToolUse hook evaluation for tool calls that originate inside a
333
- // bridge worker session. Worker dispatch previously bypassed the hook pipeline
334
- // entirely; this guard closes that gap by running the same evaluator inline.
335
- //
336
- // `ask` is treated as deny here — forwarding `ask` decisions to the channel
337
- // UI approval flow needs bidirectional prompt plumbing that does not exist.
338
- function _checkWorkerPermission(toolName, toolInput, sessionRef) {
339
- const bareToolName = _stripMcpPrefix(toolName);
340
- if (sessionRef?.owner === 'bridge' && bareToolName === 'bash') {
341
- const cmdClass = classifyBashFileLookupCommand(toolInput?.command);
342
- if (cmdClass) {
343
- return `Error: bridge worker bash file lookup blocked (${cmdClass}). Use Mixdog MCP read/grep/glob/list directly; bash is only for build/test/run/git-style commands.`;
344
- }
345
- }
346
- // Even when no explicit permissionMode is propagated to the worker, run
347
- // the evaluator under the most restrictive baseline ('default') so the
348
- // bypass-proof hard-deny patterns (UNC paths, /etc, C:/Windows, etc.)
349
- // and the user's settings.json deny rules still apply. Previously a
350
- // missing permissionMode short-circuited to null and the worker
351
- // ran ungated — a model could dispatch a bridge to read or write
352
- // protected paths even when the same call would have been denied for
353
- // the parent. Callers that genuinely need bypassPermissions can still
354
- // forward it explicitly via session-builder; this only closes the
355
- // silent default-to-bypass path.
356
- const permissionMode = sessionRef?.permissionMode || 'default';
357
- // Prefix bare mixdog tool names so the evaluator path-logic handles them correctly.
358
- const fullName = toolName.startsWith(MCP_TOOL_PREFIX) || toolName.startsWith('mcp__')
359
- ? toolName
360
- : `${MCP_TOOL_PREFIX}${toolName}`;
361
- const projectDir = sessionRef?.cwd || undefined;
362
- const userCwd = sessionRef?.cwd || undefined;
363
- try {
364
- const { decision, reason } = _evaluatePermission({
365
- toolName: fullName,
366
- toolInput: toolInput || {},
367
- permissionMode,
368
- projectDir,
369
- userCwd,
370
- });
371
- if (decision === 'deny' || decision === 'ask') {
372
- return `Error: tool "${toolName}" blocked by permission evaluator (decision=${decision}): ${reason}`;
373
- }
374
- } catch (err) {
375
- // Evaluator errors must not crash the loop — log and allow.
376
- try { process.stderr.write(`[permission-evaluator] error: ${err?.message}\n`); } catch {}
377
- }
378
- return null;
379
- }
380
- function effectiveToolPermission(sessionRef) {
381
- return sessionRef?.toolPermission || sessionRef?.permission || null;
382
- }
383
- function isBlockedByPermission(toolName, toolKind, permission) {
384
- if (permission === 'mcp') return !MCP_ONLY_ALLOWED_KINDS.has(toolKind);
385
- if (permission === 'read') return READ_BLOCKED_TOOLS.has(toolName);
386
- // Object-form {allow,deny} permission (role template / profile). The
387
- // schema-level intersection in createSession only narrows the ADVERTISED
388
- // tool list; it is not a runtime execution boundary. Enforce the same
389
- // allow/deny here as the fail-safe so a tool call for a non-advertised
390
- // (denied / out-of-allow) tool is rejected at dispatch time, matching
391
- // the string-form ('read'/'mcp') guards. Names are compared bare +
392
- // lowercased to mirror createSession's allow/deny set construction.
393
- if (permission && typeof permission === 'object') {
394
- const name = String(_stripMcpPrefix(toolName) || '').toLowerCase();
395
- const deny = Array.isArray(permission.deny) && permission.deny.length > 0
396
- ? permission.deny.map(n => String(n).toLowerCase())
397
- : null;
398
- if (deny && deny.includes(name)) return true;
399
- const allow = Array.isArray(permission.allow) && permission.allow.length > 0
400
- ? permission.allow.map(n => String(n).toLowerCase())
401
- : null;
402
- if (allow && !allow.includes(name)) return true;
403
- return false;
404
- }
405
- return false;
406
- }
407
- function isBlockedHiddenWrapperCall(toolName, sessionRef) {
408
- if (!RETRIEVAL_WRAPPERS.has(toolName)) return false;
409
- if (sessionRef?.owner !== 'bridge') return false;
410
- if (!isHiddenRole(sessionRef?.role)) return false;
411
- const allow = HIDDEN_ROLE_WRAPPER_ALLOWLIST[sessionRef.role];
412
- if (allow && allow.has(toolName)) return false;
413
- return true;
414
- }
415
- function messagesArrayChanged(before, after) {
416
- if (!Array.isArray(before) || !Array.isArray(after)) return before !== after;
417
- if (before.length !== after.length) return true;
418
- for (let i = 0; i < before.length; i += 1) {
419
- if (before[i] !== after[i]) return true;
420
- }
421
- return false;
422
- }
423
- const SKILL_TOOL_NAMES = new Set(['skills_list', 'skill_view', 'skill_execute']);
424
- const SPECIAL_TOOL_NAMES = new Set(['bash_session', 'apply_patch', 'code_graph']);
425
- const BASH_SESSION_HEADER_RE = /\[session: ([^\]\r\n]+)\]/;
426
- const STORED_TOOL_ARG_BODY_KEY_RE = /^(?:content|old_string|new_string|patch|rewrite)$/i;
427
- const STORED_TOOL_ARG_LONG_KEY_RE = /^(?:command|script)$/i;
428
- const STORED_TOOL_ARG_BODY_LIMIT = 2_000;
429
- const STORED_TOOL_ARG_LONG_LIMIT = 8_000;
430
- const STORED_TOOL_ARG_PREVIEW_HEAD = 360;
431
- const STORED_TOOL_ARG_PREVIEW_TAIL = 160;
432
-
433
- function compactStoredToolArgString(value, key = '') {
434
- if (typeof value !== 'string') return value;
435
- const isBody = STORED_TOOL_ARG_BODY_KEY_RE.test(key);
436
- const isLong = isBody || STORED_TOOL_ARG_LONG_KEY_RE.test(key);
437
- const limit = isBody ? STORED_TOOL_ARG_BODY_LIMIT : (isLong ? STORED_TOOL_ARG_LONG_LIMIT : Infinity);
438
- if (value.length <= limit) return value;
439
- const hash = createHash('sha256').update(value).digest('hex').slice(0, 16);
440
- const head = value.slice(0, STORED_TOOL_ARG_PREVIEW_HEAD).replace(/\r\n/g, '\n');
441
- const tail = value.slice(-STORED_TOOL_ARG_PREVIEW_TAIL).replace(/\r\n/g, '\n');
442
- return `[mixdog compacted ${key || 'string'}: ${value.length} chars, sha256:${hash}]\n${head}\n... [middle omitted from stored tool-call args] ...\n${tail}`;
443
- }
444
-
445
- function compactStoredToolArgValue(value, key = '', depth = 0) {
446
- if (value === null || value === undefined) return value;
447
- if (typeof value === 'string') return compactStoredToolArgString(value, key);
448
- if (typeof value !== 'object') return value;
449
- if (depth >= 6) return Array.isArray(value) ? `[${value.length} items]` : '{...}';
450
- if (Array.isArray(value)) {
451
- return value.map((item) => compactStoredToolArgValue(item, key, depth + 1));
452
- }
453
- const out = {};
454
- for (const [k, v] of Object.entries(value)) {
455
- out[k] = compactStoredToolArgValue(v, k, depth + 1);
456
- }
457
- return out;
458
- }
459
-
460
- function compactToolCallsForHistory(calls) {
461
- if (!Array.isArray(calls)) return calls;
462
- return calls.map((call) => {
463
- if (!call || typeof call !== 'object') return call;
464
- return {
465
- ...call,
466
- arguments: compactStoredToolArgValue(call.arguments),
467
- };
468
- });
469
- }
470
-
471
- // Restore the FULL body of ONE tool call inside a history assistant message
472
- // whose toolCalls were compacted at push time. Used for a failed edit call so
473
- // the model sees the original patch/old_string on retry instead of a
474
- // `[mixdog compacted …]` placeholder it cannot act on. Must run BEFORE the
475
- // message is first transmitted so it never mutates an already-cached prefix
476
- // (the prompt cache is content-prefix matched).
477
- //
478
- // Only the compactable body/long keys (patch, old_string, new_string, content,
479
- // rewrite, command, script) are restored, and at ANY depth — compaction is
480
- // recursive (compactStoredToolArgValue), so batch shapes like edits[].old_string
481
- // or writes[].content carry nested compacted bodies too. Every other field
482
- // (e.g. `path`, which a tool may mutate in place during execution) is taken from
483
- // the compacted snapshot captured at push time, before any mutation. The
484
- // compacted args tree is built fresh by compactToolCallsForHistory and is not
485
- // shared with originalCalls, so rebuilding it here is safe.
486
- function restoreToolCallBodyForId(assistantMsg, originalCalls, callId) {
487
- if (!assistantMsg || !Array.isArray(assistantMsg.toolCalls) || !callId) return;
488
- if (!Array.isArray(originalCalls)) return;
489
- const tc = assistantMsg.toolCalls.find((t) => t && t.id === callId);
490
- const orig = originalCalls.find((c) => c && c.id === callId);
491
- if (!tc || !orig) return;
492
- if (!tc.arguments || typeof tc.arguments !== 'object'
493
- || !orig.arguments || typeof orig.arguments !== 'object') return;
494
- tc.arguments = _restoreCompactedBodies(tc.arguments, orig.arguments, '');
495
- }
496
-
497
- // Recursively rebuild a compacted args tree: replace ONLY compactable body/long
498
- // string fields (matched by key at any depth) with their full originals, and
499
- // keep every other field from the compacted snapshot. tcVal and origVal share
500
- // the same structure (compaction only shortens body strings), so the walk
501
- // descends them in parallel; a missing or non-object origVal falls back to the
502
- // compacted value rather than throwing.
503
- function _restoreCompactedBodies(tcVal, origVal, key) {
504
- if ((STORED_TOOL_ARG_BODY_KEY_RE.test(key) || STORED_TOOL_ARG_LONG_KEY_RE.test(key))
505
- && typeof origVal === 'string') {
506
- return origVal;
507
- }
508
- if (Array.isArray(tcVal) && Array.isArray(origVal)) {
509
- return tcVal.map((item, i) => _restoreCompactedBodies(item, origVal[i], key));
510
- }
511
- if (tcVal && typeof tcVal === 'object' && origVal && typeof origVal === 'object') {
512
- const out = {};
513
- for (const k of Object.keys(tcVal)) {
514
- out[k] = (k in origVal) ? _restoreCompactedBodies(tcVal[k], origVal[k], k) : tcVal[k];
515
- }
516
- return out;
517
- }
518
- return tcVal;
519
- }
520
- /**
521
- * Execute a single tool call — routes to MCP or builtin.
522
- */
523
- function getToolKind(name) {
524
- if (SKILL_TOOL_NAMES.has(name)) return 'skill';
525
- if (SPECIAL_TOOL_NAMES.has(name)) return 'builtin';
526
- if (isMcpTool(name)) return 'mcp';
527
- if (isInternalTool(name)) return 'internal';
528
- if (isBuiltinTool(name)) return 'builtin';
529
- return 'builtin';
530
- }
531
- function buildSkillsListResponse(cwd) {
532
- const skills = collectSkillsCached(cwd);
533
- const entries = skills.map(s => ({ name: s.name, description: s.description || '' }));
534
- return JSON.stringify({ skills: entries });
535
- }
536
- function viewSkill(cwd, name) {
537
- if (!name) return 'Error: skill name is required';
538
- const content = loadSkillContent(name, cwd);
539
- return content || `Error: skill "${name}" not found`;
540
- }
541
- function executeSkill(cwd, name, _args) {
542
- if (!name) return 'Error: skill name is required';
543
- const content = loadSkillContent(name, cwd);
544
- return content || `Error: skill "${name}" not found`;
545
- }
546
- function extractBashSessionId(result) {
547
- if (typeof result !== 'string') return null;
548
- const match = BASH_SESSION_HEADER_RE.exec(result);
549
- return match ? match[1] : null;
550
- }
551
-
552
- export function buildBridgeBashSessionArgs(args, sessionRef) {
553
- if (sessionRef?.owner !== 'bridge') return null;
554
- // run_in_background is a detached one-shot job, incompatible with the
555
- // persistent bash session. Fall through to the background-job path
556
- // (executeBuiltinTool -> startBackgroundShellJob) so the worker gets a
557
- // [job: ...] id that job_wait can resolve — otherwise the persistent
558
- // session returns a [session: ...] header and job_wait reports "job not found".
559
- if (args?.run_in_background === true) return null;
560
- const routedArgs = { ...(args || {}) };
561
- const explicitSessionId = typeof routedArgs.session_id === 'string' && routedArgs.session_id.trim()
562
- ? routedArgs.session_id.trim()
563
- : null;
564
- const wantsPersistent = routedArgs.persistent === true || !!explicitSessionId;
565
- if (!wantsPersistent) return null;
566
- if (!explicitSessionId && sessionRef?.implicitBashSessionId) {
567
- routedArgs.session_id = sessionRef.implicitBashSessionId;
568
- } else if (explicitSessionId) {
569
- routedArgs.session_id = explicitSessionId;
570
- }
571
- delete routedArgs.persistent;
572
- return routedArgs;
573
- }
574
-
575
- function _scopedCacheOutcomeForCall(sessionRef, toolCallId, toolName, callerSessionId, executeOpts = {}) {
576
- if (executeOpts.scopedCacheOutcome) {
577
- if (sessionRef && toolCallId) {
578
- if (!sessionRef._scopedCacheOutcomeByCallId) sessionRef._scopedCacheOutcomeByCallId = new Map();
579
- sessionRef._scopedCacheOutcomeByCallId.set(toolCallId, executeOpts.scopedCacheOutcome);
580
- }
581
- return executeOpts.scopedCacheOutcome;
582
- }
583
- if (!callerSessionId || !toolCallId || !_isScopedCacheableTool(toolName)) return null;
584
- const outcome = createScopedCacheOutcome();
585
- if (sessionRef) {
586
- if (!sessionRef._scopedCacheOutcomeByCallId) sessionRef._scopedCacheOutcomeByCallId = new Map();
587
- sessionRef._scopedCacheOutcomeByCallId.set(toolCallId, outcome);
588
- }
589
- return outcome;
590
- }
591
-
592
- async function executeTool(name, args, cwd, callerSessionId, sessionRef, executeOpts = {}) {
593
- const scopedCacheOutcome = _scopedCacheOutcomeForCall(
594
- sessionRef,
595
- executeOpts.toolCallId,
596
- name,
597
- callerSessionId,
598
- executeOpts,
599
- );
600
- const toolOpts = scopedCacheOutcome
601
- ? { ...executeOpts, scopedCacheOutcome }
602
- : executeOpts;
603
- if (name === 'skills_list') {
604
- return buildSkillsListResponse(cwd);
605
- }
606
- if (name === 'skill_view') {
607
- return viewSkill(cwd, args?.name);
608
- }
609
- if (name === 'skill_execute') {
610
- return executeSkill(cwd, args?.name, args?.args);
611
- }
612
- if (isMcpTool(name)) {
613
- // 24h trace data shows ~24% of external MCP calls are cwd-sensitive
614
- // (bash / grep / read / list / glob etc.) but the worker session's
615
- // cwd was previously dropped here. Inject cwd only when the tool's
616
- // inputSchema declares the field — schemas without it would reject
617
- // an unknown argument.
618
- const needsCwdInjection = cwd
619
- && mcpToolHasField(name, 'cwd')
620
- && (args == null || args.cwd == null);
621
- const finalArgs = needsCwdInjection ? { ...(args || {}), cwd } : args;
622
- return executeMcpTool(name, finalArgs);
623
- }
624
- if (isCodeGraphTool(name)) {
625
- // cwd chain: args.cwd (caller-explicit) → session cwd → undefined (handler throws)
626
- const graphCwd = (typeof args?.cwd === 'string' && args.cwd.trim()) ? args.cwd.trim() : cwd;
627
- return executeCodeGraphTool(name, args, graphCwd, null, toolOpts);
628
- }
629
- if (isInternalTool(name)) {
630
- // callerSessionId propagates into server.mjs dispatchTool so that
631
- // dispatchAiWrapped can detect and reject recursive calls from a
632
- // hidden-role session (recall/search/explore → self).
633
- return executeInternalTool(name, args, { callerSessionId, callerCwd: cwd });
634
- }
635
- if (name === 'bash') {
636
- const routedArgs = buildBridgeBashSessionArgs(args, sessionRef);
637
- if (!routedArgs) {
638
- // clientHostPid scopes background shell-jobs to the dispatching
639
- // terminal's claude.exe pid (bridge sessions store it on sessionRef);
640
- // without it resolveJobOwnerHostPid falls back to the daemon-global env.
641
- return executeBuiltinTool(name, args, cwd, { sessionId: callerSessionId, clientHostPid: sessionRef?.clientHostPid, ...toolOpts });
642
- }
643
- // Thread the session's AbortSignal so bridge type=close can interrupt the
644
- // persistent child process. getSessionAbortSignal is imported at top of
645
- // loop.mjs from manager.mjs; callerSessionId identifies the controller.
646
- let _bashAbortSignal = null;
647
- try { _bashAbortSignal = getSessionAbortSignal(callerSessionId); } catch { /* ignore */ }
648
- const result = await executeBashSessionTool('bash_session', routedArgs, cwd, {
649
- sessionId: callerSessionId,
650
- abortSignal: _bashAbortSignal,
651
- });
652
- const bashSid = extractBashSessionId(result);
653
- if (bashSid) {
654
- sessionRef.implicitBashSessionId = bashSid;
655
- // Track all persistent bash sessions for bulk teardown on close.
656
- if (sessionRef.allBashSessionIds) {
657
- if (!sessionRef.allBashSessionIds.includes(bashSid)) {
658
- sessionRef.allBashSessionIds.push(bashSid);
659
- }
660
- } else {
661
- sessionRef.allBashSessionIds = [bashSid];
662
- }
663
- }
664
- return result;
665
- }
666
- if (name === 'apply_patch') {
667
- return executePatchTool(name, args, cwd, { sessionId: callerSessionId });
668
- }
669
- if (isBuiltinTool(name)) {
670
- // clientHostPid threaded for the same per-terminal job-scope reason as
671
- // the bash branch above (see resolveJobOwnerHostPid).
672
- return executeBuiltinTool(name, args, cwd, { sessionId: callerSessionId, clientHostPid: sessionRef?.clientHostPid, signal: executeOpts.signal, ...toolOpts });
673
- }
674
- return formatUnknownBuiltinToolMessage(name, args, 'tool');
675
- }
676
- /**
677
- * Agent loop: send → tool_call → execute → re-send → repeat until text.
678
- * sendOpts may include:
679
- * - `effort` (provider-specific)
680
- * - `fast` (boolean)
681
- * - `sessionId` — enables runtime liveness markers (optional)
682
- * - `signal` — AbortSignal; checked at each iteration boundary and after each
683
- * tool. When aborted, throws SessionClosedError so the ask
684
- * wrapper can propagate a clean cancellation.
685
- * - `onStageChange(stage)` / `onStreamDelta()` — forwarded to provider.send for heartbeats
686
- */
687
- // Source of truth: defaults/hidden-roles.json (loaded via _getHiddenRoles
688
- // above). Build the name Set eagerly at module load so HIDDEN_ROLE_NAMES
689
- // stays in sync with the declarative registry — no hardcoded duplicate.
690
- const HIDDEN_ROLE_NAMES = new Set(
691
- (_getHiddenRoles().roles || []).map((r) => r && r.name).filter((n) => typeof n === 'string' && n.length > 0)
692
- );
693
-
694
- // Stop reasons that signal the turn was cut short mid-synthesis (token cap,
695
- // provider pause). Empty content + one of these reasons means the worker
696
- // was not done — re-prompt instead of accepting empty as final.
697
- // Covers Anthropic (pause_turn, max_tokens), OpenAI (length), Gemini
698
- // (MAX_TOKENS, OTHER), and case variants.
699
- const INCOMPLETE_STOP_REASONS = new Set([
700
- 'pause_turn', 'max_tokens', 'length', 'MAX_TOKENS', 'OTHER',
701
- ]);
702
-
703
- export async function agentLoop(provider, messages, model, tools, onToolCall, cwd, sendOpts) {
704
- let iterations = 0;
705
- let toolCallsTotal = 0;
706
- let lastUsage;
707
- let firstTurnUsage;
708
- let response;
709
- let contractNudges = 0;
710
- const opts = sendOpts || {};
711
- const sessionId = opts.sessionId || null;
712
- const signal = opts.signal || null;
713
- const sessionRole = opts.session?.role;
714
- const forcedFirstTool = opts.forcedFirstTool ?? null;
715
- const forcedFirstToolDef = forcedFirstTool
716
- ? tools.find(tool => tool?.name === forcedFirstTool)
717
- : null;
718
- // Opaque providerState passthrough. The loop never inspects it; only the
719
- // originating provider does. Seed from sendOpts.providerState if the
720
- // manager restored one. No provider currently emits state (Codex OAuth is
721
- // stateless per contract); field remains undefined end-to-end for now.
722
- let providerState = opts.providerState ?? undefined;
723
- const throwIfAborted = () => {
724
- if (signal?.aborted) {
725
- const reason = signal.reason instanceof Error ? signal.reason : null;
726
- // Preserve any structured abort reason (SessionClosedError,
727
- // StreamStalledAbortError, etc.). Fallback to SessionClosedError
728
- // when the reason is not an Error instance.
729
- if (reason) throw reason;
730
- throw new SessionClosedError(sessionId || 'unknown', 'agent loop aborted');
731
- }
732
- };
733
- const sessionRef = opts.session || null;
734
- const maxLoopIterations = Number.isFinite(sessionRef?.maxLoopIterations)
735
- ? sessionRef.maxLoopIterations
736
- : MAX_LOOP_ITERATIONS;
737
- // Tool execution must use the session cwd even when the caller omitted the
738
- // legacy positional cwd argument. Bridge workers always carry their cwd on
739
- // sessionRef; falling through to pwd()/process.cwd() resolves relatives
740
- // against the host/plugin root instead of the worker workspace.
741
- cwd = cwd || sessionRef?.cwd || undefined;
742
- while (true) {
743
- throwIfAborted();
744
- if (iterations >= maxLoopIterations) {
745
- process.stderr.write(`[loop] hard iteration cap ${maxLoopIterations} reached (sess=${sessionId || 'unknown'}); stopping loop.\n`);
746
- break;
747
- }
748
- if (sessionRef && typeof sessionRef.contextWindow === 'number') {
749
- const safetyBudget = Math.floor(sessionRef.contextWindow * SAFETY_TRIM_PERCENT);
750
- // Reserve headroom for the tool schemas + provider request framing
751
- // that ride alongside `messages` but are invisible to the chars/4
752
- // message estimate. Without this the budget is optimistic: a
753
- // transcript that "fits" by message tokens can still overflow once
754
- // the provider serializes N tool definitions into the same request.
755
- const reserveTokens = estimateRequestReserveTokens(tools);
756
- // Snapshot pre-trim shape so trim_meta can record the actual
757
- // mutation (or no-op) for prefix-mutation forensics. Bytes are
758
- // a best-effort JSON.stringify length — close enough to the
759
- // payload we hand the provider for prefix-cache analysis.
760
- const beforeCount = messages.length;
761
- let beforeBytes = null;
762
- try { beforeBytes = Buffer.byteLength(JSON.stringify(messages), 'utf8'); } catch { beforeBytes = null; }
763
- const messageTokensEst = estimateMessagesTokensSafe(messages);
764
- let trimmed;
765
- try {
766
- trimmed = trimMessages(messages, safetyBudget, { reserveTokens });
767
- } catch (trimErr) {
768
- traceBridgeTrim({
769
- sessionId,
770
- iteration: iterations + 1,
771
- stage: 'pre_send',
772
- prune_count: 0,
773
- trim_changed: false,
774
- input_prefix_hash: messagePrefixHash(messages),
775
- before_count: beforeCount,
776
- after_count: messages.length,
777
- before_bytes: beforeBytes,
778
- after_bytes: beforeBytes,
779
- context_window: sessionRef.contextWindow,
780
- budget_tokens: safetyBudget,
781
- reserve_tokens: reserveTokens,
782
- message_tokens_est: messageTokensEst,
783
- provider: sessionRef.provider,
784
- model: sessionRef.model || model,
785
- error: trimErr && trimErr.message ? trimErr.message : String(trimErr),
786
- error_code: 'BRIDGE_CONTEXT_OVERFLOW',
787
- });
788
- throw bridgeContextOverflowError({
789
- stage: 'pre_send',
790
- sessionId,
791
- sessionRef,
792
- model,
793
- budgetTokens: safetyBudget,
794
- reserveTokens,
795
- messageTokensEst,
796
- }, trimErr);
797
- }
798
- const trimChanged = messagesArrayChanged(messages, trimmed);
799
- const pruneCount = Math.max(beforeCount - trimmed.length, 0);
800
- if (trimChanged) {
801
- messages.length = 0;
802
- messages.push(...trimmed);
803
- // Trimming the transcript invalidates the server-side
804
- // conversation anchor (xAI Responses / Codex WS rely on
805
- // previous_response_id which points at a now-mutated prefix).
806
- // Drop providerState so the next send starts a fresh chain
807
- // instead of triggering silent cache miss or hard mismatch.
808
- providerState = undefined;
809
- // Compaction shrank the transcript, so prior turns no longer
810
- // pressure the window — reset the iteration counter so a
811
- // steadily-compacting long task isn't killed by the cap,
812
- // while a non-compacting tight loop still hits it.
813
- iterations = 0;
814
- }
815
- let afterBytes = null;
816
- try { afterBytes = Buffer.byteLength(JSON.stringify(messages), 'utf8'); } catch { afterBytes = null; }
817
- traceBridgeTrim({
818
- sessionId,
819
- iteration: iterations + 1,
820
- stage: 'pre_send',
821
- prune_count: pruneCount,
822
- trim_changed: trimChanged,
823
- input_prefix_hash: messagePrefixHash(messages),
824
- before_count: beforeCount,
825
- after_count: messages.length,
826
- before_bytes: beforeBytes,
827
- after_bytes: afterBytes,
828
- context_window: sessionRef.contextWindow,
829
- budget_tokens: safetyBudget,
830
- reserve_tokens: reserveTokens,
831
- message_tokens_est: messageTokensEst,
832
- provider: sessionRef.provider,
833
- model: sessionRef.model || model,
834
- });
835
- }
836
- const nextIteration = iterations + 1;
837
- opts.iteration = nextIteration;
838
- opts.providerState = providerState;
839
- if (forcedFirstTool && toolCallsTotal === 0) {
840
- opts.toolChoice = 'required';
841
- } else {
842
- delete opts.toolChoice;
843
- }
844
- const sendTools = forcedFirstToolDef && toolCallsTotal === 0 ? [forcedFirstToolDef] : tools;
845
- // Eager-dispatch queue: when the provider streams a tool-call event,
846
- // start read-only tools immediately so execution overlaps with the
847
- // remaining SSE parse. Writes and unknown tools wait until send()
848
- // returns and run serially in the call-order loop below.
849
- const pending = new Map();
850
- // Streaming-time intra-turn dedup. When the LLM emits two
851
- // tool_use blocks with identical (name, args) signatures in
852
- // sequence, the provider's onToolCall fires for both BEFORE
853
- // the iter for-body runs, so the batch-level pre-pass would be
854
- // too late to prevent the eager dispatch of the second one.
855
- // Track signatures of in-flight eager calls and skip starting a
856
- // second one for the same sig. The duplicate's executeTool is
857
- // never invoked; the for-body's pre-pass marks it as a duplicate
858
- // and emits a stub tool_result. The sig is NOT cleared when the
859
- // eager promise settles (see finally below): a streaming onToolCall
860
- // can deliver a same-turn identical call AFTER the first promise
861
- // settles but BEFORE the deferred cache set (:1256), and the static
862
- // pre-pass (:909) only runs after send() returns — so clearing the
863
- // sig on settle would let that second streaming eager call
864
- // re-execute. A fresh Map() is created per turn, so the sig set
865
- // resets at the turn boundary without leaking across iterations.
866
- const _eagerInFlightSigs = new Map();
867
- let _mutationEpoch = 0;
868
- const startEagerTool = (call) => {
869
- if (!call?.id || pending.has(call.id) || !isEagerDispatchable(call.name, tools)) return null;
870
- const _sig = _intraTurnSig(call.name, call.arguments);
871
- if (_eagerInFlightSigs.has(_sig)) return null;
872
- // Repeat-failure guard also gates eager dispatch (reviewer-flagged):
873
- // streaming onToolCall / startEagerRun would otherwise re-run an
874
- // identical read-only call that already failed REPEAT_FAIL_LIMIT
875
- // times before the serial for-body guard runs. Returning null here
876
- // lets the serial body push the [repeat-failure-guard] stub.
877
- {
878
- const _rfg = sessionRef?._repeatFailGuard;
879
- if (_rfg && _rfg.sig === _sig && _rfg.count >= REPEAT_FAIL_LIMIT) return null;
880
- }
881
- const toolKind = getToolKind(call.name);
882
- // Shared pre-dispatch deny: identical predicate runs in the
883
- // serial path below. If any role/permission guard would reject
884
- // this call there, never start it eagerly here.
885
- if (_preDispatchDeny(call, toolKind, sessionRef) !== null) return null;
886
- const entry = { startedAt: Date.now(), endedAt: null, mutationEpoch: _mutationEpoch };
887
- _eagerInFlightSigs.set(_sig, call.id);
888
- entry.promise = (async () => {
889
- try {
890
- const permBlocked = _checkWorkerPermission(call.name, call.arguments, sessionRef);
891
- if (permBlocked !== null) return { ok: true, value: permBlocked };
892
- return { ok: true, value: await executeTool(call.name, call.arguments, cwd, sessionId, sessionRef, { toolCallId: call.id, signal }) };
893
- } catch (error) {
894
- return { ok: false, error };
895
- }
896
- })()
897
- .finally(() => {
898
- entry.endedAt = Date.now();
899
- // Intentionally do NOT delete _sig here — see the block
900
- // comment above. The sig must outlive promise settlement
901
- // so a later same-turn streaming duplicate stays blocked
902
- // at the _eagerInFlightSigs.has(_sig) guard until the turn
903
- // boundary recreates the Map.
904
- });
905
- pending.set(call.id, entry);
906
- return entry;
907
- };
908
- const startEagerRun = (calls, startIndex, dupSet) => {
909
- for (let j = startIndex; j < calls.length; j += 1) {
910
- const call = calls[j];
911
- if (!call?.id || !isEagerDispatchable(call.name, tools)) break;
912
- if (dupSet && dupSet.has(call.id)) continue;
913
- if (!startEagerTool(call) && !pending.has(call.id)) break;
914
- }
915
- };
916
- let _streamEagerBlocked = false;
917
- opts.onToolCall = (call) => {
918
- if (!isEagerDispatchable(call?.name, tools)) {
919
- _streamEagerBlocked = true;
920
- return;
921
- }
922
- if (_streamEagerBlocked) return;
923
- startEagerTool(call);
924
- };
925
- // Repair any dangling assistant tool_use left over from a prior
926
- // abort/error path before the provider sees the transcript. No-op
927
- // on the healthy iteration cycle (every assistant tool_use is
928
- // followed by tool results in the same loop body below).
929
- _ensureTranscriptPairing(messages, sessionId);
930
- // Strip soft-warn markers from prior tool results before the next
931
- // send. Marker bytes (Tool-budget(xN), Same-file reads(xN), etc.)
932
- // mutate every turn with dynamic counters, so leaving them in the
933
- // transcript breaks server-side prefix cache lookup on later turns.
934
- // The current turn's marker (if any) is appended AFTER this strip,
935
- // so the model still sees the self-correct hint on its own iteration.
936
- for (let _i = 0; _i < messages.length; _i++) {
937
- const _m = messages[_i];
938
- if (_m && _m.role === 'tool' && typeof _m.content === 'string' && _m.content.includes('⚠')) {
939
- const _stripped = stripSoftWarns(_m.content);
940
- if (_stripped !== _m.content) _m.content = _stripped;
941
- }
942
- }
943
- const sendStartedAt = Date.now();
944
- try {
945
- response = await provider.send(messages, model, sendTools.length ? sendTools : undefined, opts);
946
- } catch (sendErr) {
947
- // Context-window-exceeded is a deterministic refusal: the request is
948
- // simply too large. Retry ONCE with a stricter trim budget that
949
- // force-drops older non-system / non-latest turns before surfacing
950
- // the error. Unrelated errors (network, stall, auth, etc.) re-throw
951
- // untouched — they are handled by the provider/bridge retry layers.
952
- if (
953
- !isContextOverflowError(sendErr)
954
- || !(sessionRef && typeof sessionRef.contextWindow === 'number')
955
- ) {
956
- throw sendErr;
957
- }
958
- const overflowBudget = Math.floor(sessionRef.contextWindow * OVERFLOW_RETRY_TRIM_PERCENT);
959
- const overflowReserve = estimateRequestReserveTokens(sendTools.length ? sendTools : tools);
960
- const beforeCount = messages.length;
961
- let beforeBytes = null;
962
- try { beforeBytes = Buffer.byteLength(JSON.stringify(messages), 'utf8'); } catch { beforeBytes = null; }
963
- const messageTokensEst = estimateMessagesTokensSafe(messages);
964
- let retrimmed;
965
- try {
966
- retrimmed = trimMessages(messages, overflowBudget, { reserveTokens: overflowReserve });
967
- } catch (trimErr) {
968
- traceBridgeTrim({
969
- sessionId,
970
- iteration: nextIteration,
971
- stage: 'overflow_retry',
972
- prune_count: 0,
973
- trim_changed: false,
974
- input_prefix_hash: messagePrefixHash(messages),
975
- before_count: beforeCount,
976
- after_count: messages.length,
977
- before_bytes: beforeBytes,
978
- after_bytes: beforeBytes,
979
- context_window: sessionRef.contextWindow,
980
- budget_tokens: overflowBudget,
981
- reserve_tokens: overflowReserve,
982
- message_tokens_est: messageTokensEst,
983
- provider: sessionRef.provider,
984
- model: sessionRef.model || model,
985
- error: trimErr && trimErr.message ? trimErr.message : String(trimErr),
986
- error_code: 'BRIDGE_CONTEXT_OVERFLOW',
987
- });
988
- throw bridgeContextOverflowError({
989
- stage: 'overflow_retry',
990
- sessionId,
991
- sessionRef,
992
- model,
993
- budgetTokens: overflowBudget,
994
- reserveTokens: overflowReserve,
995
- messageTokensEst,
996
- }, trimErr);
997
- }
998
- const trimChanged = messagesArrayChanged(messages, retrimmed);
999
- const pruneCount = Math.max(beforeCount - retrimmed.length, 0);
1000
- messages.length = 0;
1001
- messages.push(...retrimmed);
1002
- let afterBytes = null;
1003
- try { afterBytes = Buffer.byteLength(JSON.stringify(messages), 'utf8'); } catch { afterBytes = null; }
1004
- traceBridgeTrim({
1005
- sessionId,
1006
- iteration: nextIteration,
1007
- stage: 'overflow_retry',
1008
- prune_count: pruneCount,
1009
- trim_changed: trimChanged,
1010
- input_prefix_hash: messagePrefixHash(messages),
1011
- before_count: beforeCount,
1012
- after_count: messages.length,
1013
- before_bytes: beforeBytes,
1014
- after_bytes: afterBytes,
1015
- context_window: sessionRef.contextWindow,
1016
- budget_tokens: overflowBudget,
1017
- reserve_tokens: overflowReserve,
1018
- message_tokens_est: messageTokensEst,
1019
- provider: sessionRef.provider,
1020
- model: sessionRef.model || model,
1021
- });
1022
- // The transcript prefix changed; the server-side conversation anchor
1023
- // (previous_response_id / WS continuation) is now invalid. Drop
1024
- // providerState so the retry starts a fresh chain instead of
1025
- // tripping a silent cache miss or hard mismatch.
1026
- providerState = undefined;
1027
- opts.providerState = undefined;
1028
- // Drop eager-dispatch state before the retry send. A tool_use
1029
- // streamed by the failed first send could otherwise orphan its
1030
- // eager result or be double-dispatched; force the retry's tools
1031
- // through the serial post-send path with a clean matching slate.
1032
- opts.onToolCall = undefined;
1033
- pending.clear();
1034
- _eagerInFlightSigs.clear();
1035
- try {
1036
- process.stderr.write(
1037
- `[loop] context overflow on send (sess=${sessionId || 'unknown'} iter=${nextIteration}); ` +
1038
- `retrying once at budget=${overflowBudget} reserve=${overflowReserve} ` +
1039
- `messages=${messages.length}\n`,
1040
- );
1041
- } catch { /* best-effort */ }
1042
- response = await provider.send(messages, model, sendTools.length ? sendTools : undefined, opts);
1043
- }
1044
- opts.onToolCall = undefined;
1045
- // Capture opaque state for the next turn (may be undefined — that's
1046
- // the stateless contract for providers that don't use continuation).
1047
- providerState = response?.providerState ?? undefined;
1048
- iterations = nextIteration;
1049
- traceBridgeLoop({
1050
- sessionId,
1051
- iteration: iterations,
1052
- sendMs: Date.now() - sendStartedAt,
1053
- messageCount: Array.isArray(messages) ? messages.length : 0,
1054
- bodyBytesEst: estimateProviderPayloadBytes(messages, model, sendTools),
1055
- });
1056
- // Accumulate usage across iterations — every billable slot, not just
1057
- // input/output. Anthropic cache_read/cache_write typically stay 0 on
1058
- // the first iteration and surge on later ones (warm prefix reuse),
1059
- // so aggregating only the head would silently drop most of the
1060
- // cache-side tokens.
1061
- if (response.usage) {
1062
- if (lastUsage) {
1063
- lastUsage.inputTokens += response.usage.inputTokens || 0;
1064
- lastUsage.outputTokens += response.usage.outputTokens || 0;
1065
- lastUsage.cachedTokens = (lastUsage.cachedTokens || 0) + (response.usage.cachedTokens || 0);
1066
- lastUsage.cacheWriteTokens = (lastUsage.cacheWriteTokens || 0) + (response.usage.cacheWriteTokens || 0);
1067
- lastUsage.promptTokens = (lastUsage.promptTokens || 0) + (response.usage.promptTokens || 0);
1068
- }
1069
- else {
1070
- lastUsage = {
1071
- inputTokens: response.usage.inputTokens || 0,
1072
- outputTokens: response.usage.outputTokens || 0,
1073
- cachedTokens: response.usage.cachedTokens || 0,
1074
- cacheWriteTokens: response.usage.cacheWriteTokens || 0,
1075
- promptTokens: response.usage.promptTokens || 0,
1076
- raw: response.usage.raw,
1077
- };
1078
- // Snapshot the first turn separately so callers can show
1079
- // iter1 vs final cache-hit ratios — first iter is the
1080
- // warm-prefix signal, final iter is the steady-state
1081
- // efficiency signal after tool-result accumulation.
1082
- firstTurnUsage = { ...lastUsage };
1083
- }
1084
- }
1085
- // Provider may have returned despite an abort (SDKs that don't honour
1086
- // signal) — bail before processing any of its output.
1087
- throwIfAborted();
1088
- // Incremental metric persistence (fix A): push per-iteration token delta
1089
- // immediately so watchdog / bridge type=list sees live totals mid-turn.
1090
- if (sessionId && opts.onUsageDelta && response.usage) {
1091
- try {
1092
- opts.onUsageDelta({
1093
- sessionId,
1094
- iterationIndex: iterations,
1095
- deltaInput: response.usage.inputTokens || 0,
1096
- deltaOutput: response.usage.outputTokens || 0,
1097
- // Cache delta carried alongside input/output so live metrics
1098
- // reflect the same token classes the terminal aggregate adds;
1099
- // additive — callers that ignore these fields keep working.
1100
- deltaCachedRead: response.usage.cachedTokens || 0,
1101
- deltaCacheWrite: response.usage.cacheWriteTokens || 0,
1102
- ts: Date.now(),
1103
- });
1104
- } catch { /* best-effort — never break the loop */ }
1105
- }
1106
- // No tool calls. For PUBLIC bridge workers, the bridge contract
1107
- // (rules/bridge/00-common.md) requires either a tool call or a
1108
- // `<final-answer>` wrapped reply.
1109
- // A text-only turn without those tags violates the contract (e.g.
1110
- // Opus 4.6 emits 'Now I'll polish…' preamble before its first tool
1111
- // call) and used to leave the session idle until the idle sweep
1112
- // collected it. Re-prompt the worker with a contract reminder; cap
1113
- // at 2 nudges so a model that never complies still terminates the
1114
- // loop. Hidden roles (cycle1-agent / cycle2-agent / explorer /
1115
- // scheduler-task / webhook-handler) are exempt:
1116
- // their own role rules define a different output contract (pipe-
1117
- // separated chunker output, structured pipe-format, etc.) and a
1118
- // text-only terminal turn is the correct shape — nudging them
1119
- // produces a contradictory user message that traps the model in a
1120
- // tool-call-blocked vs contract-required oscillation.
1121
- if (!response.toolCalls?.length) {
1122
- // No tool calls. Decide between final-answer accept vs nudge.
1123
- // - has content + non-hidden role → valid final, break.
1124
- // - empty content + hidden role → contract allows text-only
1125
- // terminal turn, break.
1126
- // - empty content + non-hidden role → one soft nudge. Repeated
1127
- // reminders waste turns and fragment the working context, so
1128
- // the second empty turn is accepted as terminal.
1129
- const hasContent = typeof response.content === 'string' && response.content.trim().length > 0;
1130
- const isHidden = HIDDEN_ROLE_NAMES.has(sessionRole);
1131
- const stopReason = response.stopReason ?? response.stop_reason ?? null;
1132
- const isIncompleteStop = stopReason && INCOMPLETE_STOP_REASONS.has(stopReason);
1133
- if (!hasContent && !isHidden) {
1134
- if (contractNudges >= 1) break;
1135
- contractNudges += 1;
1136
- let nudgeMsg;
1137
- if (isIncompleteStop) {
1138
- nudgeMsg = `[mixdog-runtime] Previous turn ended mid-synthesis (stopReason=${stopReason}) with empty content. Continue — emit <final-answer>...</final-answer> with your synthesis so far, or call more tools to finish.`;
1139
- } else {
1140
- nudgeMsg = '[mixdog-runtime] Your previous response was empty (no <final-answer> tag and no tool call). Either emit your final answer wrapped in <final-answer>...</final-answer> tags, or continue with tool calls. Do not return an empty turn.';
1141
- }
1142
- messages.push({ role: 'user', content: nudgeMsg });
1143
- continue;
1144
- }
1145
- break;
1146
- }
1147
- const calls = response.toolCalls;
1148
- toolCallsTotal += calls.length;
1149
- // Per-turn batch shape — one row per assistant turn so trace
1150
- // consumers can derive multi-tool adoption ratio without scanning
1151
- // every assistant message body.
1152
- recordToolBatch(sessionId, calls.length);
1153
- onToolCall?.(iterations, calls);
1154
- // Append assistant message with tool calls. reasoningItems is the
1155
- // OpenAI Responses API replay payload (encrypted_content blobs);
1156
- // providers that ignore it just see an extra field and drop it,
1157
- // openai-oauth.convertMessagesToResponsesInput emits matching
1158
- // type:'reasoning' input items on the next turn to keep the Codex
1159
- // server-side cache prefix stable.
1160
- const _assistantTurnMsg = {
1161
- role: 'assistant',
1162
- content: response.content || '',
1163
- toolCalls: compactToolCallsForHistory(calls),
1164
- ...(Array.isArray(response.reasoningItems) && response.reasoningItems.length
1165
- ? { reasoningItems: response.reasoningItems }
1166
- : {}),
1167
- ...(typeof response.reasoningContent === 'string' && response.reasoningContent
1168
- ? { reasoningContent: response.reasoningContent }
1169
- : {}),
1170
- };
1171
- messages.push(_assistantTurnMsg);
1172
- // Execute each tool and append results.
1173
- //
1174
- // Intra-turn duplicate suppression: when an LLM emits two tool_use
1175
- // blocks with identical (name, args) inside the SAME assistant turn,
1176
- // re-executing wastes tokens. Restricted to tools with
1177
- // `readOnlyHint:true` (= isEagerDispatchable) — bash/write/edit/
1178
- // apply_patch may be intentional repeats with distinct side effects.
1179
- // Pre-pass identifies duplicates BEFORE startEagerRun so eager
1180
- // dispatch also skips them, not just the for-body.
1181
- const _duplicateCallIds = new Set();
1182
- const _dupFirstId = new Map();
1183
- {
1184
- const _firstIdBySig = new Map();
1185
- for (const c of calls) {
1186
- if (!c?.id) continue;
1187
- if (!isEagerDispatchable(c.name, tools)) {
1188
- _firstIdBySig.clear();
1189
- continue;
1190
- }
1191
- const sig = _intraTurnSig(c.name, c.arguments);
1192
- const first = _firstIdBySig.get(sig);
1193
- if (first === undefined) {
1194
- _firstIdBySig.set(sig, c.id);
1195
- } else {
1196
- _duplicateCallIds.add(c.id);
1197
- _dupFirstId.set(c.id, first);
1198
- }
1199
- }
1200
- }
1201
- // R15: per-turn scalar read-count Map. Lifetime = this turn's tool-call batch.
1202
- // Declared between the duplicate-detection block and the for-loop so it resets
1203
- for (let callIndex = 0; callIndex < calls.length; callIndex += 1) {
1204
- const call = calls[callIndex];
1205
- if (isBuiltinTool(call.name)) {
1206
- call.name = canonicalizeBuiltinToolName(call.name);
1207
- }
1208
- if (_duplicateCallIds.has(call.id)) {
1209
- const _firstId = _dupFirstId.get(call.id);
1210
- const _stub = `[intra-turn-dedup] identical read-only \`${call.name}\` call was already executed in this same assistant turn as tool_use_id=${_firstId}. The first call's tool_result is in context immediately above; skipping re-execution to save tokens. If you needed a different slice of the file, narrow the next call (different path / offset / limit / pattern) so it has a distinct signature.`;
1211
- messages.push({
1212
- role: 'tool',
1213
- content: _stub,
1214
- toolCallId: call.id,
1215
- });
1216
- continue;
1217
- }
1218
- // Cross-iteration repeat-failure guard. Distinct from the
1219
- // intra-turn dedup above (which spans ONE assistant turn and
1220
- // resets every turn): when the model re-issues an IDENTICAL
1221
- // (name,args) call that has already failed REPEAT_FAIL_LIMIT times
1222
- // in a row across iterations, stop re-executing — the result will
1223
- // not change, and each retry burns a full (often slow) LLM
1224
- // round-trip until the hard iteration cap. Steer it to change
1225
- // approach instead.
1226
- const _repeatFailSig = _intraTurnSig(call.name, call.arguments);
1227
- {
1228
- const _rfg = sessionRef?._repeatFailGuard;
1229
- if (_rfg && _rfg.sig === _repeatFailSig && _rfg.count >= REPEAT_FAIL_LIMIT) {
1230
- messages.push({
1231
- role: 'tool',
1232
- content: `[repeat-failure-guard] This exact \`${call.name}\` call (identical arguments) has already failed ${_rfg.count} times in a row; not re-executing because the result will not change. Change approach: use different arguments, a different tool, or skip this step.`,
1233
- toolCallId: call.id,
1234
- });
1235
- continue;
1236
- }
1237
- }
1238
- if (sessionId) markSessionToolCall(sessionId, call.name);
1239
- let result;
1240
- let toolStartedAt;
1241
- let toolEndedAt;
1242
- const toolKind = getToolKind(call.name);
1243
- // Cross-turn read dedup. Mirrors Anthropic Claude Code's
1244
- // fileReadCache.ts: if the path's stat tuple (mtime/size/ino/dev)
1245
- // is unchanged since a prior read in THIS session, return the cached
1246
- // body instead of executing. Both scalar and array/object-array path
1247
- // forms are cached — keyed by (abs, offset, limit, mode, n) per entry.
1248
- //
1249
- // Scoped-tool cache (grep/glob/list + graph lookups): same idea
1250
- // but keyed by (toolName, canonical args) without per-file stat.
1251
- // These tools scan many files so a single stat tuple cannot cover
1252
- // them. The scoped cache registers dependency roots and write-class
1253
- // tools evict entries whose root contains the touched path.
1254
- let _readCacheHit = null;
1255
- let _scopedCacheHit = null;
1256
- let _executeOk = false;
1257
- let _resultKind = 'normal';
1258
- if (sessionId && _isReadTool(call.name)) {
1259
- _readCacheHit = tryReadCached({ sessionId, args: call.arguments, cwd });
1260
- } else if (sessionId && _isScopedCacheableTool(call.name)) {
1261
- _scopedCacheHit = tryScopedToolCached({ sessionId, toolName: _stripMcpPrefix(call.name), args: call.arguments, cwd });
1262
- }
1263
- try {
1264
- if (_readCacheHit !== null) {
1265
- toolStartedAt = Date.now();
1266
- toolEndedAt = toolStartedAt;
1267
- const _body = _readCacheHit.content;
1268
- // Return the cached body byte-for-byte instead of a
1269
- // human-readable cache marker. The marker made public
1270
- // bridge workers treat a successful cached read as a
1271
- // meta instruction and repeat the same read loop.
1272
- result = _body;
1273
- _resultKind = 'cache-hit';
1274
- _executeOk = true;
1275
- } else if (_scopedCacheHit !== null) {
1276
- toolStartedAt = Date.now();
1277
- toolEndedAt = toolStartedAt;
1278
- const _body = _scopedCacheHit.content;
1279
- result = _body;
1280
- _resultKind = 'scoped-cache-hit';
1281
- _executeOk = true;
1282
- } else {
1283
- // Fallback for providers that don't stream tool calls early:
1284
- // execute a contiguous read-only run in parallel, but never
1285
- // cross a write/bash/MCP boundary that may change state.
1286
- if (isEagerDispatchable(call.name, tools)) {
1287
- startEagerRun(calls, callIndex, _duplicateCallIds);
1288
- }
1289
- let eager = pending.get(call.id);
1290
- if (eager !== undefined && eager.mutationEpoch < _mutationEpoch) {
1291
- pending.delete(call.id);
1292
- eager = undefined;
1293
- }
1294
- if (eager !== undefined) {
1295
- toolStartedAt = eager.startedAt;
1296
- const settled = await eager.promise;
1297
- if (!settled.ok) throw settled.error;
1298
- result = settled.value;
1299
- toolEndedAt = eager.endedAt ?? Date.now();
1300
- const _eagerKind = classifyResultKind(result);
1301
- if (_eagerKind === 'error') {
1302
- _resultKind = 'error';
1303
- _executeOk = false;
1304
- } else {
1305
- _executeOk = true;
1306
- }
1307
- } else {
1308
- toolStartedAt = Date.now();
1309
- // Runtime permission guard. Schema profiles may hide
1310
- // tools for routing efficiency, but this remains the
1311
- // safety boundary for any tool_use that still reaches
1312
- // the loop. _preDispatchDeny is the SHARED helper used
1313
- // by both the eager dispatch path (startEagerTool) and
1314
- // this serial path — keeps the bridge-owned control-
1315
- // plane reject, role guards, wrapper guards, and
1316
- // permission guards consistent across both paths.
1317
- const _denyMsg = _preDispatchDeny(call, toolKind, sessionRef);
1318
- if (_denyMsg !== null) {
1319
- result = _denyMsg;
1320
- toolEndedAt = Date.now();
1321
- _resultKind = 'error';
1322
- } else {
1323
- const permBlocked = _checkWorkerPermission(call.name, call.arguments, sessionRef);
1324
- if (permBlocked !== null) {
1325
- result = permBlocked;
1326
- toolEndedAt = Date.now();
1327
- _resultKind = 'error';
1328
- } else {
1329
- result = await executeTool(call.name, call.arguments, cwd, sessionId, sessionRef, { toolCallId: call.id, signal });
1330
- toolEndedAt = Date.now();
1331
- // Boundary: tool-return string convention → structural kind.
1332
- // The only prefix check in this codebase; downstream layers
1333
- // operate on _resultKind.
1334
- if (classifyResultKind(result) === 'error') {
1335
- _resultKind = 'error';
1336
- _executeOk = false;
1337
- } else {
1338
- _executeOk = true;
1339
- }
1340
- // _resultKind stays 'normal' when tool returned a non-error string.
1341
- }
1342
- }
1343
- }
1344
- } // close: else branch of _readCacheHit check
1345
- }
1346
- catch (err) {
1347
- if (toolStartedAt === undefined) toolStartedAt = Date.now();
1348
- toolEndedAt = Date.now();
1349
- result = `Error: ${err instanceof Error ? err.message : String(err)}`;
1350
- _resultKind = 'error';
1351
- }
1352
- // Update the cross-iteration repeat-failure guard with this call's
1353
- // outcome: bump the consecutive-failure count for an identical
1354
- // signature, or clear it the moment the same call succeeds.
1355
- if (sessionRef) {
1356
- const _failed = !_executeOk || _resultKind === 'error';
1357
- if (_failed) {
1358
- sessionRef._repeatFailGuard = (sessionRef._repeatFailGuard?.sig === _repeatFailSig)
1359
- ? { sig: _repeatFailSig, count: sessionRef._repeatFailGuard.count + 1 }
1360
- : { sig: _repeatFailSig, count: 1 };
1361
- } else if (sessionRef._repeatFailGuard?.sig === _repeatFailSig) {
1362
- sessionRef._repeatFailGuard = null;
1363
- }
1364
- }
1365
- // A failed executed call keeps its FULL argument body in history so the
1366
- // model can retry against the original (a large apply_patch `patch` /
1367
- // edit `old_string` would otherwise be hidden behind a
1368
- // `[mixdog compacted …]` placeholder). Restored IMMEDIATELY — not at end
1369
- // of loop — so an abort or post-processing throw after this point cannot
1370
- // leave a failed edit compacted. Cache-safe: _assistantTurnMsg is not
1371
- // transmitted until the next provider.send. Early-continue paths (dedup /
1372
- // repeat-failure-guard) never reach here and stay compacted.
1373
- if ((!_executeOk || _resultKind === 'error') && call?.id) {
1374
- restoreToolCallBodyForId(_assistantTurnMsg, calls, call.id);
1375
- }
1376
- // Cross-turn cache maintenance — gate on both _executeOk and _resultKind==='normal'.
1377
- // _executeOk=false catches permission-blocked / catch-path / partial-fail results.
1378
- // _resultKind==='normal' ensures cache-hit refs are never re-stored (structural,
1379
- // no prefix sniffing).
1380
- // NOTE: setReadCached / setScopedToolCached are deferred below (after
1381
- // compressToolResult) so the cache holds the same content as conversation
1382
- // history. Cache-hit refs point to a tool_use_id whose message body matches
1383
- // exactly what's stored — no phantom full body.
1384
- if (sessionId && _executeOk && _resultKind === 'normal') {
1385
- const _toolBare = _stripMcpPrefix(call.name);
1386
- if (_readCacheHit === null && _isReadTool(call.name)) {
1387
- // Post-edit advisory: handle BOTH scalar and array forms
1388
- // of args.path. The array form (path:[a,b,c] or
1389
- // path:[{path:a},{path:b}]) was a coverage gap in R1 —
1390
- // an LLM that edits X then reads [X,Y] should still see
1391
- // the advisory for X.
1392
- const _argsPath = call.arguments?.path;
1393
- const _pathList = [];
1394
- if (typeof _argsPath === 'string') {
1395
- _pathList.push(_argsPath);
1396
- } else if (typeof call.arguments?.file_path === 'string') {
1397
- _pathList.push(call.arguments.file_path);
1398
- } else if (Array.isArray(_argsPath)) {
1399
- for (const _item of _argsPath) {
1400
- if (typeof _item === 'string') _pathList.push(_item);
1401
- else if (_item && typeof _item === 'object') {
1402
- const _itemPath = typeof _item.path === 'string' ? _item.path : _item.file_path;
1403
- if (typeof _itemPath === 'string') _pathList.push(_itemPath);
1404
- }
1405
- }
1406
- }
1407
- const _marks = [];
1408
- for (const _p of _pathList) {
1409
- const _m = consumePostEditMark({ sessionId, path: _p, cwd });
1410
- if (_m) _marks.push({ path: _p, mark: _m });
1411
- }
1412
- } else if (_toolBare === 'apply_patch') {
1413
- // apply_patch's args are a unified-diff text in `patch`
1414
- // (resolved against `base_path` or cwd). Parse the diff
1415
- // headers (`--- a/path` / `+++ b/path`) to extract the
1416
- // touched paths and invalidate / mark each one. Falls
1417
- // back to a full session clear only when no paths could
1418
- // be parsed (malformed diff or unknown format).
1419
- const _argsBase = call.arguments?.base_path;
1420
- const _patchBase = (typeof _argsBase === 'string' && _argsBase.length > 0)
1421
- ? (isAbsolute(_argsBase) ? _argsBase : resolvePath(cwd || process.cwd(), _argsBase))
1422
- : (cwd || process.cwd());
1423
- const _touched = extractTouchedPathsFromPatch(call.arguments?.patch);
1424
- if (_touched.length > 0) {
1425
- for (const _p of _touched) {
1426
- invalidatePathForSession(sessionId, _p, _patchBase);
1427
- markPostEdit({ sessionId, path: _p, cwd: _patchBase, toolName: 'apply_patch' });
1428
- // R20: cross-dispatch prefetch cache invalidation.
1429
- invalidatePrefetchCache(_p, _patchBase);
1430
- }
1431
- } else {
1432
- clearReadDedupSession(sessionId);
1433
- // R20: path unknown — can't target; no-op on prefetch cache
1434
- // (stat-validation at lookup time will naturally reject stale entries).
1435
- }
1436
- // Targeted scoped-cache invalidation: only evict entries whose
1437
- // dep paths intersect the touched set. Full wipe is the fallback
1438
- // when no paths were extracted (D).
1439
- if (_touched.length > 0) {
1440
- clearScopedToolsForSessionPaths(sessionId, _touched, _patchBase);
1441
- } else {
1442
- clearScopedToolsForSession(sessionId);
1443
- }
1444
- } else if (_isScalarWriteEditTool(call.name)) {
1445
- // Scalar `args.path` only: precise invalidate + advisory mark.
1446
- // Array-form (`edits[]`/`writes[]`): the tool may have partial-
1447
- // failed across paths and the result string aggregates;
1448
- // full-clear instead of falsely marking every path.
1449
- const _scalarPath = call.arguments?.path || call.arguments?.file_path;
1450
- const _hasArrayForm = Array.isArray(call.arguments?.edits)
1451
- || Array.isArray(call.arguments?.writes);
1452
- if (_hasArrayForm) {
1453
- clearReadDedupSession(sessionId);
1454
- clearScopedToolsForSession(sessionId);
1455
- // R20: array-form — walk each entry, extract its path,
1456
- // and invalidate the prefetch cache + mark post-edit for
1457
- // every distinct touched path. Falls back to the top-
1458
- // level `path` (or `file_path`) when an entry omits its
1459
- // own path. This covers both edit edits[] and write
1460
- // writes[] forms; entries without a resolvable path are
1461
- // silently skipped (their stat-validation safety net at
1462
- // next lookup still applies).
1463
- const _topPath = call.arguments?.path || call.arguments?.file_path;
1464
- const _entries = call.arguments?.edits || call.arguments?.writes || [];
1465
- const _seenPaths = new Set();
1466
- for (const _e of _entries) {
1467
- const _ep = _e?.path || _e?.file_path || _topPath;
1468
- if (typeof _ep === 'string' && _ep && !_seenPaths.has(_ep)) {
1469
- _seenPaths.add(_ep);
1470
- invalidatePathForSession(sessionId, _ep, cwd);
1471
- markPostEdit({ sessionId, path: _ep, cwd, toolName: _toolBare });
1472
- invalidatePrefetchCache(_ep, cwd);
1473
- }
1474
- }
1475
- if (_seenPaths.size > 0) {
1476
- clearScopedToolsForSessionPaths(sessionId, [..._seenPaths], cwd);
1477
- }
1478
- } else if (typeof _scalarPath === 'string') {
1479
- invalidatePathForSession(sessionId, _scalarPath, cwd);
1480
- markPostEdit({ sessionId, path: _scalarPath, cwd, toolName: _toolBare });
1481
- // R20: cross-dispatch prefetch cache invalidation.
1482
- invalidatePrefetchCache(_scalarPath, cwd);
1483
- // Targeted scoped-cache invalidation for the single touched path (D).
1484
- clearScopedToolsForSessionPaths(sessionId, [_scalarPath], cwd);
1485
- } else {
1486
- // No path extractable — full wipe fallback.
1487
- clearScopedToolsForSession(sessionId);
1488
- }
1489
- }
1490
- } // end _executeOk+_resultKind gate (scoped tool cache set)
1491
- // E: mutation tools (apply_patch / write / edit) must invalidate caches
1492
- // even on returned-error/partial-fail — the file state is unknown after
1493
- // an error exit, and some tools report failure as an Error: result string
1494
- // rather than throwing.
1495
- // This block runs unconditionally (not gated on _executeOk or _resultKind).
1496
- if (sessionId && (!_executeOk || _resultKind === 'error') && (_stripMcpPrefix(call.name) === 'apply_patch' || _isScalarWriteEditTool(call.name))) {
1497
- clearReadDedupSession(sessionId);
1498
- }
1499
- if (_isMutationTool(call.name)) {
1500
- _mutationEpoch += 1;
1501
- }
1502
- // Bash always clears scoped cache UNCONDITIONALLY — a mutating bash
1503
- // that throws or fails partway can still leave stale find_symbol / grep entries.
1504
- // Must not be gated on _executeOk or _resultKind.
1505
- if (sessionId && _isBashTool(call.name)) {
1506
- clearScopedToolsForSession(sessionId);
1507
- }
1508
- // R17 compression pipeline — correct ordering (compress → cache → push):
1509
- // 1. compressToolResult: lossless ANSI/dedup/separator passes.
1510
- // 2. setReadCached / setScopedToolCached: cache stores the SAME result that
1511
- // goes into conversation history. Cache-hit refs point to the tool_use_id
1512
- // whose message body matches — no phantom full body.
1513
- // 3. offload → hint → message push.
1514
- // Offload FIRST — before compress. Large RAW output goes to a disk sidecar
1515
- // + ~2K preview before any in-place shrink (lossless compress) can reduce
1516
- // it below the offload threshold and pre-empt the sidecar. When offload
1517
- // fires it replaces `result` with a short preview stub (<2K) referencing
1518
- // the on-disk path; the later compress is a no-op on that stub. compress
1519
- // then only touches output that stayed inline (<= threshold).
1520
- // Per-tool post-processing backstop. The executeTool try/catch
1521
- // above terminates BEFORE offload/compress/trim/hint/cache writes/
1522
- // trace/messages.push, so a maybeOffloadToolResult rejection (or
1523
- // any downstream throw) would otherwise leave the assistant
1524
- // tool_use message with no matching tool result. Wrap the whole
1525
- // post-processing window through messages.push() in a catch; on
1526
- // failure push a synthetic Error: tool result for this call.id
1527
- // and skip the cache writes for it.
1528
- let _postProcessOk = true;
1529
- try {
1530
- // Offload thresholds are keyed by BARE tool name
1531
- // (INLINE_THRESHOLD_BY_TOOL: grep=20k, bash=30k, read=Infinity, ...),
1532
- // so strip the MCP prefix exactly as the cache write below does.
1533
- // Otherwise an mcp__..__grep name misses its 20k grep cap and
1534
- // silently falls back to the 50k default — per-tool limits ignored.
1535
- const _toolBare = _stripMcpPrefix(call.name);
1536
- result = await maybeOffloadToolResult(sessionId, call.id, _toolBare, result);
1537
- result = compressToolResult(call.name, call.arguments, result, { sessionId, toolKind });
1538
- traceBridgeTool({
1539
- sessionId,
1540
- iteration: iterations,
1541
- toolName: call.name,
1542
- toolKind,
1543
- toolMs: toolEndedAt - toolStartedAt,
1544
- toolArgs: call.arguments,
1545
- role: sessionRef?.role || null,
1546
- model: sessionRef?.model || null,
1547
- resultKind: _resultKind,
1548
- resultText: result,
1549
- });
1550
- // Cache stores run AFTER compress+trim+offload+hint AND after all other
1551
- // post-processing (trace) so stored content == history content. Placing
1552
- // the cache writes immediately before messages.push ensures ANY throw
1553
- // earlier in post-processing skips the cache entirely — no stale or
1554
- // partial result is ever cached. Cache-hit refs pointing to an offloaded
1555
- // tool_use will show the offload stub; LLM can still recover the full
1556
- // body via the disk path in that stub.
1557
- if (sessionId && _executeOk && _resultKind === 'normal') {
1558
- if (_scopedCacheHit === null && _isScopedCacheableTool(call.name)) {
1559
- const _outcome = sessionRef?._scopedCacheOutcomeByCallId?.get(call.id);
1560
- setScopedToolCached({
1561
- sessionId,
1562
- toolName: _toolBare,
1563
- args: call.arguments,
1564
- cwd,
1565
- content: result,
1566
- toolUseId: call.id,
1567
- complete: _outcome ? _outcome.complete : true,
1568
- });
1569
- sessionRef?._scopedCacheOutcomeByCallId?.delete(call.id);
1570
- }
1571
- if (_readCacheHit === null && _isReadTool(call.name)) {
1572
- // Pass tool_use id so future cache-hits can reference the body's location in history.
1573
- setReadCached({ sessionId, args: call.arguments, cwd, content: result, toolUseId: call.id });
1574
- }
1575
- }
1576
- messages.push({
1577
- role: 'tool',
1578
- content: result,
1579
- toolCallId: call.id,
1580
- toolKind: _resultKind,
1581
- });
1582
- } catch (postErr) {
1583
- _postProcessOk = false;
1584
- // Post-processing failed AFTER a successful exec: the result is
1585
- // replaced with an error below, so preserve this call's full body
1586
- // too for a clean retry (mirrors the failed-exec path above).
1587
- if (call?.id) restoreToolCallBodyForId(_assistantTurnMsg, calls, call.id);
1588
- const _postMsg = `Error: tool result post-processing failed for "${call.name}": ${postErr instanceof Error ? postErr.message : String(postErr)}`;
1589
- // Always emit a matching tool result so the assistant
1590
- // tool_use isn't orphaned. Cache writes are placed at the
1591
- // end of the try block (immediately before messages.push),
1592
- // so ANY throw in post-processing reaches this catch before
1593
- // the cache is written — stale/partial results are never
1594
- // cached. The next read on the same path/scope re-executes
1595
- // naturally.
1596
- messages.push({
1597
- role: 'tool',
1598
- content: _postMsg,
1599
- toolCallId: call.id,
1600
- toolKind: 'error',
1601
- });
1602
- }
1603
- // Soft-cancel after each tool: if close landed during execution,
1604
- // discard the rest of the batch and skip the next provider.send.
1605
- throwIfAborted();
1606
- }
1607
- // About to re-send with tool results — transition back to connecting for the next turn.
1608
- if (sessionId) updateSessionStage(sessionId, 'connecting');
1609
- }
1610
- return {
1611
- ...response,
1612
- usage: lastUsage || response.usage,
1613
- lastTurnUsage: response.usage,
1614
- firstTurnUsage: firstTurnUsage || response.usage,
1615
- iterations,
1616
- toolCallsTotal,
1617
- providerState,
1618
- };
1619
- }