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,489 @@
1
+ /**
2
+ * Anthropic OAuth credential store + PKCE login flow.
3
+ *
4
+ * Split out of anthropic-oauth.mjs (section-scoped extraction). Owns the
5
+ * on-disk credentials file (load/save/refresh/forget) and the loopback
6
+ * PKCE login flow. anthropic-oauth.mjs imports these back and re-exports
7
+ * the public functions so external callers keep their existing import path.
8
+ */
9
+ import { readFileSync, existsSync, statSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { createServer } from 'http';
12
+ import { randomBytes, createHash } from 'crypto';
13
+ import { writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
14
+ import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
15
+ import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
16
+
17
+ // SSRF guard for the OAuth token endpoint override. Env-supplied URLs must be
18
+ // https with a valid http(s) URL shape; reject file:/data:/ftp:/etc. and any
19
+ // http override so a hostile env cannot redirect refresh-token requests.
20
+ function assertSafeTokenURL(rawURL) {
21
+ let parsed;
22
+ try {
23
+ parsed = new URL(String(rawURL));
24
+ } catch {
25
+ throw new Error(`[anthropic-oauth] invalid ANTHROPIC_OAUTH_TOKEN_URL: ${rawURL}`);
26
+ }
27
+ if (parsed.protocol.toLowerCase() !== 'https:') {
28
+ throw new Error(`[anthropic-oauth] ANTHROPIC_OAUTH_TOKEN_URL must use https (got ${parsed.protocol})`);
29
+ }
30
+ return rawURL;
31
+ }
32
+ export const TOKEN_URL = assertSafeTokenURL(process.env.ANTHROPIC_OAUTH_TOKEN_URL || 'https://platform.claude.com/v1/oauth/token');
33
+ export const DEFAULT_CREDENTIALS_PATH = join(resolvePluginData(), 'anthropic-oauth-credentials.json');
34
+ export const CLAUDE_CODE_CLIENT_ID = process.env.ANTHROPIC_OAUTH_CLIENT_ID || '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
35
+ export const TOKEN_REFRESH_SKEW_MS = 5 * 60_000;
36
+ const CLAUDE_AI_AUTHORIZE_URL = 'https://claude.com/cai/oauth/authorize';
37
+ const ALL_OAUTH_SCOPES = [
38
+ 'org:create_api_key',
39
+ 'user:profile',
40
+ 'user:inference',
41
+ 'user:sessions:claude_code',
42
+ 'user:mcp_servers',
43
+ 'user:file_upload',
44
+ ];
45
+ const OAUTH_LOGIN_SCOPE = ALL_OAUTH_SCOPES.join(' ');
46
+ const OAUTH_CALLBACK_HOST = 'localhost';
47
+ const OAUTH_CALLBACK_PORT = 54545;
48
+ const OAUTH_CALLBACK_PATH = '/callback';
49
+ const OAUTH_REDIRECT_URI = `http://${OAUTH_CALLBACK_HOST}:${OAUTH_CALLBACK_PORT}${OAUTH_CALLBACK_PATH}`;
50
+ const OAUTH_MANUAL_REDIRECT_URI = process.env.ANTHROPIC_OAUTH_MANUAL_REDIRECT_URI || 'https://platform.claude.com/oauth/code/callback';
51
+ const OAUTH_SUCCESS_REDIRECT_URL = process.env.ANTHROPIC_OAUTH_SUCCESS_REDIRECT_URL || 'https://platform.claude.com/oauth/code/success?app=claude-code';
52
+ const OAUTH_LOGIN_TIMEOUT_MS = 5 * 60_000;
53
+ const OAUTH_TOKEN_TIMEOUT_MS = 30_000;
54
+
55
+ export const DEFAULT_CLI_VERSION = '2.1.77';
56
+
57
+ export function resolveCliVersion() {
58
+ return process.env.MIXDOG_CLI_VERSION
59
+ || DEFAULT_CLI_VERSION;
60
+ }
61
+
62
+ // --- Credential helpers ---
63
+
64
+ function _pushUnique(list, value) {
65
+ if (!value || typeof value !== 'string') return;
66
+ if (!list.includes(value)) list.push(value);
67
+ }
68
+
69
+ export function credentialCandidates() {
70
+ const paths = [];
71
+ _pushUnique(paths, process.env.ANTHROPIC_OAUTH_CREDENTIALS_PATH);
72
+ _pushUnique(paths, DEFAULT_CREDENTIALS_PATH);
73
+ return paths;
74
+ }
75
+
76
+ // Fallback expiry from the access_token's JWT `exp` claim (epoch ms) when the
77
+ // credentials file carries no explicit expiresAt — without it expiresAt stays 0,
78
+ // which ensureAuth reads as "never expires", disabling proactive refresh. Claude
79
+ // OAuth tokens are opaque so this returns 0 and the file's expiresAt governs.
80
+ // JWT `exp` is epoch SECONDS (RFC 7519).
81
+ function _expiryFromAccessToken(token) {
82
+ try {
83
+ const parts = String(token || '').split('.');
84
+ if (parts.length !== 3) return 0;
85
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));
86
+ const exp = Number(payload?.exp);
87
+ return Number.isFinite(exp) && exp > 0 ? exp * 1000 : 0;
88
+ } catch { return 0; }
89
+ }
90
+
91
+ function _loadCredentialsFile(path) {
92
+ if (!existsSync(path)) return null;
93
+ try {
94
+ const stat = statSync(path);
95
+ const raw = JSON.parse(readFileSync(path, 'utf-8'));
96
+ const oauth = raw?.claudeAiOauth;
97
+ if (!oauth?.accessToken) return null;
98
+ return {
99
+ path,
100
+ mtimeMs: stat.mtimeMs,
101
+ accessToken: oauth.accessToken,
102
+ refreshToken: oauth.refreshToken || null,
103
+ expiresAt: _normalizeExpiresAt(oauth.expiresAt ?? oauth.expires_at) || _expiryFromAccessToken(oauth.accessToken),
104
+ scopes: Array.isArray(oauth.scopes) ? oauth.scopes : [],
105
+ subscriptionType: oauth.subscriptionType || null,
106
+ };
107
+ } catch {
108
+ return null;
109
+ }
110
+ }
111
+
112
+ // Cross-process safe credential save. Lockfile (O_EXCL) prevents two Mixdog
113
+ // refreshers from clobbering each other; atomic rename guarantees readers see
114
+ // either the old or new file, never a half-written one. Used so refresh_token
115
+ // rotation propagates to other Mixdog readers of the same credentials file
116
+ // instead of leaving them stuck on the previous refresh_token.
117
+ export function _saveCredentialsFile(path, raw) {
118
+ // Secret file, not parent-dir ACL mutation. `secret: true` clamps the file
119
+ // itself on Windows; it deliberately leaves the data dir inheritance alone.
120
+ writeJsonAtomicSync(path, raw, { lock: true, fsyncDir: true, mode: 0o600, secret: true });
121
+ }
122
+
123
+ // Cheap stat-only probe so ensureAuth can detect Mixdog-updated credentials
124
+ // without paying a full JSON read every call.
125
+ export function _credentialsMaxMtime() {
126
+ let max = 0;
127
+ for (const p of credentialCandidates()) {
128
+ try {
129
+ const s = statSync(p);
130
+ if (s.mtimeMs > max) max = s.mtimeMs;
131
+ } catch { /* not present — skip */ }
132
+ }
133
+ return max;
134
+ }
135
+
136
+ export function loadCredentials() {
137
+ const loaded = credentialCandidates()
138
+ .map(_loadCredentialsFile)
139
+ .filter(Boolean);
140
+ if (!loaded.length) return null;
141
+ loaded.sort((a, b) => (Number(b.expiresAt) || 0) - (Number(a.expiresAt) || 0));
142
+ return loaded[0];
143
+ }
144
+
145
+ // Public predicate used by config.buildDefaultConfig — provider is enabled
146
+ // when on-disk credentials exist AND carry the inference scope. Single
147
+ // truth: same loader the runtime uses, no parallel hard-coded path probe.
148
+ export function hasAnthropicOAuthCredentials() {
149
+ const creds = loadCredentials();
150
+ if (!creds?.accessToken) return false;
151
+ return Array.isArray(creds.scopes) && creds.scopes.includes('user:inference');
152
+ }
153
+
154
+ export function describeAnthropicOAuthCredentials() {
155
+ try {
156
+ const creds = loadCredentials();
157
+ if (!creds?.accessToken) {
158
+ return { authenticated: false, status: 'Not Set', detail: 'Mixdog OAuth credentials' };
159
+ }
160
+ const hasInferenceScope = Array.isArray(creds.scopes) && creds.scopes.includes('user:inference');
161
+ const hasRefresh = Boolean(creds.refreshToken);
162
+ const expiresAt = _normalizeExpiresAt(creds.expiresAt);
163
+ const expiring = expiresAt > 0 && expiresAt < Date.now() + TOKEN_REFRESH_SKEW_MS;
164
+ const expired = expiresAt > 0 && expiresAt <= Date.now();
165
+ const detail = creds.path || DEFAULT_CREDENTIALS_PATH;
166
+ if (!hasInferenceScope) {
167
+ return { authenticated: false, status: 'Missing Scope', detail, expiresAt };
168
+ }
169
+ if (!hasRefresh) {
170
+ return {
171
+ authenticated: expiresAt === 0 || !expired,
172
+ status: expired ? 'Reauth Required' : 'Access Only',
173
+ detail: `${detail}; no refresh token`,
174
+ expiresAt,
175
+ };
176
+ }
177
+ if (expired) return { authenticated: true, status: 'Refresh Required', detail, expiresAt };
178
+ if (expiring) return { authenticated: true, status: 'Refresh Soon', detail, expiresAt };
179
+ return { authenticated: true, status: 'Valid', detail, expiresAt };
180
+ } catch (err) {
181
+ return { authenticated: false, status: 'Error', detail: String(err?.message || err).slice(0, 200) };
182
+ }
183
+ }
184
+
185
+ export function forgetAnthropicOAuthCredentials() {
186
+ let removed = false;
187
+ for (const path of credentialCandidates()) {
188
+ if (!existsSync(path)) continue;
189
+ try {
190
+ const raw = JSON.parse(readFileSync(path, 'utf-8'));
191
+ if (raw?.claudeAiOauth) {
192
+ delete raw.claudeAiOauth;
193
+ _saveCredentialsFile(path, raw);
194
+ removed = true;
195
+ }
196
+ } catch (err) {
197
+ throw new Error(`Anthropic OAuth reset failed for ${path}: ${err?.message || err}`);
198
+ }
199
+ }
200
+ return { removed };
201
+ }
202
+
203
+ export function _normalizeExpiresAt(value) {
204
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) return 0;
205
+ return value < 1e12 ? value * 1000 : value;
206
+ }
207
+
208
+ export function _scrubTokens(text) {
209
+ return String(text || '')
210
+ .replace(/Bearer [A-Za-z0-9._\-]+/g, 'Bearer [REDACTED]')
211
+ .replace(/sk-ant-[A-Za-z0-9._\-]+/g, '[REDACTED]')
212
+ .replace(/"access[Tt]oken"\s*:\s*"[^"]+"/g, '"accessToken":"[REDACTED]"')
213
+ .replace(/"refresh[Tt]oken"\s*:\s*"[^"]+"/g, '"refreshToken":"[REDACTED]"')
214
+ .replace(/"access_token"\s*:\s*"[^"]+"/g, '"access_token":"[REDACTED]"')
215
+ .replace(/"refresh_token"\s*:\s*"[^"]+"/g, '"refresh_token":"[REDACTED]"');
216
+ }
217
+
218
+ export async function refreshOAuthCredentials(creds) {
219
+ if (!creds?.refreshToken) {
220
+ throw new Error('Anthropic OAuth refresh token not available. Open /providers in mixdog to sign in again.');
221
+ }
222
+
223
+ const controller = new AbortController();
224
+ const timeout = setTimeout(() => controller.abort(), 30_000);
225
+ try {
226
+ const res = await fetch(TOKEN_URL, {
227
+ method: 'POST',
228
+ headers: {
229
+ 'Content-Type': 'application/json',
230
+ 'anthropic-dangerous-direct-browser-access': 'true',
231
+ 'user-agent': `claude-cli/${resolveCliVersion()} (external, sdk-cli)`,
232
+ },
233
+ body: JSON.stringify({
234
+ grant_type: 'refresh_token',
235
+ refresh_token: creds.refreshToken,
236
+ client_id: CLAUDE_CODE_CLIENT_ID,
237
+ }),
238
+ // Never follow a redirect on a secret-bearing request: a token
239
+ // endpoint that 307/308-redirects would replay the refresh_token to
240
+ // the redirect target. Fail loud instead.
241
+ redirect: 'error',
242
+ signal: controller.signal,
243
+ dispatcher: getLlmDispatcher(),
244
+ });
245
+
246
+ const text = await res.text();
247
+ let json = null;
248
+ try { json = text ? JSON.parse(text) : null; } catch { /* handled below */ }
249
+ if (!res.ok) {
250
+ const isInvalidGrant = text.includes('invalid_grant') || json?.error === 'invalid_grant';
251
+ throw Object.assign(new Error(`token refresh ${res.status}: ${_scrubTokens(text).slice(0, 200)}`), { isInvalidGrant });
252
+ }
253
+
254
+ const accessToken = json?.access_token || json?.accessToken;
255
+ if (!accessToken) throw new Error('token refresh returned no access token');
256
+ const expiresAt = _normalizeExpiresAt(json?.expires_at ?? json?.expiresAt)
257
+ || (typeof json?.expires_in === 'number' ? Date.now() + json.expires_in * 1000 : 0);
258
+ const refreshed = {
259
+ path: creds.path,
260
+ accessToken,
261
+ refreshToken: json?.refresh_token || json?.refreshToken || creds.refreshToken,
262
+ expiresAt,
263
+ scopes: Array.isArray(json?.scope) ? json.scope : creds.scopes,
264
+ subscriptionType: creds.subscriptionType,
265
+ };
266
+ // Persist rotated tokens back so any other Mixdog reader of the same
267
+ // credentials file picks up the new refresh_token. Without this, a
268
+ // later process can replay an old single-use refresh token and loop on
269
+ // invalid_grant.
270
+ if (creds.path && existsSync(creds.path)) {
271
+ try {
272
+ const raw = JSON.parse(readFileSync(creds.path, 'utf-8'));
273
+ raw.claudeAiOauth = {
274
+ ...(raw.claudeAiOauth || {}),
275
+ accessToken: refreshed.accessToken,
276
+ refreshToken: refreshed.refreshToken,
277
+ expiresAt: refreshed.expiresAt,
278
+ scopes: refreshed.scopes,
279
+ };
280
+ _saveCredentialsFile(creds.path, raw);
281
+ } catch (err) {
282
+ process.stderr.write(`[anthropic-oauth] credential save failed: ${_scrubTokens(err?.message || String(err)).slice(0, 200)}\n`);
283
+ throw new Error(`[oauth] credentials save failed: ${err?.message ?? String(err)}`);
284
+ }
285
+ }
286
+ return refreshed;
287
+ } catch (err) {
288
+ if (err?.name === 'AbortError') {
289
+ throw new Error('Anthropic OAuth token refresh timed out after 30000ms');
290
+ }
291
+ throw err;
292
+ } finally {
293
+ clearTimeout(timeout);
294
+ }
295
+ }
296
+
297
+ // --- Login flow (PKCE loopback, export for setup UI / CLI) ---
298
+
299
+ function _oauthGeneratePKCE() {
300
+ const verifier = randomBytes(32).toString('base64url');
301
+ const challenge = createHash('sha256').update(verifier).digest('base64url');
302
+ return { verifier, challenge };
303
+ }
304
+
305
+ function _oauthCredentialsWritePath() {
306
+ for (const p of credentialCandidates()) {
307
+ if (existsSync(p)) return p;
308
+ }
309
+ return DEFAULT_CREDENTIALS_PATH;
310
+ }
311
+
312
+ function _oauthParseScopeField(scope) {
313
+ if (Array.isArray(scope)) return scope;
314
+ return String(scope || '').split(' ').filter(Boolean);
315
+ }
316
+
317
+ function _parseOAuthCodeInput(input) {
318
+ const value = String(input || '').trim();
319
+ if (!value) return { code: '', state: '' };
320
+ try {
321
+ const url = new URL(value);
322
+ const code = url.searchParams.get('code') || '';
323
+ const state = url.searchParams.get('state') || '';
324
+ if (code || state) return { code, state, redirectUri: `${url.origin}${url.pathname}` };
325
+ } catch { /* not a URL */ }
326
+ if (value.includes('#')) {
327
+ const [code, state] = value.split('#', 2);
328
+ return { code: String(code || '').trim(), state: String(state || '').trim() };
329
+ }
330
+ if (value.includes('code=')) {
331
+ const params = new URLSearchParams(value.startsWith('?') ? value.slice(1) : value);
332
+ return { code: params.get('code') || '', state: params.get('state') || '' };
333
+ }
334
+ return { code: value, state: '' };
335
+ }
336
+
337
+ async function exchangeAuthorizationCode({ pkce, code, state, redirectUri }) {
338
+ const cleanCode = String(code || '').trim();
339
+ if (!cleanCode) throw new Error('[anthropic-oauth] authorization code is required');
340
+ const tokenRes = await fetch(TOKEN_URL, {
341
+ method: 'POST',
342
+ headers: {
343
+ 'Content-Type': 'application/json',
344
+ 'anthropic-dangerous-direct-browser-access': 'true',
345
+ 'user-agent': `claude-cli/${resolveCliVersion()} (external, sdk-cli)`,
346
+ },
347
+ body: JSON.stringify({
348
+ grant_type: 'authorization_code',
349
+ code: cleanCode,
350
+ redirect_uri: redirectUri,
351
+ client_id: CLAUDE_CODE_CLIENT_ID,
352
+ code_verifier: pkce.verifier,
353
+ state,
354
+ }),
355
+ redirect: 'error',
356
+ signal: AbortSignal.timeout(OAUTH_TOKEN_TIMEOUT_MS),
357
+ dispatcher: getLlmDispatcher(),
358
+ });
359
+ if (!tokenRes.ok) {
360
+ const text = await tokenRes.text().catch(() => '');
361
+ throw new Error(`[anthropic-oauth] token exchange ${tokenRes.status}: ${_scrubTokens(text).slice(0, 500)}`);
362
+ }
363
+ const json = await tokenRes.json();
364
+ const accessToken = json?.access_token || json?.accessToken;
365
+ const refreshToken = json?.refresh_token || json?.refreshToken;
366
+ if (!accessToken || !refreshToken) {
367
+ throw new Error('[anthropic-oauth] token exchange response missing access_token or refresh_token');
368
+ }
369
+ const expiresAt = _normalizeExpiresAt(json?.expires_at ?? json?.expiresAt)
370
+ || (typeof json?.expires_in === 'number' ? Date.now() + json.expires_in * 1000 : 0);
371
+ const scopes = _oauthParseScopeField(json?.scope);
372
+ const credPath = _oauthCredentialsWritePath();
373
+ let raw = {};
374
+ if (existsSync(credPath)) {
375
+ raw = JSON.parse(readFileSync(credPath, 'utf-8'));
376
+ }
377
+ const existingOauth = raw.claudeAiOauth || {};
378
+ raw.claudeAiOauth = {
379
+ ...existingOauth,
380
+ accessToken,
381
+ refreshToken,
382
+ expiresAt,
383
+ scopes,
384
+ subscriptionType: existingOauth.subscriptionType ?? null,
385
+ };
386
+ _saveCredentialsFile(credPath, raw);
387
+ return {
388
+ path: credPath,
389
+ accessToken,
390
+ refreshToken,
391
+ expiresAt,
392
+ scopes,
393
+ subscriptionType: raw.claudeAiOauth.subscriptionType,
394
+ };
395
+ }
396
+
397
+ export async function beginOAuthLogin() {
398
+ const pkce = _oauthGeneratePKCE();
399
+ const state = randomBytes(32).toString('base64url');
400
+ const buildUrl = (redirectUri) => {
401
+ const url = new URL(CLAUDE_AI_AUTHORIZE_URL);
402
+ url.searchParams.set('code', 'true');
403
+ url.searchParams.set('client_id', CLAUDE_CODE_CLIENT_ID);
404
+ url.searchParams.set('response_type', 'code');
405
+ url.searchParams.set('redirect_uri', redirectUri);
406
+ url.searchParams.set('scope', OAUTH_LOGIN_SCOPE);
407
+ url.searchParams.set('code_challenge', pkce.challenge);
408
+ url.searchParams.set('code_challenge_method', 'S256');
409
+ url.searchParams.set('state', state);
410
+ return url;
411
+ };
412
+ const url = buildUrl(OAUTH_REDIRECT_URI);
413
+ const manualUrl = buildUrl(OAUTH_MANUAL_REDIRECT_URI);
414
+
415
+ let server = null;
416
+ let timeout = null;
417
+ let finish = null;
418
+ const waitForCallback = new Promise((resolve, reject) => {
419
+ let settled = false;
420
+ finish = (value, error = null) => {
421
+ if (settled) return;
422
+ settled = true;
423
+ if (timeout) clearTimeout(timeout);
424
+ try { server?.close(); } catch { /* already closed */ }
425
+ if (error) reject(error);
426
+ else resolve(value);
427
+ };
428
+ server = createServer(async (req, res) => {
429
+ const u = new URL(req.url || '/', `http://${OAUTH_CALLBACK_HOST}:${OAUTH_CALLBACK_PORT}`);
430
+ if (u.pathname !== OAUTH_CALLBACK_PATH) {
431
+ res.writeHead(404);
432
+ res.end();
433
+ return;
434
+ }
435
+ const code = u.searchParams.get('code');
436
+ if (!code || u.searchParams.get('state') !== state) {
437
+ res.writeHead(400);
438
+ res.end('Invalid');
439
+ finish(null);
440
+ return;
441
+ }
442
+ try {
443
+ const tokens = await exchangeAuthorizationCode({ pkce, code, state, redirectUri: OAUTH_REDIRECT_URI });
444
+ res.writeHead(302, { Location: OAUTH_SUCCESS_REDIRECT_URL });
445
+ res.end();
446
+ finish(tokens);
447
+ } catch (err) {
448
+ const error = err instanceof Error ? err : new Error(String(err));
449
+ res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
450
+ res.end(`Claude login failed: ${error.message}`);
451
+ finish(null, error);
452
+ }
453
+ });
454
+ timeout = setTimeout(() => finish(null), OAUTH_LOGIN_TIMEOUT_MS);
455
+ server.listen(OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_HOST, async () => {
456
+ process.stderr.write(`\n[anthropic-oauth] Open this URL to log in with Claude:\n${url.toString()}\n\nIf the localhost callback cannot complete, open this manual URL and paste the shown code#state:\n${manualUrl.toString()}\n\n`);
457
+ try {
458
+ const { openInBrowser } = await import('../../../shared/open-url.mjs');
459
+ openInBrowser(url.toString());
460
+ } catch (err) {
461
+ process.stderr.write(`[anthropic-oauth] browser open failed: ${String(err?.message || err).slice(0, 200)}\n`);
462
+ }
463
+ });
464
+ server.on('error', (err) => finish(null, new Error(`[anthropic-oauth] callback server failed on ${OAUTH_CALLBACK_HOST}:${OAUTH_CALLBACK_PORT}: ${err?.message || err}`)));
465
+ });
466
+
467
+ return {
468
+ provider: 'anthropic-oauth',
469
+ url: url.toString(),
470
+ manualUrl: manualUrl.toString(),
471
+ waitForCallback,
472
+ completeCode: async (input) => {
473
+ const parsed = _parseOAuthCodeInput(input);
474
+ if (parsed.state && parsed.state !== state) throw new Error('[anthropic-oauth] OAuth state mismatch');
475
+ const redirectUri = parsed.redirectUri || (parsed.state ? OAUTH_MANUAL_REDIRECT_URI : OAUTH_REDIRECT_URI);
476
+ const tokens = await exchangeAuthorizationCode({ pkce, code: parsed.code, state, redirectUri });
477
+ finish?.(tokens);
478
+ return tokens;
479
+ },
480
+ cancel: () => {
481
+ finish?.(null);
482
+ },
483
+ };
484
+ }
485
+
486
+ export async function loginOAuth() {
487
+ const login = await beginOAuthLogin();
488
+ return await login.waitForCallback;
489
+ }