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
@@ -6,14 +6,22 @@ import { PATCH_EDIT_MAX_BYTES } from './bounded-file-read.mjs';
6
6
  import { cleanString as clean } from './clean.mjs';
7
7
  import { runGitOffThread } from './git-runner.mjs';
8
8
  import { resolvePluginData } from './plugin-paths.mjs';
9
- import { normalizeChangeStatus, splitPatchSections } from './turn-changes.mjs';
9
+ import {
10
+ createReviewPatchBudget,
11
+ PATCH_TOO_LARGE,
12
+ patchOmittedFile,
13
+ REVIEW_FILE_PATCH_MAX_BYTES,
14
+ REVIEW_PATCH_MAX_BYTES,
15
+ reviewDiffTooLarge,
16
+ } from './review-diff.mjs';
17
+ import { splitPatchSections } from './turn-changes.mjs';
10
18
 
11
19
  const COMMAND_TIMEOUT_MS = 30_000;
12
20
  const COMMAND_MAX_BYTES = 8 * 1024 * 1024;
13
- const PATCH_MAX_BYTES = 2 * 1024 * 1024;
14
- // The complete (replay-safe) patch a sealing turn records; per-file and
15
- // per-session bounds are applied on top of this by the change record.
16
- const FREEZE_PATCH_MAX_BYTES = 64 * 1024 * 1024;
21
+ // Room for git's diagnostics beside a patch read up to its byte cap.
22
+ const PATCH_OUTPUT_SLACK_BYTES = 64 * 1024;
23
+ // Paths per patch read once the whole range overflowed (bounded command line).
24
+ const PATCH_BATCH_FILES = 32;
17
25
  const MAX_UNTRACKED_FILE_BYTES = 2 * 1024 * 1024;
18
26
  // Largest tool-supplied baseline kept for `hash-object`: the apply_patch edit
19
27
  // limit. (2–64 MiB explicit untracked edits keep their exact revert.)
@@ -32,9 +40,6 @@ const SHADOW_GC_PRUNE = '7.days.ago';
32
40
  // signal reports (an external editor, a command the shell classifies
33
41
  // read-only) still show within this gap.
34
42
  const MIN_RECAPTURE_GAP_MS = 15_000;
35
- // The same gap for a timer poll of a review the user has open. Shorter than
36
- // the desktop bar's 6 s poll, so each of those polls still captures.
37
- const VISIBLE_RECAPTURE_GAP_MS = 5_000;
38
43
  const states = new Map();
39
44
 
40
45
  function pathKey(value) {
@@ -44,7 +49,7 @@ function pathKey(value) {
44
49
 
45
50
  /** `resolve` plus symlinks of the nearest existing ancestor: git reports the
46
51
  * worktree root canonically (macOS /var -> /private/var), callers may not. */
47
- function canonicalPath(value, dirs = null) {
52
+ export function canonicalPath(value, dirs = null) {
48
53
  const full = resolve(value);
49
54
  if (dirs) {
50
55
  // Same result as the walk below without repeating it per file: the parent
@@ -169,8 +174,30 @@ export function noteTurnWorktreeChange(paths = null) {
169
174
  }
170
175
  }
171
176
 
172
- function withStateLock(state, task) {
173
- const next = state.lock.then(task, task);
177
+ // A lock hold (queue wait plus work) at least this long is logged, so a slow
178
+ // review bar can be told apart as waiting behind other captures or slow git.
179
+ const SLOW_LOCK_LOG_MS = 1_000;
180
+
181
+ function withStateLock(state, task, op = 'task') {
182
+ const queuedAt = Date.now();
183
+ const queuedBehind = state.lockDepth || 0;
184
+ state.lockDepth = queuedBehind + 1;
185
+ let startedAt = 0;
186
+ const timed = async () => {
187
+ startedAt = Date.now();
188
+ return await task();
189
+ };
190
+ const next = state.lock.then(timed, timed).finally(() => {
191
+ state.lockDepth -= 1;
192
+ const finishedAt = Date.now();
193
+ const waitMs = (startedAt || finishedAt) - queuedAt;
194
+ if (finishedAt - queuedAt >= SLOW_LOCK_LOG_MS) {
195
+ console.info(
196
+ `[turn-snapshot-lock] op=${op} waitMs=${waitMs} workMs=${finishedAt - (startedAt || finishedAt)} ` +
197
+ `queuedBehind=${queuedBehind} root=${state.root}`
198
+ );
199
+ }
200
+ });
174
201
  state.lock = next.then(
175
202
  () => undefined,
176
203
  () => undefined
@@ -338,6 +365,7 @@ async function ensureState(state) {
338
365
  }
339
366
 
340
367
  async function resetShadowIndex(state) {
368
+ state.lastCapture = null;
341
369
  state.sourceIndexPath ??= await sourceGitPath(state.root, 'index');
342
370
  const sourceIndex = state.sourceIndexPath;
343
371
  const target = join(state.gitDir, 'index');
@@ -400,10 +428,33 @@ async function changedWorktreePaths(state) {
400
428
  };
401
429
  }
402
430
 
403
- async function captureTreeUnlocked(state, toolPaths = []) {
431
+ /** The capture sequence a caller observes before queueing on the lock. */
432
+ function captureSequence(state) {
433
+ return state.captureSeq || 0;
434
+ }
435
+
436
+ /** `after` (a captureSequence() read before queueing): a capture of this root
437
+ * that began after that point, with no reported write since and covering
438
+ * every explicit path, already is a capture that began after the call. Many
439
+ * sessions of one repository queue reads together; they share that capture
440
+ * instead of each re-scanning the worktree in turn. */
441
+ async function captureTreeUnlocked(state, toolPaths = [], { after = -1 } = {}) {
404
442
  await ensureState(state);
405
- const { paths, excludedUntracked } = await changedWorktreePaths(state);
406
443
  const explicit = new Set(toolPaths);
444
+ const last = state.lastCapture;
445
+ if (
446
+ after >= 0 &&
447
+ last &&
448
+ last.seq > after &&
449
+ last.stamp === state.changeStamp &&
450
+ [...explicit].every((rel) => last.explicit.has(rel))
451
+ ) {
452
+ return last.tree;
453
+ }
454
+ state.captureSeq = captureSequence(state) + 1;
455
+ const capture = { seq: state.captureSeq, stamp: state.changeStamp, explicit, tree: '' };
456
+ state.lastCapture = null;
457
+ const { paths, excludedUntracked } = await changedWorktreePaths(state);
407
458
  for (const rel of explicit) {
408
459
  try {
409
460
  const stat = await lstat(resolve(state.root, rel));
@@ -432,7 +483,9 @@ async function captureTreeUnlocked(state, toolPaths = []) {
432
483
  });
433
484
  }
434
485
  const tree = await runGit(shadowArgs(state, ['write-tree']), { cwd: state.root });
435
- return clean(tree.stdout);
486
+ capture.tree = clean(tree.stdout);
487
+ state.lastCapture = capture;
488
+ return capture.tree;
436
489
  }
437
490
 
438
491
  /** Only explicit tool targets bypass ignore/size filters. Their first-before
@@ -481,7 +534,6 @@ function withOmittedBaselines(snapshot, review) {
481
534
  omitted: true,
482
535
  size,
483
536
  });
484
- if (review.patchOmitted) continue;
485
537
  const newHeader = present ? `b/${entry.path}` : '/dev/null';
486
538
  patch +=
487
539
  `diff --git a/${entry.path} b/${entry.path}\n` +
@@ -490,19 +542,7 @@ function withOmittedBaselines(snapshot, review) {
490
542
  `Binary files a/${entry.path} and ${newHeader} differ\n`;
491
543
  }
492
544
  files.sort((left, right) => left.path.localeCompare(right.path));
493
- return { ...review, files, patch };
494
- }
495
-
496
- /** Revert never claims success for a path whose baseline bytes were not captured. */
497
- function assertRevertable(snapshot, targets) {
498
- for (const target of targets) {
499
- const entry = snapshot.baselineFiles.get(pathKey(target));
500
- if (entry?.omitted === true) {
501
- throw new Error(
502
- `turn review revert is unavailable for ${target}: binary/large file content was not captured (${Number(entry.size) || 0} bytes)`
503
- );
504
- }
505
- }
545
+ return { ...review, files, patch, diffTooLarge: reviewDiffTooLarge(files, patch) };
506
546
  }
507
547
 
508
548
  async function applyBaselineFilesUnlocked(state, tree, allEntries) {
@@ -646,9 +686,98 @@ function parseNumstat(text) {
646
686
  return out;
647
687
  }
648
688
 
649
- async function diffTreesUnlocked(state, baselineTree, currentTree, paths = null, { omitPatch = false } = {}) {
689
+ const literalPathspec = (path) => `:(literal)${path}`;
690
+
691
+ /** The `diff --git` sections of one patch read, keyed by path; null once its
692
+ * output passed `maxBytes`. */
693
+ async function patchSectionsByPath(state, baselineTree, currentTree, pathspec, maxBytes) {
694
+ let result;
695
+ try {
696
+ result = await runGit(
697
+ shadowArgs(state, [
698
+ 'diff',
699
+ '--no-ext-diff',
700
+ '--unified=3',
701
+ '--find-renames',
702
+ baselineTree,
703
+ currentTree,
704
+ '--',
705
+ ...pathspec,
706
+ ]),
707
+ { cwd: state.root, maxBytes: maxBytes + PATCH_OUTPUT_SLACK_BYTES }
708
+ );
709
+ } catch (error) {
710
+ if (error?.code === 'EMAXBUFFER') return null;
711
+ throw error;
712
+ }
713
+ const sections = new Map();
714
+ for (const section of splitPatchSections(Buffer.from(result.stdout, 'utf8'))) {
715
+ const path = section.newPath ?? section.oldPath;
716
+ if (path) sections.set(pathKey(path), section.patch);
717
+ }
718
+ return sections;
719
+ }
720
+
721
+ /** After the whole range overflowed: patches read in path batches, the
722
+ * smallest changes first, until the reply's total is spent. A batch that
723
+ * overflows is halved; a single file that does is too large. */
724
+ async function batchedPatchSections(state, baselineTree, currentTree, files, sections) {
725
+ const lines = (file) => (Number(file.additions) || 0) + (Number(file.deletions) || 0);
726
+ let remaining = REVIEW_PATCH_MAX_BYTES;
727
+ const read = async (batch) => {
728
+ if (batch.length === 0 || remaining <= 0) return;
729
+ const pathspec = batch.flatMap((file) => [file.path, file.oldPath].filter(Boolean).map(literalPathspec));
730
+ const found = await patchSectionsByPath(state, baselineTree, currentTree, pathspec, remaining);
731
+ if (!found) {
732
+ if (batch.length === 1) return;
733
+ const half = Math.ceil(batch.length / 2);
734
+ await read(batch.slice(0, half));
735
+ await read(batch.slice(half));
736
+ return;
737
+ }
738
+ for (const [key, section] of found) {
739
+ sections.set(key, section);
740
+ if (section.length <= REVIEW_FILE_PATCH_MAX_BYTES) remaining -= section.length;
741
+ }
742
+ };
743
+ // Every changed line costs at least its marker and newline: a file whose
744
+ // counts alone pass the per-file cap is never read.
745
+ const queue = files
746
+ .filter((file) => 2 * lines(file) <= REVIEW_FILE_PATCH_MAX_BYTES)
747
+ .sort((left, right) => lines(left) - lines(right));
748
+ for (let index = 0; index < queue.length && remaining > 0; index += PATCH_BATCH_FILES) {
749
+ await read(queue.slice(index, index + PATCH_BATCH_FILES));
750
+ }
751
+ }
752
+
753
+ /** Every file's patch within the reply caps. A file past the per-file cap or
754
+ * the remaining total is listed without one (`patchOmitted`, too large). */
755
+ async function boundedTreePatch(state, baselineTree, currentTree, pathspec, files) {
756
+ let sections = await patchSectionsByPath(state, baselineTree, currentTree, pathspec, COMMAND_MAX_BYTES);
757
+ // Read whole, every file has its section; read in batches, a file without
758
+ // one was too large or past the total.
759
+ const whole = Boolean(sections);
760
+ if (!sections) {
761
+ sections = new Map();
762
+ await batchedPatchSections(state, baselineTree, currentTree, files, sections);
763
+ }
764
+ const budget = createReviewPatchBudget();
765
+ const kept = [];
766
+ const bounded = files.map((file) => {
767
+ const section = sections.get(pathKey(file.path));
768
+ if (!section && whole) return file;
769
+ if (section && budget.admit(section.length)) {
770
+ kept.push(section);
771
+ return file;
772
+ }
773
+ return patchOmittedFile(file);
774
+ });
775
+ return { patch: Buffer.concat(kept).toString('utf8'), files: bounded };
776
+ }
777
+
778
+ async function diffTreesUnlocked(state, baselineTree, currentTree, paths = null) {
650
779
  if (!baselineTree || !currentTree || baselineTree === currentTree) {
651
- return { patch: '', files: [], patchTruncated: false, currentTree };
780
+ return { patch: '', files: [], patchTruncated: false, diffTooLarge: false, currentTree };
652
781
  }
653
782
  // A scoped review restricts the range to the paths its session owns: the
654
783
  // baseline describes the whole worktree, concurrent sessions included.
@@ -658,23 +787,6 @@ async function diffTreesUnlocked(state, baselineTree, currentTree, paths = null,
658
787
  runGit(shadowArgs(state, ['diff', '--name-status', '-z', ...rangeArgs]), { cwd: state.root }),
659
788
  runGit(shadowArgs(state, ['diff', '--numstat', '-z', ...rangeArgs]), { cwd: state.root }),
660
789
  ]);
661
- let patch = '';
662
- let patchTruncated = false;
663
- // A files-and-counts read skips the patch text, the one part whose cost
664
- // grows with the size of the change.
665
- if (!omitPatch) {
666
- try {
667
- const result = await runGit(shadowArgs(state, ['diff', '--no-ext-diff', '--unified=3', ...rangeArgs]), {
668
- cwd: state.root,
669
- maxBytes: PATCH_MAX_BYTES + 256 * 1024,
670
- });
671
- patch = result.stdout.length <= PATCH_MAX_BYTES ? result.stdout : result.stdout.slice(0, PATCH_MAX_BYTES);
672
- patchTruncated = result.stdout.length > PATCH_MAX_BYTES;
673
- } catch (error) {
674
- if (error?.code !== 'EMAXBUFFER') throw error;
675
- patchTruncated = true;
676
- }
677
- }
678
790
  const statuses = parseNameStatus(nameStatus.stdout);
679
791
  const stats = parseNumstat(numstat.stdout);
680
792
  const keys = [...new Set([...statuses.keys(), ...stats.keys()])];
@@ -692,83 +804,27 @@ async function diffTreesUnlocked(state, baselineTree, currentTree, paths = null,
692
804
  };
693
805
  })
694
806
  .sort((left, right) => left.path.localeCompare(right.path));
695
- return { patch, files, patchTruncated, currentTree, ...(omitPatch ? { patchOmitted: true } : {}) };
807
+ const bounded = await boundedTreePatch(state, baselineTree, currentTree, pathspec, files);
808
+ return {
809
+ patch: bounded.patch,
810
+ files: bounded.files,
811
+ patchTruncated: false,
812
+ diffTooLarge: reviewDiffTooLarge(bounded.files, bounded.patch),
813
+ currentTree,
814
+ };
696
815
  }
697
816
 
698
- /**
699
- * Freeze the snapshot's last captured net diff into replayable per-file
700
- * patches: one complete `--binary --full-index` diff (independent of the
701
- * bounded display patch), split back into its `diff --git` sections. `paths`
702
- * scopes a contended worktree to the session-owned files. Callers refresh the
703
- * snapshot first; this never captures. Files whose bytes cannot be replayed
704
- * (an uncaptured baseline, a type change, an over-limit diff) stay in the
705
- * record as non-revertable with a reason.
706
- */
707
- export async function freezeTurnWorktreeChanges(snapshot, paths = null) {
708
- if (!snapshot?.state || !snapshot.baselineTree) return null;
709
- const scope = Array.isArray(paths) ? [...new Set(paths.filter(Boolean))] : null;
710
- if (scope && scope.length === 0) return { root: snapshot.root, files: [] };
711
- return await withStateLock(snapshot.state, async () => {
712
- const { state, baselineTree, currentTree } = snapshot;
713
- let review = { files: [], patch: '', patchTruncated: false };
714
- let sections = [];
715
- let unavailable = '';
716
- if (currentTree && currentTree !== baselineTree) {
717
- review = await diffTreesUnlocked(state, baselineTree, currentTree, scope, { omitPatch: true });
718
- const args = [
719
- 'diff',
720
- '--binary',
721
- '--full-index',
722
- '--no-color',
723
- '--no-ext-diff',
724
- '--no-textconv',
725
- '--src-prefix=a/',
726
- '--dst-prefix=b/',
727
- '--find-renames',
728
- baselineTree,
729
- currentTree,
730
- '--',
731
- ...(scope || ['.']),
732
- ];
733
- try {
734
- const result = await runGit(shadowArgs(state, args), {
735
- cwd: state.root,
736
- maxBytes: FREEZE_PATCH_MAX_BYTES,
737
- stdoutEncoding: 'base64',
738
- });
739
- sections = splitPatchSections(Buffer.from(result.stdout, 'base64'));
740
- } catch (error) {
741
- if (error?.code !== 'EMAXBUFFER') throw error;
742
- unavailable = 'the turn changed too much to keep its patches for undo';
743
- }
744
- }
745
- const listed = withOmittedBaselines({ ...snapshot, scopePaths: scope }, { ...review, patchOmitted: true });
746
- const byPath = new Map();
747
- for (const section of sections) {
748
- const key = pathKey(section.newPath ?? section.oldPath ?? '');
749
- // A type change diffs as a delete plus an add of one path: not one patch.
750
- byPath.set(key, byPath.has(key) ? null : section.patch);
751
- }
752
- const files = (listed.files || []).map((file) => {
753
- const entry = {
754
- path: file.path,
755
- ...(file.oldPath ? { oldPath: file.oldPath } : {}),
756
- status: normalizeChangeStatus(file.status),
757
- additions: Number(file.additions) || 0,
758
- deletions: Number(file.deletions) || 0,
759
- ...(file.binary ? { binary: true } : {}),
760
- };
761
- if (entry.status !== 'R') delete entry.oldPath;
762
- if (file.omitted === true) {
763
- return { ...entry, revertable: false, reason: 'binary/large file content was not captured' };
764
- }
765
- if (file.status === 'T') return { ...entry, revertable: false, reason: 'the file type changed' };
766
- const patch = byPath.get(pathKey(file.path));
767
- if (!patch) return { ...entry, revertable: false, reason: unavailable || 'no replayable patch was recorded' };
768
- return { ...entry, revertable: true, patch };
769
- });
770
- return { root: snapshot.root, files };
771
- });
817
+ /** One file's patch between two trees (a rename read with its old path), up
818
+ * to `maxBytes`; past it, the file is too large. */
819
+ async function filePatchUnlocked(state, baselineTree, currentTree, path, oldPath, maxBytes) {
820
+ if (!baselineTree || !currentTree || baselineTree === currentTree) return { patch: '' };
821
+ const pathspec = [path, oldPath].filter(Boolean).map(literalPathspec);
822
+ const sections = await patchSectionsByPath(state, baselineTree, currentTree, pathspec, maxBytes);
823
+ const section = sections?.get(pathKey(path)) ?? (oldPath ? sections?.get(pathKey(oldPath)) : undefined);
824
+ if (!sections || (section && section.length > maxBytes)) {
825
+ return { patch: '', patchOmitted: true, reason: PATCH_TOO_LARGE };
826
+ }
827
+ return { patch: section ? section.toString('utf8') : '' };
772
828
  }
773
829
 
774
830
  // One maintenance pass per shadow repository per process, off the turn's
@@ -862,53 +918,93 @@ export async function createTurnWorktreeSnapshot(worktree) {
862
918
 
863
919
  /** Re-open a recorded baseline after the runtime lost its in-memory tracker.
864
920
  * The tree object is the durable half of a review; `paths` scopes both this
865
- * diff and any later revert to what the recording session actually owns.
866
- * `omitPatch` leaves out the patch text: only a snapshot read once and then
867
- * discarded may ask for that, never one a later refresh keeps updating. */
868
- export async function resumeTurnWorktreeSnapshot(
921
+ * diff and any later revert to what the recording session actually owns. */
922
+ export async function resumeTurnWorktreeSnapshot(worktree, baselineTree, { paths = null, baselineFiles = [] } = {}) {
923
+ const tree = clean(baselineTree);
924
+ if (!tree) return null;
925
+ const root = await repositoryRoot(worktree).catch(() => null);
926
+ if (!root) return null;
927
+ const state = stateForRoot(root);
928
+ const after = captureSequence(state);
929
+ return await withStateLock(
930
+ state,
931
+ async () => {
932
+ if (!(await recordedTreeUnlocked(state, tree))) return null;
933
+ const scopePaths = Array.isArray(paths) && paths.length > 0 ? [...new Set(paths)] : null;
934
+ const applied = await applyBaselineFilesUnlocked(state, tree, baselineFiles || []);
935
+ const snapshot = {
936
+ state,
937
+ root,
938
+ baselineTree: applied.tree,
939
+ currentTree: tree,
940
+ patch: '',
941
+ files: [],
942
+ patchTruncated: false,
943
+ scopePaths,
944
+ toolPaths: new Set(scopePaths || []),
945
+ pendingBaselines: new Map(),
946
+ baselineFiles: new Map(applied.files.map((entry) => [pathKey(entry.path), entry])),
947
+ };
948
+ const currentTree = await captureTreeUnlocked(state, snapshot.toolPaths, { after });
949
+ Object.assign(
950
+ snapshot,
951
+ withOmittedBaselines(snapshot, await diffTreesUnlocked(state, snapshot.baselineTree, currentTree, scopePaths))
952
+ );
953
+ return snapshot;
954
+ },
955
+ 'resume'
956
+ );
957
+ }
958
+
959
+ /** A collected or never-written baseline restores nothing: false reports it
960
+ * as "no snapshot" instead of failing every later call made against it. */
961
+ async function recordedTreeUnlocked(state, tree) {
962
+ await ensureState(state);
963
+ const kind = await runGit(shadowArgs(state, ['cat-file', '-t', tree]), {
964
+ cwd: state.root,
965
+ allowFailure: true,
966
+ });
967
+ return kind.code === 0 && clean(kind.stdout) === 'tree';
968
+ }
969
+
970
+ /** One file's patch of a recorded baseline against the worktree now, up to
971
+ * `maxBytes`. `paths` is the recording session's scope, every target checked
972
+ * on its own: a file outside it answers with no patch, a rename source
973
+ * outside it is left out of the diff. Null when the baseline is gone. */
974
+ export async function resumeTurnWorktreeFilePatch(
869
975
  worktree,
870
976
  baselineTree,
871
- { paths = null, baselineFiles = [], omitPatch = false } = {}
977
+ { paths = null, baselineFiles = [], path, oldPath = '', maxBytes } = {}
872
978
  ) {
873
979
  const tree = clean(baselineTree);
874
980
  if (!tree) return null;
875
981
  const root = await repositoryRoot(worktree).catch(() => null);
876
982
  if (!root) return null;
877
983
  const state = stateForRoot(root);
878
- return await withStateLock(state, async () => {
879
- await ensureState(state);
880
- // A collected or never-written baseline restores nothing. Report that as
881
- // "no snapshot" instead of failing every later call made against it.
882
- const kind = await runGit(shadowArgs(state, ['cat-file', '-t', tree]), {
883
- cwd: state.root,
884
- allowFailure: true,
885
- });
886
- if (kind.code !== 0 || clean(kind.stdout) !== 'tree') return null;
887
- const scopePaths = Array.isArray(paths) && paths.length > 0 ? [...new Set(paths)] : null;
888
- const applied = await applyBaselineFilesUnlocked(state, tree, baselineFiles || []);
889
- const snapshot = {
890
- state,
891
- root,
892
- baselineTree: applied.tree,
893
- currentTree: tree,
894
- patch: '',
895
- files: [],
896
- patchTruncated: false,
897
- scopePaths,
898
- toolPaths: new Set(scopePaths || []),
899
- pendingBaselines: new Map(),
900
- baselineFiles: new Map(applied.files.map((entry) => [pathKey(entry.path), entry])),
901
- };
902
- const currentTree = await captureTreeUnlocked(state, snapshot.toolPaths);
903
- Object.assign(
904
- snapshot,
905
- withOmittedBaselines(
906
- snapshot,
907
- await diffTreesUnlocked(state, snapshot.baselineTree, currentTree, scopePaths, { omitPatch })
908
- )
909
- );
910
- return snapshot;
911
- });
984
+ const scope = Array.isArray(paths) && paths.length > 0 ? new Set(paths.map(pathKey)) : null;
985
+ const allowed = (rel) => !scope || scope.has(pathKey(rel));
986
+ const target = safeRelativePath(root, path);
987
+ if (!allowed(target)) return { patch: '' };
988
+ const source = clean(oldPath) ? safeRelativePath(root, oldPath) : '';
989
+ // Only authorized paths reach the diff.
990
+ const targets = source && allowed(source) && pathKey(source) !== pathKey(target) ? [target, source] : [target];
991
+ const keys = new Set(targets.map(pathKey));
992
+ const after = captureSequence(state);
993
+ return await withStateLock(
994
+ state,
995
+ async () => {
996
+ if (!(await recordedTreeUnlocked(state, tree))) return null;
997
+ // Only this file's recorded first states change its side of the tree.
998
+ const applied = await applyBaselineFilesUnlocked(
999
+ state,
1000
+ tree,
1001
+ (baselineFiles || []).filter((entry) => keys.has(pathKey(safeRelativePath(root, entry?.path))))
1002
+ );
1003
+ const currentTree = await captureTreeUnlocked(state, targets, { after });
1004
+ return await filePatchUnlocked(state, applied.tree, currentTree, targets[0], targets[1] || '', maxBytes);
1005
+ },
1006
+ 'file-patch'
1007
+ );
912
1008
  }
913
1009
 
914
1010
  /** What a capture starting now has seen: the change stamp, the source index
@@ -918,11 +1014,11 @@ async function captureMark(state) {
918
1014
  }
919
1015
 
920
1016
  /** Could the worktree differ from the snapshot's last capture? */
921
- async function captureNeeded(snapshot, gapMs) {
1017
+ async function captureNeeded(snapshot) {
922
1018
  const mark = snapshot.captured;
923
1019
  if (!mark || snapshot.pendingBaselines.size > 0) return true;
924
1020
  if (mark.stamp !== snapshot.state.changeStamp) return true;
925
- if (Date.now() - mark.at >= gapMs) return true;
1021
+ if (Date.now() - mark.at >= MIN_RECAPTURE_GAP_MS) return true;
926
1022
  return (await sourceIndexIdentity(snapshot.state)) !== mark.index;
927
1023
  }
928
1024
 
@@ -930,17 +1026,22 @@ function startRefresh(snapshot) {
930
1026
  const flight = { seq: (snapshot.refreshSeq || 0) + 1, promise: null };
931
1027
  snapshot.refreshSeq = flight.seq;
932
1028
  snapshot.refreshing = flight;
933
- flight.promise = withStateLock(snapshot.state, async () => {
934
- const mark = await captureMark(snapshot.state);
935
- await materializeToolBaselines(snapshot);
936
- const currentTree = await captureTreeUnlocked(snapshot.state, snapshot.toolPaths);
937
- if (currentTree !== snapshot.currentTree) {
938
- const review = await diffTreesUnlocked(snapshot.state, snapshot.baselineTree, currentTree, snapshot.scopePaths);
939
- Object.assign(snapshot, withOmittedBaselines(snapshot, review));
940
- }
941
- snapshot.captured = mark;
942
- return snapshot;
943
- }).finally(() => {
1029
+ const after = captureSequence(snapshot.state);
1030
+ flight.promise = withStateLock(
1031
+ snapshot.state,
1032
+ async () => {
1033
+ const mark = await captureMark(snapshot.state);
1034
+ await materializeToolBaselines(snapshot);
1035
+ const currentTree = await captureTreeUnlocked(snapshot.state, snapshot.toolPaths, { after });
1036
+ if (currentTree !== snapshot.currentTree) {
1037
+ const review = await diffTreesUnlocked(snapshot.state, snapshot.baselineTree, currentTree, snapshot.scopePaths);
1038
+ Object.assign(snapshot, withOmittedBaselines(snapshot, review));
1039
+ }
1040
+ snapshot.captured = mark;
1041
+ return snapshot;
1042
+ },
1043
+ 'refresh'
1044
+ ).finally(() => {
944
1045
  if (snapshot.refreshing === flight) snapshot.refreshing = null;
945
1046
  });
946
1047
  return flight.promise;
@@ -949,13 +1050,10 @@ function startRefresh(snapshot) {
949
1050
  /** Bring the snapshot up to date with the worktree. Single-flight: a call
950
1051
  * made while a capture runs waits for it, then captures again only if a
951
1052
  * change was reported after that capture began. Without a reported change,
952
- * an index change or the recapture gap elapsed (VISIBLE_RECAPTURE_GAP_MS
953
- * for an open review's poll, `visible`; else MIN_RECAPTURE_GAP_MS), the
954
- * last capture stands. `force` always answers with a capture that began
955
- * after this call. */
956
- export async function refreshTurnWorktreeSnapshot(snapshot, { force = false, visible = false } = {}) {
1053
+ * an index change or MIN_RECAPTURE_GAP_MS elapsed, the last capture stands.
1054
+ * `force` always answers with a capture that began after this call. */
1055
+ export async function refreshTurnWorktreeSnapshot(snapshot, { force = false } = {}) {
957
1056
  if (!snapshot?.state || !snapshot.baselineTree) return null;
958
- const gapMs = visible ? VISIBLE_RECAPTURE_GAP_MS : MIN_RECAPTURE_GAP_MS;
959
1057
  const calledAtSeq = snapshot.refreshSeq || 0;
960
1058
  for (;;) {
961
1059
  const flight = snapshot.refreshing;
@@ -964,7 +1062,7 @@ export async function refreshTurnWorktreeSnapshot(snapshot, { force = false, vis
964
1062
  if (flight.seq > calledAtSeq) return snapshot;
965
1063
  continue;
966
1064
  }
967
- if (!force && !(await captureNeeded(snapshot, gapMs))) return snapshot;
1065
+ if (!force && !(await captureNeeded(snapshot))) return snapshot;
968
1066
  if (snapshot.refreshing) continue;
969
1067
  return await startRefresh(snapshot);
970
1068
  }
@@ -981,87 +1079,6 @@ function safeRelativePath(root, value) {
981
1079
  return rel.replace(/\\/g, '/');
982
1080
  }
983
1081
 
984
- async function restorePathFromTree(snapshot, rel) {
985
- const listed = await runGit(
986
- shadowArgs(snapshot.state, ['ls-tree', '-z', '--name-only', snapshot.baselineTree, '--', rel]),
987
- { cwd: snapshot.root }
988
- );
989
- if (listed.stdout.split('\0').some((value) => value === rel)) {
990
- await runGit(shadowArgs(snapshot.state, ['checkout', snapshot.baselineTree, '--', rel]), { cwd: snapshot.root });
991
- return;
992
- }
993
- const target = resolve(snapshot.root, rel);
994
- try {
995
- const stat = await lstat(target);
996
- if (stat.isDirectory()) throw new Error('turn review revert refuses to remove a directory');
997
- await rm(target, { force: true });
998
- } catch (error) {
999
- if (error?.code !== 'ENOENT') throw error;
1000
- }
1001
- }
1002
-
1003
- async function revertPathsUnlocked(snapshot, targets) {
1004
- // Checked for every target before the first restore: no partial revert.
1005
- assertRevertable(snapshot, targets);
1006
- for (const target of targets) {
1007
- await restorePathFromTree(snapshot, target);
1008
- }
1009
- // Restores report no change signal; the next poll captures afresh.
1010
- snapshot.captured = null;
1011
- const currentTree = await captureTreeUnlocked(snapshot.state, snapshot.toolPaths);
1012
- Object.assign(
1013
- snapshot,
1014
- withOmittedBaselines(
1015
- snapshot,
1016
- await diffTreesUnlocked(snapshot.state, snapshot.baselineTree, currentTree, snapshot.scopePaths)
1017
- )
1018
- );
1019
- // The absolute paths now back at their baseline bytes.
1020
- return targets.map((target) => resolve(snapshot.root, target));
1021
- }
1022
-
1023
- export async function revertTurnWorktreeFile(snapshot, value) {
1024
- if (!snapshot?.state || !snapshot.baselineTree) throw new Error('turn worktree snapshot is unavailable');
1025
- await refreshTurnWorktreeSnapshot(snapshot, { force: true });
1026
- return await withStateLock(snapshot.state, async () => {
1027
- const rel = safeRelativePath(snapshot.root, value);
1028
- const entry = snapshot.files.find(
1029
- (file) => pathKey(file.path) === pathKey(rel) || pathKey(file.oldPath) === pathKey(rel)
1030
- );
1031
- const targets = [...new Set([entry?.path || rel, entry?.oldPath].filter(Boolean))].map((target) =>
1032
- safeRelativePath(snapshot.root, target)
1033
- );
1034
- return await revertPathsUnlocked(snapshot, targets);
1035
- });
1036
- }
1037
-
1038
- export async function revertTurnWorktreeSnapshot(snapshot) {
1039
- if (!snapshot?.state || !snapshot.baselineTree) throw new Error('turn worktree snapshot is unavailable');
1040
- await refreshTurnWorktreeSnapshot(snapshot, { force: true });
1041
- return await withStateLock(snapshot.state, async () => {
1042
- // Resolve and validate every path before the first mutation. Renames and
1043
- // copies contribute both sides so the worktree returns to the exact
1044
- // turn-start tree rather than leaving a destination behind.
1045
- const targets = [...new Set(snapshot.files.flatMap((file) => [file.path, file.oldPath]).filter(Boolean))].map(
1046
- (target) => safeRelativePath(snapshot.root, target)
1047
- );
1048
- return await revertPathsUnlocked(snapshot, targets);
1049
- });
1050
- }
1051
-
1052
- /** Restore only the listed paths. This is the session-owned revert: a shared
1053
- * worktree cannot attribute the whole-tree diff, but the paths this session's
1054
- * own tools wrote are exactly attributable. */
1055
- export async function revertTurnWorktreePaths(snapshot, values) {
1056
- if (!snapshot?.state || !snapshot.baselineTree) throw new Error('turn worktree snapshot is unavailable');
1057
- const requested = (Array.isArray(values) ? values : []).filter(Boolean);
1058
- if (requested.length === 0) throw new Error('turn review revert has no restorable files');
1059
- return await withStateLock(snapshot.state, async () => {
1060
- const targets = [...new Set(requested.map((value) => safeRelativePath(snapshot.root, value)))];
1061
- return await revertPathsUnlocked(snapshot, targets);
1062
- });
1063
- }
1064
-
1065
1082
  export function _resetTurnWorktreeSnapshotsForTest() {
1066
1083
  states.clear();
1067
1084
  }