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
@@ -5,7 +5,7 @@
5
5
  // (runAskAgentLoop). Provider-not-available and trim failures throw here so
6
6
  // the caller marks the session errored instead of leaving stage='connecting'.
7
7
  import { getProvider } from '../../providers/registry.mjs';
8
- import { prepareExplicitSkills } from '../explicit-skills.mjs';
8
+ import { notifySkillsApplied, prepareExplicitSkills, recordAppliedSkills } from '../explicit-skills.mjs';
9
9
  import { prepareTurnEffortConfiguration } from '../../providers/effort-configuration.mjs';
10
10
  import { resolveTurnAutoEffort, turnStepAutoEffort } from './ask-turn-auto-effort.mjs';
11
11
  import { saveSessionAsync } from '../store.mjs';
@@ -27,16 +27,12 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
27
27
  // Register the live session object for synchronous close snapshots.
28
28
  runtime.session = session;
29
29
  if (!provider) throw new Error(`Provider "${session.provider}" not available`);
30
- const autoEffort = await resolveTurnAutoEffort({ sessionId, session, provider, input });
31
- const turnEffort = autoEffort?.applied ? autoEffort.effort : session.effort || null;
32
- const effortConfiguration = prepareTurnEffortConfiguration(
33
- session,
34
- provider,
35
- autoEffort?.applied ? autoEffort.effort : undefined
36
- );
37
- // Auto effort also judges every tool-result step of the turn when the
38
- // installed judge was trained on steps (session/loop/step-auto-effort.mjs).
39
- const stepAutoEffort = turnStepAutoEffort(autoEffort, effortConfiguration);
30
+ // The turn-level judgment runs while the rest of the turn is prepared; it
31
+ // reads the session as the turn found it (synchronously, on this call).
32
+ // The first request still carries its decision: it is awaited below,
33
+ // together with the user turn, before the effort configuration is stamped.
34
+ const autoEffortPending = resolveTurnAutoEffort({ sessionId, session, provider, input });
35
+ autoEffortPending.catch(() => {});
40
36
  applyTurnContextMeta(session, provider);
41
37
  const effectiveCwd = cwdOverride || session.cwd;
42
38
  // A failed-turn retry resubmits the prompt the failure left unanswered;
@@ -46,25 +42,49 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
46
42
  if (session.sessionStartMetaInjected !== true && !hasUserConversationMessage(session.messages)) {
47
43
  refreshSessionBp3Environment(session, effectiveCwd);
48
44
  }
45
+ // The prewarm only opens the transport (effort never shapes the socket), so
46
+ // it starts now instead of after the judgment.
49
47
  const startupProviderPrewarm = startProviderPrewarm({
50
48
  sessionId,
51
49
  session,
52
50
  provider,
53
- turnEffort,
54
- effortConfiguration,
51
+ turnEffort: session.effort || null,
52
+ effortConfiguration: null,
55
53
  codexTurnId,
56
54
  startedAtMs: startedAt,
57
55
  });
58
- const userTurn = await buildUserTurn({
56
+ const [autoEffort, userTurn] = await Promise.all([
57
+ autoEffortPending,
58
+ buildUserTurn({
59
+ session,
60
+ prompt: input.prompt,
61
+ context: input.context,
62
+ explicitPrefetch: input.explicitPrefetch,
63
+ turnSignal,
64
+ transcriptMeta,
65
+ turnPromptSource: input.promptSource,
66
+ effortConfiguration: null,
67
+ }),
68
+ ]);
69
+ const turnEffort = autoEffort?.applied ? autoEffort.effort : session.effort || null;
70
+ const effortConfiguration = prepareTurnEffortConfiguration(
59
71
  session,
60
- prompt: input.prompt,
61
- context: input.context,
62
- explicitPrefetch: input.explicitPrefetch,
63
- turnSignal,
64
- transcriptMeta,
65
- turnPromptSource: input.promptSource,
66
- effortConfiguration,
67
- });
72
+ provider,
73
+ autoEffort?.applied ? autoEffort.effort : undefined
74
+ );
75
+ // Auto effort also judges every tool-result step of the turn when the
76
+ // installed judge was trained on steps (session/loop/step-auto-effort.mjs).
77
+ const stepAutoEffort = turnStepAutoEffort(autoEffort, effortConfiguration);
78
+ if (effortConfiguration) {
79
+ // Stamp the turn's effort on the user message (same meta order as when it
80
+ // was built with it: before the runtime-context provenance).
81
+ const { runtimeUserContext, ...meta } = userTurn.userTurnMessage.meta || {};
82
+ userTurn.userTurnMessage.meta = {
83
+ ...meta,
84
+ effortConfiguration,
85
+ ...(runtimeUserContext ? { runtimeUserContext } : {}),
86
+ };
87
+ }
68
88
  const { historyMessages, outgoing } = userTurn;
69
89
  turn.userTurnContent = userTurn.userTurnMessage.content;
70
90
  turn.outgoing = outgoing;
@@ -151,9 +171,16 @@ export async function runAskAgentLoop({
151
171
  }
152
172
  try {
153
173
  if (!input.promptSource) {
154
- await prepareExplicitSkills(input.prompt, outgoing, session, { cwd: effectiveCwd, signal: turnSignal });
174
+ const applied = await prepareExplicitSkills(input.prompt, outgoing, session, {
175
+ cwd: effectiveCwd,
176
+ signal: turnSignal,
177
+ selection: input.skillSelection,
178
+ });
179
+ // The user turn message sits right after the history it was built on.
180
+ const userMessage = recordAppliedSkills(outgoing, outgoing[prepared.beforeCount - 1], applied);
155
181
  session.messages = filterModelVisibleSessionMessages(outgoing);
156
182
  checkpoint.schedule(true);
183
+ notifySkillsApplied(askOpts?.onSkillsApplied, applied, userMessage);
157
184
  }
158
185
  return await _api_call_with_interrupt(sessionId, (signal) =>
159
186
  agentLoop(
@@ -152,6 +152,10 @@ export function buildAgentLoopOptions({
152
152
  checkpoint.schedule(true);
153
153
  relay(askOpts?.onSteerMessage, text, detail);
154
154
  },
155
+ onSkillsApplied: (names, detail) => {
156
+ checkpoint.schedule(true);
157
+ relay(askOpts?.onSkillsApplied, names, detail);
158
+ },
155
159
  notifyFn: optionalFn(askOpts?.notifyFn),
156
160
  // Same projection compaction uses, so a session presents one identity on
157
161
  // every request it makes.
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
2
  import { mergeNormalizedContentEntries } from '../loop/steering.mjs';
3
3
  import { isDeliveredCompletion, logDuplicateSkip } from './delivered-completions.mjs';
4
- import { isInternalRuntimeNotificationText, promptContentText } from './prompt-utils.mjs';
4
+ import { promptContentText } from './prompt-utils.mjs';
5
5
  import { parseTaskNotification, taskNotificationId } from '../../../../shared/task-notification-envelope.mjs';
6
6
 
7
7
  const STALE_USER_INJECTION_TTL_MS = 30 * 60 * 1000;
@@ -158,6 +158,14 @@ export function lateDeliveryText(text, entry, now = Date.now()) {
158
158
  return `[late delivery: queued ${age} ago, before the current session owner started]\n${value}`;
159
159
  }
160
160
 
161
+ // A non-completion entry may be queued explicitly as a task notification (a
162
+ // runtime/channel notice, never the user typing): the mode survives
163
+ // normalization and the durable spool so the drain stores it as one and it
164
+ // never drives explicit skill selection.
165
+ function explicitModeField(entry) {
166
+ return entry?.mode === PENDING_MODE_TASK_NOTIFICATION ? { mode: PENDING_MODE_TASK_NOTIFICATION } : {};
167
+ }
168
+
161
169
  export function normalizePendingMessageEntry(entry) {
162
170
  if (typeof entry === 'string') {
163
171
  const text = entry.trim();
@@ -173,7 +181,10 @@ export function normalizePendingMessageEntry(entry) {
173
181
  id: pendingMessageId(entry),
174
182
  enqueuedAt: Number(entry.enqueuedAt) || Date.now(),
175
183
  };
176
- const entryOptions = entry.options && typeof entry.options === 'object' ? { options: entry.options } : {};
184
+ const entryOptions = {
185
+ ...(entry.options && typeof entry.options === 'object' ? { options: entry.options } : {}),
186
+ ...explicitModeField(entry),
187
+ };
177
188
  const marker =
178
189
  entry.notificationKind === COMPLETION_NOTIFICATION_KIND
179
190
  ? {
@@ -259,38 +270,35 @@ function normalizePersistedEntryBase(entry) {
259
270
  }
260
271
  : null;
261
272
  }
273
+ const mode = explicitModeField(entry);
262
274
  if (typeof entry.message === 'string') {
263
275
  const message = entry.message.trim();
264
- return message ? { id, message, enqueuedAt } : null;
276
+ return message ? { id, message, ...mode, enqueuedAt } : null;
265
277
  }
266
278
  if (Object.hasOwn(entry, 'content')) {
267
279
  const normalized = normalizePendingMessageEntry(entry);
268
280
  if (!normalized) return null;
269
281
  if (typeof normalized.content === 'string' && !normalized.options) {
270
- return { id, message: normalized.text || normalized.content, enqueuedAt };
282
+ return { id, message: normalized.text || normalized.content, ...mode, enqueuedAt };
271
283
  }
272
284
  return {
273
285
  id,
274
286
  content: normalized.content,
275
287
  text: normalized.text,
276
288
  ...(normalized.options ? { options: normalized.options } : {}),
289
+ ...mode,
277
290
  enqueuedAt,
278
291
  };
279
292
  }
280
293
  const text = pendingMessageText(entry);
281
- return text ? { id, message: text, enqueuedAt } : null;
294
+ return text ? { id, message: text, ...mode, enqueuedAt } : null;
282
295
  }
283
296
 
284
297
  export function modelVisiblePendingMessages(messages) {
285
- return (Array.isArray(messages) ? messages : [])
286
- .map(pendingMessageQueueEntry)
287
- .filter(Boolean)
288
- .filter(
289
- (message) =>
290
- !isInternalRuntimeNotificationText(
291
- message && typeof message === 'object' && Object.hasOwn(message, 'content') ? message.content : message
292
- )
293
- );
298
+ // No content filter: isInternalRuntimeNotificationText is a display-only
299
+ // predicate. Every canonical completion envelope matches it, and a drained
300
+ // entry the model never sees is never claimed/acked/released.
301
+ return (Array.isArray(messages) ? messages : []).map(pendingMessageQueueEntry).filter(Boolean);
294
302
  }
295
303
 
296
304
  export function _mergePendingMessageEntries(entries) {
@@ -216,7 +216,7 @@ function releaseSessionReadCaches(id) {
216
216
  /* ignore */
217
217
  }
218
218
  try {
219
- forgetSessionSaveBaseline(id);
219
+ forgetSessionSaveBaseline(id, { releaseWriteControl: true });
220
220
  } catch {
221
221
  /* ignore */
222
222
  }
@@ -1,6 +1,6 @@
1
1
  import { sanitizeToolPairs } from '../context-utils.mjs';
2
2
  import { collectAssistantToolCallIds as assistantToolCallIds } from '../context-tool-pairs.mjs';
3
- import { isInternalRuntimeNotificationText, promptContentText } from './prompt-utils.mjs';
3
+ import { promptContentText } from './prompt-utils.mjs';
4
4
  import { filterModelVisibleSessionMessages } from './message-sanitize.mjs';
5
5
  import { journalDelta } from './turn-interruption-journal.mjs';
6
6
 
@@ -72,8 +72,11 @@ function finalizeInterruptedTurn({
72
72
  const index = provisionalUserTurnIndex(messages, currentUserContent);
73
73
  return index >= 0 && index < messages.length - 1;
74
74
  })();
75
- const preserveResponse =
76
- responseStarted && !isInternalRuntimeNotificationText(currentUserContent) && (producedOutput || !userCancelled);
75
+ // A turn woken by a task notification is preserved by the same rule as a
76
+ // user prompt: once it produced output its notification is consumed (the
77
+ // host commits it and never requeues it), so rewinding it would erase the
78
+ // turn and its side effects from history.
79
+ const preserveResponse = responseStarted && (producedOutput || !userCancelled);
77
80
  if (!preserveResponse) {
78
81
  if (!userCancelled) {
79
82
  return {
@@ -198,8 +201,11 @@ export function createTurnInterruptionTracker() {
198
201
  // copies of the same first sentence). Reference agents never
199
202
  // commit a replayed attempt's discarded stream at all; the
200
203
  // tombstone exists only for the window between a retraction and
201
- // its replacement.
202
- if (state.tombstonedAssistantContent) {
204
+ // its replacement. Whitespace is not a replacement: it is not output
205
+ // (finalizeInterruptedTurn trims, and the host commits the prompt only
206
+ // on non-blank text), so until real text arrives the retracted bytes
207
+ // remain the only output the user saw.
208
+ if (state.tombstonedAssistantContent && value.trim()) {
203
209
  state.tombstonedAssistantContent = '';
204
210
  state.tombEpoch += 1;
205
211
  }
@@ -212,8 +218,13 @@ export function createTurnInterruptionTracker() {
212
218
  // REPLACE, never accumulate: consecutive retractions belong to
213
219
  // successive attempts at the SAME answer, so keeping the earlier
214
220
  // tombstone alongside the newer one is what multiplied the visible
215
- // text across retries.
216
- state.tombstonedAssistantContent = state.partialAssistantContent.slice(cutAt);
221
+ // text across retries. A whitespace-only retraction is not a newer
222
+ // attempt at the answer (recordTextDelta never let it supersede the
223
+ // draft), so it never replaces a meaningful tombstone.
224
+ const retracted = state.partialAssistantContent.slice(cutAt);
225
+ if (retracted.trim() || !state.tombstonedAssistantContent.trim()) {
226
+ state.tombstonedAssistantContent = retracted;
227
+ }
217
228
  state.partialAssistantContent = state.partialAssistantContent.slice(0, cutAt);
218
229
  state.textEpoch += 1;
219
230
  state.tombEpoch += 1;
@@ -72,7 +72,6 @@ export {
72
72
  // attach-on-resume guard) — resolved through the manager facade so the
73
73
  // session-runtime turn API reaches it as mgr.publishSessionPresence.
74
74
  export { publishSessionPresence, deleteSessionPresence } from './store.mjs';
75
- export { isInternalRuntimeNotificationText as _isInternalRuntimeNotificationText } from './manager/prompt-utils.mjs';
76
75
 
77
76
  // ── Usage-metrics surface — re-exported unchanged so loop.mjs / smoke scripts
78
77
  // keep resolving these through the facade. ────────────────────────────────
@@ -408,9 +408,10 @@ function adoptCompactedTranscript(ctx, run) {
408
408
  // providerState so the next send starts a fresh chain.
409
409
  ctx.providerState = undefined;
410
410
  ctx.providerStateCleared = true;
411
- // Compaction shrank the transcript, so prior turns no longer pressure the
412
- // window — reset the iteration counter so a steadily-compacting long task
413
- // isn't killed by the cap, while a non-compacting tight loop still hits it.
411
+ // Compaction starts a new loop epoch: the iteration counter restarts at 0
412
+ // so iteration indices (traces, usage metrics, cross-turn dedup windows)
413
+ // count from the compacted transcript. The counter bounds nothing — the
414
+ // loop has no iteration cap.
414
415
  ctx.iterations = 0;
415
416
  // New loop epoch so persistIterationMetrics idempotency keys do not
416
417
  // collide when iteration indices restart at 1 (incl. iter 1 → iter 1).
@@ -12,6 +12,7 @@ import {
12
12
  isRetryableWireErrorEvent,
13
13
  isRetryableStreamErrorEvent,
14
14
  isProviderRecoveryExhausted,
15
+ isProviderRecoveryTerminal,
15
16
  isServerUnavailable,
16
17
  isStreamCut,
17
18
  isToolInputCut,
@@ -20,7 +21,9 @@ import {
20
21
  resetStallRetryBudget,
21
22
  resolveStallRetryBudget,
22
23
  retryAfterMsFromError,
24
+ sleepWithAbort,
23
25
  } from '../providers/retry-classifier.mjs';
26
+ import { DETERMINISTIC_KINDS, providerErrorKind } from '../providers/provider-error-kind.mjs';
24
27
  import {
25
28
  persistsConfirmedImageRejection,
26
29
  promptHasInlineImages,
@@ -34,7 +37,6 @@ import {
34
37
  isThinkingReplayRejection,
35
38
  repairThinkingReplayInPlace,
36
39
  } from './thinking-replay-recovery.mjs';
37
- import { setTimeout as sleepMs } from 'node:timers/promises';
38
40
  import { readStreamOutcome } from '../providers/lib/stream-outcome.mjs';
39
41
  import { cloneProviderReplay } from '../providers/lib/provider-replay.mjs';
40
42
  import { resolveWorkerCompactPolicy } from './loop/compact-policy.mjs';
@@ -126,10 +128,10 @@ const RECOVERY_RETRY_BACKOFF_MS = retryLadderFromEnv(
126
128
  [15_000, 30_000, 60_000, 60_000, 60_000]
127
129
  );
128
130
 
129
- // A server-advised wait (Retry-After) replaces the ladder step: a provider that
130
- // names its own cooldown knows better, within reason — one loop-level wait
131
- // never parks longer than this.
132
- const LOOP_RETRY_AFTER_CAP_MS = 120_000;
131
+ // A server-advised wait (Retry-After / retryAfterMs) replaces the ladder step
132
+ // and is honored in full, never shortened. The ladder length
133
+ // still bounds how many replays run, the wait stays cancellable, and the
134
+ // reconnecting stage reports its remaining delay (retryAt).
133
135
 
134
136
  // Jittering every backoff by 0.9–1.1 means parallel workers that lose the
135
137
  // same uplink do not resume in lockstep; our provider layers already jitter
@@ -138,14 +140,42 @@ const TRANSPORT_RETRY_JITTER_RATIO = 0.1;
138
140
 
139
141
  /** The ladder this failure class is entitled to: an unreachable network, a
140
142
  * provider still unavailable after its own retries, or an ordinary fault. */
141
- function retryLadderFor(error) {
143
+ function retryLadderFor(error, state = null) {
142
144
  if (isConnectFailure(error)) return CONNECTION_RETRY_BACKOFF_MS;
143
- if (isProviderRecoveryExhausted(error) && isServerUnavailable(error)) return RECOVERY_RETRY_BACKOFF_MS;
145
+ if (isProviderRecoveryExhausted(error) && isServerUnavailable(error) && recoveryCyclesEnabled(state)) {
146
+ return RECOVERY_RETRY_BACKOFF_MS;
147
+ }
144
148
  return TRANSPORT_RETRY_BACKOFF_MS;
145
149
  }
146
150
 
147
- function retryBudgetFor(error) {
148
- return retryLadderFor(error).length;
151
+ // retryPolicy.recoveryCycles:false — a provider that already ran its own full
152
+ // ladder surfaces its spent failure instead of parking on
153
+ // the 15/30/60/60/60s recovery cycles.
154
+ function recoveryCyclesEnabled(state) {
155
+ return loopRetryPolicy(state).recoveryCycles !== false;
156
+ }
157
+
158
+ // Per-provider loop retry policy, read off the provider object
159
+ // (`provider.retryPolicy`), default = the bounded ladders:
160
+ // unboundedConnectionRetry — a connect failure keeps waiting on the
161
+ // 5/10/20/40/60s… ladder (last step repeating) until the network returns;
162
+ // still cancellable, with "waiting for network" progress.
163
+ // contentFilterRetryable — a content_filter refusal is replayed
164
+ // under the transport budget with a contextual guidance notice.
165
+ // inBandInvalidRequestRetryable — an in-band stream error whose only
166
+ // deterministic evidence is its invalid_request envelope is replayed.
167
+ function loopRetryPolicy(state) {
168
+ const policy = state?.provider?.retryPolicy;
169
+ return policy && typeof policy === 'object' ? policy : {};
170
+ }
171
+
172
+ function waitsForNetwork(error, state) {
173
+ return loopRetryPolicy(state).unboundedConnectionRetry === true && isConnectFailure(error);
174
+ }
175
+
176
+ function retryBudgetFor(error, state = null) {
177
+ if (waitsForNetwork(error, state)) return Number.POSITIVE_INFINITY;
178
+ return retryLadderFor(error, state).length;
149
179
  }
150
180
 
151
181
  // A provider ladder spent on a connect failure has only shown that the network
@@ -153,15 +183,19 @@ function retryBudgetFor(error) {
153
183
  // unavailable parks on the recovery cycles, but only while nothing has reached
154
184
  // the user — a replay would otherwise re-show output or re-run a tool. Every
155
185
  // other exhausted failure surfaces as-is.
156
- function providerRecoveryEnded(error, outcome) {
186
+ // A provider VERDICT that recovery is over (an advice-gated denial, the
187
+ // repeated-overload stop) always ends it: the loop never overrides it.
188
+ function providerRecoveryEnded(error, outcome, state = null) {
189
+ if (isProviderRecoveryTerminal(error)) return true;
157
190
  if (!isProviderRecoveryExhausted(error) || isConnectFailure(error)) return false;
191
+ if (!recoveryCyclesEnabled(state)) return true;
158
192
  return !(outcome.replaySafe === true && isServerUnavailable(error));
159
193
  }
160
194
 
161
- function transportRetryWaitMs(attemptIndex, error) {
195
+ function transportRetryWaitMs(attemptIndex, error, state = null) {
162
196
  const advisedMs = retryAfterMsFromError(error);
163
- if (advisedMs != null) return Math.min(advisedMs, LOOP_RETRY_AFTER_CAP_MS);
164
- const ladder = retryLadderFor(error);
197
+ if (advisedMs != null) return advisedMs;
198
+ const ladder = retryLadderFor(error, state);
165
199
  const base = ladder[Math.min(Math.max(attemptIndex, 0), ladder.length - 1)];
166
200
  return jitterDelayMs(base, TRANSPORT_RETRY_JITTER_RATIO);
167
201
  }
@@ -258,6 +292,46 @@ function traceLoop({ sessionId, nextIteration }, kind, fields) {
258
292
  // 5s/15s wait rendered as a frozen turn with no explanation, so the stage
259
293
  // change surfaces "Reconnecting... n/max" instead of leaving the user
260
294
  // staring at a stalled stream.
295
+ // Unbounded connect-failure wait (retryPolicy.unboundedConnectionRetry): no
296
+ // "n/max" — the turn waits until the network is back, counting down each step.
297
+ function emitWaitingForNetwork(opts, { attempt, waitMs, classifier, error }) {
298
+ if (typeof opts?.onStageChange !== 'function') return;
299
+ try {
300
+ const wait = Math.max(0, Number(waitMs) || 0);
301
+ opts.onStageChange('reconnecting', {
302
+ attempt,
303
+ max: null,
304
+ waitMs: wait,
305
+ retryAt: Date.now() + wait,
306
+ reason: 'waiting for network',
307
+ waitingForNetwork: true,
308
+ classifier: classifier || error?.code || null,
309
+ message: `Reconnecting… waiting for network · attempt ${attempt}${wait > 0 ? ` in ${Math.ceil(wait / 1000)}s` : ''}`,
310
+ });
311
+ } catch {
312
+ /* display-only */
313
+ }
314
+ }
315
+
316
+ // content_filter on a path that opted in (retryPolicy.contentFilterRetryable,
317
+ // or the provider boundary stamping `contentFilterRetryable` on the error).
318
+ // A provider-terminal verdict outranks the opt-in.
319
+ function isReplayableContentFilter(sendErr, state) {
320
+ if (isProviderRecoveryTerminal(sendErr)) return false;
321
+ const optedIn = loopRetryPolicy(state).contentFilterRetryable === true || sendErr?.contentFilterRetryable === true;
322
+ return optedIn && providerErrorKind(sendErr).code === 'content_filter';
323
+ }
324
+
325
+ // The guidance travels as a contextual runtime fragment: the
326
+ // [mixdog-runtime] prefix keeps it out of the visible transcript.
327
+ const CONTENT_FILTER_NOTICE =
328
+ '[mixdog-runtime] The previous response was stopped by the provider content filter before it completed. ' +
329
+ 'Continue the task, but rephrase or avoid the content that likely triggered the filter; do not repeat it verbatim.';
330
+
331
+ function contentFilterNotice() {
332
+ return { role: 'user', content: CONTENT_FILTER_NOTICE, meta: { source: 'content-filter-recovery' } };
333
+ }
334
+
261
335
  function emitLoopReconnectProgress(opts, { attempt, maxAttempts, waitMs, classifier, error }) {
262
336
  emitProviderRetryStage(opts?.onStageChange, {
263
337
  attempt,
@@ -353,10 +427,22 @@ async function recoverFromSendError(sendErr, state) {
353
427
  }
354
428
  // Clean transient transport failure with zero exposure: replay the send
355
429
  // after a bounded wait instead of failing the turn.
430
+ if (isReplayableContentFilter(sendErr, state)) {
431
+ if (transportRetriesUsed < TRANSPORT_RETRY_MAX && (outcome.replaySafe === true || (await retract()))) {
432
+ return scheduleTransportReplay(sendErr, state, {
433
+ budget: TRANSPORT_RETRY_MAX,
434
+ note: `[loop] content filter stopped the response (${loopTag(state)}); replaying with guidance`,
435
+ trace: { kind: 'content_filter_retry' },
436
+ classifier: 'content_filter',
437
+ notice: contentFilterNotice(),
438
+ });
439
+ }
440
+ throw sendErr;
441
+ }
356
442
  if (
357
- transportRetriesUsed < retryBudgetFor(sendErr) &&
358
- !providerRecoveryEnded(sendErr, outcome) &&
359
- (await transportReplayPermitted(sendErr, outcome, retract))
443
+ transportRetriesUsed < retryBudgetFor(sendErr, state) &&
444
+ !providerRecoveryEnded(sendErr, outcome, state) &&
445
+ (await transportReplayPermitted(sendErr, outcome, retract, state))
360
446
  ) {
361
447
  return retryTransport(sendErr, state);
362
448
  }
@@ -373,7 +459,7 @@ async function recoverFromSendError(sendErr, state) {
373
459
  ) {
374
460
  return repairThinkingReplay(recoveryMessages, state);
375
461
  }
376
- // Grok Build RetryWithImageStrip: drop inline images and replay once. Safe
462
+ // Image-strip retry: drop inline images and replay once. Safe
377
463
  // only when no tool was dispatched. Text exposure must be retracted first;
378
464
  // reasoning-only high-effort streams can retry without a text reset.
379
465
  if (
@@ -464,7 +550,7 @@ function isExposedNoToolStall(sendErr, outcome) {
464
550
  // 'transient' and never fired).
465
551
  async function recoverExposedStall(sendErr, outcome, state, retract) {
466
552
  const { transportRetriesUsed = 0 } = state;
467
- const exposedRetryBudget = retryBudgetFor(sendErr);
553
+ const exposedRetryBudget = retryBudgetFor(sendErr, state);
468
554
  const len = sendErr.partialContent.length;
469
555
  if (transportRetriesUsed < exposedRetryBudget && !isProviderRecoveryExhausted(sendErr) && (await retract())) {
470
556
  return scheduleTransportReplay(sendErr, state, {
@@ -491,12 +577,17 @@ async function recoverExposedStall(sendErr, outcome, state, retract) {
491
577
  async function scheduleTransportReplay(sendErr, state, { budget, note, trace, classifier, notice = null }) {
492
578
  const { opts, signal, transportRetriesUsed = 0 } = state;
493
579
  const attempt = transportRetriesUsed + 1;
494
- const waitMs = transportRetryWaitMs(transportRetriesUsed, sendErr);
580
+ const waitMs = transportRetryWaitMs(transportRetriesUsed, sendErr, state);
495
581
  logLoop(`${note}; transport retry ${attempt}/${budget} after ${waitMs}ms`);
496
582
  const { kind, ...fields } = trace;
497
583
  traceLoop(state, kind, { attempt, waitMs, ...fields });
498
- emitLoopReconnectProgress(opts, { attempt, maxAttempts: budget, waitMs, classifier, error: sendErr });
499
- await sleepMs(waitMs, undefined, signal ? { signal } : undefined);
584
+ if (Number.isFinite(budget)) {
585
+ emitLoopReconnectProgress(opts, { attempt, maxAttempts: budget, waitMs, classifier, error: sendErr });
586
+ } else {
587
+ emitWaitingForNetwork(opts, { attempt, waitMs, classifier, error: sendErr });
588
+ }
589
+ // Chunked, abortable sleep: honored advice may exceed one timer's range.
590
+ await sleepWithAbort(waitMs, signal, undefined, 'transport retry wait aborted');
500
591
  return beginFreshTransportAttempt(opts, budget, notice);
501
592
  }
502
593
 
@@ -542,7 +633,7 @@ function continueFromShownText(sendErr, state, relayWitness) {
542
633
  // budget still bounds it.
543
634
  async function recoverToolInputCut(sendErr, outcome, state, retract) {
544
635
  const { transportRetriesUsed = 0 } = state;
545
- const budget = retryBudgetFor(sendErr);
636
+ const budget = retryBudgetFor(sendErr, state);
546
637
  if (transportRetriesUsed < budget && (outcome.replaySafe === true || (await retract()))) {
547
638
  return scheduleTransportReplay(sendErr, state, {
548
639
  budget,
@@ -650,20 +741,55 @@ function recoverPartialToolCalls(sendErr, state, toolCalls, outcome) {
650
741
  // reasoning/text exposure makes classifyError() intentionally permanent,
651
742
  // non-terminal close, or a dropped connection (undici `terminated`, socket
652
743
  // reset) — then retract the exposed text and replay.
653
- async function transportReplayPermitted(sendErr, outcome, retract) {
654
- if (outcome.replaySafe === true && classifyError(sendErr) === 'transient') return true;
655
- const transportSymptom =
744
+ async function transportReplayPermitted(sendErr, outcome, retract, state) {
745
+ if (!isSemanticallyRetryable(sendErr, state)) return false;
746
+ // Existing replay-safety gate: an unexposed failure replays outright only
747
+ // when it is typed-transient (or an opted-in in-band invalid_request); a
748
+ // named symptom replays after retraction.
749
+ if (
750
+ outcome.replaySafe === true &&
751
+ (classifyError(sendErr) === 'transient' || inBandInvalidRequestReplay(sendErr, state))
752
+ ) {
753
+ return true;
754
+ }
755
+ return await retract();
756
+ }
757
+
758
+ // The ONE semantic verdict on whether this failure is worth re-issuing,
759
+ // independent of what was exposed (the replay-safety gate decides that
760
+ // afterwards): never a deterministic refusal (quota, plan, billing, policy,
761
+ // auth, permission, context or size) — server advice never makes one
762
+ // retryable — and otherwise a named transient/transport symptom.
763
+ function inBandInvalidRequestReplay(sendErr, state) {
764
+ return (
765
+ loopRetryPolicy(state).inBandInvalidRequestRetryable === true &&
766
+ providerErrorKind(sendErr).kind === 'invalid_request' &&
767
+ isRetryableStreamErrorEvent(sendErr)
768
+ );
769
+ }
770
+
771
+ function isSemanticallyRetryable(sendErr, state) {
772
+ if (isProviderRecoveryTerminal(sendErr)) return false;
773
+ // Opt-in (retryPolicy.inBandInvalidRequestRetryable): a provider whose
774
+ // in-band stream errors carry an invalid_request envelope for server faults
775
+ // replays one with no other deterministic evidence; for every other
776
+ // provider it is a final refusal.
777
+ const { kind } = providerErrorKind(sendErr);
778
+ if (DETERMINISTIC_KINDS.has(kind) && !inBandInvalidRequestReplay(sendErr, state)) {
779
+ return false;
780
+ }
781
+ return (
656
782
  classifyError(sendErr) === 'transient' ||
657
783
  isStreamCut(sendErr) ||
658
784
  isRetryableWireErrorEvent(sendErr) ||
659
785
  isRetryableStreamErrorEvent(sendErr) ||
660
- isCursorTransientTransportError(sendErr);
661
- return transportSymptom && (await retract());
786
+ isCursorTransientTransportError(sendErr)
787
+ );
662
788
  }
663
789
 
664
790
  function retryTransport(sendErr, state) {
665
791
  return scheduleTransportReplay(sendErr, state, {
666
- budget: retryBudgetFor(sendErr),
792
+ budget: retryBudgetFor(sendErr, state),
667
793
  note:
668
794
  `[loop] transient send failure with no dispatched tools (${loopTag(state)} code=${sendErr?.code ?? 'n/a'} ` +
669
795
  `wsCloseCode=${sendErr?.wsCloseCode ?? 'n/a'} httpStatus=${sendErr?.httpStatus ?? sendErr?.status ?? 'n/a'})`,
@@ -700,7 +826,7 @@ function imageStripEligible(
700
826
  hasImages: promptHasInlineImages(recoveryMessages),
701
827
  alreadyStripped: imageStripUsed === true,
702
828
  }) ||
703
- // Grok Build RetryWithImageStrip only: a bare stream error on a Grok
829
+ // Grok only: a bare stream error on a Grok
704
830
  // upload may be the image body being rejected. Other providers' transient
705
831
  // stream errors are not evidence about images and use normal transport retry.
706
832
  (isGrok &&
@@ -30,6 +30,7 @@ import { _saveAsyncQueued, _saveWorkerPending } from './save-worker.mjs';
30
30
  import { STORED_SESSION_UNREADABLE, _ensureLifecycleFields, _storedSessionFromFile } from './serialize.mjs';
31
31
  import { _savePending } from './pending-saves.mjs';
32
32
  import { isOrdinarySession } from '../store-summary-visibility.mjs';
33
+ import { closeSessionCatalogDb } from '../catalog-db/index.mjs';
33
34
 
34
35
  // Disk mtime of the summary index when the in-memory cache was last refreshed
35
36
  // from it — the cross-process staleness detector.
@@ -145,8 +146,9 @@ function scheduleSessionSummaryIndexRebuild() {
145
146
 
146
147
  /**
147
148
  * Resolves once no summary-index writer of this process is running: the
148
- * background rebuild worker a cold listing (or sweep) spawned has exited and
149
- * every queued index mutation has landed. Both write the index, its lock and
149
+ * background rebuild worker a cold listing (or sweep) spawned has exited,
150
+ * every queued index mutation has landed and the session catalog DB is
151
+ * closed. Both write the index, its lock and
150
152
  * temp files into the data dir after the listing/sweep/save that caused them
151
153
  * has returned, so an owner that removes or switches the data dir awaits this
152
154
  * first.
@@ -159,6 +161,8 @@ export async function settleSessionSummaryIndex() {
159
161
  await new Promise((resolve) => worker.once('exit', resolve));
160
162
  }
161
163
  await settleSummaryIndexWrites();
164
+ // The daemon's session catalog DB holds its file, WAL and lock open.
165
+ await closeSessionCatalogDb();
162
166
  }
163
167
 
164
168
  export function listStoredSessionSummaries(options = {}) {
@@ -1,5 +1,5 @@
1
1
  import { MessageChannel, Worker } from 'node:worker_threads';
2
- import { guardedSaveOptions as _guardedSaveOptions } from './write-guards.mjs';
2
+ import { guardedSaveOptions as _guardedSaveOptions, releaseSessionWriteControl } from './write-guards.mjs';
3
3
  import { _ensureLifecycleFields } from './serialize.mjs';
4
4
  import {
5
5
  _attemptEvidence,
@@ -63,9 +63,12 @@ export { _hasSessionSaveBaseline } from './save-delta.mjs';
63
63
  * the writes, so a write already posted for `id` still lands first; the next
64
64
  * save for `id`, if any, simply goes out as a full snapshot.
65
65
  */
66
- export function forgetSessionSaveBaseline(id) {
66
+ export function forgetSessionSaveBaseline(id, { releaseWriteControl = false } = {}) {
67
67
  if (!id) return false;
68
68
  const had = _dropDeltaBaseline(id);
69
+ // Only when the session itself is gone (close/teardown/delete): a mid-life
70
+ // forget (compaction) must keep the landed-epoch fence for in-flight writes.
71
+ if (releaseWriteControl) releaseSessionWriteControl(String(id));
69
72
  if (_saveWorker) {
70
73
  try {
71
74
  _saveWorker.postMessage({ __forgetBase: String(id) });
@@ -854,7 +857,7 @@ export function purgeSessionSaveBookkeeping(id) {
854
857
  }
855
858
  }
856
859
  // Also frees the worker-side base: a deleted id never saves again.
857
- forgetSessionSaveBaseline(id);
860
+ forgetSessionSaveBaseline(id, { releaseWriteControl: true });
858
861
  return purged;
859
862
  }
860
863