mixdog 1.2.1 → 1.2.3

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 (557) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/gate-local.mjs +19 -3
  4. package/scripts/model-catalog-audit.mjs +1 -15
  5. package/scripts/provider-pingpong-live.mjs +509 -0
  6. package/src/agents/advisor/AGENT.md +1 -1
  7. package/src/agents/advisor/agent.json +1 -1
  8. package/src/agents/reviewer/AGENT.md +3 -3
  9. package/src/agents/reviewer/agent.json +1 -1
  10. package/src/defaults/shipped-history.json +6 -0
  11. package/src/defaults/skills/setup/SKILL.md +6 -1
  12. package/src/defaults/skills/setup/references/actions.md +44 -22
  13. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  14. package/src/headless-exec.mjs +15 -8
  15. package/src/lib/keychain-cjs.cjs +7 -4
  16. package/src/output-styles/common.md +11 -10
  17. package/src/repl/slash-commands.mjs +16 -1
  18. package/src/rules/agent/AGENT.md +5 -5
  19. package/src/rules/lead/LEAD.md +5 -5
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  21. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  22. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  23. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  24. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
  33. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  34. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  35. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  36. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  37. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  38. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  39. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  40. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  41. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  43. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  44. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  45. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +98 -0
  46. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  47. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  48. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +48 -37
  49. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +10 -7
  50. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  51. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  52. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  54. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  55. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +102 -45
  58. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  61. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  63. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  65. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +10 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +40 -7
  70. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  71. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  72. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +88 -21
  73. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +30 -10
  74. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +106 -0
  75. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  76. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +94 -15
  77. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +117 -72
  79. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  80. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +75 -10
  81. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  83. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +103 -23
  84. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  85. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  86. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +32 -6
  87. package/src/runtime/agent/orchestrator/providers/cursor.mjs +27 -3
  88. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  89. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  90. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  91. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  92. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  93. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +80 -18
  94. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  95. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +46 -8
  96. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +273 -0
  97. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  99. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  100. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  101. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  102. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +369 -112
  103. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +3 -1
  104. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +140 -35
  105. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +70 -24
  106. package/src/runtime/agent/orchestrator/providers/gemini.mjs +80 -29
  107. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  108. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  109. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +69 -19
  110. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  113. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  114. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  115. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  116. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  117. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  118. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  119. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  120. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  121. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  122. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  123. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  124. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  125. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  126. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  127. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  128. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  129. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  130. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +123 -61
  132. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  133. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +27 -2
  134. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  135. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  136. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  137. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  138. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  139. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  140. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  141. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  142. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +64 -23
  143. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +79 -33
  144. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  145. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  146. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  147. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +144 -15
  148. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  149. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  150. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +9 -2
  151. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +109 -26
  153. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +27 -6
  154. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +34 -20
  155. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +23 -4
  156. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +37 -28
  157. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  158. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  159. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  160. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +43 -15
  161. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  163. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +5 -2
  164. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  165. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +7 -3
  166. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  167. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  168. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  169. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  170. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +37 -4
  171. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +12 -1
  172. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  173. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -1
  174. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +76 -33
  175. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  176. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  177. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  178. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  179. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  180. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  181. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  182. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  183. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  184. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  185. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  186. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  187. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  188. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +41 -22
  189. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  190. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  191. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  192. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +720 -0
  193. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  195. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +123 -219
  196. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  197. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  198. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  199. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  200. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  201. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  202. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  203. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  204. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  205. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  206. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  207. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  208. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  209. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  210. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  211. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  212. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
  213. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  214. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  215. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  216. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  217. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  218. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  219. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  220. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  221. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  222. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +5 -1
  223. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  224. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  225. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  226. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  227. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  228. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  229. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  230. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  231. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  232. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  233. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  234. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  235. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  236. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  237. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  238. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  239. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +67 -20
  240. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +104 -19
  241. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  242. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  243. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  244. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  245. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  246. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
  248. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
  249. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  250. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  251. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +57 -21
  252. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  253. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  254. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  255. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  256. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
  257. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  258. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +16 -4
  259. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  260. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  261. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  262. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  263. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  264. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  265. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  266. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  267. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +168 -29
  268. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  269. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  270. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  271. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  272. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  273. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  274. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  275. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  276. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  277. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  278. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  279. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  280. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  281. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +6 -0
  282. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +13 -0
  283. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +13 -1
  284. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  285. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  286. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  287. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  288. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  289. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  290. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  291. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  292. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
  293. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  294. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  295. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  296. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  297. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  298. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  299. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  300. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  301. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  302. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  303. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  304. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  305. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  306. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  307. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  308. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  309. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  310. package/src/runtime/github/tool.mjs +92 -2
  311. package/src/runtime/memory/index.mjs +1 -0
  312. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  313. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  314. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  315. package/src/runtime/memory/lib/pg/process.mjs +65 -50
  316. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  317. package/src/runtime/memory/lib/pg/supervisor.mjs +124 -16
  318. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  319. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  320. package/src/runtime/office/README.md +2 -2
  321. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  322. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  323. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  324. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  325. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  326. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  327. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  328. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  329. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  330. package/src/runtime/office/core/office-sessions.mjs +5 -0
  331. package/src/runtime/office/core/office-transactions.mjs +3 -0
  332. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  333. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  334. package/src/runtime/office/index.mjs +30 -0
  335. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  336. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  337. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  338. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  339. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  340. package/src/runtime/office/shared/values.mjs +0 -5
  341. package/src/runtime/shared/background-tasks.mjs +92 -14
  342. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  343. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  344. package/src/runtime/shared/buffered-appender.mjs +18 -0
  345. package/src/runtime/shared/config.mjs +78 -18
  346. package/src/runtime/shared/err-text.mjs +31 -0
  347. package/src/runtime/shared/error-presentation.mjs +159 -19
  348. package/src/runtime/shared/file-permissions.d.mts +6 -1
  349. package/src/runtime/shared/file-permissions.mjs +54 -6
  350. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  351. package/src/runtime/shared/llm/anthropic-effort.mjs +129 -134
  352. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  353. package/src/runtime/shared/llm/model-catalog.mjs +53 -40
  354. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  355. package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
  356. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  357. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  358. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  359. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  360. package/src/runtime/shared/loopback-listener.mjs +9 -0
  361. package/src/runtime/shared/model-display.mjs +13 -0
  362. package/src/runtime/shared/provider-api-key.mjs +2 -0
  363. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  364. package/src/runtime/shared/review-diff.mjs +52 -0
  365. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  366. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  367. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  368. package/src/runtime/shared/slow-span.mjs +114 -0
  369. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  370. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  371. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  372. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  373. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  374. package/src/runtime/shared/tool-card-model.mjs +5 -2
  375. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  376. package/src/runtime/shared/tool-primitives.mjs +13 -0
  377. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  378. package/src/runtime/shared/tool-surface.mjs +18 -3
  379. package/src/runtime/shared/tool-work-units.mjs +40 -4
  380. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  381. package/src/runtime/shared/transcript-writer.mjs +10 -2
  382. package/src/runtime/shared/turn-changes.mjs +670 -274
  383. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  384. package/src/runtime/shared/turn-snapshot-store.mjs +94 -2
  385. package/src/runtime/shared/turn-snapshot.mjs +986 -690
  386. package/src/runtime/shared/turn-worktree-snapshot.mjs +279 -262
  387. package/src/runtime/shared/user-data-guard.mjs +262 -49
  388. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  389. package/src/runtime/shared/worktrees/contract.mjs +34 -24
  390. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  391. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  392. package/src/runtime/shared/worktrees/git.mjs +8 -3
  393. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  394. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  395. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  396. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  397. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  398. package/src/runtime/terminal-bridge/client.mjs +44 -11
  399. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  400. package/src/runtime/tidy/apply.mjs +47 -2
  401. package/src/runtime/tidy/run-engines.mjs +11 -5
  402. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  403. package/src/session-runtime/boot/apis.mjs +19 -5
  404. package/src/session-runtime/boot/facade.mjs +1 -0
  405. package/src/session-runtime/boot/infrastructure.mjs +12 -1
  406. package/src/session-runtime/boot/session.mjs +0 -1
  407. package/src/session-runtime/boot/tools.mjs +4 -1
  408. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  409. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  410. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  411. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  412. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  413. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  414. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  415. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  416. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -3
  417. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  418. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  419. package/src/session-runtime/lifecycle/teardown.mjs +73 -8
  420. package/src/session-runtime/lifecycle-api.mjs +1 -0
  421. package/src/session-runtime/mcp-glue/connect.mjs +46 -14
  422. package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
  423. package/src/session-runtime/mcp-glue.mjs +3 -0
  424. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  425. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  426. package/src/session-runtime/model-route/set-route.mjs +85 -17
  427. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  428. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  429. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  430. package/src/session-runtime/native-web-search.mjs +10 -1
  431. package/src/session-runtime/new-session-config.mjs +4 -2
  432. package/src/session-runtime/provider-auth/login.mjs +4 -0
  433. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  434. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  435. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  436. package/src/session-runtime/resource-extension-sync.mjs +4 -0
  437. package/src/session-runtime/resource-mcp-api.mjs +98 -2
  438. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  439. package/src/session-runtime/runtime-review-api.mjs +21 -2
  440. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  441. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  442. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  443. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  444. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  445. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  446. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  447. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  448. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  449. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  450. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  451. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  452. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  453. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  454. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  455. package/src/session-runtime/services/agent-tool.mjs +3 -0
  456. package/src/session-runtime/services/channel-admin.mjs +19 -3
  457. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  458. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  459. package/src/session-runtime/services/provider-admin.mjs +81 -15
  460. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  461. package/src/session-runtime/session-turn-api.mjs +14 -5
  462. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  463. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  464. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  465. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  466. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  467. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  468. package/src/session-runtime/skills-api.mjs +3 -5
  469. package/src/session-runtime/tool-surface.mjs +5 -1
  470. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  471. package/src/session-runtime/turn/session-ops.mjs +7 -5
  472. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  473. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  474. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  475. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  476. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  477. package/src/session-runtime/turn/turn-run.mjs +25 -9
  478. package/src/session-runtime/usage-stats-api.mjs +43 -11
  479. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  480. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  481. package/src/session-runtime/workspace/transitions.mjs +190 -15
  482. package/src/standalone/daemon-agent-control.mjs +9 -1
  483. package/src/standalone/daemon-drain-policy.mjs +8 -0
  484. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  485. package/src/standalone/daemon-owner.mjs +17 -4
  486. package/src/standalone/daemon-pg-stop.mjs +51 -0
  487. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  488. package/src/standalone/daemon-stop-budget.mjs +18 -0
  489. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  490. package/src/standalone/daemon-telemetry.mjs +74 -0
  491. package/src/standalone/daemon.mjs +64 -8
  492. package/src/standalone/desktop-service-registry.mjs +5 -0
  493. package/src/standalone/local-session-runtime.mjs +70 -19
  494. package/src/standalone/session-client.mjs +42 -0
  495. package/src/standalone/session-protocol.mjs +14 -0
  496. package/src/standalone/session-proxy/projection.mjs +41 -4
  497. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  498. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  499. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  500. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  501. package/src/standalone/session-service/agent-tree.mjs +12 -0
  502. package/src/standalone/session-service/quota-resume.mjs +8 -0
  503. package/src/standalone/session-service/retention/busy.mjs +15 -1
  504. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  505. package/src/standalone/session-service-api.mjs +11 -1
  506. package/src/standalone/session-service.mjs +1 -0
  507. package/src/standalone/session-state-patch.d.mts +2 -1
  508. package/src/standalone/session-state-patch.mjs +41 -1
  509. package/src/tui/App.jsx +25 -0
  510. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  511. package/src/tui/app/app-view.jsx +1 -0
  512. package/src/tui/app/btw-controller.mjs +118 -0
  513. package/src/tui/app/create-app-pickers.mjs +6 -0
  514. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  515. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  516. package/src/tui/app/extension-pickers.mjs +2 -1
  517. package/src/tui/app/model-options.mjs +11 -16
  518. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  519. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  520. package/src/tui/app/slash-commands.mjs +20 -5
  521. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  522. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  523. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  524. package/src/tui/app/use-global-key-input.mjs +5 -12
  525. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  526. package/src/tui/components/BtwPanel.jsx +53 -0
  527. package/src/tui/components/QuestionPanel.jsx +275 -0
  528. package/src/tui/components/StatusLine.jsx +2 -0
  529. package/src/tui/components/question-panel-model.mjs +156 -0
  530. package/src/tui/components/status-line/local-line.mjs +5 -2
  531. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  532. package/src/tui/dist/index.mjs +3221 -1500
  533. package/src/tui/session/context-state.mjs +4 -2
  534. package/src/tui/session/quota-resume.mjs +46 -14
  535. package/src/tui/session/session-api/commands.mjs +43 -24
  536. package/src/tui/session/session-api/controls.mjs +2 -2
  537. package/src/tui/session/session-api/extensions.mjs +22 -0
  538. package/src/tui/session/session-api/integrations.mjs +16 -2
  539. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  540. package/src/tui/session/session-api/lifecycle.mjs +7 -2
  541. package/src/tui/session/session-api/routes.mjs +71 -7
  542. package/src/tui/session/session-api/settings.mjs +11 -14
  543. package/src/tui/session/session-api.mjs +1 -0
  544. package/src/tui/session/session-flow/drain.mjs +7 -1
  545. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  546. package/src/tui/session/tool-approval.mjs +10 -7
  547. package/src/tui/session/transcript-spill.mjs +14 -7
  548. package/src/tui/session/turn-stream.mjs +174 -31
  549. package/src/tui/session/turn.mjs +46 -16
  550. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  551. package/src/ui/statusline-agents.mjs +15 -4
  552. package/src/ui/statusline.mjs +7 -3
  553. package/src/ui/streaming-markdown-heal.mjs +34 -3
  554. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  555. package/src/workflows/default/WORKFLOW.md +3 -1
  556. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  557. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
@@ -0,0 +1,103 @@
1
+ import { timingSafeEqual } from 'node:crypto';
2
+ import { OAUTH_PAGE_CONTENT_TYPE, oauthErrorHtml, oauthSuccessHtml } from '../providers/lib/oauth-page.mjs';
3
+
4
+ /** Upper bound of a pasted redirect URL (`completeMcpLogin`). */
5
+ export const MCP_OAUTH_CALLBACK_MAX_BYTES = 64 * 1024;
6
+ // Fixed messages: a pasted URL carries the authorization code and is never
7
+ // echoed into an error, a log or a status.
8
+ const INVALID_CALLBACK_PASTE =
9
+ 'The pasted sign-in URL is invalid. Paste the full address the browser was redirected to after signing in.';
10
+ export const MCP_OAUTH_CALLBACK_NOT_PENDING = 'MCP OAuth sign-in is not waiting for a callback';
11
+ const sameState = (supplied, expected) => {
12
+ const a = Buffer.from(supplied || '');
13
+ const b = Buffer.from(expected || '');
14
+ return b.length > 0 && a.length === b.length && timingSafeEqual(a, b);
15
+ };
16
+
17
+ /**
18
+ * The outcome of a redirect URL pasted from the browser for a pending login
19
+ * whose redirect URI is `redirectUrl` and state `state`: `{ code }`, or
20
+ * `{ denied: true }` for an authorization error. Throws a fixed message (never
21
+ * the pasted text) for anything that is not exactly that login's callback.
22
+ */
23
+ export function parsePastedMcpOAuthCallback(pasted, { redirectUrl, state }) {
24
+ const invalid = () => new Error(INVALID_CALLBACK_PASTE);
25
+ if (typeof pasted !== 'string' || Buffer.byteLength(pasted) > MCP_OAUTH_CALLBACK_MAX_BYTES) throw invalid();
26
+ const raw = pasted.trim();
27
+ let target;
28
+ let expected;
29
+ try {
30
+ target = new URL(raw);
31
+ expected = new URL(redirectUrl);
32
+ } catch {
33
+ throw invalid();
34
+ }
35
+ if (
36
+ raw.includes('#') ||
37
+ target.username ||
38
+ target.password ||
39
+ target.origin !== expected.origin ||
40
+ target.pathname !== expected.pathname
41
+ )
42
+ throw invalid();
43
+ const seen = new Set();
44
+ for (const [key, value] of target.searchParams) {
45
+ if (seen.has(key)) throw invalid();
46
+ seen.add(key);
47
+ // The redirect URI's own parameters come back unchanged; any others an
48
+ // authorization server adds (e.g. Google's scope/authuser/prompt) are
49
+ // ignored, as in Codex's parse_callback_url.
50
+ if (expected.searchParams.has(key) && expected.searchParams.get(key) !== value) throw invalid();
51
+ }
52
+ for (const key of expected.searchParams.keys()) if (!seen.has(key)) throw invalid();
53
+ if (!sameState(target.searchParams.get('state'), state)) throw invalid();
54
+ if (seen.has('code') === seen.has('error')) throw invalid();
55
+ if (seen.has('error')) return { denied: true };
56
+ const code = target.searchParams.get('code');
57
+ if (!code) throw invalid();
58
+ return { code };
59
+ }
60
+
61
+ // Answers a request to the loopback redirect URI: only a GET of the exact
62
+ // callback path and host, carrying exactly the pending state (compared in
63
+ // constant time) and either one code or an error, finishes the login.
64
+ export function answerOAuthCallback(req, res, { redirectUrl, state, finish, serverName }) {
65
+ const page = (status, html) => {
66
+ res.writeHead(status, {
67
+ 'Content-Type': OAUTH_PAGE_CONTENT_TYPE,
68
+ 'Cache-Control': 'no-store',
69
+ 'Referrer-Policy': 'no-referrer',
70
+ });
71
+ res.end(html);
72
+ };
73
+ const invalid = () =>
74
+ page(400, oauthErrorHtml('This sign-in link is invalid or has expired.', 'Start the sign-in again from Mixdog.'));
75
+ let target;
76
+ try {
77
+ target = new URL(req.url || '/', redirectUrl);
78
+ } catch {
79
+ invalid();
80
+ return;
81
+ }
82
+ // `finish` is absent once the login was answered (e.g. by a pasted URL).
83
+ const valid =
84
+ typeof finish === 'function' &&
85
+ req.method === 'GET' &&
86
+ req.headers.host === new URL(redirectUrl).host &&
87
+ target.pathname === '/oauth/callback' &&
88
+ target.searchParams.getAll('state').length === 1 &&
89
+ sameState(target.searchParams.get('state'), state);
90
+ if (!valid || (!target.searchParams.has('error') && target.searchParams.getAll('code').length !== 1)) {
91
+ invalid();
92
+ return;
93
+ }
94
+ const server = serverName || 'the MCP server';
95
+ if (target.searchParams.has('error')) {
96
+ // The authorization server's error text is never reflected.
97
+ page(400, oauthErrorHtml(`Sign-in to ${server} was not completed.`, 'Start the sign-in again from Mixdog.'));
98
+ finish(undefined);
99
+ return;
100
+ }
101
+ page(200, oauthSuccessHtml(server));
102
+ finish(target.searchParams.get('code'));
103
+ }
@@ -0,0 +1,150 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { withFileLock, writeFileAtomicAsync } from '../../../shared/atomic-file.mjs';
5
+ import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
6
+ import { reauthenticationRequired } from './oauth-shared.mjs';
7
+
8
+ // Same lease as the provider OAuth refresh (openai-oauth-tokens.mjs): a
9
+ // refresh is a network exchange plus keychain persistence. The lock file
10
+ // itself holds no credential (they live in the keychain).
11
+ const CREDENTIAL_LOCK_OPTIONS = Object.freeze({ timeoutMs: 120_000, staleMs: 120_000 });
12
+
13
+ export const credentialFile = (account, extension) => join(resolvePluginData(), 'mcp-oauth', `${account}.${extension}`);
14
+
15
+ // Serializes refreshes, logins and log-outs of one account across processes.
16
+ export function withCredentialLock(account, fn) {
17
+ return withFileLock(credentialFile(account, 'lock'), fn, CREDENTIAL_LOCK_OPTIONS);
18
+ }
19
+
20
+ // A non-secret marker of the stored entry's revision ('deleted' when there is
21
+ // none) and whether the entry holds tokens (a client registration alone does
22
+ // not sign in), written under the credential lock before every change of the
23
+ // entry. Every process compares it before sending a cached token, so a refresh
24
+ // or log-out elsewhere is observed without reading the keychain per request.
25
+ export const TOKENS_FLAG = '+tokens';
26
+ const markerOf = (revision, hasTokens) => {
27
+ if (revision === null) return 'deleted';
28
+ return hasTokens ? `${revision}${TOKENS_FLAG}` : revision;
29
+ };
30
+ export function writeRevisionMarker(account, revision, hasTokens = false) {
31
+ return writeFileAtomicAsync(credentialFile(account, 'rev'), markerOf(revision, hasTokens));
32
+ }
33
+ // Missing or unreadable (null) never matches: the caller re-reads the keychain.
34
+ function readRevisionMarker(account) {
35
+ return readFile(credentialFile(account, 'rev'), 'utf8').catch(() => null);
36
+ }
37
+
38
+ // The stored value as another process may have just written it, not this
39
+ // process's cached copy.
40
+ export function readStoredCredentials(credentialStore, account) {
41
+ credentialStore.invalidateSecretCache?.(account);
42
+ return credentialStore.getSecretAsync(account);
43
+ }
44
+
45
+ // Every write stores a fresh random revision; null stands for no entry.
46
+ const revisionOf = (saved) => (saved ? (JSON.parse(saved).revision ?? '') : null);
47
+
48
+ /**
49
+ * One connection's view of its stored OAuth entry. `creds` holds the cached
50
+ * values: `observed` is the revision of the stored entry this process last
51
+ * read or wrote (null: there was none, undefined: unknown after a log-out
52
+ * here); `loaded` is the pending or completed first read.
53
+ */
54
+ export function createCredentialCache({ url, credentialStore, account }) {
55
+ const creds = {
56
+ tokens: undefined,
57
+ clientInformation: undefined,
58
+ redirectUrl: undefined,
59
+ discovery: undefined,
60
+ observed: undefined,
61
+ loaded: undefined,
62
+ };
63
+ // Every token this connection held: an error or result echoing the token a
64
+ // request was sent with stays scrubbed after a refresh or log-out.
65
+ const held = new Set();
66
+ const hold = () => {
67
+ for (const token of [creds.tokens?.access_token, creds.tokens?.refresh_token]) if (token) held.add(token);
68
+ };
69
+ const adopt = (saved) => {
70
+ const value = saved ? JSON.parse(saved) : {};
71
+ if (saved && value.url !== url) throw reauthenticationRequired();
72
+ ({
73
+ tokens: creds.tokens,
74
+ clientInformation: creds.clientInformation,
75
+ redirectUrl: creds.redirectUrl,
76
+ discovery: creds.discovery,
77
+ } = value);
78
+ creds.observed = revisionOf(saved);
79
+ hold();
80
+ };
81
+ // A failed read (locked keychain) is retried by the next request.
82
+ const ready = () =>
83
+ (creds.loaded ??= credentialStore
84
+ .getSecretAsync(account)
85
+ .then(adopt)
86
+ .catch((error) => {
87
+ creds.loaded = undefined;
88
+ throw error;
89
+ }));
90
+ // The only writer of the stored entry. Under the cross-process lock it
91
+ // changes the entry only while it still has the revision the caller observed
92
+ // (by default the one this process last read or wrote); otherwise it adopts
93
+ // the newer entry, or its deletion by a log-out, and changes nothing.
94
+ function update(change, { expected = creds.observed, remove = false } = {}) {
95
+ return withCredentialLock(account, async () => {
96
+ const saved = await readStoredCredentials(credentialStore, account);
97
+ if (revisionOf(saved) !== expected) {
98
+ adopt(saved);
99
+ return false;
100
+ }
101
+ change();
102
+ hold();
103
+ if (remove) {
104
+ await writeRevisionMarker(account, null);
105
+ if (saved) await credentialStore.deleteSecretAsync(account);
106
+ creds.observed = null;
107
+ } else {
108
+ const revision = randomBytes(16).toString('hex');
109
+ await writeRevisionMarker(account, revision, Boolean(creds.tokens));
110
+ const { tokens, clientInformation, redirectUrl, discovery } = creds;
111
+ await credentialStore.setSecretAsync(
112
+ account,
113
+ JSON.stringify({ url, revision, tokens, clientInformation, redirectUrl, discovery })
114
+ );
115
+ creds.observed = revision;
116
+ }
117
+ return true;
118
+ });
119
+ }
120
+ // Before a cached token is sent: adopt a change another process made (a
121
+ // refresh, a log-out) since this process last read or wrote the entry.
122
+ async function observeStoredRevision() {
123
+ if ((await readRevisionMarker(account)) === markerOf(creds.observed, Boolean(creds.tokens))) return;
124
+ await withCredentialLock(account, async () => {
125
+ adopt(await readStoredCredentials(credentialStore, account));
126
+ // Repair a missing or stale marker (an entry stored before markers or
127
+ // the tokens flag existed, an interrupted change) so the next request
128
+ // reads no keychain.
129
+ if ((await readRevisionMarker(account)) !== markerOf(creds.observed, Boolean(creds.tokens))) {
130
+ await writeRevisionMarker(account, creds.observed, Boolean(creds.tokens));
131
+ }
132
+ });
133
+ }
134
+ // Forget the in-memory credentials; the next request re-reads the keychain.
135
+ function clear() {
136
+ creds.tokens = creds.clientInformation = creds.redirectUrl = creds.discovery = undefined;
137
+ creds.loaded = creds.observed = undefined;
138
+ }
139
+ return {
140
+ creds,
141
+ held,
142
+ ready,
143
+ adopt,
144
+ update,
145
+ observeStoredRevision,
146
+ clear,
147
+ readStored: () => readStoredCredentials(credentialStore, account),
148
+ withLock: (fn) => withCredentialLock(account, fn),
149
+ };
150
+ }
@@ -0,0 +1,111 @@
1
+ import { createServer } from 'node:http';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { normalizeMcpTransportUrl } from './client-config.mjs';
4
+ import { answerOAuthCallback } from './oauth-callback.mjs';
5
+ import { anySignal, loginFailed, reauthenticationRequired } from './oauth-shared.mjs';
6
+
7
+ /**
8
+ * The browser login of one connection over its credential `cache` and login
9
+ * `flow` state: the loopback callback listener and the SDK authorization
10
+ * (refresh, registration, redirect and code exchange).
11
+ */
12
+ export function createLoginFlow({ url, cache, flow, provider, lifetimeSignal, openBrowser, fetchImpl, timeoutMs }) {
13
+ const { creds } = cache;
14
+ function stopListening() {
15
+ flow.listener?.close();
16
+ flow.listener?.closeAllConnections();
17
+ flow.listener = undefined;
18
+ }
19
+ function closeListener() {
20
+ stopListening();
21
+ flow.verifier = undefined;
22
+ flow.state = undefined;
23
+ }
24
+ // The login's callback is answered once, by the loopback or a paste.
25
+ function answerCallback(code) {
26
+ const finish = flow.finishCallback;
27
+ flow.finishCallback = undefined;
28
+ finish?.(code);
29
+ }
30
+ async function listenForCallback(serverName) {
31
+ flow.listener = createServer((req, res) =>
32
+ answerOAuthCallback(req, res, {
33
+ redirectUrl: creds.redirectUrl,
34
+ state: flow.state,
35
+ finish: flow.finishCallback && answerCallback,
36
+ serverName,
37
+ })
38
+ );
39
+ await new Promise((resolve, reject) => {
40
+ flow.listener.once('error', reject);
41
+ // Retain the registered redirect URI if this connection later needs
42
+ // refresh or reauthorization; never register a different callback.
43
+ flow.listener.listen(creds.redirectUrl ? Number(new URL(creds.redirectUrl).port) : 0, '127.0.0.1', resolve);
44
+ });
45
+ creds.redirectUrl = `http://127.0.0.1:${flow.listener.address().port}/oauth/callback`;
46
+ }
47
+ async function login(challenge, requestSignal, interactive, browser, attemptedToken, serverName) {
48
+ flow.allowBrowser = interactive;
49
+ flow.browserOpened = false;
50
+ flow.launchBrowser = browser || openBrowser;
51
+ const signal = AbortSignal.any([lifetimeSignal, requestSignal, AbortSignal.timeout(timeoutMs)]);
52
+ const safeFetch = (input, init = {}) =>
53
+ fetchImpl(normalizeMcpTransportUrl(input), {
54
+ ...init,
55
+ redirect: 'error',
56
+ signal: anySignal(signal, init.signal),
57
+ });
58
+ let abort;
59
+ const callback = new Promise((resolve, reject) => {
60
+ const finish = (code) => (code ? resolve(code) : reject(loginFailed()));
61
+ flow.finishCallback = finish;
62
+ abort = () => finish();
63
+ signal.addEventListener('abort', abort, { once: true });
64
+ });
65
+ // A discovery/registration error can precede waiting for the callback.
66
+ callback.catch(() => {});
67
+ try {
68
+ signal.throwIfAborted();
69
+ const { auth } = await import('@modelcontextprotocol/sdk/client/auth.js');
70
+ const options = { serverUrl: url, ...challenge, fetchFn: safeFetch };
71
+ // Another process sharing this keychain entry may already have
72
+ // refreshed (and rotated) the tokens this request was rejected with:
73
+ // decide on the stored credentials, under the cross-process lock.
74
+ const result = await cache.withLock(async () => {
75
+ cache.adopt(await cache.readStored());
76
+ if (creds.tokens && creds.tokens.access_token !== attemptedToken?.access_token) return 'AUTHORIZED';
77
+ if (!interactive && (!creds.tokens?.refresh_token || !creds.clientInformation || !creds.redirectUrl)) {
78
+ throw reauthenticationRequired();
79
+ }
80
+ flow.state = randomBytes(32).toString('base64url');
81
+ if (interactive) await listenForCallback(serverName);
82
+ signal.throwIfAborted();
83
+ return auth(provider, options);
84
+ });
85
+ if (result === 'REDIRECT') {
86
+ const code = await callback;
87
+ signal.throwIfAborted();
88
+ const exchanged = await cache.withLock(async () => {
89
+ // The entry this login registered in is gone only after a log-out
90
+ // since it began, which wins: never recreate it.
91
+ const saved = await cache.readStored();
92
+ if (!saved) throw loginFailed();
93
+ cache.adopt(saved);
94
+ return auth(provider, { ...options, authorizationCode: code });
95
+ });
96
+ if (exchanged !== 'AUTHORIZED') throw loginFailed();
97
+ }
98
+ signal.throwIfAborted();
99
+ } catch (error) {
100
+ // SDK errors may contain authorization server response bodies/secrets.
101
+ if (error?.code === 'reauthenticationRequired' || !interactive) throw reauthenticationRequired();
102
+ throw loginFailed();
103
+ } finally {
104
+ flow.allowBrowser = false;
105
+ signal.removeEventListener('abort', abort);
106
+ closeListener();
107
+ flow.finishCallback = undefined;
108
+ }
109
+ }
110
+ return { login, stopListening, closeListener, answerCallback };
111
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * mcp/oauth-probe.mjs — what authentication an HTTP/SSE server (being added or
3
+ * edited) needs, decided without credentials and without a browser: one
4
+ * anonymous request, then OAuth discovery (RFC 9728 protected-resource
5
+ * metadata, RFC 8414 / OIDC authorization-server metadata) on a 401.
6
+ */
7
+ import {
8
+ normalizeMcpTransportUrl,
9
+ resolveMcpOAuthUrl,
10
+ resolveMcpTransportKind,
11
+ scrubMcpConnectionMessage,
12
+ } from './client-config.mjs';
13
+ import { hasConfiguredMcpAuth } from './client-transport.mjs';
14
+
15
+ export const MCP_AUTH_PROBE_TIMEOUT_MS = 10_000;
16
+
17
+ const INITIALIZE_PROBE = JSON.stringify({
18
+ jsonrpc: '2.0',
19
+ id: 0,
20
+ method: 'initialize',
21
+ params: {
22
+ protocolVersion: '2025-03-26',
23
+ capabilities: {},
24
+ clientInfo: { name: 'mixdog-auth-probe', version: '1' },
25
+ },
26
+ });
27
+
28
+ /**
29
+ * `cfg = { type: 'http' | 'sse', url, headers?, bearer_token_env_var?, env_http_headers? }`
30
+ * → `{ auth: 'oauth' | 'none' | 'configured' | 'unreachable', error? }`.
31
+ * Configured authentication is authoritative and never sent anywhere.
32
+ */
33
+ export async function probeMcpServerAuth(cfg, { fetchImpl = fetch, timeoutMs = MCP_AUTH_PROBE_TIMEOUT_MS } = {}) {
34
+ if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg)) throw new TypeError('MCP server config is required');
35
+ const kind = resolveMcpTransportKind(cfg);
36
+ if (kind !== 'http' && kind !== 'sse') throw new Error('MCP auth probe needs an http or sse server');
37
+ const url = resolveMcpOAuthUrl(cfg);
38
+ if (hasConfiguredMcpAuth(cfg)) return { auth: 'configured' };
39
+ const deadline = AbortSignal.timeout(timeoutMs);
40
+ // Same policy as the OAuth login's own requests: normalized URLs, no
41
+ // redirects, and nothing outlives the probe's budget.
42
+ const probeFetch = (input, init = {}) =>
43
+ fetchImpl(normalizeMcpTransportUrl(String(input)), {
44
+ ...init,
45
+ redirect: 'error',
46
+ signal: init.signal ? AbortSignal.any([deadline, init.signal]) : deadline,
47
+ });
48
+ const failure = (error) => ({
49
+ auth: 'unreachable',
50
+ error: scrubMcpConnectionMessage(
51
+ deadline.aborted ? `no answer within ${timeoutMs}ms` : String(error?.cause?.message || error?.message || error),
52
+ cfg
53
+ ),
54
+ });
55
+ let response;
56
+ try {
57
+ response =
58
+ kind === 'http'
59
+ ? await probeFetch(url, {
60
+ method: 'POST',
61
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream' },
62
+ body: INITIALIZE_PROBE,
63
+ })
64
+ : await probeFetch(url, { headers: { Accept: 'text/event-stream' } });
65
+ } catch (error) {
66
+ return failure(error);
67
+ }
68
+ // The body is discarded; a failed cancel changes nothing.
69
+ await response.body?.cancel().catch(() => {});
70
+ if (response.status === 404 || response.status >= 500)
71
+ return { auth: 'unreachable', error: `HTTP ${response.status}` };
72
+ if (response.status !== 401) return { auth: 'none' };
73
+ try {
74
+ const { discoverOAuthServerInfo, extractWWWAuthenticateParams } = await import(
75
+ '@modelcontextprotocol/sdk/client/auth.js'
76
+ );
77
+ const { resourceMetadataUrl } = extractWWWAuthenticateParams(response);
78
+ const { authorizationServerMetadata } = await discoverOAuthServerInfo(url, {
79
+ resourceMetadataUrl,
80
+ fetchFn: probeFetch,
81
+ });
82
+ if (authorizationServerMetadata) return { auth: 'oauth' };
83
+ } catch (error) {
84
+ if (deadline.aborted) return failure(error);
85
+ }
86
+ return { auth: 'none', error: 'HTTP 401: the server requires authentication but offers no OAuth sign-in' };
87
+ }
@@ -0,0 +1,95 @@
1
+ import { normalizeMcpTransportUrl } from './client-config.mjs';
2
+ import { loginFailed, reauthenticationRequired } from './oauth-shared.mjs';
3
+
4
+ /**
5
+ * The SDK's OAuth client provider over one connection's credential `cache`
6
+ * and login `flow` state.
7
+ */
8
+ export function createOAuthProvider({ cache, flow, lifetimeSignal }) {
9
+ const { creds, ready, update } = cache;
10
+ // The SDK saves only inside a login, which holds the lock and has just
11
+ // adopted the stored entry.
12
+ const save = async (change) => {
13
+ if (!(await update(change))) throw loginFailed();
14
+ };
15
+ return {
16
+ get redirectUrl() {
17
+ return creds.redirectUrl;
18
+ },
19
+ get clientMetadata() {
20
+ return {
21
+ client_name: 'Mixdog',
22
+ redirect_uris: [creds.redirectUrl],
23
+ grant_types: ['authorization_code', 'refresh_token'],
24
+ response_types: ['code'],
25
+ token_endpoint_auth_method: 'none',
26
+ };
27
+ },
28
+ state: () => flow.state,
29
+ clientInformation: async () => {
30
+ await ready();
31
+ // A connect that cannot open a sign-in never registers a client (the
32
+ // SDK registers one after dropping a rejected registration): the next
33
+ // explicit sign-in registers it.
34
+ if (!creds.clientInformation && !flow.allowBrowser) throw reauthenticationRequired();
35
+ return creds.clientInformation;
36
+ },
37
+ saveClientInformation: (value) =>
38
+ save(() => {
39
+ creds.clientInformation = value;
40
+ }),
41
+ tokens: async () => {
42
+ await ready();
43
+ return creds.tokens;
44
+ },
45
+ saveTokens: async (value) => {
46
+ lifetimeSignal.throwIfAborted();
47
+ await save(() => {
48
+ creds.tokens = value;
49
+ });
50
+ },
51
+ saveCodeVerifier: (value) => {
52
+ flow.verifier = value;
53
+ },
54
+ codeVerifier: () => {
55
+ if (!flow.verifier) throw loginFailed();
56
+ return flow.verifier;
57
+ },
58
+ discoveryState: () => creds.discovery,
59
+ saveDiscoveryState: (value) =>
60
+ save(() => {
61
+ creds.discovery = value;
62
+ }),
63
+ // The SDK invalidates 'all' when the token endpoint rejects the client
64
+ // (invalid_client / unauthorized_client) and 'tokens' when it rejects the
65
+ // grant (invalid_grant). The registration goes in both cases: an
66
+ // authorization server that forgot its clients (a restart) may answer the
67
+ // refresh with invalid_grant, and reusing that registration would fail
68
+ // every later sign-in at the authorization endpoint; re-registering is cheap.
69
+ invalidateCredentials: async (scope) => {
70
+ if (scope === 'all' || scope === 'verifier') flow.verifier = undefined;
71
+ if (scope === 'discovery') creds.discovery = undefined;
72
+ if (scope !== 'all' && scope !== 'tokens' && scope !== 'client') return;
73
+ await update(
74
+ () => {
75
+ if (scope !== 'client') creds.tokens = undefined;
76
+ creds.clientInformation = undefined;
77
+ if (scope === 'all') creds.discovery = undefined;
78
+ },
79
+ { remove: scope === 'all' }
80
+ );
81
+ },
82
+ redirectToAuthorization: async (authorizationUrl) => {
83
+ if (!flow.allowBrowser || flow.browserOpened) throw reauthenticationRequired();
84
+ const target = new URL(normalizeMcpTransportUrl(authorizationUrl));
85
+ if (
86
+ target.searchParams.get('state') !== flow.state ||
87
+ target.searchParams.get('code_challenge_method') !== 'S256' ||
88
+ !flow.verifier
89
+ )
90
+ throw loginFailed();
91
+ flow.browserOpened = true;
92
+ await flow.launchBrowser(target.href);
93
+ },
94
+ };
95
+ }
@@ -0,0 +1,122 @@
1
+ import { anySignal, loginFailed, reauthenticationRequired } from './oauth-shared.mjs';
2
+
3
+ /**
4
+ * The authenticated `fetch` of one server over its credential `cache`: it
5
+ * sends the cached token to the server's own endpoints and, on a challenge,
6
+ * joins the single login in flight (starting it with `login` when none is).
7
+ */
8
+ export function createAuthorizedFetch({ url, cache, login, fetchImpl, lifetimeSignal }) {
9
+ const { creds } = cache;
10
+ let flight;
11
+ let waiters = 0;
12
+ let flightController;
13
+ // An unreadable (e.g. locked) keychain must not block a server that needs no
14
+ // authentication; credentials are required only on challenge.
15
+ const storeReadable = () =>
16
+ cache.ready().then(
17
+ () => true,
18
+ () => false
19
+ );
20
+ // One request, carrying the cached token only to the server's own endpoints.
21
+ // Reports the revision and token it was sent with, and whether the cached
22
+ // token could not be verified.
23
+ async function sendRequest(input, options, signal, sameOrigin) {
24
+ let unreadable = false;
25
+ if (creds.tokens && sameOrigin) {
26
+ try {
27
+ await cache.observeStoredRevision();
28
+ } catch {
29
+ // Unverifiable (e.g. locked keychain): never send the cached token.
30
+ creds.tokens = creds.loaded = undefined;
31
+ unreadable = true;
32
+ }
33
+ }
34
+ signal.throwIfAborted();
35
+ const { observed: revision, tokens } = creds;
36
+ const headers = new Headers(options.headers);
37
+ if (tokens && sameOrigin) headers.set('Authorization', `Bearer ${tokens.access_token}`);
38
+ else headers.delete('Authorization');
39
+ const response = await fetchImpl(input, { ...options, headers, signal, redirect: 'error' });
40
+ return { response, revision, tokens, unreadable };
41
+ }
42
+ function startLogin(challenge, requestLogin, browser, attemptedToken, serverName) {
43
+ flightController = new AbortController();
44
+ flight = login(challenge, flightController.signal, requestLogin(), browser, attemptedToken, serverName).finally(
45
+ () => {
46
+ flight = undefined;
47
+ }
48
+ );
49
+ }
50
+ // Wait for the login in flight; the last waiter to leave cancels it.
51
+ async function awaitLogin(signal, onAuthorized) {
52
+ waiters++;
53
+ let abort;
54
+ try {
55
+ await Promise.race([
56
+ flight,
57
+ new Promise((_, reject) => {
58
+ abort = () => reject(loginFailed());
59
+ signal.addEventListener('abort', abort, { once: true });
60
+ if (signal.aborted) abort();
61
+ }),
62
+ ]);
63
+ // The login settled with tokens stored, before the retry is sent.
64
+ onAuthorized();
65
+ } finally {
66
+ signal.removeEventListener('abort', abort);
67
+ if (--waiters === 0 && flight) {
68
+ flightController.abort();
69
+ // The cancelled login's own failure is not this request's.
70
+ await flight.catch(() => {});
71
+ }
72
+ }
73
+ }
74
+ async function authorizedFetch(
75
+ input,
76
+ options = {},
77
+ requestLogin = () => false,
78
+ browser,
79
+ isTransportEndpoint = () => false,
80
+ serverName = undefined,
81
+ onAuthorized = () => {}
82
+ ) {
83
+ let storeUnreadable = !(await storeReadable());
84
+ lifetimeSignal.throwIfAborted();
85
+ const signal = anySignal(lifetimeSignal, options.signal);
86
+ const target = new URL(input);
87
+ const server = new URL(url);
88
+ const sameOrigin = target.href === server.href || (target.origin === server.origin && isTransportEndpoint(input));
89
+ const sent = await sendRequest(input, options, signal, sameOrigin);
90
+ const { response } = sent;
91
+ const attemptedToken = sent.tokens;
92
+ storeUnreadable ||= sent.unreadable;
93
+ if (!sameOrigin || ![401, 403].includes(response.status)) return response;
94
+ const { extractWWWAuthenticateParams } = await import('@modelcontextprotocol/sdk/client/auth.js');
95
+ const challenge = extractWWWAuthenticateParams(response);
96
+ if (response.status === 403 && challenge.error !== 'insufficient_scope') return response;
97
+ await response.body?.cancel();
98
+ if (storeUnreadable) throw reauthenticationRequired();
99
+ if (!flight && creds.tokens === attemptedToken) {
100
+ startLogin(challenge, requestLogin, browser, attemptedToken, serverName);
101
+ }
102
+ if (flight) await awaitLogin(signal, onAuthorized);
103
+ lifetimeSignal.throwIfAborted();
104
+ signal.throwIfAborted();
105
+ const retry = await sendRequest(input, options, signal, sameOrigin);
106
+ const retried = retry.response;
107
+ if (retried.status === 401 || retried.status === 403) {
108
+ await retried.body?.cancel();
109
+ // Forget the rejected tokens unless a refresh (here or in another
110
+ // process) or a log-out replaced them since this retry was sent.
111
+ await cache.update(
112
+ () => {
113
+ creds.tokens = undefined;
114
+ },
115
+ { expected: retry.revision }
116
+ );
117
+ throw reauthenticationRequired();
118
+ }
119
+ return retried;
120
+ }
121
+ return { fetch: authorizedFetch, cancelLogin: () => flightController?.abort() };
122
+ }
@@ -0,0 +1,9 @@
1
+ export const REAUTHENTICATION_REQUIRED_MESSAGE = 'MCP OAuth reauthenticationRequired: sign in to connect';
2
+ export const reauthenticationRequired = () =>
3
+ Object.assign(new Error(REAUTHENTICATION_REQUIRED_MESSAGE), {
4
+ code: 'reauthenticationRequired',
5
+ });
6
+ export const loginFailed = () => new Error('MCP OAuth login failed, cancelled, or timed out');
7
+
8
+ // `signal`, also aborted by the caller's optional `other` signal.
9
+ export const anySignal = (signal, other) => AbortSignal.any(other ? [signal, other] : [signal]);