mixdog 1.1.1 → 1.2.0

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 (317) hide show
  1. package/README.md +1 -0
  2. package/package.json +2 -2
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/release-paths.mjs +39 -2
  6. package/scripts/test-direct.mjs +4 -2
  7. package/src/defaults/shipped-history.json +6 -0
  8. package/src/defaults/skills/setup/references/actions.md +5 -2
  9. package/src/defaults/skills/visualize/SKILL.md +157 -0
  10. package/src/defaults/skills/visualize/references/craft.md +88 -0
  11. package/src/headless-command.mjs +56 -3
  12. package/src/headless-exec.mjs +17 -1
  13. package/src/headless-role.mjs +2 -2
  14. package/src/help.mjs +2 -0
  15. package/src/lib/rules-builder.cjs +0 -23
  16. package/src/rules/lead/LEAD.md +2 -2
  17. package/src/rules/shared/70-delivery.md +2 -2
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  21. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  22. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  23. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  24. package/src/runtime/agent/orchestrator/config.mjs +9 -1
  25. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +47 -7
  26. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +51 -8
  27. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +25 -5
  28. package/src/runtime/agent/orchestrator/mcp/client.mjs +348 -122
  29. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  31. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  32. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +13 -1
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  34. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  35. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  36. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +12 -4
  37. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  38. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  40. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  41. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  43. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  44. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +67 -0
  45. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +521 -0
  46. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  47. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  48. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  49. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +3 -0
  50. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  51. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  52. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  53. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  54. package/src/runtime/agent/orchestrator/providers/registry.mjs +15 -1
  55. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  56. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  57. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +12 -11
  58. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  59. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +11 -0
  60. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +11 -8
  61. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  62. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  63. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  64. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  65. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +18 -11
  66. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  67. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  68. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  69. package/src/runtime/agent/orchestrator/session/context-utils.mjs +12 -14
  70. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  71. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  72. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  74. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -1
  75. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +1 -0
  76. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +17 -7
  77. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  78. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -2
  79. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -0
  80. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +5 -3
  81. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  82. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  83. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  84. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  85. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  86. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  87. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  88. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  89. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  91. package/src/runtime/agent/orchestrator/session/manager.mjs +1 -5
  92. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  93. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  94. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +29 -19
  95. package/src/runtime/agent/orchestrator/session/read-dedup.mjs +0 -1
  96. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  97. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  98. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  99. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +29 -0
  100. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -1
  101. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +23 -4
  102. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  103. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +28 -8
  104. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +140 -0
  105. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  106. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  107. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +31 -12
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  111. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  112. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  113. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -66
  114. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  115. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  117. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  118. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  119. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  121. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  123. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  124. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  125. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  126. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  127. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -10
  128. package/src/runtime/attachments/store.mjs +38 -0
  129. package/src/runtime/channels/index.mjs +1 -5
  130. package/src/runtime/channels/lib/runtime-paths.mjs +8 -23
  131. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -13
  132. package/src/runtime/channels/lib/worker-ipc.mjs +2 -2
  133. package/src/runtime/channels/lib/worker-main.mjs +4 -34
  134. package/src/runtime/shared/atomic-file.mjs +8 -2
  135. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  136. package/src/runtime/shared/code-graph-state.mjs +12 -0
  137. package/src/runtime/shared/defaults-separation.mjs +2 -0
  138. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  139. package/src/runtime/shared/git-runner.mjs +4 -3
  140. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  141. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  142. package/src/runtime/shared/llm/model-catalog.mjs +240 -118
  143. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  144. package/src/runtime/shared/llm/provider-catalog-registry.mjs +156 -0
  145. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  146. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  147. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  148. package/src/runtime/shared/provider-accounts.mjs +2 -2
  149. package/src/runtime/shared/provider-api-key.mjs +21 -1
  150. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  151. package/src/runtime/shared/semver.d.mts +1 -0
  152. package/src/runtime/shared/semver.mjs +57 -0
  153. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  154. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  155. package/src/runtime/shared/singleton-owner.mjs +36 -6
  156. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  157. package/src/runtime/shared/tool-surface.mjs +9 -2
  158. package/src/runtime/shared/tool-work-units.mjs +14 -0
  159. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  160. package/src/runtime/shared/turn-changes.mjs +791 -0
  161. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  162. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  163. package/src/runtime/shared/turn-worktree-snapshot.mjs +161 -11
  164. package/src/runtime/shared/update-checker.mjs +2 -31
  165. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  166. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  167. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  168. package/src/runtime/shared/worktrees/git.mjs +247 -0
  169. package/src/runtime/shared/worktrees/include.mjs +480 -0
  170. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  171. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  172. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  173. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  174. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  175. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  176. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  177. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  178. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  179. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  180. package/src/runtime/visualize-bridge/client.mjs +181 -0
  181. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  182. package/src/session-runtime/boot/apis.mjs +35 -0
  183. package/src/session-runtime/boot/begin.mjs +0 -2
  184. package/src/session-runtime/boot/config.mjs +2 -0
  185. package/src/session-runtime/boot/facade.mjs +20 -0
  186. package/src/session-runtime/boot/infrastructure.mjs +1 -0
  187. package/src/session-runtime/boot/session.mjs +1 -0
  188. package/src/session-runtime/boot/tools.mjs +9 -4
  189. package/src/session-runtime/config-lifecycle.mjs +10 -1
  190. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +11 -1
  191. package/src/session-runtime/global-extensions.mjs +2 -2
  192. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  193. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  194. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  195. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  196. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  197. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  198. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  199. package/src/session-runtime/mcp-glue/connect.mjs +11 -6
  200. package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -3
  201. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  202. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  203. package/src/session-runtime/model-route/set-route.mjs +15 -2
  204. package/src/session-runtime/notification-bus.mjs +9 -1
  205. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  206. package/src/session-runtime/resource-extension-sync.mjs +30 -7
  207. package/src/session-runtime/resource-mcp-api.mjs +27 -10
  208. package/src/session-runtime/route-resolution.mjs +10 -1
  209. package/src/session-runtime/route-state.mjs +16 -2
  210. package/src/session-runtime/runtime-bootstrap.mjs +0 -4
  211. package/src/session-runtime/runtime-core.mjs +5 -0
  212. package/src/session-runtime/runtime-facade.mjs +8 -0
  213. package/src/session-runtime/runtime-paths.mjs +0 -1
  214. package/src/session-runtime/runtime-review-api.mjs +9 -0
  215. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  216. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  217. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  218. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  219. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  220. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  221. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  222. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  223. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  224. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  225. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  226. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  227. package/src/session-runtime/services/agent-tool.mjs +1 -0
  228. package/src/session-runtime/services/channel-worker.mjs +1 -1
  229. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  230. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  231. package/src/session-runtime/session-create/session-options.mjs +10 -1
  232. package/src/session-runtime/session-turn-api.mjs +1 -0
  233. package/src/session-runtime/settings-api.mjs +4 -2
  234. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  235. package/src/session-runtime/settings-profile-api.mjs +27 -2
  236. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  237. package/src/session-runtime/setup-tool/executor.mjs +11 -4
  238. package/src/session-runtime/setup-tool/settings-contract.mjs +3 -1
  239. package/src/session-runtime/setup-tool/tool-defs.mjs +31 -3
  240. package/src/session-runtime/starter-guide-state.mjs +62 -0
  241. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  242. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +13 -3
  243. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  244. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  245. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  246. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  247. package/src/session-runtime/turn/turn-run.mjs +6 -7
  248. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  249. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -2
  250. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  251. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  252. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  253. package/src/session-runtime/workspace/transitions.mjs +651 -0
  254. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  255. package/src/standalone/channel-binding.mjs +1 -2
  256. package/src/standalone/channel-transport/call-dispatch.mjs +4 -10
  257. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  258. package/src/standalone/channel-transport.mjs +13 -1
  259. package/src/standalone/daemon-channels-loader.mjs +2 -3
  260. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  261. package/src/standalone/daemon-owner.mjs +372 -0
  262. package/src/standalone/daemon-stored-session-views.mjs +4 -1
  263. package/src/standalone/daemon.mjs +139 -5
  264. package/src/standalone/desktop-service-registry.mjs +20 -0
  265. package/src/standalone/local-session-runtime.mjs +13 -78
  266. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  267. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  268. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  269. package/src/standalone/session-client.mjs +104 -13
  270. package/src/standalone/session-protocol.mjs +17 -0
  271. package/src/standalone/session-proxy.mjs +3 -0
  272. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  273. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  274. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  275. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  276. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  277. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  278. package/src/standalone/session-service/session-calls/session-action.mjs +11 -3
  279. package/src/standalone/session-transport/call-route.mjs +13 -0
  280. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  281. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  282. package/src/standalone/session-transport/client-registry.mjs +20 -3
  283. package/src/standalone/session-transport/routes.mjs +79 -2
  284. package/src/standalone/session-transport/stack.mjs +2 -0
  285. package/src/standalone/session-transport.mjs +24 -1
  286. package/src/tui/app/message-selector.mjs +2 -1
  287. package/src/tui/app/model-options.mjs +35 -5
  288. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  289. package/src/tui/app/model-picker.mjs +4 -2
  290. package/src/tui/dist/index.mjs +332 -36
  291. package/src/tui/index.jsx +10 -2
  292. package/src/tui/session/compaction-view.mjs +147 -0
  293. package/src/tui/session/context-state.mjs +6 -0
  294. package/src/tui/session/initial-state.mjs +3 -0
  295. package/src/tui/session/quota-resume.mjs +2 -1
  296. package/src/tui/session/session-api/commands.mjs +10 -2
  297. package/src/tui/session/session-api/controls.mjs +4 -0
  298. package/src/tui/session/session-api/extensions.mjs +12 -8
  299. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  300. package/src/tui/session/session-api/integrations.mjs +18 -0
  301. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  302. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  303. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  304. package/src/tui/session/session-api/routes.mjs +10 -1
  305. package/src/tui/session/session-api/settings.mjs +2 -0
  306. package/src/tui/session/session-api/shared.mjs +10 -6
  307. package/src/tui/session/session-api/worktree.mjs +60 -0
  308. package/src/tui/session/session-api.mjs +2 -0
  309. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  310. package/src/tui/session/session-flow/drain.mjs +15 -1
  311. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  312. package/src/tui/session/session-flow.mjs +2 -0
  313. package/src/tui/session/transcript-intake.mjs +14 -5
  314. package/src/tui/session/turn-stream.mjs +32 -4
  315. package/src/tui/session/turn-tool-cards.mjs +10 -2
  316. package/src/tui/session/turn.mjs +39 -10
  317. package/src/runtime/channels/tool-defs.mjs +0 -17
@@ -0,0 +1,147 @@
1
+ /**
2
+ * src/tui/session/compaction-view.mjs - the visible transcript across a
3
+ * compaction. The rows the compaction took out of the model context stay in
4
+ * view, marked `archived` (display-only: greyed, no model-state actions), and
5
+ * the completion row becomes the "compacted" divider at the boundary.
6
+ *
7
+ * The model transcript itself belongs to runtime.compact / the pre-send pass;
8
+ * the durable copy of the dropped rows is the runtime's compaction archive.
9
+ */
10
+ import { COMPACTION_DIVIDER_LABEL } from '../../runtime/agent/orchestrator/session/compaction-archive.mjs';
11
+
12
+ function isCompactionDivider(item) {
13
+ return item?.kind === 'statusdone' && item.status === 'compacted';
14
+ }
15
+
16
+ function archivedItem(item) {
17
+ if (!item || typeof item !== 'object' || item.archived === true || isCompactionDivider(item)) return item;
18
+ return { ...item, archived: true };
19
+ }
20
+
21
+ /** `divider` (id, detail) as the compaction boundary row. Every path — manual,
22
+ * auto-clear, mid-turn — and the persisted archive carry the one compaction
23
+ * label; the stats stay in `detail`. */
24
+ function compactionDividerItem(item) {
25
+ return {
26
+ ...item,
27
+ kind: 'statusdone',
28
+ status: 'compacted',
29
+ label: COMPACTION_DIVIDER_LABEL,
30
+ at: item?.at ?? Date.now(),
31
+ };
32
+ }
33
+
34
+ /** Row kinds a transcript message backs (prompt, reply, tool card). */
35
+ const MESSAGE_ROW_KINDS = new Set(['user', 'assistant', 'tool']);
36
+
37
+ const idSet = (values) => new Set(Array.isArray(values) ? values.filter((value) => value != null) : []);
38
+
39
+ /** The tool calls a tool row shows: its own call, a restored aggregate's
40
+ * calls, a live aggregate's members (whose ids are their call ids). */
41
+ function toolCallIdsOfItem(item) {
42
+ if (item?.kind !== 'tool') return [];
43
+ if (item.callId) return [String(item.callId)];
44
+ if (Array.isArray(item.callIds) && item.callIds.length) return item.callIds.filter(Boolean).map(String);
45
+ if (item.aggregate === true && Array.isArray(item.toolMembers)) {
46
+ return item.toolMembers
47
+ .map((member) => member?.id)
48
+ .filter((id) => id != null)
49
+ .map(String);
50
+ }
51
+ return [];
52
+ }
53
+
54
+ /**
55
+ * Which rows a compaction took out of the model context, from the result or
56
+ * event it reported:
57
+ * - `droppedIdentities` ({ messageIds, callIds }) — whenever supplied,
58
+ * whatever `droppedMessages` says — by id membership only:
59
+ * a tool row whose calls all left (or, without call ids, whose message
60
+ * id left); any other row with a message of its own (prompt, reply,
61
+ * notification, restored marker) whose message id left — a prompt,
62
+ * reply or tool row without an id stays live. Only a row with no
63
+ * message of its own (a live completion, notice or status row) follows
64
+ * its turn's prompt;
65
+ * - no identities and `droppedMessages: 0` (a rewrite-only pass): none;
66
+ * - neither (a source that reports nothing): every row.
67
+ * Returns a predicate over the rows before the boundary, in order.
68
+ */
69
+ export function compactionArchivedRows(result) {
70
+ const identities = result?.droppedIdentities;
71
+ // Identities decide when supplied: a kept message can still have lost
72
+ // part of itself (its text, some calls) with no message fully dropped.
73
+ // The count is only the fallback for a source without identities.
74
+ if (!identities || typeof identities !== 'object') {
75
+ const count = result?.droppedMessages;
76
+ return count !== undefined && count !== null && !(Number(count) > 0) ? () => false : () => true;
77
+ }
78
+ const messageIds = idSet(identities.messageIds);
79
+ const callIds = idSet(identities.callIds);
80
+ let promptDropped = false;
81
+ const predicate = (item) => {
82
+ if (!item || typeof item !== 'object') return false;
83
+ const calls = toolCallIdsOfItem(item);
84
+ let dropped;
85
+ if (calls.length) dropped = calls.every((id) => callIds.has(id));
86
+ // A message-backed row is decided by its own id alone: one without an id
87
+ // has no display identity in the result and stays live.
88
+ else if (item.messageId || MESSAGE_ROW_KINDS.has(item.kind)) {
89
+ dropped = Boolean(item.messageId) && messageIds.has(item.messageId);
90
+ }
91
+ // Only an auxiliary row (no message of its own) follows its turn.
92
+ else return promptDropped;
93
+ if (item.kind === 'user') promptDropped = dropped;
94
+ return dropped;
95
+ };
96
+ // An aggregate row some of whose calls survive stays live, with its dropped
97
+ // members marked archived (the restart path archives exactly those calls).
98
+ predicate.markDroppedMembers = (item) => {
99
+ if (item?.kind !== 'tool' || item.aggregate !== true || !Array.isArray(item.toolMembers)) return item;
100
+ if (!item.toolMembers.some((member) => member?.id != null && callIds.has(String(member.id)))) return item;
101
+ return {
102
+ ...item,
103
+ toolMembers: item.toolMembers.map((member) =>
104
+ member?.id != null && callIds.has(String(member.id)) && member.archived !== true
105
+ ? { ...member, archived: true }
106
+ : member
107
+ ),
108
+ };
109
+ };
110
+ return predicate;
111
+ }
112
+
113
+ function classifiedRow(item, archived) {
114
+ if (archived(item)) return archivedItem(item);
115
+ return typeof archived.markDroppedMembers === 'function' ? archived.markDroppedMembers(item) : item;
116
+ }
117
+
118
+ /** Items with the rows before `boundary` (default: all) that `archived`
119
+ * selects marked archived, and the divider inserted at the boundary.
120
+ * Returns the next list and the divider's index. */
121
+ export function archiveItemsForCompaction(
122
+ items,
123
+ divider,
124
+ boundary = null,
125
+ { archived = () => true, archiveTail = false } = {}
126
+ ) {
127
+ const list = Array.isArray(items) ? items : [];
128
+ const at = Number.isInteger(boundary) ? Math.max(0, Math.min(list.length, boundary)) : list.length;
129
+ const next = list.slice(0, at).map((item) => classifiedRow(item, archived));
130
+ next.push(compactionDividerItem(divider));
131
+ // The current turn's rows after the boundary stay in place but are still
132
+ // classified by the same result: what compaction explicitly dropped from
133
+ // this turn (its tool calls) is archived, the rest stays live.
134
+ for (let index = at; index < list.length; index += 1) {
135
+ next.push(archiveTail ? classifiedRow(list[index], archived) : list[index]);
136
+ }
137
+ return { items: next, dividerIndex: at };
138
+ }
139
+
140
+ /** The runtime archive range a resumed session exposes to paged views
141
+ * (`transcriptArchive` state), or null. */
142
+ export function resumedTranscriptArchive(range) {
143
+ const after = Number(range?.after);
144
+ const before = Number(range?.before);
145
+ if (!Number.isInteger(after) || !Number.isInteger(before) || before - 1 <= after) return null;
146
+ return { after, before };
147
+ }
@@ -61,12 +61,18 @@ export function createContextState({
61
61
  effortOptions: runtime.effortOptions,
62
62
  fast: runtime.fast,
63
63
  fastCapable: runtime.fastCapable,
64
+ autoEffort: runtime.autoEffort !== false,
64
65
  modelParameters: runtime.modelParameters || {},
65
66
  contextPercent: runtime.contextPercent,
66
67
  contextWindow: runtime.contextWindow,
67
68
  rawContextWindow: runtime.rawContextWindow,
68
69
  effectiveContextWindowPercent: runtime.effectiveContextWindowPercent,
69
70
  cwd: runtime.cwd || process.cwd(),
71
+ // Lead workspace record (worktrees/contract.mjs SessionWorkspace): the
72
+ // registered Project, the execution root, the worktree binding and its
73
+ // revision. Null while the session has never left its project folder.
74
+ // Identity-stable until a transition commits.
75
+ workspace: runtime.getWorkspace?.() ?? null,
70
76
  systemShell: runtime.systemShell || { source: 'auto', command: '', effective: '' },
71
77
  webSearchRoute: runtime.getWebSearchRoute?.() || runtime.webSearchRoute || null,
72
78
  autoClear: autoClearState(),
@@ -12,6 +12,9 @@ export function createInitialSessionState({ runtime, runtimeCwd, baseRouteState
12
12
  transcriptViewRevision: 0,
13
13
  transcriptHistoryBefore: false,
14
14
  transcriptHistoryAfter: false,
15
+ // Compaction-archive segments a paged view may load above the rows held
16
+ // here ({ after, before } segment numbers), set on resume.
17
+ transcriptArchive: null,
15
18
  structureRevision: 0,
16
19
  streamingTail: null,
17
20
  toasts: [],
@@ -52,7 +52,8 @@ export function latestQuotaFailure(items) {
52
52
  const transcript = Array.isArray(items) ? items : [];
53
53
  for (let index = transcript.length - 1; index >= 0; index -= 1) {
54
54
  const item = transcript[index];
55
- if (item?.kind === 'user') return null;
55
+ // A turn a compaction archived is no longer the model's to resume.
56
+ if (item?.kind === 'user' || item?.archived === true) return null;
56
57
  if (item?.kind !== 'turndone') continue;
57
58
  const quota = item.quota;
58
59
  if (String(item.status || '').toLowerCase() !== 'failed' || !quota || typeof quota.provider !== 'string') {
@@ -4,6 +4,7 @@
4
4
  * a turn is running). Each shows a command status while it holds the lock.
5
5
  */
6
6
  import { compactEventDetail } from '../labels.mjs';
7
+ import { archiveItemsForCompaction, compactionArchivedRows } from '../compaction-view.mjs';
7
8
 
8
9
  export function createSessionCommandsApi(bag) {
9
10
  const {
@@ -108,8 +109,15 @@ export function createSessionCommandsApi(bag) {
108
109
  invalidateExact: compactChanged,
109
110
  });
110
111
  set({ ...routeState(), stats: { ...getState().stats } });
111
- if (compactChanged) set({ items: replaceItems([]) });
112
- pushItem(compactDoneItem(result, startedAt));
112
+ if (compactChanged) {
113
+ // The compacted rows stay visible (archived) above the divider.
114
+ const { items } = archiveItemsForCompaction(getState().items, compactDoneItem(result, startedAt), null, {
115
+ archived: compactionArchivedRows(result),
116
+ });
117
+ set({ items: replaceItems(items, { preserveSpill: true }) });
118
+ } else {
119
+ pushItem(compactDoneItem(result, startedAt));
120
+ }
113
121
  return result;
114
122
  });
115
123
  } finally {
@@ -63,6 +63,10 @@ export function createSessionControlsApi(bag) {
63
63
  taskControl: (args = {}) => {
64
64
  return runtime.taskControl?.(args) ?? null;
65
65
  },
66
+ // Structured stop of one background shell job: { stopped, error? }.
67
+ stopShellJob: async (taskId) => {
68
+ return (await runtime.stopShellJob?.(taskId)) ?? { stopped: false, error: 'shell job control is unavailable' };
69
+ },
66
70
  goalControl: async (args = {}) => {
67
71
  const result = await runtime.goalControl?.(args);
68
72
  if (['pause', 'stop'].includes(result?.action)) {
@@ -14,15 +14,19 @@ export function createSessionExtensionsApi(bag) {
14
14
  resetStatsAndSyncContext,
15
15
  routeState,
16
16
  });
17
- // One locked runtime mutation, then republish and announce it.
17
+ // One locked runtime mutation, then republish and announce it. A refused
18
+ // change throws so a caller never reports a skipped write as saved.
18
19
  const lockedChange = (call, notice) =>
19
- withCommandLock(async (...args) => {
20
- const result = await call(...args);
21
- refreshRouteStats();
22
- const [text, level = 'info'] = notice(result, ...args);
23
- pushNotice(text, level);
24
- return result;
25
- });
20
+ withCommandLock(
21
+ async (...args) => {
22
+ const result = await call(...args);
23
+ refreshRouteStats();
24
+ const [text, level = 'info'] = notice(result, ...args);
25
+ pushNotice(text, level);
26
+ return result;
27
+ },
28
+ { busyError: 'Another command is still running; try again when it finishes.' }
29
+ );
26
30
  const pluginLabel = (result, plugin) => result?.plugin?.title || result?.plugin?.name || plugin?.name || plugin;
27
31
 
28
32
  return {
@@ -13,7 +13,8 @@ export function createRewindAction(bag) {
13
13
  if (getState().busy || getState().commandBusy) return null;
14
14
  const items = getState().items || [];
15
15
  const index = items.findIndex((item) => item?.kind === 'user' && String(item?.id) === target);
16
- if (index < 0) return null;
16
+ // An archived prompt left the model context: there is nothing to rewind.
17
+ if (index < 0 || items[index]?.archived === true) return null;
17
18
  const text = String(items[index]?.text || '').trim();
18
19
  if (!text) return null;
19
20
  set({ commandBusy: true });
@@ -61,6 +61,23 @@ export function createSessionIntegrationsApi(bag, { oauthFlows }) {
61
61
  checkpointId
62
62
  );
63
63
  },
64
+ getTurnChanges: async (options = {}) => {
65
+ return await requireRuntimeMethod('getTurnChanges', 'Turn changes are unavailable')(options);
66
+ },
67
+ revertTurnChanges: async (checkpointId, file, revision) => {
68
+ return await requireRuntimeMethod('revertTurnChanges', 'Turn changes undo is unavailable')(
69
+ checkpointId,
70
+ file,
71
+ revision
72
+ );
73
+ },
74
+ reapplyTurnChanges: async (checkpointId, file, revision) => {
75
+ return await requireRuntimeMethod('reapplyTurnChanges', 'Turn changes reapply is unavailable')(
76
+ checkpointId,
77
+ file,
78
+ revision
79
+ );
80
+ },
64
81
  getOnboardingStatus: () => {
65
82
  return runtime.getOnboardingStatus?.() || { completed: true, workflowRoutes: {} };
66
83
  },
@@ -68,6 +85,7 @@ export function createSessionIntegrationsApi(bag, { oauthFlows }) {
68
85
  // Completed-marking only; no route/agent/provider writes.
69
86
  return runtime.skipOnboarding?.() || null;
70
87
  },
88
+ saveStarterGuideState: (state) => runtime.saveStarterGuideState?.(state) || null,
71
89
  completeOnboarding: withCommandLock(async (payload = {}) => {
72
90
  const result = await runtime.completeOnboarding?.(payload);
73
91
  refreshRouteStats();
@@ -35,7 +35,12 @@ export function createDisposeAction(bag, { oauthFlows }) {
35
35
  try {
36
36
  clearInterval(lifecycle.runtimePulseTimer);
37
37
  } catch {}
38
- for (const key of ['unsubscribeRuntimeNotifications', 'unsubscribeAgentStatus', 'unsubscribeRemoteState']) {
38
+ for (const key of [
39
+ 'unsubscribeRuntimeNotifications',
40
+ 'unsubscribeAgentStatus',
41
+ 'unsubscribeRemoteState',
42
+ 'unsubscribeCompletionWakeGate',
43
+ ]) {
39
44
  try {
40
45
  lifecycle[key]?.();
41
46
  } catch {}
@@ -25,6 +25,8 @@ export function createInheritanceActions(bag, { restoreTranscriptItems }) {
25
25
  set({
26
26
  sessionId,
27
27
  items: replaceItems(items),
28
+ // An heir carries the conversation, never the source's archive.
29
+ transcriptArchive: null,
28
30
  toasts: [],
29
31
  queued: [],
30
32
  thinking: null,
@@ -1,6 +1,7 @@
1
1
  // Resume: reopen a stored session, rebuild the visible transcript from its
2
2
  // messages and reconcile live-share and steering at the same boundary.
3
3
  import { primeContextEstimates } from '../../../../runtime/agent/orchestrator/session/context-utils.mjs';
4
+ import { resumedTranscriptArchive } from '../../compaction-view.mjs';
4
5
 
5
6
  export function createResumeAction(bag, { restoreTranscriptItems }) {
6
7
  const {
@@ -52,6 +53,8 @@ export function createResumeAction(bag, { restoreTranscriptItems }) {
52
53
  });
53
54
  set({
54
55
  items: replaceItems(items),
56
+ // Paged views continue into the compaction archive past these rows.
57
+ transcriptArchive: resumedTranscriptArchive(r.transcriptArchive),
55
58
  toasts: [],
56
59
  queued: [],
57
60
  thinking: null,
@@ -9,7 +9,16 @@ import { createApiHelpers } from './shared.mjs';
9
9
  // published before the runtime write settles. What is NOT previewed about the
10
10
  // route (effort options, Fast capability, context window) is provider
11
11
  // metadata the runtime resolves while the preview is already on screen.
12
- const ROUTE_PREVIEW_KEYS = ['provider', 'model', 'effort', 'fast', 'modelParameters', 'contextPercent'];
12
+ const ROUTE_PREVIEW_KEYS = [
13
+ 'provider',
14
+ 'model',
15
+ 'effort',
16
+ 'fast',
17
+ 'autoEffort',
18
+ 'modelParameters',
19
+ 'contextPercent',
20
+ 'contextTokens',
21
+ ];
13
22
 
14
23
  /**
15
24
  * The patch that publishes a requested route before the runtime write settles.
@@ -129,6 +129,8 @@ export function createSessionSettingsApi(bag) {
129
129
  getTidyInstallStatus: () => runtime.getTidyInstallStatus?.(),
130
130
  getDeveloperSettings: () => runtime.getDeveloperSettings?.(),
131
131
  setDeveloperOption: lockedRuntimeCall((id, enabled) => runtime.setDeveloperOption?.(id, enabled)),
132
+ getWorktreeSettings: () => runtime.getWorktreeSettings(),
133
+ setWorktreeSettings: lockedRuntimeCall((patch) => runtime.setWorktreeSettings(patch)),
132
134
  setWebSearchEnabled: lockedRuntimeCall((enabled) => runtime.setWebSearchEnabled?.(enabled)),
133
135
  setInstructionFiles: lockedRuntimeCall((mode) => runtime.setInstructionFiles?.(mode)),
134
136
  setMemoryToolsEnabled: lockedRuntimeCall((enabled) => runtime.setMemoryToolsEnabled?.(enabled)),
@@ -5,15 +5,19 @@
5
5
  */
6
6
  export function createApiHelpers({ getState, set, resetStatsAndSyncContext, routeState, syncContextStats }) {
7
7
  /**
8
- * Wrap a runtime call in the command lock: refused (→ `busyResult`) while
9
- * another command holds commandBusy, otherwise held for the call's duration.
10
- * `onRelease` runs after the lock drops (RPC replies read the published
11
- * snapshot, not the mutable draft, so some callers flush there).
8
+ * Wrap a runtime call in the command lock: refused (→ `busyResult`, or a
9
+ * thrown `busyError` for callers that must not read a refusal as success)
10
+ * while another command holds commandBusy, otherwise held for the call's
11
+ * duration. `onRelease` runs after the lock drops (RPC replies read the
12
+ * published snapshot, not the mutable draft, so some callers flush there).
12
13
  */
13
14
  const withCommandLock =
14
- (run, { busyResult = null, onRelease = null } = {}) =>
15
+ (run, { busyResult = null, busyError = '', onRelease = null } = {}) =>
15
16
  async (...args) => {
16
- if (getState().commandBusy) return busyResult;
17
+ if (getState().commandBusy) {
18
+ if (busyError) throw new Error(busyError);
19
+ return busyResult;
20
+ }
17
21
  set({ commandBusy: true });
18
22
  try {
19
23
  return await run(...args);
@@ -0,0 +1,60 @@
1
+ /**
2
+ * worktree.mjs — the session object's Lead worktree surface. The daemon
3
+ * resolves these advertised actions by name (session-protocol.mjs) and each
4
+ * delegates to the runtime's workspace transition authority
5
+ * (session-runtime/workspace/transitions.mjs), then republishes the route
6
+ * state so the new `workspace` reaches every viewer. Failures are typed
7
+ * WorktreeErrors whose `code` crosses the daemon wire; a runtime without the
8
+ * authority rejects with WORKTREE_NOT_IMPLEMENTED.
9
+ *
10
+ * getBranchMenu() → { workspace, pendingWorktree, branches, occupancy, retainedWorktree }
11
+ * getProjectBranchMenu({ projectPath }) → { workspace, branches, occupancy, retainedWorktree: null }
12
+ * (no session: a New Task draft or the source-control dock)
13
+ * selectBranch({ branch, create?, expectedRevision?, confirmLarge? }) → { revision, workspace }
14
+ * selectProjectBranch({ projectPath, branch, create?, remote?, startPoint? }) → { branch, changed, commit? }
15
+ * selectProjectBranch({ projectPath, detach: commitId }) → { branch: null, changed, commit, detached: true }
16
+ * (no session: the guarded project-folder checkout)
17
+ * mergeProjectBranch({ projectPath, branch, target }) → backend merge result
18
+ * (no session: the source-control dock's merge into the confirmed target)
19
+ * requestWorktree({ name?, confirmLarge?, provision?, prompt?, txId?, expectedRevision? }) → { revision, workspace, pending, warnings? }
20
+ * (provision: a New Task's first send creates and enters it now)
21
+ * mergeWorkspace({ confirmed: true, target, cleanup?, expectedRevision? }) → backend merge result (+ cleanup)
22
+ * removeWorktree({ force?, expectedRevision? }) → { revision, workspace, removed }
23
+ * (expectedRevision: WORKTREE_REVISION_CONFLICT when the session moved on)
24
+ * (WORKTREE_REMOVAL_INCOMPLETE: partly removed; call again to finish)
25
+ * listWorktrees() → every managed worktree with its retention
26
+ * pruneWorktrees() → reconcile report (never a retained worktree)
27
+ */
28
+ import { worktreeError } from '../../../runtime/shared/worktrees/contract.mjs';
29
+
30
+ /** Session actions of the Lead worktree feature (settings live in settings.mjs). */
31
+ export const SESSION_WORKTREE_ACTIONS = Object.freeze([
32
+ 'getBranchMenu',
33
+ 'getProjectBranchMenu',
34
+ 'selectBranch',
35
+ 'selectProjectBranch',
36
+ 'mergeProjectBranch',
37
+ 'requestWorktree',
38
+ 'mergeWorkspace',
39
+ 'removeWorktree',
40
+ 'listWorktrees',
41
+ 'pruneWorktrees',
42
+ ]);
43
+
44
+ export function createSessionWorktreeApi({ runtime, set, routeState } = {}) {
45
+ return Object.fromEntries(
46
+ SESSION_WORKTREE_ACTIONS.map((action) => [
47
+ action,
48
+ async (input = {}) => {
49
+ if (typeof runtime?.[action] !== 'function') {
50
+ throw worktreeError('WORKTREE_NOT_IMPLEMENTED', `${action} is not implemented yet.`);
51
+ }
52
+ const value = await runtime[action](input ?? {});
53
+ if (!action.startsWith('get') && typeof set === 'function' && typeof routeState === 'function') {
54
+ set({ ...routeState() });
55
+ }
56
+ return value;
57
+ },
58
+ ])
59
+ );
60
+ }
@@ -12,6 +12,7 @@ import { createSessionControlsApi } from './session-api/controls.mjs';
12
12
  import { createSessionExtensionsApi } from './session-api/extensions.mjs';
13
13
  import { createSessionCommandsApi } from './session-api/commands.mjs';
14
14
  import { createQuotaResumeApi } from './session-api/quota-resume.mjs';
15
+ import { createSessionWorktreeApi } from './session-api/worktree.mjs';
15
16
 
16
17
  export function createSessionApi(bag) {
17
18
  const api = { ...createSessionApiA(bag), ...createSessionApiB(bag) };
@@ -59,5 +60,6 @@ export function createSessionApiA(bag) {
59
60
  ...createSessionExtensionsApi(bag),
60
61
  ...createSessionCommandsApi(bag),
61
62
  ...createQuotaResumeApi(bag),
63
+ ...createSessionWorktreeApi(bag),
62
64
  };
63
65
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { presentErrorText } from '../../../runtime/shared/err-text.mjs';
7
7
  import { sessionActivityTimestamp } from '../queue-helpers.mjs';
8
+ import { archiveItemsForCompaction, compactionArchivedRows } from '../compaction-view.mjs';
8
9
 
9
10
  // Upper bound on the awaited idle compaction. Without a bound a stalled
10
11
  // compaction wedges autoClearRunning/commandBusy and suppresses input drain.
@@ -52,23 +53,26 @@ export function createAutoClearOps(bag, { reset, kickDrain }) {
52
53
  autoClearState,
53
54
  routeState,
54
55
  syncContextStats,
56
+ replaceItems,
55
57
  } = bag;
56
58
 
57
- // Only an actual compaction resets the visible transcript; an unchanged
58
- // success keeps it. The model transcript always belongs to runtime.compact.
59
+ // Only an actual compaction resets the visible activity; an unchanged
60
+ // success keeps it. The compacted rows stay in view, archived above the
61
+ // divider. The model transcript always belongs to runtime.compact.
59
62
  function applyAutoClearUi(result) {
60
63
  if (result.changed === false) return false;
61
- const compactChanged = result.changed !== false;
62
- if (compactChanged) {
63
- reset.resetStats();
64
- reset.clearUiActivityBeforeContextSync();
65
- }
66
- syncContextStats({ allowEstimated: true, invalidateExact: compactChanged });
64
+ const kept = getState().items;
65
+ reset.resetStats();
66
+ reset.clearUiActivityBeforeContextSync({ keepTranscript: true });
67
+ syncContextStats({ allowEstimated: true, invalidateExact: true });
68
+ const { items } = archiveItemsForCompaction(kept, { id: nextId() }, null, {
69
+ archived: compactionArchivedRows(result),
70
+ });
67
71
  set({
72
+ items: replaceItems(items, { preserveSpill: true }),
68
73
  ...routeState(),
69
74
  stats: { ...getState().stats },
70
75
  });
71
- pushItem({ kind: 'statusdone', id: nextId(), label: 'Auto-clear complete' });
72
76
  return true;
73
77
  }
74
78
 
@@ -129,7 +133,9 @@ export function createAutoClearOps(bag, { reset, kickDrain }) {
129
133
  }
130
134
  }
131
135
 
132
- async function autoClearBeforeSubmit() {
136
+ // `ignorePending`: a completion-wake drain has already dequeued its own
137
+ // batch, so other queued entries must not veto the idle check.
138
+ async function autoClearBeforeSubmit({ ignorePending = false } = {}) {
133
139
  flushDeferredClearedSessionUi();
134
140
  const cfg = autoClearState();
135
141
  const now = Date.now();
@@ -138,7 +144,7 @@ export function createAutoClearOps(bag, { reset, kickDrain }) {
138
144
  const notIdle =
139
145
  !cfg.enabled ||
140
146
  getState().busy ||
141
- pending.length > 0 ||
147
+ (!ignorePending && pending.length > 0) ||
142
148
  flags.autoClearRunning ||
143
149
  flags.autoClearInFlight ||
144
150
  idleMs < cfg.idleMs;
@@ -188,5 +194,18 @@ export function createAutoClearOps(bag, { reset, kickDrain }) {
188
194
  }
189
195
  }
190
196
 
191
- return { autoClearBeforeSubmit, performAutoClear, flushDeferredClearedSessionUi };
197
+ // Same eligibility as a user submit, run before a completion wake turn so a
198
+ // cold cache after a long idle is compacted first. Never throws: a skipped or
199
+ // failed auto-clear must still let the wake run. A viewer mirroring another
200
+ // surface's owner leaves compaction to that owner.
201
+ async function autoClearBeforeWake() {
202
+ if (bag.liveShareMirroring?.() === true) return false;
203
+ try {
204
+ return await autoClearBeforeSubmit({ ignorePending: true });
205
+ } catch {
206
+ return false;
207
+ }
208
+ }
209
+
210
+ return { autoClearBeforeSubmit, autoClearBeforeWake, performAutoClear, flushDeferredClearedSessionUi };
192
211
  }
@@ -114,7 +114,12 @@ function turnOptionsForBatch(batch, steering) {
114
114
  };
115
115
  }
116
116
 
117
- export function createDrainLoop(bag, { queue, steering, submissions, flushDeferredClearedSessionUi }) {
117
+ const COMPLETION_WAKE_MODES = new Set(['task-notification', 'pending-resume']);
118
+
119
+ export function createDrainLoop(
120
+ bag,
121
+ { queue, steering, submissions, flushDeferredClearedSessionUi, autoClearBeforeWake }
122
+ ) {
118
123
  const {
119
124
  tuiDebug,
120
125
  flags,
@@ -248,6 +253,15 @@ export function createDrainLoop(bag, { queue, steering, submissions, flushDeferr
248
253
  if (isGoalQueuedEntry(batch[0]) && bag.shouldRunGoalContinuation?.(batch[0]) !== true) {
249
254
  continue;
250
255
  }
256
+ // A completion wake calls the model like a user turn, so a cold cache
257
+ // after a long idle is compacted first; the wake runs regardless.
258
+ if (autoClearBeforeWake && batch.every((entry) => COMPLETION_WAKE_MODES.has(entry.mode))) {
259
+ await autoClearBeforeWake();
260
+ if (flags.drainEpoch !== drainEpoch || admission() !== 'open') {
261
+ queue.requeueEntriesFront(batch);
262
+ return;
263
+ }
264
+ }
251
265
  tuiDebug(`busy-queue drain batch=${batch.length} remaining=${pending.length}`);
252
266
  const merged = mergePromptContents(batch);
253
267
  rewindFailedTurnRows(batch);
@@ -30,12 +30,13 @@ export function createSessionResetOps(bag) {
30
30
  return stats;
31
31
  };
32
32
 
33
- const clearUiActivityBeforeContextSync = () => {
33
+ // `keepTranscript`: a compaction keeps the visible rows (its caller archives
34
+ // them); a clear or session reset wipes them and the archive range with them.
35
+ const clearUiActivityBeforeContextSync = ({ keepTranscript = false } = {}) => {
34
36
  clearToastTimers();
35
37
  resetAllStreamingMarkdownStablePrefixes();
36
- const items = replaceItems([]);
37
38
  set({
38
- items,
39
+ ...(keepTranscript ? {} : { items: replaceItems([]), transcriptArchive: null }),
39
40
  toasts: [],
40
41
  queued: [],
41
42
  thinking: null,
@@ -23,6 +23,7 @@ export function createSessionFlow(bag) {
23
23
  steering,
24
24
  submissions,
25
25
  flushDeferredClearedSessionUi: autoClear.flushDeferredClearedSessionUi,
26
+ autoClearBeforeWake: autoClear.autoClearBeforeWake,
26
27
  });
27
28
 
28
29
  return {
@@ -41,6 +42,7 @@ export function createSessionFlow(bag) {
41
42
  drainPendingSteering: steering.drainPendingSteering,
42
43
  restoreLeadSteeringFromDisk: steering.restoreLeadSteeringFromDisk,
43
44
  autoClearBeforeSubmit: autoClear.autoClearBeforeSubmit,
45
+ autoClearBeforeWake: autoClear.autoClearBeforeWake,
44
46
  performAutoClear: autoClear.performAutoClear,
45
47
  restoreQueued: queue.restoreQueued,
46
48
  prioritizeQueued: queue.prioritizeQueued,