mixdog 1.4.0 → 1.4.2

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 (493) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  4. package/scripts/gate-local.mjs +183 -63
  5. package/scripts/latest-guard.mjs +21 -9
  6. package/scripts/lib/gate-scheduler.mjs +138 -32
  7. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  8. package/scripts/lib/isolated-test-env.mjs +10 -5
  9. package/scripts/lib/run-node-tests.mjs +91 -66
  10. package/scripts/lib/scoped-temp.d.mts +16 -0
  11. package/scripts/lib/scoped-temp.mjs +124 -0
  12. package/scripts/lib/stale-temp.mjs +44 -0
  13. package/scripts/lib/test-failure-records.mjs +14 -1
  14. package/scripts/lib/test-failure-summary.mjs +16 -4
  15. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  16. package/scripts/post-release-check.mjs +1 -6
  17. package/scripts/pptx-kit-index.mjs +17 -5
  18. package/scripts/suite-health.mjs +115 -24
  19. package/scripts/test-data-dir.mjs +10 -7
  20. package/scripts/test.mjs +9 -14
  21. package/src/agents/advisor/AGENT.md +4 -4
  22. package/src/agents/advisor/agent.json +1 -1
  23. package/src/agents/explore/AGENT.md +22 -0
  24. package/src/agents/explore/agent.json +6 -0
  25. package/src/app.mjs +4 -0
  26. package/src/cli.mjs +7 -0
  27. package/src/defaults/agents.json +7 -0
  28. package/src/defaults/shipped-history.json +23 -2
  29. package/src/defaults/skills/browser-use/SKILL.md +10 -5
  30. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  31. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  32. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  33. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  34. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  35. package/src/defaults/skills/computer-use/SKILL.md +16 -4
  36. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  37. package/src/defaults/skills/setup/references/actions.md +12 -6
  38. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  39. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  40. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  41. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  42. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  43. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  44. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  45. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  46. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  47. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  48. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  49. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  50. package/src/defaults/skills/visualize/SKILL.md +6 -2
  51. package/src/lib/rules-builder.cjs +35 -42
  52. package/src/output-styles/common.md +2 -2
  53. package/src/output-styles/simple.md +1 -1
  54. package/src/rules/agent/AGENT.md +5 -4
  55. package/src/rules/lead/LEAD.md +12 -7
  56. package/src/rules/shared/10-tool-workflow.md +3 -1
  57. package/src/rules/shared/20-research.md +3 -6
  58. package/src/rules/shared/40-editing.md +1 -0
  59. package/src/rules/shared/45-destructive.md +0 -2
  60. package/src/rules/shared/50-execution.md +2 -0
  61. package/src/rules/shared/60-verification.md +10 -9
  62. package/src/rules/shared/70-delivery.md +8 -9
  63. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  64. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  65. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
  66. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  67. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  68. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  69. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  70. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
  71. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  72. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  74. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  75. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  76. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  77. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  78. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  79. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
  80. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  81. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
  82. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  83. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
  84. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  85. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  86. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  87. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  89. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  90. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
  91. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
  92. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  93. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  94. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  95. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  97. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
  99. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  100. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  101. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  102. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  103. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  104. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  105. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  106. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
  108. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  109. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  110. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  113. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  114. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  115. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  117. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  119. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  125. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  127. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  129. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  130. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  131. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  132. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  133. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
  135. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  136. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
  137. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  139. package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
  140. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  141. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  142. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  143. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
  145. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
  147. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
  148. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
  150. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  151. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  152. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  153. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  154. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  155. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  156. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
  157. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  158. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  159. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  160. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  161. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
  162. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  163. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
  164. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
  165. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  166. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  167. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
  168. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  169. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  170. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
  171. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
  172. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  173. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  174. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
  175. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  179. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
  180. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  181. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  184. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
  185. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  186. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
  187. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
  188. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
  189. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  190. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
  192. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
  193. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  194. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  195. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
  196. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  197. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
  198. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
  199. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  200. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  201. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
  202. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
  203. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  204. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  206. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  207. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
  208. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
  209. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  210. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  211. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  212. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
  213. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  214. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  215. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  216. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
  217. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
  218. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
  219. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  220. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
  221. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  223. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  224. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  225. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  226. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  227. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  228. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  229. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  230. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  231. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  232. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  233. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  234. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  235. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  236. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  237. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  238. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  239. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  240. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  241. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  242. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  243. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  244. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
  245. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  246. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  248. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  249. package/src/runtime/channels/lib/whisper-port.mjs +29 -11
  250. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  251. package/src/runtime/computer-bridge/action-schema.mjs +2 -2
  252. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  253. package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
  254. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  255. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  256. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  257. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  258. package/src/runtime/explore/explorer.mjs +93 -0
  259. package/src/runtime/explore/tool-defs.mjs +35 -0
  260. package/src/runtime/explore/tool.mjs +123 -0
  261. package/src/runtime/local-provider/server-process.mjs +2 -1
  262. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  263. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  264. package/src/runtime/memory/index.mjs +4 -0
  265. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  266. package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
  267. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  268. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  269. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  270. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  271. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  272. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  273. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  274. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  275. package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
  276. package/src/runtime/office/quality/design-review.mjs +1 -5
  277. package/src/runtime/office/quality/document-brief.mjs +1 -5
  278. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  279. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  280. package/src/runtime/shared/agent-route-config.mjs +17 -2
  281. package/src/runtime/shared/atomic-file.mjs +103 -2
  282. package/src/runtime/shared/background-tasks.mjs +15 -6
  283. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  284. package/src/runtime/shared/connector-accounts.mjs +5 -5
  285. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  286. package/src/runtime/shared/connector-catalog.mjs +60 -52
  287. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  288. package/src/runtime/shared/defaults-separation.mjs +1 -4
  289. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  290. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  291. package/src/runtime/shared/file-has-data.mjs +26 -2
  292. package/src/runtime/shared/file-lock.mjs +13 -15
  293. package/src/runtime/shared/git-launch.d.mts +9 -0
  294. package/src/runtime/shared/git-launch.mjs +213 -0
  295. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  296. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  297. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  298. package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
  299. package/src/runtime/shared/llm/cost.mjs +11 -2
  300. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  301. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  302. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  303. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  304. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  305. package/src/runtime/shared/loopback-listener.mjs +25 -2
  306. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  307. package/src/runtime/shared/model-route-chain.mjs +22 -4
  308. package/src/runtime/shared/open-url.mjs +10 -0
  309. package/src/runtime/shared/provider-accounts.mjs +52 -2
  310. package/src/runtime/shared/session-activity-signal.mjs +14 -0
  311. package/src/runtime/shared/session-scratch.mjs +37 -0
  312. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  313. package/src/runtime/shared/staged-update.mjs +47 -2
  314. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  315. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  316. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  317. package/src/runtime/shared/tool-surface.mjs +113 -36
  318. package/src/runtime/shared/tool-work-units.mjs +6 -0
  319. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  320. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  321. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  322. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  323. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  324. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  325. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  326. package/src/runtime/shared/worktrees/git.mjs +72 -0
  327. package/src/runtime/shared/worktrees/manager.mjs +285 -168
  328. package/src/runtime/shared/worktrees/test-support.mjs +6 -10
  329. package/src/runtime/tidy/install.mjs +95 -15
  330. package/src/runtime/tidy/languages.mjs +17 -15
  331. package/src/runtime/tidy/process.mjs +7 -4
  332. package/src/runtime/tidy/resolve.mjs +19 -16
  333. package/src/runtime/tidy/runners/biome.mjs +3 -3
  334. package/src/runtime/tidy/tool.mjs +1 -1
  335. package/src/runtime/tidy/worktree.mjs +10 -6
  336. package/src/runtime/visualize-bridge/client.mjs +6 -1
  337. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  338. package/src/runtime/web-search/index.mjs +1 -1
  339. package/src/session-runtime/agent-home-delivery.mjs +41 -0
  340. package/src/session-runtime/agent-task-sessions.mjs +458 -312
  341. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  342. package/src/session-runtime/boot/tools.mjs +7 -1
  343. package/src/session-runtime/bot-status.mjs +107 -101
  344. package/src/session-runtime/bot-target.mjs +38 -0
  345. package/src/session-runtime/channel-config-api.mjs +6 -1
  346. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  347. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  348. package/src/session-runtime/conversation-import/discovery.mjs +401 -36
  349. package/src/session-runtime/conversation-import/service.mjs +35 -8
  350. package/src/session-runtime/handoff-attachments.mjs +82 -31
  351. package/src/session-runtime/held-requests.mjs +3 -1
  352. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
  353. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
  354. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  355. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  356. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  357. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
  358. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  359. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
  360. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  361. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  362. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  363. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  364. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  365. package/src/session-runtime/model-route/set-route.mjs +11 -2
  366. package/src/session-runtime/provider-auth/login.mjs +2 -2
  367. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  368. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  369. package/src/session-runtime/provider-models.mjs +4 -2
  370. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  371. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  372. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  373. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  374. package/src/session-runtime/resource-recall-api.mjs +6 -1
  375. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  376. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  377. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  378. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  379. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  380. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  381. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  382. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  383. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
  384. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  385. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  386. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  387. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  388. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  389. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  390. package/src/session-runtime/services/provider-admin.mjs +283 -11
  391. package/src/session-runtime/session-turn-api.mjs +2 -0
  392. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  393. package/src/session-runtime/settings-system-api.mjs +1 -1
  394. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  395. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  396. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  397. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  398. package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
  399. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  400. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  401. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  402. package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
  403. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  404. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  405. package/src/session-runtime/workspace/session-view.mjs +149 -0
  406. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  407. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  408. package/src/session-runtime/workspace/transitions.mjs +108 -1041
  409. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  410. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  411. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  412. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  413. package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
  414. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  415. package/src/standalone/daemon-session-control.mjs +169 -138
  416. package/src/standalone/daemon.mjs +7 -0
  417. package/src/standalone/desktop-service-registry.mjs +9 -0
  418. package/src/standalone/local-session-runtime.mjs +5 -1
  419. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  420. package/src/standalone/session-client.mjs +5 -1
  421. package/src/standalone/session-frame-stream.mjs +83 -11
  422. package/src/standalone/session-protocol.mjs +4 -0
  423. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  424. package/src/standalone/session-service/agent-tree.mjs +45 -9
  425. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  426. package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
  427. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  428. package/src/standalone/session-service/stored-reader.mjs +127 -40
  429. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  430. package/src/tui/App.jsx +154 -164
  431. package/src/tui/app/app-view/floating-panel.jsx +10 -4
  432. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  433. package/src/tui/app/clipboard.mjs +21 -10
  434. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  435. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  436. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  437. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  438. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  439. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  440. package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
  441. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  442. package/src/tui/app/provider-setup-picker.mjs +29 -18
  443. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  444. package/src/tui/app/slash-commands.mjs +1 -0
  445. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  446. package/src/tui/app/slash-dispatch.mjs +1 -1
  447. package/src/tui/app/use-btw-panel.mjs +29 -0
  448. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  449. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  450. package/src/tui/app/use-mouse-input.mjs +11 -0
  451. package/src/tui/app/use-onboarding-start.mjs +8 -3
  452. package/src/tui/app/use-panel-transition.mjs +10 -1
  453. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  454. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  455. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  456. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  457. package/src/tui/app/use-selection-focus.mjs +11 -2
  458. package/src/tui/app/use-selection-geometry.mjs +18 -9
  459. package/src/tui/app/use-selection-paint.mjs +4 -4
  460. package/src/tui/app/use-selection-stitch.mjs +3 -3
  461. package/src/tui/app/use-slash-palette.mjs +62 -0
  462. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  463. package/src/tui/app/use-transcript-activity.mjs +47 -32
  464. package/src/tui/app/use-transcript-follow.mjs +12 -4
  465. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  466. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  467. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  468. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  469. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  470. package/src/tui/app/use-transcript-window.mjs +21 -11
  471. package/src/tui/app/use-tui-ready.mjs +18 -0
  472. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  473. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  474. package/src/tui/components/Picker.jsx +27 -3
  475. package/src/tui/components/QuestionPanel.jsx +13 -4
  476. package/src/tui/components/SignInUrl.jsx +68 -0
  477. package/src/tui/components/TextEntryPanel.jsx +76 -46
  478. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  479. package/src/tui/dist/index.mjs +1963 -1361
  480. package/src/tui/session/goal-turn-state.mjs +6 -2
  481. package/src/tui/session/session-api/controls.mjs +4 -0
  482. package/src/tui/session/session-api/extensions.mjs +8 -0
  483. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  484. package/src/tui/session/session-api/intake.mjs +2 -0
  485. package/src/tui/session/session-api/integrations.mjs +2 -2
  486. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  487. package/src/tui/session/session-api/settings.mjs +2 -2
  488. package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
  489. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  490. package/src/tui/session/transcript-spill.mjs +92 -4
  491. package/src/tui/session/transcript-store.mjs +2 -0
  492. package/src/ui/statusline-agents.mjs +1 -3
  493. package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
@@ -3,7 +3,7 @@
3
3
  import { compareCodePoints } from '../../../../shared/code-point-order.mjs';
4
4
  import { getMcpTools } from '../../mcp/client.mjs';
5
5
  import { getInternalTools } from '../../internal-tools.mjs';
6
- import { BUILTIN_TOOLS, bindShellKind } from '../../tools/builtin/builtin-tools.mjs';
6
+ import { BUILTIN_TOOLS, bindAgentOwner, bindShellKind } from '../../tools/builtin/builtin-tools.mjs';
7
7
  import { resolveShellFor } from '../../tools/builtin/shell-runtime.mjs';
8
8
  import { PATCH_TOOL_DEFS } from '../../tools/patch-tool-defs.mjs';
9
9
  import { CODE_GRAPH_TOOL_DEFS } from '../../tools/code-graph-tool-defs.mjs';
@@ -51,7 +51,7 @@ const FILESYSTEM_TOOL_NAMES = new Set(['code_graph', 'find', 'glob', 'list', 'gr
51
51
  const READONLY_FS_TOOL_NAMES = new Set(['code_graph', 'find', 'glob', 'list', 'grep', 'read']);
52
52
 
53
53
  // Lead-only control tools: never part of a worker/sub-agent session surface.
54
- export const AGENT_WORKER_DENIED_TOOLS = Object.freeze(new Set(['agent', 'ask_user', 'session']));
54
+ export const AGENT_WORKER_DENIED_TOOLS = Object.freeze(new Set(['agent', 'ask_user', 'handoff_user', 'session']));
55
55
 
56
56
  export function finalizeSessionToolList(
57
57
  tools,
@@ -70,7 +70,7 @@ export function finalizeSessionToolList(
70
70
  }
71
71
  if (ownerIsAgent) {
72
72
  out = out.filter((t) => !AGENT_WORKER_DENIED_TOOLS.has(String(t?.name || '').toLowerCase()));
73
- out = orderSessionTools(out);
73
+ out = orderSessionTools(out.map(bindAgentOwner));
74
74
  }
75
75
  return out;
76
76
  }
@@ -91,6 +91,11 @@ const ALL_BUILTIN_SESSION_TOOLS = orderSessionTools(
91
91
  _dedupByName([...BUILTIN_TOOLS, ...PATCH_TOOL_DEFS, ...CODE_GRAPH_TOOL_DEFS])
92
92
  );
93
93
 
94
+ /** The read-only filesystem tools (`tools:readonly`), in session route order. */
95
+ export function readOnlyFilesystemTools() {
96
+ return ALL_BUILTIN_SESSION_TOOLS.filter((t) => READONLY_FS_TOOL_NAMES.has(t.name));
97
+ }
98
+
94
99
  export function resolveSessionTools(
95
100
  toolSpec,
96
101
  skills,
@@ -154,7 +159,7 @@ function _computeBaseTools(toolSpec, mcp, skillTools) {
154
159
  addMany(ALL_BUILTIN_SESSION_TOOLS.filter((t) => FILESYSTEM_TOOL_NAMES.has(t.name)));
155
160
  break;
156
161
  case 'tools:readonly':
157
- addMany(ALL_BUILTIN_SESSION_TOOLS.filter((t) => READONLY_FS_TOOL_NAMES.has(t.name)));
162
+ addMany(readOnlyFilesystemTools());
158
163
  break;
159
164
  case 'tools:shell':
160
165
  case 'tools:analysis':
@@ -186,8 +191,7 @@ function _computeBaseTools(toolSpec, mcp, skillTools) {
186
191
  case 'mcp':
187
192
  return orderSessionTools(_dedupByName([...mcp, ...skillTools]));
188
193
  case 'readonly': {
189
- const readTools = ALL_BUILTIN_SESSION_TOOLS.filter((t) => READONLY_FS_TOOL_NAMES.has(t.name));
190
- return orderSessionTools(_dedupByName([...readTools, ...mcp, ...skillTools]));
194
+ return orderSessionTools(_dedupByName([...readOnlyFilesystemTools(), ...mcp, ...skillTools]));
191
195
  }
192
196
  // 'full' and any unknown spec.
193
197
  default:
@@ -93,6 +93,10 @@ export function captureTurnCheckpointContextState(session, messages, currentUser
93
93
  ),
94
94
  ...(kind === 'post_compact' ? { usageSnapshot: { ...usageSnapshot } } : {}),
95
95
  compaction: compactCheckpointState(session.compaction),
96
+ // Diagnostic only: the prefix fingerprint of the turn's last settled
97
+ // request (anthropic-prefix-trace.mjs), so the first request after a
98
+ // crash is still traced against it.
99
+ ...(session.providerPrefixBaseline ? { prefixBaseline: session.providerPrefixBaseline } : {}),
96
100
  };
97
101
  }
98
102
 
@@ -224,6 +224,9 @@ export function recoverTurnCheckpoint(session) {
224
224
  // mark it unanchored so the first resumed send cannot compact from a gross
225
225
  // whole-transcript estimate.
226
226
  const contextStateRestored = restoreTurnCheckpointContextState(session, checkpoint);
227
+ // Diagnostic only (anthropic-prefix-trace.mjs): the turn's last settled
228
+ // request, whatever the context reading's alignment.
229
+ if (checkpoint.contextState?.prefixBaseline) session.providerPrefixBaseline = checkpoint.contextState.prefixBaseline;
227
230
  delete session.activeTurnCheckpoint;
228
231
  delete session.providerState;
229
232
  delete session._providerPrefixGuardState;
@@ -110,6 +110,15 @@ function finalizeInterruptedTurn({
110
110
  };
111
111
  }
112
112
 
113
+ // Killed with a request on the wire (phase 'streaming' after a tool run):
114
+ // the provider received the transcript up to its last message, and may
115
+ // have cached it. Stamping that message keeps what this interruption
116
+ // appends (the marker, the next prompt) out of the tool_result it ends —
117
+ // see toAnthropicMessages. The stamp is meta only; no wire bytes change.
118
+ const sentTail = messages.length - 1;
119
+ if (phase === 'streaming' && (messages[sentTail]?.role === 'tool' || messages[sentTail]?.role === 'user')) {
120
+ messages[sentTail] = { ...messages[sentTail], meta: { ...messages[sentTail].meta, sentRequestTail: true } };
121
+ }
113
122
  appendObservedTurnOutput(messages, {
114
123
  partialAssistantContent,
115
124
  partialReasoningContent,
@@ -186,8 +195,8 @@ function appendObservedTurnOutput(
186
195
  }
187
196
  }
188
197
 
189
- export function createTurnInterruptionTracker() {
190
- const state = {
198
+ function newInterruptionState() {
199
+ return {
191
200
  responseStarted: false,
192
201
  partialAssistantContent: '',
193
202
  tombstonedAssistantContent: '',
@@ -204,7 +213,10 @@ export function createTurnInterruptionTracker() {
204
213
  tombEpoch: 0,
205
214
  callsEpoch: 0,
206
215
  };
216
+ }
207
217
 
218
+ /** Streamed assistant text, including the tombstone of a retracted attempt. */
219
+ function textBufferMethods(state) {
208
220
  return {
209
221
  recordTextDelta(chunk) {
210
222
  const value = String(chunk ?? '');
@@ -254,33 +266,12 @@ export function createTurnInterruptionTracker() {
254
266
  state.tombEpoch += 1;
255
267
  return true;
256
268
  },
257
- hasResponseStarted() {
258
- return state.responseStarted;
259
- },
260
- recordReasoningDelta(chunk) {
261
- const value = String(chunk ?? '');
262
- if (!value) return;
263
- state.responseStarted = true;
264
- state.partialReasoningContent += value;
265
- },
266
- recordAssistantText(text) {
267
- const value = String(text ?? '');
268
- if (!value.trim()) return;
269
- state.responseStarted = true;
270
- // Buffered providers report the whole segment here; streaming
271
- // providers already accumulated the same segment via text deltas.
272
- if (!state.partialAssistantContent.trim()) state.partialAssistantContent += value;
273
- },
274
- markAssistantMessageCommitted() {
275
- state.partialAssistantContent = '';
276
- state.tombstonedAssistantContent = '';
277
- state.partialReasoningContent = '';
278
- state.observedToolCalls.clear();
279
- state.textEpoch += 1;
280
- state.reasoningEpoch += 1;
281
- state.tombEpoch += 1;
282
- state.callsEpoch += 1;
283
- },
269
+ };
270
+ }
271
+
272
+ /** Tool calls and early-notified results observed during the turn. */
273
+ function toolObservationMethods(state) {
274
+ return {
284
275
  recordToolCalls(calls, { eagerStarted = false } = {}) {
285
276
  for (const call of Array.isArray(calls) ? calls : []) {
286
277
  if (!call?.id) continue;
@@ -308,6 +299,42 @@ export function createTurnInterruptionTracker() {
308
299
  state.observedToolResults.delete(callId);
309
300
  }
310
301
  },
302
+ };
303
+ }
304
+
305
+ export function createTurnInterruptionTracker() {
306
+ const state = newInterruptionState();
307
+
308
+ return {
309
+ ...textBufferMethods(state),
310
+ ...toolObservationMethods(state),
311
+ hasResponseStarted() {
312
+ return state.responseStarted;
313
+ },
314
+ recordReasoningDelta(chunk) {
315
+ const value = String(chunk ?? '');
316
+ if (!value) return;
317
+ state.responseStarted = true;
318
+ state.partialReasoningContent += value;
319
+ },
320
+ recordAssistantText(text) {
321
+ const value = String(text ?? '');
322
+ if (!value.trim()) return;
323
+ state.responseStarted = true;
324
+ // Buffered providers report the whole segment here; streaming
325
+ // providers already accumulated the same segment via text deltas.
326
+ if (!state.partialAssistantContent.trim()) state.partialAssistantContent += value;
327
+ },
328
+ markAssistantMessageCommitted() {
329
+ state.partialAssistantContent = '';
330
+ state.tombstonedAssistantContent = '';
331
+ state.partialReasoningContent = '';
332
+ state.observedToolCalls.clear();
333
+ state.textEpoch += 1;
334
+ state.reasoningEpoch += 1;
335
+ state.tombEpoch += 1;
336
+ state.callsEpoch += 1;
337
+ },
311
338
  markProviderSendStarted() {
312
339
  state.phase = 'streaming';
313
340
  },
@@ -0,0 +1,125 @@
1
+ // manager/turn-reap.mjs
2
+ // A turn abandoned by a timeout or stall stops the background shell work that
3
+ // turn started — a job auto-promoted during it, and the survivors its commands
4
+ // left running. Only those: a user cancel, a steering interrupt, a normal
5
+ // finish, and jobs from earlier turns keep their jobs. Only the entry's own
6
+ // turn start is armed, and a new turn clears it, so a reap that runs late
7
+ // never touches a newer turn's work.
8
+ import { cancelBackgroundTasks, getBackgroundTask } from '../../../../shared/background-tasks.mjs';
9
+ import { runtimeState } from './runtime-state.mjs';
10
+
11
+ // Timeout ownership is explicit and turn-scoped: the code that makes a timeout
12
+ // WIN (the watchdog's own abort, the provider's terminal error) arms the reap
13
+ // itself, and only while nothing else has stopped the turn. An abort that
14
+ // arrives first stays a cancel; an error chain is never read afterwards.
15
+
16
+ // Codes of the runtime's own timeout/stall errors (stall-policy's
17
+ // streamStalledError / providerTimeoutError, the Gemini stream timeout).
18
+ const PROVIDER_TIMEOUT_CODES = new Set(['ESTREAMSTALL', 'EPROVIDERTIMEOUT', 'EGEMINITIMEOUT']);
19
+
20
+ /** A timeout is the first thing to stop this session's turn: arm its reap now,
21
+ * before the abort propagates. No-op when the turn's controller is already
22
+ * aborted (a cancel, interrupt or close won) or no turn runs. */
23
+ export function armTurnTimeout(id) {
24
+ const entry = runtimeState.get(id);
25
+ if (!entry || entry.controller?.signal.aborted) return;
26
+ armTurnShellReap(id, entry);
27
+ }
28
+
29
+ /** The turn ended with a provider timeout/stall error that no retry recovered
30
+ * from: arm only while the turn's own signal is un-aborted and the error
31
+ * itself carries a timeout code or the producer's own `streamStalled` flag. */
32
+ export function armTurnTimeoutOnProviderError(id, entry, turnSignal, err) {
33
+ if (turnSignal?.aborted) return;
34
+ if (!PROVIDER_TIMEOUT_CODES.has(err?.code) && err?.streamStalled !== true) return;
35
+ armTurnShellReap(id, entry);
36
+ }
37
+
38
+ // session id -> jobs stopped by a timeout reap, until the owner's handoff
39
+ // reports them (or the session's next turn starts).
40
+ const stoppedJobs = new Map();
41
+
42
+ // What the cancel path already produces, read without waiting: a task whose
43
+ // own cancel hook kills (survivors of a finished command) answers with its
44
+ // kill outcome; a promoted job is over once its process tree is no longer
45
+ // tracked.
46
+ function trackStoppedJob(taskId) {
47
+ const job = { taskId, outcome: null, terminated: false };
48
+ const kill = getBackgroundTask(taskId)?.cancelOutcome;
49
+ if (kill) {
50
+ job.outcome = kill;
51
+ Promise.resolve(kill).then(
52
+ (result) => {
53
+ job.terminated = result?.terminated === true;
54
+ },
55
+ () => {}
56
+ );
57
+ }
58
+ return job;
59
+ }
60
+
61
+ // shell-jobs depends on the session manager, which loads this module, so it is
62
+ // loaded at the first reap. The classification awaits only this module load,
63
+ // never a process; a probe that cannot be loaded leaves a job "still stopping".
64
+ let shellJobProbe = null;
65
+ function loadShellJobProbe() {
66
+ shellJobProbe ??= import('../../tools/builtin/shell-jobs.mjs').then(
67
+ (shellJobs) => shellJobs.isShellJobRunning,
68
+ () => null
69
+ );
70
+ return shellJobProbe;
71
+ }
72
+
73
+ // Confirmed by the runtime's own state, never assumed: a task whose cancel hook
74
+ // kills is confirmed by that kill's `terminated`; a promoted job once neither
75
+ // its native task nor its process-tree tracker is live. Without the probe
76
+ // (shell-jobs not loaded) nothing is confirmed.
77
+ function isConfirmedGone(job, isRunning) {
78
+ if (job.outcome) return job.terminated;
79
+ return Boolean(isRunning) && !isRunning(job.taskId);
80
+ }
81
+
82
+ function reap(id, entry) {
83
+ loadShellJobProbe();
84
+ const { taskIds } = cancelBackgroundTasks({
85
+ surface: 'shell',
86
+ executionSessionId: id,
87
+ startedSinceMs: entry.shellReapSince,
88
+ reason: 'turn-timeout',
89
+ notify: false,
90
+ });
91
+ if (taskIds.length > 0) stoppedJobs.set(id, [...(stoppedJobs.get(id) || []), ...taskIds.map(trackStoppedJob)]);
92
+ }
93
+
94
+ /** The turn is being abandoned by a timeout: remember when it started and stop
95
+ * what it has started so far. A settled (idle) entry has no turn to reap. */
96
+ export function armTurnShellReap(id, entry) {
97
+ if (!(entry?.askStartedAt > 0) || entry.closed) return;
98
+ entry.shellReapSince = entry.askStartedAt;
99
+ reap(id, entry);
100
+ }
101
+
102
+ /** The abandoned turn has unwound: sweep what it registered while stopping
103
+ * (survivors of its aborted commands). No-op unless this turn was armed. */
104
+ export function reapTurnShellJobs(id, entry) {
105
+ if (entry?.shellReapSince > 0 && !entry.closed && runtimeState.get(id) === entry) reap(id, entry);
106
+ }
107
+
108
+ /** A new turn owns the session: nothing earlier may be reaped on its account. */
109
+ export function resetTurnShellReap(id, entry) {
110
+ entry.shellReapSince = null;
111
+ stoppedJobs.delete(id);
112
+ }
113
+
114
+ /** The jobs the reap stopped, split by what is known now: `stopped` are
115
+ * confirmed gone, `stopping` were killed but their processes are not yet
116
+ * confirmed gone (they keep being terminated). */
117
+ export async function takeStoppedShellJobs(id) {
118
+ const jobs = stoppedJobs.get(id) || [];
119
+ stoppedJobs.delete(id);
120
+ if (jobs.length === 0) return { stopped: [], stopping: [] };
121
+ const isRunning = await loadShellJobProbe();
122
+ const taskIds = (confirmed) =>
123
+ jobs.filter((job) => isConfirmedGone(job, isRunning) === confirmed).map((job) => job.taskId);
124
+ return { stopped: taskIds(true), stopping: taskIds(false) };
125
+ }
@@ -3,9 +3,13 @@
3
3
  // Every model- or user-facing string that names it reads this, re-read on each
4
4
  // use so a rename applies at once. Text shared across agents (tool schemas,
5
5
  // other agents' shared prompt block) says "the representative agent" instead,
6
- // so a rename never changes cached bytes.
7
- import { dirname, join } from 'node:path';
8
- import { mixdogGlobalDir, readSafe } from '../context/skill-catalog.mjs';
6
+ // so a rename never changes those cached bytes. Each other bot's own contact
7
+ // catalog (workflow/context.mjs) names her, so a request naming her finds her.
8
+ // Light imports only: the prompt's contact catalog (workflow/context.mjs) reads
9
+ // the name too, and must not pull the skill catalog and config in with it.
10
+ import { readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
9
13
  import { mixyIdentityDir } from '../../../shared/agent-store.mjs';
10
14
 
11
15
  export const DEFAULT_MIXY_NAME = 'Mixy';
@@ -19,9 +23,11 @@ export function mixyNameOf(manifest) {
19
23
  export function mixyDisplayName() {
20
24
  try {
21
25
  // bots/mixy once Mixy is its own bot, agents/lead before (agent-store.mjs).
22
- const dir = mixyIdentityDir(dirname(mixdogGlobalDir('agents')));
23
- return mixyNameOf(JSON.parse(readSafe(join(dir, 'agent.json')) || '{}'));
26
+ const dir = mixyIdentityDir(resolvePluginData());
27
+ return mixyNameOf(JSON.parse(readFileSync(join(dir, 'agent.json'), 'utf8')));
24
28
  } catch {
29
+ // Never throws: prompt composition calls this unguarded, and the name only labels Mixy.
30
+ // A missing, empty, unreadable or malformed agent.json is the unrenamed default.
25
31
  return DEFAULT_MIXY_NAME;
26
32
  }
27
33
  }
@@ -92,7 +92,11 @@ function preSendCompactDecision(state, compactPolicy) {
92
92
  messages,
93
93
  sessionRef,
94
94
  });
95
- const cacheExpired = shouldCompactForExpiredAgentCache(sessionRef, state.opts);
95
+ const cacheExpired = shouldCompactForExpiredAgentCache(sessionRef, state.opts, {
96
+ provider: sessionRef?.provider || state.provider?.name,
97
+ model: state.model,
98
+ messages,
99
+ });
96
100
  const mediaPressure = shouldCompactForRequestMedia(messages, sessionRef?.provider);
97
101
  const requireReduction = !reactivePending && !mediaPressure;
98
102
  let shouldCompact =
@@ -26,6 +26,20 @@ function validTouch(value) {
26
26
  );
27
27
  }
28
28
 
29
+ // A warm use as stored: `{ at, provider, model, accountId }` — the route and
30
+ // account whose cached prefix the warm read. A bare time (written before the
31
+ // identity was recorded) keeps its time and matches no route.
32
+ function warmUseRecord(value) {
33
+ const record = value && typeof value === 'object' ? value : { at: value };
34
+ if (!(Number(record.at) > 0)) return null;
35
+ return {
36
+ at: Number(record.at),
37
+ provider: String(record.provider || ''),
38
+ model: String(record.model || ''),
39
+ accountId: String(record.accountId || ''),
40
+ };
41
+ }
42
+
29
43
  /** A touch store backed by `path` (default: the data dir's file). */
30
44
  export function createPromptCacheTouchStore(
31
45
  path = () => join(resolvePluginData(), PROMPT_CACHE_TOUCH_FILE),
@@ -43,8 +57,9 @@ export function createPromptCacheTouchStore(
43
57
  for (const [sessionId, touch] of Object.entries(stored?.touches || {})) {
44
58
  if (validTouch(touch)) touches.set(sessionId, { ...touch });
45
59
  }
46
- for (const [sessionId, at] of Object.entries(stored?.warmUses || {})) {
47
- if (Number(at) > 0) warmUses.set(sessionId, Number(at));
60
+ for (const [sessionId, use] of Object.entries(stored?.warmUses || {})) {
61
+ const record = warmUseRecord(use);
62
+ if (record) warmUses.set(sessionId, record);
48
63
  }
49
64
  return touches;
50
65
  };
@@ -102,12 +117,20 @@ export function createPromptCacheTouchStore(
102
117
  },
103
118
  /** When a warm last used the session's cache successfully, or 0. */
104
119
  lastWarmUse(sessionId) {
105
- return uses().get(String(sessionId || '')) || 0;
120
+ return uses().get(String(sessionId || ''))?.at || 0;
121
+ },
122
+ /** The session's last successful warm use `{ at, provider, model,
123
+ * accountId }`, or null. */
124
+ warmUse(sessionId) {
125
+ const use = uses().get(String(sessionId || ''));
126
+ return use ? { ...use } : null;
106
127
  },
107
- /** A warm read the session's cache without writing at `at`. */
108
- recordWarmUse(sessionId, at) {
109
- if (!sessionId || !(Number(at) > 0)) return;
110
- uses().set(String(sessionId), Number(at));
128
+ /** A warm read the session's cache without writing at `at`, on the
129
+ * route and account `{ provider, model, accountId }`. */
130
+ recordWarmUse(sessionId, at, identity = {}) {
131
+ const record = warmUseRecord({ ...identity, at });
132
+ if (!sessionId || !record) return;
133
+ uses().set(String(sessionId), record);
111
134
  scheduleFlush();
112
135
  },
113
136
  /** The session's own activity supersedes its warm use (a real request
@@ -0,0 +1,154 @@
1
+ // Child process of restart-prefix-parity.slow.test.mjs: one phase of a scripted
2
+ // anthropic-oauth session in its own process, so a "restart" is a real new
3
+ // process reading the data dir the previous one left. The fake provider
4
+ // records the request body the real OAuth builder makes for every main-loop
5
+ // send (providers/anthropic-oauth.mjs buildRequestBody, after the same
6
+ // tool-pair sanitize its send path runs) and answers from a script.
7
+ //
8
+ // node restart-prefix-parity.fixture.mjs <phase> <dataDir> <outFile> [sessionId]
9
+ // phase idle two complete turns, then exit
10
+ // phase killtool one complete turn; the second turn's second request is
11
+ // answered with a tool call whose run settles the journal,
12
+ // prints READY and hangs until the parent kills the process
13
+ // phase killrequest the same, but it hangs in the second request itself
14
+ // (sent, never answered)
15
+ // phase resume one turn on the stored session (crash recovery included)
16
+ import { mkdirSync, writeFileSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+
19
+ const [phase, dataDir, outFile, resumeId] = process.argv.slice(2);
20
+ process.env.MIXDOG_DATA_DIR = dataDir;
21
+ process.env.MIXDOG_AGENT_TRACE_DISABLE = '1';
22
+ const workDir = join(dataDir, '..', 'work');
23
+ mkdirSync(workDir, { recursive: true });
24
+ // The response language defaults to the system locale, and only a Korean one
25
+ // gets Opus 5.5's turn-scoped tail reminder (tail-reminder-rules.mjs), so the
26
+ // language is pinned: every host sends the same requests. The resume phase
27
+ // reads what the first phase wrote.
28
+ if (phase !== 'resume') {
29
+ writeFileSync(join(dataDir, 'mixdog-config.json'), JSON.stringify({ agent: { profile: { language: 'ko' } } }));
30
+ }
31
+ // One file per call: a repeated read is answered from the read cache, which
32
+ // never reaches the tool run the midturn phase hangs in.
33
+ for (const name of ['call_1', 'call_2', 'call_3']) writeFileSync(join(workDir, `${name}.txt`), `${name}\n`);
34
+
35
+ const { createSession } = await import('./manager/session-lifecycle.mjs');
36
+ const { askSession } = await import('./manager/ask-session.mjs');
37
+ const { settleTurnCheckpointWrites } = await import('./manager/turn-checkpoint.mjs');
38
+ const { sanitizeToolPairs } = await import('./context-utils.mjs');
39
+ const { _withRegisteredProviderForTestAsync } = await import('../providers/registry.mjs');
40
+ const { _buildRequestBodyForCacheSmoke } = await import('../providers/anthropic-oauth.mjs');
41
+ const { modelToolSchemaAllowlist } = await import('../runtime-core/tool-profile.mjs');
42
+ const { anthropicPrefixChanges, anthropicPrefixFingerprint, anthropicPrefixSessionId, recordAnthropicPrefix } =
43
+ await import('../providers/lib/anthropic-prefix-trace.mjs');
44
+
45
+ const MODEL = 'claude-opus-5-5';
46
+ const USAGE = { inputTokens: 10, outputTokens: 5, cachedTokens: 0, cacheWriteTokens: 0 };
47
+ const thinking = (id) => [{ type: 'thinking', thinking: `plan ${id}`, signature: `sig-${id}` }];
48
+ const toolStep = (id) => async () => ({
49
+ content: 'Reading.',
50
+ thinkingBlocks: thinking(id),
51
+ hasThinkingContent: true,
52
+ toolCalls: [{ id, name: 'read', arguments: { path: `${id}.txt` } }],
53
+ stopReason: 'tool_use',
54
+ usage: USAGE,
55
+ });
56
+ const textStep = (text) => async () => ({
57
+ content: text,
58
+ thinkingBlocks: thinking(text),
59
+ hasThinkingContent: true,
60
+ stopReason: 'end_turn',
61
+ usage: USAGE,
62
+ });
63
+
64
+ const bodies = [];
65
+ const changes = [];
66
+ let warmBody = null;
67
+ let script = [];
68
+ let sessionId = resumeId || null;
69
+ const provider = {
70
+ name: 'anthropic-oauth',
71
+ contextWindow: 200_000,
72
+ async send(messages, model, tools, opts) {
73
+ if (opts?.requestKind && opts.requestKind !== 'turn') return { content: 'Title', usage: USAGE };
74
+ const body = await _buildRequestBodyForCacheSmoke(sanitizeToolPairs(messages), model, tools || [], opts);
75
+ bodies.push(JSON.parse(JSON.stringify(body)));
76
+ // The diagnostic fingerprint the real send records, and what it reports
77
+ // against the previous request (the record's, after a restart).
78
+ recordAnthropicPrefix(anthropicPrefixSessionId(opts), anthropicPrefixFingerprint(body));
79
+ changes.push(anthropicPrefixChanges(anthropicPrefixSessionId(opts)));
80
+ const step = script.shift();
81
+ if (!step) throw new Error(`unscripted send #${bodies.length}`);
82
+ return step();
83
+ },
84
+ };
85
+
86
+ // Where the interrupted turn stops: the checkpoint made durable, the recorded
87
+ // bodies handed over, and nothing ever finishes (the parent kills the process).
88
+ const hang = async () => {
89
+ await settleTurnCheckpointWrites(sessionId);
90
+ await new Promise((resolve) => setTimeout(resolve, 300));
91
+ await settleTurnCheckpointWrites(sessionId);
92
+ writeFileSync(outFile, JSON.stringify({ sessionId, bodies, changes }));
93
+ process.stdout.write('READY\n');
94
+ return new Promise(() => {});
95
+ };
96
+ // killtool: in the second tool run of the turn (after its second request settled).
97
+ let toolRuns = 0;
98
+ const hangInSecondTool = async () => {
99
+ toolRuns += 1;
100
+ if (toolRuns >= 2) await hang();
101
+ };
102
+
103
+ await _withRegisteredProviderForTestAsync('anthropic-oauth', provider, async () => {
104
+ if (phase === 'resume') {
105
+ // The cache warm a restart re-arms (cache-warm.mjs rearmCacheWarmsAfterRestart)
106
+ // replays this request, built before the next turn touches the session.
107
+ const { lastSentMainRequest } = await import('./side-question.mjs');
108
+ const { cacheWarmSend } = await import('./cache-warm.mjs');
109
+ const request = lastSentMainRequest(sessionId);
110
+ if (request) {
111
+ const send = cacheWarmSend(request);
112
+ warmBody = await _buildRequestBodyForCacheSmoke(
113
+ sanitizeToolPairs(send.messages),
114
+ send.model,
115
+ send.tools || [],
116
+ send.opts
117
+ );
118
+ }
119
+ script = [textStep('resumed')];
120
+ await askSession(sessionId, 'third task', null, null, null, null, {});
121
+ return;
122
+ }
123
+ const session = createSession({
124
+ provider: 'anthropic-oauth',
125
+ model: MODEL,
126
+ cwd: workDir,
127
+ skipSkills: true,
128
+ schemaAllowedTools: modelToolSchemaAllowlist('headless'),
129
+ workflow: { id: 'headless', delegatesAgents: false },
130
+ });
131
+ sessionId = session.id;
132
+ if (phase === 'idle') {
133
+ script = [toolStep('call_1'), textStep('done one'), toolStep('call_2'), textStep('done two')];
134
+ await askSession(sessionId, 'first task', null, null, null, null, {});
135
+ await askSession(sessionId, 'second task', null, null, null, null, {});
136
+ return;
137
+ }
138
+ // killrequest: while the turn's second request is on the wire.
139
+ const killTool = phase === 'killtool';
140
+ script = [toolStep('call_1'), textStep('done one'), toolStep('call_2'), killTool ? toolStep('call_3') : hang];
141
+ await askSession(sessionId, 'first task', null, null, null, null, {});
142
+ await askSession(
143
+ sessionId,
144
+ 'second task',
145
+ null,
146
+ null,
147
+ null,
148
+ null,
149
+ killTool ? { beforeToolExecution: hangInSecondTool } : {}
150
+ );
151
+ });
152
+
153
+ writeFileSync(outFile, JSON.stringify({ sessionId, bodies, changes, warmBody }));
154
+ process.exit(0);
@@ -36,6 +36,7 @@ import { _savePending, _clearDebounce, _releasePendingSlot } from './pending-sav
36
36
  import { _runtimeLivenessVeto } from './liveness-veto.mjs';
37
37
  import { _runSessionPurgeHooks } from './session-observers.mjs';
38
38
  import { deleteCompactionArchive } from '../compaction-archive.mjs';
39
+ import { removeSessionScratchDir } from '../../../../shared/session-scratch.mjs';
39
40
 
40
41
  /** Child-agent session ids linked to one visible parent. Candidates come from
41
42
  * metadata only (the summary rows and the agent-worker links — no folder
@@ -154,6 +155,12 @@ export function deleteSession(id, options = {}) {
154
155
  _runSessionPurgeHooks(id, { cwds: knownSessionCwds(id) });
155
156
  // The display-only compaction archive has no meaning without its session.
156
157
  deleteCompactionArchive(getStoreDir(), id);
158
+ // The session's scratch directory (shell TEMP/TMP/TMPDIR) goes with it.
159
+ try {
160
+ removeSessionScratchDir(id);
161
+ } catch {
162
+ /* a locked scratch file never fails the delete */
163
+ }
157
164
  _clearLiveSession(id);
158
165
  // The file is gone: every marker of the retired incarnation goes with it
159
166
  // (save error + failed-snapshot evidence, split-brain drop flag, lifecycle
@@ -30,6 +30,7 @@ import {
30
30
  inheritedFromSessionId,
31
31
  isStoredSessionId,
32
32
  positiveNumber,
33
+ compactCountField,
33
34
  realTurnCount,
34
35
  workspaceSummary,
35
36
  } from './store-summary-fields.mjs';
@@ -216,6 +217,9 @@ export function normalizedRow(row) {
216
217
  // Absent from a row read off a session file: its assistant text is not
217
218
  // decoded there, so the unread count falls back until the next save.
218
219
  ...(Array.isArray(row.incomingTail) ? { incomingTail: normalizedIncomingTail(row.incomingTail) } : {}),
220
+ // Absent from a row read off a session file (its compaction record is not
221
+ // decoded there) until the next save.
222
+ ...compactCountField(row),
219
223
  realTurnCount: realTurnCount(row),
220
224
  title: cleanText(row.title, 100),
221
225
  preview: cleanText(row.preview),
@@ -63,6 +63,21 @@ export function realTurnCount(value) {
63
63
  return typeof count === 'number' && Number.isFinite(count) && count >= 0 ? Math.floor(count) : null;
64
64
  }
65
65
 
66
+ /** How many compactions a session has had (`compaction.compactCount`): each
67
+ * one trims the live messages and so restarts their numbering (the unread
68
+ * cursors count them). Null for a value without it, e.g. a row written
69
+ * before the field existed. */
70
+ export function compactCount(value) {
71
+ const count = value?.compactCount;
72
+ return typeof count === 'number' && Number.isSafeInteger(count) && count >= 0 ? count : null;
73
+ }
74
+
75
+ /** `{ compactCount }` for a row that carries one, else nothing. */
76
+ export function compactCountField(value) {
77
+ const count = compactCount(value);
78
+ return count === null ? {} : { compactCount: count };
79
+ }
80
+
66
81
  export function importKey(value) {
67
82
  const key = typeof value?.importKey === 'string' ? value.importKey : value?.importOrigin?.key;
68
83
  return typeof key === 'string' && key.trim() ? key : null;