mixdog 1.4.2 → 1.4.3

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 (417) hide show
  1. package/package.json +2 -2
  2. package/scripts/build-shipped-history.mjs +15 -5
  3. package/scripts/gate-local.mjs +65 -19
  4. package/scripts/graph-parity.mjs +8 -3
  5. package/scripts/lib/gate-scheduler.mjs +2 -1
  6. package/scripts/lib/run-node-tests.mjs +17 -5
  7. package/scripts/lib/test-failure-summary.mjs +1 -8
  8. package/scripts/lib/test-spawn-ledger.mjs +33 -2
  9. package/scripts/post-release-check.mjs +26 -16
  10. package/scripts/provider-pingpong-live.mjs +1 -1
  11. package/scripts/prune-embedding-runtime.mjs +3 -1
  12. package/scripts/release-paths.mjs +7 -1
  13. package/src/defaults/shipped-history.json +20 -0
  14. package/src/defaults/skills/setup/SKILL.md +2 -2
  15. package/src/defaults/skills/setup/references/actions.md +16 -3
  16. package/src/defaults/skills/setup/references/surfaces.md +3 -0
  17. package/src/defaults/skills/setup-guide/SKILL.md +6 -1
  18. package/src/lib/instruction-text.cjs +36 -0
  19. package/src/lib/rules-builder.cjs +34 -20
  20. package/src/orchestration/balanced.md +23 -0
  21. package/src/orchestration/focused.md +22 -0
  22. package/src/orchestration/none.md +5 -0
  23. package/src/orchestration/swarm.md +24 -0
  24. package/src/rules/agent-mode/AGENT_MODE.md +3 -1
  25. package/src/rules/agent-mode/MIXY.md +2 -2
  26. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +3 -3
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +0 -1
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +4 -2
  29. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +0 -9
  30. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +2 -2
  31. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +67 -0
  32. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +1 -0
  33. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +4 -4
  34. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -1
  35. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +199 -46
  36. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +3 -5
  37. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +9 -3
  38. package/src/runtime/agent/orchestrator/mcp/client.mjs +3 -5
  39. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +8 -2
  41. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +1 -1
  42. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +14 -0
  43. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-stop.mjs +3 -3
  46. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +2 -2
  48. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +4 -6
  49. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2784 -2872
  50. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +5 -8
  51. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +288 -300
  52. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +10 -1
  53. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +10 -30
  54. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +30 -0
  55. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +0 -11
  56. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +8 -0
  57. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +17 -3
  58. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +47 -27
  59. package/src/runtime/agent/orchestrator/runtime-core/goal-deadlines.mjs +141 -10
  60. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +26 -63
  61. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +28 -3
  62. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +36 -11
  63. package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +88 -56
  64. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +4 -1
  65. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +51 -32
  66. package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +45 -145
  67. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +15 -98
  68. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +32 -99
  69. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +23 -78
  70. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +69 -48
  71. package/src/runtime/agent/orchestrator/runtime-core/todo-list.mjs +367 -0
  72. package/src/runtime/agent/orchestrator/runtime-core/todo-tool-defs.mjs +47 -0
  73. package/src/runtime/agent/orchestrator/runtime-core/tool-availability.mjs +155 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +9 -0
  75. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +119 -35
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +28 -22
  79. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +30 -0
  80. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +1 -1
  81. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +2 -2
  82. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -2
  83. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +1 -1
  84. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -1
  85. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +3 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +1 -1
  87. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -12
  88. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +7 -3
  92. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +1 -1
  93. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +1 -2
  94. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +1 -1
  95. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +11 -1
  97. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
  99. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/todo-check.mjs +196 -0
  100. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +116 -50
  101. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +8 -1
  102. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +1 -1
  104. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -1
  105. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +5 -6
  106. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +6 -1
  107. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  108. package/src/runtime/agent/orchestrator/session/loop/turn-report.mjs +35 -0
  109. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +57 -26
  111. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +9 -7
  112. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +21 -3
  113. package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +27 -11
  114. package/src/runtime/agent/orchestrator/session/manager/ask-turn-open.mjs +37 -34
  115. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +15 -0
  116. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +25 -15
  117. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +79 -24
  118. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +3 -0
  119. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +12 -1
  120. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +1 -1
  121. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +6 -1
  122. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +9 -1
  123. package/src/runtime/agent/orchestrator/session/manager/pending-spool-path.mjs +2 -2
  124. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +1 -1
  125. package/src/runtime/agent/orchestrator/session/manager/prompt-settings.mjs +598 -0
  126. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +19 -0
  127. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +19 -10
  128. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +5 -0
  129. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +1 -1
  130. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +31 -0
  131. package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +31 -0
  132. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +42 -6
  133. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +2 -0
  134. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +11 -1
  135. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +8 -9
  136. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +339 -88
  137. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +135 -11
  138. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +377 -104
  139. package/src/runtime/agent/orchestrator/session/manager/turn-durability.mjs +144 -0
  140. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +1 -1
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +4 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +6 -0
  143. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +8 -1
  145. package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +5 -1
  146. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +2 -2
  148. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +23 -2
  149. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +3 -3
  150. package/src/runtime/agent/orchestrator/session/store/transcript-durability.mjs +88 -0
  151. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +5 -0
  152. package/src/runtime/agent/orchestrator/session/store-summary-log.mjs +1 -1
  153. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +14 -5
  154. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +2 -2
  155. package/src/runtime/agent/orchestrator/session/store.mjs +5 -0
  156. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +7 -1
  157. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +9 -1
  158. package/src/runtime/agent/orchestrator/session/transcript-restore/agent-envelope.mjs +1 -1
  159. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +3 -1
  160. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +2 -2
  161. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -5
  162. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -1
  163. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  165. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/worktree-add-guard.mjs +77 -0
  166. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -1
  167. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +7 -3
  168. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +27 -9
  169. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +2 -2
  170. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +1 -1
  171. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +0 -1
  172. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +0 -10
  173. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1 -2
  174. package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -24
  175. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary/child-kill.mjs +4 -3
  176. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +1 -2
  177. package/src/runtime/agent/orchestrator/tools/code-graph/modes/calls.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/tools/code-graph/modes/symbols.mjs +2 -2
  179. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +4 -5
  180. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +3 -4
  181. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4 -22
  182. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +2 -2
  183. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +1 -1
  184. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -4
  185. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +0 -1
  186. package/src/runtime/attachments/attachment-reference.mjs +1 -1
  187. package/src/runtime/attachments/pdf-extract.mjs +1 -1
  188. package/src/runtime/attachments/store.d.mts +1 -0
  189. package/src/runtime/attachments/store.mjs +2 -2
  190. package/src/runtime/channels/lib/config-reload-watch.mjs +42 -0
  191. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +29 -9
  192. package/src/runtime/channels/lib/scheduler.mjs +33 -0
  193. package/src/runtime/channels/lib/schedules-db.mjs +11 -4
  194. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +4 -1
  195. package/src/runtime/channels/lib/webhooks-db.mjs +9 -3
  196. package/src/runtime/channels/lib/whisper-server.mjs +12 -12
  197. package/src/runtime/channels/lib/worker-main.mjs +9 -31
  198. package/src/runtime/computer-bridge/linux-readiness.mjs +10 -14
  199. package/src/runtime/connectors/connector-accounts-mcp.mjs +169 -33
  200. package/src/runtime/connectors/gmail-app-password.mjs +2 -2
  201. package/src/runtime/connectors/google-mcp.mjs +1 -1
  202. package/src/runtime/effort-judge/judge-client.mjs +2 -2
  203. package/src/runtime/effort-judge/judge-input.mjs +4 -4
  204. package/src/runtime/explore/explorer.mjs +0 -5
  205. package/src/runtime/explore/tool.mjs +3 -3
  206. package/src/runtime/local-provider/hardware.mjs +1 -1
  207. package/src/runtime/media/adapters/codex-image.mjs +3 -3
  208. package/src/runtime/media/adapters/gemini-video.mjs +1 -1
  209. package/src/runtime/media/media-usage.mjs +1 -1
  210. package/src/runtime/media/store.mjs +1 -1
  211. package/src/runtime/memory/index.mjs +11 -1
  212. package/src/runtime/memory/lib/embedding-reindex.mjs +27 -0
  213. package/src/runtime/memory/lib/memory-embed.mjs +83 -14
  214. package/src/runtime/memory/lib/memory-process-lock.mjs +159 -36
  215. package/src/runtime/memory/lib/memory-text-utils.mjs +6 -1
  216. package/src/runtime/memory/lib/memory.mjs +17 -0
  217. package/src/runtime/memory/lib/pg/process.mjs +1 -1
  218. package/src/runtime/memory/lib/pg/supervisor.mjs +16 -96
  219. package/src/runtime/memory/lib/recall-hybrid/hybrid-sql.mjs +12 -3
  220. package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
  221. package/src/runtime/office/authoring/pptx-author-action.mjs +1 -1
  222. package/src/runtime/office/authoring/xlsx-html-build.mjs +1 -1
  223. package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
  224. package/src/runtime/office/capabilities.mjs +1 -1
  225. package/src/runtime/office/core/office-core.mjs +1 -1
  226. package/src/runtime/office/design/content-model.mjs +3 -3
  227. package/src/runtime/office/design/docx/design-docx/section.mjs +6 -6
  228. package/src/runtime/office/design/xlsx/design-xlsx-components.mjs +1 -1
  229. package/src/runtime/office/design/xlsx/design-xlsx.mjs +1 -1
  230. package/src/runtime/office/pdf/ocr-engine.mjs +1 -1
  231. package/src/runtime/office/pdf/pdf-writer.mjs +4 -4
  232. package/src/runtime/office/portable/portable-docx-operations.mjs +2 -2
  233. package/src/runtime/office/portable/portable-soffice.mjs +8 -6
  234. package/src/runtime/office/portable/xlsx-contract.mjs +1 -1
  235. package/src/runtime/shared/atomic-file.d.mts +8 -0
  236. package/src/runtime/shared/background-tasks.mjs +22 -4
  237. package/src/runtime/shared/background-wait.mjs +66 -0
  238. package/src/runtime/shared/child-guardian.mjs +17 -7
  239. package/src/runtime/shared/config.mjs +313 -16
  240. package/src/runtime/shared/connector-catalog.d.mts +4 -5
  241. package/src/runtime/shared/connector-catalog.mjs +11 -1
  242. package/src/runtime/shared/file-lock.mjs +8 -3
  243. package/src/runtime/shared/git-runner.mjs +7 -2
  244. package/src/runtime/shared/idle-gc.mjs +30 -7
  245. package/src/runtime/shared/llm/http-agent.mjs +8 -5
  246. package/src/runtime/shared/llm/system-proxy.mjs +2 -1
  247. package/src/runtime/shared/llm/usage-ledger-quota.mjs +31 -12
  248. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +84 -8
  249. package/src/runtime/shared/llm/usage-ledger.mjs +3 -0
  250. package/src/runtime/shared/llm/usage-rollup.mjs +48 -0
  251. package/src/runtime/shared/maintenance-schedule.mjs +109 -0
  252. package/src/runtime/shared/process-runner.mjs +8 -4
  253. package/src/runtime/shared/routine-trigger.d.mts +13 -0
  254. package/src/runtime/shared/routine-trigger.mjs +34 -0
  255. package/src/runtime/shared/run-child.d.mts +2 -1
  256. package/src/runtime/shared/run-child.mjs +70 -18
  257. package/src/runtime/shared/session-scratch.mjs +14 -2
  258. package/src/runtime/shared/shipped-definitions.mjs +7 -1
  259. package/src/runtime/shared/tool-card-model.golden.json +3 -12
  260. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  261. package/src/runtime/shared/tool-work-units.mjs +31 -6
  262. package/src/runtime/shared/turn-snapshot.mjs +26 -4
  263. package/src/runtime/shared/turn-worktree-snapshot.mjs +6 -11
  264. package/src/runtime/shared/worker-requests.mjs +17 -3
  265. package/src/runtime/shared/worktrees/branch-management.mjs +1 -5
  266. package/src/runtime/shared/worktrees/contract.mjs +16 -6
  267. package/src/runtime/shared/worktrees/dependencies.mjs +127 -89
  268. package/src/runtime/shared/worktrees/disk.mjs +79 -2
  269. package/src/runtime/shared/worktrees/git.mjs +44 -11
  270. package/src/runtime/shared/worktrees/include.mjs +6 -4
  271. package/src/runtime/shared/worktrees/maintenance.mjs +422 -30
  272. package/src/runtime/shared/worktrees/manager.mjs +772 -75
  273. package/src/runtime/shared/worktrees/settings.mjs +33 -7
  274. package/src/runtime/shared/worktrees/test-support.mjs +5 -1
  275. package/src/runtime/shared/worktrees/worker-snapshots.mjs +82 -0
  276. package/src/runtime/tidy/process.mjs +2 -1
  277. package/src/runtime/web-search/index.mjs +3 -22
  278. package/src/runtime/web-search/lib/web-tools.mjs +2 -2
  279. package/src/runtime/web-search/tool-defs.mjs +17 -26
  280. package/src/session-runtime/agent-profile-changes.mjs +55 -4
  281. package/src/session-runtime/boot/apis.mjs +27 -4
  282. package/src/session-runtime/boot/begin.mjs +2 -0
  283. package/src/session-runtime/boot/config.mjs +10 -1
  284. package/src/session-runtime/boot/facade.mjs +1 -0
  285. package/src/session-runtime/boot/providers.mjs +70 -3
  286. package/src/session-runtime/boot/session.mjs +2 -0
  287. package/src/session-runtime/boot/tools.mjs +69 -25
  288. package/src/session-runtime/channel-config-api.mjs +1 -0
  289. package/src/session-runtime/config-change-effects.mjs +179 -0
  290. package/src/session-runtime/config-lifecycle/config-writers.mjs +1 -0
  291. package/src/session-runtime/config-lifecycle.mjs +160 -24
  292. package/src/session-runtime/conversation-import/discovery.mjs +86 -7
  293. package/src/session-runtime/conversation-import/service.mjs +3 -2
  294. package/src/session-runtime/goal-facade-api.mjs +49 -3
  295. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +3 -0
  296. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -2
  297. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +3 -11
  298. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +18 -4
  299. package/src/session-runtime/internal-tool-executor.mjs +12 -19
  300. package/src/session-runtime/lifecycle/cold-heir.mjs +7 -0
  301. package/src/session-runtime/lifecycle/memory-ingest.mjs +3 -3
  302. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +53 -5
  303. package/src/session-runtime/lifecycle/session-switch.mjs +13 -2
  304. package/src/session-runtime/lifecycle/teardown/session-close.mjs +4 -4
  305. package/src/session-runtime/lifecycle-api.mjs +1 -0
  306. package/src/session-runtime/mcp-glue/server-catalog.mjs +14 -3
  307. package/src/session-runtime/mcp-glue/server-connection.mjs +34 -8
  308. package/src/session-runtime/model-route/set-route.mjs +40 -7
  309. package/src/session-runtime/native-web-search/routes.mjs +3 -3
  310. package/src/session-runtime/provider-auth/refresh.mjs +6 -3
  311. package/src/session-runtime/provider-auth/setup.mjs +1 -1
  312. package/src/session-runtime/provider-readiness.mjs +53 -4
  313. package/src/session-runtime/resource-api.mjs +1 -1
  314. package/src/session-runtime/resource-extension-sync.mjs +4 -8
  315. package/src/session-runtime/resource-mcp-connector-accounts.mjs +5 -1
  316. package/src/session-runtime/resource-mcp-secrets.mjs +3 -3
  317. package/src/session-runtime/resource-plugins-api.mjs +4 -0
  318. package/src/session-runtime/routine-executor.mjs +152 -0
  319. package/src/session-runtime/routine-home-turn.mjs +46 -5
  320. package/src/session-runtime/runtime-facade.mjs +5 -0
  321. package/src/session-runtime/runtime-feature-gates.mjs +2 -11
  322. package/src/session-runtime/schedule-session-run.mjs +1 -0
  323. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +6 -1
  324. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +7 -0
  325. package/src/session-runtime/services/agent-tool/send-flow.mjs +110 -6
  326. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +12 -0
  327. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +5 -0
  328. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +24 -6
  329. package/src/session-runtime/services/agent-tool.mjs +51 -1
  330. package/src/session-runtime/services/channel-admin.mjs +18 -0
  331. package/src/session-runtime/services/hook-bus/command-handler.mjs +7 -11
  332. package/src/session-runtime/services/plugin-admin.mjs +4 -0
  333. package/src/session-runtime/services/seeds.mjs +7 -0
  334. package/src/session-runtime/services/usage-stats-model.mjs +32 -11
  335. package/src/session-runtime/session-create/materialize.mjs +4 -2
  336. package/src/session-runtime/session-create/session-options.mjs +2 -0
  337. package/src/session-runtime/settings-builtin-tools-api.mjs +1 -9
  338. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +1 -1
  339. package/src/session-runtime/setup-tool/executor.mjs +8 -1
  340. package/src/session-runtime/setup-tool/extended-actions.mjs +2 -0
  341. package/src/session-runtime/setup-tool/settings-contract.mjs +28 -0
  342. package/src/session-runtime/setup-tool/tool-defs.mjs +26 -1
  343. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +40 -43
  344. package/src/session-runtime/tool-policy/mode-switch.mjs +1 -1
  345. package/src/session-runtime/tool-policy/policy-stage.mjs +106 -8
  346. package/src/session-runtime/tool-policy/workflow-overlay.mjs +2 -1
  347. package/src/session-runtime/tool-policy-refresh.mjs +182 -5
  348. package/src/session-runtime/tool-surface.mjs +38 -1
  349. package/src/session-runtime/turn/agent-ops.mjs +10 -2
  350. package/src/session-runtime/turn/session-ops.mjs +2 -0
  351. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +5 -0
  352. package/src/session-runtime/turn/turn-run.mjs +1 -1
  353. package/src/session-runtime/webhook-session-run.mjs +2 -0
  354. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +1 -4
  355. package/src/session-runtime/workflow-agents-api/agent-route.mjs +3 -10
  356. package/src/session-runtime/workflow-agents-api/instruction-files.mjs +165 -0
  357. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +5 -4
  358. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +2 -3
  359. package/src/session-runtime/workflow-agents-api.mjs +2 -0
  360. package/src/session-runtime/workspace/project-branches.mjs +1 -1
  361. package/src/session-runtime/workspace/session-view.mjs +1 -1
  362. package/src/session-runtime/workspace/transitions.mjs +20 -4
  363. package/src/session-runtime/workspace/worktree-maintenance.mjs +126 -6
  364. package/src/session-runtime/workspace/worktree-setup.mjs +13 -1
  365. package/src/session-runtime/workspace/worktree-tool.mjs +16 -0
  366. package/src/standalone/daemon-agent-control.mjs +25 -8
  367. package/src/standalone/daemon-desktop-runtime.mjs +6 -2
  368. package/src/standalone/daemon-lag-profiler.mjs +7 -7
  369. package/src/standalone/daemon.mjs +54 -2
  370. package/src/standalone/local-session-runtime.mjs +16 -0
  371. package/src/standalone/session-client/daemon-unavailable.mjs +1 -1
  372. package/src/standalone/session-protocol.mjs +11 -0
  373. package/src/standalone/session-service/agent-route-follow.mjs +23 -20
  374. package/src/standalone/session-service/agent-tree/agent-turns.mjs +18 -6
  375. package/src/standalone/session-service/quota-resume.mjs +9 -0
  376. package/src/standalone/session-service/session-calls/session-action.mjs +51 -1
  377. package/src/standalone/session-service/session-calls/session-turns.mjs +37 -18
  378. package/src/standalone/session-service/session-calls/turn-gate.mjs +34 -0
  379. package/src/standalone/session-service/session-calls.mjs +2 -1
  380. package/src/tui/App.jsx +8 -0
  381. package/src/tui/app/clipboard.mjs +1 -1
  382. package/src/tui/app/create-app-pickers.mjs +2 -0
  383. package/src/tui/app/doctor.mjs +2 -2
  384. package/src/tui/app/panel-signature.mjs +1 -1
  385. package/src/tui/app/settings-picker.mjs +53 -8
  386. package/src/tui/app/shell-layout/row-budgets.mjs +2 -2
  387. package/src/tui/app/slash-commands.mjs +1 -1
  388. package/src/tui/app/slash-dispatch/route-commands.mjs +1 -1
  389. package/src/tui/app/slash-dispatch/session-commands.mjs +1 -1
  390. package/src/tui/app/use-prompt-queue-history.mjs +2 -2
  391. package/src/tui/app/use-transcript-activity.mjs +1 -1
  392. package/src/tui/app/use-transcript-window/geometry.mjs +1 -1
  393. package/src/tui/components/prompt-input/escape-policy.d.mts +0 -1
  394. package/src/tui/components/prompt-input/escape-policy.mjs +1 -1
  395. package/src/tui/components/question-panel-model.mjs +4 -4
  396. package/src/tui/components/tool-execution/standard-card.jsx +3 -4
  397. package/src/tui/components/user-message-skills.mjs +1 -1
  398. package/src/tui/dist/index.mjs +309 -30
  399. package/src/tui/markdown/render-ansi.mjs +1 -1
  400. package/src/tui/session/agent-job-feed/notification-router.mjs +10 -0
  401. package/src/tui/session/agent-job-feed.mjs +2 -0
  402. package/src/tui/session/context-state.mjs +3 -0
  403. package/src/tui/session/quota-resume.mjs +1 -1
  404. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  405. package/src/tui/session/session-api/lifecycle/session-reset.mjs +7 -0
  406. package/src/tui/session/session-api/routes-agent-definitions.mjs +5 -0
  407. package/src/tui/session/session-api/worktree.mjs +13 -1
  408. package/src/tui/session/session-flow/drain.mjs +11 -14
  409. package/src/tui/session/tool-card-results/grouped-result.mjs +2 -2
  410. package/src/tui/session/turn-quiet.mjs +1 -1
  411. package/src/tui/session/turn.mjs +3 -0
  412. package/src/tui/session/worktree-setup-notice.mjs +1 -1
  413. package/src/tui/spinner-meta.mjs +3 -3
  414. package/src/tui/spinner-verbs.mjs +2 -2
  415. package/src/defaults/skills/goal-management/SKILL.md +0 -169
  416. package/src/runtime/agent/orchestrator/runtime-core/goal-tasks.mjs +0 -156
  417. package/src/session-runtime/goal-tasks.mjs +0 -1
@@ -5,132 +5,398 @@ import {
5
5
  cancelJournalWrites,
6
6
  createTurnJournalEncoder,
7
7
  emptyInterruptionSnapshot,
8
- findTurnStart,
8
+ finishPendingSetAside,
9
9
  openJournalForTurn,
10
10
  readTurnCheckpointHeader,
11
11
  readTurnCheckpointHeaderState,
12
+ removeOrphanTurnJournal,
12
13
  removeTurnJournal,
13
14
  replayTurnJournal,
14
15
  settleTurnJournalWrites,
15
16
  turnCheckpointPath,
16
17
  turnCheckpointReadPath,
17
- turnMessagesForCheckpoint,
18
+ withCheckpointOpenLock,
18
19
  writeCheckpointHeader,
19
20
  } from './turn-checkpoint-journal.mjs';
20
21
  import { captureTurnCheckpointContextState, restoreTurnCheckpointContextState } from './turn-checkpoint-context.mjs';
21
22
  import { stripProviderReplayMessages } from './provider-replay.mjs';
22
23
  import { sensitiveValuesVersion } from '../sensitive-values.mjs';
24
+ import { contextMessagesSignature } from '../context-utils.mjs';
25
+ import { mintTranscriptEpoch, readDurableSessionRecord } from '../store/transcript-durability.mjs';
23
26
 
24
- export { turnCheckpointPath, turnMessagesForCheckpoint };
27
+ export { turnCheckpointPath };
25
28
  export { captureTurnCheckpointContextState, restoreTurnCheckpointContextState };
26
29
 
27
30
  /** Resolve once every queued journal append for a session has been written. */
28
31
  export const settleTurnCheckpointWrites = settleTurnJournalWrites;
29
32
 
30
- /** Whether `checkpoint` describes this session's current turn: same
31
- * generation, and either the session's active-turn marker names it or (with
32
- * no marker) it is newer than the session record. */
33
- function checkpointAppliesTo(session, checkpoint) {
34
- const sessionGeneration = Number(session.generation) || 0;
35
- const checkpointGeneration = Number(checkpoint.generation) || 0;
36
- const markerToken =
37
- typeof session.activeTurnCheckpoint?.turnToken === 'string' ? session.activeTurnCheckpoint.turnToken : null;
38
- const markerMatches = markerToken === checkpoint.turnToken;
39
- const checkpointIsNewer = Number(checkpoint.updatedAt) > Number(session.updatedAt || 0);
40
- return !(
41
- checkpointGeneration !== sessionGeneration ||
42
- (markerToken && !markerMatches) ||
43
- (!markerToken && !checkpointIsNewer)
44
- );
33
+ // ── What a checkpoint means against the durable record ──────────────────────
34
+ // A checkpoint names the base it was taken against: `base` (the transcript
35
+ // epoch and length of the landed record the turn opened on) and `boundary`
36
+ // (how many of that record's messages precede the turn). A self-contained
37
+ // checkpoint (`fullTranscript`) carries the whole context instead. Every
38
+ // history rewrite in this process mints a new transcript epoch before a
39
+ // record carries it, and only the base or a later save of the turn can be
40
+ // on disk after the turn opened (store/transcript-durability.mjs), so:
41
+ // covered the record carries the finished turn's coverage token: nothing
42
+ // to replay, the checkpoint only awaits deletion;
43
+ // replay the record is of the base epoch and holds the base prefix (the
44
+ // base itself, a pre-open save of it, or a save of this same
45
+ // active turn), or — for a self-contained checkpoint — is this
46
+ // same active turn's save on any epoch;
47
+ // conflict anything else. The checkpoint is kept and reported, never
48
+ // spliced onto a record it was not taken against.
49
+ // A legacy checkpoint (written before checkpoints named their base: no
50
+ // `boundary`) replays only when the record is exactly the prefix the turn
51
+ // recorded in its context state (count and exact-content signature), or a
52
+ // save of that same turn holding that prefix; otherwise it is a conflict.
53
+
54
+ /** `{ action: 'covered' | 'replay' | 'conflict', reason?, boundary? }` for
55
+ * `checkpoint` against the canonical record `durable` (as it is on disk);
56
+ * `boundary` is how many of the record's messages a replay keeps. */
57
+ export function checkpointDisposition(durable, checkpoint) {
58
+ if (!durable || !Array.isArray(durable.messages)) {
59
+ return { action: 'conflict', reason: 'the session has no durable record' };
60
+ }
61
+ if (typeof checkpoint?.turnToken === 'string' && durable.turnCoverage === checkpoint.turnToken) {
62
+ return { action: 'covered' };
63
+ }
64
+ if (checkpoint && !Object.hasOwn(checkpoint, 'boundary')) return legacyDisposition(durable, checkpoint);
65
+ const base = checkpoint?.base;
66
+ const baseEpoch = typeof base?.epoch === 'string' && base.epoch ? base.epoch : null;
67
+ const sameTurn = durable.activeTurnCheckpoint?.turnToken === checkpoint?.turnToken;
68
+ const onBaseEpoch = baseEpoch !== null && durable.transcriptEpoch === baseEpoch;
69
+ if (checkpoint?.fullTranscript === true) {
70
+ if (onBaseEpoch || sameTurn) return { action: 'replay' };
71
+ return { action: 'conflict', reason: 'the record is neither the base nor a save of this turn' };
72
+ }
73
+ const boundary = checkpoint?.boundary;
74
+ if (!baseEpoch || !Number.isInteger(boundary) || boundary < 0 || boundary > Number(base.length)) {
75
+ return { action: 'conflict', reason: 'the checkpoint names no base revision' };
76
+ }
77
+ if (onBaseEpoch && durable.messages.length >= boundary) return { action: 'replay', boundary };
78
+ return { action: 'conflict', reason: 'the record is not of the base the checkpoint was taken against' };
79
+ }
80
+
81
+ function legacyDisposition(durable, checkpoint) {
82
+ const conflict = (reason) => ({ action: 'conflict', reason: `legacy checkpoint: ${reason}` });
83
+ if ((Number(checkpoint.generation) || 0) !== (Number(durable.generation) || 0)) {
84
+ return conflict('the record is of another lifecycle generation');
85
+ }
86
+ const marker = durable.activeTurnCheckpoint?.turnToken;
87
+ if (typeof marker === 'string' && marker !== checkpoint.turnToken) {
88
+ return conflict('the record names another active turn');
89
+ }
90
+ if (checkpoint.fullTranscript === true) return conflict('a self-contained checkpoint names no base');
91
+ const state = checkpoint.contextState;
92
+ const count = state?.committedPrefixCount;
93
+ if (!Number.isInteger(count) || count < 0 || typeof state.committedPrefixSignature !== 'string') {
94
+ return conflict('the checkpoint records no prefix');
95
+ }
96
+ if (
97
+ durable.messages.length < count ||
98
+ contextMessagesSignature(durable.messages, count) !== state.committedPrefixSignature
99
+ ) {
100
+ return conflict('the record does not hold the prefix the turn opened on');
101
+ }
102
+ if (durable.messages.length !== count && marker !== checkpoint.turnToken) {
103
+ return conflict('the record holds more than the prefix the turn opened on');
104
+ }
105
+ return { action: 'replay', boundary: count };
106
+ }
107
+
108
+ /** The messages `checkpoint` contributes (finalized as interrupted unless it
109
+ * was sealed) and the transcript they make on `durable`, keeping its first
110
+ * `boundary` messages. */
111
+ function replayCheckpoint(durable, checkpoint, boundary, routeChanged) {
112
+ const finalized =
113
+ checkpoint.sealed === true
114
+ ? { messages: checkpoint.turnMessages.slice(), responsePreserved: true }
115
+ : finalizeTurnInterruptionSnapshot({
116
+ turnOutgoing: checkpoint.turnMessages,
117
+ currentUserContent: checkpoint.currentUserContent,
118
+ snapshot: checkpoint.interruption,
119
+ abortReason: 'process-crash',
120
+ });
121
+ // The route switched models mid-turn and the journal may predate the
122
+ // switch: its turn still carries the previous provider's native replay
123
+ // state, which the saved route's provider must not receive.
124
+ const turnMessages = routeChanged ? stripProviderReplayMessages(finalized.messages) : finalized.messages;
125
+ const messages =
126
+ checkpoint.fullTranscript === true ? turnMessages : [...durable.messages.slice(0, boundary), ...turnMessages];
127
+ return { messages, responsePreserved: finalized.responsePreserved };
128
+ }
129
+
130
+ const _reportedConflicts = new Set();
131
+
132
+ export function reportCheckpointConflict(sessionId, checkpoint, reason) {
133
+ const key = `${sessionId}:${checkpoint?.turnToken}`;
134
+ if (_reportedConflicts.has(key)) return;
135
+ _reportedConflicts.add(key);
136
+ try {
137
+ process.stderr.write(
138
+ `[turn-checkpoint] kept unrecovered checkpoint session=${sessionId} turn=${checkpoint?.turnToken}: ${reason}\n`
139
+ );
140
+ } catch {
141
+ /* stderr best-effort */
142
+ }
45
143
  }
46
144
 
47
- /** Read-only projection for a turn still owned by another process. Unlike
48
- * recoverTurnCheckpoint this never mutates the session, clears the checkpoint,
49
- * or marks a live turn as interrupted. */
145
+ /** Read-only projection for a turn still owned by another process (or not
146
+ * yet durable in this one). Unlike recoverTurnCheckpoint this never mutates
147
+ * the session, clears the checkpoint, or marks a live turn as interrupted.
148
+ * `session` is read as the durable record. */
50
149
  export function projectTurnCheckpointMessages(session, checkpoint) {
51
150
  const source = Array.isArray(session?.messages) ? session.messages : [];
52
151
  if (!session?.id || checkpoint?.sessionId !== session.id || !Array.isArray(checkpoint?.turnMessages)) return source;
53
- if (!checkpointAppliesTo(session, checkpoint)) return source;
54
- if (checkpoint.fullTranscript === true) {
55
- return checkpoint.turnMessages.slice();
56
- }
57
- const start = findTurnStart(source, checkpoint.currentUserContent);
58
- return [...(start >= 0 ? source.slice(0, start) : source), ...checkpoint.turnMessages];
152
+ const disposition = checkpointDisposition(session, checkpoint);
153
+ if (disposition.action !== 'replay') return source;
154
+ if (checkpoint.fullTranscript === true) return checkpoint.turnMessages.slice();
155
+ return [...source.slice(0, disposition.boundary), ...checkpoint.turnMessages];
59
156
  }
60
157
 
61
158
  // ── Journal-backed checkpoint writes ─────────────────────────────────────────
62
- // A turn is checkpointed as a write-once header plus an append-only delta
63
- // journal (turn-checkpoint-journal.mjs). The recorder owns exactly one turn:
64
- // the FIRST record() writes the header synchronously — it is the crash
65
- // durability anchor for the prompt and must land before the provider runs —
66
- // and every later record() appends ONLY what changed since the previous flush.
67
- // Per-flush cost is therefore O(new bytes), not O(whole growing turn), so a
68
- // long tool-heavy turn no longer amplifies into the shared engine thread that
69
- // every fanned-out agent session shares.
70
- export function createTurnCheckpointRecorder({ sessionId, generation, turnToken, startedAt }) {
159
+ // A turn is checkpointed as a header plus an append-only delta journal
160
+ // (turn-checkpoint-journal.mjs). The recorder owns exactly one turn: the FIRST
161
+ // record() writes the header synchronously — it is the crash durability
162
+ // anchor for the prompt and must land before the provider runs — and every
163
+ // later record() appends ONLY what changed since the previous flush. Per-flush
164
+ // cost is therefore O(new bytes), not O(whole growing turn).
165
+ //
166
+ // The header names its base explicitly: the turn opens at `boundary` (the
167
+ // index of its user message) on the durable landed transcript `base`
168
+ // (`{ epoch, refs }`, store/transcript-durability.mjs). When the messages
169
+ // before the boundary are not exactly the base's, or a later flush finds any
170
+ // of them replaced (compaction, a prefix rewrite), the header is rewritten as
171
+ // ONE self-contained replacement of the whole context under a new transcript
172
+ // epoch — atomically, before any session record can carry that epoch — and
173
+ // the journal restarts under it. A bare index move is never journaled.
174
+ export function createTurnCheckpointRecorder({
175
+ session = null,
176
+ sessionId,
177
+ generation,
178
+ turnToken,
179
+ startedAt,
180
+ base = null,
181
+ }) {
71
182
  const encoder = createTurnJournalEncoder({ turnToken, sessionId });
72
183
  let opened = false;
73
184
  let stopped = false;
74
185
  // The sensitive-values version (sensitive-values.mjs) the header and
75
186
  // journal were written under. A newly marked value rewrites both from the
76
- // turn's current state with the value replaced, under a new journal epoch,
77
- // so no recovery record keeps it.
187
+ // turn's current state with the value replaced, so no recovery record
188
+ // keeps it.
78
189
  let openedVersion = 0;
190
+ let headerPending = false;
191
+ let headerWrites = 0;
192
+ // Frame: the header holds turnOutgoing[offset..] (offset 0 when
193
+ // self-contained); `pinned` are the messages before the turn's own region,
194
+ // any replacement of which is a history rewrite.
195
+ let fullTranscript = false;
196
+ let offset = 0;
197
+ let pinned = [];
198
+ let openingIndex = -1;
199
+ let openingRef = null;
200
+ let transcriptEpoch = null;
201
+ let contextState = null;
202
+
203
+ const adoptEpoch = (epoch) => {
204
+ transcriptEpoch = epoch;
205
+ if (session) session.transcriptEpoch = epoch;
206
+ };
207
+ const opensOnBase = (source, boundary) => {
208
+ if (!base || typeof base.epoch !== 'string' || !Array.isArray(base.refs)) return false;
209
+ if (session && session.transcriptEpoch !== base.epoch) return false;
210
+ if (boundary > base.refs.length) return false;
211
+ for (let index = 0; index < boundary; index += 1) if (source[index] !== base.refs[index]) return false;
212
+ return true;
213
+ };
214
+ const pinnedIntact = (source) => {
215
+ if (source.length < pinned.length) return false;
216
+ for (let index = 0; index < pinned.length; index += 1) if (source[index] !== pinned[index]) return false;
217
+ return true;
218
+ };
219
+ const trackOpening = (source) => {
220
+ openingRef = openingIndex >= 0 ? (source[openingIndex] ?? null) : null;
221
+ };
222
+ const writeHeader = ({ source, currentUserContent, interruption, captureContext, sealed }) => {
223
+ const snapshot =
224
+ typeof interruption?.snapshot === 'function' ? interruption.snapshot() : emptyInterruptionSnapshot();
225
+ if (typeof captureContext === 'function') contextState = captureContext(offset);
226
+ headerWrites += 1;
227
+ const seed = encoder.seed(source, interruption, contextState, { offset, snapshot, epoch: headerWrites });
228
+ // Header publish and journal open are one step under the session's
229
+ // checkpoint lock, so the orphan-journal sweep of another process never
230
+ // deletes the journal between them (turn-checkpoint-journal.mjs).
231
+ withCheckpointOpenLock(sessionId, () => {
232
+ writeCheckpointHeader({
233
+ sessionId,
234
+ generation,
235
+ turnToken,
236
+ startedAt,
237
+ currentUserContent,
238
+ base: base ? { epoch: base.epoch, length: base.refs.length } : null,
239
+ boundary: offset,
240
+ transcriptEpoch,
241
+ ...(fullTranscript ? { fullTranscript: true } : {}),
242
+ ...(sealed ? { sealed: true } : {}),
243
+ journalEpoch: headerWrites,
244
+ turnMessages: seed.turnMessages,
245
+ interruption: snapshot,
246
+ contextState: seed.contextState,
247
+ });
248
+ // Opening the journal is part of the anchor: a leftover journal (an
249
+ // earlier turn's, or this turn's under its previous header) must never
250
+ // replay onto the new header. It lands synchronously when the lane is
251
+ // idle and is otherwise FENCED behind the older in-flight write; the
252
+ // head record's token and journal epoch guard the interim window.
253
+ openJournalForTurn(sessionId, seed.openLines);
254
+ });
255
+ trackOpening(source);
256
+ return true;
257
+ };
258
+ const report = (text) => {
259
+ try {
260
+ process.stderr.write(`[turn-checkpoint] session=${sessionId}: ${text}\n`);
261
+ } catch {
262
+ // stderr may be closed; the removal is already done
263
+ }
264
+ };
265
+ // The journal removal is owed until it is confirmed (`journalOwed`): the
266
+ // recorder writes nothing meanwhile, and every flush (and stop) tries again.
267
+ let journalOwed = false;
268
+ let journalRemoving = false;
269
+ let redactionFailure = '';
270
+ const removeOwedJournal = () => {
271
+ if (journalRemoving) return;
272
+ journalRemoving = true;
273
+ removeTurnJournal(sessionId).then((gone) => {
274
+ journalRemoving = false;
275
+ if (gone) journalOwed = false;
276
+ report(
277
+ gone
278
+ ? `removed the checkpoint: a value marked sensitive could not be redacted in it (${redactionFailure})`
279
+ : 'the checkpoint journal still holds a value marked sensitive and could not be removed; trying again on the next flush'
280
+ );
281
+ });
282
+ };
283
+ // The rewrite that would redact a newly marked value failed: remove the
284
+ // checkpoint rather than leave the plaintext (losing recovery data is the
285
+ // lesser harm). Once header and journal are both gone the next flush opens a
286
+ // fresh, redacted one. When the header cannot be removed the recorder stops,
287
+ // so nothing more is appended to the files.
288
+ const failClosed = (cause) => {
289
+ redactionFailure = cause?.message || String(cause);
290
+ let removed = false;
291
+ try {
292
+ removed = clearTurnCheckpoint(sessionId, turnToken);
293
+ } catch {
294
+ // reported below with the stop
295
+ }
296
+ if (removed) {
297
+ opened = false;
298
+ headerPending = false;
299
+ journalOwed = true;
300
+ removeOwedJournal();
301
+ } else {
302
+ stopped = true;
303
+ cancelJournalWrites(sessionId);
304
+ report(
305
+ `a value marked sensitive could not be redacted in the checkpoint (${redactionFailure}); could not remove it, no longer recording`
306
+ );
307
+ }
308
+ return false;
309
+ };
310
+ // Persistence state advances only once the header is written: a write that
311
+ // fails (a transient EPERM) leaves the header pending, so the next flush
312
+ // writes it again instead of appending journal deltas under a header that
313
+ // never landed (recovery reads no journal without one).
314
+ const commitHeader = (header, version) => {
315
+ headerPending = true;
316
+ let written;
317
+ try {
318
+ written = writeHeader(header);
319
+ } catch (error) {
320
+ // The files on disk hold the turn from before a value was marked
321
+ // sensitive and could not be replaced: they must not outlive this call.
322
+ if (opened && version !== openedVersion) return failClosed(error);
323
+ throw error;
324
+ }
325
+ headerPending = false;
326
+ openedVersion = version;
327
+ opened = true;
328
+ return written;
329
+ };
79
330
  return {
80
331
  get opened() {
81
332
  return opened;
82
333
  },
83
- /** True when this call wrote or queued durable state. */
84
- record({ currentUserContent, turnOutgoing, interruption, contextState = null }) {
334
+ /** True when this call wrote or queued durable state. `boundary` is the
335
+ * index of the turn's user message in `turnOutgoing` (read when the
336
+ * checkpoint opens); `captureContext(boundary)` reads the context state
337
+ * aligned with the header's split, re-read when `contextDirty`;
338
+ * `sealed` records the turn as finished. */
339
+ record({
340
+ boundary,
341
+ currentUserContent,
342
+ turnOutgoing,
343
+ interruption,
344
+ captureContext = null,
345
+ contextDirty = false,
346
+ sealed = false,
347
+ }) {
85
348
  if (stopped || !sessionId || !turnToken) return false;
349
+ if (journalOwed) {
350
+ removeOwedJournal();
351
+ return false;
352
+ }
353
+ const source = Array.isArray(turnOutgoing) ? turnOutgoing : [];
86
354
  const version = sensitiveValuesVersion(sessionId);
87
- if (!opened || version !== openedVersion) {
88
- const snapshot =
89
- typeof interruption?.snapshot === 'function' ? interruption.snapshot() : emptyInterruptionSnapshot();
90
- const seed = encoder.seed(turnOutgoing, currentUserContent, interruption, contextState, {
91
- snapshot,
92
- epoch: version,
93
- });
94
- writeCheckpointHeader({
95
- sessionId,
96
- generation,
97
- turnToken,
98
- startedAt,
99
- currentUserContent,
100
- turnMessages: seed.turnMessages,
101
- ...(seed.fullTranscript ? { fullTranscript: true } : {}),
102
- ...(version ? { journalEpoch: version } : {}),
103
- interruption: snapshot,
104
- contextState: seed.contextState,
105
- });
106
- openedVersion = version;
107
- // Opening the journal is part of the anchor: a leftover journal
108
- // from an earlier turn of this session must never replay onto
109
- // the new header. It lands synchronously when the lane is idle
110
- // and is otherwise FENCED behind the older in-flight write, so
111
- // an old turn can never appear beneath this turn's token (the
112
- // head record's token guards the interim window).
113
- openJournalForTurn(sessionId, seed.openLines);
114
- opened = true;
115
- return true;
355
+ const header = { source, currentUserContent, interruption, captureContext, sealed };
356
+ if (!opened) {
357
+ const at = Number.isInteger(boundary) && boundary >= 0 && boundary <= source.length ? boundary : source.length;
358
+ if (opensOnBase(source, at)) {
359
+ fullTranscript = false;
360
+ offset = at;
361
+ transcriptEpoch = base.epoch;
362
+ } else {
363
+ fullTranscript = true;
364
+ offset = 0;
365
+ adoptEpoch(mintTranscriptEpoch());
366
+ }
367
+ pinned = source.slice(0, at);
368
+ openingIndex = at < source.length ? at : -1;
369
+ return commitHeader(header, version);
116
370
  }
117
- const lines = encoder.encode({
118
- turnOutgoing,
119
- currentUserContent,
120
- interruption,
121
- contextState,
122
- });
371
+ if (!pinnedIntact(source)) {
372
+ // A history rewrite. The turn's own opening message, found by
373
+ // identity, bounds the new pinned prefix; without it every message
374
+ // present now is pinned.
375
+ const at = openingRef ? source.indexOf(openingRef) : -1;
376
+ fullTranscript = true;
377
+ offset = 0;
378
+ adoptEpoch(mintTranscriptEpoch());
379
+ pinned = source.slice(0, at >= 0 ? at : source.length);
380
+ openingIndex = at;
381
+ return commitHeader(header, version);
382
+ }
383
+ if (headerPending || version !== openedVersion) return commitHeader(header, version);
384
+ if (contextDirty && typeof captureContext === 'function') contextState = captureContext(offset);
385
+ const lines = encoder.encode({ turnOutgoing: source, interruption, contextState, sealed });
386
+ trackOpening(source);
123
387
  if (lines.length === 0) return false;
124
- appendJournalLines(sessionId, lines);
388
+ appendJournalLines(sessionId, lines, { immediate: sealed });
125
389
  return true;
126
390
  },
127
- stop() {
391
+ /** `keepQueued`: the turn was sealed, so its queued appends still land. */
392
+ stop({ keepQueued = false } = {}) {
128
393
  if (stopped) return;
129
394
  stopped = true;
395
+ if (journalOwed) removeOwedJournal();
130
396
  // Retire queued (not yet written) appends. The durable prefix stays on
131
- // disk on purpose: between turn commit and clearTurnCheckpoint a crash
132
- // must still recover the turn's work, not just its opening header.
133
- cancelJournalWrites(sessionId);
397
+ // disk on purpose: until a save covering the turn lands a crash must
398
+ // still recover the turn's work, not just its opening header.
399
+ if (!keepQueued) cancelJournalWrites(sessionId);
134
400
  },
135
401
  };
136
402
  }
@@ -147,6 +413,9 @@ export function readTurnCheckpoint(sessionId) {
147
413
  // the directory probes left the event loop.
148
414
  export function clearTurnCheckpoint(sessionId, turnToken = null) {
149
415
  const target = turnCheckpointReadPath(sessionId);
416
+ // The files of a set-aside still being moved are not this checkpoint's to
417
+ // clear (turn-checkpoint-journal.mjs): it is finished first, or kept.
418
+ if (finishPendingSetAside(sessionId).pending) return false;
150
419
  // Queued appends are stale the moment the caller decides to clear this
151
420
  // turn's checkpoint; retire them BEFORE the token guard reads disk so a
152
421
  // lagging write can never resurrect state after the unlink.
@@ -180,44 +449,48 @@ export function clearTurnCheckpoint(sessionId, turnToken = null) {
180
449
  }
181
450
 
182
451
  /**
183
- * Merge a force-killed turn checkpoint into a loaded session. The caller must
184
- * durably save the mutated session before clearing the returned token.
452
+ * Merge a force-killed turn checkpoint into a loaded session, deciding from
453
+ * the canonical record `durable` as it is on disk (checkpointDisposition) —
454
+ * never from the in-memory session's freshness. Returns `covered` when that
455
+ * record already carries the turn (the caller deletes the checkpoint) and
456
+ * `conflict` when the checkpoint was not taken against it (kept, reported).
457
+ * A recovered session carries the turn's coverage token; the caller must
458
+ * durably save it before clearing the returned token.
185
459
  */
186
- export function recoverTurnCheckpoint(session) {
460
+ export function recoverTurnCheckpoint(session, durableRecord = undefined) {
187
461
  if (!session?.id || session.closed === true) {
188
462
  return { changed: false, recovered: false, turnToken: null };
189
463
  }
190
464
  const checkpoint = readTurnCheckpoint(session.id);
191
465
  const marker = session.activeTurnCheckpoint;
192
466
  if (!checkpoint) {
467
+ // A journal left without its header (a removal that failed, or a restart
468
+ // that cut it short) can never be recovered: delete it.
469
+ removeOrphanTurnJournal(session.id);
193
470
  if (!marker) return { changed: false, recovered: false, turnToken: null };
194
471
  delete session.activeTurnCheckpoint;
195
472
  return { changed: true, recovered: false, turnToken: null };
196
473
  }
197
474
 
198
- if (!checkpointAppliesTo(session, checkpoint)) {
199
- clearTurnCheckpoint(session.id, checkpoint.turnToken);
200
- if (typeof marker?.turnToken === 'string' && marker.turnToken === checkpoint.turnToken) {
201
- delete session.activeTurnCheckpoint;
202
- }
203
- return { changed: false, recovered: false, turnToken: null };
475
+ const durable = durableRecord === undefined ? readDurableSessionRecord(session.id) : durableRecord;
476
+ const disposition = checkpointDisposition(durable, checkpoint);
477
+ if (disposition.action === 'covered') {
478
+ return { changed: false, recovered: false, covered: true, turnToken: checkpoint.turnToken };
479
+ }
480
+ if (disposition.action === 'conflict') {
481
+ reportCheckpointConflict(session.id, checkpoint, disposition.reason);
482
+ return { changed: false, recovered: false, conflict: disposition.reason, turnToken: null };
204
483
  }
205
484
 
206
- const finalized = finalizeTurnInterruptionSnapshot({
207
- turnOutgoing: checkpoint.turnMessages,
208
- currentUserContent: checkpoint.currentUserContent,
209
- snapshot: checkpoint.interruption,
210
- abortReason: 'process-crash',
211
- });
212
- const current = Array.isArray(session.messages) ? session.messages : [];
213
- const start = findTurnStart(current, checkpoint.currentUserContent);
214
- const kept = start >= 0 ? current.slice(0, start) : current;
215
- // The route switched models mid-turn (the marker says so) and the journal
216
- // may predate the switch: its turn still carries the previous provider's
217
- // native replay state, which the saved route's provider must not receive.
218
- const turnMessages =
219
- marker?.routeChanged === true ? stripProviderReplayMessages(finalized.messages) : finalized.messages;
220
- session.messages = checkpoint.fullTranscript === true ? turnMessages : [...kept, ...turnMessages];
485
+ const routeChanged = [durable.activeTurnCheckpoint, marker].some(
486
+ (entry) => entry?.turnToken === checkpoint.turnToken && entry.routeChanged === true
487
+ );
488
+ const finalized = replayCheckpoint(durable, checkpoint, disposition.boundary, routeChanged);
489
+ session.messages = finalized.messages;
490
+ // The splice is a history rewrite of its own, and this record now covers
491
+ // the turn: a crash after it is saved never replays the checkpoint again.
492
+ session.transcriptEpoch = mintTranscriptEpoch();
493
+ session.turnCoverage = checkpoint.turnToken;
221
494
  // Re-anchor provider usage / post-compact replacement state to the exact
222
495
  // canonical checkpoint representation. If a legacy or corrupted checkpoint
223
496
  // cannot prove alignment, retain the last provider reading for display but