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
@@ -60,6 +60,33 @@ function buildShellEnvironmentContext(opts, ownerIsAgent, toolsForRouting) {
60
60
  .join('\n');
61
61
  }
62
62
 
63
+ /**
64
+ * Workspace-scoped prompt facts for an existing session re-rooted at `cwd`
65
+ * (a Lead workspace change or a worker following its Lead): the shell/git/
66
+ * listing environment and the instruction files of the new root, derived
67
+ * exactly as at creation. `undefined` means "keep what the session has": a
68
+ * tool-free role carries neither, a profile skipping instruction files keeps
69
+ * none.
70
+ * @returns {{ environmentContext: string|undefined, instructionFilesContext: string|undefined }}
71
+ */
72
+ export function buildWorkspacePromptContext(session, cwd) {
73
+ const ownerIsAgent = session?.owner === 'agent';
74
+ const toolFreeRole =
75
+ ownerIsAgent && Array.isArray(session?.schemaAllowedTools) && session.schemaAllowedTools.length === 0;
76
+ if (toolFreeRole) return { environmentContext: undefined, instructionFilesContext: undefined };
77
+ const tools = [
78
+ ...(Array.isArray(session?.tools) ? session.tools : []),
79
+ ...(Array.isArray(session?.deferredToolCatalog) ? session.deferredToolCatalog : []),
80
+ ];
81
+ return {
82
+ environmentContext: buildShellEnvironmentContext({ cwd }, ownerIsAgent, tools),
83
+ instructionFilesContext:
84
+ session?.skipInstructionFiles === true
85
+ ? undefined
86
+ : loadInstructionFilesContext({ mode: configuredInstructionFilesMode(), cwd }),
87
+ };
88
+ }
89
+
63
90
  // BP1/BP2/BP3 rule blocks plus the volatile environment, composed into the
64
91
  // four system blocks; environmentTailContext is the persisted env tail.
65
92
  export function composeSessionSystem(opts, { profile, providerName, modelName, surface }) {
@@ -92,7 +92,18 @@ export function sessionOriginFields(opts, { profile, presetObj, providerCacheOpt
92
92
 
93
93
  // The persisted session record.
94
94
  export function buildSessionRecord({ opts, route, presetObj, surface, prompt, messages, contextMeta, origin }) {
95
- const { providerName, modelName, toolPreset, effort, fast, modelParameters, contextPercent, id } = route;
95
+ const {
96
+ providerName,
97
+ modelName,
98
+ toolPreset,
99
+ effort,
100
+ fast,
101
+ autoEffort,
102
+ modelParameters,
103
+ contextPercent,
104
+ contextTokens,
105
+ id,
106
+ } = route;
96
107
  return {
97
108
  id,
98
109
  codexWireSessionId: providerName === 'openai-oauth' ? mintUuidV7() : null,
@@ -115,8 +126,11 @@ export function buildSessionRecord({ opts, route, presetObj, surface, prompt, me
115
126
  presetName: presetObj?.name || null,
116
127
  effort,
117
128
  fast,
129
+ // Absent means Auto effort is on; only the off state is persisted.
130
+ ...(autoEffort === false ? { autoEffort: false } : {}),
118
131
  modelParameters,
119
132
  contextPercent,
133
+ ...(contextTokens ? { contextTokens } : {}),
120
134
  selectedContextWindow: opts.selectedContextWindow || null,
121
135
  agent: opts.agent,
122
136
  owner: opts.owner || 'user',
@@ -144,6 +158,7 @@ export function buildSessionRecord({ opts, route, presetObj, surface, prompt, me
144
158
  lastHeartbeatAt: null,
145
159
  totalInputTokens: 0,
146
160
  totalOutputTokens: 0,
161
+ totalToolCalls: 0,
147
162
  // Refreshed on each completed ask() — surfaced by agent type=list for
148
163
  // debugging + consumed by store.mjs's idle-sweep to reclaim stalled
149
164
  // agent sessions past RUNNING_STALL_MS.
@@ -35,6 +35,7 @@ function standaloneStatusRouteInfo(session) {
35
35
  modelDisplay: session.modelDisplay || session.displayName || session.model,
36
36
  effort: session.effort || '',
37
37
  fast: session.fast === true,
38
+ ...(session.autoEffort === false ? { autoEffort: false } : {}),
38
39
  modelParameters: session.modelParameters || {},
39
40
  contextWindow: session.contextWindow || null,
40
41
  rawContextWindow: session.rawContextWindow || session.contextWindow || null,
@@ -390,6 +390,7 @@ export function applyAskTerminalUsageTotals(session, result, options = {}) {
390
390
  accumulateSessionUsage(session, {
391
391
  deltaInput: result.usage.inputTokens || 0,
392
392
  deltaOutput: result.usage.outputTokens || 0,
393
+ deltaToolCalls: result.toolCallsTotal || 0,
393
394
  ...reasoningUsage(result.usage),
394
395
  deltaCachedRead: result.usage.cachedTokens || 0,
395
396
  deltaCacheWrite: result.usage.cacheWriteTokens || 0,
@@ -465,6 +466,7 @@ function accumulateSessionUsage(session, delta) {
465
466
  : uncachedInputTokensForProvider(session.provider, deltaInput, deltaCachedRead, deltaCacheWrite);
466
467
  session.totalInputTokens = (session.totalInputTokens || 0) + (deltaInput || 0);
467
468
  session.totalOutputTokens = (session.totalOutputTokens || 0) + (deltaOutput || 0);
469
+ session.totalToolCalls = (session.totalToolCalls || 0) + (delta.deltaToolCalls || 0);
468
470
  session.tokensCumulative = (session.tokensCumulative || 0) + (deltaInput || 0) + (deltaOutput || 0);
469
471
  session.totalCachedReadTokens = (session.totalCachedReadTokens || 0) + (deltaCachedRead || 0);
470
472
  session.totalCacheWriteTokens = (session.totalCacheWriteTokens || 0) + (deltaCacheWrite || 0);
@@ -1,9 +1,5 @@
1
1
  // src/runtime/agent/orchestrator/session/manager.mjs
2
- // Thin facade over the split session-manager modules in ./manager/. This file
3
- // re-exports the EXACT public surface the previous monolith exposed so every
4
- // importer (loop.mjs, loop/tool-exec.mjs, agent-dispatch.mjs, session-builder,
5
- // abort-lookup, statusline-agents, headless-role, session-runtime, and the
6
- // smoke scripts) keeps resolving its symbols through manager.mjs unchanged.
2
+ // Thin facade over the split session-manager modules in ./manager/.
7
3
  //
8
4
  // Module map:
9
5
  // manager/runtime-liveness.mjs — in-memory stage/heartbeat + accessors
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Stable per-message identity: `meta.transcript.messageId`.
3
+ *
4
+ * Prompts and replies get theirs at creation (shared/transcript-metadata.mjs).
5
+ * Every other message — tool calls and results, injected rows, legacy
6
+ * transcripts — is stamped before a compaction runs or a stored session is
7
+ * resumed (stampMessageIds), and every compaction rewrite carries the id of
8
+ * the message it rewrites to its copy (spread copies keep `meta`;
9
+ * constructed copies use carryMessageId). A compaction's dropped messages
10
+ * are then exactly the ids its result no longer holds.
11
+ *
12
+ * A message whose `meta` is a string marker ('skill', 'hook': injected
13
+ * model context that is never displayed) cannot carry an id without changing
14
+ * what that marker means; it stays unstamped and has no display identity.
15
+ *
16
+ * Leaf module: no store, no config.
17
+ */
18
+ import { createHash } from 'node:crypto';
19
+
20
+ /** The message's stable id, or ''. */
21
+ export function messageIdOf(message) {
22
+ const meta = message?.meta;
23
+ if (!meta || typeof meta !== 'object') return '';
24
+ const id = meta.transcript?.messageId;
25
+ return typeof id === 'string' ? id : '';
26
+ }
27
+
28
+ /** Whether `meta` can hold a transcript id (absent or a plain object). */
29
+ export function canCarryMessageId(message) {
30
+ if (!message || typeof message !== 'object') return false;
31
+ const meta = message.meta;
32
+ return meta === undefined || meta === null || (typeof meta === 'object' && !Array.isArray(meta));
33
+ }
34
+
35
+ function withMessageId(meta, messageId) {
36
+ const base = meta && typeof meta === 'object' ? meta : {};
37
+ const transcript = base.transcript && typeof base.transcript === 'object' ? base.transcript : {};
38
+ return { ...base, transcript: { ...transcript, messageId } };
39
+ }
40
+
41
+ // A deterministic seed: the same unstamped transcript gets the same ids on
42
+ // every stamping (a legacy session reopened twice before its stamps are
43
+ // saved keeps them; two compactions of one transcript agree). The content
44
+ // part is bounded — its length and edges, media by kind and size — so
45
+ // stamping never serializes whole images or documents. The position keeps
46
+ // equal messages apart.
47
+ const SEED_EDGE_CHARS = 512;
48
+ function boundedText(value) {
49
+ const text = String(value);
50
+ if (text.length <= SEED_EDGE_CHARS * 2) return text;
51
+ return `${text.slice(0, SEED_EDGE_CHARS)}\u0000${text.length}\u0000${text.slice(-SEED_EDGE_CHARS)}`;
52
+ }
53
+ function boundedContent(content) {
54
+ if (typeof content === 'string') return boundedText(content);
55
+ if (!Array.isArray(content)) return content == null ? '' : typeof content;
56
+ return content
57
+ .map((part) => {
58
+ if (!part || typeof part !== 'object') return String(part);
59
+ if (typeof part.text === 'string') return `t:${boundedText(part.text)}`;
60
+ const data = part.data ?? part.source?.data ?? part.image_url?.url ?? part.url ?? part.ref ?? '';
61
+ return `${part.type || 'part'}:${typeof data === 'string' ? data.length : 0}`;
62
+ })
63
+ .join('\u0001');
64
+ }
65
+ function seededMessageId(sessionId, index, message) {
66
+ const calls = Array.isArray(message.toolCalls) ? message.toolCalls : message.tool_calls;
67
+ const callIds = Array.isArray(calls) ? calls.map((call) => call?.id || '').join(',') : '';
68
+ const seed = [
69
+ sessionId,
70
+ index,
71
+ message.role,
72
+ message.toolCallId || message.tool_call_id || '',
73
+ callIds,
74
+ boundedContent(message.content),
75
+ ].join('\u0002');
76
+ return `msg_${createHash('sha1').update(seed).digest('hex').slice(0, 32)}`;
77
+ }
78
+
79
+ /** Stamp a stable id on every message that can carry one and has none, in
80
+ * place. Returns how many were stamped (the caller persists them). */
81
+ export function stampMessageIds(messages, { sessionId = '' } = {}) {
82
+ if (!Array.isArray(messages)) return 0;
83
+ let stamped = 0;
84
+ const seen = new Set();
85
+ messages.forEach((message, index) => {
86
+ const existing = messageIdOf(message);
87
+ if (existing) {
88
+ seen.add(existing);
89
+ return;
90
+ }
91
+ if (!canCarryMessageId(message)) return;
92
+ let id = seededMessageId(String(sessionId || ''), index, message);
93
+ // Never two messages with one id within a transcript.
94
+ for (let salt = 1; seen.has(id); salt += 1) id = seededMessageId(`${sessionId}#${salt}`, index, message);
95
+ seen.add(id);
96
+ message.meta = withMessageId(message.meta, id);
97
+ stamped += 1;
98
+ });
99
+ return stamped;
100
+ }
101
+
102
+ /** JSON.stringify replacer that leaves stable ids out: hashes and archives
103
+ * the compactor derives from message JSON describe the conversation, not
104
+ * its display bookkeeping (a meta that only held the id is left out too). */
105
+ export function withoutMessageIdsReplacer(key, value) {
106
+ if (key !== 'meta' || !value || typeof value !== 'object' || typeof value.transcript?.messageId !== 'string') {
107
+ return value;
108
+ }
109
+ const { messageId: _id, ...transcript } = value.transcript;
110
+ const meta = { ...value };
111
+ if (Object.keys(transcript).length) meta.transcript = transcript;
112
+ else delete meta.transcript;
113
+ return Object.keys(meta).length ? meta : undefined;
114
+ }
115
+
116
+ /** `copy` carrying the stable id of `original` (a compaction rewrite that
117
+ * builds a new message instead of spreading the original). Only the id
118
+ * travels; the copy's own meta is kept. */
119
+ export function carryMessageId(copy, original) {
120
+ const id = messageIdOf(original);
121
+ if (!id || !canCarryMessageId(copy) || messageIdOf(copy) === id) return copy;
122
+ return { ...copy, meta: withMessageId(copy.meta, id) };
123
+ }
@@ -0,0 +1,29 @@
1
+ // Tests that compare a transcript before and after a compaction: compaction
2
+ // stamps stable ids (message-ids.mjs) on messages that had none, which is
3
+ // bookkeeping, not a change of the conversation.
4
+
5
+ /** `messages` without the stable ids stamped on them (and without the meta a
6
+ * stamp alone created). */
7
+ export function withoutStampedIds(messages) {
8
+ return messages.map((message) => {
9
+ const transcript = message?.meta?.transcript;
10
+ if (!transcript?.messageId) return message;
11
+ const { messageId: _id, ...rest } = transcript;
12
+ const meta = { ...message.meta };
13
+ if (Object.keys(rest).length) meta.transcript = rest;
14
+ else delete meta.transcript;
15
+ const out = { ...message };
16
+ if (Object.keys(meta).length) out.meta = meta;
17
+ else delete out.meta;
18
+ return out;
19
+ });
20
+ }
21
+
22
+ /** Whether every message that can carry an id has one. */
23
+ export function everyMessageStamped(messages) {
24
+ return messages.every(
25
+ (message) =>
26
+ (message?.meta !== undefined && (typeof message.meta !== 'object' || message.meta === null)) ||
27
+ typeof message?.meta?.transcript?.messageId === 'string'
28
+ );
29
+ }
@@ -29,20 +29,14 @@ import { traceAgentCompact, messagePrefixHash } from '../agent-trace.mjs';
29
29
  import { invalidateProviderRequestToolsScope } from '../runtime-core/provider-request-tools.mjs';
30
30
  import { bumpUsageMetricsEpoch } from './manager/usage-metrics.mjs';
31
31
  import { resetReadStateAfterCompaction } from './read-dedup.mjs';
32
+ import { scheduleCompactionArchive, stampCompactionMessageIds } from './compaction-archive-capture.mjs';
33
+ import { writeStderrBestEffort } from '../../../shared/best-effort-stderr.mjs';
32
34
  import {
33
35
  acknowledgePendingGoalReminder,
34
36
  markPendingGoalReminder,
35
37
  snapshotPendingGoalReminder,
36
38
  } from '../runtime-core/goal-reminder.mjs';
37
39
 
38
- function writeStderr(line) {
39
- try {
40
- process.stderr.write(line);
41
- } catch {
42
- /* best-effort */
43
- }
44
- }
45
-
46
40
  async function setStage(opts, stage) {
47
41
  try {
48
42
  await opts.onStageChange?.(stage);
@@ -126,20 +120,23 @@ function preSendCompactDecision(state, compactPolicy) {
126
120
  activeTurn: true,
127
121
  }).reducesTokens;
128
122
  }
123
+ let compactTrigger = 'auto';
124
+ if (reactivePending) compactTrigger = 'reactive';
125
+ else if (cacheExpired) compactTrigger = 'cache_expired';
129
126
  return {
130
127
  messageTokensEst,
131
128
  pressureTokens,
132
129
  shouldCompact,
133
130
  requireReduction,
134
- compactTrigger: reactivePending ? 'reactive' : cacheExpired ? 'cache_expired' : 'auto',
131
+ compactTrigger,
135
132
  compactBudgetTokens,
136
133
  };
137
134
  }
138
135
 
139
- // Gauge sync (user: 컴팩트될 때 컨텍스트 표기량이 달랐다). The host refreshes
140
- // its context readout on provider usage deltas and turn end, so a tool batch
141
- // that pushed the transcript over the trigger fired compaction while the
142
- // screen still showed headroom. Publish the decision's OWN numerator here —
136
+ // Gauge sync. The host refreshes its context readout only on provider usage
137
+ // deltas and turn end, so a tool batch that pushed the transcript over the
138
+ // trigger would compact while the screen still showed headroom. Publish the
139
+ // decision's OWN numerator here —
143
140
  // before compaction mutates the transcript — so the readout and the trigger
144
141
  // describe one moment.
145
142
  function publishContextPressure(opts, sessionId, compactPolicy, { pressureTokens, shouldCompact }) {
@@ -158,15 +155,14 @@ function publishContextPressure(opts, sessionId, compactPolicy, { pressureTokens
158
155
  }
159
156
 
160
157
  // Pre/PostCompact bridge to the standard hook bus: a session-property hook
161
- // (manager/loop have no bus access), { trigger } normalized to
162
- // 'auto'|'manual'. Best-effort — a hook must never break compaction or the
163
- // loop.
158
+ // (manager/loop have no bus access); a pre-send pass is always trigger
159
+ // 'auto'. Best-effort — a hook must never break compaction or the loop.
164
160
  async function runCompactHook(ctx, name) {
165
161
  const { opts, sessionRef, sessionId, cwd } = ctx.state;
166
162
  const hook = typeof opts[name] === 'function' ? opts[name] : sessionRef?.[name];
167
163
  if (typeof hook !== 'function') return;
168
164
  try {
169
- await hook({ sessionId, cwd, trigger: ctx.decision.compactTrigger === 'manual' ? 'manual' : 'auto' });
165
+ await hook({ sessionId, cwd, trigger: 'auto' });
170
166
  } catch {
171
167
  /* best-effort */
172
168
  }
@@ -342,7 +338,7 @@ function throwCompactFailure(ctx, run, compactErr) {
342
338
  const { sessionRef, sessionId, signal, model } = state;
343
339
  acknowledgeGoalReminder(sessionRef, run);
344
340
  if (isCompactAbort(signal, compactErr)) {
345
- writeStderr(
341
+ writeStderrBestEffort(
346
342
  `[loop] pre-send compact cancelled (sess=${sessionId || 'unknown'}): ${compactErr?.message || compactErr}\n`
347
343
  );
348
344
  throw compactErr;
@@ -381,8 +377,16 @@ function throwCompactFailure(ctx, run, compactErr) {
381
377
  function adoptCompactedTranscript(ctx, run) {
382
378
  const { state } = ctx;
383
379
  const { messages, sessionRef, sessionId, opts } = state;
380
+ const replaced = messages.slice();
384
381
  messages.length = 0;
385
382
  messages.push(...run.compacted);
383
+ // Display-only archive of the dropped conversation; the projection and the
384
+ // write run off this synchronous adoption.
385
+ const archived = sessionRef
386
+ ? scheduleCompactionArchive({ session: sessionRef, sessionId, before: replaced, after: messages })
387
+ : null;
388
+ ctx.droppedMessages = archived?.dropped ?? 0;
389
+ ctx.droppedIdentities = archived?.identities ?? null;
386
390
  acknowledgeGoalReminder(sessionRef, run);
387
391
  resetReadStateAfterCompaction(sessionId);
388
392
  // Cross-turn receipts reference tool results that compaction just removed
@@ -451,6 +455,8 @@ function reportCompactOutcome(ctx, run) {
451
455
  status: changed ? 'compacted' : 'no_change',
452
456
  afterTokens,
453
457
  changed,
458
+ droppedMessages: ctx.droppedMessages || 0,
459
+ droppedIdentities: ctx.droppedIdentities || null,
454
460
  freshContext: freshContextResult?.freshContext === true,
455
461
  handoffSource: freshContextResult?.handoffSource || null,
456
462
  durationMs,
@@ -460,6 +466,10 @@ function reportCompactOutcome(ctx, run) {
460
466
  async function compactBeforeSend(ctx) {
461
467
  const { state, decision } = ctx;
462
468
  const { messages, sessionRef, sessionId, opts } = state;
469
+ // Every message enters the compaction with a stable id (legacy ones are
470
+ // stamped now; the turn's saves persist them): the result's rewrites carry
471
+ // it, so what was dropped is exactly the ids the result no longer holds.
472
+ stampCompactionMessageIds(messages, sessionId || sessionRef?.id || '');
463
473
  // Snapshot BEFORE mutating the live array below, so compact_meta reports
464
474
  // real savings and the true input prefix rather than the replacement
465
475
  // transcript.
@@ -489,7 +499,7 @@ async function compactBeforeSend(ctx) {
489
499
  await compactTranscript(ctx, run);
490
500
  } catch (freshErr) {
491
501
  run.freshContextError = freshErr;
492
- writeStderr(
502
+ writeStderrBestEffort(
493
503
  `[loop] fresh-context compact failed (sess=${sessionId || 'unknown'}): ${freshErr?.message || freshErr}\n`
494
504
  );
495
505
  throw freshErr;
@@ -1,5 +1,4 @@
1
1
  // Thin re-export facade — re-exports all cache symbols from their split modules.
2
- // Existing import paths keep working unchanged.
3
2
  export * from './cache/read-cache.mjs';
4
3
  export * from './cache/scoped-cache.mjs';
5
4
  export * from './cache/prefetch-cache.mjs';
@@ -31,6 +31,7 @@ import { _uncacheSessionSummary, _queueSessionSummaryRemoval } from './summary-c
31
31
  import { _savePending, _clearDebounce, _releasePendingSlot } from './pending-saves.mjs';
32
32
  import { _runtimeLivenessVeto, _heartbeatLivenessVeto, _deleteHeartbeatUnlessNewer } from './liveness-veto.mjs';
33
33
  import { _runSessionPurgeHooks } from './session-observers.mjs';
34
+ import { deleteCompactionArchive } from '../compaction-archive.mjs';
34
35
 
35
36
  /** Strictly enumerate child-agent session files linked to one visible parent.
36
37
  * Used only by explicit parent deletion; ordinary close/context switches keep
@@ -130,6 +131,8 @@ export function deleteSession(id, options = {}) {
130
131
  // Preserve a sidecar published strictly after the sweep's scan snapshot.
131
132
  _deleteHeartbeatUnlessNewer(id, options);
132
133
  deleteSessionPresence(id);
134
+ // The display-only compaction archive has no meaning without its session.
135
+ deleteCompactionArchive(getStoreDir(), id);
133
136
  _clearLiveSession(id);
134
137
  // The file is gone: every marker of the retired incarnation goes with it
135
138
  // (save error + failed-snapshot evidence, split-brain drop flag, lifecycle
@@ -6,6 +6,7 @@ import { join } from 'node:path';
6
6
  import { probePath, PROBE_PRESENT, PROBE_ABSENT } from '../fs-probe.mjs';
7
7
  import { _queueSummaryIndexPrune } from '../summary-cache.mjs';
8
8
  import { deleteSession } from '../session-delete.mjs';
9
+ import { COMPACTION_ARCHIVE_SUFFIX } from '../../compaction-archive.mjs';
9
10
 
10
11
  /** Retention cap: prune resumable open (non-tombstone) sessions newest-first
11
12
  * — keep the most recent openMaxCount, prune anything older than openMaxAgeMs
@@ -47,18 +48,24 @@ export function pruneOpenCandidates({ plan, now, tally }) {
47
48
  }
48
49
  }
49
50
 
50
- /** Orphan .hb/.own reap: a heartbeat/presence sidecar whose .json no longer
51
+ /** Orphan .hb/.own/compaction-archive reap: a sidecar whose .json no longer
51
52
  * exists is dead weight once it is also stale (older than maxAge) — the
52
53
  * session JSON was swept/closed but the sidecar lingered (crashed owner or
53
54
  * pre-fix orphan). The staleness gate avoids nuking the sidecar of a session
54
55
  * mid-create whose .json write has not landed yet. Yields between sidecars
55
56
  * like the row loop. */
56
57
  export function* reapOrphanSidecars({ plan, dir, now, tally }) {
58
+ const recordOf = (sidecar) =>
59
+ sidecar.endsWith(COMPACTION_ARCHIVE_SUFFIX)
60
+ ? `${sidecar.slice(0, -COMPACTION_ARCHIVE_SUFFIX.length)}.json`
61
+ : sidecar.replace(/\.(hb|own)$/, '.json');
57
62
  try {
58
- for (const h of readdirSync(dir).filter((f) => f.endsWith('.hb') || f.endsWith('.own'))) {
63
+ for (const h of readdirSync(dir).filter(
64
+ (f) => f.endsWith('.hb') || f.endsWith('.own') || f.endsWith(COMPACTION_ARCHIVE_SUFFIX)
65
+ )) {
59
66
  yield undefined;
60
67
  // Only a PROVEN-absent session file makes its sidecar an orphan.
61
- if (probePath(join(dir, h.replace(/\.(hb|own)$/, '.json'))).state !== PROBE_ABSENT) continue;
68
+ if (probePath(join(dir, recordOf(h))).state !== PROBE_ABSENT) continue;
62
69
  const sidecarProbe = probePath(join(dir, h));
63
70
  if (sidecarProbe.state !== PROBE_PRESENT) continue;
64
71
  if (now - sidecarProbe.mtimeMs > plan.maxAge) {
@@ -27,6 +27,7 @@ import {
27
27
  storedLeadWorkerIndexPath,
28
28
  } from './store-summary-locations.mjs';
29
29
  import { cleanValue, isStoredSessionId, positiveNumber } from './store-summary-fields.mjs';
30
+ import { sessionUsageTotals } from '../../../shared/session-usage-totals.mjs';
30
31
 
31
32
  const DEAD_AGENT_STATUS =
32
33
  /^(?:done|complete|completed|success|closed|error|fail|failed|cancelled|canceled|killed|timeout)$/i;
@@ -223,6 +224,7 @@ const WORKER_HEADER_FIELDS = [
223
224
  'model',
224
225
  'effort',
225
226
  'fast',
227
+ 'autoEffort',
226
228
  'createdAt',
227
229
  'updatedAt',
228
230
  'cwd',
@@ -234,6 +236,9 @@ const WORKER_HEADER_FIELDS = [
234
236
  'startedAt',
235
237
  'cancelledAt',
236
238
  'finishedAt',
239
+ 'totalInputTokens',
240
+ 'totalOutputTokens',
241
+ 'totalToolCalls',
237
242
  ...SESSION_CANCEL_FIELDS,
238
243
  ];
239
244
 
@@ -321,6 +326,7 @@ function projectChildWorkerRow(row, { now, heartbeatMtimes }) {
321
326
  model: cleanValue(row.model || session?.model) || null,
322
327
  effort: cleanValue(row.effort || session?.effort) || null,
323
328
  fast: row.fast === true || session?.fast === true,
329
+ ...(row.autoEffort === false || session?.autoEffort === false ? { autoEffort: false } : {}),
324
330
  status: declaredWorking && !working ? 'unknown' : declaredStatus,
325
331
  stage: declaredWorking && !working ? 'unknown' : declaredStage,
326
332
  // A live turn blocked on the user's approval (persisted by the agent
@@ -335,6 +341,7 @@ function projectChildWorkerRow(row, { now, heartbeatMtimes }) {
335
341
  cwd: cleanValue(row.cwd || session?.cwd) || null,
336
342
  clientHostPid: positiveNumber(row.clientHostPid || session?.clientHostPid, 0) || null,
337
343
  taskId: cleanValue(row.task_id || row.taskId) || null,
344
+ ...sessionUsageTotals(session),
338
345
  };
339
346
  }
340
347
 
@@ -356,6 +363,7 @@ function sidecarWorkerRow(
356
363
  model: cleanValue(session?.model) || current.model || null,
357
364
  effort: cleanValue(session?.effort) || current.effort || null,
358
365
  fast: session?.fast === true || current.fast === true,
366
+ ...(session?.autoEffort === false || current.autoEffort === false ? { autoEffort: false } : {}),
359
367
  status,
360
368
  stage: status,
361
369
  startedAt: session?.createdAt || current.startedAt || heartbeatAt,
@@ -365,6 +373,7 @@ function sidecarWorkerRow(
365
373
  cwd: cleanValue(session?.cwd) || current.cwd || null,
366
374
  clientHostPid: positiveNumber(session?.clientHostPid, 0) || current.clientHostPid || null,
367
375
  taskId: cleanValue(session?.task_id || session?.taskId) || current.taskId || null,
376
+ ...sessionUsageTotals(session),
368
377
  };
369
378
  }
370
379
 
@@ -458,6 +467,7 @@ function projectLeadWorkerRow(row, { now, heartbeatMtimes }) {
458
467
  model: cleanValue(row.model) || null,
459
468
  effort: cleanValue(row.effort) || null,
460
469
  fast: row.fast === true,
470
+ ...(row.autoEffort === false ? { autoEffort: false } : {}),
461
471
  status: shownStatus,
462
472
  stage: shownStatus,
463
473
  startedAt: row.startedAt || row.createdAt || null,
@@ -468,6 +478,7 @@ function projectLeadWorkerRow(row, { now, heartbeatMtimes }) {
468
478
  cwd: cleanValue(row.cwd) || null,
469
479
  clientHostPid: positiveNumber(row.clientHostPid, 0) || null,
470
480
  taskId: cleanValue(row.task_id || row.taskId) || null,
481
+ ...sessionUsageTotals(readWorkerSessionHeader(sessionId)),
471
482
  };
472
483
  }
473
484
 
@@ -19,6 +19,35 @@ export function cleanValue(value) {
19
19
  return String(value || '').trim();
20
20
  }
21
21
 
22
+ const textOrNull = (value) => cleanValue(typeof value === 'string' ? value : '') || null;
23
+ const TRANSITION_STATES = new Set(['idle', 'provisioning', 'switching', 'removing', 'recovery']);
24
+
25
+ /** Catalog view of a session workspace record (the SessionWorkspace shape of
26
+ * runtime/shared/worktrees/contract.mjs): where it executes (`cwd`, `branch`,
27
+ * `worktreeId`) under which registered Project (`projectPath`), at which
28
+ * `revision`. Null when the record is absent or lacks either path, so
29
+ * consumers group by `projectPath` and never mistake a worktree `cwd` for
30
+ * the project. */
31
+ export function workspaceSummary(value) {
32
+ if (!value || typeof value !== 'object') return null;
33
+ const projectPath = cleanValue(typeof value.projectPath === 'string' ? value.projectPath : '');
34
+ const cwd = cleanValue(typeof value.cwd === 'string' ? value.cwd : '');
35
+ if (!projectPath || !cwd || projectPath.includes('\0') || cwd.includes('\0')) return null;
36
+ const state = TRANSITION_STATES.has(value.transition?.state) ? value.transition.state : 'idle';
37
+ const txId = state === 'idle' ? null : textOrNull(value.transition?.txId);
38
+ return {
39
+ projectPath,
40
+ repoCommonDir: textOrNull(value.repoCommonDir),
41
+ cwd,
42
+ worktreeId: textOrNull(value.worktreeId),
43
+ branch: textOrNull(value.branch),
44
+ baseCommit: textOrNull(value.baseCommit),
45
+ mergeTarget: textOrNull(value.mergeTarget),
46
+ revision: Number.isSafeInteger(value.revision) && value.revision >= 0 ? value.revision : 0,
47
+ transition: txId ? { state, txId } : { state: 'idle', txId: null },
48
+ };
49
+ }
50
+
22
51
  export function desktopSession(value, cwd = '') {
23
52
  if (!value || typeof value !== 'object') return null;
24
53
  if (value.classification === 'task') return { classification: 'task', projectPath: null };
@@ -26,7 +26,7 @@ import {
26
26
  sessionMessageText,
27
27
  } from '../runtime-core/session-text.mjs';
28
28
  import { sessionVisibility } from './store-summary-visibility.mjs';
29
- import { isStoredSessionId, positiveNumber as _positiveNumber } from './store-summary-fields.mjs';
29
+ import { isStoredSessionId, positiveNumber as _positiveNumber, workspaceSummary } from './store-summary-fields.mjs';
30
30
 
31
31
  export const SESSION_SUMMARY_INDEX_VERSION = 2;
32
32
 
@@ -182,11 +182,13 @@ export function _sessionSummary(session) {
182
182
  clientHostPid: _positiveNumber(session.clientHostPid, 0) || null,
183
183
  cwd: session.cwd || '',
184
184
  desktopSession: _desktopSessionSummary(session.desktopSession, session.cwd),
185
+ ...(workspaceSummary(session.workspace) ? { workspace: workspaceSummary(session.workspace) } : {}),
185
186
  provider: session.provider || null,
186
187
  model: session.model || null,
187
188
  presetName: session.presetName || session.profileId || null,
188
189
  effort: session.effort || null,
189
190
  fast: session.fast === true,
191
+ ...(session.autoEffort === false ? { autoEffort: false } : {}),
190
192
  modelParameters:
191
193
  session.modelParameters && typeof session.modelParameters === 'object' ? { ...session.modelParameters } : null,
192
194
  agentTag: session.agentTag || null,
@@ -231,11 +233,13 @@ function _normalizeSummaryRow(row) {
231
233
  clientHostPid: _positiveNumber(row.clientHostPid, 0) || null,
232
234
  cwd: row.cwd || '',
233
235
  desktopSession: _desktopSessionSummary(row.desktopSession, row.cwd),
236
+ ...(workspaceSummary(row.workspace) ? { workspace: workspaceSummary(row.workspace) } : {}),
234
237
  provider: row.provider || null,
235
238
  model: row.model || null,
236
239
  presetName: row.presetName || row.profileId || null,
237
240
  effort: row.effort || null,
238
241
  fast: row.fast === true,
242
+ ...(row.autoEffort === false ? { autoEffort: false } : {}),
239
243
  modelParameters: row.modelParameters && typeof row.modelParameters === 'object' ? { ...row.modelParameters } : null,
240
244
  agentTag: row.agentTag || null,
241
245
  task_id: row.task_id || null,