mixdog 1.1.2 → 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 (288) hide show
  1. package/README.md +1 -0
  2. package/package.json +2 -1
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/test-direct.mjs +4 -2
  6. package/src/defaults/shipped-history.json +6 -0
  7. package/src/defaults/skills/setup/references/actions.md +5 -2
  8. package/src/defaults/skills/visualize/SKILL.md +157 -0
  9. package/src/defaults/skills/visualize/references/craft.md +88 -0
  10. package/src/headless-command.mjs +56 -3
  11. package/src/headless-exec.mjs +17 -1
  12. package/src/help.mjs +2 -0
  13. package/src/rules/shared/70-delivery.md +2 -2
  14. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  15. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  16. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  17. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  18. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  19. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/config.mjs +9 -1
  21. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +47 -7
  22. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +51 -8
  23. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +25 -5
  24. package/src/runtime/agent/orchestrator/mcp/client.mjs +348 -122
  25. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  26. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  27. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  28. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +13 -1
  29. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  30. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  31. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  32. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +12 -4
  33. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  34. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  35. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  36. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  37. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  38. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  39. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  40. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +67 -0
  41. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +521 -0
  42. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  43. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  44. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  45. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +3 -0
  46. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  47. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  49. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  50. package/src/runtime/agent/orchestrator/providers/registry.mjs +15 -1
  51. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  52. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  53. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +12 -11
  54. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  55. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +11 -0
  56. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
  57. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  58. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  59. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  60. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  61. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +18 -11
  62. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  63. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  64. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  65. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  66. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  67. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  69. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -1
  70. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +1 -0
  71. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +17 -7
  72. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  73. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -2
  74. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -0
  75. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +5 -3
  76. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  77. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  78. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  79. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  80. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  81. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  82. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  83. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  84. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  85. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  86. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  87. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  88. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +29 -19
  89. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  90. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  91. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  92. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +29 -0
  93. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -1
  94. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +23 -4
  95. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  96. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +28 -8
  97. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +140 -0
  98. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  99. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  100. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  101. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  102. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +31 -12
  103. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  104. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  105. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  106. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -66
  107. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -1
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  111. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  113. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  114. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  115. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  116. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  117. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  118. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  119. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  120. package/src/runtime/attachments/store.mjs +38 -0
  121. package/src/runtime/shared/atomic-file.mjs +8 -2
  122. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  123. package/src/runtime/shared/code-graph-state.mjs +12 -0
  124. package/src/runtime/shared/defaults-separation.mjs +2 -0
  125. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  126. package/src/runtime/shared/git-runner.mjs +4 -3
  127. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  128. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  129. package/src/runtime/shared/llm/model-catalog.mjs +240 -118
  130. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  131. package/src/runtime/shared/llm/provider-catalog-registry.mjs +156 -0
  132. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  133. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  134. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  135. package/src/runtime/shared/provider-accounts.mjs +2 -2
  136. package/src/runtime/shared/provider-api-key.mjs +21 -1
  137. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  138. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  139. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  140. package/src/runtime/shared/singleton-owner.mjs +36 -6
  141. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  142. package/src/runtime/shared/tool-surface.mjs +7 -0
  143. package/src/runtime/shared/tool-work-units.mjs +14 -0
  144. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  145. package/src/runtime/shared/turn-changes.mjs +791 -0
  146. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  147. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  148. package/src/runtime/shared/turn-worktree-snapshot.mjs +161 -11
  149. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  150. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  151. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  152. package/src/runtime/shared/worktrees/git.mjs +247 -0
  153. package/src/runtime/shared/worktrees/include.mjs +480 -0
  154. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  155. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  156. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  157. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  158. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  159. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  160. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  161. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  162. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  163. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  164. package/src/runtime/visualize-bridge/client.mjs +181 -0
  165. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  166. package/src/session-runtime/boot/apis.mjs +35 -0
  167. package/src/session-runtime/boot/config.mjs +2 -0
  168. package/src/session-runtime/boot/facade.mjs +20 -0
  169. package/src/session-runtime/boot/infrastructure.mjs +1 -0
  170. package/src/session-runtime/boot/session.mjs +1 -0
  171. package/src/session-runtime/boot/tools.mjs +4 -0
  172. package/src/session-runtime/config-lifecycle.mjs +10 -1
  173. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +11 -1
  174. package/src/session-runtime/global-extensions.mjs +2 -2
  175. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  176. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  177. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  178. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  179. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  180. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  181. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  182. package/src/session-runtime/mcp-glue/connect.mjs +11 -6
  183. package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -3
  184. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  185. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  186. package/src/session-runtime/model-route/set-route.mjs +15 -2
  187. package/src/session-runtime/notification-bus.mjs +9 -1
  188. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  189. package/src/session-runtime/resource-extension-sync.mjs +30 -7
  190. package/src/session-runtime/resource-mcp-api.mjs +20 -7
  191. package/src/session-runtime/route-resolution.mjs +10 -1
  192. package/src/session-runtime/route-state.mjs +16 -2
  193. package/src/session-runtime/runtime-core.mjs +5 -0
  194. package/src/session-runtime/runtime-facade.mjs +8 -0
  195. package/src/session-runtime/runtime-review-api.mjs +9 -0
  196. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  197. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  198. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  199. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  200. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  201. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  202. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  203. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  204. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  205. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  206. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  207. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  208. package/src/session-runtime/services/agent-tool.mjs +1 -0
  209. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  210. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  211. package/src/session-runtime/session-create/session-options.mjs +10 -1
  212. package/src/session-runtime/session-turn-api.mjs +1 -0
  213. package/src/session-runtime/settings-api.mjs +4 -2
  214. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  215. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  216. package/src/session-runtime/setup-tool/executor.mjs +11 -4
  217. package/src/session-runtime/setup-tool/settings-contract.mjs +3 -1
  218. package/src/session-runtime/setup-tool/tool-defs.mjs +31 -3
  219. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  220. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +13 -3
  221. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  222. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  223. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  224. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  225. package/src/session-runtime/turn/turn-run.mjs +6 -7
  226. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  227. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  228. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  229. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  230. package/src/session-runtime/workspace/transitions.mjs +651 -0
  231. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  232. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  233. package/src/standalone/channel-transport.mjs +13 -1
  234. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  235. package/src/standalone/daemon-owner.mjs +372 -0
  236. package/src/standalone/daemon-stored-session-views.mjs +4 -1
  237. package/src/standalone/daemon.mjs +139 -5
  238. package/src/standalone/desktop-service-registry.mjs +20 -0
  239. package/src/standalone/local-session-runtime.mjs +11 -0
  240. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  241. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  242. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  243. package/src/standalone/session-client.mjs +104 -13
  244. package/src/standalone/session-protocol.mjs +16 -0
  245. package/src/standalone/session-proxy.mjs +3 -0
  246. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  247. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  248. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  249. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  250. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  251. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  252. package/src/standalone/session-service/session-calls/session-action.mjs +11 -3
  253. package/src/standalone/session-transport/call-route.mjs +13 -0
  254. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  255. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  256. package/src/standalone/session-transport/client-registry.mjs +20 -3
  257. package/src/standalone/session-transport/routes.mjs +79 -2
  258. package/src/standalone/session-transport/stack.mjs +2 -0
  259. package/src/standalone/session-transport.mjs +24 -1
  260. package/src/tui/app/message-selector.mjs +2 -1
  261. package/src/tui/app/model-options.mjs +35 -5
  262. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  263. package/src/tui/app/model-picker.mjs +4 -2
  264. package/src/tui/dist/index.mjs +332 -36
  265. package/src/tui/index.jsx +10 -2
  266. package/src/tui/session/compaction-view.mjs +147 -0
  267. package/src/tui/session/context-state.mjs +6 -0
  268. package/src/tui/session/initial-state.mjs +3 -0
  269. package/src/tui/session/quota-resume.mjs +2 -1
  270. package/src/tui/session/session-api/commands.mjs +10 -2
  271. package/src/tui/session/session-api/controls.mjs +4 -0
  272. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  273. package/src/tui/session/session-api/integrations.mjs +17 -0
  274. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  275. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  276. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  277. package/src/tui/session/session-api/routes.mjs +10 -1
  278. package/src/tui/session/session-api/settings.mjs +2 -0
  279. package/src/tui/session/session-api/worktree.mjs +60 -0
  280. package/src/tui/session/session-api.mjs +2 -0
  281. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  282. package/src/tui/session/session-flow/drain.mjs +15 -1
  283. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  284. package/src/tui/session/session-flow.mjs +2 -0
  285. package/src/tui/session/transcript-intake.mjs +14 -5
  286. package/src/tui/session/turn-stream.mjs +32 -4
  287. package/src/tui/session/turn-tool-cards.mjs +10 -2
  288. package/src/tui/session/turn.mjs +39 -10
@@ -1,5 +1,6 @@
1
1
  // Steering-message normalization/merge helpers.
2
2
  // Merges queued steering entries into a single content payload + display text.
3
+ import { stableMessageId } from '../../../../shared/transcript-metadata.mjs';
3
4
 
4
5
  function steeringContentText(content) {
5
6
  if (typeof content === 'string') return content;
@@ -135,10 +136,14 @@ function injectSteeringEntry(merged, { messages, opts, stage, onSkillPrompt }) {
135
136
  const rawSubmittedAt = Number(merged.submittedAt);
136
137
  const submittedAt = Number.isFinite(rawSubmittedAt) && rawSubmittedAt > 0 ? rawSubmittedAt : undefined;
137
138
  const injectedAt = Date.now();
139
+ // Every steered message carries a stable id from creation; the surface
140
+ // gets it back (onSteerMessage) for the row it draws. A steered prompt
141
+ // keeps the id the surface gave it, else gets one.
142
+ const messageId = stableMessageId(merged.transcriptMeta?.messageId);
138
143
  const steeringTranscriptMeta =
139
144
  merged.transcriptMeta && typeof merged.transcriptMeta === 'object'
140
- ? { at: submittedAt ?? injectedAt, ...merged.transcriptMeta }
141
- : null;
145
+ ? { at: submittedAt ?? injectedAt, ...merged.transcriptMeta, messageId }
146
+ : { messageId };
142
147
  // Tag steering-origin user messages so provider lowering keeps them
143
148
  // distinct from preceding tool results. Keep each queued command as
144
149
  // its own user turn instead of collapsing priority/mode buckets
@@ -158,7 +163,7 @@ function injectSteeringEntry(merged, { messages, opts, stage, onSkillPrompt }) {
158
163
  source: notification ? 'task-notification' : 'steering',
159
164
  ...(execution ? { execution } : {}),
160
165
  ...(submissionIds.length ? { submissionIds } : {}),
161
- ...(steeringTranscriptMeta ? { transcript: steeringTranscriptMeta } : {}),
166
+ transcript: steeringTranscriptMeta,
162
167
  },
163
168
  });
164
169
  if (!notification) onSkillPrompt(merged.content);
@@ -172,9 +177,12 @@ function injectSteeringEntry(merged, { messages, opts, stage, onSkillPrompt }) {
172
177
  ...(notification ? { mode: 'task-notification' } : {}),
173
178
  ...(execution ? { execution } : {}),
174
179
  ...(Array.isArray(merged.images) && merged.images.length ? { images: merged.images } : {}),
175
- ...(steeringTranscriptMeta ? { transcriptMeta: steeringTranscriptMeta } : {}),
180
+ transcriptMeta: steeringTranscriptMeta,
181
+ messageId,
176
182
  });
177
- } catch {}
183
+ } catch {
184
+ /* best-effort host callback */
185
+ }
178
186
  return {
179
187
  count: Number(merged.count) || 1,
180
188
  textLength: String(text || '').length,
@@ -224,9 +232,11 @@ export function createSteeringDrain({ messages, opts, sessionId, onSkillPrompt }
224
232
  try {
225
233
  process.stderr.write(
226
234
  `[steer] sess=${sessionId} injected ${stage} user message(s)` +
227
- ` (merged=${totalCount} len=${totalTextLen} waitMs=${Math.max(0, maxQueueWaitMs)})\n`
235
+ ` (merged=${totalCount} len=${totalTextLen} waitMs=${maxQueueWaitMs})\n`
228
236
  );
229
- } catch {}
237
+ } catch {
238
+ /* best-effort diagnostics */
239
+ }
230
240
  }
231
241
  return true;
232
242
  };
@@ -40,6 +40,7 @@ export function usageDeltaEvent({
40
40
  model,
41
41
  usage,
42
42
  sendTools,
43
+ toolCalls = 0,
43
44
  }) {
44
45
  return {
45
46
  sessionId,
@@ -51,6 +52,8 @@ export function usageDeltaEvent({
51
52
  usageMetricsEpoch,
52
53
  deltaInput: usage.inputTokens || 0,
53
54
  deltaOutput: usage.outputTokens || 0,
55
+ // Tool calls this response dispatched; the session's cumulative count.
56
+ deltaToolCalls: toolCalls,
54
57
  ...reasoningUsage(usage),
55
58
  deltaPrompt: usage.promptTokens || 0,
56
59
  // Cache delta carried alongside input/output so live metrics reflect
@@ -40,13 +40,23 @@ const traceAskStart = (sessionId, explicitPrefetch) => {
40
40
 
41
41
  /** The transcript timestamps the caller supplied for this ask: the persisted
42
42
  * user metadata plus a one-shot reader for the assistant metadata. */
43
- function createAskTranscriptMeta(rawTranscriptMeta) {
43
+ export function createAskTranscriptMeta(rawTranscriptMeta) {
44
44
  return {
45
45
  raw: rawTranscriptMeta,
46
46
  user: persistedUserTranscriptMetadata(rawTranscriptMeta),
47
47
  takeAssistant: () => {
48
+ const live = rawTranscriptMeta && typeof rawTranscriptMeta === 'object';
49
+ // An id the live row already minted is the one persisted; otherwise the
50
+ // row has not been drawn yet and takes this message's id and time.
51
+ const rowKnowsId =
52
+ live && typeof rawTranscriptMeta.assistantMessageId === 'string' && rawTranscriptMeta.assistantMessageId;
48
53
  const metadata = persistedAssistantTranscriptMetadata(rawTranscriptMeta);
49
- if (rawTranscriptMeta && typeof rawTranscriptMeta === 'object') delete rawTranscriptMeta.assistantAt;
54
+ if (live) {
55
+ // One-shot: the next reply gets its own time and stable id.
56
+ delete rawTranscriptMeta.assistantAt;
57
+ delete rawTranscriptMeta.assistantMessageId;
58
+ rawTranscriptMeta.persistedAssistant = rowKnowsId ? null : { messageId: metadata.messageId, at: metadata.at };
59
+ }
50
60
  return metadata;
51
61
  },
52
62
  };
@@ -70,6 +70,8 @@ export async function resolveTurnAutoEffort({ sessionId, session, provider, inpu
70
70
  // Agent sessions are judged like any other: their spawn brief and later Lead
71
71
  // `send` messages arrive as plain prompts.
72
72
  if (mode === 'off' || input?.promptSource) return null;
73
+ // The session's route turned Auto effort off: its chosen effort stands.
74
+ if (session?.autoEffort === false) return null;
73
75
  const request = promptContentText(input?.prompt).trim();
74
76
  // Skill bodies and runtime blocks arrive as tagged text, not as a request.
75
77
  if (!request || request.startsWith('<')) return null;
@@ -13,7 +13,9 @@ const optionalFn = (value) => (typeof value === 'function' ? value : undefined);
13
13
  const relay = (fn, ...args) => {
14
14
  try {
15
15
  fn?.(...args);
16
- } catch {}
16
+ } catch {
17
+ /* a host callback must never break the turn */
18
+ }
17
19
  };
18
20
 
19
21
  /**
@@ -70,10 +72,10 @@ export function buildAgentLoopOptions({
70
72
  checkpoint.schedule();
71
73
  if (typeof askOpts?.onReasoningDelta === 'function') askOpts.onReasoningDelta(chunk);
72
74
  },
73
- onAssistantText: (text) => {
75
+ onAssistantText: (text, detail) => {
74
76
  interruption.recordAssistantText(text);
75
77
  checkpoint.schedule(true);
76
- if (typeof askOpts?.onAssistantText === 'function') askOpts.onAssistantText(text);
78
+ if (typeof askOpts?.onAssistantText === 'function') askOpts.onAssistantText(text, detail);
77
79
  },
78
80
  takeAssistantTranscriptMetadata,
79
81
  // Send-opt only. Never copy onto `session` — a transient
@@ -22,11 +22,13 @@ import { recordProviderContextBaseline } from '../loop/compact-policy.mjs';
22
22
  import { throwIfAborted } from '../../../../shared/abort-race.mjs';
23
23
  import { appendAssistantTurnMessage, persistProviderState, recordAskUsage } from './ask-turn-commit.mjs';
24
24
 
25
- function attachAssistantTranscriptCompletion(messages, completion, turnStartedAt = 0) {
25
+ export function attachAssistantTranscriptCompletion(messages, completion, turnStartedAt = 0) {
26
26
  if (!Array.isArray(messages) || !completion || typeof completion !== 'object') return false;
27
27
  const elapsedMs = Math.max(0, Number(completion.elapsedMs || 0));
28
28
  const status = typeof completion.status === 'string' && completion.status ? completion.status : 'done';
29
29
  const verb = typeof completion.verb === 'string' && completion.verb ? completion.verb : 'Thought';
30
+ const checkpointId =
31
+ typeof completion.checkpointId === 'string' && completion.checkpointId ? completion.checkpointId : '';
30
32
  let turnStart = -1;
31
33
  const expectedAt = Number(turnStartedAt || 0);
32
34
  if (expectedAt > 0) {
@@ -60,7 +62,7 @@ function attachAssistantTranscriptCompletion(messages, completion, turnStartedAt
60
62
  ...meta,
61
63
  transcript: {
62
64
  ...transcript,
63
- completion: { status, verb, elapsedMs },
65
+ completion: { status, verb, elapsedMs, ...(checkpointId ? { checkpointId } : {}) },
64
66
  },
65
67
  },
66
68
  };
@@ -145,6 +147,7 @@ export function publishAskTurn({ sessionId, opened, terminalResultPreview, askOp
145
147
  status: 'done',
146
148
  verb: rawTranscriptMeta?.completionVerb,
147
149
  elapsedMs: Date.now() - turnStartedAt,
150
+ checkpointId: rawTranscriptMeta?.checkpointId,
148
151
  },
149
152
  turnStartedAt
150
153
  );
@@ -15,6 +15,7 @@ import {
15
15
  } from '../context-utils.mjs';
16
16
  import { previewFreshContextCompaction, runFreshContextCompact } from '../loop/fresh-context.mjs';
17
17
  import { positiveInt } from '../../../../shared/numbers.mjs';
18
+ import { writeStderrBestEffort } from '../../../../shared/best-effort-stderr.mjs';
18
19
  import { traceAgentCompact, messagePrefixHash } from '../../agent-trace.mjs';
19
20
  import { uncachedInputTokensForProvider } from './usage-metrics.mjs';
20
21
  import { pruneOffloadSession } from '../tool-result-offload.mjs';
@@ -33,6 +34,7 @@ import {
33
34
  shouldCompactForRequestMedia,
34
35
  } from '../loop/compact-policy.mjs';
35
36
  import { snapshotProviderRequestTools } from '../../runtime-core/tool-catalog.mjs';
37
+ import { scheduleCompactionArchive, stampCompactionMessageIds } from '../compaction-archive-capture.mjs';
36
38
 
37
39
  // 'compacting' is a transient in-flight stage written just before Compact
38
40
  // runs. If the process crashes or only partially
@@ -126,7 +128,7 @@ function handoffSummaryTimeoutMs(session, messageTokens) {
126
128
  * mutated and nothing is persisted — so a caller may compact a COPY of a
127
129
  * conversation. Session inheritance relies on exactly that: an oversized
128
130
  * transcript is compacted for the heir while the session that owns it keeps
129
- * every message (user: 자동으로 컴팩트하고 승계).
131
+ * every message.
130
132
  */
131
133
  export async function runHandoffCompaction({
132
134
  session,
@@ -190,9 +192,8 @@ async function sessionCompactionPlan(session, opts) {
190
192
  }
191
193
  // Reserve must mirror loop.mjs (buildCompactPolicy): request reserve (tool
192
194
  // schema) PLUS the configured reserve (session.compaction.reservedTokens or
193
- // MIXDOG_AGENT_COMPACT_RESERVED_TOKENS env). The old request-only value left
194
- // the manual / auto-clear compact budget without the configured headroom the
195
- // loop path reserves, so a compacted transcript could overflow on next send.
195
+ // MIXDOG_AGENT_COMPACT_RESERVED_TOKENS env). Without the configured headroom
196
+ // the loop path reserves, a compacted transcript could overflow on next send.
196
197
  // A cold (just loaded) transcript is metered in slices first; the plan
197
198
  // numbers below then read the per-message memos.
198
199
  await primeContextEstimates(messages, session);
@@ -202,6 +203,7 @@ async function sessionCompactionPlan(session, opts) {
202
203
  resolvedSessionId,
203
204
  mode,
204
205
  force,
206
+ archive: opts.archive !== false,
205
207
  messages,
206
208
  boundary,
207
209
  alignedPolicy,
@@ -292,8 +294,8 @@ function compactionRecord(plan, { stage, after, now, reserve, outcome }) {
292
294
  }
293
295
 
294
296
  // compact_meta parity with the loop's pre-send pass: the out-of-loop
295
- // (post-turn/manual) compaction — and its failure — used to be invisible to
296
- // trace analytics.
297
+ // (post-turn/manual) compaction — and its failure — is visible to trace
298
+ // analytics.
297
299
  function traceSessionCompaction(plan, compactStartedAt, { counts, failure }) {
298
300
  const { session, mode } = plan;
299
301
  traceAgentCompact({
@@ -342,13 +344,9 @@ async function runSessionHandoff(plan, opts) {
342
344
  } catch (err) {
343
345
  run.freshContextError = err;
344
346
  run.compactError = err;
345
- try {
346
- process.stderr.write(
347
- `[session] fresh-context ${mode} compact failed (sess=${session.id || 'unknown'}): ${err?.message || err}\n`
348
- );
349
- } catch {
350
- /* best-effort */
351
- }
347
+ writeStderrBestEffort(
348
+ `[session] fresh-context ${mode} compact failed (sess=${session.id || 'unknown'}): ${err?.message || err}\n`
349
+ );
352
350
  }
353
351
  if (!run.compacted && !run.compactError) {
354
352
  run.compactError = new Error('fresh-context compact produced no messages');
@@ -515,8 +513,8 @@ function compactionOutcome(session, freshContextResult, changed, now) {
515
513
  }
516
514
 
517
515
  // Observability parity with the loop's pre-send pass: record the
518
- // out-of-loop mutation as compact_meta (observed live: a 403k→10k post-turn
519
- // compact traced as intentional_transition: null with no compact_meta).
516
+ // out-of-loop mutation as compact_meta, so a large post-turn compact is not
517
+ // traced as intentional_transition: null.
520
518
  function traceCommittedCompaction(plan, compactStartedAt, { messages, compacted, beforeBytes, afterBytes, changed }) {
521
519
  let beforePrefixHash = null;
522
520
  try {
@@ -645,6 +643,19 @@ async function commitSessionCompaction(plan, run, compactStartedAt) {
645
643
  },
646
644
  outcome: compactionOutcome(session, freshContextResult, changed, now),
647
645
  });
646
+ // Display-only: the dropped conversation stays readable above the divider.
647
+ // Only the selection and the segment number happen here; the projection and
648
+ // the write run later, off this commit.
649
+ const archived =
650
+ changed && plan.archive
651
+ ? scheduleCompactionArchive({
652
+ session,
653
+ sessionId: resolvedSessionId,
654
+ before: messages,
655
+ after: transcript,
656
+ at: now,
657
+ })
658
+ : null;
648
659
  if (changed) {
649
660
  invalidateProviderContextBaseline(session);
650
661
  if (postCompactPolicy) {
@@ -678,6 +689,10 @@ async function commitSessionCompaction(plan, run, compactStartedAt) {
678
689
  freshContextError: null,
679
690
  handoffSource: freshContextResult?.handoffSource || 'session-local',
680
691
  usage: freshContextResult?.usage || null,
692
+ // Messages that left the model context (0 for a rewrite-only pass):
693
+ // surfaces archive only those rows.
694
+ droppedMessages: archived?.dropped ?? 0,
695
+ droppedIdentities: archived?.identities ?? null,
681
696
  }
682
697
  );
683
698
  }
@@ -692,7 +707,8 @@ export async function runSessionCompaction(session, opts = {}) {
692
707
  });
693
708
  }
694
709
  plan.requireReduction =
695
- (plan.mode === 'auto' || opts.requireReduction === true) && !shouldCompactForRequestMedia(plan.messages, session.provider);
710
+ (plan.mode === 'auto' || opts.requireReduction === true) &&
711
+ !shouldCompactForRequestMedia(plan.messages, session.provider);
696
712
  const skipped = () =>
697
713
  compactionResult(plan, { changed: false, skipped: true, reason: 'no_token_reduction' }, unchangedAfter(plan), {
698
714
  freshContext: false,
@@ -713,6 +729,11 @@ export async function runSessionCompaction(session, opts = {}) {
713
729
  }).reducesTokens
714
730
  )
715
731
  return skipped();
732
+ // Every message enters the compaction with a stable id (legacy ones are
733
+ // stamped now and persisted): the result's rewrites carry it, so what was
734
+ // dropped is exactly the ids the result no longer holds. The snapshot is a
735
+ // prefix of the live transcript holding the same message objects.
736
+ stampCompactionMessageIds(plan.messages, plan.resolvedSessionId);
716
737
  const compactStartedAt = Date.now();
717
738
  try {
718
739
  await opts.onStageChange?.('compacting');
@@ -117,8 +117,12 @@ export function resolveSessionContextMeta(provider, model, seed = {}, { wholeWin
117
117
  // (a session older than selectedContextWindow, a route saved before the
118
118
  // catalog warmed) is still the user's choice and keeps the served window.
119
119
  const recordsSelection = wholeWindow || boundedPercent(seed.contextPercent) !== null;
120
+ // An exact token selection without its resolved window still names it,
121
+ // bounded by what the model serves.
122
+ const selectedTokens = positiveInt(seed.contextTokens);
120
123
  const requestedContextWindow =
121
124
  positiveInt(seed.selectedContextWindow) ||
125
+ (selectedTokens ? Math.min(selectedTokens, servedContextWindow) : 0) ||
122
126
  (recordsSelection
123
127
  ? servedContextWindow
124
128
  : contextWindowRange({ provider: providerNameOf(provider), contextWindow: servedContextWindow }).defaultWindow);
@@ -185,6 +185,96 @@ export function refreshSessionBp3Environment(session, cwd) {
185
185
  return true;
186
186
  }
187
187
 
188
+ const CORE_MEMORY_HEADING = '# Core Memory\n';
189
+ const INSTRUCTION_FILES_HEADING = '# Instruction Files';
190
+
191
+ function trimmedSlice(value) {
192
+ return typeof value === 'string' ? value.trim() : '';
193
+ }
194
+
195
+ // Where a heading-led slice starts in a BP3 join: `cut` ends the text before
196
+ // it (separator included), `start` is the slice's first character.
197
+ function bp3SliceAt(text, heading) {
198
+ if (text.startsWith(heading)) return { cut: 0, start: 0 };
199
+ const at = text.indexOf(`${BP3_PART_SEPARATOR}${heading}`);
200
+ return at < 0 ? null : { cut: at, start: at + BP3_PART_SEPARATOR.length };
201
+ }
202
+
203
+ /**
204
+ * Replace the workspace-scoped tail of a BP3 core (composeSystemPrompt order:
205
+ * …, `# Core Memory`, `# Instruction Files`). The instruction-files slice is
206
+ * always last and may itself contain separators, so it is cut by position;
207
+ * the core-memory slice is one separator-free part. `undefined` keeps a slice,
208
+ * an empty string drops it.
209
+ * @param {string} core
210
+ * @param {{ coreMemoryContext?: string, instructionFilesContext?: string }} slices
211
+ */
212
+ export function replaceWorkspaceCoreSlices(core, { coreMemoryContext, instructionFilesContext } = {}) {
213
+ let head = String(core || '');
214
+ let instructionFiles = '';
215
+ const instructionAt = bp3SliceAt(head, INSTRUCTION_FILES_HEADING);
216
+ if (instructionAt) {
217
+ instructionFiles = head.slice(instructionAt.start);
218
+ head = head.slice(0, instructionAt.cut);
219
+ }
220
+ let parts = head ? head.split(BP3_PART_SEPARATOR) : [];
221
+ if (coreMemoryContext !== undefined) {
222
+ parts = parts.filter((part) => !part.startsWith(CORE_MEMORY_HEADING));
223
+ const memory = trimmedSlice(coreMemoryContext);
224
+ if (memory) parts.push(`${CORE_MEMORY_HEADING}${memory}`);
225
+ }
226
+ if (instructionFilesContext !== undefined) instructionFiles = trimmedSlice(instructionFilesContext);
227
+ return [...parts, instructionFiles].filter((part) => part && part.trim()).join(BP3_PART_SEPARATOR);
228
+ }
229
+
230
+ /**
231
+ * Replace the shell/project facts that lead the persisted environment tail
232
+ * (`- Cwd …`, shell tools, git state, cwd listing); the trailing response
233
+ * language block is kept. `undefined` keeps the facts.
234
+ */
235
+ export function replaceWorkspaceEnvironment(environmentTail, environmentContext) {
236
+ if (environmentContext === undefined) return environmentTail;
237
+ const parts = typeof environmentTail === 'string' && environmentTail ? environmentTail.split(BP3_PART_SEPARATOR) : [];
238
+ const facts = trimmedSlice(environmentContext);
239
+ if (parts[0]?.startsWith('- ')) parts[0] = facts;
240
+ else parts.unshift(facts);
241
+ return parts.filter((part) => part && part.trim()).join(BP3_PART_SEPARATOR);
242
+ }
243
+
244
+ /**
245
+ * Re-root an existing session's prompt at a new workspace without touching
246
+ * the conversation: environment facts, project memory and instruction files
247
+ * are replaced in place (never appended), then the `# Session` block is
248
+ * rebuilt for `cwd`. Messages are replaced, not mutated, so delta saves write
249
+ * a full snapshot.
250
+ * @param {object} session
251
+ * @param {{ cwd: string, environmentContext?: string, coreMemoryContext?: string, instructionFilesContext?: string }} context
252
+ * @returns {boolean}
253
+ */
254
+ export function refreshSessionWorkspaceContext(
255
+ session,
256
+ { cwd, environmentContext, coreMemoryContext, instructionFilesContext } = {}
257
+ ) {
258
+ if (!session) return false;
259
+ if (typeof session.bp3EnvironmentContext === 'string') {
260
+ session.bp3EnvironmentContext = replaceWorkspaceEnvironment(session.bp3EnvironmentContext, environmentContext);
261
+ }
262
+ const slices = { coreMemoryContext, instructionFilesContext };
263
+ if (session.bp3EnvSplit === true) {
264
+ const target = bp3SystemMessage(session);
265
+ if (target && typeof target.content === 'string') {
266
+ const nextCore = replaceWorkspaceCoreSlices(target.content, slices);
267
+ replaceSystemMessageContent(session, target, nextCore);
268
+ session.bp3CoreContext = nextCore;
269
+ }
270
+ } else if (typeof session.bp3CoreContext === 'string') {
271
+ // Legacy combined layout: refreshSessionBp3Environment rebuilds the tier3
272
+ // block from the core plus the environment below.
273
+ session.bp3CoreContext = replaceWorkspaceCoreSlices(session.bp3CoreContext, slices);
274
+ }
275
+ return refreshSessionBp3Environment(session, cwd);
276
+ }
277
+
188
278
  export function resetSessionBp3Environment(session) {
189
279
  if (session?.bp3EnvSplit === true) {
190
280
  const target = bpEnvSystemMessage(session);
@@ -25,6 +25,8 @@ import {
25
25
  import { getProvider } from '../../providers/registry.mjs';
26
26
  import { isSessionCompactionBlocked, getSessionAbortSignal, _runtimeEntries } from './runtime-liveness.mjs';
27
27
  import { mintSessionId } from './session-id.mjs';
28
+ import { hideCompactionArchive } from '../compaction-archive-capture.mjs';
29
+ import { writeStderrBestEffort } from '../../../../shared/best-effort-stderr.mjs';
28
30
 
29
31
  /** Force-flush session metrics to disk. Used by watchdog terminal-reap (fix B). */
30
32
  export async function flushSessionMetrics(sessionId) {
@@ -97,14 +99,6 @@ export async function updateSessionManualTitle(id, title) {
97
99
  return true;
98
100
  }
99
101
  // --- Clear messages (keep system prompt + provider/model/cwd) ---
100
- function bestEffortStderr(line) {
101
- try {
102
- process.stderr.write(line);
103
- } catch {
104
- /* best-effort */
105
- }
106
- }
107
-
108
102
  // Token/provider accounting a cleared transcript starts from; shared by the
109
103
  // live session and the cold fork of its outgoing transcript.
110
104
  function clearedTokenAccounting(now) {
@@ -112,6 +106,7 @@ function clearedTokenAccounting(now) {
112
106
  providerState: undefined,
113
107
  totalInputTokens: 0,
114
108
  totalOutputTokens: 0,
109
+ totalToolCalls: 0,
115
110
  reasoningUsage: null,
116
111
  totalCachedReadTokens: 0,
117
112
  totalCacheWriteTokens: 0,
@@ -133,11 +128,18 @@ async function compactBeforeClear(session, sessionId, clearOptions) {
133
128
  let clearCompactError = null;
134
129
  if (messages.length >= 3) {
135
130
  try {
136
- const compactResult = await runSessionCompaction(session, { mode: 'manual', force: true, sessionId });
131
+ // The clear right after discards this compaction's display too, so it
132
+ // archives nothing (clear semantics are unchanged).
133
+ const compactResult = await runSessionCompaction(session, {
134
+ mode: 'manual',
135
+ force: true,
136
+ sessionId,
137
+ archive: false,
138
+ });
137
139
  if (compactResult?.error) clearCompactError = new Error(compactResult.error);
138
140
  } catch (err) {
139
141
  clearCompactError = err;
140
- bestEffortStderr(`[session] auto-clear pre-compact failed (sess=${sessionId}): ${err?.message || err}\n`);
142
+ writeStderrBestEffort(`[session] auto-clear pre-compact failed (sess=${sessionId}): ${err?.message || err}\n`);
141
143
  }
142
144
  messages = Array.isArray(session.messages) ? session.messages : [];
143
145
  }
@@ -208,6 +210,11 @@ function forkOutgoingTranscript(session, messages, now) {
208
210
  ...clearedTokenAccounting(now),
209
211
  };
210
212
  delete fork.liveTurnMessages;
213
+ // The archive sidecar belongs to the source id; the fork has none.
214
+ if (fork.compaction?.archive) {
215
+ fork.compaction = { ...fork.compaction };
216
+ delete fork.compaction.archive;
217
+ }
211
218
  setLiveSession(fork);
212
219
  void saveSessionAsync(fork)
213
220
  .then(() => {
@@ -219,10 +226,10 @@ function forkOutgoingTranscript(session, messages, now) {
219
226
  evictLiveSession(forkId);
220
227
  })
221
228
  .catch((err) => {
222
- bestEffortStderr(`[session] clear-fork save failed (sess=${forkId}): ${err?.message || err}\n`);
229
+ writeStderrBestEffort(`[session] clear-fork save failed (sess=${forkId}): ${err?.message || err}\n`);
223
230
  });
224
231
  } catch (err) {
225
- bestEffortStderr(`[session] clear-fork failed (sess=${session.id}): ${err?.message || err}\n`);
232
+ writeStderrBestEffort(`[session] clear-fork failed (sess=${session.id}): ${err?.message || err}\n`);
226
233
  }
227
234
  }
228
235
 
@@ -277,6 +284,9 @@ export async function clearSessionMessages(sessionId, options = {}) {
277
284
  lastClearAfterMessageTokens: tokens.afterMessageTokens,
278
285
  lastClearCompactError: clearCompactError?.message || null,
279
286
  };
287
+ // A clear wipes the visible conversation: earlier compactions' archived
288
+ // display goes with it (nothing new is archived).
289
+ hideCompactionArchive(session);
280
290
  if (summaryCarriedForward && tokens.postClearPolicy) {
281
291
  recordContextUsageSnapshot(session, tokens.postClearPolicy, {
282
292
  messages: keep,
@@ -32,11 +32,13 @@ import {
32
32
  unusedModelEditToolName,
33
33
  } from '../../../../shared/edit-tool-dialect.mjs';
34
34
  import { resolveSessionContextMeta } from './context-meta.mjs';
35
+ import { normalizeContextTokens } from '../../../../shared/llm/default-context-window.mjs';
35
36
  import { getAgentRuntimeSync, warnAgentRuntimeResolveFailureOnce } from './agent-runtime-singleton.mjs';
36
37
  import { ensureCodexWireSessionId, mintSessionId } from './session-id.mjs';
37
38
  import { providerCacheKey } from './provider-cache-key.mjs';
38
39
  import { buildSessionRecord, normalizeDesktopSessionMetadata, sessionOriginFields } from './session-record.mjs';
39
40
  import { refreshSessionBp3Environment } from './prompt-utils.mjs';
41
+ import { isWorktreeBound } from '../../../../shared/worktrees/contract.mjs';
40
42
 
41
43
  // Owner liveness (attach-on-resume guard) and the durable turn-checkpoint
42
44
  // recovery it gates; re-exported so prior importers of this module stay
@@ -153,6 +155,8 @@ function resolveSessionRoute(opts, presetObj, profile) {
153
155
  const toolPreset = opts.tools || presetObj?.tools || (typeof opts.preset === 'string' ? opts.preset : null) || 'full';
154
156
  const effort = Object.hasOwn(opts, 'effort') ? opts.effort || null : presetObj?.effort || null;
155
157
  const fast = presetObj?.fast === true || opts.fast === true;
158
+ // Absent means on: an explicit request wins, else the preset's own flag.
159
+ const autoEffort = opts.autoEffort !== undefined ? opts.autoEffort !== false : presetObj?.autoEffort !== false;
156
160
  let modelParameters = {};
157
161
  if (opts.modelParameters && typeof opts.modelParameters === 'object') {
158
162
  modelParameters = { ...opts.modelParameters };
@@ -160,6 +164,7 @@ function resolveSessionRoute(opts, presetObj, profile) {
160
164
  modelParameters = { ...presetObj.modelParameters };
161
165
  }
162
166
  const contextPercent = normalizeContextPercent(opts.contextPercent);
167
+ const contextTokens = normalizeContextTokens(opts.contextTokens) || null;
163
168
  if (!providerName) throw new Error('createSession: provider is required');
164
169
  if (!modelName) throw new Error('createSession: model is required');
165
170
  const provider = getProvider(providerName);
@@ -172,7 +177,19 @@ function resolveSessionRoute(opts, presetObj, profile) {
172
177
  // materialization. Supplying that reservation here keeps the address
173
178
  // stable across intake -> queued turn -> provider execution.
174
179
  const id = requestedId || mintSessionId();
175
- return { providerName, modelName, provider, toolPreset, effort, fast, modelParameters, contextPercent, id };
180
+ return {
181
+ providerName,
182
+ modelName,
183
+ provider,
184
+ toolPreset,
185
+ effort,
186
+ fast,
187
+ autoEffort,
188
+ modelParameters,
189
+ contextPercent,
190
+ contextTokens,
191
+ id,
192
+ };
176
193
  }
177
194
 
178
195
  // Unified-shard policy — no broad role-specific schema filter. Keep
@@ -213,6 +230,7 @@ export function createSession(opts) {
213
230
  const contextMeta = resolveSessionContextMeta(provider, modelName, {
214
231
  selectedContextWindow: opts.selectedContextWindow,
215
232
  contextPercent: route.contextPercent,
233
+ contextTokens: route.contextTokens,
216
234
  });
217
235
  const session = buildSessionRecord({
218
236
  opts,
@@ -287,6 +305,10 @@ function applyRouteFields(session, route) {
287
305
  if (route.provider) session.provider = route.provider;
288
306
  if (route.model) session.model = route.model;
289
307
  if (Object.hasOwn(route, 'fast')) session.fast = route.fast === true;
308
+ if (Object.hasOwn(route, 'autoEffort')) {
309
+ if (route.autoEffort === false) session.autoEffort = false;
310
+ else delete session.autoEffort;
311
+ }
290
312
  if (Object.hasOwn(route, 'effort')) session.effort = route.effort || null;
291
313
  ensureCodexWireSessionId(session);
292
314
  if (Object.hasOwn(route, 'modelParameters')) {
@@ -294,6 +316,8 @@ function applyRouteFields(session, route) {
294
316
  route.modelParameters && typeof route.modelParameters === 'object' ? { ...route.modelParameters } : {};
295
317
  }
296
318
  if (Object.hasOwn(route, 'contextPercent')) session.contextPercent = normalizeContextPercent(route.contextPercent);
319
+ if (Object.hasOwn(route, 'contextTokens'))
320
+ session.contextTokens = normalizeContextTokens(route.contextTokens) || null;
297
321
  if (Object.hasOwn(route, 'selectedContextWindow')) {
298
322
  session.selectedContextWindow = Number(route.selectedContextWindow) || null;
299
323
  }
@@ -441,13 +465,18 @@ function _applyDesktopResumeScope(session, options) {
441
465
  }
442
466
  // The host's summary may predate a cwd change. It supplies the
443
467
  // classification check, not permission to overwrite the session's
444
- // newer execution Project. Task metadata always remains pathless.
468
+ // newer execution Project. Task metadata always remains pathless. A Lead
469
+ // in its worktree executes outside the Project: its registered Project
470
+ // stays the workspace record's projectPath, never the worktree cwd.
471
+ const workspace = session.workspace;
472
+ const executionProject =
473
+ isWorktreeBound(workspace) && typeof workspace.projectPath === 'string' ? workspace.projectPath : session.cwd;
445
474
  session.desktopSession =
446
475
  expectedDesktop.classification === 'project'
447
476
  ? normalizeDesktopSessionMetadata(
448
477
  {
449
478
  ...expectedDesktop,
450
- projectPath: session.cwd || expectedDesktop.projectPath,
479
+ projectPath: executionProject || expectedDesktop.projectPath,
451
480
  },
452
481
  session.cwd
453
482
  )
@@ -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 }) {