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
@@ -21,7 +21,7 @@ import {
21
21
  } from 'node:fs/promises';
22
22
  import { dirname, basename, join } from 'node:path';
23
23
  import { randomBytes } from 'node:crypto';
24
- import { DEFAULT_BACKOFFS_MS, withFileLock, withFileLockSync } from './file-lock.mjs';
24
+ import { DEFAULT_BACKOFFS_MS, recordLockTiming, withFileLock, withFileLockSync } from './file-lock.mjs';
25
25
  import {
26
26
  enforceOwnerOnlyAclWin32 as _enforceOwnerOnlyAclWin32,
27
27
  enforceOwnerOnlyAclWin32Async as _enforceOwnerOnlyAclWin32Async,
@@ -321,9 +321,30 @@ export async function writeFileAtomicAsync(filePath, data, opts = {}) {
321
321
  };
322
322
  // Acquire the file lock before occupying an I/O slot. Otherwise a waiter can
323
323
  // exhaust the gate while the current lock owner needs it to finish its write.
324
- return opts.lock === true
325
- ? withFileLock(`${filePath}.lock`, () => _runAdaptiveFileIo(run), opts)
326
- : _runAdaptiveFileIo(run);
324
+ if (opts.lock !== true) return _runAdaptiveFileIo(run);
325
+ const lockPath = `${filePath}.lock`;
326
+ return withFileLock(
327
+ lockPath,
328
+ async () => {
329
+ const queuedAt = Date.now();
330
+ let startedAt = 0;
331
+ try {
332
+ return await _runAdaptiveFileIo(() => {
333
+ startedAt = Date.now();
334
+ return run();
335
+ });
336
+ } finally {
337
+ const now = Date.now();
338
+ // Recorded from this (lock-owner) context so the held warning can
339
+ // split the hold into gate wait vs. the write itself.
340
+ recordLockTiming(lockPath, {
341
+ ioGateWaitMs: (startedAt || now) - queuedAt,
342
+ writeMs: startedAt ? now - startedAt : 0,
343
+ });
344
+ }
345
+ },
346
+ opts
347
+ );
327
348
  }
328
349
 
329
350
  export function writeJsonAtomicAsync(filePath, value, opts = {}) {
@@ -18,6 +18,10 @@ export { TOOL_ASYNC_EXECUTION_CONTRACT, TOOL_MANUAL_CONTROL_CONTRACT, TOOL_SYNC_
18
18
  const TASK_TTL_MS = 30 * 60_000;
19
19
  const MAX_TASKS = 300;
20
20
  const TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled']);
21
+ // A `notifyOnCancel` task reports its cancellation to the owner once, after its
22
+ // run settles, so the notification can carry what the run produced before it
23
+ // stopped. A run that never settles still reports, header-only, after this grace.
24
+ const CANCEL_NOTIFY_GRACE_MS = 10_000;
21
25
  const tasks = new Map();
22
26
  let seq = 0;
23
27
 
@@ -151,6 +155,7 @@ export function registerBackgroundTask({
151
155
  resultType,
152
156
  renderResult,
153
157
  cancel,
158
+ notifyOnCancel = false,
154
159
  } = {}) {
155
160
  pruneTasks();
156
161
  const explicitId = clean(taskId);
@@ -189,6 +194,7 @@ export function registerBackgroundTask({
189
194
  resultType: clean(resultType) || null,
190
195
  renderResult: typeof renderResult === 'function' ? renderResult : null,
191
196
  cancel: typeof cancel === 'function' ? cancel : null,
197
+ notifyOnCancel: notifyOnCancel === true,
192
198
  };
193
199
  tasks.set(id, task);
194
200
  return task;
@@ -200,10 +206,14 @@ export function startBackgroundTask(options = {}) {
200
206
  .then(() => options.run?.())
201
207
  .then((result) => {
202
208
  completeBackgroundTask(task.taskId, { status: 'completed', result, terminalReason: 'run-resolved' });
209
+ deliverCancelledNotification(task);
203
210
  return result;
204
211
  })
205
212
  .catch((error) => {
206
- if (task.status === 'cancelled') return null;
213
+ if (task.status === 'cancelled') {
214
+ deliverCancelledNotification(task);
215
+ return null;
216
+ }
207
217
  completeBackgroundTask(task.taskId, {
208
218
  status: 'failed',
209
219
  error,
@@ -258,15 +268,62 @@ export function cleanupBackgroundTasks(options = {}) {
258
268
  return { removed: before - after, tasks: after };
259
269
  }
260
270
 
261
- export function cancelBackgroundTask(taskId, reason = 'cancelled') {
271
+ // `notify`: true notifies immediately, false never notifies (owner teardown).
272
+ // Left unset, a `notifyOnCancel` task defers its single `cancelled`
273
+ // notification until its run settles (see settleCancelledBackgroundTask); any
274
+ // other task stays silent.
275
+ function cancelRunningTask(task, reason, notify) {
276
+ try {
277
+ task.cancel?.();
278
+ } catch {}
279
+ completeBackgroundTask(task.taskId, { status: 'cancelled', error: reason, notify: notify === true });
280
+ if (task.notifyOnCancel !== true || notify === true || task.status !== 'cancelled' || task.cancelNotify) return;
281
+ if (notify === false) {
282
+ task.cancelNotify = 'silent';
283
+ return;
284
+ }
285
+ task.cancelNotify = 'pending';
286
+ task.cancelNotifyTimer = setTimeout(() => deliverCancelledNotification(task), CANCEL_NOTIFY_GRACE_MS);
287
+ task.cancelNotifyTimer.unref?.();
288
+ }
289
+
290
+ // Owner teardown/delete (`notify: false`) also silences a cancellation that is
291
+ // still waiting for its run to settle.
292
+ function resolvePendingCancelNotify(task, next) {
293
+ if (task.cancelNotify !== 'pending') return false;
294
+ clearTimeout(task.cancelNotifyTimer);
295
+ task.cancelNotifyTimer = null;
296
+ task.cancelNotify = next;
297
+ return true;
298
+ }
299
+
300
+ function silenceCancelledNotification(task) {
301
+ resolvePendingCancelNotify(task, 'silent');
302
+ }
303
+
304
+ function deliverCancelledNotification(task) {
305
+ if (!resolvePendingCancelNotify(task, 'delivered')) return false;
306
+ if (tasks.get(task.taskId) !== task) return false;
307
+ task.meta = sanitizeTaskMeta(task.meta);
308
+ return notifyTaskCompletion(task, task.notificationInstruction);
309
+ }
310
+
311
+ export function cancelBackgroundTask(taskId, reason = 'cancelled', { notify } = {}) {
262
312
  const task = getBackgroundTask(taskId);
263
313
  if (!task) return null;
264
- if (!TERMINAL_STATUSES.has(task.status)) {
265
- try {
266
- task.cancel?.();
267
- } catch {}
268
- completeBackgroundTask(task.taskId, { status: 'cancelled', error: reason, notify: false });
269
- }
314
+ if (!TERMINAL_STATUSES.has(task.status)) cancelRunningTask(task, reason, notify);
315
+ else if (notify === false) silenceCancelledNotification(task);
316
+ return task;
317
+ }
318
+
319
+ /** The cancelled run has stopped: attach what it produced before stopping (if
320
+ * any) and deliver the task's single deferred `cancelled` notification. A
321
+ * no-op unless that notification is still pending. */
322
+ export function settleCancelledBackgroundTask(taskId, { result } = {}) {
323
+ const task = getBackgroundTask(taskId);
324
+ if (task?.status !== 'cancelled' || task.cancelNotify !== 'pending') return null;
325
+ if (result !== undefined) task.result = result;
326
+ deliverCancelledNotification(task);
270
327
  return task;
271
328
  }
272
329
 
@@ -280,11 +337,11 @@ export function cancelBackgroundTasks(options = {}) {
280
337
  // visible/recoverable, but may not be claimed by whichever session happens
281
338
  // to close first.
282
339
  if (!taskMatchesScope(task, options, { includeUnattributed: false })) continue;
283
- if (TERMINAL_STATUSES.has(task.status)) continue;
284
- try {
285
- task.cancel?.();
286
- } catch {}
287
- completeBackgroundTask(task.taskId, { status: 'cancelled', error: reason, notify: options.notify === true });
340
+ if (TERMINAL_STATUSES.has(task.status)) {
341
+ if (options.notify === false) silenceCancelledNotification(task);
342
+ continue;
343
+ }
344
+ cancelRunningTask(task, reason, options.notify);
288
345
  cancelled += 1;
289
346
  }
290
347
  return { cancelled };
@@ -358,10 +415,26 @@ export function completeBackgroundTask(
358
415
  return task;
359
416
  }
360
417
 
418
+ // Surfaces whose later reads consume the structured result object itself:
419
+ // shell `task read` re-reads output via result paths and renders exit_code /
420
+ // timed_out / signal; agent job views return `job.result`. They keep it for the
421
+ // TTL; every other surface keeps only resultText + metadata once delivered.
422
+ const RESULT_KEPT_SURFACES = new Set(['shell', 'agent']);
423
+
424
+ function releaseDeliveredResult(task, body) {
425
+ if (task.result === undefined || RESULT_KEPT_SURFACES.has(task.surface)) return;
426
+ // Pin the rendered body first so re-renders and retries stay identical.
427
+ if (!task.resultText) task.resultText = body;
428
+ task.result = undefined;
429
+ }
430
+
361
431
  export function notifyTaskCompletion(task, instruction) {
362
432
  if (!task) return false;
363
433
  if (!TERMINAL_STATUSES.has(task.status)) return false;
364
434
  if (task.completionAcknowledged === true) return false;
435
+ // A deferred cancellation is delivered only by deliverCancelledNotification;
436
+ // a silent (owner-teardown) cancellation never notifies.
437
+ if (task.cancelNotify === 'pending' || task.cancelNotify === 'silent') return false;
365
438
  // `notified` tracks whether *any* completion notification was sent (e.g. an
366
439
  // early header-only preview from the agent surface). A later call that can
367
440
  // finally include the result body must still fire once, otherwise the owner
@@ -396,7 +469,10 @@ export function notifyTaskCompletion(task, instruction) {
396
469
  });
397
470
  if (sent) {
398
471
  task.notified = true;
399
- if (hasBody) task.notifiedWithBody = true;
472
+ if (hasBody) {
473
+ task.notifiedWithBody = true;
474
+ releaseDeliveredResult(task, body);
475
+ }
400
476
  }
401
477
  return sent;
402
478
  }
@@ -457,6 +533,8 @@ export function renderBackgroundTaskNotification(task) {
457
533
  status: task.status,
458
534
  result,
459
535
  error: task.error,
536
+ // A cancelled Agent's header names the cancel reason.
537
+ detail: task.surface === 'agent' && task.status === 'cancelled' ? task.error : null,
460
538
  });
461
539
  }
462
540
 
@@ -180,9 +180,34 @@ export function isCancelLikeError(error) {
180
180
  );
181
181
  }
182
182
 
183
+ // One-line reason per provider kind. Fixed sentences (optionally with a
184
+ // parenthesised value) are the renderer's translation keys.
185
+ const KIND_LINES = {
186
+ overloaded: 'Provider is busy at capacity.',
187
+ rate_limited: ({ retryAfterMs }) =>
188
+ retryAfterMs ? `Provider is rate-limited (retry in ${formatDurationMs(retryAfterMs)}).` : 'Provider is rate-limited.',
189
+ quota_exhausted: 'Provider usage limit reached.',
190
+ entitlement: 'Not included in your plan.',
191
+ billing: 'Out of credits or at spend limit.',
192
+ permission: 'Access denied by the provider.',
193
+ policy: 'Refused by the provider safety policy.',
194
+ auth: 'Provider authentication failed.',
195
+ };
196
+
197
+ // Typed evidence: a persisted descriptor or structured status/code fields.
198
+ // A bare message carries none, so its wording keeps the legacy text path.
199
+ function hasTypedEvidence(error) {
200
+ if (!error || typeof error !== 'object') return false;
201
+ return Boolean(error.errorKind?.kind) || errorHttpStatus(error) > 0 || Boolean(error.code) || Boolean(error.retryAfterMs);
202
+ }
203
+
183
204
  export function presentErrorText(error, options = {}) {
184
205
  const presentation = describeError(error);
185
206
  if (['image-size', 'payload-size', 'request'].includes(presentation.kind)) return presentation.summary;
207
+ const kindLine = hasTypedEvidence(error) ? KIND_LINES[presentation.providerKind] : null;
208
+ if (kindLine && presentation.kind !== 'unknown') {
209
+ return typeof kindLine === 'function' ? kindLine(presentation) : kindLine;
210
+ }
186
211
  const surface = options.surface || options.tool || '';
187
212
  const subject = subjectForSurface(surface);
188
213
  const max = options.max ?? 240;
@@ -1,3 +1,4 @@
1
+ import { PROVIDER_ERROR_KINDS, providerErrorKind } from '../agent/orchestrator/providers/provider-error-kind.mjs';
1
2
  import { transportErrorText } from './transport-error-text.mjs';
2
3
 
3
4
  const DETAILS_LIMIT = 12_000;
@@ -72,34 +73,167 @@ export function isRejected429(value) {
72
73
  return /\bAPI 429 request rejected\b/.test(messageOf(value));
73
74
  }
74
75
 
76
+ // Provider kind → presentation. Summaries are also the persisted form of a
77
+ // failure, so each maps back to its kind when only the text survives.
78
+ const KIND_COPY = {
79
+ overloaded: ['overloaded', 'The provider is busy right now.', 'Try again later or choose another model.'],
80
+ rate_limited: ['rate-limit', 'The provider is temporarily rate-limiting requests.', 'Try again shortly.'],
81
+ quota_exhausted: [
82
+ 'quota',
83
+ 'The provider usage limit was reached.',
84
+ 'Wait for the limit to reset or choose another account.',
85
+ ],
86
+ entitlement: [
87
+ 'entitlement',
88
+ "This model isn't included in your plan.",
89
+ 'Upgrade your plan, or choose another model or provider.',
90
+ ],
91
+ billing: [
92
+ 'billing',
93
+ 'The provider account is out of credits or at its spend limit.',
94
+ 'Check your billing with the provider, then try again.',
95
+ ],
96
+ permission: [
97
+ 'permission',
98
+ "You don't have access to this resource.",
99
+ 'Check your account permissions, or choose another model.',
100
+ ],
101
+ context_overflow: [
102
+ 'context-overflow',
103
+ 'The conversation is too long for this model.',
104
+ 'Compact the conversation or start a new one.',
105
+ ],
106
+ request_too_large: [
107
+ 'payload-size',
108
+ 'The request exceeds the size limit.',
109
+ 'Reduce the attachments or request size, then try again.',
110
+ ],
111
+ policy: [
112
+ 'policy',
113
+ 'The provider refused this request under its safety policy.',
114
+ 'Rephrase the request or choose another model.',
115
+ ],
116
+ service_tier_unavailable: [
117
+ 'service-tier',
118
+ 'The requested service tier is unavailable right now.',
119
+ 'Switch to the standard tier or try again later.',
120
+ ],
121
+ };
122
+ const SUMMARY_KINDS = new Map(Object.entries(KIND_COPY).map(([providerKind, [, summary]]) => [summary, providerKind]));
123
+
124
+ function waitLabel(ms) {
125
+ const seconds = Math.max(1, Math.ceil(ms / 1000));
126
+ if (seconds < 60) return `${seconds}s`;
127
+ const minutes = Math.ceil(seconds / 60);
128
+ if (minutes < 60) return `${minutes}m`;
129
+ const hours = Math.floor(minutes / 60);
130
+ return minutes % 60 ? `${hours}h ${minutes % 60}m` : `${hours}h`;
131
+ }
132
+
133
+ /** Local HH:mm, prefixed with month/day when the reset is not today. */
134
+ function resetLabel(resetAt) {
135
+ const reset = new Date(resetAt);
136
+ const pad = (n) => String(n).padStart(2, '0');
137
+ const time = `${pad(reset.getHours())}:${pad(reset.getMinutes())}`;
138
+ return reset.toDateString() === new Date().toDateString() ? time : `${reset.getMonth() + 1}/${reset.getDate()} ${time}`;
139
+ }
140
+
141
+ // Only legacy items (no persisted descriptor) and untyped strings reach this.
142
+ const LEGACY_TEXT_KINDS = [
143
+ [/The provider usage limit was reached|quota.*(?:hit|exceed)|usage limit/i, 'quota_exhausted'],
144
+ [/rate[_ -]?limit|too many requests/i, 'rate_limited'],
145
+ ];
146
+
147
+ /**
148
+ * The provider kind descriptor: the persisted one, else typed evidence, else
149
+ * wording. `typed` is true when the kind came from a persisted descriptor or a
150
+ * typed error: wording heuristics must then never override it.
151
+ */
152
+ function classify(value, raw, clean, status, code) {
153
+ const persisted = value?.errorKind;
154
+ if (persisted && PROVIDER_ERROR_KINDS.includes(persisted.kind)) {
155
+ return { descriptor: persisted, typed: persisted.kind !== 'unknown' };
156
+ }
157
+ const hasTypedFields =
158
+ value &&
159
+ typeof value === 'object' &&
160
+ Boolean(value.status || value.httpStatus || value.response?.status || value.code || value.retryAfterMs);
161
+ if (!hasTypedFields) {
162
+ // Legacy persisted strings: their wording (and wait) is the only evidence.
163
+ const legacy = SUMMARY_KINDS.get(clean) ?? LEGACY_TEXT_KINDS.find(([re]) => re.test(clean))?.[1];
164
+ if (legacy) {
165
+ const wait = /retryAfter=(\d+)(ms|s|m)\b/i.exec(raw);
166
+ const retryAfterMs = wait ? Number(wait[1]) * { ms: 1, s: 1000, m: 60_000 }[wait[2].toLowerCase()] : undefined;
167
+ return { descriptor: { kind: legacy, ...(retryAfterMs ? { retryAfterMs } : {}) }, typed: false };
168
+ }
169
+ }
170
+ let descriptor = value && typeof value === 'object' ? providerErrorKind(value) : null;
171
+ const typed = Boolean(descriptor) && descriptor.kind !== 'unknown';
172
+ if (!descriptor || descriptor.kind === 'unknown') {
173
+ descriptor = providerErrorKind(
174
+ Object.assign(new Error(raw), { ...(status ? { status } : {}), ...(code ? { code } : {}) })
175
+ );
176
+ }
177
+ if (descriptor.kind === 'unknown') {
178
+ const known = SUMMARY_KINDS.get(clean);
179
+ const legacy = known ?? LEGACY_TEXT_KINDS.find(([re]) => re.test(clean))?.[1];
180
+ if (legacy) return { descriptor: { ...descriptor, kind: legacy }, typed: false };
181
+ }
182
+ return { descriptor, typed };
183
+ }
184
+
75
185
  /** Presentation only: never changes retry policy or the underlying failure. */
76
186
  export function describeError(value) {
77
- const { raw, message, status, code } = unwrap(value);
78
- const rejected429 = isRejected429(value);
187
+ const { raw, message, status: textStatus, code: textCode } = unwrap(value);
79
188
  const details = safeErrorDetails(raw);
80
189
  const clean = safeErrorDetails(message)
81
190
  .replace(/^(?:Error invoking remote method ['"][^'"]+['"]:\s*)?(?:Error:\s*)?/i, '')
82
191
  .trim();
192
+ const { descriptor, typed } = classify(value, raw, clean, textStatus, textCode);
193
+ // A typed or persisted kind owns the verdict: its own status and code stand
194
+ // in for the text-derived ones, and wording below may only refine it.
195
+ const status = typed ? Number(descriptor.status) || 0 : textStatus;
196
+ const code = typed ? String(descriptor.code || '') : textCode;
197
+ // A 429 refusal without quota headers refines a rate limit, nothing else.
198
+ const rejected429 = isRejected429(value) && (!typed || descriptor.kind === 'rate_limited');
199
+ const copy = KIND_COPY[descriptor.kind];
200
+ const retryAfterMs = Number(descriptor.retryAfterMs) > 0 ? Number(descriptor.retryAfterMs) : null;
201
+ const resetAt = Number(descriptor.resetAt) > 0 ? Number(descriptor.resetAt) : null;
83
202
  let kind = 'unknown';
84
203
  let summary = '';
85
204
  let recovery = '';
86
- const transport = transportErrorText(value) || transportErrorText(raw);
205
+ // Localization hook: a recovery that carries a value is given as a template.
206
+ let recoveryTemplate = '';
207
+ let params = null;
208
+ // A persisted descriptor keeps the status and errno code, not the cause
209
+ // chain: rebuild the transport evidence from them. Wording only reads for
210
+ // untyped failures and transient ones (a bare "fetch failed" is that evidence).
211
+ const transport =
212
+ transportErrorText(value) ||
213
+ (typed && transportErrorText({ code: code.toUpperCase(), status })) ||
214
+ (!typed || descriptor.kind === 'transient' ? transportErrorText(raw) : null);
87
215
  if (
88
216
  value?.reauthRequired === true ||
89
- /invalid_grant|refresh token (?:expired|revoked|not available)|sign in again|sign-in expired|Reauth Required/i.test(raw)
217
+ (!typed &&
218
+ /invalid_grant|refresh token (?:expired|revoked|not available)|sign in again|sign-in expired|Reauth Required/i.test(raw))
90
219
  ) {
91
220
  kind = 'reauth';
92
221
  summary = 'Sign-in expired.';
93
222
  recovery = 'Sign in again from Providers.';
94
223
  } else if (
95
- status === 401 ||
96
- status === 403 ||
97
- /\b(?:invalid|expired)\s+(?:api[ _-]?key|access token|credentials?)\b|Provider authentication failed/i.test(clean)
224
+ descriptor.kind === 'auth' ||
225
+ (descriptor.kind === 'unknown' &&
226
+ /\b(?:invalid|expired)\s+(?:api[ _-]?key|access token|credentials?)\b|Provider authentication failed/i.test(clean))
98
227
  ) {
99
228
  kind = 'authentication';
100
229
  summary = 'Provider authentication failed';
101
230
  recovery = 'Check your sign-in or API key, then try again.';
231
+ } else if (rejected429) {
232
+ kind = 'request';
233
+ summary = 'The provider rejected this request.';
234
+ recovery = 'Review the request details before trying again.';
102
235
  } else if (
236
+ ['invalid_request', 'unknown', 'request_too_large'].includes(descriptor.kind) &&
103
237
  /image.*dimensions.*exceed|image.*(?:too large|maximum.*(?:size|dimension))|many-image requests|An attached image exceeds the provider limit/i.test(
104
238
  clean
105
239
  )
@@ -108,20 +242,21 @@ export function describeError(value) {
108
242
  summary = 'An attached image exceeds the provider limit.';
109
243
  recovery = 'Reduce the image dimensions or number of images, then try again.';
110
244
  } else if (
111
- status === 413 ||
245
+ descriptor.kind === 'unknown' &&
112
246
  /payload too large|request (?:body )?too large|The request exceeds the size limit/i.test(clean)
113
247
  ) {
114
- kind = 'payload-size';
115
- summary = 'The request exceeds the size limit.';
116
- recovery = 'Reduce the attachments or request size, then try again.';
117
- } else if (
118
- !rejected429 &&
119
- (status === 429 ||
120
- /rate[_ -]?limit|quota.*(?:hit|exceed)|too many requests|The provider usage limit was reached/i.test(clean))
121
- ) {
122
- kind = 'rate-limit';
123
- summary = 'The provider usage limit was reached.';
124
- recovery = 'Wait for the limit to reset or choose another account.';
248
+ [kind, summary, recovery] = KIND_COPY.request_too_large;
249
+ } else if (copy) {
250
+ [kind, summary, recovery] = copy;
251
+ if (descriptor.kind === 'rate_limited' && retryAfterMs) {
252
+ recovery = `Try again in ${waitLabel(retryAfterMs)}.`;
253
+ recoveryTemplate = 'Try again in {{wait}}.';
254
+ params = { wait: waitLabel(retryAfterMs) };
255
+ } else if (descriptor.kind === 'quota_exhausted' && resetAt) {
256
+ recovery = `It resets at ${resetLabel(resetAt)}. Switch accounts or providers to continue now.`;
257
+ recoveryTemplate = 'It resets at {{time}}. Switch accounts or providers to continue now.';
258
+ params = { time: resetLabel(resetAt) };
259
+ }
125
260
  } else if (transport) {
126
261
  kind = 'connection';
127
262
  summary = transport.replace(/\.$/, '');
@@ -150,6 +285,11 @@ export function describeError(value) {
150
285
  kind,
151
286
  summary,
152
287
  recovery,
288
+ recoveryTemplate,
289
+ params,
290
+ providerKind: descriptor.kind,
291
+ resetAt,
292
+ retryAfterMs,
153
293
  details,
154
294
  // Known structured failures ignore volatile indexes; unknown failures
155
295
  // retain their diagnostic identity rather than merging unrelated work.
@@ -16,6 +16,7 @@
16
16
  import { resolve, sep } from 'node:path';
17
17
  import { clean } from './clean.mjs';
18
18
  import { isPlainObject } from './object.mjs';
19
+ import { parseMcpToolName } from './tool-primitives.mjs';
19
20
 
20
21
  export const EXTENSION_SCOPE_KINDS = Object.freeze(['skills', 'mcp', 'plugins']);
21
22
 
@@ -123,9 +124,10 @@ export function skillAllowedForCwd(scopes, skill, cwd) {
123
124
  return true;
124
125
  }
125
126
 
126
- export function mcpServerNameOfTool(toolName) {
127
- const match = /^mcp__(.+?)__(.+)$/.exec(clean(toolName));
128
- return match ? match[1] : '';
127
+ /** Server of an MCP tool definition or name (metadata first; see parseMcpToolName). */
128
+ export function mcpServerNameOfTool(toolOrName) {
129
+ const value = toolOrName && typeof toolOrName === 'object' ? toolOrName : clean(toolOrName);
130
+ return clean(parseMcpToolName(value)?.server);
129
131
  }
130
132
 
131
133
  /** New config with one entry replaced; `projects` null/empty removes it (global). */
@@ -177,16 +177,38 @@ function warnLockRateLimited(warnedAt, lockPath, line) {
177
177
  }
178
178
  }
179
179
 
180
- function reportLockWait(lockPath, waitedMs, mode) {
180
+ function formatLockTiming(timing) {
181
+ return Object.entries(timing || {})
182
+ .filter(([, ms]) => Number.isFinite(ms))
183
+ .map(([name, ms]) => ` ${name}=${ms}ms`)
184
+ .join('');
185
+ }
186
+
187
+ // Lets a lock owner's callback attribute its time (io gate wait, write) to the
188
+ // held-warning of the lock it runs under.
189
+ export function recordLockTiming(lockPath, fields) {
190
+ const lease = heldLockPaths.getStore()?.get(lockPath);
191
+ if (lease?.active) Object.assign(lease.timing, fields);
192
+ }
193
+
194
+ function reportLockWait(lockPath, waitedMs, mode, timing) {
181
195
  if (waitedMs < LOCK_WAIT_WARN_MS) return;
182
- warnLockRateLimited(lockWaitWarnedAt, lockPath, `[atomic-file] ${mode} lock wait ${waitedMs}ms: ${lockPath}\n`);
196
+ warnLockRateLimited(
197
+ lockWaitWarnedAt,
198
+ lockPath,
199
+ `[atomic-file] ${mode} lock wait ${waitedMs}ms:${formatLockTiming(timing)} ${lockPath}\n`
200
+ );
183
201
  }
184
202
 
185
203
  // A lock held this long starves every waiter (they time out at 2s), so the
186
204
  // holder's duration is reported once released.
187
- function reportLockHeld(lockPath, heldMs, mode) {
205
+ function reportLockHeld(lockPath, heldMs, mode, timing) {
188
206
  if (heldMs < LOCK_HELD_WARN_MS) return;
189
- warnLockRateLimited(lockHeldWarnedAt, lockPath, `[atomic-file] ${mode} lock held ${heldMs}ms: ${lockPath}\n`);
207
+ warnLockRateLimited(
208
+ lockHeldWarnedAt,
209
+ lockPath,
210
+ `[atomic-file] ${mode} lock held ${heldMs}ms:${formatLockTiming(timing)} ${lockPath}\n`
211
+ );
190
212
  }
191
213
 
192
214
  function* tryAcquireReclaimGuard(lockPath) {
@@ -387,7 +409,7 @@ export function sweepStagedLockFiles(lockPath, now = Date.now()) {
387
409
  return removed;
388
410
  }
389
411
 
390
- function* acquireLock(lockPath, opts, mode, markHeld) {
412
+ function* acquireLock(lockPath, opts, mode, markHeld, timing = null) {
391
413
  const timeoutMs = lockTimeoutMs(opts);
392
414
  const staleMs = lockStaleMs(opts);
393
415
  const deadline = Date.now() + timeoutMs;
@@ -415,7 +437,9 @@ function* acquireLock(lockPath, opts, mode, markHeld) {
415
437
  attempt += 1;
416
438
  continue;
417
439
  }
418
- reportLockWait(lockPath, Date.now() - waitStartedAt, mode);
440
+ const osMs = Date.now() - waitStartedAt;
441
+ if (timing) timing.osAcquireMs = osMs;
442
+ reportLockWait(lockPath, osMs, mode, timing);
419
443
  return;
420
444
  }
421
445
  throw timeoutError(lockPath, timeoutMs, lastError, yield* describeLockHolder(lockPath));
@@ -444,13 +468,15 @@ export function withFileLockSync(lockPath, fn, opts = {}) {
444
468
  export async function withFileLock(lockPath, fn, opts = {}) {
445
469
  if (heldLockPaths.getStore()?.get(lockPath)?.active) return fn();
446
470
  if (lockTimeoutMs(opts) <= 0) return withOsFileLock(lockPath, fn, opts);
447
- return lockQueue(lockPath, () => withOsFileLock(lockPath, fn, opts));
471
+ const enqueuedAt = Date.now();
472
+ return lockQueue(lockPath, () => withOsFileLock(lockPath, fn, opts, Date.now() - enqueuedAt));
448
473
  }
449
474
 
450
- async function withOsFileLock(lockPath, fn, opts = {}) {
451
- await runAsync(acquireLock(lockPath, opts, 'async', true));
475
+ async function withOsFileLock(lockPath, fn, opts = {}, queueWaitMs = 0) {
476
+ const timing = { queueWaitMs };
477
+ await runAsync(acquireLock(lockPath, opts, 'async', true, timing));
452
478
  const heldSince = Date.now();
453
- const lease = { active: false };
479
+ const lease = { active: false, timing };
454
480
  try {
455
481
  if (opts.secret === true) await enforceOwnerOnlyAclWin32Async(lockPath, { fresh: true });
456
482
  // Async descendants inherit a reference to this lease, not permanent
@@ -464,7 +490,7 @@ async function withOsFileLock(lockPath, fn, opts = {}) {
464
490
  try {
465
491
  await runAsync(releaseLock(lockPath));
466
492
  } finally {
467
- reportLockHeld(lockPath, Date.now() - heldSince, 'async');
493
+ reportLockHeld(lockPath, Date.now() - heldSince, 'async', timing);
468
494
  // Unmark only once the lock file is gone: a sync waiter arriving while
469
495
  // the release is still pending must fail fast, not block its completion.
470
496
  unmarkOsHeld(lockPath);
@@ -13,12 +13,13 @@ export const SERVER_SIDE_FALLBACK_BETA_HEADER = 'server-side-fallback-2026-07-01
13
13
  // `speed: "fast"` and Opus 4.6 silently runs at standard speed.
14
14
  const KNOWN_FAST_MODE_MODELS = /^claude-opus-(?:4-8|5)(?:$|[-@])/;
15
15
 
16
- // The id pattern is the offline floor. A model the pricing catalog declares
17
- // Fast-capable is accepted too, so a later release needs no edit here.
16
+ // The catalog's declaration (true or false) decides; the id pattern is only
17
+ // the offline floor for a model the catalog says nothing about.
18
18
  export function supportsAnthropicFastMode(model) {
19
19
  const id = normalizeClaudeModelId(model);
20
20
  if (!id) return false;
21
- return KNOWN_FAST_MODE_MODELS.test(id) || getModelMetadataSync(id, 'anthropic')?.supportsFastMode === true;
21
+ const declared = getModelMetadataSync(id, 'anthropic')?.supportsFastMode;
22
+ return typeof declared === 'boolean' ? declared : KNOWN_FAST_MODE_MODELS.test(id);
22
23
  }
23
24
 
24
25
  export function buildAnthropicBetaHeaders({