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
@@ -0,0 +1,144 @@
1
+ // manager/turn-durability.mjs
2
+ // One unsettled turn per session. A finished (or failed) turn stays
3
+ // unsettled — its checkpoint kept — until a landed save of its session
4
+ // carries the turn's coverage token (store/transcript-durability.mjs); only
5
+ // that acknowledgement deletes the checkpoint. The next turn of the session
6
+ // waits for it before it opens its own checkpoint or touches the session, and
7
+ // a save that cannot land blocks that turn with a TurnPersistenceError
8
+ // instead of releasing it. A settled promise, an idle queue or a coalesced,
9
+ // cancelled or dropped write is never an acknowledgement. Sessions are
10
+ // independent: nothing here waits on another session's saves.
11
+ import { readSessionLifecycleFromDisk, saveSessionAsync } from '../store.mjs';
12
+ import { landedTranscript, landedTranscriptMatches, mintTranscriptEpoch } from '../store/transcript-durability.mjs';
13
+ import { clearTurnCheckpoint } from './turn-checkpoint.mjs';
14
+
15
+ const _unsettled = new Map(); // sessionId → { session, turnToken, generation, ack, error }
16
+
17
+ export class TurnPersistenceError extends Error {
18
+ constructor(sessionId, message, cause = null) {
19
+ super(`Session ${sessionId}: ${message}${cause ? ` (${cause.message || cause})` : ''}`);
20
+ this.name = 'TurnPersistenceError';
21
+ this.code = 'ETURNNOTDURABLE';
22
+ this.sessionId = sessionId;
23
+ if (cause) this.cause = cause;
24
+ }
25
+ }
26
+
27
+ // Split-brain re-adoption: another surface (e.g. desktop click-through
28
+ // of a session still actively owned by this process) may have
29
+ // resumed-and-detached this session, bumping the ON-DISK generation
30
+ // while the conversation kept going here. Every save from this process
31
+ // would then be silently dropped by _shouldDrop's ownership rule and
32
+ // the on-disk transcript would freeze at the last landed save. A new
33
+ // ask on a NON-closed session is an explicit ownership claim: adopt
34
+ // the disk generation so this turn's commits land and heal the file.
35
+ export function adoptDiskGeneration(session, sessionId) {
36
+ const diskLifecycle = readSessionLifecycleFromDisk(sessionId);
37
+ if (
38
+ diskLifecycle &&
39
+ diskLifecycle.closed !== true &&
40
+ diskLifecycle.generation > (typeof session.generation === 'number' ? session.generation : 0)
41
+ ) {
42
+ session.generation = diskLifecycle.generation;
43
+ }
44
+ }
45
+
46
+ /** Whether a landed save of `session` carries `turnToken`'s coverage. */
47
+ export function turnCoveredOnDisk(session, turnToken) {
48
+ return landedTranscript(session)?.coverage === turnToken;
49
+ }
50
+
51
+ function release(sessionId, entry) {
52
+ if (_unsettled.get(sessionId) !== entry) return;
53
+ _unsettled.delete(sessionId);
54
+ clearTurnCheckpoint(sessionId, entry.turnToken);
55
+ }
56
+
57
+ /** Keep `turnToken` (already the session's `turnCoverage`) unsettled until a
58
+ * landed save covers it; `save` is the save expected to. Resolves whether
59
+ * it did. */
60
+ export function holdTurnUntilDurable({ sessionId, session, turnToken, generation, save }) {
61
+ const entry = { session, turnToken, generation, error: null, ack: null };
62
+ _unsettled.set(sessionId, entry);
63
+ entry.ack = Promise.resolve(save)
64
+ .then(
65
+ () => turnCoveredOnDisk(session, turnToken),
66
+ (error) => {
67
+ entry.error = error;
68
+ return false;
69
+ }
70
+ )
71
+ .then((covered) => {
72
+ if (covered) release(sessionId, entry);
73
+ return covered;
74
+ });
75
+ return entry.ack;
76
+ }
77
+
78
+ /** Whether this process holds a turn of `sessionId` not yet durable. */
79
+ export function hasUnsettledTurn(sessionId) {
80
+ return _unsettled.has(sessionId);
81
+ }
82
+
83
+ /** A closed or deleted session: nothing will open on it again. */
84
+ export function forgetUnsettledTurn(sessionId) {
85
+ _unsettled.delete(sessionId);
86
+ }
87
+
88
+ /** Resolves once the previous turn of `sessionId` is durably covered (its
89
+ * checkpoint then deleted). When its own save did not land it, one fresh
90
+ * save of the finalized transcript is attempted; if that does not land
91
+ * either, throws TurnPersistenceError and the checkpoint stays. A save the
92
+ * ownership guard refused because another surface moved the disk generation
93
+ * is retried under that generation: this ask is the ownership claim that
94
+ * adoptDiskGeneration describes, and the retry stays guarded by it, so a
95
+ * closed session or a further handoff still refuses it. */
96
+ export async function awaitPreviousTurnDurable(sessionId) {
97
+ const entry = _unsettled.get(sessionId);
98
+ if (!entry || (await entry.ack)) return;
99
+ adoptDiskGeneration(entry.session, sessionId);
100
+ entry.generation = Math.max(entry.generation, entry.session.generation);
101
+ try {
102
+ await saveSessionAsync(entry.session, { expectedGeneration: entry.generation });
103
+ } catch (error) {
104
+ entry.error = error;
105
+ }
106
+ if (!turnCoveredOnDisk(entry.session, entry.turnToken)) {
107
+ throw new TurnPersistenceError(
108
+ sessionId,
109
+ 'the previous turn is not saved to disk, so the next turn cannot start',
110
+ entry.error
111
+ );
112
+ }
113
+ release(sessionId, entry);
114
+ }
115
+
116
+ /** Make `session`'s current transcript durable, so a turn opening on it
117
+ * names a base that is on disk. A transcript that is not a pure extension
118
+ * of the last landed one (an unsaved rewrite) starts a new epoch first. */
119
+ export async function ensureDurableTranscript(session) {
120
+ if (landedTranscriptMatches(session)) return;
121
+ const landed = landedTranscript(session);
122
+ const messages = Array.isArray(session.messages) ? session.messages : [];
123
+ const extendsLanded =
124
+ landed &&
125
+ landed.epoch === session.transcriptEpoch &&
126
+ landed.refs.length <= messages.length &&
127
+ landed.refs.every((message, index) => message === messages[index]);
128
+ if (typeof session.transcriptEpoch !== 'string' || (landed && !extendsLanded)) {
129
+ session.transcriptEpoch = mintTranscriptEpoch();
130
+ }
131
+ let failure = null;
132
+ try {
133
+ await saveSessionAsync(session, { expectedGeneration: session.generation });
134
+ } catch (error) {
135
+ failure = error;
136
+ }
137
+ if (!landedTranscriptMatches(session)) {
138
+ throw new TurnPersistenceError(
139
+ session.id,
140
+ 'the conversation is not saved to disk, so the next turn cannot start',
141
+ failure
142
+ );
143
+ }
144
+ }
@@ -225,7 +225,7 @@ function textBufferMethods(state) {
225
225
  // The replayed attempt has started producing its OWN text, so the
226
226
  // retracted tail it replaces is no longer what the user is looking
227
227
  // at. Dropping it here keeps exactly one copy of the opening
228
- // instead of one per retry (a 3-retry stall used to persist four
228
+ // instead of one per retry (a 3-retry stall would persist four
229
229
  // copies of the same first sentence). Reference agents never
230
230
  // commit a replayed attempt's discarded stream at all; the
231
231
  // tombstone exists only for the window between a retraction and
@@ -8,6 +8,7 @@
8
8
  import { providerInputExcludesCache } from '../../providers/registry.mjs';
9
9
  import { reasoningUsage, combineReasoningUsage } from '../../../../shared/llm/reasoning-usage.mjs';
10
10
  import { foldUsageReadout } from '../../../../shared/usage-readout-totals.mjs';
11
+ import { emitSessionActivityChange } from '../../../../shared/session-activity-signal.mjs';
11
12
  import {
12
13
  loadSession,
13
14
  saveSessionAsync,
@@ -560,6 +561,9 @@ export async function persistIterationMetrics(delta) {
560
561
  // saved before this field began at their first call after it.
561
562
  session.usageReadout = foldUsageReadout(session.usageReadout, delta, delta.requestMs);
562
563
  recordLastContextTokens(session, iterationContext(delta));
564
+ // The Agents pool shows each agent's running totals from this live
565
+ // counter: it re-reads on this pulse instead of the next throttled save.
566
+ emitSessionActivityChange(sessionId);
563
567
  }
564
568
  session.lastIterationIndex = iterationIndex;
565
569
  session.updatedAt = ts || Date.now();
@@ -65,7 +65,12 @@ export {
65
65
  settlePendingMessageWrites,
66
66
  markCompletionEntry,
67
67
  COMPLETION_NOTIFICATION_KIND,
68
+ hasPendingMessages,
68
69
  } from './manager/pending-messages.mjs';
70
+ // The per-session ask mutex every turn holds, tried without waiting, and the
71
+ // unsettled-turn check: the idle-only delete (session-delete.mjs) acts under them.
72
+ export { tryAcquireSessionLock, sessionLockContended } from './manager/session-lock.mjs';
73
+ export { hasUnsettledTurn } from './manager/turn-durability.mjs';
69
74
 
70
75
  // ── Usage-metrics surface — re-exported unchanged so loop.mjs / smoke scripts
71
76
  // keep resolving these through the facade. ────────────────────────────────
@@ -96,6 +101,7 @@ export { prewarmAgentLoop } from './manager/runtime-loaders.mjs';
96
101
  export {
97
102
  createSession,
98
103
  updateSessionRoute,
104
+ holdSessionRoute,
99
105
  prefetchSession,
100
106
  prepareSessionProjection,
101
107
  resumeSession,
@@ -15,7 +15,7 @@ import { mixyIdentityDir } from '../../../shared/agent-store.mjs';
15
15
  export const DEFAULT_MIXY_NAME = 'Mixy';
16
16
 
17
17
  /** The display name in a lead manifest (agent.json), else Mixy. */
18
- export function mixyNameOf(manifest) {
18
+ function mixyNameOf(manifest) {
19
19
  const name = typeof manifest?.name === 'string' ? manifest.name.replace(/\s+/g, ' ').trim() : '';
20
20
  return name || DEFAULT_MIXY_NAME;
21
21
  }
@@ -41,6 +41,7 @@ import {
41
41
  snapshotPendingGoalReminder,
42
42
  } from '../runtime-core/goal-reminder.mjs';
43
43
  import { rearmLateDeferredTools } from '../runtime-core/tool-catalog.mjs';
44
+ import { rearmPromptSettingsNotice } from './manager/prompt-settings.mjs';
44
45
 
45
46
  async function setStage(opts, stage) {
46
47
  try {
@@ -437,7 +438,12 @@ function adoptCompactedTranscript(ctx, run) {
437
438
  // The system prompt is rebuilt from current sources outside a running turn
438
439
  // (session-runtime agent-profile-fold.mjs): the next cold point folds it in
439
440
  // instead of adding a break to the turn that is already warm again.
440
- if (sessionRef) sessionRef.promptFoldPending = true;
441
+ // The fold runs at the next turn start (compactionFoldPending): it lists the
442
+ // late tools and the settings this compaction dropped the notices of.
443
+ if (sessionRef) {
444
+ sessionRef.promptFoldPending = true;
445
+ sessionRef.compactionFoldPending = true;
446
+ }
441
447
  resetReadStateAfterCompaction(sessionId);
442
448
  // Cross-turn receipts reference tool results that compaction just removed
443
449
  // from context; a stub for them would point at nothing. Clear in place so
@@ -573,6 +579,7 @@ async function compactBeforeSend(ctx) {
573
579
  // The adopted compaction strips late MCP tool announcements from older turns.
574
580
  try {
575
581
  rearmLateDeferredTools(sessionRef);
582
+ rearmPromptSettingsNotice(sessionRef);
576
583
  } catch {
577
584
  /* best-effort, like the Goal reminder: a tool reminder never fails the compaction */
578
585
  }
@@ -60,9 +60,13 @@ parentPort.on('message', (message) => {
60
60
  }
61
61
  return;
62
62
  }
63
- const { session, delta, id, opts, reqId } = message || {};
63
+ const { session, delta, id, opts, reqId, dataDir } = message || {};
64
64
  let full = session;
65
65
  try {
66
+ // This realm's env was copied when the worker spawned; the parent's data
67
+ // dir may have moved since (save-worker.mjs _postAsyncWrite), and every
68
+ // path below resolves it from the env at call time.
69
+ if (dataDir) process.env.MIXDOG_DATA_DIR = dataDir;
66
70
  const sessionId = id ?? session?.id;
67
71
  if (!full && delta) {
68
72
  const base = _baseMessages.get(sessionId);
@@ -190,7 +190,7 @@ export function withdrawPendingOwnCommit(target, scratchStamp) {
190
190
  }
191
191
 
192
192
  /** Read-only lifecycle verdict of a pending peer commit whose identity `stamp` carries. */
193
- export function pendingOwnCommittedLifecycle(target, stamp) {
193
+ function pendingOwnCommittedLifecycle(target, stamp) {
194
194
  _absorbPeerCommits();
195
195
  const pending = _pendingOwnCommits.get(target);
196
196
  return pending && _sameScratchIdentity(pending.stamp, stamp) ? pending.value : null;
@@ -17,7 +17,7 @@ import { probePath, readTextFile, PROBE_PRESENT } from './fs-probe.mjs';
17
17
  export const LEGACY_MARKER_SUFFIXES = ['.hb', '.own'];
18
18
  // An older live owner rewrote `.hb` every ≤5s mid-turn and `.own` every ~20s
19
19
  // while holding the session open; one untouched this long holds nothing.
20
- export const LEGACY_MARKER_FRESH_MS = 2 * 60 * 1000;
20
+ const LEGACY_MARKER_FRESH_MS = 2 * 60 * 1000;
21
21
 
22
22
  function processAlive(pid) {
23
23
  try {
@@ -47,7 +47,7 @@ export function readLegacyMarker(path, now = Date.now()) {
47
47
 
48
48
  /** Whether a legacy marker of the session names a live owner pid with a
49
49
  * fresh heartbeat: that session is driven by another live process. */
50
- export function _legacyOwnerLivenessVeto(id, now = Date.now()) {
50
+ function _legacyOwnerLivenessVeto(id, now = Date.now()) {
51
51
  if (typeof id !== 'string' || !/^[A-Za-z0-9_-]+$/.test(id)) return false;
52
52
  const dir = getStoreDir();
53
53
  return LEGACY_MARKER_SUFFIXES.some((suffix) => {
@@ -18,6 +18,8 @@ import {
18
18
  _acquireSessionIncarnation,
19
19
  _releaseSessionIncarnation,
20
20
  _isCurrentSessionIncarnation,
21
+ SAVE_OUTCOME_SAVED,
22
+ SAVE_OUTCOME_DROPPED,
21
23
  SAVE_OUTCOME_STALE,
22
24
  } from './live-state.mjs';
23
25
  import {
@@ -33,6 +35,8 @@ import {
33
35
  _sessionStoreTestMode,
34
36
  } from './save-fault.mjs';
35
37
  import { connectOwnCommitPeer } from './canonical-reader.mjs';
38
+ import { getPluginData } from '../../config.mjs';
39
+ import { captureTranscriptSnapshot, recordLandedTranscript } from './transcript-durability.mjs';
36
40
 
37
41
  // ── Worker-thread async save ──────────────────────────────────────────────────
38
42
  // Single long-lived Worker serializes all saveSessionAsync calls.
@@ -278,6 +282,7 @@ function _settleLandedWrite(
278
282
  // built — never a row re-derived from the live session, which
279
283
  // may already carry a newer (unwritten, possibly failing) turn.
280
284
  _queueSessionSummaryUpsertRow(summaryRow, summaryVersion);
285
+ recordLandedTranscript(p.session, p.transcript, epoch);
281
286
  // ONLY a landed write proves durability, so this is the only
282
287
  // place the save-failure flag may be cleared — and only when
283
288
  // THIS payload is at least as new as the newest failed/dropped
@@ -304,7 +309,14 @@ function _settleLandedWrite(
304
309
  _rollbackCachedSessionSummary(id, summaryVersion);
305
310
  _recordSaveDrop(id, epoch);
306
311
  }
307
- for (const w of waiters) w.resolve();
312
+ // Callers that act only on a durable write (e.g. a migration deleting its
313
+ // source) read this outcome; every other resolution leaves it undefined.
314
+ const settled = saved
315
+ ? SAVE_OUTCOME_SAVED
316
+ : outcome === SAVE_OUTCOME_STALE
317
+ ? SAVE_OUTCOME_STALE
318
+ : SAVE_OUTCOME_DROPPED;
319
+ for (const w of waiters) w.resolve(settled);
308
320
  }
309
321
 
310
322
  /**
@@ -531,6 +543,9 @@ function _postAsyncWrite(
531
543
  { forceFull = false, retriedFull = false } = {}
532
544
  ) {
533
545
  const reqId = ++_saveWorkerReqId;
546
+ // Before the bytes are built: what this write will prove durable once it
547
+ // lands (store/transcript-durability.mjs).
548
+ const transcript = captureTranscriptSnapshot(session);
534
549
  // The wire payload is built at POST time from the live session: N calls
535
550
  // coalesced behind one in-flight write project ONCE (newest state), and a
536
551
  // pure-append turn ships only the tail (see _buildWirePayload).
@@ -548,6 +563,7 @@ function _postAsyncWrite(
548
563
  _saveWorkerPending.set(reqId, {
549
564
  id,
550
565
  session,
566
+ transcript,
551
567
  attempt,
552
568
  opts,
553
569
  summaryVersion,
@@ -567,7 +583,12 @@ function _postAsyncWrite(
567
583
  // Cheap key compare; posts only when the fault config/env moved.
568
584
  _syncFaultToWorker(w);
569
585
  const { attempt: _attempt, ...wireForPost } = wire;
570
- w.postMessage({ ...wireForPost, id, opts, reqId });
586
+ // The worker's process.env is a copy taken at spawn, so a data dir that
587
+ // moved since (a pristine headless run per page) would otherwise keep
588
+ // receiving writes in the first run's removed directory. The dir current
589
+ // at POST time travels with the write: a write still queued in the worker
590
+ // when the env flips lands where its own session lives.
591
+ w.postMessage({ ...wireForPost, id, opts, reqId, dataDir: getPluginData() });
571
592
  // Ref AFTER successful postMessage so a queue/throw failure path does
572
593
  // not leave the worker held alive with no pending message. Paired with
573
594
  // the unref in the message handler when count hits 0.
@@ -16,12 +16,12 @@ import { deleteSession } from '../session-delete.mjs';
16
16
  import { readSweepRecord } from './sweep-record.mjs';
17
17
 
18
18
  const DAY_MS = 24 * 60 * 60 * 1000;
19
- export const SUB_AGENT_ARCHIVE_AFTER_MS = 3 * DAY_MS;
20
- export const SUB_AGENT_DELETE_AFTER_MS = 30 * DAY_MS;
19
+ const SUB_AGENT_ARCHIVE_AFTER_MS = 3 * DAY_MS;
20
+ const SUB_AGENT_DELETE_AFTER_MS = 30 * DAY_MS;
21
21
  const PERMANENT_ID = /^sess_(?:home|room|rmem)_/;
22
22
  // The desktop change an automatic archive applies: that one session, no
23
23
  // cascade (an explicit archive of a parent takes its sub-agents along).
24
- export const SUB_AGENT_AUTO_ARCHIVE = Object.freeze({ archived: true, cascade: false });
24
+ const SUB_AGENT_AUTO_ARCHIVE = Object.freeze({ archived: true, cascade: false });
25
25
 
26
26
  const positive = (value) => (Number(value) > 0 ? Number(value) : 0);
27
27
 
@@ -0,0 +1,88 @@
1
+ // store/transcript-durability.mjs
2
+ // What a session's canonical record durably holds, as evidence for the turn
3
+ // checkpoints (manager/turn-checkpoint.mjs) — never inferred from in-memory
4
+ // freshness.
5
+ //
6
+ // transcriptEpoch persisted. Names one append-only lineage of the
7
+ // transcript: a record of epoch E with `length` messages
8
+ // agrees with every other record of E on their common
9
+ // prefix. A history rewrite (compaction, a prefix edit,
10
+ // a recovery splice) mints a new epoch.
11
+ // turnCoverage persisted. The token of the last turn whose finalized
12
+ // transcript this record carries, so a checkpoint the
13
+ // record already covers is deleted, never replayed.
14
+ //
15
+ // A landed save records, per session object, the exact epoch, coverage and
16
+ // message references it serialized (captured when the bytes were built, so a
17
+ // coalesced or later-built write reports what it actually wrote). Only that
18
+ // record proves what is on disk; a settled promise or an idle queue does not.
19
+ import { randomUUID } from 'node:crypto';
20
+ import { basename, dirname } from 'node:path';
21
+ import { sessionPath } from './session-paths.mjs';
22
+ import { _storedSessionFromFile, STORED_SESSION_UNREADABLE } from './serialize.mjs';
23
+
24
+ const _landed = new WeakMap(); // session → { epoch, coverage, refs, saveEpoch }
25
+ const _snapshotHooks = new WeakMap(); // session → () => void
26
+
27
+ export function mintTranscriptEpoch() {
28
+ return randomUUID();
29
+ }
30
+
31
+ /** Run `hook` before every serialization of `session` (the turn checkpoint
32
+ * journals a history rewrite, under a new epoch, before any record carries
33
+ * it). Returns the unregister function. */
34
+ export function setTranscriptSnapshotHook(session, hook) {
35
+ _snapshotHooks.set(session, hook);
36
+ return () => {
37
+ if (_snapshotHooks.get(session) === hook) _snapshotHooks.delete(session);
38
+ };
39
+ }
40
+
41
+ /** The transcript identity a save of `session` is about to serialize. */
42
+ export function captureTranscriptSnapshot(session) {
43
+ _snapshotHooks.get(session)?.();
44
+ return {
45
+ epoch: typeof session.transcriptEpoch === 'string' ? session.transcriptEpoch : null,
46
+ coverage: typeof session.turnCoverage === 'string' ? session.turnCoverage : null,
47
+ refs: Array.isArray(session.messages) ? session.messages.slice() : [],
48
+ };
49
+ }
50
+
51
+ /** A save of `snapshot` (issued under `saveEpoch`) landed on disk. */
52
+ export function recordLandedTranscript(session, snapshot, saveEpoch) {
53
+ if (!session || !snapshot) return;
54
+ const current = _landed.get(session);
55
+ const issued = Number.isFinite(saveEpoch) ? saveEpoch : 0;
56
+ if (current && current.saveEpoch > issued) return;
57
+ _landed.set(session, { ...snapshot, saveEpoch: issued });
58
+ }
59
+
60
+ export function landedTranscript(session) {
61
+ return (session && _landed.get(session)) || null;
62
+ }
63
+
64
+ /** Whether the last landed save of `session` holds exactly its transcript. */
65
+ export function landedTranscriptMatches(session) {
66
+ const landed = landedTranscript(session);
67
+ const messages = Array.isArray(session?.messages) ? session.messages : null;
68
+ if (!landed || !messages || landed.epoch !== session.transcriptEpoch) return false;
69
+ if (landed.refs.length !== messages.length) return false;
70
+ return landed.refs.every((message, index) => message === messages[index]);
71
+ }
72
+
73
+ /** The durable transcript a turn opening now would be taken against
74
+ * (`{ epoch, refs }`), or null when no landed save holds `session`'s
75
+ * current transcript exactly. */
76
+ export function durableTranscriptBase(session) {
77
+ if (!landedTranscriptMatches(session)) return null;
78
+ const landed = landedTranscript(session);
79
+ return { epoch: landed.epoch, refs: landed.refs };
80
+ }
81
+
82
+ /** The canonical record of `id` as it is on disk (never a cached or live
83
+ * copy), or null when it is absent, unreadable or not this session's. */
84
+ export function readDurableSessionRecord(id) {
85
+ const path = sessionPath(id);
86
+ const record = _storedSessionFromFile(dirname(path), basename(path));
87
+ return record && record !== STORED_SESSION_UNREADABLE ? record : null;
88
+ }
@@ -42,6 +42,7 @@ import {
42
42
  import { dataDir, storedAgentWorkerIndexPath, storedLeadWorkerIndexPath } from './store-summary-locations.mjs';
43
43
  import { cleanValue, isStoredSessionId, positiveNumber } from './store-summary-fields.mjs';
44
44
  import { sessionUsageTotals } from '../../../shared/session-usage-totals.mjs';
45
+ import { todoProgress } from '../runtime-core/todo-list.mjs';
45
46
 
46
47
  const WORKING_AGENT_STATUS =
47
48
  /^(?:connecting|requesting|streaming|tool[-_\s]?running|running|queued|pending|starting|cancelling)$/i;
@@ -327,6 +328,7 @@ const WORKER_HEADER_FIELDS = [
327
328
  'totalInputTokens',
328
329
  'totalOutputTokens',
329
330
  'totalToolCalls',
331
+ 'todo',
330
332
  ...SESSION_CANCEL_FIELDS,
331
333
  ];
332
334
 
@@ -584,6 +586,7 @@ function projectChildWorkerRow(row, context) {
584
586
  // beside a header that records a cancel-unconfirmed (possibly running)
585
587
  // process. A new row may precede its first session save (null header).
586
588
  const session = readWorkerSessionHeader(sessionId);
589
+ const todo = todoProgress(session);
587
590
  const declaredStatus = cleanValue(row.status) || 'running';
588
591
  const declaredStage = cleanValue(row.stage || row.status) || 'running';
589
592
  const declaredWorking = WORKING_AGENT_STATUS.test(workerLifecycle(row) || 'running');
@@ -649,6 +652,8 @@ function projectChildWorkerRow(row, context) {
649
652
  clientHostPid: positiveNumber(row.clientHostPid || session?.clientHostPid, 0) || null,
650
653
  taskId: cleanValue(row.task_id || row.taskId) || null,
651
654
  ...sessionUsageTotals(session),
655
+ // The agent's own step list as counts only; a closed agent keeps its last.
656
+ ...(todo ? { todo } : {}),
652
657
  };
653
658
  }
654
659
 
@@ -19,7 +19,7 @@
19
19
  import { readFileSync, statSync } from 'node:fs';
20
20
  import { dirname, join } from 'node:path';
21
21
 
22
- export const SUMMARY_LOG_FILENAME = 'session-summaries.log';
22
+ const SUMMARY_LOG_FILENAME = 'session-summaries.log';
23
23
 
24
24
  export function summaryLogPath(indexPath) {
25
25
  return join(dirname(indexPath), SUMMARY_LOG_FILENAME);
@@ -15,6 +15,7 @@ import { join } from 'node:path';
15
15
  import { desktopSession, positiveNumber } from './store-summary-fields.mjs';
16
16
  import { compactionArchiveRange, readCompactionArchivePage } from './compaction-archive.mjs';
17
17
  import { dataDir } from './store-summary-locations.mjs';
18
+ import { todoSnapshot } from '../runtime-core/todo-list.mjs';
18
19
 
19
20
  /** The parse/recovery/projection body of a stored transcript read. Runs once
20
21
  * per distinct content; its result is shared read-only through the cache. */
@@ -40,7 +41,9 @@ export async function prepareStoredTranscriptBase(sessionId, doc, options) {
40
41
  // A running turn is never recovered: project its checkpoint read-only.
41
42
  if (isSessionTurnLive(sessionId)) projectCheckpoint();
42
43
  else {
43
- session = recoverSessionAfterProcessRestart(sessionId) || session;
44
+ // `doc` is the record exactly as this read parsed it from disk: the
45
+ // durable evidence recovery decides on.
46
+ session = recoverSessionAfterProcessRestart(sessionId, doc) || session;
44
47
  // A closed session keeps its marker unrecovered.
45
48
  if (session?.activeTurnCheckpoint) projectCheckpoint();
46
49
  }
@@ -78,9 +81,10 @@ export async function prepareStoredTranscriptBase(sessionId, doc, options) {
78
81
  }
79
82
  const messages = Array.isArray(session.messages) ? session.messages : [];
80
83
  const hasConversationActivity = messages.some((message) => message?.role === 'user');
81
- const measuredStats = contextMeasurementStats({
82
- measurement: sessionContextMeasurement(session, hasConversationActivity),
83
- });
84
+ const measuredStats = {
85
+ ...contextMeasurementStats({ measurement: sessionContextMeasurement(session, hasConversationActivity) }),
86
+ usageReadout: session.usageReadout || null,
87
+ };
84
88
  const contextWindow = positiveNumber(session.contextWindow);
85
89
  const rawContextWindow = positiveNumber(session.rawContextWindow, contextWindow);
86
90
  const displayContextWindow = positiveNumber(session.compactBoundaryTokens, contextWindow);
@@ -218,7 +222,7 @@ function budgetedEdge(from, step, length, rows, byteBudget, sizeOf) {
218
222
  }
219
223
 
220
224
  /** The most rows one historical-position read carries, whatever it asks. */
221
- export const CURSOR_PAGE_MAX_ROWS = 512;
225
+ const CURSOR_PAGE_MAX_ROWS = 512;
222
226
 
223
227
  /** A cursor's page size: 1 to CURSOR_PAGE_MAX_ROWS rows. */
224
228
  export function cursorPageRows(maxRows) {
@@ -305,6 +309,11 @@ export async function windowStoredTranscript(sessionId, base, options) {
305
309
  cwd: session.cwd || '',
306
310
  desktopSession: desktopSession(session.desktopSession, session.cwd),
307
311
  workflow: session.workflow || null,
312
+ todo: todoSnapshot(session),
313
+ // Turns whose checkpoints recovery could not apply, kept on disk.
314
+ ...(Array.isArray(session.unrecoveredTurns) && session.unrecoveredTurns.length
315
+ ? { unrecoveredTurns: session.unrecoveredTurns }
316
+ : {}),
308
317
  ...contextFields,
309
318
  };
310
319
  }
@@ -337,8 +337,8 @@ const baseBuilds = createBaseBuilds({ build: buildBase });
337
337
  // history) and a page of a retained one costs ~0.2 s, a rebuilt one ~1 s. 96
338
338
  // MB keeps the largest sessions seen (~78 MB records) smooth; 128 MB holds
339
339
  // one of those beside a few ordinary ones.
340
- export const WORKER_HISTORY_SESSION_MAX_BYTES = 96 * 1024 * 1024;
341
- export const WORKER_HISTORY_MAX_BYTES = 128 * 1024 * 1024;
340
+ const WORKER_HISTORY_SESSION_MAX_BYTES = 96 * 1024 * 1024;
341
+ const WORKER_HISTORY_MAX_BYTES = 128 * 1024 * 1024;
342
342
 
343
343
  /**
344
344
  * The display histories one worker retains (`{ value, rowBytes, bytes }`,
@@ -31,6 +31,7 @@ export {
31
31
  _removeSessionSummary,
32
32
  } from './store-summary-index.mjs';
33
33
  import { _sessionForDisk, _ensureLifecycleFields } from './store/serialize.mjs';
34
+ import { captureTranscriptSnapshot, recordLandedTranscript } from './store/transcript-durability.mjs';
34
35
  import {
35
36
  _cacheSessionSummary,
36
37
  _rollbackCachedSessionSummary,
@@ -337,6 +338,7 @@ function _doSaveSync(payload) {
337
338
  // rebuilt from them, never re-read from the later mutable live session.
338
339
  let attempted = null;
339
340
  try {
341
+ const transcript = captureTranscriptSnapshot(session);
340
342
  const disk = _sessionForDisk(session);
341
343
  attempted = JSON.stringify(disk);
342
344
  // The lifecycle these exact bytes carry, recorded with the commit stamp.
@@ -382,6 +384,7 @@ function _doSaveSync(payload) {
382
384
  _recordOwnSessionCommit(target, scratch, lifecycle);
383
385
  _publishLandedWriteEpoch(opts, epoch);
384
386
  _untrackSaveTmp(tmp);
387
+ recordLandedTranscript(session, transcript, epoch);
385
388
  if (mayMutate()) {
386
389
  if (publishSummary) _queueSessionSummaryUpsert(session, summaryVersion);
387
390
  _clearSaveStateIfCurrent(id, epoch);
@@ -627,6 +630,7 @@ async function _doSave(payload) {
627
630
  // payload, never the live session as it looks at settlement time.
628
631
  let attempted = null;
629
632
  try {
633
+ const transcript = captureTranscriptSnapshot(session);
630
634
  const disk = _sessionForDisk(session);
631
635
  attempted = JSON.stringify(disk);
632
636
  // Captured before the await: the lifecycle of exactly these bytes.
@@ -671,6 +675,7 @@ async function _doSave(payload) {
671
675
  _recordOwnSessionCommit(target, scratch, lifecycle);
672
676
  _publishLandedWriteEpoch(opts, epoch);
673
677
  _untrackSaveTmp(tmp);
678
+ recordLandedTranscript(session, transcript, epoch);
674
679
  if (mayMutate()) {
675
680
  _queueSessionSummaryUpsert(session, summaryVersion);
676
681
  _clearSaveStateIfCurrent(id, epoch);
@@ -45,6 +45,11 @@ const RUNTIME_CONTROL_SOURCES = new Set([
45
45
  'task-notification',
46
46
  ]);
47
47
  const ENVELOPE_OPEN_RE = new RegExp(`^\\s*<${SYNTHETIC_USER_ENVELOPE_TAG}\\b`, 'i');
48
+ // The stored crash marker reads like a user cancel ("Request interrupted ..."),
49
+ // and models answered it with "stopped as you asked" and dropped the work.
50
+ // On the wire it names the restart and asks to resume.
51
+ const PROCESS_RESTART_MARKER = '[Request interrupted by process restart]';
52
+ const PROCESS_RESTART_WIRE_TEXT = '[mixdog-runtime] The app restarted mid-turn. Continue from where you left off.';
48
53
 
49
54
  function messageText(message) {
50
55
  if (typeof message?.content === 'string') return message.content;
@@ -128,7 +133,8 @@ export function projectSyntheticUserEnvelopes(messages) {
128
133
  const projected = messages.map((message) => {
129
134
  const kind = classifySyntheticUserMessage(message);
130
135
  if (!kind) return message;
131
- const content = envelopeContent(message.content, kind);
136
+ const source = messageText(message).trim() === PROCESS_RESTART_MARKER ? PROCESS_RESTART_WIRE_TEXT : message.content;
137
+ const content = envelopeContent(source, kind);
132
138
  if (content === message.content) return message;
133
139
  changed = true;
134
140
  stats.wrapped += 1;
@@ -119,7 +119,15 @@ export function matchTailReminder(context, rules = TAIL_REMINDER_RULES) {
119
119
  }
120
120
 
121
121
  /** `messages` plus the tail reminder for this request (a copy; the input is untouched). */
122
- export function withTailReminder(messages, { session, provider, model }, language = _buildLeadLanguage()) {
122
+ // The session's own response language (as its prompt and settings notices
123
+ // carry it, session/manager/prompt-settings.mjs); a session without one (an
124
+ // agent, an older record) reads the config.
125
+ export function withTailReminder(
126
+ messages,
127
+ { session, provider, model },
128
+ // null is a recorded "no language", not a missing record.
129
+ language = session?.responseLanguage !== undefined ? session.responseLanguage : _buildLeadLanguage()
130
+ ) {
123
131
  const text = matchTailReminder({
124
132
  provider: session?.provider || provider?.name || '',
125
133
  model,