mixdog 1.1.2 → 1.2.1

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 (424) hide show
  1. package/README.md +8 -5
  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 +10 -0
  7. package/src/defaults/skills/pptx/SKILL.md +6 -6
  8. package/src/defaults/skills/pptx/references/review.md +6 -4
  9. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  10. package/src/defaults/skills/setup/references/actions.md +18 -5
  11. package/src/defaults/skills/visualize/SKILL.md +160 -0
  12. package/src/defaults/skills/visualize/references/craft.md +88 -0
  13. package/src/headless-command.mjs +56 -3
  14. package/src/headless-exec.mjs +17 -1
  15. package/src/help.mjs +2 -0
  16. package/src/rules/shared/60-verification.md +3 -1
  17. package/src/rules/shared/70-delivery.md +4 -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/cache-mtime.mjs +26 -5
  22. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  23. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  24. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +19 -5
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +133 -15
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +55 -8
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +86 -8
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +474 -164
  33. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  34. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  35. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  36. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -12
  41. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  43. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  45. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  49. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  50. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +21 -6
  51. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  52. package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
  53. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  54. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  55. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  56. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  57. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  58. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  59. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  60. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  61. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +82 -0
  62. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +556 -0
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  64. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  65. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  66. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +15 -0
  67. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  68. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +15 -4
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +4 -4
  77. package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -7
  78. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  79. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  80. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -11
  81. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  82. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  83. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +278 -84
  85. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  86. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  87. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  88. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  90. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  91. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  92. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  93. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
  94. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  95. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  96. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  97. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +24 -13
  98. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  99. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  100. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  101. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  102. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  103. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  104. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  105. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  106. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  107. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  108. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  109. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  112. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  113. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  114. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +5 -3
  115. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -4
  116. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +21 -10
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  119. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
  120. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -3
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +22 -12
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +9 -3
  124. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  125. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  126. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +17 -4
  128. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  129. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  130. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  131. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  132. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  133. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  136. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  137. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  138. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +33 -22
  139. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  140. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  141. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  142. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  143. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  144. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +817 -0
  145. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +36 -0
  146. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +23 -12
  147. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  148. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +202 -374
  149. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  150. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  151. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  152. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +48 -9
  153. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +161 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  157. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  158. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  159. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  160. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  161. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  162. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +203 -33
  163. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  164. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  165. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  166. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  167. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -68
  168. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  169. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +20 -3
  170. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  171. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  175. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  176. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  177. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  179. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  180. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  181. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  182. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  183. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  184. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  185. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  186. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  187. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  188. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  189. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  190. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  191. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  192. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  193. package/src/runtime/attachments/store.d.mts +3 -0
  194. package/src/runtime/attachments/store.mjs +38 -0
  195. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  196. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  197. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  198. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  199. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  200. package/src/runtime/memory/lib/memory.mjs +1 -1
  201. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  202. package/src/runtime/shared/atomic-file.mjs +33 -6
  203. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  204. package/src/runtime/shared/code-graph-state.mjs +12 -0
  205. package/src/runtime/shared/defaults-separation.mjs +2 -0
  206. package/src/runtime/shared/extension-scopes.mjs +5 -3
  207. package/src/runtime/shared/file-lock.mjs +37 -11
  208. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  209. package/src/runtime/shared/git-runner.mjs +4 -3
  210. package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
  211. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  212. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  213. package/src/runtime/shared/llm/model-catalog.mjs +240 -130
  214. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  215. package/src/runtime/shared/llm/provider-catalog-registry.mjs +162 -0
  216. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  217. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  218. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  219. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  220. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  221. package/src/runtime/shared/pristine-execution.mjs +28 -4
  222. package/src/runtime/shared/provider-accounts.mjs +125 -21
  223. package/src/runtime/shared/provider-api-key.mjs +33 -1
  224. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  225. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  226. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  227. package/src/runtime/shared/shell-display.mjs +9 -3
  228. package/src/runtime/shared/singleton-owner.mjs +36 -6
  229. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  230. package/src/runtime/shared/skill-mentions.mjs +133 -0
  231. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  232. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  233. package/src/runtime/shared/tool-primitives.mjs +35 -2
  234. package/src/runtime/shared/tool-surface.mjs +7 -0
  235. package/src/runtime/shared/tool-work-units.mjs +14 -0
  236. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  237. package/src/runtime/shared/turn-changes.mjs +791 -0
  238. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  239. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  240. package/src/runtime/shared/turn-worktree-snapshot.mjs +185 -15
  241. package/src/runtime/shared/worker-requests.mjs +15 -2
  242. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  243. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  244. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  245. package/src/runtime/shared/worktrees/git.mjs +247 -0
  246. package/src/runtime/shared/worktrees/include.mjs +480 -0
  247. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  248. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  249. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  250. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  251. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  252. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  253. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  254. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  255. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  256. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  257. package/src/runtime/visualize-bridge/client.mjs +181 -0
  258. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  259. package/src/session-runtime/automation-agents.mjs +8 -2
  260. package/src/session-runtime/boot/apis.mjs +90 -0
  261. package/src/session-runtime/boot/config.mjs +7 -2
  262. package/src/session-runtime/boot/facade.mjs +21 -0
  263. package/src/session-runtime/boot/infrastructure.mjs +30 -18
  264. package/src/session-runtime/boot/session.mjs +1 -0
  265. package/src/session-runtime/boot/tools.mjs +4 -0
  266. package/src/session-runtime/config-lifecycle.mjs +10 -1
  267. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  268. package/src/session-runtime/conversation-import/service.mjs +369 -0
  269. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +21 -6
  270. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  271. package/src/session-runtime/global-extensions.mjs +2 -2
  272. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  273. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  274. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  275. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  276. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  277. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  278. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  279. package/src/session-runtime/mcp-glue/connect.mjs +68 -16
  280. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  281. package/src/session-runtime/mcp-glue/server-catalog.mjs +122 -33
  282. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  283. package/src/session-runtime/mcp-glue.mjs +22 -2
  284. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  285. package/src/session-runtime/model-route/set-route.mjs +15 -2
  286. package/src/session-runtime/new-session-config.mjs +3 -2
  287. package/src/session-runtime/notification-bus.mjs +18 -3
  288. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  289. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  290. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  291. package/src/session-runtime/provider-readiness.mjs +16 -10
  292. package/src/session-runtime/resource-extension-sync.mjs +45 -7
  293. package/src/session-runtime/resource-mcp-api.mjs +121 -84
  294. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  295. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  296. package/src/session-runtime/resource-skills-api.mjs +23 -2
  297. package/src/session-runtime/route-resolution.mjs +10 -1
  298. package/src/session-runtime/route-state.mjs +16 -2
  299. package/src/session-runtime/runtime-core.mjs +5 -0
  300. package/src/session-runtime/runtime-facade.mjs +8 -0
  301. package/src/session-runtime/runtime-review-api.mjs +9 -0
  302. package/src/session-runtime/schedule-session-run.mjs +1 -1
  303. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  304. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  305. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  306. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  307. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  308. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  310. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  311. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  312. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  313. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  314. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  315. package/src/session-runtime/services/agent-tool.mjs +1 -0
  316. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  317. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  318. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  319. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  320. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  321. package/src/session-runtime/services/provider-admin.mjs +64 -18
  322. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  323. package/src/session-runtime/session-create/session-options.mjs +10 -1
  324. package/src/session-runtime/session-turn-api.mjs +1 -0
  325. package/src/session-runtime/settings-api.mjs +4 -2
  326. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  327. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  328. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  329. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  330. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -1
  331. package/src/session-runtime/setup-tool/tool-defs.mjs +38 -4
  332. package/src/session-runtime/skills-api.mjs +7 -3
  333. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  334. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +14 -3
  335. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  336. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  337. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  338. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  339. package/src/session-runtime/turn/turn-run.mjs +6 -7
  340. package/src/session-runtime/webhook-session-run.mjs +16 -4
  341. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  342. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  343. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  344. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  345. package/src/session-runtime/workspace/transitions.mjs +651 -0
  346. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  347. package/src/skill-script-entry.mjs +36 -0
  348. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  349. package/src/standalone/channel-transport.mjs +13 -1
  350. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  351. package/src/standalone/daemon-owner.mjs +372 -0
  352. package/src/standalone/daemon-stored-session-views.mjs +15 -4
  353. package/src/standalone/daemon.mjs +179 -8
  354. package/src/standalone/desktop-service-registry.mjs +20 -0
  355. package/src/standalone/local-session-runtime.mjs +11 -0
  356. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  357. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  358. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  359. package/src/standalone/session-client.mjs +104 -13
  360. package/src/standalone/session-protocol.mjs +21 -0
  361. package/src/standalone/session-proxy.mjs +3 -0
  362. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  363. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  364. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  365. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  366. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  367. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  368. package/src/standalone/session-service/session-calls/session-action.mjs +17 -4
  369. package/src/standalone/session-transport/call-route.mjs +13 -0
  370. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  371. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  372. package/src/standalone/session-transport/client-registry.mjs +20 -3
  373. package/src/standalone/session-transport/routes.mjs +79 -2
  374. package/src/standalone/session-transport/stack.mjs +2 -0
  375. package/src/standalone/session-transport.mjs +24 -1
  376. package/src/tui/App.jsx +9 -1
  377. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  378. package/src/tui/app/app-view.jsx +6 -0
  379. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  380. package/src/tui/app/message-selector.mjs +2 -1
  381. package/src/tui/app/model-options.mjs +37 -5
  382. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  383. package/src/tui/app/model-picker.mjs +4 -2
  384. package/src/tui/app/skill-mention-palette.mjs +88 -0
  385. package/src/tui/app/slash-palette.mjs +23 -7
  386. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  387. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  388. package/src/tui/app/use-prompt-state.mjs +5 -0
  389. package/src/tui/components/Message.jsx +22 -1
  390. package/src/tui/components/PromptInput.jsx +21 -3
  391. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  392. package/src/tui/components/TranscriptItem.jsx +9 -1
  393. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  394. package/src/tui/components/user-message-skills.mjs +38 -0
  395. package/src/tui/dist/index.mjs +839 -207
  396. package/src/tui/index.jsx +10 -2
  397. package/src/tui/session/compaction-view.mjs +147 -0
  398. package/src/tui/session/context-state.mjs +6 -0
  399. package/src/tui/session/initial-state.mjs +3 -0
  400. package/src/tui/session/queue-helpers.mjs +7 -2
  401. package/src/tui/session/quota-resume.mjs +2 -1
  402. package/src/tui/session/session-api/commands.mjs +10 -2
  403. package/src/tui/session/session-api/controls.mjs +4 -0
  404. package/src/tui/session/session-api/extensions.mjs +4 -2
  405. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  406. package/src/tui/session/session-api/integrations.mjs +17 -0
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  408. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  409. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  410. package/src/tui/session/session-api/routes.mjs +10 -1
  411. package/src/tui/session/session-api/settings.mjs +9 -0
  412. package/src/tui/session/session-api/worktree.mjs +60 -0
  413. package/src/tui/session/session-api.mjs +2 -0
  414. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  415. package/src/tui/session/session-flow/drain.mjs +27 -3
  416. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  417. package/src/tui/session/session-flow.mjs +2 -0
  418. package/src/tui/session/transcript-intake.mjs +14 -5
  419. package/src/tui/session/tui-steering-persist.mjs +25 -9
  420. package/src/tui/session/turn-stream.mjs +32 -4
  421. package/src/tui/session/turn-tool-cards.mjs +10 -2
  422. package/src/tui/session/turn.mjs +56 -10
  423. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  424. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
@@ -2,13 +2,17 @@
2
2
  // construction helpers.
3
3
  import { SUMMARY_PREFIX, SUMMARY_PREFIX_ANCHOR } from './constants.mjs';
4
4
  import { sha16, roleCounts } from './text-utils.mjs';
5
+ import { carryMessageId, withoutMessageIdsReplacer } from '../message-ids.mjs';
6
+ import { isImportedMessage } from '../imported-message.mjs';
5
7
  import {
6
8
  isInternalRuntimeNotificationText,
7
9
  isModelVisibleToolCompletionWrapper,
8
10
  } from '../../../../shared/tool-execution-contract.mjs';
9
11
 
10
12
  export function compactHeader(oldHistory) {
11
- const encoded = JSON.stringify(oldHistory ?? []);
13
+ // Stable display ids are bookkeeping, not conversation: the header names
14
+ // the same history whether or not its messages were stamped.
15
+ const encoded = JSON.stringify(oldHistory ?? [], withoutMessageIdsReplacer);
12
16
  return [
13
17
  SUMMARY_PREFIX,
14
18
  `messages=${oldHistory.length} sha256=${sha16(encoded)} roles=${roleCounts(oldHistory) || 'none'}`,
@@ -27,6 +31,7 @@ export function makeSummaryMessage(content) {
27
31
  export function isSummaryMessage(m) {
28
32
  return (
29
33
  m?.role === 'user' &&
34
+ !isImportedMessage(m) &&
30
35
  (String(m?.meta?.source || '') === 'compact-summary' ||
31
36
  (typeof m.content === 'string' &&
32
37
  m.content.startsWith(SUMMARY_PREFIX_ANCHOR) &&
@@ -35,7 +40,7 @@ export function isSummaryMessage(m) {
35
40
  }
36
41
 
37
42
  export function isProtectedContextUserMessage(m) {
38
- if (m?.role !== 'user' || typeof m.content !== 'string') return false;
43
+ if (m?.role !== 'user' || typeof m.content !== 'string' || isImportedMessage(m)) return false;
39
44
  let content = m.content.trim();
40
45
  if (!content.toLowerCase().startsWith('<system-reminder>')) return false;
41
46
  const closingTag = '</system-reminder>';
@@ -59,7 +64,7 @@ export function isProtectedContextUserMessage(m) {
59
64
  // "latest human request" selection (deriveCurrentRequest). The `meta:'skill'`
60
65
  // marker is also honoured.
61
66
  export function isInjectedSkillBodyMessage(m) {
62
- if (m?.role !== 'user') return false;
67
+ if (m?.role !== 'user' || isImportedMessage(m)) return false;
63
68
  if (m.meta === 'skill') return true;
64
69
  return typeof m.content === 'string' && m.content.trimStart().startsWith('<skill>');
65
70
  }
@@ -93,6 +98,8 @@ export function isActualUserInstructionMessage(message) {
93
98
  return false;
94
99
  }
95
100
  const text = userMessageText(message);
101
+ // Imported prose is the user's own words whatever it looks like.
102
+ if (isImportedMessage(message)) return text.trim().length > 0;
96
103
  if (/^Reference files:\s*/i.test(text.trimStart())) return false;
97
104
  if (
98
105
  SYNTHETIC_USER_CONTROL_RE.test(text.trimStart()) ||
@@ -130,16 +137,20 @@ function referenceFilesManifestMessage(message) {
130
137
  const paths = [...text.matchAll(/^###\s+(.+?)\s*$/gm)].map((match) => String(match[1] || '').trim()).filter(Boolean);
131
138
  if (paths.length === 0) return null;
132
139
  const uniquePaths = [...new Set(paths)];
133
- return {
134
- role: 'user',
135
- content: [
136
- '<system-reminder>',
137
- '# Reference files',
138
- 'These files were attached when the session started. Re-read a path when its current contents are needed; the original bodies are not re-injected after Compact.',
139
- ...uniquePaths.map((path) => `- ${path}`),
140
- '</system-reminder>',
141
- ].join('\n'),
142
- };
140
+ // The manifest rewrites the reference-files message: it keeps that id.
141
+ return carryMessageId(
142
+ {
143
+ role: 'user',
144
+ content: [
145
+ '<system-reminder>',
146
+ '# Reference files',
147
+ 'These files were attached when the session started. Re-read a path when its current contents are needed; the original bodies are not re-injected after Compact.',
148
+ ...uniquePaths.map((path) => `- ${path}`),
149
+ '</system-reminder>',
150
+ ].join('\n'),
151
+ },
152
+ message
153
+ );
143
154
  }
144
155
 
145
156
  export function splitProtectedContext(messages) {
@@ -20,6 +20,7 @@ import { buildExecutionTail } from './execution-tail.mjs';
20
20
  import { latestSkillBodies } from '../../context/skill-state.mjs';
21
21
  import { effectiveBudget } from './budget.mjs';
22
22
  import { stripRuntimeUserContext, withRuntimeUserContext } from '../runtime-user-context.mjs';
23
+ import { carryMessageId } from '../message-ids.mjs';
23
24
  import { normalizeIngestRole, shouldExcludeIngestMessage } from '../../../../memory/lib/session-ingest.mjs';
24
25
  import { codexWireSendOpts } from '../manager/session-id.mjs';
25
26
  import { rebaseCompactedEffortConfiguration } from '../../providers/effort-configuration.mjs';
@@ -380,7 +381,8 @@ function skillLoaderPair(live, bodyIndex, name, retainedCallIds) {
380
381
  const call = owner?.toolCalls.find((entry) => entry.id === result.toolCallId);
381
382
  if (call?.name !== 'Skill' || call.arguments?.name !== name) continue;
382
383
  if (!result.nativeToolSearch || retainedCallIds.has(result.toolCallId)) return [];
383
- return [{ role: 'assistant', content: '', toolCalls: [call] }, result];
384
+ // The re-attached call is part of its owner message: it keeps that id.
385
+ return [carryMessageId({ role: 'assistant', content: '', toolCalls: [call] }, owner), result];
384
386
  }
385
387
  return [];
386
388
  }
@@ -549,11 +551,17 @@ function assertHandoffFits(summaryMessage, room) {
549
551
  export function freshContextCompactMessages(messages, budgetTokens, opts = {}) {
550
552
  const startedAt = Date.now();
551
553
  const targetBudget = effectiveBudget(budgetTokens, opts);
552
- let budget = targetBudget;
553
554
  const baseSanitized = reconcileDedupStubs(sanitizeToolPairs(messages));
554
555
  const baseTokens = safeEstimateMessagesTokens(baseSanitized);
555
- if (baseTokens != null && baseTokens <= budget && opts.force !== true) {
556
- return fitsBudgetResult({ startedAt, messages, baseSanitized, baseTokens, budget, query: opts.query || '' });
556
+ if (baseTokens != null && baseTokens <= targetBudget && opts.force !== true) {
557
+ return fitsBudgetResult({
558
+ startedAt,
559
+ messages,
560
+ baseSanitized,
561
+ baseTokens,
562
+ budget: targetBudget,
563
+ query: opts.query || '',
564
+ });
557
565
  }
558
566
 
559
567
  const source = splitFreshSource(baseSanitized);
@@ -562,7 +570,7 @@ export function freshContextCompactMessages(messages, budgetTokens, opts = {}) {
562
570
  budgetTokens,
563
571
  opts
564
572
  );
565
- budget = freshContextBudget({ targetBudget, budgetTokens, mandatoryCost, summaryMessage, opts });
573
+ const budget = freshContextBudget({ targetBudget, budgetTokens, mandatoryCost, summaryMessage, opts });
566
574
  assertMandatoryFits(budget, mandatoryCost, summaryMessage, opts.contextWindow);
567
575
  const handoff = handoffRoomFor(budget, mandatoryCost, opts);
568
576
  const summaryContent = String(summaryMessage?.content || '');
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Capture side of the display-only compaction archive (compaction-archive.mjs).
3
+ *
4
+ * A committed compaction hands over the transcript it replaced and the one
5
+ * that replaced it. What left the model context — the messages whose stable
6
+ * id (message-ids.mjs) the result no longer holds, and the lost parts of kept
7
+ * messages (compactionArchiveProjection) — is projected to transcript items
8
+ * exactly like stored history
9
+ * (restoreTranscriptItems over media-sanitized messages, so tool cards and
10
+ * diffs survive), namespaced as `arch<seq>_…` with `archived: true`, and
11
+ * appended as ONE segment to the session's sidecar, followed by the
12
+ * compaction divider.
13
+ *
14
+ * The commit only selects the dropped messages and allocates the segment
15
+ * number (`session.compaction.archive.nextSeq`, saved with the commit); the
16
+ * projection and the write run on a later event-loop turn, serialized per
17
+ * session and fenced against a delete. Nothing here touches
18
+ * `session.messages`.
19
+ */
20
+ import { randomBytes } from 'node:crypto';
21
+ import { existsSync, unlinkSync } from 'node:fs';
22
+ import { open, readFile } from 'node:fs/promises';
23
+ import { basename, join } from 'node:path';
24
+ import { renameWithRetrySync } from '../../../shared/atomic-file.mjs';
25
+ import { writeStderrBestEffort } from '../../../shared/best-effort-stderr.mjs';
26
+
27
+ import { sanitizeContentForStoredHistory } from '../providers/media-normalization.mjs';
28
+ import { getStoreDir } from './store/paths-heartbeat.mjs';
29
+ import { forgetSessionSaveBaseline } from './store/save-worker.mjs';
30
+ import { messageIdOf, stampMessageIds } from './message-ids.mjs';
31
+ import { restoreTranscriptItems } from './transcript-restore/restore.mjs';
32
+ import {
33
+ COMPACTION_ARCHIVE_MAX_BYTES,
34
+ COMPACTION_DIVIDER_LABEL,
35
+ compactionArchiveDeleteEpoch,
36
+ compactionArchivePath,
37
+ compactionSegmentHead,
38
+ compactionSegmentLine,
39
+ } from './compaction-archive.mjs';
40
+
41
+ export { COMPACTION_DIVIDER_LABEL };
42
+
43
+ const pendingWrites = new Map();
44
+
45
+ // ── Dropped messages: by stable id only ──────────────────────────────────────
46
+
47
+ function toolCallsOf(message) {
48
+ const calls = Array.isArray(message.toolCalls) ? message.toolCalls : message.tool_calls;
49
+ return Array.isArray(calls) ? calls : [];
50
+ }
51
+
52
+ function callIdOf(call) {
53
+ return String(call?.id || '');
54
+ }
55
+
56
+ function resultCallIdOf(message) {
57
+ return message.role === 'tool' ? String(message.tool_call_id || message.toolCallId || '') : '';
58
+ }
59
+
60
+ function hasText(content) {
61
+ if (typeof content === 'string') return content.trim().length > 0;
62
+ return Array.isArray(content) && content.some((part) => typeof part?.text === 'string' && part.text.trim());
63
+ }
64
+
65
+ /** The stable ids `messages` carries (stampMessageIds / carryMessageId). */
66
+ function idsOf(messages) {
67
+ const ids = new Set();
68
+ for (const message of messages) {
69
+ const id = messageIdOf(message);
70
+ if (id) ids.add(id);
71
+ }
72
+ return ids;
73
+ }
74
+
75
+ /**
76
+ * Messages of `before` whose stable id the compacted `after` no longer holds,
77
+ * in their original order. Identity is the id alone — never an object
78
+ * reference, a position or content. A message without an id (a string-meta
79
+ * context marker, see message-ids.mjs) has no display identity and is never
80
+ * reported.
81
+ */
82
+ export function droppedByCompaction(before, after) {
83
+ const kept = idsOf(Array.isArray(after) ? after : []);
84
+ return (Array.isArray(before) ? before : []).filter((message) => {
85
+ const id = messageIdOf(message);
86
+ return id && !kept.has(id);
87
+ });
88
+ }
89
+
90
+ /**
91
+ * What a compaction took out of the model context, in `before` order, for the
92
+ * display archive: every dropped message, plus — for a kept message whose
93
+ * rewrite lost part of it — the lost part (tool calls no copy still carries,
94
+ * text when no copy has any) as a message of the same id. The tool results of
95
+ * those calls are dropped messages, so their cards restore whole.
96
+ * `textIds` are the ids whose text rows left the context: the dropped
97
+ * messages and the kept ones that lost all their text.
98
+ */
99
+ export function compactionArchiveProjection(before, after) {
100
+ const copiesById = new Map();
101
+ for (const message of Array.isArray(after) ? after : []) {
102
+ const id = messageIdOf(message);
103
+ if (!id) continue;
104
+ if (!copiesById.has(id)) copiesById.set(id, []);
105
+ copiesById.get(id).push(message);
106
+ }
107
+ const messages = [];
108
+ const textIds = [];
109
+ for (const message of Array.isArray(before) ? before : []) {
110
+ const id = messageIdOf(message);
111
+ if (!id) continue;
112
+ const copies = copiesById.get(id);
113
+ if (!copies) {
114
+ messages.push(message);
115
+ textIds.push(id);
116
+ continue;
117
+ }
118
+ const keptCalls = new Set(copies.flatMap((copy) => toolCallsOf(copy).map(callIdOf)));
119
+ const lostCalls = toolCallsOf(message).filter((call) => callIdOf(call) && !keptCalls.has(callIdOf(call)));
120
+ const lostText = hasText(message.content) && !copies.some((copy) => hasText(copy.content));
121
+ if (!lostCalls.length && !lostText) continue;
122
+ if (lostText) textIds.push(id);
123
+ const { toolCalls: _calls, tool_calls: _wireCalls, ...rest } = message;
124
+ messages.push({
125
+ ...rest,
126
+ content: lostText ? message.content : '',
127
+ ...(lostCalls.length ? { toolCalls: lostCalls } : {}),
128
+ });
129
+ }
130
+ return { messages, textIds };
131
+ }
132
+
133
+ /** What a live view needs to find the rows of what left the context: the
134
+ * stable ids whose text rows went (projection `textIds`) and the tool calls
135
+ * that went (dropped results and calls no kept copy carries). */
136
+ export function droppedIdentitySummary(projection) {
137
+ const callIds = new Set();
138
+ for (const message of projection.messages) {
139
+ for (const call of toolCallsOf(message)) if (callIdOf(call)) callIds.add(callIdOf(call));
140
+ const resultId = resultCallIdOf(message);
141
+ if (resultId) callIds.add(resultId);
142
+ }
143
+ return { messageIds: [...new Set(projection.textIds)], callIds: [...callIds] };
144
+ }
145
+
146
+ /** Stamp stable ids on the transcript a compaction is about to run on (or a
147
+ * resumed session restores rows from) and make sure they are persisted: the
148
+ * next save goes out whole instead of as a delta that would leave the
149
+ * stamped, already-saved messages unchanged on disk. Returns the count. */
150
+ export function stampCompactionMessageIds(messages, sessionId) {
151
+ const stamped = stampMessageIds(messages, { sessionId });
152
+ if (stamped && sessionId) forgetSessionSaveBaseline(String(sessionId));
153
+ return stamped;
154
+ }
155
+ // ── Segment projection and publication ───────────────────────────────────────
156
+
157
+ function storedHistoryMessage(message) {
158
+ const content = sanitizeContentForStoredHistory(message.content);
159
+ return content === message.content ? message : { ...message, content };
160
+ }
161
+
162
+ /** The display items of one segment: what left the context
163
+ * (compactionArchiveProjection messages) as stored history restores it,
164
+ * namespaced and marked archived, then the divider. Empty when nothing
165
+ * visible left. */
166
+ export function compactionSegmentItems(messages, { sessionId, seq, at }) {
167
+ const restored = restoreTranscriptItems(messages.map(storedHistoryMessage), { sessionId });
168
+ if (!restored.length) return [];
169
+ const prefix = `hist_${sessionId}_`;
170
+ const items = restored.map((item) => {
171
+ const id = String(item?.id ?? '');
172
+ return { ...item, id: `arch${seq}_${id.startsWith(prefix) ? id.slice(prefix.length) : id}`, archived: true };
173
+ });
174
+ items.push({
175
+ kind: 'statusdone',
176
+ id: `arch${seq}_divider`,
177
+ status: 'compacted',
178
+ label: COMPACTION_DIVIDER_LABEL,
179
+ at,
180
+ });
181
+ return items;
182
+ }
183
+
184
+ function itemBytes(item) {
185
+ return Buffer.byteLength(JSON.stringify(item));
186
+ }
187
+
188
+ /** A segment line whose bytes plus its trailing newline fit `maxBytes`: a
189
+ * segment that alone exceeds the cap keeps its newest items (and the
190
+ * divider). `bytes` includes the newline. */
191
+ function cappedSegmentLine({ seq, prev, at, items }, maxBytes) {
192
+ let line = compactionSegmentLine({ seq, prev, at, items });
193
+ let bytes = Buffer.byteLength(line) + 1;
194
+ if (bytes <= maxBytes) return { line, bytes };
195
+ const sizes = items.map(itemBytes);
196
+ let start = 0;
197
+ while (bytes > maxBytes && start < items.length - 1) {
198
+ bytes -= sizes[start] + 1;
199
+ start += 1;
200
+ }
201
+ line = compactionSegmentLine({ seq, prev, at, items: items.slice(start) });
202
+ return { line, bytes: Buffer.byteLength(line) + 1 };
203
+ }
204
+
205
+ let publishHook = null;
206
+ /** Test seam: runs after a segment's temp file is written, right before the
207
+ * fenced publication. */
208
+ export function _setCompactionArchivePublishHook(hook) {
209
+ publishHook = typeof hook === 'function' ? hook : null;
210
+ }
211
+
212
+ let syncFile = (handle) => handle.sync();
213
+ /** Test seam: replaces the temp file's fsync (null restores it). */
214
+ export function _setCompactionArchiveFsync(sync) {
215
+ syncFile = typeof sync === 'function' ? sync : (handle) => handle.sync();
216
+ }
217
+
218
+ const recordExists = (dir, sessionId) => existsSync(join(dir, `${sessionId}.json`));
219
+
220
+ /** Write `text` to a private temp file and fsync it — a failed write or
221
+ * fsync fails the publication — then publish it with a synchronous fence
222
+ * check + rename: a delete that bumped the epoch (or removed the record)
223
+ * since `epoch` was captured wins. Any outcome but a rename removes the temp
224
+ * file. */
225
+ async function publishArchive({ dir, path, sessionId, text, epoch }) {
226
+ const tmp = join(dir, `.${basename(path)}.${randomBytes(8).toString('hex')}.tmp`);
227
+ const removeTmp = () => {
228
+ try {
229
+ unlinkSync(tmp);
230
+ } catch {
231
+ /* already gone */
232
+ }
233
+ };
234
+ try {
235
+ const handle = await open(tmp, 'wx', 0o600);
236
+ try {
237
+ await handle.writeFile(text, 'utf8');
238
+ await syncFile(handle);
239
+ } finally {
240
+ await handle.close();
241
+ }
242
+ if (publishHook) await publishHook({ sessionId, path, tmp });
243
+ } catch (error) {
244
+ removeTmp();
245
+ throw error;
246
+ }
247
+ // No await between the fence and the rename.
248
+ if (compactionArchiveDeleteEpoch(sessionId) !== epoch || !recordExists(dir, sessionId)) {
249
+ removeTmp();
250
+ return false;
251
+ }
252
+ try {
253
+ renameWithRetrySync(tmp, path);
254
+ } catch (error) {
255
+ removeTmp();
256
+ throw error;
257
+ }
258
+ return true;
259
+ }
260
+
261
+ async function appendSegment({ sessionId, seq, at, items, clearedThrough, maxBytes, epoch }) {
262
+ const dir = getStoreDir();
263
+ const path = compactionArchivePath(dir, sessionId);
264
+ // A deleted session must not regrow its archive.
265
+ if (!path || compactionArchiveDeleteEpoch(sessionId) !== epoch || !recordExists(dir, sessionId)) return null;
266
+ let text = '';
267
+ try {
268
+ text = await readFile(path, 'utf8');
269
+ } catch {
270
+ text = '';
271
+ }
272
+ const existing = [];
273
+ for (const line of text.split('\n')) {
274
+ const head = compactionSegmentHead(line);
275
+ // Segments a clear hid are dropped on the next write.
276
+ if (head && head.seq > clearedThrough && head.seq !== seq) existing.push({ line, head });
277
+ }
278
+ const prev = existing.length ? existing.at(-1).head.seq : null;
279
+ const segment = cappedSegmentLine({ seq, prev, at, items }, maxBytes);
280
+ let total = segment.bytes;
281
+ for (const entry of existing) total += Buffer.byteLength(entry.line) + 1;
282
+ while (existing.length && total > maxBytes) {
283
+ total -= Buffer.byteLength(existing.shift().line) + 1;
284
+ }
285
+ const lines = [...existing.map((entry) => entry.line), segment.line];
286
+ if (!(await publishArchive({ dir, path, sessionId, text: `${lines.join('\n')}\n`, epoch }))) return null;
287
+ return lines.map((line) => compactionSegmentHead(line)).filter(Boolean);
288
+ }
289
+
290
+ /**
291
+ * Archive what a committed compaction dropped. Synchronous part: select the
292
+ * dropped messages, capture the delete fence and allocate the segment number
293
+ * on `session.compaction.archive`; the rest runs later. Returns
294
+ * `{ dropped, identities, written }` — the dropped message count, their
295
+ * identities for a live view (droppedIdentitySummary) and the pending write
296
+ * (resolves to the retained segment list) — or null when nothing visible was
297
+ * dropped (a rewrite-only compaction).
298
+ */
299
+ export function scheduleCompactionArchive({
300
+ session,
301
+ sessionId = null,
302
+ before,
303
+ after,
304
+ at = Date.now(),
305
+ maxBytes = COMPACTION_ARCHIVE_MAX_BYTES,
306
+ }) {
307
+ const id = String(sessionId || session?.id || '');
308
+ if (!session || typeof session !== 'object' || !compactionArchivePath('', id)) return null;
309
+ const projection = compactionArchiveProjection(before, after);
310
+ // Only what the transcript shows is archived; a pass that took out nothing
311
+ // visible (a rewrite, control rows, hidden context) leaves the view live.
312
+ // Short-circuits on the first visible message.
313
+ if (!projection.messages.some((message) => restoreTranscriptItems([message], { sessionId: id }).length > 0)) {
314
+ return null;
315
+ }
316
+ const dropped = droppedByCompaction(before, after);
317
+ if (!session.compaction || typeof session.compaction !== 'object') session.compaction = {};
318
+ const previous =
319
+ session.compaction.archive && typeof session.compaction.archive === 'object' ? session.compaction.archive : {};
320
+ const seq = Math.max(1, Math.floor(Number(previous.nextSeq) || 1));
321
+ const clearedThrough = Math.max(0, Math.floor(Number(previous.clearedThrough) || 0));
322
+ const epoch = compactionArchiveDeleteEpoch(id);
323
+ session.compaction.archive = { ...previous, nextSeq: seq + 1 };
324
+ const run = async () => {
325
+ await new Promise((resolve) => setImmediate(resolve));
326
+ const items = compactionSegmentItems(projection.messages, { sessionId: id, seq, at });
327
+ if (!items.length) return null;
328
+ const segments = await appendSegment({ sessionId: id, seq, at, items, clearedThrough, maxBytes, epoch });
329
+ const archive = session.compaction?.archive;
330
+ // Small metadata only; a later clear may have hidden this segment.
331
+ if (segments && archive && typeof archive === 'object' && !(Number(archive.clearedThrough) >= seq)) {
332
+ archive.segments = segments;
333
+ }
334
+ return segments;
335
+ };
336
+ const task = (pendingWrites.get(id) || Promise.resolve()).then(run).catch((error) => {
337
+ writeStderrBestEffort(`[session] compaction archive write failed (sess=${id}): ${error?.message || error}\n`);
338
+ return null;
339
+ });
340
+ pendingWrites.set(id, task);
341
+ void task.finally(() => {
342
+ if (pendingWrites.get(id) === task) pendingWrites.delete(id);
343
+ });
344
+ return { dropped: dropped.length, identities: droppedIdentitySummary(projection), written: task };
345
+ }
346
+
347
+ /** Hide everything archived so far (a clear): later reads and the next write
348
+ * skip those segments. */
349
+ export function hideCompactionArchive(session) {
350
+ const archive = session?.compaction?.archive;
351
+ if (!archive || typeof archive !== 'object') return;
352
+ session.compaction.archive = {
353
+ ...archive,
354
+ clearedThrough: Math.max(0, Math.floor(Number(archive.nextSeq) || 1) - 1),
355
+ segments: [],
356
+ };
357
+ }
358
+
359
+ /** Settles once every archive write scheduled so far has finished. */
360
+ export async function flushCompactionArchiveWrites() {
361
+ await Promise.all([...pendingWrites.values()]);
362
+ }