mixdog 0.9.2 → 0.9.4

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 (417) hide show
  1. package/package.json +8 -3
  2. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/review-tasks.json +20 -0
  7. package/scripts/bench/round-codex.json +114 -0
  8. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  9. package/scripts/bench/round-mixdog-lead.json +269 -0
  10. package/scripts/bench/round-mixdog.json +126 -0
  11. package/scripts/bench/round-r10-bigsample.json +679 -0
  12. package/scripts/bench/round-r11-codexalign.json +257 -0
  13. package/scripts/bench/round-r4-codex.json +114 -0
  14. package/scripts/bench/round-r4-mixed.json +225 -0
  15. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  16. package/scripts/bench/round-r6-codex.json +114 -0
  17. package/scripts/bench/round-r6-solo.json +257 -0
  18. package/scripts/bench/round-r7-full.json +254 -0
  19. package/scripts/bench/round-r8-fulldefault.json +255 -0
  20. package/scripts/bench-run.mjs +215 -29
  21. package/scripts/build-tui.mjs +13 -1
  22. package/scripts/explore-bench.mjs +124 -0
  23. package/scripts/freevar-smoke.mjs +95 -0
  24. package/scripts/hook-bus-test.mjs +191 -0
  25. package/scripts/internal-comms-bench.mjs +1 -0
  26. package/scripts/internal-comms-smoke.mjs +10 -9
  27. package/scripts/mouse-probe.mjs +45 -0
  28. package/scripts/output-style-bench.mjs +13 -6
  29. package/scripts/output-style-smoke.mjs +4 -4
  30. package/scripts/path-suffix-test.mjs +57 -0
  31. package/scripts/provider-toolcall-test.mjs +7 -3
  32. package/scripts/recall-bench.mjs +207 -0
  33. package/scripts/recall-usecase-cases.json +18 -0
  34. package/scripts/recall-usecase-probe.json +6 -0
  35. package/scripts/session-bench.mjs +152 -6
  36. package/scripts/tool-smoke.mjs +30 -67
  37. package/scripts/tui-render-smoke.mjs +90 -0
  38. package/scripts/webhook-smoke.mjs +208 -0
  39. package/src/agents/debugger/AGENT.md +5 -2
  40. package/src/agents/heavy-worker/AGENT.md +21 -11
  41. package/src/agents/maintainer/AGENT.md +4 -0
  42. package/src/agents/reviewer/AGENT.md +3 -2
  43. package/src/agents/worker/AGENT.md +21 -11
  44. package/src/lib/rules-builder.cjs +4 -0
  45. package/src/mixdog-session-runtime.mjs +933 -3731
  46. package/src/output-styles/default.md +34 -9
  47. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  48. package/src/output-styles/minimal.md +4 -1
  49. package/src/output-styles/simple.md +22 -7
  50. package/src/repl.mjs +5 -5
  51. package/src/rules/agent/00-common.md +2 -0
  52. package/src/rules/agent/30-explorer.md +8 -11
  53. package/src/rules/lead/lead-brief.md +12 -0
  54. package/src/rules/lead/lead-tool.md +2 -9
  55. package/src/rules/shared/01-tool.md +11 -5
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  59. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  60. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  61. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  62. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  63. package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
  66. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
  70. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  71. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
  72. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
  73. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  74. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  75. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  76. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  77. package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
  78. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  80. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  81. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  82. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  83. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  84. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
  85. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
  92. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  94. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  96. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
  99. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
  100. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  101. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  102. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  103. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  107. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  108. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  109. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
  110. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  111. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  112. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  113. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  114. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
  115. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  116. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  117. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  118. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  120. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  121. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  122. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  123. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  124. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  125. package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
  126. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  127. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
  128. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  129. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
  130. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  131. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  132. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  133. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
  136. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  137. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  138. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  141. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
  142. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  143. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  145. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  146. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  148. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  149. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
  152. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
  155. package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
  156. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  157. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  158. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  159. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  161. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  163. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  164. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  165. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  166. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
  167. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  168. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  169. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  170. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  171. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  172. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  173. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
  174. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  175. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  176. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  177. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  178. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  179. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  180. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  181. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  182. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  183. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  184. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
  185. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
  186. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  187. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  188. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  189. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  190. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  191. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  192. package/src/runtime/channels/backends/discord.mjs +12 -292
  193. package/src/runtime/channels/index.mjs +241 -894
  194. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  195. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  196. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  197. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  198. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  199. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  200. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  201. package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
  202. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  203. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  204. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  205. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  206. package/src/runtime/channels/lib/telegram-format.mjs +19 -22
  207. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  208. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  209. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  210. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  211. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  212. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  213. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  214. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  215. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  216. package/src/runtime/channels/lib/webhook.mjs +36 -570
  217. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  218. package/src/runtime/channels/tool-defs.mjs +11 -130
  219. package/src/runtime/memory/index.mjs +258 -2050
  220. package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
  221. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  222. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  223. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  224. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  225. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  226. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  227. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  228. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  229. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  230. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  231. package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
  232. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  233. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  234. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  235. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  236. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  237. package/src/runtime/memory/lib/memory.mjs +20 -0
  238. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  239. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  240. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  241. package/src/runtime/memory/lib/recall-format.mjs +238 -0
  242. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  243. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  244. package/src/runtime/memory/tool-defs.mjs +6 -14
  245. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  246. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  247. package/src/runtime/search/lib/web-tools.mjs +24 -602
  248. package/src/runtime/shared/abort-controller.mjs +1 -1
  249. package/src/runtime/shared/atomic-file.mjs +26 -1
  250. package/src/runtime/shared/background-tasks.mjs +2 -3
  251. package/src/runtime/shared/buffered-appender.mjs +149 -0
  252. package/src/runtime/shared/launcher-control.mjs +2 -2
  253. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  254. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  255. package/src/runtime/shared/tool-primitives.mjs +308 -0
  256. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  257. package/src/runtime/shared/tool-surface.mjs +80 -898
  258. package/src/runtime/shared/transcript-writer.mjs +52 -2
  259. package/src/runtime/shared/update-checker.mjs +7 -4
  260. package/src/session-runtime/config-helpers.mjs +291 -0
  261. package/src/session-runtime/config-lifecycle.mjs +232 -0
  262. package/src/session-runtime/cwd-plugins.mjs +226 -0
  263. package/src/session-runtime/effort.mjs +128 -0
  264. package/src/session-runtime/fs-utils.mjs +10 -0
  265. package/src/session-runtime/mcp-glue.mjs +177 -0
  266. package/src/session-runtime/model-capabilities.mjs +130 -0
  267. package/src/session-runtime/model-recency.mjs +111 -0
  268. package/src/session-runtime/native-search.mjs +247 -0
  269. package/src/session-runtime/output-styles.mjs +126 -0
  270. package/src/session-runtime/plugin-mcp.mjs +114 -0
  271. package/src/session-runtime/prewarm.mjs +142 -0
  272. package/src/session-runtime/provider-models.mjs +278 -0
  273. package/src/session-runtime/provider-usage.mjs +120 -0
  274. package/src/session-runtime/quick-model-rows.mjs +170 -0
  275. package/src/session-runtime/quick-search-models.mjs +46 -0
  276. package/src/session-runtime/session-hooks.mjs +93 -0
  277. package/src/session-runtime/session-text.mjs +100 -0
  278. package/src/session-runtime/settings-api.mjs +319 -0
  279. package/src/session-runtime/statusline-route.mjs +35 -0
  280. package/src/session-runtime/tool-catalog.mjs +720 -0
  281. package/src/session-runtime/tool-defs.mjs +84 -0
  282. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  283. package/src/session-runtime/workflow.mjs +358 -0
  284. package/src/standalone/agent-tool/helpers.mjs +237 -0
  285. package/src/standalone/agent-tool/notify.mjs +107 -0
  286. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  287. package/src/standalone/agent-tool/render.mjs +152 -0
  288. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  289. package/src/standalone/agent-tool.mjs +155 -677
  290. package/src/standalone/channel-worker.mjs +7 -9
  291. package/src/standalone/explore-tool.mjs +40 -12
  292. package/src/standalone/hook-bus/config.mjs +207 -0
  293. package/src/standalone/hook-bus/constants.mjs +90 -0
  294. package/src/standalone/hook-bus/handlers.mjs +481 -0
  295. package/src/standalone/hook-bus/payload.mjs +31 -0
  296. package/src/standalone/hook-bus/rules.mjs +77 -0
  297. package/src/standalone/hook-bus.mjs +110 -746
  298. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  299. package/src/standalone/opencode-go-login.mjs +125 -0
  300. package/src/standalone/provider-admin.mjs +15 -19
  301. package/src/standalone/usage-dashboard.mjs +3 -1
  302. package/src/tui/App.jsx +1163 -7571
  303. package/src/tui/app/app-format.mjs +206 -0
  304. package/src/tui/app/channel-pickers.mjs +510 -0
  305. package/src/tui/app/clipboard.mjs +67 -0
  306. package/src/tui/app/core-memory-picker.mjs +210 -0
  307. package/src/tui/app/extension-pickers.mjs +506 -0
  308. package/src/tui/app/input-parsers.mjs +193 -0
  309. package/src/tui/app/maintenance-pickers.mjs +324 -0
  310. package/src/tui/app/model-options.mjs +330 -0
  311. package/src/tui/app/model-picker.mjs +365 -0
  312. package/src/tui/app/onboarding-steps.mjs +400 -0
  313. package/src/tui/app/project-picker.mjs +247 -0
  314. package/src/tui/app/provider-setup-picker.mjs +580 -0
  315. package/src/tui/app/resume-picker.mjs +55 -0
  316. package/src/tui/app/route-pickers.mjs +419 -0
  317. package/src/tui/app/settings-picker.mjs +490 -0
  318. package/src/tui/app/slash-commands.mjs +101 -0
  319. package/src/tui/app/slash-dispatch.mjs +427 -0
  320. package/src/tui/app/text-layout.mjs +46 -0
  321. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  322. package/src/tui/app/transcript-window.mjs +671 -0
  323. package/src/tui/app/use-mouse-input.mjs +460 -0
  324. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  325. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  326. package/src/tui/app/use-transcript-window.mjs +589 -0
  327. package/src/tui/components/ConfirmBar.jsx +1 -1
  328. package/src/tui/components/Picker.jsx +32 -4
  329. package/src/tui/components/PromptInput.jsx +259 -80
  330. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  331. package/src/tui/components/StatusLine.jsx +63 -12
  332. package/src/tui/components/TextEntryPanel.jsx +11 -0
  333. package/src/tui/components/ToolExecution.jsx +56 -588
  334. package/src/tui/components/TranscriptItem.jsx +105 -0
  335. package/src/tui/components/UsagePanel.jsx +18 -4
  336. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  337. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  338. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  339. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  340. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  341. package/src/tui/components/tool-output-format.mjs +2 -2
  342. package/src/tui/display-width.mjs +20 -3
  343. package/src/tui/dist/index.mjs +18034 -17188
  344. package/src/tui/engine/agent-envelope.mjs +296 -0
  345. package/src/tui/engine/agent-job-feed.mjs +133 -0
  346. package/src/tui/engine/boot-profile.mjs +21 -0
  347. package/src/tui/engine/labels.mjs +67 -0
  348. package/src/tui/engine/notice-text.mjs +112 -0
  349. package/src/tui/engine/notification-plan.mjs +76 -0
  350. package/src/tui/engine/queue-helpers.mjs +161 -0
  351. package/src/tui/engine/render-timing.mjs +17 -0
  352. package/src/tui/engine/session-stats.mjs +46 -0
  353. package/src/tui/engine/tool-approval.mjs +94 -0
  354. package/src/tui/engine/tool-call-fields.mjs +23 -0
  355. package/src/tui/engine/tool-card-results.mjs +234 -0
  356. package/src/tui/engine/tool-result-status.mjs +135 -0
  357. package/src/tui/engine/tool-result-text.mjs +126 -0
  358. package/src/tui/engine.mjs +405 -1385
  359. package/src/tui/figures.mjs +5 -0
  360. package/src/tui/index.jsx +105 -0
  361. package/src/tui/input-editing.mjs +60 -10
  362. package/src/tui/keyboard-protocol.mjs +2 -2
  363. package/src/tui/lib/voice-recorder.mjs +35 -19
  364. package/src/tui/markdown/format-token.mjs +11 -9
  365. package/src/tui/paste-attachments.mjs +38 -0
  366. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  367. package/src/tui/theme.mjs +6 -0
  368. package/src/tui/themes/base.mjs +2 -2
  369. package/src/tui/themes/kanagawa.mjs +4 -4
  370. package/src/tui/themes/teal.mjs +4 -5
  371. package/src/tui/themes/utils.mjs +1 -1
  372. package/src/ui/statusline-agents.mjs +213 -0
  373. package/src/ui/statusline-format.mjs +146 -0
  374. package/src/ui/statusline-segments.mjs +148 -0
  375. package/src/ui/statusline.mjs +77 -462
  376. package/src/ui/tool-card.mjs +0 -1
  377. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  378. package/src/workflows/default/WORKFLOW.md +16 -9
  379. package/src/workflows/sequential/WORKFLOW.md +16 -11
  380. package/src/workflows/solo/WORKFLOW.md +5 -1
  381. package/vendor/ink/build/display-width.js +19 -3
  382. package/vendor/ink/build/ink.js +103 -6
  383. package/vendor/ink/build/log-update.js +17 -3
  384. package/vendor/ink/build/wrap-text.js +125 -0
  385. package/scripts/_test-folder-dialog.mjs +0 -30
  386. package/scripts/fix-brief-fn.mjs +0 -35
  387. package/scripts/fix-format-tool-surface.mjs +0 -24
  388. package/scripts/fix-tool-exec-visible.mjs +0 -42
  389. package/scripts/patch-agent-brief.mjs +0 -48
  390. package/scripts/patch-app.mjs +0 -21
  391. package/scripts/patch-app2.mjs +0 -18
  392. package/scripts/patch-dist-brief.mjs +0 -96
  393. package/scripts/patch-tool-exec.mjs +0 -70
  394. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  395. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  396. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  397. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  398. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  399. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  400. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  401. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  402. package/src/standalone/agent-task-status.test.mjs +0 -76
  403. package/src/tui/components/tool-output-format.test.mjs +0 -399
  404. package/src/tui/display-width.test.mjs +0 -35
  405. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  406. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  407. package/src/tui/markdown/format-token.test.mjs +0 -354
  408. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  409. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  410. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  411. package/src/tui/prompt-history-store.test.mjs +0 -52
  412. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  413. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  414. package/src/ui/markdown.test.mjs +0 -70
  415. package/src/ui/statusline-context-label.test.mjs +0 -15
  416. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  417. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,607 @@
1
+ /**
2
+ * anthropic-sse.mjs — Anthropic SSE stream parser + mid-stream retry policy.
3
+ *
4
+ * Extracted from anthropic-oauth.mjs. Shared by BOTH Anthropic providers:
5
+ * anthropic-oauth.mjs (OAuth) and anthropic.mjs (API key) import
6
+ * parseSSEStream / _classifyMidstreamError / ANTHROPIC_MAX_MIDSTREAM_RETRIES
7
+ * from here (via the anthropic-oauth.mjs re-export for legacy paths).
8
+ */
9
+ import { randomBytes } from 'crypto';
10
+ import {
11
+ PROVIDER_FIRST_BYTE_TIMEOUT_MS,
12
+ PROVIDER_SSE_IDLE_WATCHDOG_ENABLED,
13
+ PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
14
+ streamStalledError,
15
+ } from '../stall-policy.mjs';
16
+ import {
17
+ classifyMidstreamError,
18
+ MIDSTREAM_RETRY_POLICY,
19
+ sleepWithAbort,
20
+ } from './retry-classifier.mjs';
21
+ import { makeInvalidToolArgsMarker } from './openai-compat-stream.mjs';
22
+ import { scanLeakedToolCalls, createToolCallDedupe } from './anthropic-leaked-toolcall.mjs';
23
+
24
+ /** Bounded mid-stream SSE retries (transient stream loss); shared with anthropic.mjs.
25
+ * Sourced from the single shared retry-budget table (MIDSTREAM_RETRY_POLICY.sse). */
26
+ export const ANTHROPIC_MAX_MIDSTREAM_RETRIES = MIDSTREAM_RETRY_POLICY.sse.defaultRetries;
27
+
28
+ // Policy passed to the shared classifyMidstreamError for the SSE path. The
29
+ // top-of-function attempt-budget gate uses defaultRetries (3); perClassifierGate
30
+ // is false so the classifier returns raw bucket strings (the loop owns the
31
+ // MAX_MIDSTREAM_RETRIES bound), matching the former _classifyMidstreamError.
32
+ const SSE_MIDSTREAM_POLICY = {
33
+ mode: 'sse',
34
+ defaultRetries: MIDSTREAM_RETRY_POLICY.sse.defaultRetries,
35
+ perClassifierGate: false,
36
+ };
37
+
38
+ // --- SSE parser ---
39
+
40
+ function _captureMidstreamAbort(state, reason) {
41
+ if (!state) return;
42
+ const reasonName = reason?.name || '';
43
+ if (reasonName === 'AgentStallAbortError' || reasonName === 'StreamStalledAbortError') {
44
+ state.watchdogAbort = reasonName;
45
+ } else {
46
+ state.userAbort = true;
47
+ }
48
+ }
49
+
50
+ // Abort-aware mid-stream backoff sleep → shared sleepWithAbort
51
+ // (retry-classifier.mjs). abortMessage preserves the prior fallback text.
52
+ export function _midstreamSleepWithAbort(ms, signal) {
53
+ return sleepWithAbort(ms, signal, undefined, 'Anthropic OAuth mid-stream retry backoff aborted');
54
+ }
55
+
56
+ function _statusForAnthropicSseError(type, message) {
57
+ const kind = String(type || '').toLowerCase();
58
+ const text = String(message || '').toLowerCase();
59
+ if (kind.includes('overload') || text.includes('overload')) return 503;
60
+ if (kind.includes('rate_limit') || text.includes('rate limit') || text.includes('quota')) return 429;
61
+ if (kind.includes('authentication') || text.includes('authentication') || text.includes('unauthorized')) return 401;
62
+ if (kind.includes('permission') || text.includes('forbidden')) return 403;
63
+ if (kind.includes('not_found') || text.includes('not found')) return 404;
64
+ if (kind.includes('invalid_request')) return 400;
65
+ return 0;
66
+ }
67
+
68
+ function _anthropicSseError(event) {
69
+ const payload = event?.error && typeof event.error === 'object' ? event.error : event;
70
+ const type = payload?.type || event?.type || 'error';
71
+ const message = payload?.message || 'Anthropic SSE error';
72
+ const err = new Error(`Anthropic OAuth SSE error ${type}: ${message}`);
73
+ err.name = 'AnthropicSseError';
74
+ err.code = 'EANTHROPIC_SSE_ERROR';
75
+ err.providerErrorType = type;
76
+ err.requestId = event?.request_id || event?.requestId || null;
77
+ const status = _statusForAnthropicSseError(type, message);
78
+ if (status) {
79
+ err.httpStatus = status;
80
+ err.status = status;
81
+ }
82
+ return err;
83
+ }
84
+
85
+ export async function parseSSEStream(response, signal, abortStream, onStreamDelta, onToolCall, state, onTextDelta, knownToolNames) {
86
+ const reader = response.body.getReader();
87
+ const decoder = new TextDecoder();
88
+ // SEMANTIC idle window: reset only by real model events (message/content/
89
+ // tool deltas), NOT by raw keepalive bytes. A ping-only wedge therefore
90
+ // trips this within the window instead of hanging until the 30-min agent
91
+ // watchdog. See resetIdleTimer + the per-event reset in the loop below.
92
+ // state.semanticIdleTimeoutMs is a test/override seam (same shape as
93
+ // firstMessageTimeoutMs); production uses the shared env-backed default.
94
+ const SSE_IDLE_TIMEOUT_MS = Number.isFinite(Number(state?.semanticIdleTimeoutMs))
95
+ && Number(state.semanticIdleTimeoutMs) > 0
96
+ ? Number(state.semanticIdleTimeoutMs)
97
+ : PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS;
98
+ const SSE_FIRST_MESSAGE_TIMEOUT_MS = Number.isFinite(Number(state?.firstMessageTimeoutMs))
99
+ && Number(state.firstMessageTimeoutMs) > 0
100
+ ? Number(state.firstMessageTimeoutMs)
101
+ : PROVIDER_FIRST_BYTE_TIMEOUT_MS;
102
+ let content = '';
103
+ let hasThinkingContent = false;
104
+ const contentBlockTypes = new Set();
105
+ let model = '';
106
+ let toolCalls = [];
107
+ let usage = { inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, raw: null };
108
+ let stopReason = null;
109
+ let buffer = '';
110
+ let idleTimedOut = false;
111
+ let firstMessageTimedOut = false;
112
+ let idleTimer = null;
113
+ let firstMessageTimer = null;
114
+ let currentEvent = '';
115
+
116
+ const pendingToolInputs = new Map();
117
+
118
+ // Leaked tool-call guard. The model (esp. Opus via OAuth) occasionally
119
+ // emits a tool call as plain text tags inside `text_delta` instead of a
120
+ // native `tool_use` block. `leakBuffer` is a minimal rolling window that
121
+ // only holds back text when a partial sentinel prefix is present, so a
122
+ // tag split across chunk boundaries is still detected while ordinary text
123
+ // still streams promptly. The guard is additive: the native tool_use path
124
+ // (content_block_start/input_json_delta/content_block_stop) is untouched.
125
+ const _knownTools = knownToolNames instanceof Set
126
+ ? knownToolNames
127
+ : new Set(Array.isArray(knownToolNames) ? knownToolNames : []);
128
+ const _leakGuardEnabled = _knownTools.size > 0;
129
+ const _isKnownTool = (name) => _knownTools.has(name);
130
+ let leakBuffer = '';
131
+ // Running markdown fence/inline-code state threaded across text_delta
132
+ // chunks (Fix 1): a tool-call tag inside a ```code fence``` or inline span
133
+ // is a doc example, not a real call — the guard emits it as text.
134
+ let leakFenceState = undefined;
135
+ // Cross-path fingerprint dedupe (Fix 2): a synthesized text-leaked call and
136
+ // an identical native tool_use block must dispatch onToolCall exactly once.
137
+ const _toolDedupe = createToolCallDedupe();
138
+
139
+ // Synthesize + dispatch a recovered leaked call exactly like the native
140
+ // content_block_stop path (push into toolCalls, flag state, eager
141
+ // onToolCall). A generated id uses the same `toolu_`-prefixed shape as
142
+ // Anthropic's native tool-call ids.
143
+ const dispatchLeakedCall = (recovered) => {
144
+ let args = recovered?.arguments;
145
+ if (args === null || typeof args !== 'object' || Array.isArray(args)) args = {};
146
+ // Skip if an identical native (or prior synthetic) call already fired.
147
+ if (!_toolDedupe.shouldDispatch(recovered.name, args)) return;
148
+ const call = {
149
+ id: `toolu_leaked_${randomBytes(8).toString('hex')}`,
150
+ name: recovered.name,
151
+ arguments: args,
152
+ };
153
+ toolCalls.push(call);
154
+ if (state) state.emittedToolCall = true;
155
+ try { onToolCall?.(call); } catch {}
156
+ try { onStreamDelta?.(); } catch {}
157
+ };
158
+
159
+ // Feed accumulated text through the scanner. On `final` nothing is held
160
+ // back so legitimate text is never lost at stream end.
161
+ const pumpLeakBuffer = (final) => {
162
+ if (!_leakGuardEnabled) return;
163
+ if (!leakBuffer && !final) return;
164
+ const { emit, calls, rest, fenceState } = scanLeakedToolCalls(leakBuffer, { isKnownTool: _isKnownTool, final, fenceState: leakFenceState });
165
+ leakBuffer = rest;
166
+ leakFenceState = fenceState;
167
+ if (emit) {
168
+ content += emit;
169
+ if (onTextDelta) {
170
+ if (state) state.emittedText = true;
171
+ try { onTextDelta(emit); } catch {}
172
+ }
173
+ }
174
+ for (const c of calls) dispatchLeakedCall(c);
175
+ };
176
+
177
+ // Holds the in-flight reader.read() race rejector so the idle timer can
178
+ // force-unblock the loop even when reader.cancel() fails to settle the
179
+ // pending read (undici half-open socket). See resetIdleTimer below.
180
+ let idleReject = null;
181
+
182
+ const firstMessageTimeoutError = () => {
183
+ const err = new Error(`Anthropic OAuth SSE stream produced no message_start within ${SSE_FIRST_MESSAGE_TIMEOUT_MS}ms`);
184
+ err.code = 'EEMPTYSTREAM';
185
+ err.isEmptyStream = true;
186
+ err.firstByteTimeout = true;
187
+ return err;
188
+ };
189
+
190
+ const clearFirstMessageTimer = () => {
191
+ if (firstMessageTimer) {
192
+ clearTimeout(firstMessageTimer);
193
+ firstMessageTimer = null;
194
+ }
195
+ };
196
+
197
+ const armFirstMessageTimer = () => {
198
+ if (!(SSE_FIRST_MESSAGE_TIMEOUT_MS > 0)) return;
199
+ clearFirstMessageTimer();
200
+ firstMessageTimer = setTimeout(() => {
201
+ if (state?.sawMessageStart) return;
202
+ firstMessageTimedOut = true;
203
+ const err = firstMessageTimeoutError();
204
+ try { abortStream?.(err); } catch (abortErr) {
205
+ try { process.stderr.write(`[anthropic-oauth] sse first-message abortStream failed: ${abortErr?.message ?? String(abortErr)}\n`); } catch {}
206
+ }
207
+ try {
208
+ const _c = reader.cancel('SSE first message timeout');
209
+ if (_c && typeof _c.catch === 'function') _c.catch(() => {});
210
+ } catch (cancelErr) {
211
+ try { process.stderr.write(`[anthropic-oauth] sse first-message cancel failed: ${cancelErr?.message ?? String(cancelErr)}\n`); } catch {}
212
+ }
213
+ if (idleReject) {
214
+ const r = idleReject; idleReject = null; r(err);
215
+ }
216
+ }, SSE_FIRST_MESSAGE_TIMEOUT_MS);
217
+ try { firstMessageTimer.unref?.(); } catch {}
218
+ };
219
+
220
+ // Attach the partial stream state to a mid-stream stall error so the agent
221
+ // loop can decide SUCCESS vs FAILURE. The recurring "worker finished but
222
+ // owner never notified" case is a FINAL no-tool summary stream that wedges
223
+ // ping-only after the real work (tool calls) already completed in earlier
224
+ // iterations: there is streamed `content`, no pending tool_use, and no
225
+ // emitted tool call this iteration. The loop treats that as a successful
226
+ // partial-final (deliver the summary we have) instead of dropping it. A
227
+ // stall WITH a pending/emitted tool call stays a hard failure (a tool whose
228
+ // input never completed must never be reported as done).
229
+ const _attachStallPartial = (err) => {
230
+ try {
231
+ err.partialContent = content;
232
+ err.partialToolCalls = toolCalls.length ? toolCalls.slice() : undefined;
233
+ err.pendingToolUse = pendingToolInputs.size > 0;
234
+ err.partialModel = model || undefined;
235
+ err.partialUsage = usage;
236
+ err.partialStopReason = stopReason || undefined;
237
+ err.partialHasThinking = hasThinkingContent;
238
+ } catch { /* best-effort enrichment */ }
239
+ return err;
240
+ };
241
+
242
+ const resetIdleTimer = () => {
243
+ // OFF by default. When disabled the
244
+ // idle timer never arms, so the stream is never killed on inactivity;
245
+ // the agent stall watchdog (600s) remains the dead-stream backstop.
246
+ if (!PROVIDER_SSE_IDLE_WATCHDOG_ENABLED) return;
247
+ if (idleTimer) clearTimeout(idleTimer);
248
+ idleTimer = setTimeout(() => {
249
+ idleTimedOut = true;
250
+ try { abortStream?.(); } catch (err) {
251
+ try { process.stderr.write(`[anthropic-oauth] sse idle abortStream failed: ${err?.message ?? String(err)}\n`); } catch {}
252
+ }
253
+ try {
254
+ const _c = reader.cancel('SSE idle timeout');
255
+ if (_c && typeof _c.catch === 'function') _c.catch(() => {});
256
+ } catch (err) {
257
+ try { process.stderr.write(`[anthropic-oauth] sse idle cancel failed: ${err?.message ?? String(err)}\n`); } catch {}
258
+ }
259
+ // Force-reject the in-flight reader.read() race even when reader.cancel()
260
+ // fails to settle the pending read: without this the await below stays
261
+ // pending forever and the SSE idle timeout never unblocks the loop —
262
+ // the 391s-hang root cause.
263
+ if (idleReject) {
264
+ const e = _attachStallPartial(streamStalledError('Anthropic OAuth SSE', SSE_IDLE_TIMEOUT_MS, { emittedToolCall: !!state?.emittedToolCall }));
265
+ const r = idleReject; idleReject = null; r(e);
266
+ }
267
+ // Shared provider policy: short SEMANTIC-event inactivity catches the
268
+ // ping-only wedge where SSE starts, emits some deltas, then goes silent
269
+ // while `:ping` keepalives keep the transport socket warm.
270
+ }, SSE_IDLE_TIMEOUT_MS);
271
+ try { idleTimer.unref?.(); } catch {}
272
+ };
273
+
274
+ const onAbort = () => {
275
+ try {
276
+ const _c = reader.cancel('SSE aborted');
277
+ if (_c && typeof _c.catch === 'function') _c.catch(() => {});
278
+ } catch {}
279
+ };
280
+ if (signal) {
281
+ if (signal.aborted) {
282
+ _captureMidstreamAbort(state, signal.reason);
283
+ throw signal.reason instanceof Error
284
+ ? signal.reason
285
+ : new Error('Anthropic OAuth SSE stream aborted');
286
+ }
287
+ signal.addEventListener('abort', onAbort, { once: true });
288
+ }
289
+
290
+ try {
291
+ // Part A / reviewer fix: do NOT arm the SEMANTIC idle timer before the
292
+ // stream has produced its first event. A slow first response is governed
293
+ // by armFirstMessageTimer() (first-byte window) alone; arming the
294
+ // semantic idle here could let it win and mis-abort a legitimately slow
295
+ // first response as a stall. The semantic idle is first armed at
296
+ // `message_start` (see below), so it only ever guards MID-stream silence.
297
+ armFirstMessageTimer();
298
+ streamLoop: while (true) {
299
+ let chunk;
300
+ try {
301
+ // Race the read against the idle timer's rejector so a stuck
302
+ // reader.read() (cancel did not settle it) still unblocks here.
303
+ chunk = await new Promise((resolve, reject) => {
304
+ idleReject = reject;
305
+ reader.read().then(resolve, reject);
306
+ });
307
+ } catch (err) {
308
+ if (idleTimedOut) {
309
+ throw _attachStallPartial(streamStalledError('Anthropic OAuth SSE', SSE_IDLE_TIMEOUT_MS, { emittedToolCall: !!state?.emittedToolCall }));
310
+ }
311
+ if (firstMessageTimedOut) {
312
+ throw firstMessageTimeoutError();
313
+ }
314
+ if (signal?.aborted) {
315
+ _captureMidstreamAbort(state, signal.reason);
316
+ throw signal.reason instanceof Error
317
+ ? signal.reason
318
+ : new Error('Anthropic OAuth SSE stream aborted');
319
+ }
320
+ throw err;
321
+ }
322
+ const { done, value } = chunk;
323
+ if (done) break;
324
+
325
+ buffer += decoder.decode(value, { stream: true });
326
+ const lines = buffer.split('\n');
327
+ buffer = lines.pop() || '';
328
+
329
+ for (const line of lines) {
330
+ if (line.startsWith(':')) {
331
+ // SSE comment frame (Anthropic `:ping` keepalive). Keep it
332
+ // at transport level only: comments must not refresh the
333
+ // agent's semantic progress timestamp, or a ping-only 200
334
+ // can look alive forever without message_start/content.
335
+ // Crucially it also does NOT reset the SEMANTIC idle timer
336
+ // below — a ping-only wedge must trip the idle abort.
337
+ continue;
338
+ }
339
+ // Blank lines are SSE record separators — emitted after EVERY
340
+ // frame, including `:ping` keepalives — so they are NOT semantic
341
+ // progress and must not reset the idle timer (else a ping frame's
342
+ // trailing blank would keep a wedge alive forever).
343
+ if (line === '') continue;
344
+ if (line.startsWith('event: ')) {
345
+ currentEvent = line.slice(7).trim();
346
+ continue;
347
+ }
348
+ if (!line.startsWith('data: ')) continue;
349
+ const data = line.slice(6).trim();
350
+ if (!data) continue;
351
+
352
+ try {
353
+ const event = JSON.parse(data);
354
+
355
+ // SEMANTIC idle reset (Part A): reset the idle timer ONLY for
356
+ // real progress events, NOT for Anthropic keepalives. Anthropic
357
+ // sends pings as a NAMED event (`event: ping` /
358
+ // `data: {"type":"ping"}`), not just `:` comment frames, so a
359
+ // named ping must be excluded here or a ping-only wedge keeps
360
+ // the timer alive forever. Everything that is not a ping is a
361
+ // genuine server event (message_start/content/tool/thinking
362
+ // deltas, message_delta/stop, errors) and counts as progress.
363
+ if (currentEvent !== 'ping' && event?.type !== 'ping') {
364
+ resetIdleTimer();
365
+ }
366
+
367
+ if (currentEvent === 'error' || event?.type === 'error' || event?.error) {
368
+ throw _anthropicSseError(event);
369
+ }
370
+
371
+ if (event.type === 'message_start' && event.message) {
372
+ clearFirstMessageTimer();
373
+ if (state) state.sawMessageStart = true;
374
+ if (event.message.model) model = event.message.model;
375
+ if (event.message.usage) {
376
+ usage.inputTokens = event.message.usage.input_tokens || 0;
377
+ usage.cachedTokens = event.message.usage.cache_read_input_tokens || 0;
378
+ usage.cacheWriteTokens = event.message.usage.cache_creation_input_tokens || 0;
379
+ usage.raw = { ...event.message.usage };
380
+ }
381
+ }
382
+
383
+ if (event.type === 'content_block_start') {
384
+ const block = event.content_block;
385
+ if (block?.type === 'tool_use') {
386
+ pendingToolInputs.set(event.index, {
387
+ id: block.id || '',
388
+ name: block.name || '',
389
+ inputJson: '',
390
+ });
391
+ }
392
+ }
393
+
394
+ if (event.type === 'content_block_delta') {
395
+ const delta = event.delta;
396
+ if (delta?.type) contentBlockTypes.add(delta.type);
397
+ // Time-to-first-token: stamp the first content delta
398
+ // (text / thinking / tool input_json) exactly once so
399
+ // the SSE trace can separate first-byte latency from
400
+ // total stream/generation time. Without this stamp
401
+ // ttftMs was always null and reported as 0ms.
402
+ if (state && !state.ttftAt) state.ttftAt = Date.now();
403
+ if (delta?.type === 'text_delta') {
404
+ try { onStreamDelta?.(); } catch {}
405
+ // Live text relay (gateway): forward the explicit
406
+ // text chunk. thinking/signature/input_json deltas
407
+ // intentionally stay off this path.
408
+ // Invariant: once a non-empty chunk has been relayed
409
+ // live it cannot be withdrawn, so flag the attempt so
410
+ // the mid-stream retry loop treats any later failure
411
+ // as final (a retry would concatenate attempts).
412
+ if (_leakGuardEnabled) {
413
+ // Route text through the leaked-tool-call guard.
414
+ // It appends to `content`, forwards visible text
415
+ // via onTextDelta, and synthesizes/dispatches any
416
+ // recovered known-tool call — suppressing the
417
+ // tags from the visible stream. A partial sentinel
418
+ // is held in leakBuffer until the next chunk.
419
+ leakBuffer += delta.text || '';
420
+ pumpLeakBuffer(false);
421
+ } else {
422
+ content += delta.text || '';
423
+ if (delta.text && onTextDelta) {
424
+ if (state) state.emittedText = true;
425
+ try { onTextDelta(delta.text); } catch {}
426
+ }
427
+ }
428
+ }
429
+ if (delta?.type === 'thinking_delta' || delta?.type === 'signature_delta') {
430
+ // Extended-thinking block: provider reasoning without
431
+ // user-visible text. Track presence so a final turn
432
+ // that emitted ONLY thinking (no text_delta, no
433
+ // tool_use) can be classified by the loop as
434
+ // synthesis-stalled rather than silent empty.
435
+ hasThinkingContent = true;
436
+ try { onStreamDelta?.(); } catch {}
437
+ }
438
+ if (delta?.type === 'input_json_delta') {
439
+ const pending = pendingToolInputs.get(event.index);
440
+ if (pending) {
441
+ pending.inputJson += delta.partial_json || '';
442
+ }
443
+ try { onStreamDelta?.(); } catch {}
444
+ }
445
+ }
446
+
447
+ if (event.type === 'content_block_stop') {
448
+ const pending = pendingToolInputs.get(event.index);
449
+ if (pending) {
450
+ // Bare JSON.parse threw straight up into the
451
+ // surrounding broad catch, which swallowed the
452
+ // whole tool_call — the loop never saw it and
453
+ // the assistant turn ended with an unmatched
454
+ // tool_use id. Wrap the parse so a malformed
455
+ // input still produces a tool_call (with an
456
+ // invalid-args marker and a logged error) instead
457
+ // of a silent drop or accidental `{}` dispatch.
458
+ let parsedArgs = {};
459
+ if (pending.inputJson) {
460
+ try { parsedArgs = JSON.parse(pending.inputJson); }
461
+ catch (parseErr) {
462
+ process.stderr.write(`[anthropic-oauth] tool args JSON.parse failed (id=${pending.id}, name=${pending.name}): ${parseErr?.message || parseErr}\n`);
463
+ parsedArgs = makeInvalidToolArgsMarker(pending.inputJson, parseErr instanceof Error ? parseErr.message : String(parseErr));
464
+ }
465
+ }
466
+ // Tool arguments must be a plain object. Anthropic's
467
+ // tool_use input is always a JSON object, but a
468
+ // malformed stream could parse to an array/string/
469
+ // number — wrap those as {} to keep the contract
470
+ // (invariant-based, no heuristic coercion).
471
+ if (parsedArgs === null
472
+ || typeof parsedArgs !== 'object'
473
+ || Array.isArray(parsedArgs)) {
474
+ process.stderr.write(`[anthropic-oauth] tool args not a plain object (id=${pending.id}, name=${pending.name}, type=${Array.isArray(parsedArgs) ? 'array' : typeof parsedArgs}); using {}\n`);
475
+ parsedArgs = {};
476
+ }
477
+ const call = {
478
+ id: pending.id,
479
+ name: pending.name,
480
+ arguments: parsedArgs,
481
+ };
482
+ pendingToolInputs.delete(event.index);
483
+ // Eager dispatch: let the loop start this tool
484
+ // before message_stop arrives. The loop keys
485
+ // pending promises by call.id so order is safe.
486
+ // Fix 2: skip the ENTIRE call (push + dispatch) when a
487
+ // text-leaked synthetic of the same (name,args) already
488
+ // fired — otherwise the duplicate stays in `toolCalls`
489
+ // and the loop executes the side-effecting tool twice.
490
+ // An invalid-args marker never fingerprint-collides with
491
+ // a real recovered call, so malformed native calls still
492
+ // dispatch (the marker path is unaffected).
493
+ if (_toolDedupe.shouldDispatch(call.name, call.arguments)) {
494
+ toolCalls.push(call);
495
+ if (state) state.emittedToolCall = true;
496
+ // Eager dispatch: let the loop start this tool
497
+ // before message_stop arrives. The loop keys
498
+ // pending promises by call.id so order is safe.
499
+ try { onToolCall?.(call); } catch {}
500
+ }
501
+ try { onStreamDelta?.(); } catch {}
502
+ }
503
+ }
504
+
505
+ if (event.type === 'message_delta') {
506
+ if (event.delta?.stop_reason) {
507
+ stopReason = event.delta.stop_reason;
508
+ }
509
+ if (event.usage) {
510
+ usage.outputTokens = event.usage.output_tokens || 0;
511
+ usage.raw = { ...(usage.raw || {}), ...event.usage };
512
+ }
513
+ if (stopReason === 'tool_use' && toolCalls.length > 0 && pendingToolInputs.size === 0) {
514
+ if (state) state.sawCompleted = true;
515
+ break streamLoop;
516
+ }
517
+ }
518
+ if (event.type === 'message_stop') {
519
+ if (state) state.sawCompleted = true;
520
+ // Anthropic streams can keep emitting `:ping` keepalive
521
+ // frames after `message_stop`; if we wait for EOF the
522
+ // outer reader.read() loop hangs indefinitely. Break
523
+ // out of streamLoop the moment the message ends.
524
+ break streamLoop;
525
+ }
526
+ // Unified prompt volume — what the model actually ingested.
527
+ // Anthropic splits input into three billable slots (uncached
528
+ // input + cache_read + cache_create); keep them separate for
529
+ // cost math but also expose the sum so cross-provider logs
530
+ // have a consistent `promptTokens` meaning.
531
+ usage.promptTokens = (usage.inputTokens || 0)
532
+ + (usage.cachedTokens || 0)
533
+ + (usage.cacheWriteTokens || 0);
534
+ } catch (err) {
535
+ if (err?.code === 'EANTHROPIC_SSE_ERROR') throw err;
536
+ /* skip malformed events */
537
+ }
538
+ }
539
+ }
540
+
541
+ // Stream ended: flush any held-back leaked-tool-call buffer. `final`
542
+ // holds nothing back, so a trailing partial sentinel that never
543
+ // resolved into a real call is surfaced as ordinary text — legitimate
544
+ // user-visible content is never lost on the failure path.
545
+ pumpLeakBuffer(true);
546
+
547
+ // Truncated-stream guard: if the reader loop exited (EOF or break)
548
+ // after message_start but without seeing message_stop / a tool_use
549
+ // stop_reason, the assistant turn was cut off mid-flight. Returning
550
+ // success here would silently surface partial content (or a partially
551
+ // streamed tool_use whose input_json never completed) as final.
552
+ // Throw a typed truncated-stream error so the loop can decide whether
553
+ // to retry, surface, or escalate instead of accepting the partial.
554
+ if (state?.sawMessageStart && !state?.sawCompleted) {
555
+ const pendingToolUse = pendingToolInputs.size > 0;
556
+ const err = Object.assign(
557
+ new Error(
558
+ `Anthropic OAuth SSE stream truncated: message_start without message_stop`
559
+ + (pendingToolUse ? ` (pending tool_use input)` : ''),
560
+ ),
561
+ {
562
+ name: 'TruncatedStreamError',
563
+ code: 'TRUNCATED_STREAM',
564
+ truncatedStream: true,
565
+ pendingToolUse,
566
+ stopReason,
567
+ },
568
+ );
569
+ throw err;
570
+ }
571
+
572
+ return {
573
+ content,
574
+ model,
575
+ toolCalls: toolCalls.length ? toolCalls : undefined,
576
+ usage,
577
+ stopReason,
578
+ hasThinkingContent,
579
+ contentBlockTypes: Array.from(contentBlockTypes),
580
+ };
581
+ } finally {
582
+ if (idleTimer) clearTimeout(idleTimer);
583
+ clearFirstMessageTimer();
584
+ if (signal) signal.removeEventListener('abort', onAbort);
585
+ try { reader.releaseLock(); } catch (err) {
586
+ try { process.stderr.write(`[anthropic-oauth] reader releaseLock failed: ${err?.message ?? String(err)}\n`); } catch {}
587
+ }
588
+ }
589
+ }
590
+
591
+ /**
592
+ * Classify an Anthropic SSE failure for single-shot mid-stream retry.
593
+ *
594
+ * Retry is allowed only after `message_start` and before `message_stop`,
595
+ * and only when no tool call has already been surfaced to the loop.
596
+ * That keeps recovery limited to transport/stream stalls without risking
597
+ * duplicate eager tool execution.
598
+ */
599
+ // Thin wrapper: the SSE mid-stream decision tree now lives in the shared
600
+ // classifyMidstreamError (retry-classifier.mjs, policy.mode='sse'). Kept as a
601
+ // named export so internal call sites AND anthropic.mjs (which imports this
602
+ // symbol) keep resolving it. Behavior is byte-identical — the shared function
603
+ // is the relocated original, gated by SSE_MIDSTREAM_POLICY (defaultRetries=3,
604
+ // perClassifierGate:false).
605
+ export function _classifyMidstreamError(err, state) {
606
+ return classifyMidstreamError(err, state, SSE_MIDSTREAM_POLICY);
607
+ }