mixdog 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. package/README.md +7 -5
  2. package/package.json +1 -1
  3. package/scripts/gate-local.mjs +19 -3
  4. package/scripts/model-catalog-audit.mjs +1 -15
  5. package/src/agents/reviewer/AGENT.md +3 -3
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +5 -0
  8. package/src/defaults/skills/pptx/SKILL.md +6 -6
  9. package/src/defaults/skills/pptx/references/review.md +6 -4
  10. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  11. package/src/defaults/skills/setup/references/actions.md +13 -3
  12. package/src/defaults/skills/visualize/SKILL.md +12 -9
  13. package/src/repl/slash-commands.mjs +16 -1
  14. package/src/rules/shared/60-verification.md +3 -1
  15. package/src/rules/shared/70-delivery.md +4 -2
  16. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  17. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  18. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  19. package/src/runtime/agent/orchestrator/config.mjs +10 -4
  20. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  21. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  22. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  23. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +122 -15
  24. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +54 -11
  25. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +104 -9
  26. package/src/runtime/agent/orchestrator/mcp/client.mjs +358 -140
  27. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  28. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  29. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  30. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  31. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  32. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  33. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  34. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  35. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  36. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  37. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +294 -44
  38. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  40. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -0
  41. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  42. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +25 -21
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
  45. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  46. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  49. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  50. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  51. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  52. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +73 -35
  53. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  54. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  55. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -1
  56. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +53 -18
  57. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  58. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  59. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +30 -14
  60. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
  61. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
  62. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
  63. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  64. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  65. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  66. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +447 -0
  67. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
  68. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  70. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +102 -23
  71. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
  73. package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
  74. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  75. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +86 -19
  76. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
  78. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -0
  79. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  80. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  81. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  82. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  83. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +21 -82
  84. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
  85. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
  86. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
  87. package/src/runtime/agent/orchestrator/providers/gemini.mjs +97 -51
  88. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  89. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
  90. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  91. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  92. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  93. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  94. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +15 -0
  95. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +35 -0
  96. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  97. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -0
  98. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +5 -3
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  103. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
  109. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  110. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  111. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
  113. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +6 -3
  114. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
  115. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +26 -6
  116. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
  117. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  119. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +18 -11
  120. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +2 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  125. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +34 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
  127. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  128. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
  129. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
  130. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  132. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  133. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  135. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +35 -24
  136. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
  137. package/src/runtime/agent/orchestrator/providers/registry.mjs +35 -9
  138. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  139. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -219
  140. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  141. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  142. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  143. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +3 -0
  145. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  146. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  147. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  148. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  149. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  150. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +268 -181
  151. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  152. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  154. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  155. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +270 -0
  156. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  157. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  158. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +6 -2
  159. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  160. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  161. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  162. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  163. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  164. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  165. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  166. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  167. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  168. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +3 -2
  169. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -4
  170. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +4 -3
  171. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
  172. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  173. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
  174. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +42 -5
  175. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +14 -3
  176. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +20 -12
  177. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +50 -23
  178. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +4 -0
  179. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +22 -14
  180. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  182. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  183. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +4 -3
  184. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
  185. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  186. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  187. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  188. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  189. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  190. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +819 -0
  191. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +12 -0
  192. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +22 -12
  193. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  194. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +183 -374
  195. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  196. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  197. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +41 -3
  198. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +38 -17
  199. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  200. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  201. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  202. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  203. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  205. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  206. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +173 -22
  207. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  208. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +5 -4
  209. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +15 -2
  210. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  211. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  212. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  213. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  214. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  215. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  216. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  217. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  218. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  219. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  220. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  221. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  222. package/src/runtime/attachments/store.d.mts +3 -0
  223. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  224. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  225. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  226. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  227. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  228. package/src/runtime/memory/lib/memory.mjs +1 -1
  229. package/src/runtime/memory/lib/pg/process.mjs +34 -24
  230. package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
  231. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  232. package/src/runtime/office/README.md +1 -1
  233. package/src/runtime/office/shared/values.mjs +0 -5
  234. package/src/runtime/shared/atomic-file.mjs +25 -4
  235. package/src/runtime/shared/background-tasks.mjs +92 -14
  236. package/src/runtime/shared/err-text.mjs +25 -0
  237. package/src/runtime/shared/error-presentation.mjs +159 -19
  238. package/src/runtime/shared/extension-scopes.mjs +5 -3
  239. package/src/runtime/shared/file-lock.mjs +37 -11
  240. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  241. package/src/runtime/shared/llm/anthropic-effort.mjs +134 -131
  242. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  243. package/src/runtime/shared/llm/model-catalog.mjs +53 -52
  244. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  245. package/src/runtime/shared/llm/provider-catalog-registry.mjs +17 -0
  246. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  247. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  248. package/src/runtime/shared/model-display.mjs +13 -0
  249. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  250. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  251. package/src/runtime/shared/pristine-execution.mjs +28 -4
  252. package/src/runtime/shared/provider-accounts.mjs +124 -20
  253. package/src/runtime/shared/provider-api-key.mjs +12 -0
  254. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  255. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  256. package/src/runtime/shared/shell-display.mjs +9 -3
  257. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  258. package/src/runtime/shared/skill-mentions.mjs +133 -0
  259. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  260. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  261. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  262. package/src/runtime/shared/tool-primitives.mjs +35 -2
  263. package/src/runtime/shared/turn-changes.mjs +447 -219
  264. package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
  265. package/src/runtime/shared/turn-snapshot.mjs +170 -48
  266. package/src/runtime/shared/turn-worktree-snapshot.mjs +160 -51
  267. package/src/runtime/shared/worker-requests.mjs +15 -2
  268. package/src/runtime/shared/worktrees/contract.mjs +0 -24
  269. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  270. package/src/session-runtime/automation-agents.mjs +8 -2
  271. package/src/session-runtime/boot/apis.mjs +61 -0
  272. package/src/session-runtime/boot/config.mjs +5 -2
  273. package/src/session-runtime/boot/facade.mjs +1 -0
  274. package/src/session-runtime/boot/infrastructure.mjs +35 -18
  275. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  276. package/src/session-runtime/conversation-import/service.mjs +369 -0
  277. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +10 -5
  278. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  279. package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
  280. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  281. package/src/session-runtime/mcp-glue/connect.mjs +83 -16
  282. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  283. package/src/session-runtime/mcp-glue/server-catalog.mjs +135 -33
  284. package/src/session-runtime/mcp-glue.mjs +25 -2
  285. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  286. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  287. package/src/session-runtime/model-route/set-route.mjs +83 -15
  288. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  289. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  290. package/src/session-runtime/new-session-config.mjs +3 -2
  291. package/src/session-runtime/notification-bus.mjs +9 -2
  292. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  293. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  294. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  295. package/src/session-runtime/provider-readiness.mjs +16 -10
  296. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  297. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  298. package/src/session-runtime/resource-extension-sync.mjs +24 -5
  299. package/src/session-runtime/resource-mcp-api.mjs +198 -78
  300. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  301. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  302. package/src/session-runtime/resource-skills-api.mjs +23 -2
  303. package/src/session-runtime/runtime-review-api.mjs +13 -0
  304. package/src/session-runtime/schedule-session-run.mjs +1 -1
  305. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  306. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  307. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  308. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  310. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  311. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  312. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  313. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  314. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  315. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  316. package/src/session-runtime/services/provider-admin.mjs +64 -18
  317. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  318. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  319. package/src/session-runtime/setup-tool/executor.mjs +12 -1
  320. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  321. package/src/session-runtime/setup-tool/settings-contract.mjs +1 -0
  322. package/src/session-runtime/setup-tool/tool-defs.mjs +7 -1
  323. package/src/session-runtime/skills-api.mjs +7 -3
  324. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  325. package/src/session-runtime/webhook-session-run.mjs +16 -4
  326. package/src/skill-script-entry.mjs +36 -0
  327. package/src/standalone/daemon-drain-policy.mjs +8 -0
  328. package/src/standalone/daemon-stored-session-views.mjs +11 -3
  329. package/src/standalone/daemon-telemetry.mjs +55 -0
  330. package/src/standalone/daemon.mjs +44 -11
  331. package/src/standalone/session-protocol.mjs +12 -0
  332. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  333. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  334. package/src/standalone/session-service/agent-tree.mjs +12 -0
  335. package/src/standalone/session-service/quota-resume.mjs +8 -0
  336. package/src/standalone/session-service/retention/busy.mjs +15 -1
  337. package/src/standalone/session-service/session-calls/session-action.mjs +6 -1
  338. package/src/standalone/session-service-api.mjs +5 -0
  339. package/src/standalone/session-service.mjs +1 -0
  340. package/src/tui/App.jsx +9 -1
  341. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  342. package/src/tui/app/app-view.jsx +7 -0
  343. package/src/tui/app/create-app-pickers.mjs +4 -0
  344. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  345. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  346. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  347. package/src/tui/app/extension-pickers.mjs +2 -1
  348. package/src/tui/app/model-options.mjs +13 -16
  349. package/src/tui/app/skill-mention-palette.mjs +88 -0
  350. package/src/tui/app/slash-commands.mjs +7 -2
  351. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  352. package/src/tui/app/slash-palette.mjs +23 -7
  353. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  354. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  355. package/src/tui/app/use-prompt-state.mjs +5 -0
  356. package/src/tui/components/Message.jsx +22 -1
  357. package/src/tui/components/PromptInput.jsx +21 -3
  358. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  359. package/src/tui/components/StatusLine.jsx +2 -0
  360. package/src/tui/components/TranscriptItem.jsx +9 -1
  361. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  362. package/src/tui/components/status-line/local-line.mjs +5 -2
  363. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  364. package/src/tui/components/user-message-skills.mjs +38 -0
  365. package/src/tui/dist/index.mjs +1323 -214
  366. package/src/tui/session/queue-helpers.mjs +7 -2
  367. package/src/tui/session/quota-resume.mjs +46 -14
  368. package/src/tui/session/session-api/commands.mjs +33 -24
  369. package/src/tui/session/session-api/extensions.mjs +26 -2
  370. package/src/tui/session/session-api/integrations.mjs +7 -0
  371. package/src/tui/session/session-api/lifecycle.mjs +5 -1
  372. package/src/tui/session/session-api/routes.mjs +71 -7
  373. package/src/tui/session/session-api/settings.mjs +12 -14
  374. package/src/tui/session/session-flow/drain.mjs +21 -5
  375. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  376. package/src/tui/session/tui-steering-persist.mjs +25 -9
  377. package/src/tui/session/turn.mjs +55 -15
  378. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  379. package/src/ui/statusline-agents.mjs +15 -4
  380. package/src/ui/statusline.mjs +7 -3
  381. package/src/ui/streaming-markdown-heal.mjs +34 -3
  382. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  383. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  384. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
  385. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  386. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
@@ -15,6 +15,7 @@
15
15
  import { createHash } from 'node:crypto';
16
16
  import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
17
17
  import { join, resolve } from 'node:path';
18
+ import { withFileLock } from './atomic-file.mjs';
18
19
  import { cleanString as clean } from './clean.mjs';
19
20
  import { resolvePluginData } from './plugin-paths.mjs';
20
21
 
@@ -37,6 +38,12 @@ let recordRootOverride = '';
37
38
  function recordRoot() {
38
39
  return recordRootOverride || join(resolvePluginData(), 'turn-snapshots');
39
40
  }
41
+
42
+ /** The directory every turn-snapshot store lives under (follows the test
43
+ * override and a runtime MIXDOG_DATA_DIR retarget). */
44
+ export function turnSnapshotStoreRoot() {
45
+ return recordRoot();
46
+ }
40
47
  // Per-session write chain. Two turns of one session never race their own file,
41
48
  // and a read never observes a partially written record (writes land by rename).
42
49
  const writeChains = new Map();
@@ -133,7 +140,7 @@ async function readRecordFile(path) {
133
140
 
134
141
  /** Oldest-first sweep of one record directory so a long-lived install cannot
135
142
  * accumulate records. */
136
- async function sweepDirectory(directory, ttlMs, maxFiles) {
143
+ export async function sweepDirectory(directory, ttlMs, maxFiles) {
137
144
  try {
138
145
  const names = await readdir(directory);
139
146
  const now = Date.now();
@@ -231,6 +238,7 @@ export async function saveTurnSnapshotRecord(sessionId, turn) {
231
238
  .map(serializeScope),
232
239
  };
233
240
  await writeJsonAtomically(path, payload);
241
+ await endClearedHistory(id);
234
242
  return true;
235
243
  } catch {
236
244
  return false;
@@ -257,6 +265,108 @@ export async function loadTurnSnapshotRecord(sessionId) {
257
265
  return null;
258
266
  }
259
267
 
268
+ // ── Last computed review summary ────────────────────────────────────────────
269
+ //
270
+ // Rebuilding a recorded review rescans the whole worktree behind every other
271
+ // capture of that repository. Entering a session reads the last computed
272
+ // files and counts instead and lets the refreshing read follow. Keyed to the
273
+ // turn record it was computed from, so a newer turn never shows an older one.
274
+
275
+ let summarySweepDone = false;
276
+
277
+ function summariesRoot() {
278
+ return join(recordRoot(), 'review-summaries');
279
+ }
280
+
281
+ /** Identity of the paths a record turn scopes its review to. */
282
+ function summaryToolFilesKey(turn) {
283
+ return (Array.isArray(turn?.toolFiles) ? turn.toolFiles : []).map(rootKey).sort().join('\0');
284
+ }
285
+
286
+ function summaryPath(sessionId) {
287
+ const hash = createHash('sha256').update(sessionId).digest('hex').slice(0, 24);
288
+ return join(summariesRoot(), `${hash}.json`);
289
+ }
290
+
291
+ // Test hook: runs inside the summary critical section, between the cleared
292
+ // check and the write.
293
+ let summaryWriteHookForTest = null;
294
+ export function _setSummaryWriteHookForTest(hook) {
295
+ summaryWriteHookForTest = typeof hook === 'function' ? hook : null;
296
+ }
297
+
298
+ const SUMMARY_LOCK_TIMEOUT_MS = 10_000;
299
+ const withSummaryLock = (sessionId, task) =>
300
+ withFileLock(`${summaryPath(sessionId)}.lock`, task, { timeoutMs: SUMMARY_LOCK_TIMEOUT_MS });
301
+
302
+ // Summary saves still running. Callers fire them and move on, so a test that
303
+ // removes the store root settles them first instead of racing their writes.
304
+ const pendingSummarySaves = new Set();
305
+ export async function _settleSummarySavesForTest() {
306
+ while (pendingSummarySaves.size) await Promise.allSettled([...pendingSummarySaves]);
307
+ }
308
+
309
+ /** Keep the files and counts last computed for `turn` (a loaded record turn). */
310
+ export function saveTurnReviewSummary(sessionId, turn, review) {
311
+ const saving = saveTurnReviewSummaryNow(sessionId, turn, review);
312
+ pendingSummarySaves.add(saving);
313
+ const done = () => pendingSummarySaves.delete(saving);
314
+ saving.then(done, done);
315
+ return saving;
316
+ }
317
+
318
+ async function saveTurnReviewSummaryNow(sessionId, turn, review) {
319
+ const id = clean(sessionId);
320
+ if (!id || !turn?.baselineTree || !Array.isArray(review?.files)) return false;
321
+ return await chain(`summary:${id}`, async () => {
322
+ try {
323
+ await mkdir(summariesRoot(), { recursive: true });
324
+ if (!summarySweepDone) {
325
+ summarySweepDone = true;
326
+ void sweepDirectory(summariesRoot(), RECORD_TTL_MS, MAX_RECORD_FILES);
327
+ }
328
+ return await withSummaryLock(id, async () => {
329
+ // Deleted history stays deleted: a summary computed before the delete
330
+ // must not bring it back (a newly recorded turn ends the marker). The
331
+ // check and the write are one step against a delete in any process.
332
+ if (await sessionChangeHistoryCleared(id)) return false;
333
+ await summaryWriteHookForTest?.(id);
334
+ await writeJsonAtomically(summaryPath(id), {
335
+ version: RECORD_VERSION,
336
+ sessionId: id,
337
+ generation: Number(turn.generation) || 0,
338
+ baselineTree: clean(turn.baselineTree),
339
+ checkpointId: clean(turn.checkpointId),
340
+ toolFilesKey: summaryToolFilesKey(turn),
341
+ files: review.files,
342
+ patchTruncated: review.patchTruncated === true,
343
+ savedAt: Date.now(),
344
+ });
345
+ return true;
346
+ });
347
+ } catch {
348
+ return false;
349
+ }
350
+ });
351
+ }
352
+
353
+ /** The summary saved for exactly this record turn, else null. */
354
+ export async function loadTurnReviewSummary(sessionId, turn) {
355
+ const id = clean(sessionId);
356
+ if (!id || !turn?.baselineTree) return null;
357
+ try {
358
+ const parsed = JSON.parse(await readFile(summaryPath(id), 'utf8'));
359
+ if (parsed?.version !== RECORD_VERSION || parsed.sessionId !== id || !Array.isArray(parsed.files)) return null;
360
+ if (parsed.baselineTree !== clean(turn.baselineTree)) return null;
361
+ if (parsed.generation !== (Number(turn.generation) || 0)) return null;
362
+ if (parsed.checkpointId !== clean(turn.checkpointId)) return null;
363
+ if (parsed.toolFilesKey !== summaryToolFilesKey(turn)) return null;
364
+ return parsed;
365
+ } catch {
366
+ return null;
367
+ }
368
+ }
369
+
260
370
  /** Earliest retained baseline plus every path attributed to this session,
261
371
  * grouped by repository root. This is the durable source for the pane's
262
372
  * session-wide Diff and is intentionally separate from latest-turn Undo. */
@@ -268,6 +378,75 @@ export async function loadSessionSnapshotRecords(sessionId) {
268
378
  return sessionScopes(record).map(serializeScope);
269
379
  }
270
380
 
381
+ /**
382
+ * Permanently delete one session's retained change history: its turn
383
+ * baselines and session scopes, and every per-turn change record (stored
384
+ * patches, file metadata, applied/reverted state). The baseline index keeps a
385
+ * `historyCleared` marker so the session review answers an authoritative
386
+ * empty set instead of falling back to older sources; the next recorded turn
387
+ * starts a fresh scope. Each half is serialized with the other writes of its
388
+ * file. Resolves to the number of turn change records deleted.
389
+ */
390
+ export async function clearSessionChangeHistory(sessionId) {
391
+ const id = clean(sessionId);
392
+ if (!id) return 0;
393
+ await chain(id, async () => {
394
+ // The marker lands before the records go: a failure in between leaves
395
+ // the session answering empty, never resurrecting what was asked gone.
396
+ await mkdir(clearedRoot(), { recursive: true });
397
+ // Under the summary's file lock, shared with every process: a summary
398
+ // write that passed its marker check finishes before the marker lands (and
399
+ // is removed with it); one that starts later sees the marker and stays away.
400
+ await withSummaryLock(id, async () => {
401
+ await writeJsonAtomically(clearedPath(id), { version: RECORD_VERSION, sessionId: id, clearedAt: Date.now() });
402
+ await rm(summaryPath(id), { force: true });
403
+ });
404
+ await rm(recordPath(id), { force: true });
405
+ });
406
+ return await chain(`changes:${id}`, async () => {
407
+ const records = (await readChangesFile(id))?.records || [];
408
+ await rm(changesPath(id), { force: true });
409
+ changeRecordsCache.delete(id);
410
+ return records.length;
411
+ });
412
+ }
413
+
414
+ // "History deleted" markers live in their own directory: neither record
415
+ // sweep (age or count) reaches it, so a deletion stays in force for as long
416
+ // as the session exists. The next recorded turn ends it, and deleting the
417
+ // session removes it (forgetSessionChangeHistoryMarker).
418
+ function clearedRoot() {
419
+ return join(recordRoot(), 'cleared');
420
+ }
421
+
422
+ function clearedPath(sessionId) {
423
+ const hash = createHash('sha256').update(sessionId).digest('hex').slice(0, 24);
424
+ return join(clearedRoot(), `${hash}.json`);
425
+ }
426
+
427
+ /** End the cleared state: a new turn record of the session was written. */
428
+ async function endClearedHistory(sessionId) {
429
+ await rm(clearedPath(sessionId), { force: true });
430
+ }
431
+
432
+ /** The session's history was deleted and no turn has been recorded since. */
433
+ export async function sessionChangeHistoryCleared(sessionId) {
434
+ const id = clean(sessionId);
435
+ if (!id) return false;
436
+ try {
437
+ return JSON.parse(await readFile(clearedPath(id), 'utf8'))?.sessionId === id;
438
+ } catch {
439
+ return false;
440
+ }
441
+ }
442
+
443
+ /** The session itself was deleted: its history marker goes with it. */
444
+ export async function forgetSessionChangeHistoryMarker(sessionId) {
445
+ const id = clean(sessionId);
446
+ if (!id) return;
447
+ await chain(id, () => rm(clearedPath(id), { force: true })).catch(() => {});
448
+ }
449
+
271
450
  // Absolute, slash-normalized and (on Windows) case-folded: equal for every
272
451
  // spelling of one folder, so a stored root and a caller's root compare equal.
273
452
  function matchKey(value) {
@@ -520,6 +699,8 @@ export async function upsertTurnChangeRecord(sessionId, next) {
520
699
  else records.push(record);
521
700
  boundChangeRecords(records, checkpointId);
522
701
  await writeChangeRecords(id, records);
702
+ // Any new turn record (Git or not) starts the history again.
703
+ await endClearedHistory(id);
523
704
  return records.find((entry) => entry.checkpointId === checkpointId) || null;
524
705
  });
525
706
  }
@@ -4,6 +4,7 @@ import { dirname, isAbsolute, relative, resolve } from 'node:path';
4
4
  import {
5
5
  _resetTurnWorktreeSnapshotsForTest,
6
6
  createTurnWorktreeSnapshot,
7
+ diffTurnWorktreeHead,
7
8
  freezeTurnWorktreeChanges,
8
9
  noteTurnWorktreeChange,
9
10
  refreshTurnWorktreeSnapshot,
@@ -14,11 +15,15 @@ import {
14
15
  revertTurnWorktreeSnapshot,
15
16
  } from './turn-worktree-snapshot.mjs';
16
17
  import {
18
+ clearSessionChangeHistory as clearStoredChangeHistory,
17
19
  loadSessionSnapshotRecords,
18
20
  loadTurnChangeRecord,
19
21
  loadTurnChangeRecords,
22
+ loadTurnReviewSummary,
20
23
  loadTurnSnapshotRecord,
24
+ saveTurnReviewSummary,
21
25
  saveTurnSnapshotRecord,
26
+ sessionChangeHistoryCleared,
22
27
  updateTurnChangeRecord,
23
28
  upsertTurnChangeRecord,
24
29
  } from './turn-snapshot-store.mjs';
@@ -27,13 +32,21 @@ import {
27
32
  changePathKey,
28
33
  findChangeFile,
29
34
  measureLegacyChangeFile,
30
- mergeRecordedSessionChanges,
31
35
  namedChangePatch,
36
+ sessionChangeReview,
32
37
  storedChangeFile,
33
38
  trackedChangeFile,
34
39
  turnChangesReply,
35
40
  unknownStateChangeFile,
36
41
  } from './turn-changes.mjs';
42
+ import {
43
+ _resetSessionBaselinesForTest,
44
+ captureSessionBaselines,
45
+ clearSessionBaselines,
46
+ loadSessionBaselines,
47
+ primeReviewOwner,
48
+ recordReviewOwner,
49
+ } from './session-baseline-store.mjs';
37
50
  import { boundReviewPatch } from './review-diff.mjs';
38
51
  import { PATCH_EDIT_MAX_BYTES } from './bounded-file-read.mjs';
39
52
  import { cleanString as clean } from './clean.mjs';
@@ -473,13 +486,48 @@ export function recordTurnDiffChanges(sessionId, changes = []) {
473
486
  const id = clean(sessionId);
474
487
  if (!id) return '';
475
488
  const tracker = _diffTrackersBySession.get(id) || resetDiffTracker(id);
489
+ const list = Array.isArray(changes) ? changes : [];
490
+ const ownerTracker = tracker?.ownerSessionId ? _diffTrackersBySession.get(tracker.ownerSessionId) : tracker;
491
+ const snapshot = ownerTracker?.worktreeSnapshot;
492
+ // Independent of this tracker's validity: the session review needs every
493
+ // write's first-seen state even once the turn's buffers are gone.
494
+ captureReviewBaselines(id, tracker, ownerTracker, list, snapshot);
476
495
  if (!tracker?.valid || tracker.sealed) return tracker?.unifiedDiff || '';
477
- const snapshot = (tracker.ownerSessionId ? _diffTrackersBySession.get(tracker.ownerSessionId) : tracker)
478
- ?.worktreeSnapshot;
479
- foldTrackedChanges(tracker, Array.isArray(changes) ? changes : [], snapshot);
496
+ foldTrackedChanges(tracker, list, snapshot);
480
497
  return tracker.unifiedDiff;
481
498
  }
482
499
 
500
+ const insidePath = (root, path) => {
501
+ const rel = relative(root, path);
502
+ return Boolean(rel) && !isAbsolute(rel) && rel.split(/[\\/]+/)[0] !== '..';
503
+ };
504
+
505
+ /** Hand each change's first-seen state to the session baselines of `sessionId`
506
+ * and the Lead it works for. A path inside the turn's Git root is skipped: the
507
+ * shadow baseline tree already vouches for it. */
508
+ function captureReviewBaselines(sessionId, tracker, ownerTracker, list, snapshot) {
509
+ const gitRoot = clean(snapshot?.root);
510
+ const root = clean(tracker?.worktreeRoot) || clean(ownerTracker?.worktreeRoot);
511
+ const changes = [];
512
+ for (const raw of list) {
513
+ const path = clean(raw?.path);
514
+ if (!path || !isAbsolute(path)) continue;
515
+ const newPath = isAbsolute(clean(raw?.newPath)) ? resolve(clean(raw.newPath)) : '';
516
+ if (gitRoot && insidePath(gitRoot, path) && (!newPath || insidePath(gitRoot, newPath))) continue;
517
+ const before = raw?.before ?? null;
518
+ const after = raw?.after ?? null;
519
+ if (!newPath && before === null && after === null) continue;
520
+ changes.push({
521
+ path: resolve(path),
522
+ root,
523
+ before,
524
+ ...(newPath ? { newPath } : {}),
525
+ replaced: (before === null) !== (after === null),
526
+ });
527
+ }
528
+ captureSessionBaselines(sessionId, changes, { owner: tracker?.ownerSessionId || '' });
529
+ }
530
+
483
531
  /** Fold exact before/after states into `tracker`. `snapshot` — the owning
484
532
  * Lead turn's Git baseline, when tools are writing — learns each written
485
533
  * path's first-before bytes. */
@@ -626,6 +674,9 @@ function continueTurnSnapshot(ownerSessionId) {
626
674
  export async function beginTurnSnapshot(worktree, sessionId, options = {}) {
627
675
  const ownerSessionId = clean(sessionId);
628
676
  if (DISABLED || !ownerSessionId) return undefined;
677
+ // A worker resumed after a restart still credits its Lead's review (a
678
+ // capture racing this lookup waits for it).
679
+ void primeReviewOwner(ownerSessionId);
629
680
  // Turn start and Undo/Reapply share the per-session lock: a start waits for
630
681
  // an in-flight mutation, and a mutation queued after this start re-checks
631
682
  // under the lock and finds the turn running.
@@ -701,6 +752,9 @@ export function beginAgentTurnReview(ownerSessionId, childSessionId, meta = {})
701
752
  if (DISABLED) return null;
702
753
  const owner = clean(ownerSessionId);
703
754
  const child = clean(childSessionId);
755
+ // Durable: the worker's edits join the Lead's session review even when it
756
+ // runs its own session turn, and whatever the turn's agent list keeps.
757
+ recordReviewOwner(child, owner);
704
758
  const turn = _turnsBySession.get(owner);
705
759
  if (!owner || !child || !turn) return null;
706
760
  const existingTracker = _diffTrackersBySession.get(child);
@@ -735,11 +789,15 @@ export function completeAgentTurnReview(handle, patches = []) {
735
789
  const tracker = _diffTrackersBySession.get(key);
736
790
  const matchingTracker =
737
791
  tracker && tracker.ownerSessionId === clean(handle.ownerSessionId) && tracker.ownerGeneration === handle.generation;
792
+ // A worker that runs its own session turn replaces the bound tracker with
793
+ // its turn's own one (no owner). beginAgentTurnReview reset any older
794
+ // tracker, so an unowned one here holds this execution's edits.
795
+ const source = matchingTracker || (tracker && !tracker.ownerSessionId) ? tracker : null;
738
796
  try {
739
797
  if (!turn || turn.generation !== handle.generation) return false;
740
798
  const prior = turn.agents.get(key);
741
- const tracked = matchingTracker && tracker.revision > 0;
742
- const patch = tracked ? tracker.unifiedDiff : mergePatches(patches);
799
+ const tracked = Boolean(source) && source.revision > 0;
800
+ const patch = tracked ? source.unifiedDiff : mergePatches(patches);
743
801
  if (!patch) {
744
802
  if (tracked && prior) {
745
803
  turn.agents.delete(key);
@@ -755,8 +813,8 @@ export function completeAgentTurnReview(handle, patches = []) {
755
813
  patch: tracked ? patch : mergePatches([prior?.patch, patch]),
756
814
  });
757
815
  const ownerTracker = _diffTrackersBySession.get(clean(handle.ownerSessionId));
758
- if (matchingTracker && ownerTracker && ownerTracker !== tracker) {
759
- for (const path of tracker.ownedPaths) ownerTracker.ownedPaths.add(path);
816
+ if (source && ownerTracker && ownerTracker !== source) {
817
+ for (const path of source.ownedPaths) ownerTracker.ownedPaths.add(path);
760
818
  }
761
819
  trimAgentReviews(turn);
762
820
  // A worker that finishes after its Lead turn sealed re-seals that turn's
@@ -780,7 +838,7 @@ export function completeAgentTurnReview(handle, patches = []) {
780
838
  * are precisely the attribution that contention destroys. */
781
839
  async function persistTurnSnapshotRecord(ownerSessionId, tracker) {
782
840
  const snapshot = tracker?.worktreeSnapshot;
783
- if (!ownerSessionId || !snapshot?.baselineTree || !snapshot.root) return;
841
+ if (!ownerSessionId || tracker.discarded || !snapshot?.baselineTree || !snapshot.root) return;
784
842
  const owned = [...sessionAttributedPaths(snapshot.root, tracker)];
785
843
  if (owned.length === 0) return;
786
844
  try {
@@ -937,6 +995,8 @@ const pendingFreezeKey = (ownerSessionId, checkpointId) => `${ownerSessionId}\0$
937
995
  * re-freezes a turn that is still sealed.
938
996
  */
939
997
  function sealTurn(ownerSessionId, tracker, { reseal = false } = {}) {
998
+ // Its history was deleted (clearSessionChangeHistory): never written again.
999
+ if (tracker.discarded) return Promise.resolve();
940
1000
  if (tracker.freezing) {
941
1001
  if (reseal) return tracker.freezing;
942
1002
  return tracker.freezing.then(() => (tracker.sealed ? undefined : sealTurn(ownerSessionId, tracker)));
@@ -953,11 +1013,11 @@ function sealTurn(ownerSessionId, tracker, { reseal = false } = {}) {
953
1013
  // Best-effort refresh: seal with the last captured worktree state.
954
1014
  }
955
1015
  }
956
- if (tracker.sealEpoch !== epoch || (reseal && !tracker.sealed)) return;
1016
+ if (tracker.discarded || tracker.sealEpoch !== epoch || (reseal && !tracker.sealed)) return;
957
1017
  tracker.sealed = true;
958
1018
  await persistTurnSnapshotRecord(ownerSessionId, tracker);
959
1019
  try {
960
- await freezeTurnChanges(ownerSessionId, tracker, agents);
1020
+ if (!tracker.discarded) await freezeTurnChanges(ownerSessionId, tracker, agents);
961
1021
  } catch {
962
1022
  /* the change record adds Undo history; it never fails a turn */
963
1023
  }
@@ -1055,6 +1115,7 @@ async function resumeRecordedSnapshot(ownerSessionId, { omitPatch = false } = {}
1055
1115
  async function scopedReviewFromRecord(ownerSessionId, { omitPatch = false } = {}) {
1056
1116
  const resumed = await resumeRecordedSnapshot(ownerSessionId, { omitPatch });
1057
1117
  if (!resumed) return null;
1118
+ void saveTurnReviewSummary(ownerSessionId, resumed.record, resumed.snapshot);
1058
1119
  return {
1059
1120
  supported: true,
1060
1121
  files: resumed.snapshot.files || [],
@@ -1070,6 +1131,28 @@ async function scopedReviewFromRecord(ownerSessionId, { omitPatch = false } = {}
1070
1131
  };
1071
1132
  }
1072
1133
 
1134
+ /** The files and counts last computed from the durable record, without
1135
+ * rescanning the worktree. `cached` (not `recorded`): the caller's
1136
+ * refreshing read still follows and replaces it. */
1137
+ async function cachedReviewFromRecord(ownerSessionId) {
1138
+ const record = await loadTurnSnapshotRecord(ownerSessionId).catch(() => null);
1139
+ const cached = record ? await loadTurnReviewSummary(ownerSessionId, record) : null;
1140
+ if (!cached) return null;
1141
+ return {
1142
+ supported: true,
1143
+ files: cached.files,
1144
+ patch: '',
1145
+ patchOmitted: true,
1146
+ snapshotKind: 'scoped',
1147
+ revertMode: 'scoped',
1148
+ checkpointId: clean(record.checkpointId),
1149
+ patchTruncated: cached.patchTruncated === true,
1150
+ authoritative: true,
1151
+ agents: publicAgentReviews(ownerSessionId),
1152
+ reason: 'cached',
1153
+ };
1154
+ }
1155
+
1073
1156
  function assertCheckpointMatches(expectedCheckpointId, actualCheckpointId) {
1074
1157
  const expected = clean(expectedCheckpointId);
1075
1158
  if (!expected) return;
@@ -1123,6 +1206,12 @@ export async function getTurnReviewDiff(_worktree, sessionId, options = {}) {
1123
1206
  // turn cache evicted it, or the runtime restarted. The recorded baseline is
1124
1207
  // still on disk and still exact. Rebuilt per read and then discarded, so a
1125
1208
  // summary read skips its patch text instead of dropping it afterwards.
1209
+ // A collapsed read that asked for no refresh (entering the session) takes
1210
+ // the last computed summary instead of queueing a rescan.
1211
+ if (options.refresh === false && options.summary === true) {
1212
+ const cached = await cachedReviewFromRecord(ownerSessionId);
1213
+ if (cached) return cached;
1214
+ }
1126
1215
  const recorded = await scopedReviewFromRecord(ownerSessionId, { omitPatch: options.summary === true });
1127
1216
  if (recorded) return recorded;
1128
1217
  }
@@ -1152,44 +1241,27 @@ function scopeMatchesWorktree(scope, worktree, tracker) {
1152
1241
  return requested === root || requested.startsWith(`${root}${process.platform === 'win32' ? '\\' : '/'}`);
1153
1242
  }
1154
1243
 
1155
- /** Return the net diff from this session's earliest retained baseline to the
1156
- * current worktree, scoped only to paths its own tools changed. Unlike
1157
- * getTurnReviewDiff this survives user-turn boundaries and has no revert
1158
- * contract; the composer keeps owning latest-turn Undo. */
1244
+ /** Return the session's net change: inside a Git root, the diff from its
1245
+ * earliest retained baseline tree to the worktree, scoped to the paths it
1246
+ * changed; everywhere else (a non-Git root, files outside the Git root) each
1247
+ * file's FIRST state observed in the session — by the Lead or a worker
1248
+ * attributed to it — against the disk now. Unlike getTurnReviewDiff this
1249
+ * survives user-turn boundaries and has no revert contract; the composer
1250
+ * keeps owning latest-turn Undo. */
1159
1251
  export async function getSessionReviewDiff(worktree, sessionId) {
1160
1252
  if (DISABLED) return { supported: false, files: [], patch: '' };
1161
1253
  const ownerSessionId = clean(sessionId);
1162
1254
  if (!ownerSessionId) return { supported: true, files: [], patch: '', authoritative: true };
1163
- const review = await baselineSessionReviewDiff(worktree, ownerSessionId);
1164
- // The same recorded changes the turn-changes cards list: a non-Git session
1165
- // and files outside the worktree have no baseline above, but do have these.
1166
- // A Git session whose baseline vouches for its files has nothing to add but
1167
- // edits outside the root, so its records are read only through the
1168
- // mtime-keyed cache.
1169
- const records = await loadTurnChangeRecords(ownerSessionId).catch(() => []);
1170
- const live = review.source === 'git' ? null : liveTrackedRecord(_diffTrackersBySession.get(ownerSessionId));
1171
- return mergeRecordedSessionChanges(review, records, live);
1172
- }
1173
-
1174
- const LIVE_RECORD_TTL_MS = 1000;
1175
- const _liveRecords = new WeakMap();
1176
-
1177
- /** The running non-Git turn as a record, so its edits join the net review
1178
- * before the turn seals. Rebuilt at most once a second. */
1179
- function liveTrackedRecord(tracker) {
1180
- if (!tracker || tracker.worktreeSnapshot || !tracker.worktreeRoot || !clean(tracker.checkpointId)) return null;
1181
- const cached = _liveRecords.get(tracker);
1182
- if (cached && Date.now() - cached.at < LIVE_RECORD_TTL_MS && cached.checkpointId === tracker.checkpointId) {
1183
- return cached.record;
1184
- }
1185
- const record = {
1186
- checkpointId: clean(tracker.checkpointId),
1187
- kind: 'tracked',
1188
- root: tracker.worktreeRoot,
1189
- files: trackedRecordFiles(tracker, tracker.worktreeRoot).map(storedChangeFile),
1190
- };
1191
- _liveRecords.set(tracker, { at: Date.now(), checkpointId: record.checkpointId, record });
1192
- return record;
1255
+ const [review, baselines, records] = await Promise.all([
1256
+ baselineSessionReviewDiff(worktree, ownerSessionId),
1257
+ loadSessionBaselines(ownerSessionId).catch(() => null),
1258
+ // Read through the mtime-keyed cache: only files no baseline knows (a
1259
+ // session recorded before baselines existed) come from them.
1260
+ loadTurnChangeRecords(ownerSessionId).catch(() => []),
1261
+ ]);
1262
+ const merged = await sessionChangeReview(review, { baselines, records });
1263
+ // `historyTurns`: the retained turn records clearSessionChangeHistory deletes.
1264
+ return { ...merged, historyTurns: records.length };
1193
1265
  }
1194
1266
 
1195
1267
  async function baselineSessionReviewDiff(worktree, ownerSessionId) {
@@ -1211,8 +1283,9 @@ async function baselineSessionReviewDiff(worktree, ownerSessionId) {
1211
1283
  for (const path of sessionAttributedPaths(root, tracker)) ownedPaths.add(path);
1212
1284
  if (ownedPaths.size === 0) {
1213
1285
  // Authoritatively empty only when the session left ownership metadata
1214
- // behind (a stored scope or a live tracker); with neither, the retained
1215
- // change records are the only memory of what it changed.
1286
+ // behind (a stored scope, a live tracker, or a deleted history); with
1287
+ // none, the retained change records are the only memory of what it changed.
1288
+ const known = Boolean(stored || tracker) || (await sessionChangeHistoryCleared(ownerSessionId).catch(() => false));
1216
1289
  return {
1217
1290
  supported: true,
1218
1291
  root,
@@ -1220,7 +1293,7 @@ async function baselineSessionReviewDiff(worktree, ownerSessionId) {
1220
1293
  patch: '',
1221
1294
  snapshotKind: 'session',
1222
1295
  authoritative: true,
1223
- source: stored || tracker ? 'empty' : 'absent',
1296
+ source: known ? 'empty' : 'absent',
1224
1297
  };
1225
1298
  }
1226
1299
  const baselineTree = stored?.baselineTree || tracker?.worktreeSnapshot?.baselineTree;
@@ -1235,6 +1308,9 @@ async function baselineSessionReviewDiff(worktree, ownerSessionId) {
1235
1308
  baselineFiles: [...baselineFiles.values()],
1236
1309
  }).catch(() => null);
1237
1310
  if (snapshot) {
1311
+ // The island counts only what is still uncommitted: the owned files
1312
+ // that differ from HEAD now, measured against HEAD.
1313
+ const uncommittedFiles = await diffTurnWorktreeHead(snapshot, [...ownedPaths]).catch(() => null);
1238
1314
  return {
1239
1315
  supported: true,
1240
1316
  root,
@@ -1244,31 +1320,37 @@ async function baselineSessionReviewDiff(worktree, ownerSessionId) {
1244
1320
  snapshotKind: 'session',
1245
1321
  authoritative: true,
1246
1322
  source: 'git',
1323
+ ...(uncommittedFiles ? { uncommittedFiles } : {}),
1247
1324
  };
1248
1325
  }
1249
1326
  }
1250
1327
  // A non-Git first turn still has an exact apply_patch tracker. It cannot
1251
1328
  // cross a runtime restart, but remains preferable to widening into unrelated
1252
1329
  // files from the surrounding directory.
1330
+ // `owned` lets the session review flag an owned file no other source knows.
1253
1331
  if (tracker?.valid) {
1254
1332
  return {
1255
1333
  supported: true,
1334
+ root,
1256
1335
  files: [],
1257
1336
  patch: tracker.unifiedDiff || '',
1258
1337
  patchTruncated: tracker.patchTruncated === true,
1259
1338
  snapshotKind: 'session-tool',
1260
1339
  authoritative: true,
1261
1340
  source: 'tool',
1341
+ owned: [...ownedPaths],
1262
1342
  };
1263
1343
  }
1264
1344
  return {
1265
1345
  supported: false,
1346
+ root,
1266
1347
  files: [],
1267
1348
  patch: '',
1268
1349
  snapshotKind: 'session',
1269
1350
  authoritative: false,
1270
1351
  reason: 'session baseline is unavailable',
1271
1352
  source: 'unavailable',
1353
+ owned: [...ownedPaths],
1272
1354
  };
1273
1355
  }
1274
1356
 
@@ -1625,6 +1707,45 @@ export function reapplyTurnChanges(sessionId, checkpointId, file, revision) {
1625
1707
  return mutateTurnChanges(sessionId, checkpointId, file, revision, false);
1626
1708
  }
1627
1709
 
1710
+ /** Permanently delete this session's retained change history — every turn's
1711
+ * change record (patches, file metadata, applied/reverted state), its stored
1712
+ * Git baselines, and the live and latest-turn trackers that would resurface
1713
+ * them — so its reviews answer empty, across a restart too. Edits from the
1714
+ * next turn on are tracked again. Refused while a turn runs or seals; on the
1715
+ * per-session lock, behind any Undo/Reapply. Files, the Git index, commits
1716
+ * and the conversation are never touched. Resolves to `{ turns }`, the
1717
+ * number of turn records deleted. */
1718
+ export async function clearSessionChangeHistory(sessionId) {
1719
+ const ownerSessionId = clean(sessionId);
1720
+ if (!ownerSessionId) throw new Error('change history is unavailable without a session');
1721
+ return await serializeChangeMutation(ownerSessionId, async () => {
1722
+ if (sessionTurnBusy(ownerSessionId)) {
1723
+ throw new Error('change history cannot be deleted while this session is running a turn');
1724
+ }
1725
+ // Invalidate every producer of the old history BEFORE the awaited
1726
+ // deletion: a worker finishing meanwhile finds no turn to fold into, and
1727
+ // a re-seal it (or anything earlier) already queued finds its tracker
1728
+ // discarded, so nothing begun before the delete can write it back.
1729
+ for (const [trackedSessionId, tracker] of _diffTrackersBySession) {
1730
+ if (trackedSessionId !== ownerSessionId && tracker.ownerSessionId !== ownerSessionId) continue;
1731
+ tracker.discarded = true;
1732
+ tracker.resealQueued = false;
1733
+ releaseDiffTrackerContent(tracker);
1734
+ _diffTrackersBySession.delete(trackedSessionId);
1735
+ }
1736
+ _turnsBySession.delete(ownerSessionId);
1737
+ for (const key of _unknownChangeStates.keys()) {
1738
+ if (key.startsWith(`${ownerSessionId}\0`)) _unknownChangeStates.delete(key);
1739
+ }
1740
+ // The session's first-seen baselines go too: later edits start fresh ones.
1741
+ const [turns] = await Promise.all([
1742
+ clearStoredChangeHistory(ownerSessionId),
1743
+ clearSessionBaselines(ownerSessionId),
1744
+ ]);
1745
+ return { turns };
1746
+ });
1747
+ }
1748
+
1628
1749
  export function _resetTurnSnapshotForTest() {
1629
1750
  _turnsBySession.clear();
1630
1751
  _diffTrackersBySession.clear();
@@ -1634,4 +1755,5 @@ export function _resetTurnSnapshotForTest() {
1634
1755
  _turnOpenedHookForTest = null;
1635
1756
  _agentTurnSeq = 0;
1636
1757
  _resetTurnWorktreeSnapshotsForTest();
1758
+ _resetSessionBaselinesForTest();
1637
1759
  }