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,1991 +0,0 @@
1
- import { createRequire } from 'module';
2
- import { fileURLToPath } from 'url';
3
- import { randomBytes, createHash } from 'crypto';
4
- import { existsSync } from 'fs';
5
- import { join, resolve as pathResolve } from 'path';
6
- import { homedir } from 'os';
7
- import { getProvider, providerInputExcludesCache } from '../providers/registry.mjs';
8
- import { agentLoop } from './loop.mjs';
9
- import { getMcpTools } from '../mcp/client.mjs';
10
- import { getInternalTools, executeInternalTool } from '../internal-tools.mjs';
11
- import { BUILTIN_TOOLS, executeBuiltinTool } from '../tools/builtin.mjs';
12
- import { PATCH_TOOL_DEFS } from '../tools/patch-tool-defs.mjs';
13
- import { CODE_GRAPH_TOOL_DEFS } from '../tools/code-graph-tool-defs.mjs';
14
- import { executeCodeGraphTool } from '../tools/code-graph.mjs';
15
- import { closeBashSession } from '../tools/bash-session.mjs';
16
- import { collectSkillsCached, buildSkillToolDefs, loadAgentTemplate, loadRoleTemplate, composeSystemPrompt, collectProjectMd } from '../context/collect.mjs';
17
- import { saveSession, saveSessionAsync, loadSession, deleteSession, listStoredSessions, getStoredSessionsRaw, sweepStaleSessions, markSessionClosed, publishHeartbeat, deleteHeartbeat, setLiveSession } from './store.mjs';
18
- import { clearReadDedupSession, tryPrefetchCached, setPrefetchCached, invalidatePrefetchCache } from './read-dedup.mjs';
19
- import { clearOffloadSession } from './tool-result-offload.mjs';
20
- import { classifyResultKind } from './result-classification.mjs';
21
- import { createAbortController } from '../../../shared/abort-controller.mjs';
22
- import { logLlmCall } from '../../../shared/llm/usage-log.mjs';
23
- import { resolvePluginData, DEFAULT_PLUGIN, DEFAULT_MARKETPLACE } from '../../../shared/plugin-paths.mjs';
24
- import { traceBridgeTool, appendBridgeTrace } from '../bridge-trace.mjs';
25
- import { isHiddenRole } from '../internal-roles.mjs';
26
- import { runWithCwdOverride, pwd } from '../../../shared/user-cwd.mjs';
27
- import { maxMtimeRecursive } from '../cache-mtime.mjs';
28
- // Phase B: Pool B Tier 2 content builder (common rules only).
29
- // Loaded once per process via createRequire so the CJS module reaches us.
30
- const _require = createRequire(import.meta.url);
31
- const _rulesBuilder = (() => {
32
- const candidates = [
33
- process.env.CLAUDE_PLUGIN_ROOT && join(process.env.CLAUDE_PLUGIN_ROOT, 'lib', 'rules-builder.cjs'),
34
- ].filter(Boolean);
35
- for (const p of candidates) {
36
- try { return _require(p); } catch { /* fall through */ }
37
- }
38
- // Fallback: walk up from this file's location to find lib/rules-builder.cjs.
39
- try { return _require('../../../../lib/rules-builder.cjs'); } catch { return null; }
40
- })();
41
-
42
- // bridgeRules is the bridge shared prefix (shared rules + bridge common rules +
43
- // user agent configs). It's rebuilt from disk
44
- // by rules-builder.cjs on every call; since createSession fires on every
45
- // Pool B/C bridge turn, that's a lot of redundant readFileSync + concat.
46
- // BP1/BP3 cache — invalidated by source file mtime, not a timer.
47
- // Cheap: O(sentinel-count) stat calls on each bridge turn, no I/O otherwise.
48
- // BP1 cache — single shared entry. buildBridgeInjectionContent is
49
- // role-agnostic (true cross-role common), so every bridge role reuses the
50
- // same prefix bytes.
51
- let _bridgeRulesCache = null;
52
- let _bridgeRulesMtime = 0;
53
- function _buildBridgeRules() {
54
- if (!_rulesBuilder || typeof _rulesBuilder.buildBridgeInjectionContent !== 'function') return '';
55
- const PLUGIN_ROOT = process.env.CLAUDE_PLUGIN_ROOT
56
- || join(homedir(), '.claude', 'plugins', 'marketplaces', DEFAULT_MARKETPLACE, 'external_plugins', DEFAULT_PLUGIN);
57
- const DATA_DIR = resolvePluginData();
58
- const RULES_DIR = join(PLUGIN_ROOT, 'rules');
59
- const mtime = maxMtimeRecursive([
60
- join(RULES_DIR, 'shared'),
61
- join(RULES_DIR, 'bridge'),
62
- join(DATA_DIR, 'roles'),
63
- join(DATA_DIR, 'mixdog-config.json'),
64
- ]);
65
- if (_bridgeRulesCache !== null && mtime <= _bridgeRulesMtime) {
66
- return _bridgeRulesCache;
67
- }
68
- try {
69
- const built = _rulesBuilder.buildBridgeInjectionContent({ PLUGIN_ROOT, DATA_DIR });
70
- _bridgeRulesCache = built;
71
- _bridgeRulesMtime = mtime;
72
- return built;
73
- } catch (e) {
74
- throw new Error(`[session] bridge common rules build failed: ${e.message}`);
75
- }
76
- }
77
-
78
- // BP3 role-specific cache — keyed by role. webhook / schedule / hidden
79
- // retrieval roles each have their own scoped instruction set; other roles
80
- // return ''.
81
- const _roleSpecificCache = new Map(); // role → { value, mtime }
82
- function _buildRoleSpecific(currentRole) {
83
- if (!_rulesBuilder || typeof _rulesBuilder.buildBridgeRoleSpecificContent !== 'function') return '';
84
- if (!currentRole) return '';
85
- const PLUGIN_ROOT = process.env.CLAUDE_PLUGIN_ROOT
86
- || join(homedir(), '.claude', 'plugins', 'marketplaces', DEFAULT_MARKETPLACE, 'external_plugins', DEFAULT_PLUGIN);
87
- const DATA_DIR = resolvePluginData();
88
- const RULES_DIR = join(PLUGIN_ROOT, 'rules');
89
- const mtime = maxMtimeRecursive([
90
- join(RULES_DIR, 'shared'),
91
- join(DATA_DIR, 'mixdog-config.json'),
92
- join(DATA_DIR, 'webhooks'),
93
- join(DATA_DIR, 'schedules'),
94
- ]);
95
- const entry = _roleSpecificCache.get(currentRole);
96
- if (entry && mtime <= entry.mtime) {
97
- return entry.value;
98
- }
99
- try {
100
- const built = _rulesBuilder.buildBridgeRoleSpecificContent({ PLUGIN_ROOT, DATA_DIR, currentRole });
101
- _roleSpecificCache.set(currentRole, { mtime, value: built });
102
- return built;
103
- } catch (e) {
104
- throw new Error(`[session] role-specific rules build failed (role: ${currentRole}): ${e.message}`);
105
- }
106
- }
107
-
108
- // Smart Bridge is optional — injected via setSmartBridge() during plugin init
109
- // so session creation never depends on a circular import. If never injected,
110
- // createSession simply falls back to classic preset-only behavior.
111
- let _smartBridgeApi = null;
112
- let _smartBridgeWarned = false;
113
-
114
- /**
115
- * Inject the Smart Bridge singleton. Called once by agent/index.mjs init()
116
- * after initSmartBridge(). Safe to call multiple times — later calls
117
- * replace the previous reference.
118
- */
119
- export function setSmartBridge(api) {
120
- _smartBridgeApi = api || null;
121
- }
122
-
123
- function getSmartBridgeSync() {
124
- return _smartBridgeApi;
125
- }
126
-
127
- /**
128
- * Thrown when a session is closed while a call is in-flight. Callers (bridge
129
- * handler, CLI) should render this as "cancelled" rather than a hard error.
130
- */
131
- export class SessionClosedError extends Error {
132
- constructor(sessionId, reason, closeReason) {
133
- super(reason ? `Session "${sessionId}" closed: ${reason}` : `Session "${sessionId}" closed`);
134
- this.name = 'SessionClosedError';
135
- this.sessionId = sessionId;
136
- this.cancelled = true;
137
- // closeReason is the diagnostic enum (request-abort / manual /
138
- // idle-sweep / runner-crash). Kept separate from `reason` (the free
139
- // -form message) so consumers can branch on it without regex parsing.
140
- this.reason = closeReason || null;
141
- }
142
- }
143
- const HEARTBEAT_THROTTLE_MS = 60_000; // 60s
144
-
145
- // Merge externally-connected MCP tools with the plugin's in-process tools
146
- // (registered by agent's toolExecutor bridge). Internal tools are exposed
147
- // under their bare names — no mcp__ prefix, since the dispatcher in
148
- // server.mjs handles them directly without a transport.
149
- // Sorted deterministically by name — protects BP_1 hash stability from
150
- // listTools() ordering churn. Anthropic / OpenAI / Gemini all hash the
151
- // tools array verbatim, so any reorder rewrites the prefix.
152
- // No cache: getMcpTools() and getInternalTools() are O(n) in-memory reads;
153
- // the sort overhead on ~30 tools is negligible.
154
- function _getMcpTools() {
155
- const mcp = getMcpTools() || [];
156
- const internalRaw = getInternalTools() || [];
157
- const internal = internalRaw.map(t => ({
158
- name: t.name,
159
- description: typeof t.description === 'string' ? t.description : '',
160
- inputSchema: t.inputSchema || { type: 'object', properties: {} },
161
- // Keep annotations so the permission filter / role invariants can
162
- // tell read-only from write-capable internal tools, and so
163
- // bridgeHidden can be read during deny filtering.
164
- annotations: t.annotations || {},
165
- }));
166
- return [...mcp, ...internal].sort((a, b) => {
167
- const an = a?.name || '';
168
- const bn = b?.name || '';
169
- return an < bn ? -1 : an > bn ? 1 : 0;
170
- });
171
- }
172
-
173
- // Phase D-2 — profile.tools resolution.
174
- //
175
- // `toolSpec` may be:
176
- // • Array<string> (profile.tools) — toolset ids like "tools:filesystem",
177
- // "tools:git", "tools:mcp", "tools:search",
178
- // "tools:readonly", or the literal "full"
179
- // • 'full' / 'readonly' / 'mcp' — legacy preset.tools strings
180
- // • null / undefined — same as 'full' (historical default)
181
- //
182
- // Array form is the Phase B/D target: each profile declares its tool surface
183
- // explicitly, BP_1 hash differs across profiles with different tool subsets
184
- // (by design — sub-task profile cannot see bash; worker-full can), and
185
- // adding a new toolset id here is a localised change.
186
- //
187
- // Unified-shard policy — the session's tool array normally never narrows
188
- // with permission or role. Bridge sessions share the same schema so BP_1
189
- // stays bit-identical and the provider-side cache shard is shared
190
- // workspace-wide. Rare specialist roles may pass schemaAllowedTools from a
191
- // declarative hidden-role toolSchemaProfile to keep their first-turn routing
192
- // surface intentionally tiny; runtime permission guards in loop.mjs remain
193
- // the fail-safe either way.
194
-
195
- const SESSION_ROUTE_TOOL_ORDER = [
196
- 'code_graph',
197
- 'glob',
198
- 'list',
199
- 'grep',
200
- 'read',
201
- 'edit',
202
- 'write',
203
- 'apply_patch',
204
- 'bash',
205
- 'job_wait',
206
- ];
207
- const SESSION_ROUTE_TOOL_RANK = new Map(SESSION_ROUTE_TOOL_ORDER.map((name, index) => [name, index]));
208
- const FILESYSTEM_TOOL_NAMES = new Set([
209
- 'code_graph',
210
- 'glob',
211
- 'list',
212
- 'grep',
213
- 'read',
214
- 'edit',
215
- 'write',
216
- 'apply_patch',
217
- ]);
218
- const READONLY_TOOL_NAMES = new Set([
219
- 'code_graph',
220
- 'glob',
221
- 'list',
222
- 'grep',
223
- 'read',
224
- ]);
225
-
226
- function orderSessionTools(tools) {
227
- return tools.map((tool, index) => ({ tool, index }))
228
- .sort((a, b) => {
229
- const ar = SESSION_ROUTE_TOOL_RANK.get(a.tool?.name) ?? 10_000;
230
- const br = SESSION_ROUTE_TOOL_RANK.get(b.tool?.name) ?? 10_000;
231
- if (ar !== br) return ar - br;
232
- return a.index - b.index;
233
- })
234
- .map((entry) => entry.tool);
235
- }
236
-
237
- const ALL_BUILTIN_SESSION_TOOLS = orderSessionTools(_dedupByName([
238
- ...BUILTIN_TOOLS,
239
- ...PATCH_TOOL_DEFS,
240
- ...CODE_GRAPH_TOOL_DEFS,
241
- ]));
242
-
243
- function resolveSessionTools(toolSpec, skills, { ownerIsBridge = false } = {}) {
244
- const mcp = _getMcpTools();
245
- // Bridge sessions freeze the 3 skill meta-tools into the schema
246
- // unconditionally — concrete skill resolution is cwd-scoped at tool-call
247
- // time (loop.mjs), so the schema bytes stay bit-identical across roles /
248
- // cwds and the provider cache shard does not fragment.
249
- const skillTools = buildSkillToolDefs(skills, { ownerIsBridge });
250
- return _computeBaseTools(toolSpec, mcp, skillTools);
251
- }
252
-
253
- // Dedup by name, first occurrence wins. BUILTIN_TOOLS is passed in ahead
254
- // of the MCP-registered internal tools so plugin-side definitions take
255
- // precedence when both surfaces declare the same name (e.g. read / grep / glob).
256
- // Without this merge, Anthropic rejected the request with
257
- // "tools: Tool names must be unique" and the orchestrator burned up to
258
- // 20 iterations retrying before the final answer landed.
259
- function _dedupByName(tools) {
260
- const seen = new Map();
261
- for (const t of tools) {
262
- const n = t?.name;
263
- if (!n || seen.has(n)) continue;
264
- seen.set(n, t);
265
- }
266
- return [...seen.values()];
267
- }
268
-
269
- // Bridge visibility is declared per-tool via annotations.bridgeHidden.
270
- // Tools with bridgeHidden:true are stripped from bridge sessions at schema
271
- // build time (see deny filtering below). No code-level name list needed.
272
-
273
- function _computeBaseTools(toolSpec, mcp, skillTools) {
274
- if (Array.isArray(toolSpec)) {
275
- if (toolSpec.length === 0) {
276
- // Explicit "no tools" — skill meta tools still travel so the model
277
- // can at least discover and invoke skills if that is the one
278
- // dynamic surface the profile retains.
279
- return _dedupByName([...skillTools]);
280
- }
281
- if (toolSpec.includes('full')) {
282
- return _dedupByName([...ALL_BUILTIN_SESSION_TOOLS, ...mcp, ...skillTools]);
283
- }
284
- const byName = new Map();
285
- const add = (tool) => { if (tool?.name && !byName.has(tool.name)) byName.set(tool.name, tool); };
286
- const addMany = (arr) => { for (const t of arr) add(t); };
287
- for (const tagRaw of toolSpec) {
288
- const tag = String(tagRaw || '').trim();
289
- switch (tag) {
290
- case 'tools:filesystem':
291
- addMany(ALL_BUILTIN_SESSION_TOOLS.filter(t => FILESYSTEM_TOOL_NAMES.has(t.name)));
292
- break;
293
- case 'tools:readonly':
294
- addMany(ALL_BUILTIN_SESSION_TOOLS.filter(t => READONLY_TOOL_NAMES.has(t.name)));
295
- break;
296
- case 'tools:bash':
297
- case 'tools:git':
298
- case 'tools:analysis':
299
- // Three aliases for the same surface — `bash` is the only
300
- // shell-class tool. `tools:git` / `tools:analysis` exist so
301
- // profile authors can name the intent (git workflows / data
302
- // analysis) without inventing new toolset ids.
303
- addMany(ALL_BUILTIN_SESSION_TOOLS.filter(t => t.name === 'bash'));
304
- break;
305
- case 'tools:mcp':
306
- addMany(mcp);
307
- break;
308
- case 'tools:search':
309
- // Name-pattern match: picks up `search` and any future tool
310
- // whose name contains `search`. `recall` and `explore` deliberately do NOT match
311
- // — they need `tools:mcp` (full mcp surface) or their own
312
- // toolset id if a role wants targeted retrieval. Public bridge
313
- // roles never reach the wrapper bodies regardless: see the
314
- // isBlockedPublicWrapperCall guard in session/loop.mjs.
315
- addMany(mcp.filter(t => /search/i.test(t?.name || '')));
316
- break;
317
- default:
318
- process.stderr.write(`[session] unknown toolset id "${tag}" (profile.tools); skipping\n`);
319
- }
320
- }
321
- return _dedupByName([...byName.values(), ...skillTools]);
322
- }
323
-
324
- switch (toolSpec) {
325
- case 'mcp':
326
- return _dedupByName([...mcp, ...skillTools]);
327
- case 'readonly': {
328
- const readTools = ALL_BUILTIN_SESSION_TOOLS.filter(t => READONLY_TOOL_NAMES.has(t.name));
329
- return _dedupByName([...readTools, ...mcp, ...skillTools]);
330
- }
331
- case 'full':
332
- default:
333
- return _dedupByName([...ALL_BUILTIN_SESSION_TOOLS, ...mcp, ...skillTools]);
334
- }
335
- }
336
-
337
- function permissionFromToolSpec(toolSpec) {
338
- if (toolSpec === 'readonly') return 'read';
339
- if (toolSpec === 'mcp') return 'mcp';
340
- if (Array.isArray(toolSpec)) {
341
- const tags = new Set(toolSpec.map(t => String(t || '').trim()));
342
- const hasWriteOrShell = tags.has('full')
343
- || tags.has('tools:filesystem')
344
- || tags.has('tools:bash')
345
- || tags.has('tools:git')
346
- || tags.has('tools:analysis');
347
- if (tags.has('tools:readonly') && !hasWriteOrShell) return 'read';
348
- }
349
- return null;
350
- }
351
-
352
- let nextId = Date.now();
353
- // Known context windows for the current-generation models this plugin
354
- // routes to. Anything not listed falls through to guessContextWindow() —
355
- // local llama/mistral/phi default to 8192, everything else 128000. Keep
356
- // this map trimmed to live models; older generations slow down reads
357
- // without buying anything.
358
- const CONTEXT_WINDOWS = {
359
- // OpenAI GPT-5.x family
360
- 'gpt-5.5': 272000,
361
- 'gpt-5.4': 272000,
362
- 'gpt-5.4-mini': 272000,
363
- 'gpt-5.4-nano': 272000,
364
- // Anthropic Claude 4.x
365
- 'claude-opus-4-8': 1000000,
366
- 'claude-opus-4-7': 1000000,
367
- 'claude-sonnet-4-6': 1000000,
368
- 'claude-haiku-4-5-20251001': 200000,
369
- // Google Gemini 3.x
370
- 'gemini-3.1-pro': 1000000,
371
- 'gemini-3-pro': 1000000,
372
- 'gemini-3.5-flash': 1000000,
373
- 'gemini-3-flash': 1000000,
374
- };
375
- function guessContextWindow(model) {
376
- if (CONTEXT_WINDOWS[model])
377
- return CONTEXT_WINDOWS[model];
378
- if (model.includes('llama') || model.includes('mistral') || model.includes('phi'))
379
- return 8192;
380
- return 128000;
381
- }
382
- function positiveContextWindow(value) {
383
- const n = Number(value);
384
- return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
385
- }
386
- function resolveSessionContextWindow(provider, model) {
387
- const info = typeof provider?.getCachedModelInfo === 'function'
388
- ? provider.getCachedModelInfo(model)
389
- : null;
390
- return positiveContextWindow(info?.contextWindow)
391
- || positiveContextWindow(info?.context_window)
392
- || guessContextWindow(model);
393
- }
394
- // Provider-scoped unified cache key. Goal: all orchestrator-internal
395
- // dispatches (bridge/maintenance/mcp/scheduler/webhook) targeting the
396
- // same provider land in a single server-side cache shard, so the
397
- // shared prefix (tools + system + pool system prompt) is reused
398
- // regardless of role. Per-role / per-session differentiation lives in
399
- // the message tail, which is naturally separated by content hashing.
400
- const PROVIDER_ALIAS = {
401
- 'openai-oauth': 'codex', // ChatGPT subscription (Codex backend)
402
- 'anthropic-oauth': 'claude', // Claude Max subscription
403
- 'openai': 'openai',
404
- 'anthropic': 'anthropic',
405
- 'gemini': 'gemini',
406
- 'deepseek': 'deepseek',
407
- 'xai': 'xai',
408
- };
409
- function providerCacheKey(provider, override) {
410
- if (override) return String(override);
411
- if (!provider) return 'mixdog-default';
412
- return `mixdog-${PROVIDER_ALIAS[provider] || provider}`;
413
- }
414
-
415
- // ── Prefetch permission guard ─────────────────────────────────────────────────
416
- // Mirrors _checkWorkerPermission in loop.mjs for tool calls that originate
417
- // in the prefetch path (outside the agent loop). Returns an error string if
418
- // blocked, or null if allowed.
419
- const _permEvalForPrefetch = (() => {
420
- const _req = createRequire(import.meta.url);
421
- try {
422
- const { dirname: _pdir, resolve: _pres } = _req('path');
423
- const _hooksLib = _pres(_pdir(fileURLToPath(import.meta.url)), '../../../../hooks/lib/permission-evaluator.cjs');
424
- return _req(_hooksLib).evaluatePermission;
425
- } catch { return null; }
426
- })();
427
- function _guardedPrefetchTool(toolName, toolArgs, session) {
428
- if (!_permEvalForPrefetch) return null;
429
- // Same baseline as _checkWorkerPermission: when no explicit mode is
430
- // attached to the session, run the evaluator under 'default' so the
431
- // bypass-proof hard-deny patterns still apply during prefetch dispatch.
432
- const permissionMode = session?.permissionMode || 'default';
433
- const projectDir = session?.cwd || undefined;
434
- const userCwd = session?.cwd || undefined;
435
- const MCP_PFX = 'mcp__plugin_mixdog_mixdog__';
436
- const fullName = toolName.startsWith(MCP_PFX) || toolName.startsWith('mcp__') ? toolName : `${MCP_PFX}${toolName}`;
437
- try {
438
- const { decision, reason } = _permEvalForPrefetch({ toolName: fullName, toolInput: toolArgs || {}, permissionMode, projectDir, userCwd });
439
- if (decision === 'deny' || decision === 'ask') {
440
- return `Error: prefetch tool "${toolName}" blocked (decision=${decision}): ${reason}`;
441
- }
442
- } catch (e) {
443
- process.stderr.write(`[prefetch-guard] evaluator error: ${e?.message}\n`);
444
- }
445
- return null;
446
- }
447
-
448
- async function _tryBridgeExplicitPrefetch(session, explicitPrefetch) {
449
- if (!explicitPrefetch || typeof explicitPrefetch !== 'object') return null;
450
- if (session?.owner !== 'bridge') return null;
451
- const parts = [];
452
- const failed = [];
453
- const totalEntries = [];
454
- // files[] — string entries use the default head excerpt; object entries
455
- // {path, n?, full?} let the caller widen the window or pull the full file
456
- // so worker doesn't have to re-read deep ranges of an already-prefetched
457
- // file (a recurring iter burner observed in baseline session telemetry).
458
- const _rawFilesIn = Array.isArray(explicitPrefetch.files) ? explicitPrefetch.files : [];
459
- const _readOptsByFile = new Map();
460
- const files = [];
461
- const _seenFiles = new Set();
462
- const _addPrefetchFile = (file, opts = null) => {
463
- if (typeof file !== 'string' || !file) return;
464
- if (!_seenFiles.has(file)) {
465
- _seenFiles.add(file);
466
- files.push(file);
467
- }
468
- if (!opts || Object.keys(opts).length === 0) return;
469
- const prev = _readOptsByFile.get(file) || {};
470
- const merged = { ...prev };
471
- if (opts.mode === 'full') {
472
- merged.mode = 'full';
473
- delete merged.n;
474
- } else if (merged.mode !== 'full' && Number.isFinite(opts.n) && opts.n > 0) {
475
- merged.n = Math.max(Number(merged.n) || 0, opts.n);
476
- }
477
- if (Object.keys(merged).length > 0) _readOptsByFile.set(file, merged);
478
- };
479
- for (const entry of _rawFilesIn) {
480
- if (typeof entry === 'string' && entry) {
481
- _addPrefetchFile(entry);
482
- } else if (entry && typeof entry === 'object' && typeof entry.path === 'string' && entry.path) {
483
- const opts = {};
484
- if (entry.full === true) opts.mode = 'full';
485
- else if (Number.isFinite(entry.n) && entry.n > 0) opts.n = entry.n;
486
- _addPrefetchFile(entry.path, opts);
487
- }
488
- }
489
- if (files.length > 0) {
490
- const _pfGuard = _guardedPrefetchTool('read', { path: files }, session);
491
- if (_pfGuard) {
492
- process.stderr.write(`[bridge-prefetch] files read blocked: ${_pfGuard}\n`);
493
- failed.push(...files);
494
- totalEntries.push(...files);
495
- } else {
496
- totalEntries.push(...files);
497
- // R20: per-file prefetch cache (cross-dispatch, process-local).
498
- // Try each file from cache first; batch misses into one disk read.
499
- const { resolve: _pfResolve, isAbsolute: _pfIsAbs, normalize: _pfNorm } = await import('path');
500
- const _pfCwd = session.cwd || null;
501
- function _pfAbsPath(f) {
502
- const abs = _pfIsAbs(f) ? f : _pfResolve(_pfCwd || process.cwd(), f);
503
- return _pfNorm(abs);
504
- }
505
- const fileHits = []; // { file, abs, content } — satisfied from cache
506
- const fileMisses = []; // { file, abs } — need disk read
507
- for (const f of files) {
508
- const abs = _pfAbsPath(f);
509
- // Skip the cross-dispatch cache when the caller asked for a
510
- // non-default window (custom n or full-file). Cache key is the
511
- // path alone, so a default-window cache hit would silently feed
512
- // the wrong slice back to the next caller.
513
- const hit = _readOptsByFile.has(f) ? null : tryPrefetchCached(abs);
514
- if (hit) {
515
- fileHits.push({ file: f, abs, content: hit.content });
516
- } else {
517
- fileMisses.push({ file: f, abs });
518
- }
519
- }
520
- // Disk read for misses (single batch call).
521
- const missFiles = fileMisses.map(m => m.file);
522
- const missResults = {}; // file → content string
523
- if (missFiles.length > 0) {
524
- // Read each miss file individually so we can cache per-file.
525
- // The files list is small (typically 2-5), so N awaits is fine.
526
- await Promise.all(missFiles.map(async (f) => {
527
- const opts = _readOptsByFile.get(f) || {};
528
- const readArgs = { path: f };
529
- if (opts.mode === 'full') {
530
- readArgs.mode = 'full';
531
- } else {
532
- readArgs.mode = 'head';
533
- readArgs.n = Number.isFinite(opts.n) ? opts.n : 120;
534
- }
535
- const out = await executeInternalTool('read', readArgs).catch((e) => {
536
- process.stderr.write(`[bridge-prefetch] file read failed (${f}): ${e && e.message || e}\n`);
537
- return null;
538
- });
539
- if (out !== null) {
540
- missResults[f] = String(out);
541
- }
542
- }));
543
- // Cache successful miss results.
544
- for (const { file, abs } of fileMisses) {
545
- const content = missResults[file];
546
- if (content && classifyResultKind(content) !== 'error') {
547
- // Only cache default-window reads; custom-window results
548
- // would poison the shared cross-dispatch cache.
549
- if (!_readOptsByFile.has(file)) setPrefetchCached(abs, content);
550
- } else if (content === undefined || classifyResultKind(content) === 'error') {
551
- failed.push(file);
552
- }
553
- }
554
- }
555
- // Assemble combined output preserving original file order.
556
- const readParts = [];
557
- const hitByFile = new Map(fileHits.map((h) => [h.file, h]));
558
- for (const f of files) {
559
- const hitEntry = hitByFile.get(f);
560
- if (hitEntry) {
561
- readParts.push(hitEntry.content);
562
- continue;
563
- }
564
- const content = missResults[f];
565
- if (content && classifyResultKind(content) !== 'error') {
566
- readParts.push(content);
567
- }
568
- // else: already pushed to failed above
569
- }
570
- if (readParts.length > 0) {
571
- parts.push(`### prefetch files\nread ${readParts.length}\n\n${readParts.join('\n\n')}`);
572
- }
573
- // Log hit/miss counters so dispatch telemetry shows prefetch effectiveness.
574
- process.stderr.write(
575
- `[prefetch] files=${files.length} cached=${fileHits.length} miss=${fileMisses.length} failed=${failed.length}\n`
576
- );
577
- // Attach stats to session so post-hoc analyzers (inspect-session.mjs)
578
- // can see prefetch effectiveness without parsing stderr logs.
579
- if (session && typeof session === 'object') {
580
- if (!session.prefetchStats) session.prefetchStats = { files: 0, cached: 0, miss: 0, failed: 0 };
581
- session.prefetchStats.files += files.length;
582
- session.prefetchStats.cached += fileHits.length;
583
- session.prefetchStats.miss += fileMisses.length;
584
- session.prefetchStats.failed += failed.length;
585
- }
586
- }
587
- }
588
- // callers[]
589
- const callers = Array.isArray(explicitPrefetch.callers) ? explicitPrefetch.callers.filter(c => c && typeof c.symbol === 'string') : [];
590
- {
591
- const callerTasks = callers.map(({ symbol, file }) => {
592
- const cgArgs = { mode: 'callers', symbol };
593
- if (file) cgArgs.file = file;
594
- if (session?.cwd) cgArgs.cwd = session.cwd;
595
- totalEntries.push(symbol);
596
- const blocked = _guardedPrefetchTool('code_graph', cgArgs, session);
597
- if (blocked) {
598
- process.stderr.write(`[bridge-prefetch] callers(${symbol}) blocked: ${blocked}\n`);
599
- return Promise.resolve({ symbol, out: null, blocked: true });
600
- }
601
- return executeCodeGraphTool('code_graph', cgArgs, session?.cwd)
602
- .then(out => ({ symbol, out }))
603
- .catch(e => {
604
- process.stderr.write(`[bridge-prefetch] callers(${symbol}) failed: ${e && e.message || e}\n`);
605
- return { symbol, out: null };
606
- });
607
- });
608
- const callerResults = await Promise.allSettled(callerTasks);
609
- for (const r of callerResults) {
610
- const { symbol, out, blocked } = r.status === 'fulfilled' ? r.value : { symbol: '?', out: null };
611
- if (blocked) { failed.push(symbol); continue; }
612
- if (out && classifyResultKind(String(out)) !== 'error') {
613
- parts.push(`### prefetch callers ${symbol}\n${out}`);
614
- } else {
615
- failed.push(symbol);
616
- }
617
- }
618
- }
619
- // references[]
620
- const references = Array.isArray(explicitPrefetch.references) ? explicitPrefetch.references.filter(r => r && typeof r.symbol === 'string') : [];
621
- {
622
- const refTasks = references.map(({ symbol, file }) => {
623
- const cgArgs = { mode: 'references', symbol };
624
- if (file) cgArgs.file = file;
625
- if (session?.cwd) cgArgs.cwd = session.cwd;
626
- totalEntries.push(symbol);
627
- const blocked = _guardedPrefetchTool('code_graph', cgArgs, session);
628
- if (blocked) {
629
- process.stderr.write(`[bridge-prefetch] references(${symbol}) blocked: ${blocked}\n`);
630
- return Promise.resolve({ symbol, out: null, blocked: true });
631
- }
632
- return executeCodeGraphTool('code_graph', cgArgs, session?.cwd)
633
- .then(out => ({ symbol, out }))
634
- .catch(e => {
635
- process.stderr.write(`[bridge-prefetch] references(${symbol}) failed: ${e && e.message || e}\n`);
636
- return { symbol, out: null };
637
- });
638
- });
639
- const refResults = await Promise.allSettled(refTasks);
640
- for (const r of refResults) {
641
- const { symbol, out, blocked } = r.status === 'fulfilled' ? r.value : { symbol: '?', out: null };
642
- if (blocked) { failed.push(symbol); continue; }
643
- if (out && classifyResultKind(String(out)) !== 'error') {
644
- parts.push(`### prefetch references ${symbol}\n${out}`);
645
- } else {
646
- failed.push(symbol);
647
- }
648
- }
649
- }
650
- if (session && typeof session === 'object' && (callers.length > 0 || references.length > 0)) {
651
- if (!session.prefetchStats) session.prefetchStats = { files: 0, cached: 0, miss: 0, failed: 0, callers: 0, references: 0 };
652
- session.prefetchStats.callers = (session.prefetchStats.callers || 0) + callers.length;
653
- session.prefetchStats.references = (session.prefetchStats.references || 0) + references.length;
654
- }
655
- if (parts.length === 0) {
656
- // All entries failed but Lead presence must still be signalled — emit
657
- // warn-only so the gate logic can distinguish "prefetch was requested"
658
- // from "no prefetch at all".
659
- if (totalEntries.length > 0 && failed.length > 0) {
660
- return `<prefetch-warn>${failed.length} of ${totalEntries.length} prefetch entries failed: ${[...new Set(failed)].join(', ')}</prefetch-warn>`;
661
- }
662
- return null;
663
- }
664
- const warnLine = failed.length > 0
665
- ? `<prefetch-warn>${failed.length} of ${totalEntries.length} prefetch entries failed: ${[...new Set(failed)].join(', ')}</prefetch-warn>\n`
666
- : '';
667
- return `${warnLine}<prefetch>\n${parts.join('\n\n')}\n</prefetch>`;
668
- }
669
-
670
- // --- bridge spawn (createSession) ---
671
- // opts can pass either a `preset` object (from config.presets) or raw provider/model.
672
- // Preset shape: { name, provider, model, effort?, fast?, tools? }
673
- //
674
- // Smart Bridge integration:
675
- // opts.taskType / opts.role / opts.profileId — enables profile-aware routing.
676
- // Rule-based SmartRouter resolves these synchronously; the resolved
677
- // profile controls context filtering (skip.skills/memory/etc) and cache
678
- // strategy. If no rule matches, falls back to classic preset behavior.
679
- // opts.profile — pre-resolved profile (bypasses router; used by async
680
- // callers who already ran SmartBridge.resolve()).
681
- // opts.providerCacheOpts — pre-resolved cache options merged into ask() sendOpts.
682
- export function createSession(opts) {
683
- const presetObj = opts.preset && typeof opts.preset === 'object' ? opts.preset : null;
684
-
685
- // --- Smart Bridge profile resolution (best-effort, sync) ---
686
- let profile = opts.profile || null;
687
- let providerCacheOpts = opts.providerCacheOpts || null;
688
- if (!profile && (opts.taskType || opts.role || opts.profileId)) {
689
- const smartBridge = getSmartBridgeSync();
690
- if (smartBridge) {
691
- try {
692
- const resolved = smartBridge.resolveSync({
693
- taskType: opts.taskType,
694
- role: opts.role,
695
- profileId: opts.profileId,
696
- preset: presetObj?.name || (typeof opts.preset === 'string' ? opts.preset : null),
697
- provider: opts.provider || presetObj?.provider,
698
- });
699
- if (resolved) {
700
- profile = resolved.profile;
701
- providerCacheOpts = resolved.providerCacheOpts;
702
- }
703
- } catch (e) {
704
- // Smart Bridge error — log once, fall back to classic behavior.
705
- if (!_smartBridgeWarned) {
706
- _smartBridgeWarned = true;
707
- process.stderr.write(`[session] smart bridge resolve failed: ${e.message}\n`);
708
- }
709
- }
710
- }
711
- }
712
-
713
- const providerName = opts.provider || presetObj?.provider
714
- || (profile?.preferredProviders?.[0]);
715
- const modelName = opts.model || presetObj?.model;
716
- // opts.tools (caller-supplied) wins over presetObj.tools — caller
717
- // intent ('tools:readonly' from Pool C, etc.) must override the
718
- // preset's default 'full'. Previous priority let HAIKU's tools='full'
719
- // shadow Pool C's explicit readonly request, leaking write tools and
720
- // bash into a read-only agent.
721
- const toolPreset = opts.tools || presetObj?.tools || (typeof opts.preset === 'string' ? opts.preset : null) || 'full';
722
- const effort = presetObj?.effort || opts.effort || null;
723
- const fast = presetObj?.fast === true || opts.fast === true;
724
- if (!providerName)
725
- throw new Error('createSession: provider is required');
726
- if (!modelName)
727
- throw new Error('createSession: model is required');
728
- const provider = getProvider(providerName);
729
- if (!provider)
730
- throw new Error(`Provider "${providerName}" not found or not enabled`);
731
- const id = `sess_${process.pid}_${nextId++}_${Date.now()}_${randomBytes(16).toString('hex')}`;
732
- const messages = [];
733
- const agentTemplate = opts.agent ? loadAgentTemplate(opts.agent, opts.cwd) : null;
734
- const skills = collectSkillsCached(opts.cwd);
735
-
736
- // Bridge shared prefix (bit-identical across roles). Hidden roles reuse the
737
- // same shared bridge rules so the cache shard stays stable across bridge
738
- // callers. User-defined data (DATA_DIR roles/schedules/webhooks) is baked
739
- // into BP1 as a single fixed-value monolithic block so every role shares
740
- // one cache shard. A user edit invalidates BP1 once and the new prefix
741
- // re-warms across all roles together.
742
- const bridgeRulesRole = opts.role || profile?.taskType || null;
743
- const bridgeRules = opts.skipBridgeRules ? '' : _buildBridgeRules();
744
- const roleSpecific = opts.skipBridgeRules ? '' : _buildRoleSpecific(bridgeRulesRole);
745
- // Project MD (cwd-based, Tier 3 slot).
746
- const projectContext = collectProjectMd(opts.cwd);
747
-
748
- // Role template (Phase B §4 — UI-managed). Reads <DATA_DIR>/roles/<role>.md
749
- // and parses frontmatter (description, permission). The template is
750
- // injected into the Tier 3 system-reminder so role differences never
751
- // touch the BP_2 cache prefix.
752
- const resolvedRole = opts.role || profile?.taskType || null;
753
- const dataDir = process.env.CLAUDE_PLUGIN_DATA;
754
- const roleTemplate = resolvedRole && dataDir
755
- ? loadRoleTemplate(resolvedRole, dataDir)
756
- : null;
757
-
758
- // Bridge sessions must not inherit role/profile/preset tool narrowing: Pool
759
- // B and Pool C share one bit-identical tool schema for BP_1/BP_2 cache
760
- // reuse, and permission differences are enforced only at call time. Raw
761
- // non-bridge callers keep the historical profile.tools / preset.tools
762
- // behaviour.
763
- const toolSpec = opts.owner === 'bridge'
764
- ? 'full'
765
- : (Array.isArray(profile?.tools) ? profile.tools : toolPreset);
766
-
767
- // Prompt permission is metadata only. Preset tool restrictions must NOT
768
- // enter the prompt, or they split the shared bridge cache tail; they map
769
- // to toolPermission below and are enforced only at call time.
770
- const permission = opts.permission
771
- || roleTemplate?.permission
772
- || null;
773
- const toolPermission = opts.permission
774
- || profile?.permission
775
- || roleTemplate?.permission
776
- || permissionFromToolSpec(toolPreset)
777
- || null;
778
- let toolsForRouting = resolveSessionTools(toolSpec, skills, { ownerIsBridge: opts.owner === 'bridge' });
779
- // Fail-closed permission intersection: when a role declares an explicit
780
- // permission (from user-workflow.json or the role template), intersect the
781
- // resolved tool list with the permission's allow/deny lists. If the
782
- // intersection produces an empty set the permission config is broken —
783
- // fail closed (zero tools) rather than silently falling back to the full
784
- // preset, which would grant the role more surface than declared.
785
- if (toolPermission && typeof toolPermission === 'object') {
786
- const allowSet = Array.isArray(toolPermission.allow) && toolPermission.allow.length > 0
787
- ? new Set(toolPermission.allow.map(n => String(n).toLowerCase()))
788
- : null;
789
- const denySet = Array.isArray(toolPermission.deny) && toolPermission.deny.length > 0
790
- ? new Set(toolPermission.deny.map(n => String(n).toLowerCase()))
791
- : null;
792
- if (allowSet || denySet) {
793
- const filtered = toolsForRouting.filter(t => {
794
- const name = String(t?.name || '').toLowerCase();
795
- if (denySet && denySet.has(name)) return false;
796
- if (allowSet && !allowSet.has(name)) return false;
797
- return true;
798
- });
799
- // Fail-closed: an empty intersection means the permission config is
800
- // misconfigured — do not silently fall back to the full preset.
801
- toolsForRouting = filtered;
802
- if (filtered.length === 0) {
803
- process.stderr.write(`[session] WARN: role permission intersection produced 0 tools — failing closed (role=${opts.role || 'unknown'})
804
- `);
805
- }
806
- }
807
- }
808
-
809
- const { baseRules, roleCatalog, sessionMarker, volatileTail } = composeSystemPrompt({
810
- userPrompt: opts.systemPrompt,
811
- bridgeRules: bridgeRules || undefined,
812
- roleSpecific: roleSpecific || undefined,
813
- agentTemplate: agentTemplate || undefined,
814
- roleTemplate: roleTemplate || undefined,
815
- hasSkills: skills.length > 0,
816
- profile: profile || undefined,
817
- role: resolvedRole,
818
- skipRoleReminder: opts.skipRoleReminder || false,
819
- permission,
820
- taskBrief: opts.taskBrief || null,
821
- projectContext: projectContext || null,
822
- tools: toolsForRouting,
823
- bashIsPersistent: opts.owner === 'bridge' && toolsForRouting.some(t => t?.name === 'bash'),
824
- // Effective cwd rides in tier3Reminder so explore-like tools know
825
- // their search root without needing to shove "Override cwd:" into
826
- // the user message body (that used to fragment the shard prefix).
827
- cwd: opts.cwd || null,
828
- // BP2 catalog policy — explicit-cache providers see the unified
829
- // all-roles catalog; implicit-prefix-hash providers keep self-only.
830
- provider: providerName || null,
831
- });
832
- // 4-BP layout (see composeSystemPrompt docs):
833
- // system block #1 = baseRules — BP1 (1h) shared across ALL roles
834
- // system block #2 = roleCatalog — BP2 (1h) scoped role catalog + project
835
- // first <system-reminder> user = sessionMarker — BP3 (1h) role-specific task body
836
- // second <system-reminder> user = volatileTail — rides near BP4 (5m)
837
- // Anthropic multi-block system pins each block with cache_control.
838
- // OpenAI gets a stable provider cache key/session prefix. Gemini relies
839
- // on implicit prompt caching only, so hits are observed, not treated as a
840
- // guaranteed warm shard.
841
- if (baseRules) {
842
- messages.push({ role: 'system', content: baseRules });
843
- }
844
- if (roleCatalog) {
845
- messages.push({ role: 'system', content: roleCatalog });
846
- }
847
- if (sessionMarker) {
848
- messages.push({ role: 'user', content: `<system-reminder>\n${sessionMarker}\n</system-reminder>` });
849
- messages.push({ role: 'assistant', content: 'Session context noted.' });
850
- }
851
- if (volatileTail) {
852
- messages.push({ role: 'user', content: `<system-reminder>\n${volatileTail}\n</system-reminder>` });
853
- messages.push({ role: 'assistant', content: 'Understood.' });
854
- }
855
- if (opts.files?.length) {
856
- const fileContext = opts.files
857
- .map(f => `### ${f.path}\n\`\`\`\n${f.content}\n\`\`\``)
858
- .join('\n\n');
859
- messages.push({ role: 'user', content: `Reference files:\n\n${fileContext}` });
860
- messages.push({ role: 'assistant', content: 'Understood. I have the files in context.' });
861
- }
862
- let tools = toolsForRouting;
863
-
864
- // Schema filtering applied after schema build:
865
- // - opts.schemaAllowedTools : declarative hidden-role schema profile
866
- // allowlist for tiny specialist roles where one-shot tool routing
867
- // beats the shared-schema cache win.
868
- // - opts.disallowedTools : per-call caller override (Anthropic
869
- // BuiltInAgentDefinition pattern)
870
- // - annotations.bridgeHidden : declarative per-tool flag (tools.json
871
- // and internal tool defs). Pool A (Lead) still sees all tools.
872
- //
873
- const hasCallerAllow = Array.isArray(opts.schemaAllowedTools);
874
- const callerAllow = hasCallerAllow ? opts.schemaAllowedTools.map(n => String(n).toLowerCase()) : [];
875
- if (hasCallerAllow) {
876
- const allowSet = new Set(callerAllow);
877
- const before = tools.length;
878
- tools = tools.filter(t => allowSet.has(String(t?.name || '').toLowerCase()));
879
- if (tools.length !== before) {
880
- process.stderr.write(`[session] schemaAllowedTools=${callerAllow.join(',')} kept ${tools.length}/${before} tools\n`);
881
- }
882
- }
883
- const callerDeny = Array.isArray(opts.disallowedTools) ? opts.disallowedTools.map(n => String(n)) : [];
884
- if (callerDeny.length) {
885
- const denySet = new Set(callerDeny);
886
- const before = tools.length;
887
- tools = tools.filter(t => !denySet.has(String(t?.name || '').toLowerCase()));
888
- if (tools.length !== before) {
889
- process.stderr.write(`[session] disallowedTools=${callerDeny.join(',')} stripped ${before - tools.length} tools\n`);
890
- }
891
- }
892
- if (opts.owner === 'bridge') {
893
- const before = tools.length;
894
- tools = tools.filter(t => !t?.annotations?.bridgeHidden);
895
- if (tools.length !== before) {
896
- process.stderr.write(`[session] bridgeHidden stripped ${before - tools.length} tools\n`);
897
- }
898
- }
899
-
900
- // Bridge tool canonicalization: keep route-sensitive tools in policy order
901
- // while preserving deterministic MCP/skill order for BP1 shard stability.
902
- if (opts.owner === 'bridge') {
903
- tools = orderSessionTools(tools);
904
- }
905
-
906
- // Unified-shard policy — no broad role-specific schema filter. Keep
907
- // bridge schemas shared unless a hidden-role schema profile explicitly
908
- // passes schemaAllowedTools for a small specialist; broad role
909
- // whitelists would fragment the cache shard.
910
- if (resolvedRole) {
911
- process.stderr.write(`[session] role=${resolvedRole} permission=${permission || 'full'} toolPermission=${toolPermission || 'full'} tools=${tools.length}\n`);
912
- }
913
- const session = {
914
- id,
915
- provider: providerName,
916
- model: modelName,
917
- messages,
918
- contextWindow: resolveSessionContextWindow(provider, modelName),
919
- tools,
920
- preset: toolPreset,
921
- presetName: presetObj?.name || null,
922
- effort,
923
- fast,
924
- agent: opts.agent,
925
- owner: opts.owner || 'user',
926
- mcpPid: process.pid,
927
- scopeKey: opts.scopeKey || null,
928
- lane: opts.lane || 'bridge',
929
- cwd: opts.cwd,
930
- createdAt: Date.now(),
931
- updatedAt: Date.now(),
932
- lastHeartbeatAt: null,
933
- totalInputTokens: 0,
934
- totalOutputTokens: 0,
935
- // Refreshed on each completed ask() — surfaced by bridge type=list for
936
- // debugging + consumed by store.mjs's idle-sweep to reclaim stalled
937
- // bridge sessions past RUNNING_STALL_MS.
938
- lastUsedAt: Date.now(),
939
- tokensCumulative: 0,
940
- role: opts.role || null,
941
- taskType: opts.taskType || null,
942
- maxLoopIterations: Number.isFinite(opts.maxLoopIterations) ? opts.maxLoopIterations : null,
943
- // Bridge tag (auto worker{n} on spawn) persisted so the forked status
944
- // process (statusline) + aggregator can read it from the session JSON.
945
- // In-process send/close still resolve via _tagSessionRegistry.
946
- bridgeTag: opts.bridgeTag || null,
947
- // Prompt permission is separate from runtime toolPermission so preset
948
- // restrictions do not fragment the bridge cache prefix.
949
- permission: permission || null,
950
- toolPermission: toolPermission || null,
951
- // Origin tag written into every bridge-trace usage row so analytics
952
- // can slice by (sourceType, sourceName) — e.g. maintenance/cycle1,
953
- // scheduler/daily-standup, webhook/github-push, lead/worker.
954
- sourceType: opts.sourceType || null,
955
- sourceName: opts.sourceName || null,
956
- // Provider-scoped unified cache key — one shard per provider,
957
- // shared across all roles / sources (bridge/maintenance/mcp/
958
- // scheduler/webhook). Role or source-specific context must be
959
- // injected into the message tail, not the shared prefix.
960
- promptCacheKey: providerCacheKey(presetObj?.provider || opts.provider, opts.cacheKeyOverride),
961
- // Bridge shell continuity: when a bridge session explicitly opts into
962
- // persistent shell state (`bash` with `persistent:true`, or direct
963
- // `bash_session`), the minted bash_session id is stored here so later
964
- // opted-in `bash` calls can reuse the same shell state.
965
- implicitBashSessionId: null,
966
- // Tracks every persistent bash session id minted during this
967
- // orchestrator session so closeSession can kill them all, not just
968
- // the most recently recorded one.
969
- allBashSessionIds: [],
970
- // Smart Bridge metadata — optional. Applied on every ask() to merge
971
- // profile-driven cache settings into provider sendOpts.
972
- profileId: profile?.id || null,
973
- permissionMode: opts.permissionMode ?? null,
974
- providerCacheOpts: providerCacheOpts || null,
975
- ownerSessionId: opts.ownerSessionId || null,
976
- clientHostPid: opts.clientHostPid || null,
977
- };
978
- // In-process registry + async debounced save: same-process create → load
979
- // reads live memory; disk flush is for cross-process / restart durability.
980
- setLiveSession(session);
981
- saveSession(session);
982
- return session;
983
- }
984
-
985
- // ── Runtime liveness map ──────────────────────────────────────────────
986
- // In-memory only. Tracks per-session stage + stream heartbeat so bridge type=list
987
- // can surface whether a session is actually alive vs stuck. Never persisted —
988
- // heartbeats would otherwise churn the session JSON on every SSE delta.
989
- // Entry shape: {
990
- // stage, lastStreamDeltaAt, lastToolCall, lastError, updatedAt,
991
- // controller?: AbortController, // set while an ask is in flight
992
- // generation?: number, // snapshot taken at ask start
993
- // closed?: boolean, // flipped by closeSession()
994
- // }
995
- const _runtimeState = new Map();
996
- const VALID_STAGES = new Set([
997
- 'connecting', 'requesting', 'streaming', 'tool_running', 'idle', 'error', 'done', 'cancelling',
998
- ]);
999
- function _touchRuntime(id) {
1000
- let entry = _runtimeState.get(id);
1001
- if (!entry) {
1002
- entry = { stage: 'idle', lastStreamDeltaAt: null, lastToolCall: null, lastError: null, updatedAt: Date.now() };
1003
- _runtimeState.set(id, entry);
1004
- }
1005
- return entry;
1006
- }
1007
- export function updateSessionStage(id, stage) {
1008
- if (!id || !VALID_STAGES.has(stage)) return;
1009
- const entry = _touchRuntime(id);
1010
- const now = Date.now();
1011
- entry.stage = stage;
1012
- entry.lastProgressAt = now;
1013
- entry.updatedAt = now;
1014
- }
1015
- /**
1016
- * Reset heartbeat-visible fields for a new ask. Preserves controller/generation/
1017
- * closed (lifecycle) but clears the previous run's streaming state so stale
1018
- * lastToolCall / lastStreamDeltaAt from the previous ask don't leak into the
1019
- * new one.
1020
- */
1021
- export function markSessionAskStart(id) {
1022
- if (!id) return;
1023
- const entry = _touchRuntime(id);
1024
- entry.stage = 'connecting';
1025
- entry.lastStreamDeltaAt = null;
1026
- entry.lastToolCall = null;
1027
- entry.lastError = null;
1028
- // A new ask starts a fresh turn lifecycle — clear any stale empty-final
1029
- // classification from the prior turn so inspectBridgeEntry doesn't keep
1030
- // short-circuiting to 'empty-synthesis' (which would disable stall
1031
- // detection for the entire new turn).
1032
- entry.emptyFinal = false;
1033
- entry.emptyFinalAt = null;
1034
- // askStartedAt is the watchdog's fallback reference when a session
1035
- // hangs before any stream delta arrives. Without it, a provider that
1036
- // never returns a first token would stall forever because the watchdog
1037
- // keys solely on lastStreamDeltaAt.
1038
- const now = Date.now();
1039
- entry.askStartedAt = now;
1040
- entry.lastProgressAt = now;
1041
- entry.updatedAt = now;
1042
- // Publish heartbeat immediately so the status aggregator picks the
1043
- // session up in the connecting / requesting window. Without this the
1044
- // .hb file only landed on the first stream chunk — producing a 3–10s
1045
- // (xhigh: 30s+) invisible gap where bridge sessions ran but the CC
1046
- // statusline showed no maintenance/agent badge. STREAM_FRESH_MS (5 min)
1047
- // still drops a session whose provider truly never returns a chunk;
1048
- // markSessionStreamDelta keeps refreshing once chunks arrive.
1049
- publishHeartbeat(id, now);
1050
- }
1051
- export async function markSessionStreamDelta(id) {
1052
- if (!id) return;
1053
- // Non-creating lookup: a live ask ALWAYS has a runtime entry (markSessionAskStart
1054
- // creates it before streaming begins). _touchRuntime would instead resurrect a
1055
- // blank entry — and closeSession()/idle-sweep clear _runtimeState on a deferred
1056
- // tick while a detached provider stream may still be trickling deltas. A delta
1057
- // arriving after that clear must NOT re-create an entry or it would republish the
1058
- // .hb heartbeat that markSessionClosed deleted, orphaning a dead session's
1059
- // heartbeat indefinitely (the disk tombstone blocks ask resumption but not this
1060
- // path). Skip a missing, tombstoned, or aborted entry — never refresh liveness.
1061
- const entry = _runtimeState.get(id);
1062
- if (!entry || entry.closed || entry.controller?.signal?.aborted) return;
1063
- const now = Date.now();
1064
- entry.lastStreamDeltaAt = now;
1065
- entry.lastProgressAt = now;
1066
- // Only promote to 'streaming' if we were in a pre-stream stage; never downgrade
1067
- // mid-tool (tool_running has its own delta source if the tool streams back).
1068
- if (entry.stage === 'connecting' || entry.stage === 'requesting') {
1069
- entry.stage = 'streaming';
1070
- }
1071
- // Lightweight heartbeat (≤5s self-throttled) for the status aggregator.
1072
- // Disk-side session.lastHeartbeatAt below is the heavy 60s zombie-reaper
1073
- // signal; the .hb file is the fast fresh-session signal consumed by the
1074
- // status line.
1075
- publishHeartbeat(id, now);
1076
- const session = loadSession(id);
1077
- if (session && now - (session.lastHeartbeatAt || 0) > HEARTBEAT_THROTTLE_MS) {
1078
- session.lastHeartbeatAt = now;
1079
- await saveSessionAsync(session, { expectedGeneration: session.generation });
1080
- }
1081
- entry.updatedAt = now;
1082
- }
1083
- export function markSessionToolCall(id, toolName) {
1084
- if (!id) return;
1085
- const entry = _touchRuntime(id);
1086
- entry.stage = 'tool_running';
1087
- entry.lastToolCall = toolName || null;
1088
- entry.toolStartedAt = Date.now();
1089
- entry.lastProgressAt = entry.toolStartedAt;
1090
- entry.updatedAt = entry.toolStartedAt;
1091
- publishHeartbeat(id, entry.toolStartedAt);
1092
- }
1093
- export function markSessionDone(id, { empty = false } = {}) {
1094
- if (!id) return;
1095
- const entry = _touchRuntime(id);
1096
- entry.stage = 'done';
1097
- entry.lastError = null;
1098
- entry.askStartedAt = null;
1099
- entry.toolStartedAt = null;
1100
- // Non-empty completion: drop any stale empty-final flag so a subsequent
1101
- // ask on the same reusable runtime entry starts clean. Empty-final
1102
- // completions preserve the flag (set by markSessionEmptyFinal just prior).
1103
- if (!empty) {
1104
- entry.emptyFinal = false;
1105
- entry.emptyFinalAt = null;
1106
- }
1107
- const doneTs = Date.now();
1108
- entry.doneAt = doneTs;
1109
- entry.lastProgressAt = doneTs;
1110
- entry.updatedAt = doneTs;
1111
- // Terminal stage — drop the heartbeat so the status badge releases
1112
- // immediately. A subsequent ask on the same session re-publishes via
1113
- // markSessionStreamDelta on the first chunk.
1114
- deleteHeartbeat(id);
1115
- }
1116
- // Tag a session as having completed with empty final synthesis (no
1117
- // content/reasoning). Distinct from `markSessionDone`: still a success
1118
- // (no abort), but the stall watchdog and post-mortem tools can
1119
- // distinguish "finished empty" from "finished with content" without
1120
- // mistaking the silence for a stall.
1121
- export function markSessionEmptyFinal(id) {
1122
- if (!id) return;
1123
- const entry = _touchRuntime(id);
1124
- entry.emptyFinal = true;
1125
- entry.emptyFinalAt = Date.now();
1126
- }
1127
- export function markSessionError(id, msg) {
1128
- if (!id) return;
1129
- const entry = _touchRuntime(id);
1130
- entry.stage = 'error';
1131
- entry.lastError = msg ? String(msg).slice(0, 200) : null;
1132
- entry.askStartedAt = null;
1133
- entry.toolStartedAt = null;
1134
- // Error path is a non-empty completion (we have an error message, not a
1135
- // silent empty final). Clear the flag so the next ask starts clean.
1136
- entry.emptyFinal = false;
1137
- entry.emptyFinalAt = null;
1138
- const errTs = Date.now();
1139
- entry.doneAt = errTs;
1140
- entry.lastProgressAt = errTs;
1141
- entry.updatedAt = errTs;
1142
- deleteHeartbeat(id);
1143
- }
1144
- export function getSessionRuntime(id) {
1145
- return id ? (_runtimeState.get(id) || null) : null;
1146
- }
1147
- /**
1148
- * Iterate all active session runtimes. Used by the stream watchdog.
1149
- * Returns an iterable of [sessionId, entry] pairs; consumers should
1150
- * treat entries as read-only snapshots and avoid mutating them.
1151
- */
1152
- export function forEachSessionRuntime() {
1153
- return _runtimeState.entries();
1154
- }
1155
-
1156
- // --- Incremental metric persistence (fix A) ---
1157
- // Per-session idempotency tracking: sessionId → Set of seen iterationIndex keys.
1158
- const _metricSeenIter = new Map();
1159
-
1160
- /**
1161
- * Persist incremental usage delta immediately after each provider.send iteration.
1162
- * Idempotency key `sessionId:iterationIndex` ensures a retry of the same iteration
1163
- * index overwrites instead of double-counting.
1164
- */
1165
- export async function persistIterationMetrics(delta) {
1166
- if (!delta || !delta.sessionId) return;
1167
- const { sessionId, iterationIndex, deltaInput, deltaOutput, deltaCachedRead, deltaCacheWrite, ts } = delta;
1168
- let seen = _metricSeenIter.get(sessionId);
1169
- if (!seen) {
1170
- seen = new Set();
1171
- _metricSeenIter.set(sessionId, seen);
1172
- }
1173
- const ikey = `${sessionId}:${iterationIndex}`;
1174
- const isReplay = seen.has(ikey);
1175
- seen.add(ikey);
1176
- const runtimeEntry = _runtimeState.get(sessionId);
1177
- const session = runtimeEntry?.session ?? loadSession(sessionId);
1178
- if (!session || session.closed) return;
1179
- if (!isReplay) {
1180
- session.totalInputTokens = (session.totalInputTokens || 0) + (deltaInput || 0);
1181
- session.totalOutputTokens = (session.totalOutputTokens || 0) + (deltaOutput || 0);
1182
- session.tokensCumulative = (session.tokensCumulative || 0) + (deltaInput || 0) + (deltaOutput || 0);
1183
- // Cache totals — additive fields, default 0 on legacy sessions; both
1184
- // are undefined-safe so the schema migrates lazily as new iterations
1185
- // land. Keeps live + terminal aggregates in lock-step (loop.mjs already
1186
- // includes cached_read / cache_write in its terminal usage rollup).
1187
- session.totalCachedReadTokens = (session.totalCachedReadTokens || 0) + (deltaCachedRead || 0);
1188
- session.totalCacheWriteTokens = (session.totalCacheWriteTokens || 0) + (deltaCacheWrite || 0);
1189
- // Window snapshot updated per iteration so bridge type=list reflects the
1190
- // most-recent provider-reported input size even for short dispatches
1191
- // that finish before askSession's terminal save lands.
1192
- session.lastInputTokens = deltaInput || 0;
1193
- session.lastOutputTokens = deltaOutput || 0;
1194
- session.lastCachedReadTokens = deltaCachedRead || 0;
1195
- // Normalized last-call context footprint: how many prompt tokens the
1196
- // model actually saw on the most-recent send, comparable ACROSS
1197
- // providers. Anthropic reports input_tokens EXCLUDING cache (cache_read
1198
- // is a separate field), so the cached portion must be added back to
1199
- // reflect real context size; openai/grok/gemini already fold cached
1200
- // tokens INTO the input count, so input alone is the footprint.
1201
- const _inputExcludesCache = providerInputExcludesCache(session.provider);
1202
- session.lastContextTokens = _inputExcludesCache
1203
- ? (deltaInput || 0) + (deltaCachedRead || 0)
1204
- : (deltaInput || 0);
1205
- }
1206
- session.lastIterationIndex = iterationIndex;
1207
- session.updatedAt = ts || Date.now();
1208
- await saveSessionAsync(session, { expectedGeneration: session.generation });
1209
- }
1210
-
1211
- /** Force-flush session metrics to disk. Used by watchdog terminal-reap (fix B). */
1212
- export async function flushSessionMetrics(sessionId) {
1213
- if (!sessionId) return;
1214
- const session = loadSession(sessionId);
1215
- if (!session) return;
1216
- session.updatedAt = Date.now();
1217
- await saveSessionAsync(session, { expectedGeneration: session.generation });
1218
- }
1219
-
1220
- /** Mark session hidden so listSessions() filters it out (runtime-only). */
1221
- export function hideSessionFromList(sessionId) {
1222
- if (!sessionId) return;
1223
- const entry = _runtimeState.get(sessionId);
1224
- if (entry) entry.listHidden = true;
1225
- }
1226
-
1227
- export function getSessionAbortSignal(sessionId) {
1228
- return _runtimeState.get(sessionId)?.controller?.signal ?? null;
1229
- }
1230
-
1231
- /**
1232
- * Return the most recent "session is making progress" timestamp.
1233
- *
1234
- * Combines three independent progress signals so an idle watchdog can stay
1235
- * alive across both streaming and long tool calls:
1236
- * - lastStreamDeltaAt: provider stream chunk landed
1237
- * - toolStartedAt: a tool call just kicked off (nested tool work may
1238
- * stall the outer stream for a while; this keeps the watchdog from
1239
- * killing legitimate sub-agent runs)
1240
- * - askStartedAt: ask just started; covers the pre-stream connect window
1241
- *
1242
- * Returns 0 when the runtime entry is unknown so callers can decide to
1243
- * either skip the watchdog or treat 0 as "no progress yet".
1244
- */
1245
- export function getSessionLastProgressAt(sessionId) {
1246
- const entry = _runtimeState.get(sessionId);
1247
- if (!entry) return 0;
1248
- return Math.max(
1249
- entry.lastStreamDeltaAt || 0,
1250
- entry.toolStartedAt || 0,
1251
- entry.askStartedAt || 0,
1252
- );
1253
- }
1254
-
1255
- /**
1256
- * Link a parent AbortSignal to a sub-session's controller so that aborting
1257
- * the parent (fan-out deadline or caller ESC) tears down the bridge role's
1258
- * provider call promptly. Safe to call after prepareBridgeSession but before
1259
- * askSession completes. No-op if the session runtime isn't found.
1260
- *
1261
- * @param {string} sessionId — the sub-session to abort
1262
- * @param {AbortSignal} parentSignal — upstream signal (from fan-out coordinator)
1263
- */
1264
- export function linkParentSignalToSession(sessionId, parentSignal) {
1265
- if (!(parentSignal instanceof AbortSignal)) return;
1266
- const entry = _touchRuntime(sessionId);
1267
- if (!entry.controller) entry.controller = createAbortController();
1268
- if (parentSignal.aborted) {
1269
- try { entry.controller.abort(new Error('parent signal aborted')); } catch { /* ignore */ }
1270
- return;
1271
- }
1272
- parentSignal.addEventListener('abort', () => {
1273
- try { entry.controller?.abort(new Error('parent signal aborted')); } catch { /* ignore */ }
1274
- }, { once: true });
1275
- }
1276
- function _clearSessionRuntime(id) {
1277
- if (id) {
1278
- _runtimeState.delete(id);
1279
- // R15: also drop the per-session metric-idempotency Set; otherwise it
1280
- // grows O(sessions x iterations) for the whole server lifetime since
1281
- // nothing else deletes from _metricSeenIter on session close.
1282
- _metricSeenIter.delete(id);
1283
- }
1284
- }
1285
-
1286
- /**
1287
- * Wrap an async call so that if the session's controller aborts mid-flight,
1288
- * the wrapper settles with a SessionClosedError even if the underlying promise
1289
- * hasn't returned yet. The original promise is kept alive with a detached
1290
- * `.catch()` to prevent unhandled-rejection warnings once it eventually
1291
- * settles. Callers still must check generation/closed after await returns
1292
- * to handle providers that ignore the AbortSignal entirely.
1293
- */
1294
- export async function _api_call_with_interrupt(sessionId, fn) {
1295
- const entry = _touchRuntime(sessionId);
1296
- if (!entry.controller) entry.controller = createAbortController();
1297
- const signal = entry.controller.signal;
1298
- if (signal.aborted) throw new SessionClosedError(sessionId, 'aborted before call');
1299
- const underlying = fn(signal);
1300
- underlying.catch(() => {}); // prevent unhandled rejection if we race ahead
1301
- let onAbort = null;
1302
- const aborted = new Promise((_, reject) => {
1303
- onAbort = () => reject(new SessionClosedError(sessionId, 'aborted during call'));
1304
- if (signal.aborted) onAbort();
1305
- else signal.addEventListener('abort', onAbort, { once: true });
1306
- });
1307
- try {
1308
- return await Promise.race([underlying, aborted]);
1309
- } finally {
1310
- // If the underlying promise settled first, the abort listener is
1311
- // still attached. Remove it to avoid accumulating listeners across
1312
- // many asks on the same session.
1313
- if (onAbort && !signal.aborted) {
1314
- try { signal.removeEventListener('abort', onAbort); } catch { /* ignore */ }
1315
- }
1316
- }
1317
- }
1318
-
1319
- // Per-session mutex: queues concurrent askSession calls to prevent message loss
1320
- const _sessionLocks = new Map();
1321
- // Per-session pending-message queue (Claude Code `pendingMessages` pattern).
1322
- // A `bridge type=send` to a worker whose turn is still in flight ENQUEUES the
1323
- // message here instead of rejecting; askSession drains the queue after each
1324
- // turn and runs the messages as the next user turn(s), preserving order — the
1325
- // queued send runs AFTER the in-flight prompt, which also closes the spawn
1326
- // startup race (a send landing before the initial turn settles no longer
1327
- // jumps ahead of the original prompt). Map<sessionId, string[]>. Shared with
1328
- // index.mjs's bridge send handler via the enqueue/drain accessors below — one
1329
- // queue contract, two call sites.
1330
- const _sessionPendingMessages = new Map();
1331
- export function enqueuePendingMessage(sessionId, message) {
1332
- if (!sessionId || typeof message !== 'string' || !message) return 0;
1333
- let q = _sessionPendingMessages.get(sessionId);
1334
- if (!q) { q = []; _sessionPendingMessages.set(sessionId, q); }
1335
- q.push(message);
1336
- return q.length;
1337
- }
1338
- export function drainPendingMessages(sessionId) {
1339
- const q = _sessionPendingMessages.get(sessionId);
1340
- if (!q || q.length === 0) return [];
1341
- _sessionPendingMessages.delete(sessionId);
1342
- return q;
1343
- }
1344
- function acquireSessionLock(sessionId) {
1345
- let entry = _sessionLocks.get(sessionId);
1346
- if (!entry) {
1347
- entry = { promise: Promise.resolve(), count: 0 };
1348
- _sessionLocks.set(sessionId, entry);
1349
- }
1350
- entry.count++;
1351
- const prev = entry.promise;
1352
- let release;
1353
- entry.promise = new Promise(r => { release = r; });
1354
- // Self-heal: if the previous holder rejected, swallow so subsequent
1355
- // queued waiters don't propagate that rejection and brick the lock chain.
1356
- return prev.catch(() => {}).then(() => () => {
1357
- entry.count--;
1358
- if (entry.count === 0) _sessionLocks.delete(sessionId);
1359
- release();
1360
- });
1361
- }
1362
-
1363
- export async function askSession(sessionId, prompt, context, onToolCall, cwdOverride, explicitPrefetch) {
1364
- const _askStartedAt = Date.now();
1365
- const _promptSrc = 'prompt';
1366
- const _prefetchFiles = (explicitPrefetch?.files?.length) || 0;
1367
- const _prefetchCallers = (explicitPrefetch?.callers?.length) || 0;
1368
- const _prefetchRefs = (explicitPrefetch?.references?.length) || 0;
1369
- if (process.env.MIXDOG_DEBUG_BRIDGE) {
1370
- process.stderr.write(`[bridge-trace] t0-ask-start sessionHash=${createHash('sha256').update(String(sessionId)).digest('hex').slice(0, 8)} role=? iteration=0 promptSrc=${_promptSrc} prefetchFiles=${_prefetchFiles} callers=${_prefetchCallers} references=${_prefetchRefs}\n`);
1371
- }
1372
- const unlock = await acquireSessionLock(sessionId);
1373
- const _lockWaitedMs = Date.now() - _askStartedAt;
1374
- if (process.env.MIXDOG_DEBUG_BRIDGE) {
1375
- process.stderr.write(`[bridge-trace] lock-acquired waitedMs=${_lockWaitedMs}\n`);
1376
- }
1377
- // The mutex is held for the WHOLE askSession call, including any follow-up
1378
- // turns drained from the pending-message queue below — the single outer
1379
- // try/finally releases it exactly once. _result holds the last turn's
1380
- // return value (the queued tail turns supersede the original prompt's
1381
- // result, mirroring how a live chat returns the latest turn).
1382
- let _result;
1383
- // Local FIFO of follow-up prompts drained from the pending-message queue
1384
- // after each turn — keeps queued `bridge type=send` messages in order.
1385
- const _pendingTail = [];
1386
- // Hoisted so the outer finally (which runs once after the whole turn loop)
1387
- // can compare against the last turn's generation.
1388
- let askGeneration = 0;
1389
- try {
1390
- // Turn loop (pendingMessages pattern): run the current prompt, then drain
1391
- // any `bridge type=send` messages that were queued while this turn was in
1392
- // flight and run them — in order — as the next user turn(s). Because the
1393
- // queued send always lands AFTER the in-flight prompt here, ordering is
1394
- // preserved and the spawn/connecting startup race disappears.
1395
- for (;;) {
1396
- // After the first turn, the next prompt comes from the drained queue.
1397
- // (On the first iteration _pendingTail is empty and `prompt` is the
1398
- // caller's original message.)
1399
- if (_pendingTail.length > 0) {
1400
- prompt = _pendingTail.shift();
1401
- // Queued follow-ups are plain user turns — no caller context /
1402
- // prefetch is re-applied (those belonged to the original ask).
1403
- context = null;
1404
- explicitPrefetch = null;
1405
- }
1406
- // ── Synchronous pre-await setup (must happen before any await so
1407
- // closeSession() can't interleave between load and registration) ──
1408
- const preSession = loadSession(sessionId);
1409
- if (!preSession) {
1410
- throw new Error(`Session "${sessionId}" not found`);
1411
- }
1412
- if (preSession.closed === true) {
1413
- throw new SessionClosedError(sessionId, 'session already closed');
1414
- }
1415
- askGeneration = typeof preSession.generation === 'number' ? preSession.generation : 0;
1416
- const runtime = _touchRuntime(sessionId);
1417
- // Fresh controller per ask — the previous ask's controller may have aborted.
1418
- runtime.controller = createAbortController();
1419
- runtime.generation = askGeneration;
1420
- runtime.closed = false;
1421
- markSessionAskStart(sessionId);
1422
- // Preprocessing is inside try so provider-not-available / trim failures
1423
- // fall into the catch and mark the session as errored rather than
1424
- // leaving stage='connecting' forever.
1425
- try {
1426
- const session = preSession;
1427
- const provider = getProvider(session.provider);
1428
- // Register the live session object into runtime so closeSession()
1429
- // can read allBashSessionIds that loop.mjs appends mid-turn.
1430
- runtime.session = session;
1431
- if (!provider)
1432
- throw new Error(`Provider "${session.provider}" not available`);
1433
- session.contextWindow = resolveSessionContextWindow(provider, session.model);
1434
- // Cap caller-supplied / prefetched context so an oversized
1435
- // payload can't blow the session token budget before the
1436
- // first model call. 32 KB ~ 8k tokens at the 4 B/tok
1437
- // working average; longer is silently truncated with a
1438
- // visible marker so the model still sees the prefix and
1439
- // a hint about the cut.
1440
- const _CTX_CHAR_CAP = 32 * 1024;
1441
- const _capCtx = (text) => {
1442
- if (typeof text !== 'string') return '';
1443
- if (text.length <= _CTX_CHAR_CAP) return text;
1444
- return `${text.slice(0, _CTX_CHAR_CAP)}\n\n... [context truncated; original ${text.length} chars]`;
1445
- };
1446
- // Inline context + prefetch INTO the prompt as a single user turn,
1447
- // marked with explicit section headers. The previous design pushed
1448
- // context as separate user messages with pre-injected assistant
1449
- // "Noted." acks; that conversational pattern taught some models a
1450
- // low-effort rhythm and they responded with "Noted." / empty tags
1451
- // even to the real task. Single-turn structure with a labelled
1452
- // `# Task` block forces the model to treat the brief as the work
1453
- // unit, not as another piece of context to ack.
1454
- const explicitPrefetchResult = await _tryBridgeExplicitPrefetch(session, explicitPrefetch);
1455
- let _contextBlock = '';
1456
- if (context) {
1457
- _contextBlock += `# Additional context\n${_capCtx(context)}\n\n`;
1458
- }
1459
- if (explicitPrefetchResult) {
1460
- _contextBlock += `# Prefetch\n${_capCtx(explicitPrefetchResult)}\n\n`;
1461
- }
1462
- const beforeCount = session.messages.length + 1;
1463
- // Soft warning only; real size management (compaction primary,
1464
- // byte-budget trim as safety net) lives in agentLoop. Selecting a
1465
- // 25% pre-trim here would starve compaction's 50% threshold.
1466
- const softBudget = Math.floor(session.contextWindow * 0.25);
1467
- const promptTokenEstimate = prompt.length * 0.5; // conservative for CJK
1468
- if (promptTokenEstimate > softBudget * 0.7) {
1469
- process.stderr.write(`[session] Warning: prompt is very large (est. ${Math.round(promptTokenEstimate)} tokens vs ${softBudget} soft budget)\n`);
1470
- }
1471
- const effectiveCwd = cwdOverride || session.cwd;
1472
- const _userTurnContent = _contextBlock
1473
- ? `${_contextBlock}# Task\n${prompt}`
1474
- : prompt;
1475
- const outgoing = [...session.messages, { role: 'user', content: _userTurnContent }];
1476
- // Per-turn injected-context trace row (complements kind:"usage").
1477
- // Cheap byte-length accounting — no hashing, no payload bodies.
1478
- // Honors the same MIXDOG_BRIDGE_TRACE_DISABLE gate as usage rows;
1479
- // appendBridgeTrace is a no-op when that env is set.
1480
- try {
1481
- const _ctxBytes = Buffer.byteLength(context || '', 'utf8');
1482
- const _prefetchBytes = Buffer.byteLength(explicitPrefetchResult || '', 'utf8');
1483
- const _promptBytes = Buffer.byteLength(prompt || '', 'utf8');
1484
- const _userTurnBytes = Buffer.byteLength(_userTurnContent, 'utf8');
1485
- const _messagesBytes = Buffer.byteLength(JSON.stringify(session.messages || []), 'utf8');
1486
- const _totalBytes = _userTurnBytes + _messagesBytes;
1487
- appendBridgeTrace({
1488
- kind: 'context',
1489
- sessionId,
1490
- model: session.model,
1491
- provider: session.provider,
1492
- totalBytes: _totalBytes,
1493
- breakdown: {
1494
- contextBytes: _ctxBytes,
1495
- prefetchBytes: _prefetchBytes,
1496
- promptBytes: _promptBytes,
1497
- userTurnBytes: _userTurnBytes,
1498
- messagesBytes: _messagesBytes,
1499
- messagesCount: Array.isArray(session.messages) ? session.messages.length : 0,
1500
- },
1501
- });
1502
- } catch { /* trace must never break the ask path */ }
1503
- const result = await _api_call_with_interrupt(sessionId, (signal) =>
1504
- agentLoop(provider, outgoing, session.model, session.tools, onToolCall, effectiveCwd, {
1505
- effort: session.effort || null,
1506
- fast: session.fast === true,
1507
- sessionId,
1508
- onUsageDelta: (d) => persistIterationMetrics(d).catch(() => {}),
1509
- promptCacheKey: session.promptCacheKey || sessionId,
1510
- // Provider-scoped cache key (mixdog-codex, mixdog-claude…).
1511
- // Distinct from sessionId — providers that pool sockets
1512
- // per-session (openai-oauth WS) use sessionId as the
1513
- // pool bucket and providerCacheKey as the server-side
1514
- // prompt-cache shard so parallel callers don't collide
1515
- // on a mid-turn socket while still sharing prefix cache.
1516
- providerCacheKey: session.promptCacheKey || null,
1517
- signal,
1518
- providerState: session.providerState ?? undefined,
1519
- session,
1520
- // Smart Bridge cache settings — merged last so session overrides
1521
- // don't get overridden by defaults. When session has no profile,
1522
- // providerCacheOpts is null and this spread is a no-op.
1523
- ...(session.providerCacheOpts || {}),
1524
- onStageChange: (stage) => updateSessionStage(sessionId, stage),
1525
- onStreamDelta: () => markSessionStreamDelta(sessionId).catch(() => {}),
1526
- }),
1527
- );
1528
- // Post-loop validation: if closeSession() landed while we were awaiting,
1529
- // drop the save so the tombstone on disk isn't overwritten.
1530
- const currentRuntime = _runtimeState.get(sessionId);
1531
- if (currentRuntime?.closed || currentRuntime?.generation !== askGeneration) {
1532
- const reason = currentRuntime?.closedReason;
1533
- throw new SessionClosedError(sessionId, `closed during call (reason=${reason || 'unknown'})`, reason || null);
1534
- }
1535
- // Update and save. outgoing is mutated in place by agentLoop
1536
- // (compaction + safety trim), so its length reflects post-loop state.
1537
- const messagesDropped = Math.max(0, beforeCount - outgoing.length);
1538
- session.messages = outgoing;
1539
- if (result.content || result.reasoningContent) {
1540
- session.messages.push({
1541
- role: 'assistant',
1542
- content: result.content || '',
1543
- ...(typeof result.reasoningContent === 'string' && result.reasoningContent
1544
- ? { reasoningContent: result.reasoningContent }
1545
- : {}),
1546
- });
1547
- } else {
1548
- // Empty terminal turn: still persist a forensic record so
1549
- // post-mortem inspection can distinguish "work landed but
1550
- // synthesis missing" from "session never ran". Stop reason,
1551
- // usage, iterations, and tool-call totals survive even when
1552
- // the assistant produced no content/reasoning.
1553
- const _emptyStop = result?.stopReason ?? result?.stop_reason ?? null;
1554
- const _emptyUsage = result?.usage ? {
1555
- inputTokens: result.usage.inputTokens || 0,
1556
- outputTokens: result.usage.outputTokens || 0,
1557
- cachedTokens: result.usage.cachedTokens || 0,
1558
- cacheWriteTokens: result.usage.cacheWriteTokens || 0,
1559
- } : null;
1560
- // Provider content-block classification — distinguishes a
1561
- // thinking-only stall (model emitted reasoning blocks but no
1562
- // text/tool_use) from a true silent empty turn. Anthropic
1563
- // providers (anthropic.mjs, anthropic-oauth.mjs) set these
1564
- // fields on the result; other providers may omit them.
1565
- const _emptyHasThinking = typeof result?.hasThinkingContent === 'boolean'
1566
- ? result.hasThinkingContent
1567
- : null;
1568
- const _emptyBlockTypes = Array.isArray(result?.contentBlockTypes)
1569
- ? result.contentBlockTypes.slice()
1570
- : null;
1571
- session.messages.push({
1572
- role: 'assistant',
1573
- content: '',
1574
- emptyFinal: true,
1575
- stopReason: _emptyStop,
1576
- iterations: result?.iterations ?? null,
1577
- toolCallsTotal: result?.toolCallsTotal ?? null,
1578
- usage: _emptyUsage,
1579
- ...(_emptyHasThinking !== null ? { hasThinkingContent: _emptyHasThinking } : {}),
1580
- ...(_emptyBlockTypes !== null ? { contentBlockTypes: _emptyBlockTypes } : {}),
1581
- ts: Date.now(),
1582
- });
1583
- try {
1584
- const _blockTypesStr = _emptyBlockTypes ? _emptyBlockTypes.join(',') || 'none' : 'unknown';
1585
- const _thinkingStr = _emptyHasThinking === null ? 'unknown' : String(_emptyHasThinking);
1586
- process.stderr.write(`[session] empty-final persisted sessionId=${sessionId} stopReason=${_emptyStop ?? 'unknown'} iterations=${result?.iterations ?? 0} toolCallsTotal=${result?.toolCallsTotal ?? 0} outTokens=${_emptyUsage?.outputTokens ?? 0} hasThinking=${_thinkingStr} blockTypes=${_blockTypesStr}\n`);
1587
- } catch {}
1588
- }
1589
- session.updatedAt = Date.now();
1590
- session.lastUsedAt = Date.now();
1591
- if (result.usage) {
1592
- session.totalInputTokens += result.usage.inputTokens;
1593
- session.totalOutputTokens += result.usage.outputTokens;
1594
- session.tokensCumulative = (session.tokensCumulative || 0)
1595
- + (result.usage.inputTokens || 0)
1596
- + (result.usage.outputTokens || 0);
1597
- // Cache totals — same `||0` undefined-safe accumulation pattern as
1598
- // persistIterationMetrics so live + terminal paths stay in lock-step
1599
- // and legacy sessions migrate lazily on first iteration.
1600
- session.totalCachedReadTokens = (session.totalCachedReadTokens || 0) + (result.usage.cachedTokens || 0);
1601
- session.totalCacheWriteTokens = (session.totalCacheWriteTokens || 0) + (result.usage.cacheWriteTokens || 0);
1602
- // Window snapshot = the current context size, which is the LAST
1603
- // single call — NOT result.usage (that is lastUsage, the per-turn
1604
- // SUM accumulated with += across iterations in agentLoop). Use
1605
- // lastTurnUsage (the final iteration's raw usage) so this reflects
1606
- // "what's in the window now" rather than the lifetime sum.
1607
- const _lastTurn = result.lastTurnUsage || result.usage || {};
1608
- session.lastInputTokens = _lastTurn.inputTokens || 0;
1609
- session.lastOutputTokens = _lastTurn.outputTokens || 0;
1610
- session.lastCachedReadTokens = _lastTurn.cachedTokens || 0;
1611
- session.lastCacheWriteTokens = _lastTurn.cacheWriteTokens || 0;
1612
- // Provider-normalized footprint, identical formula to
1613
- // persistIterationMetrics so both writers agree: Anthropic
1614
- // input_tokens excludes cache (add it back), openai/grok/gemini
1615
- // already include it.
1616
- const _inputExcludesCache = providerInputExcludesCache(session.provider);
1617
- session.lastContextTokens = _inputExcludesCache
1618
- ? (_lastTurn.inputTokens || 0) + (_lastTurn.cachedTokens || 0)
1619
- : (_lastTurn.inputTokens || 0);
1620
- }
1621
- // Smart Bridge cache stats — record hit/miss after every successful
1622
- // ask so the registry reflects all bridge traffic, not just
1623
- // maintenance cycles. Guarded against any smart-bridge error so
1624
- // metric recording never breaks the ask itself.
1625
- let prefixHashForLog = null;
1626
- if (session.profileId && result.usage && _smartBridgeApi) {
1627
- try {
1628
- const profile = _smartBridgeApi.getProfile(session.profileId);
1629
- if (profile) {
1630
- // Collect every leading system-role message (BP1, BP2, ...)
1631
- // until the first non-system message so the registry hash
1632
- // captures the full ordered provider prefix, not just BP1.
1633
- const systemMsgs = [];
1634
- for (const m of session.messages) {
1635
- if (m?.role !== 'system') break;
1636
- systemMsgs.push(typeof m.content === 'string' ? m.content : '');
1637
- }
1638
- _smartBridgeApi.recordCall(profile, session.provider, {
1639
- systemPrompt: systemMsgs,
1640
- tools: session.tools || [],
1641
- usage: result.usage,
1642
- });
1643
- const entry = _smartBridgeApi.registry?.data?.profiles?.[session.profileId]?.[session.provider];
1644
- prefixHashForLog = entry?.prefixHash || null;
1645
- }
1646
- } catch {}
1647
- }
1648
- // Append to bridge-trace.jsonl with the rich bridge usage fields.
1649
- if (result.usage) {
1650
- const inputTokens = result.usage.inputTokens || 0;
1651
- const outputTokens = result.usage.outputTokens || 0;
1652
- const cacheReadTokens = result.usage.cachedTokens || 0;
1653
- const cacheWriteTokens = result.usage.cacheWriteTokens || 0;
1654
- // Unified total-prompt field. Anthropic = input+cache_read+cache_write
1655
- // (additive); OpenAI/Codex/Gemini = input_tokens already includes the
1656
- // cached portion (inclusive), so the fallback must not double-count.
1657
- const { isInclusiveProvider, computeCostUsd } = await import('../../../shared/llm/cost.mjs');
1658
- const inclusive = isInclusiveProvider(session.provider);
1659
- const promptTokens = typeof result.usage.promptTokens === 'number'
1660
- ? result.usage.promptTokens
1661
- : (inclusive
1662
- ? Math.max(inputTokens, cacheReadTokens + cacheWriteTokens)
1663
- : inputTokens + cacheReadTokens + cacheWriteTokens);
1664
- let costUsd = result.usage.costUsd || 0;
1665
- if (!costUsd) {
1666
- try {
1667
- costUsd = computeCostUsd({
1668
- model: session.model,
1669
- provider: session.provider,
1670
- inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens,
1671
- });
1672
- } catch { /* best-effort */ }
1673
- }
1674
- logLlmCall({
1675
- ts: new Date().toISOString(),
1676
- sourceType: session.sourceType || 'lead',
1677
- sourceName: session.sourceName || session.role || null,
1678
- preset: session.presetName || null,
1679
- model: session.model,
1680
- provider: session.provider,
1681
- duration: Date.now() - _askStartedAt,
1682
- profileId: session.profileId || null,
1683
- sessionId: session.id,
1684
- inputTokens,
1685
- outputTokens,
1686
- cacheReadTokens,
1687
- cacheWriteTokens,
1688
- promptTokens,
1689
- prefixHash: prefixHashForLog,
1690
- costUsd,
1691
- });
1692
- }
1693
- // Persist opaque providerState for future stateful providers.
1694
- // No provider currently emits it (Codex OAuth is stateless per
1695
- // contract), so this branch is dormant — kept so a future
1696
- // Responses-API provider with stable continuation can plug in
1697
- // without reworking the session shape.
1698
- if (result.providerState !== undefined) {
1699
- session.providerState = result.providerState;
1700
- }
1701
- await saveSessionAsync(session, { expectedGeneration: askGeneration });
1702
- // Tag empty-synthesis BEFORE markSessionDone so the watchdog
1703
- // (which inspects entry.emptyFinal first) classifies the
1704
- // terminal state correctly even if it ticks during unwind.
1705
- const isEmptyFinal = !result.content && !result.reasoningContent;
1706
- if (isEmptyFinal) {
1707
- markSessionEmptyFinal(sessionId);
1708
- }
1709
- markSessionDone(sessionId, { empty: isEmptyFinal });
1710
- _result = {
1711
- ...result,
1712
- trimmed: messagesDropped > 0,
1713
- messagesDropped,
1714
- };
1715
- } catch (err) {
1716
- if (err instanceof SessionClosedError) {
1717
- // Cancellation is not an error; propagate silently so callers
1718
- // can render it as "cancelled" rather than a red failure.
1719
- throw err;
1720
- }
1721
- markSessionError(sessionId, err && err.message ? err.message : String(err));
1722
- throw err;
1723
- }
1724
- // ── Turn complete. Drain the pending-message queue (Claude Code
1725
- // pendingMessages): any `bridge type=send` that arrived while this
1726
- // turn was in flight runs next, in order, as a follow-up user turn.
1727
- // The mutex is still held, so a send racing this drain either landed
1728
- // before (picked up here) or enqueues for the next loop. When the
1729
- // queue is empty we return the latest turn's result. ──
1730
- const _drained = drainPendingMessages(sessionId);
1731
- if (_drained.length > 0) {
1732
- _pendingTail.push(..._drained);
1733
- continue;
1734
- }
1735
- return _result;
1736
- }
1737
- } finally {
1738
- // Clear the controller only if it's still ours (closeSession may have
1739
- // swapped it). Leave the rest of the runtime entry intact so bridge type=list
1740
- // can still surface the final stage (done/error/cancelling).
1741
- const entry = _runtimeState.get(sessionId);
1742
- if (entry && entry.generation === askGeneration) {
1743
- entry.controller = null;
1744
- // Detach the live session reference; ask is over.
1745
- entry.session = null;
1746
- }
1747
- unlock();
1748
- }
1749
- }
1750
- // Session lookup by scopeKey — used by CLI bridge to resume a pinned
1751
- // scope session when the caller passes --scope (agent/<name>).
1752
- export function findSessionByScopeKey(scopeKey) {
1753
- if (!scopeKey) return null;
1754
- const sessions = listStoredSessions();
1755
- // Exclude tombstoned sessions (`closed === true`) so callers never receive
1756
- // a session whose controller was aborted by closeSession(). The `closed`
1757
- // bit is the authoritative tombstone flag; `status === 'error'` is not,
1758
- // since transient-error sessions remain resumable.
1759
- return sessions.find(s => s.scopeKey === scopeKey && s.closed !== true) || null;
1760
- }
1761
- // --- resume (reload tools for a stored session) ---
1762
- export async function resumeSession(sessionId, preset) {
1763
- const session = loadSession(sessionId);
1764
- if (!session)
1765
- return null;
1766
- // Resuming a closed session is a resurrection attempt — refuse. The guarded
1767
- // save below would also block the write, but failing fast here is cleaner
1768
- // than silently dropping the tool-refresh side effects.
1769
- if (session.closed === true) return null;
1770
- if (!session.owner) session.owner = 'user';
1771
- // Refresh tools (MCP connections may have changed).
1772
- // Re-resolve from profile.tools when the session stored a profileId —
1773
- // otherwise fall back to preset.tools. Same resolution order as
1774
- // createSession so resume and spawn produce identical BP_1 shapes.
1775
- const oldTools = session.tools || [];
1776
- const skills = collectSkillsCached(session.cwd);
1777
- let toolSpec = preset || session.preset || 'full';
1778
- if (session.profileId && _smartBridgeApi?.getProfile) {
1779
- try {
1780
- const profile = _smartBridgeApi.getProfile(session.profileId);
1781
- if (Array.isArray(profile?.tools)) toolSpec = profile.tools;
1782
- } catch { /* ignore lookup failures, keep preset fallback */ }
1783
- }
1784
- session.tools = resolveSessionTools(toolSpec, skills, { ownerIsBridge: session.owner === 'bridge' });
1785
- const newTools = session.tools;
1786
- const missing = oldTools.filter(t => !newTools.find(n => n.name === t.name));
1787
- if (missing.length) {
1788
- process.stderr.write(`[session] Warning: ${missing.length} tools no longer available: ${missing.map(t => t.name).join(', ')}\n`);
1789
- }
1790
- await saveSessionAsync(session, { expectedGeneration: session.generation });
1791
- return session;
1792
- }
1793
- // --- CRUD ---
1794
- export function getSession(id) {
1795
- return loadSession(id);
1796
- }
1797
- export function listSessions(opts = {}) {
1798
- const includeClosed = opts.includeClosed === true;
1799
- const sessions = listStoredSessions();
1800
- const hiddenIds = new Set([..._runtimeState.entries()].filter(([, e]) => e.listHidden).map(([id]) => id));
1801
- // Tombstoned sessions (closed===true) are excluded unless the caller opts in
1802
- // (e.g. bridge list includeClosed:true).
1803
- return sessions.filter(s => !hiddenIds.has(s.id) && (includeClosed || s.closed !== true));
1804
- }
1805
- // --- Clear messages (keep system prompt + provider/model/cwd) ---
1806
- export async function clearSessionMessages(sessionId) {
1807
- const session = loadSession(sessionId);
1808
- if (!session)
1809
- return false;
1810
- // Don't resurrect a closed session just to clear its messages.
1811
- if (session.closed === true) return false;
1812
- session.messages = (session.messages || []).filter(m => m && m.role === 'system');
1813
- session.totalInputTokens = 0;
1814
- session.totalOutputTokens = 0;
1815
- session.updatedAt = Date.now();
1816
- await saveSessionAsync(session, { expectedGeneration: session.generation });
1817
- return true;
1818
- }
1819
- export async function updateSessionStatus(id, status) {
1820
- const session = loadSession(id);
1821
- if (!session) return false;
1822
- // Respect tombstones — don't resurrect a closed session just to update a
1823
- // status label (bridge handler emits running→idle/error around askSession).
1824
- if (session.closed === true) return false;
1825
- session.status = status;
1826
- session.updatedAt = Date.now();
1827
- await saveSessionAsync(session, { expectedGeneration: session.generation });
1828
- return true;
1829
- }
1830
- /**
1831
- * Close a session. Plants a `closed=true` tombstone on disk with a bumped
1832
- * generation (so any racing saveSession() drops its write), aborts the
1833
- * in-flight controller if one exists, and clears the in-memory runtime entry.
1834
- *
1835
- * IMPORTANT: we deliberately do NOT unlink the session file here. The tombstone
1836
- * on disk is the authoritative signal that blocks resurrection — a late
1837
- * saveSession() re-reads disk via _shouldDrop() and will find the tombstone.
1838
- * If we delete the file, a late save sees no file, decides nothing to drop,
1839
- * and recreates the session in its pre-close state.
1840
- *
1841
- * Long-term cleanup: `sweepTombstones()` below unlinks tombstones older than
1842
- * TOMBSTONE_MAX_AGE_MS (24h — vastly longer than any realistic in-flight race).
1843
- */
1844
- export function closeSession(id, reason = 'manual') {
1845
- if (!id) return false;
1846
- // Prefer in-memory runtime session — allBashSessionIds may not be persisted
1847
- // yet for shells opened in the current turn (BL-bash-disk-sync).
1848
- const inMemory = _runtimeState.get(id)?.session;
1849
- const persisted = inMemory || loadSession(id);
1850
- const bashSessionId = persisted?.implicitBashSessionId || null;
1851
- // Collect all persistent bash shells created during this session.
1852
- const allBashIds = Array.isArray(persisted?.allBashSessionIds)
1853
- ? persisted.allBashSessionIds.filter(Boolean)
1854
- : (bashSessionId ? [bashSessionId] : []);
1855
- // Deduplicate: allBashIds already covers implicitBashSessionId, but guard
1856
- // against old session records that only have implicitBashSessionId.
1857
- if (bashSessionId && !allBashIds.includes(bashSessionId)) allBashIds.push(bashSessionId);
1858
- // 1. Tombstone first — this wins the race against saveSession().
1859
- const newGen = markSessionClosed(id, reason);
1860
- // 2. Mark runtime as closed so post-await validation in askSession fires.
1861
- const entry = _runtimeState.get(id);
1862
- if (entry) {
1863
- entry.closed = true;
1864
- entry.closedReason = reason;
1865
- if (typeof newGen === 'number') entry.generation = newGen;
1866
- entry.stage = 'cancelling';
1867
- entry.updatedAt = Date.now();
1868
- // 3. Abort the in-flight controller. Providers that honour the signal
1869
- // unwind immediately; providers that don't will still be caught by
1870
- // the generation check after their await eventually returns.
1871
- try { entry.controller?.abort(new SessionClosedError(id, `closeSession (reason=${reason})`, reason)); } catch { /* ignore */ }
1872
- }
1873
- // Diagnostic: one-line stderr so operators can distinguish the four close
1874
- // pathways (request-abort / manual / idle-sweep / runner-crash). iterCount
1875
- // is not currently tracked on runtime state; askStartedAt is — derive
1876
- // duration from it when present.
1877
- try {
1878
- const askStartedAt = entry?.askStartedAt;
1879
- const durationMs = (typeof askStartedAt === 'number') ? (Date.now() - askStartedAt) : null;
1880
- const parts = [`session=${id}`, `reason=${reason}`];
1881
- if (durationMs != null) parts.push(`duration=${durationMs}ms`);
1882
- process.stderr.write(`[bridge-close] ${parts.join(' ')}\n`);
1883
- } catch { /* best-effort */ }
1884
- for (const bsid of allBashIds) {
1885
- try { closeBashSession(bsid, `bridge-close:${id}`); } catch { /* ignore */ }
1886
- }
1887
- // Drop session-scoped read dedup cache so the Map doesn't accumulate
1888
- // entries across mcp-server lifetime.
1889
- try { clearReadDedupSession(id); } catch { /* ignore */ }
1890
- // Drop offload sidecars + module-level counter for this session so a
1891
- // long-running mcp-server doesn't leak disk (tool-results/<id>/*.txt)
1892
- // or Map entries across session lifetime. Fire-and-forget — close path
1893
- // should not await disk IO; errors are swallowed inside.
1894
- try { clearOffloadSession(id); } catch { /* ignore */ }
1895
- // 4. Defer runtime map clear to next tick so any settling askSession can
1896
- // observe `closed=true` / bumped generation before we yank the entry.
1897
- // Disk tombstone remains — that's what blocks resurrection.
1898
- setImmediate(() => {
1899
- _clearSessionRuntime(id);
1900
- });
1901
- return true;
1902
- }
1903
-
1904
- // --- Periodic idle session cleanup ---
1905
- const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // check every 5 minutes
1906
- const TOMBSTONE_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24h — far longer than any realistic ask race window
1907
- let _cleanupTimer = null;
1908
-
1909
- function sweepIdleSessions() {
1910
- try {
1911
- const { cleaned, remaining, details } = sweepStaleSessions();
1912
- if (cleaned > 0) {
1913
- for (const d of details) {
1914
- // Skip entries with an active in-flight controller — aborting
1915
- // them via closeSession() is the safe path; clearing the runtime
1916
- // without signalling the controller leaves orphan provider work.
1917
- const rtEntry = _runtimeState.get(d.id);
1918
- if (rtEntry && rtEntry.controller && !rtEntry.controller.signal?.aborted) {
1919
- try { closeSession(d.id, 'idle-sweep'); } catch { /* ignore */ }
1920
- } else {
1921
- _clearSessionRuntime(d.id);
1922
- if (d.bashSessionId) {
1923
- try { closeBashSession(d.bashSessionId, `idle-sweep:${d.id}`); } catch { /* ignore */ }
1924
- }
1925
- }
1926
- process.stderr.write(`[bridge-session] idle cleanup: closed ${d.id} (idle ${d.idleMinutes}m, owner=${d.owner})\n`);
1927
- }
1928
- process.stderr.write(`[bridge-session] idle sweep: cleaned ${cleaned} session(s), ${remaining} remaining\n`);
1929
- }
1930
- } catch (e) {
1931
- process.stderr.write(`[bridge-session] idle sweep error: ${e && e.message || e}\n`);
1932
- }
1933
- }
1934
-
1935
- /**
1936
- * Unlink tombstone session files (closed=true) older than TOMBSTONE_MAX_AGE_MS.
1937
- *
1938
- * Rationale: closeSession() leaves the tombstone on disk as the authoritative
1939
- * resurrection-blocker for racing saveSession() calls. That race resolves in
1940
- * microseconds (the window inside _doSave between temp write and rename), so
1941
- * 24h is vastly safe. After the TTL expires we reclaim the disk slot.
1942
- *
1943
- * Uses `getStoredSessionsRaw()` rather than `listStoredSessions()` because the
1944
- * latter's inline 30-min idle cleanup would race-unlink tombstones before we
1945
- * get to log them — we want to own the unlink decision and stderr line here.
1946
- */
1947
- export function sweepTombstones() {
1948
- try {
1949
- const now = Date.now();
1950
- const sessions = getStoredSessionsRaw();
1951
- let cleaned = 0;
1952
- for (const s of sessions) {
1953
- if (!s.closed) continue;
1954
- const updated = Number(s.updatedAt);
1955
- if (!Number.isFinite(updated)) continue;
1956
- const age = now - updated;
1957
- if (age < TOMBSTONE_MAX_AGE_MS) continue;
1958
- try {
1959
- deleteSession(s.id);
1960
- _clearSessionRuntime(s.id);
1961
- cleaned++;
1962
- process.stderr.write(`[session-sweep] unlinked tombstone ${s.id} (age=${Math.floor(age / 1000)}s)\n`);
1963
- } catch (e) {
1964
- process.stderr.write(`[session-sweep] unlink failed ${s.id}: ${e && e.message || e}\n`);
1965
- }
1966
- }
1967
- return cleaned;
1968
- } catch (e) {
1969
- process.stderr.write(`[session-sweep] tombstone sweep error: ${e && e.message || e}\n`);
1970
- return 0;
1971
- }
1972
- }
1973
-
1974
- function _runCleanupCycle() {
1975
- sweepIdleSessions();
1976
- sweepTombstones();
1977
- }
1978
-
1979
- export function startIdleCleanup() {
1980
- if (_cleanupTimer) return;
1981
- _runCleanupCycle();
1982
- _cleanupTimer = setInterval(_runCleanupCycle, CLEANUP_INTERVAL_MS);
1983
- if (_cleanupTimer.unref) _cleanupTimer.unref(); // don't block process exit
1984
- }
1985
-
1986
- export function stopIdleCleanup() {
1987
- if (_cleanupTimer) {
1988
- clearInterval(_cleanupTimer);
1989
- _cleanupTimer = null;
1990
- }
1991
- }