mixdog 0.9.3 → 0.9.5

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 (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  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/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. 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
+ }