mixdog 1.2.0 → 1.2.2

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 (386) hide show
  1. package/README.md +7 -5
  2. package/package.json +1 -1
  3. package/scripts/gate-local.mjs +19 -3
  4. package/scripts/model-catalog-audit.mjs +1 -15
  5. package/src/agents/reviewer/AGENT.md +3 -3
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +5 -0
  8. package/src/defaults/skills/pptx/SKILL.md +6 -6
  9. package/src/defaults/skills/pptx/references/review.md +6 -4
  10. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  11. package/src/defaults/skills/setup/references/actions.md +13 -3
  12. package/src/defaults/skills/visualize/SKILL.md +12 -9
  13. package/src/repl/slash-commands.mjs +16 -1
  14. package/src/rules/shared/60-verification.md +3 -1
  15. package/src/rules/shared/70-delivery.md +4 -2
  16. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  17. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  18. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  19. package/src/runtime/agent/orchestrator/config.mjs +10 -4
  20. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  21. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  22. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  23. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +122 -15
  24. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +54 -11
  25. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +104 -9
  26. package/src/runtime/agent/orchestrator/mcp/client.mjs +358 -140
  27. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  28. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  29. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  30. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  31. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  32. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  33. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  34. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  35. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  36. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  37. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +294 -44
  38. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  40. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -0
  41. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  42. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +25 -21
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
  45. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  46. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  49. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  50. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  51. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  52. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +73 -35
  53. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  54. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  55. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -1
  56. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +53 -18
  57. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  58. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  59. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +30 -14
  60. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
  61. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
  62. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
  63. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  64. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  65. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  66. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +447 -0
  67. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
  68. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  70. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +102 -23
  71. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
  73. package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
  74. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  75. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +86 -19
  76. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
  78. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -0
  79. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  80. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  81. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  82. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  83. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +21 -82
  84. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
  85. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
  86. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
  87. package/src/runtime/agent/orchestrator/providers/gemini.mjs +97 -51
  88. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  89. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
  90. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  91. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  92. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  93. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  94. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +15 -0
  95. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +35 -0
  96. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  97. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -0
  98. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +5 -3
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  103. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
  109. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  110. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  111. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
  113. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +6 -3
  114. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
  115. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +26 -6
  116. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
  117. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  119. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +18 -11
  120. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +2 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  125. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +34 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
  127. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  128. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
  129. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
  130. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  132. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  133. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  135. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +35 -24
  136. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
  137. package/src/runtime/agent/orchestrator/providers/registry.mjs +35 -9
  138. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  139. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -219
  140. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  141. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  142. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  143. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +3 -0
  145. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  146. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  147. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  148. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  149. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  150. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +268 -181
  151. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  152. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  154. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  155. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +270 -0
  156. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  157. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  158. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +6 -2
  159. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  160. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  161. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  162. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  163. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  164. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  165. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  166. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  167. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  168. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +3 -2
  169. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -4
  170. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +4 -3
  171. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
  172. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  173. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
  174. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +42 -5
  175. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +14 -3
  176. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +20 -12
  177. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +50 -23
  178. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +4 -0
  179. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +22 -14
  180. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  182. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  183. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +4 -3
  184. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
  185. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  186. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  187. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  188. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  189. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  190. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +819 -0
  191. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +12 -0
  192. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +22 -12
  193. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  194. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +183 -374
  195. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  196. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  197. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +41 -3
  198. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +38 -17
  199. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  200. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  201. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  202. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  203. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  205. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  206. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +173 -22
  207. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  208. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +5 -4
  209. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +15 -2
  210. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  211. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  212. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  213. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  214. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  215. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  216. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  217. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  218. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  219. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  220. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  221. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  222. package/src/runtime/attachments/store.d.mts +3 -0
  223. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  224. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  225. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  226. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  227. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  228. package/src/runtime/memory/lib/memory.mjs +1 -1
  229. package/src/runtime/memory/lib/pg/process.mjs +34 -24
  230. package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
  231. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  232. package/src/runtime/office/README.md +1 -1
  233. package/src/runtime/office/shared/values.mjs +0 -5
  234. package/src/runtime/shared/atomic-file.mjs +25 -4
  235. package/src/runtime/shared/background-tasks.mjs +92 -14
  236. package/src/runtime/shared/err-text.mjs +25 -0
  237. package/src/runtime/shared/error-presentation.mjs +159 -19
  238. package/src/runtime/shared/extension-scopes.mjs +5 -3
  239. package/src/runtime/shared/file-lock.mjs +37 -11
  240. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  241. package/src/runtime/shared/llm/anthropic-effort.mjs +134 -131
  242. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  243. package/src/runtime/shared/llm/model-catalog.mjs +53 -52
  244. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  245. package/src/runtime/shared/llm/provider-catalog-registry.mjs +17 -0
  246. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  247. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  248. package/src/runtime/shared/model-display.mjs +13 -0
  249. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  250. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  251. package/src/runtime/shared/pristine-execution.mjs +28 -4
  252. package/src/runtime/shared/provider-accounts.mjs +124 -20
  253. package/src/runtime/shared/provider-api-key.mjs +12 -0
  254. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  255. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  256. package/src/runtime/shared/shell-display.mjs +9 -3
  257. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  258. package/src/runtime/shared/skill-mentions.mjs +133 -0
  259. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  260. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  261. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  262. package/src/runtime/shared/tool-primitives.mjs +35 -2
  263. package/src/runtime/shared/turn-changes.mjs +447 -219
  264. package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
  265. package/src/runtime/shared/turn-snapshot.mjs +170 -48
  266. package/src/runtime/shared/turn-worktree-snapshot.mjs +160 -51
  267. package/src/runtime/shared/worker-requests.mjs +15 -2
  268. package/src/runtime/shared/worktrees/contract.mjs +0 -24
  269. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  270. package/src/session-runtime/automation-agents.mjs +8 -2
  271. package/src/session-runtime/boot/apis.mjs +61 -0
  272. package/src/session-runtime/boot/config.mjs +5 -2
  273. package/src/session-runtime/boot/facade.mjs +1 -0
  274. package/src/session-runtime/boot/infrastructure.mjs +35 -18
  275. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  276. package/src/session-runtime/conversation-import/service.mjs +369 -0
  277. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +10 -5
  278. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  279. package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
  280. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  281. package/src/session-runtime/mcp-glue/connect.mjs +83 -16
  282. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  283. package/src/session-runtime/mcp-glue/server-catalog.mjs +135 -33
  284. package/src/session-runtime/mcp-glue.mjs +25 -2
  285. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  286. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  287. package/src/session-runtime/model-route/set-route.mjs +83 -15
  288. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  289. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  290. package/src/session-runtime/new-session-config.mjs +3 -2
  291. package/src/session-runtime/notification-bus.mjs +9 -2
  292. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  293. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  294. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  295. package/src/session-runtime/provider-readiness.mjs +16 -10
  296. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  297. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  298. package/src/session-runtime/resource-extension-sync.mjs +24 -5
  299. package/src/session-runtime/resource-mcp-api.mjs +198 -78
  300. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  301. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  302. package/src/session-runtime/resource-skills-api.mjs +23 -2
  303. package/src/session-runtime/runtime-review-api.mjs +13 -0
  304. package/src/session-runtime/schedule-session-run.mjs +1 -1
  305. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  306. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  307. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  308. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  310. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  311. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  312. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  313. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  314. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  315. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  316. package/src/session-runtime/services/provider-admin.mjs +64 -18
  317. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  318. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  319. package/src/session-runtime/setup-tool/executor.mjs +12 -1
  320. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  321. package/src/session-runtime/setup-tool/settings-contract.mjs +1 -0
  322. package/src/session-runtime/setup-tool/tool-defs.mjs +7 -1
  323. package/src/session-runtime/skills-api.mjs +7 -3
  324. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  325. package/src/session-runtime/webhook-session-run.mjs +16 -4
  326. package/src/skill-script-entry.mjs +36 -0
  327. package/src/standalone/daemon-drain-policy.mjs +8 -0
  328. package/src/standalone/daemon-stored-session-views.mjs +11 -3
  329. package/src/standalone/daemon-telemetry.mjs +55 -0
  330. package/src/standalone/daemon.mjs +44 -11
  331. package/src/standalone/session-protocol.mjs +12 -0
  332. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  333. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  334. package/src/standalone/session-service/agent-tree.mjs +12 -0
  335. package/src/standalone/session-service/quota-resume.mjs +8 -0
  336. package/src/standalone/session-service/retention/busy.mjs +15 -1
  337. package/src/standalone/session-service/session-calls/session-action.mjs +6 -1
  338. package/src/standalone/session-service-api.mjs +5 -0
  339. package/src/standalone/session-service.mjs +1 -0
  340. package/src/tui/App.jsx +9 -1
  341. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  342. package/src/tui/app/app-view.jsx +7 -0
  343. package/src/tui/app/create-app-pickers.mjs +4 -0
  344. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  345. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  346. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  347. package/src/tui/app/extension-pickers.mjs +2 -1
  348. package/src/tui/app/model-options.mjs +13 -16
  349. package/src/tui/app/skill-mention-palette.mjs +88 -0
  350. package/src/tui/app/slash-commands.mjs +7 -2
  351. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  352. package/src/tui/app/slash-palette.mjs +23 -7
  353. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  354. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  355. package/src/tui/app/use-prompt-state.mjs +5 -0
  356. package/src/tui/components/Message.jsx +22 -1
  357. package/src/tui/components/PromptInput.jsx +21 -3
  358. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  359. package/src/tui/components/StatusLine.jsx +2 -0
  360. package/src/tui/components/TranscriptItem.jsx +9 -1
  361. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  362. package/src/tui/components/status-line/local-line.mjs +5 -2
  363. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  364. package/src/tui/components/user-message-skills.mjs +38 -0
  365. package/src/tui/dist/index.mjs +1323 -214
  366. package/src/tui/session/queue-helpers.mjs +7 -2
  367. package/src/tui/session/quota-resume.mjs +46 -14
  368. package/src/tui/session/session-api/commands.mjs +33 -24
  369. package/src/tui/session/session-api/extensions.mjs +26 -2
  370. package/src/tui/session/session-api/integrations.mjs +7 -0
  371. package/src/tui/session/session-api/lifecycle.mjs +5 -1
  372. package/src/tui/session/session-api/routes.mjs +71 -7
  373. package/src/tui/session/session-api/settings.mjs +12 -14
  374. package/src/tui/session/session-flow/drain.mjs +21 -5
  375. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  376. package/src/tui/session/tui-steering-persist.mjs +25 -9
  377. package/src/tui/session/turn.mjs +55 -15
  378. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  379. package/src/ui/statusline-agents.mjs +15 -4
  380. package/src/ui/statusline.mjs +7 -3
  381. package/src/ui/streaming-markdown-heal.mjs +34 -3
  382. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  383. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  384. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
  385. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  386. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
@@ -3,11 +3,30 @@
3
3
  * response: frame parsing, tool-batch hand-off to the run registry, the
4
4
  * close verdict and the bounded in-place restart.
5
5
  */
6
- import { isRetryableCursorStreamError } from './cursor-wire-guards.mjs';
7
- import { markProviderRecoveryExhausted } from './retry-classifier.mjs';
6
+ import {
7
+ isCursorAuthError,
8
+ isCursorThrottleError,
9
+ isRetryableCursorStreamError,
10
+ withCursorFailureHint,
11
+ } from './cursor-wire-guards.mjs';
12
+ import { emitProviderRetryStage, markProviderRecoveryExhausted, retryAfterMsFromError } from './retry-classifier.mjs';
8
13
  import { createFrameParser, cursorError, parseEndStream } from './cursor-wire-transport.mjs';
9
14
  import { processServerMessage } from './cursor-wire-stream-messages.mjs';
10
15
 
16
+ // Resolves after `ms`, or early when the stream is cancelled.
17
+ function abortableSleep(ms, signal) {
18
+ return new Promise((resolve) => {
19
+ if (signal?.aborted) return resolve();
20
+ const done = () => {
21
+ clearTimeout(timer);
22
+ signal?.removeEventListener('abort', done);
23
+ resolve();
24
+ };
25
+ const timer = setTimeout(done, ms);
26
+ signal?.addEventListener('abort', done, { once: true });
27
+ });
28
+ }
29
+
11
30
  /**
12
31
  * @param {object} ctx
13
32
  * @param {{ bridge, heartbeat }} ctx.live the bridge currently feeding the stream (mutated on restart)
@@ -16,7 +35,10 @@ import { processServerMessage } from './cursor-wire-stream-messages.mjs';
16
35
  */
17
36
  export function createBridgeController(ctx) {
18
37
  const { live, state, filter, watchdog, sink, tuning, run, runs, restart, isCancelled } = ctx;
38
+ const { sleep = abortableSleep, cancelSignal = null, onStageChange = null } = ctx;
19
39
  let retryCount = 0;
40
+ let throttleCount = 0;
41
+ let authCount = 0;
20
42
 
21
43
  const finishToolBatch = () => {
22
44
  if (state.closed || state.pending.length === 0) return;
@@ -35,37 +57,91 @@ export function createBridgeController(ctx) {
35
57
  sink.finish('tool_calls');
36
58
  };
37
59
 
60
+ // Exponential backoff with +-20% jitter for in-place retries.
61
+ const backoffMs = (attempt) => {
62
+ const ceiling = Math.min(tuning.retryMaxDelayMs, tuning.retryBaseDelayMs * 2 ** (attempt - 1));
63
+ return Math.floor(ceiling * (0.8 + Math.random() * 0.4));
64
+ };
65
+
38
66
  const recoverOrFail = (error) => {
39
67
  const retryable = isRetryableCursorStreamError(error);
68
+ // A throttle retries within the bounded throttle retry count; otherwise
69
+ // the error surfaces with retryAfterMs for the outer layer.
70
+ const throttled = !retryable && isCursorThrottleError(error);
71
+ // Rejected credentials get a forced token refresh before the retry.
72
+ const authFailure = !retryable && !throttled && isCursorAuthError(error);
73
+ // A throttle retry rotates the conversation and drops the checkpoint
74
+ // so unlike other recoveries it can only replay a run that
75
+ // has shown nothing; the rest may also continue from a checkpoint.
40
76
  const recoveryEligible =
41
- !isCancelled() && typeof restart === 'function' && (!state.visibleOutput || run.conversation.checkpoint);
42
- const canRetry = retryCount < tuning.maxRetries && recoveryEligible && retryable;
77
+ !isCancelled() &&
78
+ typeof restart === 'function' &&
79
+ (!state.visibleOutput || (!throttled && Boolean(run.conversation.checkpoint)));
80
+ const serverDelay = retryAfterMsFromError(error);
81
+ // Server advice is honored in full for every retryable kind; local
82
+ // backoff applies only when the server gave none.
83
+ const delayMs = Number.isFinite(serverDelay)
84
+ ? serverDelay
85
+ : backoffMs(throttled ? throttleCount + 1 : retryCount + 1);
86
+ const throttleOk = throttled && throttleCount < tuning.maxThrottleRetries;
87
+ const authOk = authFailure && authCount < tuning.maxAuthRetries;
88
+ const canRetry =
89
+ retryCount < tuning.maxRetries && recoveryEligible && (retryable || throttleOk || authOk);
43
90
  if (!canRetry) {
44
91
  // A fresh Cursor run owns its bounded in-place retries. Mark
45
92
  // exhaustion so the outer loop does not multiply that budget.
46
93
  // Resumed tool-result streams have no restart closure; those
47
94
  // intentionally fall through unmarked so the outer loop can
48
95
  // rebuild from the committed assistant/tool-result history.
49
- if (retryable && recoveryEligible && retryCount >= tuning.maxRetries) {
96
+ // A throttle that spent its bounded retries is final for the turn too.
97
+ if (
98
+ recoveryEligible &&
99
+ ((retryable && retryCount >= tuning.maxRetries) ||
100
+ (throttled && throttleCount >= tuning.maxThrottleRetries))
101
+ ) {
50
102
  markProviderRecoveryExhausted(error, { owner: 'cursor-wire', attempts: retryCount + 1 });
51
103
  }
52
- sink.fail(error);
104
+ sink.fail(withCursorFailureHint(error));
53
105
  return;
54
106
  }
55
107
  retryCount += 1;
108
+ if (throttled) throttleCount += 1;
109
+ if (authFailure) authCount += 1;
56
110
  state.sawEnd = false;
57
111
  state.batchBoundaryReady = false;
58
112
  state.batchBoundaryChunkSeq = -1;
59
- try {
60
- const next = restart({
61
- attempt: retryCount,
62
- fromCheckpoint: Boolean(run.conversation.checkpoint),
63
- visibleOutput: state.visibleOutput,
64
- });
65
- attach(next.bridge, next.heartbeat);
66
- } catch (restartError) {
67
- sink.fail(restartError);
68
- }
113
+ // The wait is intentional, not stream silence; attach() re-arms on restart.
114
+ watchdog.pause();
115
+ emitProviderRetryStage(onStageChange, {
116
+ attempt: retryCount + 1,
117
+ maxAttempts: tuning.maxRetries + 1,
118
+ lastErr: error,
119
+ delayMs,
120
+ });
121
+ void sleep(delayMs, cancelSignal).then(async () => {
122
+ if (isCancelled() || state.closed) return;
123
+ try {
124
+ // restart() may refresh credentials first, so it can be async.
125
+ const next = await restart({
126
+ attempt: retryCount,
127
+ fromCheckpoint: !throttled && Boolean(run.conversation.checkpoint),
128
+ visibleOutput: state.visibleOutput,
129
+ reason: throttled ? 'throttle' : authFailure ? 'auth' : 'transient',
130
+ error,
131
+ });
132
+ if (isCancelled() || state.closed) {
133
+ clearInterval(next.heartbeat);
134
+ next.bridge.close();
135
+ return;
136
+ }
137
+ attach(next.bridge, next.heartbeat);
138
+ try {
139
+ onStageChange?.('streaming');
140
+ } catch {}
141
+ } catch (restartError) {
142
+ sink.fail(withCursorFailureHint(restartError));
143
+ }
144
+ });
69
145
  };
70
146
 
71
147
  /** The Connect end frame: an error closes, pending tools delimit, a finished turn completes. */
@@ -99,20 +175,23 @@ export function createBridgeController(ctx) {
99
175
  closeError = frameError;
100
176
  }
101
177
  }
102
- // Cursor commonly closes HTTP/2 immediately after turnEnded without
103
- // a separate Connect end frame. The turn is already complete.
104
- if (state.sawTurnEnded) {
105
- sink.finish();
106
- return;
107
- }
108
178
  // Tool calls already emitted to the caller remain actionable even if
109
179
  // the parked transport vanished. The next request rebuilds/resumes.
180
+ // Checked before turnEnded: a close that follows turnEnded only ends a
181
+ // completed turn when no exec is still waiting (turn ended
182
+ // with no MCP exec outstanding), never by dropping a tool call.
110
183
  if (state.pending.length > 0) {
111
184
  sink.finish('tool_calls');
112
185
  return;
113
186
  }
187
+ // Cursor commonly closes HTTP/2 immediately after turnEnded, with or
188
+ // without an end-stream error. The turn is already complete.
189
+ if (state.sawTurnEnded) {
190
+ sink.finish();
191
+ return;
192
+ }
114
193
  if (!closeError && !state.sawEnd) {
115
- closeError = cursorError('Cursor stream closed before its end frame', { code: 'protocol_error' });
194
+ closeError = cursorError('Cursor stream closed before its end frame', { code: 'incomplete_stream' });
116
195
  }
117
196
  if (closeError) recoverOrFail(closeError);
118
197
  else sink.finish();
@@ -4,6 +4,7 @@ import crypto from 'node:crypto';
4
4
  import http2 from 'node:http2';
5
5
  import { MAX_CONNECT_FRAME_BYTES, createCursorByteQueue } from './cursor-wire-guards.mjs';
6
6
  import { cursorClientVersion, warmCursorClientVersion } from './cursor-client-version.mjs';
7
+ import { retryAfterMsFromError } from './retry-classifier.mjs';
7
8
 
8
9
  export { warmCursorClientVersion };
9
10
 
@@ -40,7 +41,66 @@ function rpcHeaders(accessToken, path, unary) {
40
41
  };
41
42
  }
42
43
 
43
- export function cursorError(message, { status = 0, code = '', retryAfter = null } = {}) {
44
+ // HTTP-equivalent status for every Connect error code.
45
+ const CONNECT_CODE_STATUS = Object.freeze({
46
+ canceled: 499,
47
+ unknown: 500,
48
+ invalid_argument: 400,
49
+ deadline_exceeded: 504,
50
+ not_found: 404,
51
+ already_exists: 409,
52
+ permission_denied: 403,
53
+ resource_exhausted: 429,
54
+ failed_precondition: 400,
55
+ aborted: 409,
56
+ out_of_range: 400,
57
+ unimplemented: 501,
58
+ internal: 500,
59
+ unavailable: 503,
60
+ data_loss: 500,
61
+ unauthenticated: 401,
62
+ });
63
+
64
+ // Connect code for a plain HTTP failure that carried no Connect error body.
65
+ const HTTP_STATUS_CODE = Object.freeze({
66
+ 401: 'unauthenticated',
67
+ 403: 'permission_denied',
68
+ 404: 'not_found',
69
+ 429: 'resource_exhausted',
70
+ 500: 'internal',
71
+ 502: 'unavailable',
72
+ 503: 'unavailable',
73
+ 504: 'deadline_exceeded',
74
+ });
75
+
76
+ // Only explicit wording about the tool catalog or request payload being too
77
+ // large counts; resource_exhausted otherwise stays a throttle.
78
+ const REQUEST_TOO_LARGE_RE =
79
+ /\b(?:tool\s+(?:catalog|definitions?|schemas?)|tools|payload|request(?:\s+body)?)\b[^.\n]{0,80}?\b(?:too\s+(?:large|big)|exceed(?:s|ed)?\b[^.\n]{0,30}?\b(?:size|limit|maximum))/i;
80
+
81
+ // Rate/quota wording wins over the size match: "request exceeded the rate
82
+ // limit" is a throttle, not an oversized request.
83
+ const THROTTLE_CUE_RE = /too\s+many\s+requests|quota|rate[\s_-]*limit|throttl/i;
84
+
85
+ function firstValue(value) {
86
+ return Array.isArray(value) ? value[0] : value;
87
+ }
88
+
89
+ // Lowercased string-valued header map without HTTP/2 pseudo-headers.
90
+ function plainHeaders(source) {
91
+ const headers = {};
92
+ for (const [key, value] of Object.entries(source || {})) {
93
+ const first = firstValue(value);
94
+ if (key.startsWith(':') || first == null || typeof first === 'object') continue;
95
+ headers[key.toLowerCase()] = String(first);
96
+ }
97
+ return headers;
98
+ }
99
+
100
+ export function cursorError(
101
+ message,
102
+ { status = 0, code = '', cursorCode = code, headers = null, retryAfter = null, reauthRequired = false, detail } = {}
103
+ ) {
44
104
  const error = new Error(message);
45
105
  if (status) {
46
106
  error.status = status;
@@ -48,17 +108,32 @@ export function cursorError(message, { status = 0, code = '', retryAfter = null
48
108
  }
49
109
  if (code) {
50
110
  error.code = code;
51
- error.cursorCode = code;
111
+ error.cursorCode = cursorCode;
52
112
  }
113
+ if (detail != null) error.cursorDetail = detail;
114
+ if (reauthRequired) error.reauthRequired = true;
115
+ const errorHeaders = plainHeaders(headers);
53
116
  if (retryAfter != null && retryAfter !== '') {
54
- const headers = { 'retry-after': String(retryAfter) };
55
117
  error.retryAfter = retryAfter;
56
- error.headers = headers;
57
- error.response = { status, headers };
118
+ errorHeaders['retry-after'] = String(retryAfter);
119
+ }
120
+ if (Object.keys(errorHeaders).length) {
121
+ error.headers = errorHeaders;
122
+ error.response = { status, headers: errorHeaders };
123
+ const retryAfterMs = retryAfterMsFromError(error);
124
+ if (Number.isFinite(retryAfterMs)) error.retryAfterMs = retryAfterMs;
58
125
  }
59
126
  return error;
60
127
  }
61
128
 
129
+ function httpStatusError(status, headers) {
130
+ return cursorError(`Cursor request failed (${status})`, {
131
+ status,
132
+ code: HTTP_STATUS_CODE[status] || '',
133
+ headers,
134
+ });
135
+ }
136
+
62
137
  export function openCursorStream({ accessToken, path = RUN_PATH, url = API_URL }) {
63
138
  const session = http2.connect(url);
64
139
  const request = session.request(rpcHeaders(accessToken, path, false));
@@ -66,7 +141,7 @@ export function openCursorStream({ accessToken, path = RUN_PATH, url = API_URL }
66
141
  let closeHandler = null;
67
142
  let closed = false;
68
143
  let status = 0;
69
- let retryAfter = null;
144
+ let responseHeaders = null;
70
145
  let closeError = null;
71
146
  let timeout = setTimeout(
72
147
  () => close(cursorError('Cursor connection timed out', { code: 'connection_timeout' })),
@@ -97,8 +172,7 @@ export function openCursorStream({ accessToken, path = RUN_PATH, url = API_URL }
97
172
  closed = true;
98
173
  clearTimeout(timeout);
99
174
  clearInterval(ping);
100
- closeError =
101
- error || (status >= 400 ? cursorError(`Cursor request failed (${status})`, { status, retryAfter }) : null);
175
+ closeError = error || (status >= 400 ? httpStatusError(status, responseHeaders) : null);
102
176
  try {
103
177
  request.close();
104
178
  } catch {}
@@ -120,7 +194,7 @@ export function openCursorStream({ accessToken, path = RUN_PATH, url = API_URL }
120
194
 
121
195
  request.on('response', (headers) => {
122
196
  status = Number(headers[':status'] || 0);
123
- retryAfter = headers['retry-after'] ?? null;
197
+ responseHeaders = headers;
124
198
  resetTimeout();
125
199
  });
126
200
  request.on('data', (chunk) => {
@@ -167,7 +241,7 @@ export async function callCursorUnary({ accessToken, path, body, url = API_URL,
167
241
  const request = session.request(rpcHeaders(accessToken, path, true));
168
242
  const chunks = [];
169
243
  let status = 0;
170
- let retryAfter = null;
244
+ let responseHeaders = null;
171
245
  let settled = false;
172
246
  const timeout = setTimeout(() => finish(new Error('Cursor request timed out')), timeoutMs);
173
247
  const finish = (error = null) => {
@@ -181,12 +255,12 @@ export async function callCursorUnary({ accessToken, path, body, url = API_URL,
181
255
  session.close();
182
256
  } catch {}
183
257
  if (error) reject(error);
184
- else if (status >= 400) reject(cursorError(`Cursor request failed (${status})`, { status, retryAfter }));
258
+ else if (status >= 400) reject(httpStatusError(status, responseHeaders));
185
259
  else resolve(Buffer.concat(chunks));
186
260
  };
187
261
  request.on('response', (headers) => {
188
262
  status = Number(headers[':status'] || 0);
189
- retryAfter = headers['retry-after'] ?? null;
263
+ responseHeaders = headers;
190
264
  });
191
265
  request.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
192
266
  request.on('end', () => finish());
@@ -235,7 +309,7 @@ export function createFrameParser(onMessage, onEnd) {
235
309
  };
236
310
  parse.finish = () => {
237
311
  if (pending.byteLength) {
238
- throw cursorError('Cursor stream ended with a truncated frame', { code: 'protocol_error' });
312
+ throw cursorError('Cursor stream ended with a truncated frame', { code: 'incomplete_stream' });
239
313
  }
240
314
  };
241
315
  parse.bufferedBytes = () => pending.byteLength;
@@ -246,26 +320,22 @@ export function parseEndStream(bytes) {
246
320
  try {
247
321
  const payload = JSON.parse(textDecoder.decode(bytes));
248
322
  if (!payload?.error) return null;
249
- const code = String(payload.error.code || 'error');
250
- const status =
251
- {
252
- unauthenticated: 401,
253
- permission_denied: 403,
254
- not_found: 404,
255
- resource_exhausted: 429,
256
- invalid_argument: 400,
257
- internal: 500,
258
- unavailable: 503,
259
- }[code] || 0;
260
- return cursorError(`Cursor ${code}: ${payload.error.message || 'request failed'}`, {
261
- status,
262
- code,
263
- retryAfter:
264
- payload.error.retryAfter ??
265
- payload.error.retry_after ??
266
- payload.metadata?.['retry-after'] ??
267
- payload.metadata?.retryAfter ??
268
- null,
323
+ const connectCode = String(payload.error.code || 'error').toLowerCase();
324
+ const detail = String(payload.error.message || 'request failed');
325
+ const tooLarge = connectCode === 'resource_exhausted' && !THROTTLE_CUE_RE.test(detail) && REQUEST_TOO_LARGE_RE.test(detail);
326
+ const retryAfter =
327
+ payload.error.retryAfter ??
328
+ payload.error.retry_after ??
329
+ payload.metadata?.['retry-after'] ??
330
+ payload.metadata?.retryAfter ??
331
+ null;
332
+ return cursorError(`Cursor ${connectCode}: ${detail}`, {
333
+ status: tooLarge ? 413 : CONNECT_CODE_STATUS[connectCode] || 0,
334
+ code: tooLarge ? 'request_too_large' : connectCode,
335
+ cursorCode: connectCode,
336
+ headers: payload.metadata,
337
+ retryAfter: firstValue(retryAfter),
338
+ detail,
269
339
  });
270
340
  } catch {
271
341
  return cursorError('Cursor returned an invalid end-stream frame', { code: 'protocol_error' });
@@ -14,6 +14,7 @@ import {
14
14
  } from './cursor-wire-protobuf.mjs';
15
15
  import {
16
16
  assertCursorUserImages,
17
+ createCursorCredentialRefresher,
17
18
  createCursorStreamWatchdog,
18
19
  resolveCursorStreamTuning,
19
20
  storeCursorBlob,
@@ -358,12 +359,16 @@ function createStreamResponse({
358
359
  sessionId = '',
359
360
  sawTurnEnded = false,
360
361
  restart = null,
362
+ sleep = undefined,
363
+ onStageChange = null,
361
364
  }) {
362
365
  const id = newCompletionId();
363
366
  // The bridge currently feeding the stream; a restart swaps it in place.
364
367
  const live = { bridge, heartbeat };
365
368
  let watchdog = null;
366
369
  let cancelled = false;
370
+ // Cuts a pending retry backoff short when the caller cancels.
371
+ const cancelController = new AbortController();
367
372
  const runs = { get: (runKey) => activeRuns.get(runKey), store: storeActiveRun, forget: forgetActiveRun };
368
373
  const stream = new ReadableStream({
369
374
  start(controller) {
@@ -395,12 +400,16 @@ function createStreamResponse({
395
400
  run: { key, conversation, tools, cloudRule, modelParameters, maxMode, sessionId },
396
401
  runs,
397
402
  restart,
403
+ sleep,
404
+ onStageChange,
405
+ cancelSignal: cancelController.signal,
398
406
  isCancelled: () => cancelled,
399
407
  });
400
408
  bridges.attach(bridge, heartbeat);
401
409
  },
402
410
  cancel(reason) {
403
411
  cancelled = true;
412
+ cancelController.abort();
404
413
  watchdog?.stop();
405
414
  clearInterval(live.heartbeat);
406
415
  const active = activeRuns.get(key);
@@ -459,7 +468,7 @@ function noUserMessageResponse() {
459
468
  });
460
469
  }
461
470
 
462
- export async function handleChatCompletion(body, accessToken) {
471
+ export async function handleChatCompletion(body, accessToken, { onStageChange = null, getAccessToken = null } = {}) {
463
472
  await warmCursorClientVersion();
464
473
  const parsed = parseMessages(body.messages);
465
474
  if (
@@ -504,6 +513,10 @@ export async function handleChatCompletion(body, accessToken) {
504
513
  };
505
514
  const requestBytes = buildRunRequest(runInput);
506
515
  const { bridge, heartbeat } = startRun(accessToken, requestBytes);
516
+ // Every retry force-refreshes credentials and falls back to the previous
517
+ // token if the refresh fails. A rejected credential is only
518
+ // retried when the refresh produced a different token.
519
+ const credentials = createCursorCredentialRefresher(accessToken, getAccessToken);
507
520
  return createStreamResponse({
508
521
  bridge,
509
522
  heartbeat,
@@ -515,11 +528,21 @@ export async function handleChatCompletion(body, accessToken) {
515
528
  maxMode,
516
529
  key,
517
530
  sessionId,
518
- restart: ({ fromCheckpoint }) =>
519
- startRun(
520
- accessToken,
521
- fromCheckpoint ? buildRunRequest({ ...runInput, userText: '', userImages: [] }) : requestBytes
522
- ),
531
+ onStageChange,
532
+ restart: async ({ fromCheckpoint, reason, error }) => {
533
+ if (reason === 'throttle') {
534
+ // Cursor can pin resource_exhausted to one conversation id (conversation
535
+ // rotation after a rate limit): start a fresh one from the full
536
+ // history instead of the checkpoint it refused.
537
+ conversation.id = crypto.randomUUID();
538
+ conversation.checkpoint = null;
539
+ }
540
+ const token = await credentials.refresh(reason, error);
541
+ let bytes = requestBytes;
542
+ if (fromCheckpoint) bytes = buildRunRequest({ ...runInput, userText: '', userImages: [] });
543
+ else if (reason === 'throttle') bytes = buildRunRequest(runInput);
544
+ return startRun(token, bytes);
545
+ },
523
546
  });
524
547
  }
525
548
 
@@ -10,6 +10,7 @@ import {
10
10
  } from './openai-compat-wire.mjs';
11
11
  import { preparePdfTextForProvider } from './media-normalization.mjs';
12
12
  import { consumeCompatChatCompletionStream } from './openai-compat-stream.mjs';
13
+ import { retryAfterMsFromError } from './retry-classifier.mjs';
13
14
  import { ensureChatToolPairs } from './lib/wire-pairing.mjs';
14
15
  import { cursorTokenExpiry, exchangeCursorToken, resolveCursorOAuthAccessToken } from './cursor-auth.mjs';
15
16
  import {
@@ -62,14 +63,24 @@ function toCursorToolChoice(toolChoice) {
62
63
  async function responseError(response, label) {
63
64
  const text = await response.text().catch(() => '');
64
65
  let message = text;
66
+ let code = '';
65
67
  try {
66
68
  const parsed = JSON.parse(text);
67
69
  message = parsed?.error?.message || parsed?.message || text;
70
+ code = String(parsed?.error?.code || parsed?.code || '').toLowerCase();
68
71
  } catch {
69
72
  /* plain text */
70
73
  }
71
74
  const error = new Error(`${label} request failed (${response.status})${message ? `: ${message}` : ''}`);
72
75
  error.status = response.status;
76
+ error.httpStatus = response.status;
77
+ if (code) error.code = code;
78
+ const headers = Object.fromEntries(response.headers?.entries?.() ?? []);
79
+ if (Object.keys(headers).length) {
80
+ error.headers = headers;
81
+ const retryAfterMs = retryAfterMsFromError(error);
82
+ if (Number.isFinite(retryAfterMs)) error.retryAfterMs = retryAfterMs;
83
+ }
73
84
  throw error;
74
85
  }
75
86
 
@@ -535,6 +546,9 @@ class CursorProviderBase {
535
546
  nativePdf = false;
536
547
 
537
548
  static inputExcludesCache = false;
549
+ // The in-place stream retry budget is the whole recovery: a spent budget
550
+ // surfaces instead of being re-run by the outer recovery cycles.
551
+ retryPolicy = Object.freeze({ recoveryCycles: false });
538
552
  name;
539
553
  config;
540
554
  _runtimeOverride;
@@ -622,7 +636,10 @@ class CursorProviderBase {
622
636
  sendOpts.onStageChange?.('requesting');
623
637
  } catch {}
624
638
  try {
625
- const response = await runtime.handleChatCompletion(body, accessToken);
639
+ const response = await runtime.handleChatCompletion(body, accessToken, {
640
+ onStageChange: sendOpts.onStageChange,
641
+ getAccessToken: (options) => this._accessToken({ signal, ...options }),
642
+ });
626
643
  try {
627
644
  sendOpts.onStageChange?.('streaming');
628
645
  } catch {}
@@ -4,6 +4,7 @@
4
4
  // runtime reuses the existing OpenAI-compat / Anthropic engines, constructed
5
5
  // under the custom id so model routes and provider identity stay `custom-…`.
6
6
 
7
+ import { resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
7
8
  import { effectiveCustomModelRows, normalizeCustomProviderConfig } from './custom-provider-config.mjs';
8
9
  import { ANTHROPIC_VERSION } from './lib/anthropic-models.mjs';
9
10
  import { enrichModels } from './model-catalog.mjs';
@@ -39,8 +40,11 @@ function withCustomModels(Base, discover) {
39
40
  customConfig = null;
40
41
  customDiscoveryError = null;
41
42
  _customDiscovered = null;
42
- _customApiKey = '';
43
43
  _customKeyless = false;
44
+ // The key for discovery and error redaction, resolved per use like sends.
45
+ _customKey() {
46
+ return this._customKeyless ? '' : resolveProviderApiKey(this.name, this.config);
47
+ }
44
48
  _customRows() {
45
49
  return effectiveCustomModelRows(this.customConfig, this._customDiscovered || [], { providerId: this.name });
46
50
  }
@@ -57,7 +61,7 @@ function withCustomModels(Base, discover) {
57
61
  this.customDiscoveryError = null;
58
62
  } catch (error) {
59
63
  if (signal?.aborted) throw error;
60
- failure = sanitizeDiscoveryError(error, this._customApiKey);
64
+ failure = sanitizeDiscoveryError(error, this._customKey());
61
65
  this.customDiscoveryError = failure;
62
66
  }
63
67
  const rows = this._customRows();
@@ -82,7 +86,7 @@ async function discoverAnthropicModels(provider, signal) {
82
86
  method: 'GET',
83
87
  signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
84
88
  headers: {
85
- ...(provider._customKeyless ? {} : { 'x-api-key': provider.apiKey || '' }),
89
+ ...(provider._customKeyless ? {} : { 'x-api-key': provider._customKey() }),
86
90
  'anthropic-version': ANTHROPIC_VERSION,
87
91
  },
88
92
  });
@@ -141,7 +145,6 @@ export async function createCustomProvider(id, config) {
141
145
  provider = new Custom(providerId, engine);
142
146
  }
143
147
  provider.customConfig = cfg;
144
- provider._customApiKey = apiKey;
145
148
  provider._customKeyless = keyless;
146
149
  provider._applyCustomRows(provider._customRows());
147
150
  return provider;