mixdog 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (557) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/gate-local.mjs +19 -3
  4. package/scripts/model-catalog-audit.mjs +1 -15
  5. package/scripts/provider-pingpong-live.mjs +509 -0
  6. package/src/agents/advisor/AGENT.md +1 -1
  7. package/src/agents/advisor/agent.json +1 -1
  8. package/src/agents/reviewer/AGENT.md +3 -3
  9. package/src/agents/reviewer/agent.json +1 -1
  10. package/src/defaults/shipped-history.json +6 -0
  11. package/src/defaults/skills/setup/SKILL.md +6 -1
  12. package/src/defaults/skills/setup/references/actions.md +44 -22
  13. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  14. package/src/headless-exec.mjs +15 -8
  15. package/src/lib/keychain-cjs.cjs +7 -4
  16. package/src/output-styles/common.md +11 -10
  17. package/src/repl/slash-commands.mjs +16 -1
  18. package/src/rules/agent/AGENT.md +5 -5
  19. package/src/rules/lead/LEAD.md +5 -5
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  21. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  22. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  23. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  24. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
  33. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  34. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  35. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  36. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  37. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  38. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  39. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  40. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  41. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  43. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  44. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  45. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +98 -0
  46. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  47. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  48. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +48 -37
  49. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +10 -7
  50. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  51. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  52. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  54. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  55. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +102 -45
  58. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  61. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  63. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  65. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +10 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +40 -7
  70. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  71. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  72. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +88 -21
  73. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +30 -10
  74. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +106 -0
  75. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  76. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +94 -15
  77. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +117 -72
  79. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  80. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +75 -10
  81. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  83. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +103 -23
  84. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  85. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  86. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +32 -6
  87. package/src/runtime/agent/orchestrator/providers/cursor.mjs +27 -3
  88. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  89. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  90. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  91. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  92. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  93. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +80 -18
  94. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  95. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +46 -8
  96. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +273 -0
  97. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  99. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  100. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  101. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  102. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +369 -112
  103. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +3 -1
  104. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +140 -35
  105. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +70 -24
  106. package/src/runtime/agent/orchestrator/providers/gemini.mjs +80 -29
  107. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  108. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  109. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +69 -19
  110. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  113. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  114. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  115. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  116. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  117. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  118. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  119. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  120. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  121. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  122. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  123. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  124. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  125. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  126. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  127. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  128. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  129. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  130. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +123 -61
  132. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  133. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +27 -2
  134. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  135. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  136. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  137. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  138. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  139. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  140. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  141. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  142. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +64 -23
  143. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +79 -33
  144. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  145. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  146. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  147. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +144 -15
  148. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  149. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  150. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +9 -2
  151. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +109 -26
  153. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +27 -6
  154. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +34 -20
  155. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +23 -4
  156. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +37 -28
  157. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  158. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  159. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  160. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +43 -15
  161. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  163. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +5 -2
  164. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  165. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +7 -3
  166. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  167. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  168. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  169. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  170. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +37 -4
  171. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +12 -1
  172. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  173. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -1
  174. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +76 -33
  175. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  176. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  177. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  178. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  179. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  180. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  181. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  182. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  183. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  184. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  185. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  186. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  187. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  188. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +41 -22
  189. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  190. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  191. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  192. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +720 -0
  193. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  195. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +123 -219
  196. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  197. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  198. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  199. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  200. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  201. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  202. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  203. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  204. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  205. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  206. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  207. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  208. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  209. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  210. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  211. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  212. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
  213. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  214. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  215. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  216. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  217. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  218. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  219. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  220. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  221. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  222. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +5 -1
  223. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  224. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  225. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  226. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  227. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  228. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  229. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  230. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  231. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  232. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  233. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  234. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  235. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  236. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  237. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  238. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  239. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +67 -20
  240. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +104 -19
  241. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  242. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  243. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  244. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  245. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  246. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
  248. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
  249. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  250. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  251. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +57 -21
  252. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  253. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  254. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  255. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  256. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
  257. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  258. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +16 -4
  259. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  260. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  261. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  262. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  263. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  264. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  265. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  266. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  267. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +168 -29
  268. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  269. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  270. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  271. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  272. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  273. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  274. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  275. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  276. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  277. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  278. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  279. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  280. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  281. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +6 -0
  282. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +13 -0
  283. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +13 -1
  284. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  285. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  286. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  287. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  288. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  289. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  290. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  291. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  292. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
  293. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  294. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  295. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  296. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  297. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  298. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  299. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  300. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  301. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  302. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  303. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  304. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  305. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  306. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  307. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  308. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  309. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  310. package/src/runtime/github/tool.mjs +92 -2
  311. package/src/runtime/memory/index.mjs +1 -0
  312. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  313. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  314. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  315. package/src/runtime/memory/lib/pg/process.mjs +65 -50
  316. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  317. package/src/runtime/memory/lib/pg/supervisor.mjs +124 -16
  318. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  319. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  320. package/src/runtime/office/README.md +2 -2
  321. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  322. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  323. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  324. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  325. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  326. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  327. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  328. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  329. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  330. package/src/runtime/office/core/office-sessions.mjs +5 -0
  331. package/src/runtime/office/core/office-transactions.mjs +3 -0
  332. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  333. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  334. package/src/runtime/office/index.mjs +30 -0
  335. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  336. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  337. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  338. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  339. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  340. package/src/runtime/office/shared/values.mjs +0 -5
  341. package/src/runtime/shared/background-tasks.mjs +92 -14
  342. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  343. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  344. package/src/runtime/shared/buffered-appender.mjs +18 -0
  345. package/src/runtime/shared/config.mjs +78 -18
  346. package/src/runtime/shared/err-text.mjs +31 -0
  347. package/src/runtime/shared/error-presentation.mjs +159 -19
  348. package/src/runtime/shared/file-permissions.d.mts +6 -1
  349. package/src/runtime/shared/file-permissions.mjs +54 -6
  350. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  351. package/src/runtime/shared/llm/anthropic-effort.mjs +129 -134
  352. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  353. package/src/runtime/shared/llm/model-catalog.mjs +53 -40
  354. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  355. package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
  356. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  357. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  358. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  359. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  360. package/src/runtime/shared/loopback-listener.mjs +9 -0
  361. package/src/runtime/shared/model-display.mjs +13 -0
  362. package/src/runtime/shared/provider-api-key.mjs +2 -0
  363. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  364. package/src/runtime/shared/review-diff.mjs +52 -0
  365. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  366. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  367. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  368. package/src/runtime/shared/slow-span.mjs +114 -0
  369. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  370. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  371. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  372. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  373. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  374. package/src/runtime/shared/tool-card-model.mjs +5 -2
  375. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  376. package/src/runtime/shared/tool-primitives.mjs +13 -0
  377. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  378. package/src/runtime/shared/tool-surface.mjs +18 -3
  379. package/src/runtime/shared/tool-work-units.mjs +40 -4
  380. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  381. package/src/runtime/shared/transcript-writer.mjs +10 -2
  382. package/src/runtime/shared/turn-changes.mjs +670 -274
  383. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  384. package/src/runtime/shared/turn-snapshot-store.mjs +94 -2
  385. package/src/runtime/shared/turn-snapshot.mjs +986 -690
  386. package/src/runtime/shared/turn-worktree-snapshot.mjs +279 -262
  387. package/src/runtime/shared/user-data-guard.mjs +262 -49
  388. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  389. package/src/runtime/shared/worktrees/contract.mjs +34 -24
  390. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  391. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  392. package/src/runtime/shared/worktrees/git.mjs +8 -3
  393. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  394. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  395. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  396. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  397. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  398. package/src/runtime/terminal-bridge/client.mjs +44 -11
  399. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  400. package/src/runtime/tidy/apply.mjs +47 -2
  401. package/src/runtime/tidy/run-engines.mjs +11 -5
  402. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  403. package/src/session-runtime/boot/apis.mjs +19 -5
  404. package/src/session-runtime/boot/facade.mjs +1 -0
  405. package/src/session-runtime/boot/infrastructure.mjs +12 -1
  406. package/src/session-runtime/boot/session.mjs +0 -1
  407. package/src/session-runtime/boot/tools.mjs +4 -1
  408. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  409. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  410. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  411. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  412. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  413. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  414. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  415. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  416. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -3
  417. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  418. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  419. package/src/session-runtime/lifecycle/teardown.mjs +73 -8
  420. package/src/session-runtime/lifecycle-api.mjs +1 -0
  421. package/src/session-runtime/mcp-glue/connect.mjs +46 -14
  422. package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
  423. package/src/session-runtime/mcp-glue.mjs +3 -0
  424. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  425. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  426. package/src/session-runtime/model-route/set-route.mjs +85 -17
  427. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  428. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  429. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  430. package/src/session-runtime/native-web-search.mjs +10 -1
  431. package/src/session-runtime/new-session-config.mjs +4 -2
  432. package/src/session-runtime/provider-auth/login.mjs +4 -0
  433. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  434. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  435. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  436. package/src/session-runtime/resource-extension-sync.mjs +4 -0
  437. package/src/session-runtime/resource-mcp-api.mjs +98 -2
  438. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  439. package/src/session-runtime/runtime-review-api.mjs +21 -2
  440. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  441. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  442. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  443. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  444. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  445. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  446. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  447. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  448. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  449. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  450. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  451. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  452. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  453. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  454. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  455. package/src/session-runtime/services/agent-tool.mjs +3 -0
  456. package/src/session-runtime/services/channel-admin.mjs +19 -3
  457. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  458. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  459. package/src/session-runtime/services/provider-admin.mjs +81 -15
  460. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  461. package/src/session-runtime/session-turn-api.mjs +14 -5
  462. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  463. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  464. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  465. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  466. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  467. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  468. package/src/session-runtime/skills-api.mjs +3 -5
  469. package/src/session-runtime/tool-surface.mjs +5 -1
  470. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  471. package/src/session-runtime/turn/session-ops.mjs +7 -5
  472. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  473. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  474. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  475. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  476. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  477. package/src/session-runtime/turn/turn-run.mjs +25 -9
  478. package/src/session-runtime/usage-stats-api.mjs +43 -11
  479. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  480. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  481. package/src/session-runtime/workspace/transitions.mjs +190 -15
  482. package/src/standalone/daemon-agent-control.mjs +9 -1
  483. package/src/standalone/daemon-drain-policy.mjs +8 -0
  484. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  485. package/src/standalone/daemon-owner.mjs +17 -4
  486. package/src/standalone/daemon-pg-stop.mjs +51 -0
  487. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  488. package/src/standalone/daemon-stop-budget.mjs +18 -0
  489. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  490. package/src/standalone/daemon-telemetry.mjs +74 -0
  491. package/src/standalone/daemon.mjs +64 -8
  492. package/src/standalone/desktop-service-registry.mjs +5 -0
  493. package/src/standalone/local-session-runtime.mjs +70 -19
  494. package/src/standalone/session-client.mjs +42 -0
  495. package/src/standalone/session-protocol.mjs +14 -0
  496. package/src/standalone/session-proxy/projection.mjs +41 -4
  497. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  498. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  499. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  500. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  501. package/src/standalone/session-service/agent-tree.mjs +12 -0
  502. package/src/standalone/session-service/quota-resume.mjs +8 -0
  503. package/src/standalone/session-service/retention/busy.mjs +15 -1
  504. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  505. package/src/standalone/session-service-api.mjs +11 -1
  506. package/src/standalone/session-service.mjs +1 -0
  507. package/src/standalone/session-state-patch.d.mts +2 -1
  508. package/src/standalone/session-state-patch.mjs +41 -1
  509. package/src/tui/App.jsx +25 -0
  510. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  511. package/src/tui/app/app-view.jsx +1 -0
  512. package/src/tui/app/btw-controller.mjs +118 -0
  513. package/src/tui/app/create-app-pickers.mjs +6 -0
  514. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  515. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  516. package/src/tui/app/extension-pickers.mjs +2 -1
  517. package/src/tui/app/model-options.mjs +11 -16
  518. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  519. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  520. package/src/tui/app/slash-commands.mjs +20 -5
  521. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  522. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  523. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  524. package/src/tui/app/use-global-key-input.mjs +5 -12
  525. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  526. package/src/tui/components/BtwPanel.jsx +53 -0
  527. package/src/tui/components/QuestionPanel.jsx +275 -0
  528. package/src/tui/components/StatusLine.jsx +2 -0
  529. package/src/tui/components/question-panel-model.mjs +156 -0
  530. package/src/tui/components/status-line/local-line.mjs +5 -2
  531. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  532. package/src/tui/dist/index.mjs +3221 -1500
  533. package/src/tui/session/context-state.mjs +4 -2
  534. package/src/tui/session/quota-resume.mjs +46 -14
  535. package/src/tui/session/session-api/commands.mjs +43 -24
  536. package/src/tui/session/session-api/controls.mjs +2 -2
  537. package/src/tui/session/session-api/extensions.mjs +22 -0
  538. package/src/tui/session/session-api/integrations.mjs +16 -2
  539. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  540. package/src/tui/session/session-api/lifecycle.mjs +7 -2
  541. package/src/tui/session/session-api/routes.mjs +71 -7
  542. package/src/tui/session/session-api/settings.mjs +11 -14
  543. package/src/tui/session/session-api.mjs +1 -0
  544. package/src/tui/session/session-flow/drain.mjs +7 -1
  545. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  546. package/src/tui/session/tool-approval.mjs +10 -7
  547. package/src/tui/session/transcript-spill.mjs +14 -7
  548. package/src/tui/session/turn-stream.mjs +174 -31
  549. package/src/tui/session/turn.mjs +46 -16
  550. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  551. package/src/ui/statusline-agents.mjs +15 -4
  552. package/src/ui/statusline.mjs +7 -3
  553. package/src/ui/streaming-markdown-heal.mjs +34 -3
  554. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  555. package/src/workflows/default/WORKFLOW.md +3 -1
  556. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  557. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
@@ -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';
@@ -42,6 +44,7 @@ import { agentContextOverflowError } from './loop/context-overflow.mjs';
42
44
  import { estimateMessagesTokensSafe } from './loop/compact-debug.mjs';
43
45
  import { isOutputLimitStopReason } from './loop/termination.mjs';
44
46
  import { instrumentSendCallbacks } from './send-instrumentation.mjs';
47
+ import { rejectedToolSchema } from './tool-schema-rejection.mjs';
45
48
 
46
49
  function normalizedIncompleteUsage(raw) {
47
50
  if (!raw || typeof raw !== 'object') return undefined;
@@ -126,10 +129,10 @@ const RECOVERY_RETRY_BACKOFF_MS = retryLadderFromEnv(
126
129
  [15_000, 30_000, 60_000, 60_000, 60_000]
127
130
  );
128
131
 
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;
132
+ // A server-advised wait (Retry-After / retryAfterMs) replaces the ladder step
133
+ // and is honored in full, never shortened. The ladder length
134
+ // still bounds how many replays run, the wait stays cancellable, and the
135
+ // reconnecting stage reports its remaining delay (retryAt).
133
136
 
134
137
  // Jittering every backoff by 0.9–1.1 means parallel workers that lose the
135
138
  // same uplink do not resume in lockstep; our provider layers already jitter
@@ -138,14 +141,42 @@ const TRANSPORT_RETRY_JITTER_RATIO = 0.1;
138
141
 
139
142
  /** The ladder this failure class is entitled to: an unreachable network, a
140
143
  * provider still unavailable after its own retries, or an ordinary fault. */
141
- function retryLadderFor(error) {
144
+ function retryLadderFor(error, state = null) {
142
145
  if (isConnectFailure(error)) return CONNECTION_RETRY_BACKOFF_MS;
143
- if (isProviderRecoveryExhausted(error) && isServerUnavailable(error)) return RECOVERY_RETRY_BACKOFF_MS;
146
+ if (isProviderRecoveryExhausted(error) && isServerUnavailable(error) && recoveryCyclesEnabled(state)) {
147
+ return RECOVERY_RETRY_BACKOFF_MS;
148
+ }
144
149
  return TRANSPORT_RETRY_BACKOFF_MS;
145
150
  }
146
151
 
147
- function retryBudgetFor(error) {
148
- return retryLadderFor(error).length;
152
+ // retryPolicy.recoveryCycles:false — a provider that already ran its own full
153
+ // ladder surfaces its spent failure instead of parking on
154
+ // the 15/30/60/60/60s recovery cycles.
155
+ function recoveryCyclesEnabled(state) {
156
+ return loopRetryPolicy(state).recoveryCycles !== false;
157
+ }
158
+
159
+ // Per-provider loop retry policy, read off the provider object
160
+ // (`provider.retryPolicy`), default = the bounded ladders:
161
+ // unboundedConnectionRetry — a connect failure keeps waiting on the
162
+ // 5/10/20/40/60s… ladder (last step repeating) until the network returns;
163
+ // still cancellable, with "waiting for network" progress.
164
+ // contentFilterRetryable — a content_filter refusal is replayed
165
+ // under the transport budget with a contextual guidance notice.
166
+ // inBandInvalidRequestRetryable — an in-band stream error whose only
167
+ // deterministic evidence is its invalid_request envelope is replayed.
168
+ function loopRetryPolicy(state) {
169
+ const policy = state?.provider?.retryPolicy;
170
+ return policy && typeof policy === 'object' ? policy : {};
171
+ }
172
+
173
+ function waitsForNetwork(error, state) {
174
+ return loopRetryPolicy(state).unboundedConnectionRetry === true && isConnectFailure(error);
175
+ }
176
+
177
+ function retryBudgetFor(error, state = null) {
178
+ if (waitsForNetwork(error, state)) return Number.POSITIVE_INFINITY;
179
+ return retryLadderFor(error, state).length;
149
180
  }
150
181
 
151
182
  // A provider ladder spent on a connect failure has only shown that the network
@@ -153,15 +184,19 @@ function retryBudgetFor(error) {
153
184
  // unavailable parks on the recovery cycles, but only while nothing has reached
154
185
  // the user — a replay would otherwise re-show output or re-run a tool. Every
155
186
  // other exhausted failure surfaces as-is.
156
- function providerRecoveryEnded(error, outcome) {
187
+ // A provider VERDICT that recovery is over (an advice-gated denial, the
188
+ // repeated-overload stop) always ends it: the loop never overrides it.
189
+ function providerRecoveryEnded(error, outcome, state = null) {
190
+ if (isProviderRecoveryTerminal(error)) return true;
157
191
  if (!isProviderRecoveryExhausted(error) || isConnectFailure(error)) return false;
192
+ if (!recoveryCyclesEnabled(state)) return true;
158
193
  return !(outcome.replaySafe === true && isServerUnavailable(error));
159
194
  }
160
195
 
161
- function transportRetryWaitMs(attemptIndex, error) {
196
+ function transportRetryWaitMs(attemptIndex, error, state = null) {
162
197
  const advisedMs = retryAfterMsFromError(error);
163
- if (advisedMs != null) return Math.min(advisedMs, LOOP_RETRY_AFTER_CAP_MS);
164
- const ladder = retryLadderFor(error);
198
+ if (advisedMs != null) return advisedMs;
199
+ const ladder = retryLadderFor(error, state);
165
200
  const base = ladder[Math.min(Math.max(attemptIndex, 0), ladder.length - 1)];
166
201
  return jitterDelayMs(base, TRANSPORT_RETRY_JITTER_RATIO);
167
202
  }
@@ -258,6 +293,46 @@ function traceLoop({ sessionId, nextIteration }, kind, fields) {
258
293
  // 5s/15s wait rendered as a frozen turn with no explanation, so the stage
259
294
  // change surfaces "Reconnecting... n/max" instead of leaving the user
260
295
  // staring at a stalled stream.
296
+ // Unbounded connect-failure wait (retryPolicy.unboundedConnectionRetry): no
297
+ // "n/max" — the turn waits until the network is back, counting down each step.
298
+ function emitWaitingForNetwork(opts, { attempt, waitMs, classifier, error }) {
299
+ if (typeof opts?.onStageChange !== 'function') return;
300
+ try {
301
+ const wait = Math.max(0, Number(waitMs) || 0);
302
+ opts.onStageChange('reconnecting', {
303
+ attempt,
304
+ max: null,
305
+ waitMs: wait,
306
+ retryAt: Date.now() + wait,
307
+ reason: 'waiting for network',
308
+ waitingForNetwork: true,
309
+ classifier: classifier || error?.code || null,
310
+ message: `Reconnecting… waiting for network · attempt ${attempt}${wait > 0 ? ` in ${Math.ceil(wait / 1000)}s` : ''}`,
311
+ });
312
+ } catch {
313
+ /* display-only */
314
+ }
315
+ }
316
+
317
+ // content_filter on a path that opted in (retryPolicy.contentFilterRetryable,
318
+ // or the provider boundary stamping `contentFilterRetryable` on the error).
319
+ // A provider-terminal verdict outranks the opt-in.
320
+ function isReplayableContentFilter(sendErr, state) {
321
+ if (isProviderRecoveryTerminal(sendErr)) return false;
322
+ const optedIn = loopRetryPolicy(state).contentFilterRetryable === true || sendErr?.contentFilterRetryable === true;
323
+ return optedIn && providerErrorKind(sendErr).code === 'content_filter';
324
+ }
325
+
326
+ // The guidance travels as a contextual runtime fragment: the
327
+ // [mixdog-runtime] prefix keeps it out of the visible transcript.
328
+ const CONTENT_FILTER_NOTICE =
329
+ '[mixdog-runtime] The previous response was stopped by the provider content filter before it completed. ' +
330
+ 'Continue the task, but rephrase or avoid the content that likely triggered the filter; do not repeat it verbatim.';
331
+
332
+ function contentFilterNotice() {
333
+ return { role: 'user', content: CONTENT_FILTER_NOTICE, meta: { source: 'content-filter-recovery' } };
334
+ }
335
+
261
336
  function emitLoopReconnectProgress(opts, { attempt, maxAttempts, waitMs, classifier, error }) {
262
337
  emitProviderRetryStage(opts?.onStageChange, {
263
338
  attempt,
@@ -331,6 +406,7 @@ async function recoverFromSendError(sendErr, state) {
331
406
  transportRetriesUsed = 0,
332
407
  imageStripUsed = false,
333
408
  thinkingReplayRepairUsed = false,
409
+ toolSchemaDropUsed = false,
334
410
  relayWitness,
335
411
  } = state;
336
412
  const outcome = readStreamOutcome(sendErr, relayWitness);
@@ -353,13 +429,36 @@ async function recoverFromSendError(sendErr, state) {
353
429
  }
354
430
  // Clean transient transport failure with zero exposure: replay the send
355
431
  // after a bounded wait instead of failing the turn.
432
+ if (isReplayableContentFilter(sendErr, state)) {
433
+ if (transportRetriesUsed < TRANSPORT_RETRY_MAX && (outcome.replaySafe === true || (await retract()))) {
434
+ return scheduleTransportReplay(sendErr, state, {
435
+ budget: TRANSPORT_RETRY_MAX,
436
+ note: `[loop] content filter stopped the response (${loopTag(state)}); replaying with guidance`,
437
+ trace: { kind: 'content_filter_retry' },
438
+ classifier: 'content_filter',
439
+ notice: contentFilterNotice(),
440
+ });
441
+ }
442
+ throw sendErr;
443
+ }
356
444
  if (
357
- transportRetriesUsed < retryBudgetFor(sendErr) &&
358
- !providerRecoveryEnded(sendErr, outcome) &&
359
- (await transportReplayPermitted(sendErr, outcome, retract))
445
+ transportRetriesUsed < retryBudgetFor(sendErr, state) &&
446
+ !providerRecoveryEnded(sendErr, outcome, state) &&
447
+ (await transportReplayPermitted(sendErr, outcome, retract, state))
360
448
  ) {
361
449
  return retryTransport(sendErr, state);
362
450
  }
451
+ // A 400 naming one tool's input schema: the session drops that tool (with
452
+ // the notice) and the turn is replayed once without it. Nothing was
453
+ // generated, and the same body would fail every later turn.
454
+ if (toolSchemaDropUsed !== true) {
455
+ const exclusion = rejectedToolSchema(sendErr, state);
456
+ if (exclusion) {
457
+ logLoop(`[loop] provider rejected tool schema "${exclusion.name}" (${loopTag(state)}); dropping it and replaying once`);
458
+ traceLoop(state, 'tool_schema_drop', { tool: exclusion.name });
459
+ return { action: 'retry_tool_schema_drop', exclusion };
460
+ }
461
+ }
363
462
  // Anthropic replay-shape refusal: the latest assistant turn carries
364
463
  // thinking blocks the API will not accept back (a thinking run it did not
365
464
  // produce, or an empty text block). This is deterministic — the same body
@@ -373,7 +472,7 @@ async function recoverFromSendError(sendErr, state) {
373
472
  ) {
374
473
  return repairThinkingReplay(recoveryMessages, state);
375
474
  }
376
- // Grok Build RetryWithImageStrip: drop inline images and replay once. Safe
475
+ // Image-strip retry: drop inline images and replay once. Safe
377
476
  // only when no tool was dispatched. Text exposure must be retracted first;
378
477
  // reasoning-only high-effort streams can retry without a text reset.
379
478
  if (
@@ -464,7 +563,7 @@ function isExposedNoToolStall(sendErr, outcome) {
464
563
  // 'transient' and never fired).
465
564
  async function recoverExposedStall(sendErr, outcome, state, retract) {
466
565
  const { transportRetriesUsed = 0 } = state;
467
- const exposedRetryBudget = retryBudgetFor(sendErr);
566
+ const exposedRetryBudget = retryBudgetFor(sendErr, state);
468
567
  const len = sendErr.partialContent.length;
469
568
  if (transportRetriesUsed < exposedRetryBudget && !isProviderRecoveryExhausted(sendErr) && (await retract())) {
470
569
  return scheduleTransportReplay(sendErr, state, {
@@ -491,12 +590,17 @@ async function recoverExposedStall(sendErr, outcome, state, retract) {
491
590
  async function scheduleTransportReplay(sendErr, state, { budget, note, trace, classifier, notice = null }) {
492
591
  const { opts, signal, transportRetriesUsed = 0 } = state;
493
592
  const attempt = transportRetriesUsed + 1;
494
- const waitMs = transportRetryWaitMs(transportRetriesUsed, sendErr);
593
+ const waitMs = transportRetryWaitMs(transportRetriesUsed, sendErr, state);
495
594
  logLoop(`${note}; transport retry ${attempt}/${budget} after ${waitMs}ms`);
496
595
  const { kind, ...fields } = trace;
497
596
  traceLoop(state, kind, { attempt, waitMs, ...fields });
498
- emitLoopReconnectProgress(opts, { attempt, maxAttempts: budget, waitMs, classifier, error: sendErr });
499
- await sleepMs(waitMs, undefined, signal ? { signal } : undefined);
597
+ if (Number.isFinite(budget)) {
598
+ emitLoopReconnectProgress(opts, { attempt, maxAttempts: budget, waitMs, classifier, error: sendErr });
599
+ } else {
600
+ emitWaitingForNetwork(opts, { attempt, waitMs, classifier, error: sendErr });
601
+ }
602
+ // Chunked, abortable sleep: honored advice may exceed one timer's range.
603
+ await sleepWithAbort(waitMs, signal, undefined, 'transport retry wait aborted');
500
604
  return beginFreshTransportAttempt(opts, budget, notice);
501
605
  }
502
606
 
@@ -542,7 +646,7 @@ function continueFromShownText(sendErr, state, relayWitness) {
542
646
  // budget still bounds it.
543
647
  async function recoverToolInputCut(sendErr, outcome, state, retract) {
544
648
  const { transportRetriesUsed = 0 } = state;
545
- const budget = retryBudgetFor(sendErr);
649
+ const budget = retryBudgetFor(sendErr, state);
546
650
  if (transportRetriesUsed < budget && (outcome.replaySafe === true || (await retract()))) {
547
651
  return scheduleTransportReplay(sendErr, state, {
548
652
  budget,
@@ -650,20 +754,55 @@ function recoverPartialToolCalls(sendErr, state, toolCalls, outcome) {
650
754
  // reasoning/text exposure makes classifyError() intentionally permanent,
651
755
  // non-terminal close, or a dropped connection (undici `terminated`, socket
652
756
  // 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 =
757
+ async function transportReplayPermitted(sendErr, outcome, retract, state) {
758
+ if (!isSemanticallyRetryable(sendErr, state)) return false;
759
+ // Existing replay-safety gate: an unexposed failure replays outright only
760
+ // when it is typed-transient (or an opted-in in-band invalid_request); a
761
+ // named symptom replays after retraction.
762
+ if (
763
+ outcome.replaySafe === true &&
764
+ (classifyError(sendErr) === 'transient' || inBandInvalidRequestReplay(sendErr, state))
765
+ ) {
766
+ return true;
767
+ }
768
+ return await retract();
769
+ }
770
+
771
+ // The ONE semantic verdict on whether this failure is worth re-issuing,
772
+ // independent of what was exposed (the replay-safety gate decides that
773
+ // afterwards): never a deterministic refusal (quota, plan, billing, policy,
774
+ // auth, permission, context or size) — server advice never makes one
775
+ // retryable — and otherwise a named transient/transport symptom.
776
+ function inBandInvalidRequestReplay(sendErr, state) {
777
+ return (
778
+ loopRetryPolicy(state).inBandInvalidRequestRetryable === true &&
779
+ providerErrorKind(sendErr).kind === 'invalid_request' &&
780
+ isRetryableStreamErrorEvent(sendErr)
781
+ );
782
+ }
783
+
784
+ function isSemanticallyRetryable(sendErr, state) {
785
+ if (isProviderRecoveryTerminal(sendErr)) return false;
786
+ // Opt-in (retryPolicy.inBandInvalidRequestRetryable): a provider whose
787
+ // in-band stream errors carry an invalid_request envelope for server faults
788
+ // replays one with no other deterministic evidence; for every other
789
+ // provider it is a final refusal.
790
+ const { kind } = providerErrorKind(sendErr);
791
+ if (DETERMINISTIC_KINDS.has(kind) && !inBandInvalidRequestReplay(sendErr, state)) {
792
+ return false;
793
+ }
794
+ return (
656
795
  classifyError(sendErr) === 'transient' ||
657
796
  isStreamCut(sendErr) ||
658
797
  isRetryableWireErrorEvent(sendErr) ||
659
798
  isRetryableStreamErrorEvent(sendErr) ||
660
- isCursorTransientTransportError(sendErr);
661
- return transportSymptom && (await retract());
799
+ isCursorTransientTransportError(sendErr)
800
+ );
662
801
  }
663
802
 
664
803
  function retryTransport(sendErr, state) {
665
804
  return scheduleTransportReplay(sendErr, state, {
666
- budget: retryBudgetFor(sendErr),
805
+ budget: retryBudgetFor(sendErr, state),
667
806
  note:
668
807
  `[loop] transient send failure with no dispatched tools (${loopTag(state)} code=${sendErr?.code ?? 'n/a'} ` +
669
808
  `wsCloseCode=${sendErr?.wsCloseCode ?? 'n/a'} httpStatus=${sendErr?.httpStatus ?? sendErr?.status ?? 'n/a'})`,
@@ -700,7 +839,7 @@ function imageStripEligible(
700
839
  hasImages: promptHasInlineImages(recoveryMessages),
701
840
  alreadyStripped: imageStripUsed === true,
702
841
  }) ||
703
- // Grok Build RetryWithImageStrip only: a bare stream error on a Grok
842
+ // Grok only: a bare stream error on a Grok
704
843
  // upload may be the image body being rejected. Other providers' transient
705
844
  // stream errors are not evidence about images and use normal transport retry.
706
845
  (isGrok &&
@@ -0,0 +1,96 @@
1
+ // side-question-store.mjs — the in-memory state behind /btw side questions:
2
+ // per session, the latest provider request the main loop sent (the request a
3
+ // side question forks) and the side question in flight. It holds live
4
+ // provider/tool objects, so it never reaches the session record or disk.
5
+ import { isAgentOwner } from '../agent-owner.mjs';
6
+
7
+ // sessionId -> { provider, model, messages, tools, opts, response }
8
+ const requests = new Map();
9
+ // sessionId -> AbortController of the side question in flight.
10
+ const inFlight = new Map();
11
+
12
+ // Send options that steer the live loop rather than shape the request: its
13
+ // abort signal, a one-shot cache-break trace tag and the per-request stall
14
+ // budget. A forced tool choice is dropped too (a side question must be able
15
+ // to answer in text); the hard-cap `none` choice is kept.
16
+ const LOOP_ONLY_OPTIONS = new Set(['signal', 'cacheBreakIntent', '_stallRetryBudget', 'toolChoice']);
17
+
18
+ function requestOptions(opts) {
19
+ const out = {};
20
+ for (const [key, value] of Object.entries(opts || {})) {
21
+ if (typeof value === 'function' || LOOP_ONLY_OPTIONS.has(key)) continue;
22
+ out[key] = value;
23
+ }
24
+ if (opts?.toolChoice === 'none') out.toolChoice = 'none';
25
+ return out;
26
+ }
27
+
28
+ /** A request in the stored shape: its own message list and only the send
29
+ * options a fork may reuse. `response` is null until the reply settles. */
30
+ export function sideQuestionRequestSnapshot({ provider, model, messages, tools, opts }) {
31
+ return {
32
+ provider,
33
+ model,
34
+ messages: [...messages],
35
+ tools: Array.isArray(tools) ? tools : [],
36
+ opts: requestOptions(opts),
37
+ response: null,
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Record a provider request the main loop is sending as the session's latest
43
+ * one. Agent sessions record nothing (/btw is a Lead command). The handle
44
+ * attaches the reply once the send proceeds, or puts the previous request
45
+ * back when this one fails, so only a successfully sent request is kept.
46
+ */
47
+ export function captureSideQuestionRequest({ sessionId, sessionRef, provider, model, messages, tools, opts }) {
48
+ if (!sessionId || !provider || isAgentOwner(sessionRef)) return null;
49
+ const previous = requests.get(sessionId);
50
+ const request = sideQuestionRequestSnapshot({ provider, model, messages, tools, opts });
51
+ requests.set(sessionId, request);
52
+ return {
53
+ settle(response) {
54
+ request.response = response || null;
55
+ },
56
+ discard() {
57
+ if (requests.get(sessionId) !== request) return;
58
+ if (previous) requests.set(sessionId, previous);
59
+ else requests.delete(sessionId);
60
+ },
61
+ };
62
+ }
63
+
64
+ export function sideQuestionRequest(sessionId) {
65
+ return (sessionId && requests.get(sessionId)) || null;
66
+ }
67
+
68
+ /** Start the session's side question, aborting the one still in flight. */
69
+ export function beginSideQuestion(sessionId) {
70
+ cancelSideQuestion(sessionId);
71
+ const controller = new AbortController();
72
+ inFlight.set(sessionId, controller);
73
+ return {
74
+ signal: controller.signal,
75
+ end() {
76
+ if (inFlight.get(sessionId) === controller) inFlight.delete(sessionId);
77
+ },
78
+ };
79
+ }
80
+
81
+ export function cancelSideQuestion(sessionId) {
82
+ const controller = sessionId ? inFlight.get(sessionId) : null;
83
+ if (!controller) return false;
84
+ inFlight.delete(sessionId);
85
+ controller.abort(new Error('Side question canceled.'));
86
+ return true;
87
+ }
88
+
89
+ /** The transcript the saved request carried is gone (clear, rewind): a later
90
+ * side question waits for the next request. `cancel` also aborts the side
91
+ * question in flight (session close / runtime unload). */
92
+ export function forgetSideQuestion(sessionId, { cancel = false } = {}) {
93
+ if (!sessionId) return;
94
+ requests.delete(sessionId);
95
+ if (cancel) cancelSideQuestion(sessionId);
96
+ }
@@ -0,0 +1,192 @@
1
+ // side-question.mjs — /btw: a quick question answered while the main turn
2
+ // keeps working. The answer comes from a cache-sharing fork of the session's
3
+ // latest provider request (side-question-store.mjs): the same model, system
4
+ // prompt, tools and cache-relevant options and the same message prefix, so
5
+ // the provider's prompt cache serves the prefix. Only the reply that request
6
+ // produced and one question are appended. One provider call, no tool loop;
7
+ // the answer is returned to the caller and never enters the transcript.
8
+ import { collectAssistantToolCallIds } from './context-tool-pairs.mjs';
9
+ import { buildIntermediateAssistantMessage, buildToolCallAssistantMessage } from './loop/assistant-commit.mjs';
10
+ import { logSessionLlmUsage } from './manager/ask-turn-commit.mjs';
11
+ import { disposeTransportScope, forkTransportScope } from '../providers/transport-scope.mjs';
12
+ import { isAgentOwner } from '../agent-owner.mjs';
13
+ import { getProvider } from '../providers/registry.mjs';
14
+ import { prepareTurnEffortConfiguration } from '../providers/effort-configuration.mjs';
15
+ import { providerNativeToolPrefixCount } from '../runtime-core/provider-request-tools.mjs';
16
+ import { snapshotProviderRequestTools } from '../runtime-core/tool-catalog.mjs';
17
+ import { repairTranscriptBeforeProviderSend } from './loop/transcript-repair.mjs';
18
+ import { buildRequestSendOptions } from './manager/ask-turn-loop-options.mjs';
19
+ import { filterModelVisibleSessionMessages } from './manager/message-sanitize.mjs';
20
+ import { hasUserConversationMessage } from './manager/prompt-utils.mjs';
21
+ import { loadSession } from './store.mjs';
22
+ import { projectSyntheticUserEnvelopes } from './synthetic-user-envelope.mjs';
23
+ import { beginSideQuestion, sideQuestionRequest, sideQuestionRequestSnapshot } from './side-question-store.mjs';
24
+
25
+ export { cancelSideQuestion } from './side-question-store.mjs';
26
+
27
+ export const SIDE_QUESTION_NO_REQUEST = 'Send a message first, then try /btw.';
28
+ export const SIDE_QUESTION_TOOL_ATTEMPT =
29
+ 'The model tried to use a tool instead of answering. A side question cannot run tools; try asking it differently.';
30
+ const SIDE_QUESTION_EMPTY_ANSWER = 'The model returned no answer.';
31
+
32
+ // The reply's tool calls run in the main session; the fork only needs each
33
+ // call answered so the request stays well-formed.
34
+ const SIDE_QUESTION_TOOL_STUB =
35
+ 'This tool call belongs to the main task and is handled there; its result is not part of this side question.';
36
+
37
+ function sideQuestionPrompt(question) {
38
+ return [
39
+ '<side-question>',
40
+ 'The user is asking you a quick side question. The main agent keeps working on the task on its own;',
41
+ 'this exchange does not reach it and does not change that work.',
42
+ 'Answer directly, in this one response, from what this conversation already contains.',
43
+ 'No tools are available here: do not call any, and do not offer or promise to do anything.',
44
+ 'If the conversation does not tell you the answer, say that you do not know.',
45
+ '',
46
+ question,
47
+ '</side-question>',
48
+ ].join('\n');
49
+ }
50
+
51
+ // The assistant turn the saved request produced, built the way the loop
52
+ // commits it (without the turn's one-shot transcript metadata), plus a stub
53
+ // result per tool call.
54
+ function replyMessages(response) {
55
+ if (!response) return [];
56
+ const reply = response.toolCalls?.length
57
+ ? buildToolCallAssistantMessage(response, { calls: response.toolCalls, suppressMidTurnText: false, opts: {} })
58
+ : buildIntermediateAssistantMessage(response, {});
59
+ if (!reply) return [];
60
+ const stubs = collectAssistantToolCallIds(reply).map((id) => ({
61
+ role: 'tool',
62
+ toolCallId: id,
63
+ content: SIDE_QUESTION_TOOL_STUB,
64
+ }));
65
+ return [reply, ...stubs];
66
+ }
67
+
68
+ /**
69
+ * The fork of a saved request: its messages unchanged, then the reply and
70
+ * the question. The send options are the saved ones; the provider
71
+ * continuation state follows the reply when it is appended, exactly as the
72
+ * main loop's next request would carry it. `sharedCachePrefixCount` (the
73
+ * shared prefix's message count, which survives model-proxy serialization)
74
+ * keeps an Anthropic message breakpoint on the shared prefix, so the fork reads the
75
+ * saved request's cache entry and writes none for its suffix.
76
+ */
77
+ export function buildSideQuestionRequest(request, question) {
78
+ const reply = replyMessages(request.response);
79
+ const opts = { ...request.opts };
80
+ if (reply.length && Object.hasOwn(request.response, 'providerState')) {
81
+ opts.providerState = request.response.providerState;
82
+ }
83
+ if (request.messages.length) opts.sharedCachePrefixCount = request.messages.length;
84
+ const messages = [
85
+ ...request.messages,
86
+ ...reply,
87
+ // A user interjection keeps its own turn (never folded into a preceding
88
+ // tool result), so the prefix's last block reaches the provider unchanged.
89
+ { role: 'user', content: sideQuestionPrompt(question), meta: { source: 'steering' } },
90
+ ];
91
+ return { messages, opts };
92
+ }
93
+
94
+ /**
95
+ * The request the main loop would send next for a session that has none saved
96
+ * (the runtime restarted, or the saved one was forgotten): the session's own
97
+ * provider, model, tools, send options and transcript, assembled by the same
98
+ * pieces the loop uses, without the loop's side effects (no compaction, no
99
+ * transcript repair in place, nothing written to the session). Null when the
100
+ * session has no conversation, no resolvable provider, or is an agent's.
101
+ */
102
+ function nextMainRequest(sessionId) {
103
+ const session = sessionId ? loadSession(sessionId) : null;
104
+ if (!session || session.closed === true || isAgentOwner(session)) return null;
105
+ if (!hasUserConversationMessage(session.messages)) return null;
106
+ const provider = getProvider(session.provider);
107
+ if (!provider) return null;
108
+ const transcript = filterModelVisibleSessionMessages(session.messages);
109
+ repairTranscriptBeforeProviderSend(transcript, sessionId);
110
+ const opts = buildRequestSendOptions({
111
+ sessionId,
112
+ session,
113
+ turnEffort: session.effort || null,
114
+ effortConfiguration: prepareTurnEffortConfiguration(session, provider),
115
+ stepAutoEffort: null,
116
+ codexTurnId: null,
117
+ startedAtMs: Date.now(),
118
+ signal: undefined,
119
+ });
120
+ const sendTools = snapshotProviderRequestTools({
121
+ provider: session.provider || provider.name,
122
+ tools: session.tools,
123
+ nativeTools: opts.nativeTools,
124
+ messages: transcript,
125
+ session,
126
+ });
127
+ opts.iteration = 1;
128
+ opts.providerToolSnapshotAuthoritative = true;
129
+ opts.providerNativeToolPrefixCount = providerNativeToolPrefixCount(sendTools);
130
+ return sideQuestionRequestSnapshot({
131
+ provider,
132
+ model: session.model,
133
+ messages: projectSyntheticUserEnvelopes(transcript).messages,
134
+ tools: sendTools,
135
+ opts,
136
+ });
137
+ }
138
+
139
+ function answerText(response) {
140
+ if (response?.toolCalls?.length) return SIDE_QUESTION_TOOL_ATTEMPT;
141
+ const text = typeof response?.content === 'string' ? response.content.trim() : '';
142
+ return text || SIDE_QUESTION_EMPTY_ANSWER;
143
+ }
144
+
145
+ /**
146
+ * Answer `question` from the session's latest provider request. One side
147
+ * question runs per session: a new one aborts the previous, and
148
+ * cancelSideQuestion aborts it. A session without a saved request forks the
149
+ * request its next turn would send. Rejects when the session has no
150
+ * conversation or provider, with the provider's error, or with the cancel
151
+ * reason.
152
+ */
153
+ export async function askSideQuestion(sessionId, question) {
154
+ const text = typeof question === 'string' ? question.trim() : '';
155
+ if (!text) throw new TypeError('Side question is empty.');
156
+ const request = sideQuestionRequest(sessionId) || nextMainRequest(sessionId);
157
+ if (!request) throw new Error(SIDE_QUESTION_NO_REQUEST);
158
+ const run = beginSideQuestion(sessionId);
159
+ const startedAt = Date.now();
160
+ // The fork keeps the session id (prompt cache routing) but runs on its own
161
+ // transport scope: no pooled socket, response-chain anchor, turn-state
162
+ // token or provider run of the main session is waited on, read or replaced.
163
+ const transportScope = forkTransportScope(sessionId, 'side');
164
+ try {
165
+ const { messages, opts } = buildSideQuestionRequest(request, text);
166
+ let response;
167
+ try {
168
+ response = await request.provider.send(messages, request.model, request.tools.length ? request.tools : undefined, {
169
+ ...opts,
170
+ transportScope,
171
+ admissionOwner: sessionId,
172
+ signal: run.signal,
173
+ });
174
+ } catch (error) {
175
+ throw run.signal.aborted ? run.signal.reason : error;
176
+ }
177
+ if (run.signal.aborted) throw run.signal.reason;
178
+ const session = request.opts.session;
179
+ if (response?.usage && session?.id) {
180
+ void logSessionLlmUsage({
181
+ session: { ...session, model: request.model },
182
+ usage: response.usage,
183
+ startedAt,
184
+ mode: 'side-question',
185
+ }).catch(() => {});
186
+ }
187
+ return { text: answerText(response) };
188
+ } finally {
189
+ run.end();
190
+ disposeTransportScope(transportScope, request.provider);
191
+ }
192
+ }
@@ -158,9 +158,23 @@ function _logSessionCloseMetric(id, existing, closedAt, reason) {
158
158
  }
159
159
  }
160
160
 
161
+ /**
162
+ * A barrier that found no record at all — nothing on disk and no live or
163
+ * pending copy in memory — fenced nothing because there is nothing to fence:
164
+ * its pending writes were already cancelled under the commit lock. It still
165
+ * returns null (no generation exists), but a caller passing `options.outcome`
166
+ * learns that the record was legitimately gone rather than vetoed, contended
167
+ * or failed.
168
+ */
169
+ function _recordAbsent(options) {
170
+ if (options?.outcome && typeof options.outcome === 'object') options.outcome.absent = true;
171
+ return null;
172
+ }
173
+
161
174
  /**
162
175
  * Atomically mark a session closed on disk with a bumped generation.
163
176
  * Returns the new generation, or null if the session file doesn't exist.
177
+ * `options.outcome` (an object) gets `absent: true` when no record existed.
164
178
  * Used by closeSession() to plant a tombstone that races against in-flight
165
179
  * saveSession() calls.
166
180
  */
@@ -187,7 +201,7 @@ export function markSessionClosed(id, reason = 'manual', options = {}) {
187
201
  _cancelSessionWrites(id);
188
202
  _uncacheSessionSummary(id);
189
203
  const existing = loadSession(id);
190
- if (!existing) return null;
204
+ if (!existing) return _recordAbsent(options);
191
205
  // Re-close idempotence: a session that is ALREADY tombstoned keeps its
192
206
  // ORIGINAL close time (updatedAt) and generation. The old code refreshed
193
207
  // updatedAt=Date.now() on every call, so the 5-min idle sweep re-closing a
@@ -253,7 +267,7 @@ export function markSessionClosed(id, reason = 'manual', options = {}) {
253
267
  * of clobbering whatever the resumed session writes next.
254
268
  * Returns the new generation, or null if the session file doesn't exist.
255
269
  */
256
- export function bumpSessionGeneration(id, reason = 'detach') {
270
+ export function bumpSessionGeneration(id, reason = 'detach', options = {}) {
257
271
  clearSessionLifecycleCommitError(id);
258
272
  // The detach barrier is a canonical write and MUST take the same commit
259
273
  // lock as markSessionClosed. Cancellation alone is not a barrier: a writer
@@ -278,7 +292,7 @@ export function bumpSessionGeneration(id, reason = 'detach') {
278
292
  _cancelSessionWrites(id);
279
293
  _uncacheSessionSummary(id);
280
294
  const existing = loadSession(id);
281
- if (!existing) return null;
295
+ if (!existing) return _recordAbsent(options);
282
296
  const newGen = (typeof existing.generation === 'number' ? existing.generation : 0) + 1;
283
297
  const detached = { ...existing, generation: newGen, updatedAt: Date.now(), detachedReason: reason };
284
298
  if (!_writeLifecycleRecord(id, detached, existing, reason)) return null;