mixdog 1.3.1 → 1.3.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 (468) hide show
  1. package/README.md +8 -4
  2. package/package.json +8 -8
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/scripts/verify-runtime-dependency-versions.mjs +158 -0
  5. package/src/agents/reviewer/AGENT.md +5 -7
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +9 -1
  8. package/src/defaults/skills/browser-use/SKILL.md +6 -3
  9. package/src/defaults/skills/local-provider/SKILL.md +33 -6
  10. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  11. package/src/defaults/skills/setup/SKILL.md +23 -1
  12. package/src/defaults/skills/setup/references/actions.md +50 -3
  13. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  14. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  15. package/src/defaults/skills/visualize/SKILL.md +9 -6
  16. package/src/defaults/skills/visualize/references/craft.md +8 -1
  17. package/src/help.mjs +1 -1
  18. package/src/lib/keychain-cjs.cjs +4 -1
  19. package/src/lib/rules-builder.cjs +14 -7
  20. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  21. package/src/rules/agent-mode/MIXY.md +6 -8
  22. package/src/rules/lead/LEAD.md +7 -10
  23. package/src/rules/shared/10-tool-workflow.md +4 -0
  24. package/src/rules/shared/20-research.md +2 -0
  25. package/src/rules/shared/45-destructive.md +5 -1
  26. package/src/rules/shared/60-verification.md +6 -4
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  30. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  31. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  32. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  33. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  35. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  37. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  38. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  39. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  40. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  41. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
  45. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  46. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
  48. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  49. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
  50. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  51. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  52. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  54. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  55. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
  56. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  57. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  59. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  60. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  62. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  64. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  65. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  69. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  70. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  72. package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
  73. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
  74. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  75. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
  78. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  79. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  80. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
  81. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  82. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  83. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
  85. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
  86. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  87. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
  90. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  92. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  93. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  94. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
  95. package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
  96. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  97. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  98. package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
  99. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
  100. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
  102. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
  103. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
  107. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
  108. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  109. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  110. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
  111. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
  112. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  113. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
  114. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  115. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  116. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  117. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  118. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
  119. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
  120. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
  123. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
  124. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  125. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  126. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
  127. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  128. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
  129. package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
  130. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
  131. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  132. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  134. package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
  135. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
  136. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
  137. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  138. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
  139. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
  140. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  143. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
  144. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
  145. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  146. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
  147. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  148. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  149. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  150. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
  151. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
  155. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  156. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
  157. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
  158. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  160. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  161. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
  163. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
  166. package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
  167. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
  168. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  169. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  170. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  171. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  172. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  173. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
  174. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
  178. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
  179. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  180. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  181. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
  182. package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
  183. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
  184. package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
  185. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  186. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  189. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  190. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
  191. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  196. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
  199. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
  200. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  201. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  202. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  203. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  204. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  205. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
  206. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  207. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  208. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  209. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  210. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  211. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  212. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  213. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  214. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  215. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  216. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  217. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  218. package/src/runtime/attachments/legacy-office.mjs +110 -0
  219. package/src/runtime/attachments/limits.mjs +5 -1
  220. package/src/runtime/attachments/office-extract.mjs +3 -6
  221. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  222. package/src/runtime/attachments/prompt-restore.mjs +70 -0
  223. package/src/runtime/attachments/store.mjs +109 -33
  224. package/src/runtime/browser-bridge/client.mjs +73 -20
  225. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  226. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  227. package/src/runtime/channels/lib/state-file.mjs +15 -15
  228. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  229. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  230. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  231. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  232. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  233. package/src/runtime/effort-judge/judge-client.mjs +66 -16
  234. package/src/runtime/effort-judge/model-install.mjs +47 -7
  235. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  236. package/src/runtime/local-provider/catalog.mjs +5 -2
  237. package/src/runtime/local-provider/registered-models.mjs +64 -27
  238. package/src/runtime/local-provider/server.mjs +2 -3
  239. package/src/runtime/media/adapters/image-api.mjs +89 -0
  240. package/src/runtime/media/auth.mjs +64 -1
  241. package/src/runtime/media/catalog.mjs +66 -4
  242. package/src/runtime/media/jobs.mjs +4 -0
  243. package/src/runtime/media/lanes.mjs +46 -5
  244. package/src/runtime/media/media-usage.mjs +17 -0
  245. package/src/runtime/media/renditions.mjs +15 -15
  246. package/src/runtime/media/store.mjs +36 -35
  247. package/src/runtime/media/tool.mjs +8 -1
  248. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  249. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  250. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  251. package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
  252. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  253. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  254. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  255. package/src/runtime/office/core/journal.mjs +7 -14
  256. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  257. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  258. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  259. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  260. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  261. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  262. package/src/runtime/office/portable/portable-soffice.mjs +135 -29
  263. package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
  264. package/src/runtime/shared/agent-avatar.mjs +3 -2
  265. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  266. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  267. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  268. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  269. package/src/runtime/shared/agent-room-host.d.mts +31 -0
  270. package/src/runtime/shared/agent-room-host.mjs +58 -0
  271. package/src/runtime/shared/agent-store.mjs +24 -7
  272. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  273. package/src/runtime/shared/atomic-file.d.mts +50 -0
  274. package/src/runtime/shared/atomic-file.mjs +394 -132
  275. package/src/runtime/shared/background-tasks.mjs +7 -4
  276. package/src/runtime/shared/bot-hops.mjs +35 -0
  277. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  278. package/src/runtime/shared/bounded-download.mjs +4 -2
  279. package/src/runtime/shared/config.mjs +39 -9
  280. package/src/runtime/shared/defaults-separation.mjs +6 -2
  281. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  282. package/src/runtime/shared/err-text.mjs +4 -0
  283. package/src/runtime/shared/error-presentation.mjs +22 -4
  284. package/src/runtime/shared/file-has-data.mjs +28 -0
  285. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  286. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  287. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  288. package/src/runtime/shared/projects.mjs +50 -41
  289. package/src/runtime/shared/provider-accounts.mjs +17 -12
  290. package/src/runtime/shared/redact-directories.mjs +18 -0
  291. package/src/runtime/shared/remove-directory.mjs +28 -0
  292. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  293. package/src/runtime/shared/routine-trigger.mjs +14 -6
  294. package/src/runtime/shared/run-child.d.mts +3 -0
  295. package/src/runtime/shared/run-child.mjs +24 -3
  296. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  297. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  298. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  299. package/src/runtime/shared/singleton-owner.mjs +7 -6
  300. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  301. package/src/runtime/shared/staged-update.mjs +4 -2
  302. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  303. package/src/runtime/shared/tool-execution-contract.mjs +10 -0
  304. package/src/runtime/shared/tool-work-units.mjs +12 -9
  305. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  306. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  307. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  308. package/src/runtime/shared/turn-snapshot.mjs +277 -124
  309. package/src/runtime/shared/user-cwd.mjs +9 -4
  310. package/src/runtime/shared/user-data-guard.mjs +92 -61
  311. package/src/runtime/shared/utf8-cut.mjs +10 -0
  312. package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
  313. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  314. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  315. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  316. package/src/runtime/shared/worktrees/registry.mjs +29 -27
  317. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  318. package/src/runtime/tidy/core-install.mjs +26 -4
  319. package/src/runtime/tidy/install.mjs +91 -23
  320. package/src/runtime/tidy/process.mjs +41 -8
  321. package/src/runtime/tidy/resolve.mjs +10 -7
  322. package/src/runtime/web-search/lib/config.mjs +4 -16
  323. package/src/session-runtime/agent-home-delivery.mjs +542 -204
  324. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  325. package/src/session-runtime/agent-room-delivery.mjs +81 -35
  326. package/src/session-runtime/agent-task-sessions.mjs +811 -179
  327. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  328. package/src/session-runtime/boot/apis.mjs +48 -24
  329. package/src/session-runtime/boot/config.mjs +1 -0
  330. package/src/session-runtime/boot/facade.mjs +16 -7
  331. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  332. package/src/session-runtime/boot/providers.mjs +2 -2
  333. package/src/session-runtime/boot/shared.mjs +12 -6
  334. package/src/session-runtime/boot/tools.mjs +23 -3
  335. package/src/session-runtime/bot-status.mjs +432 -0
  336. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  337. package/src/session-runtime/config-lifecycle.mjs +60 -55
  338. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  339. package/src/session-runtime/held-requests.mjs +31 -33
  340. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  341. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  342. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
  343. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  344. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  345. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  346. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  347. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  348. package/src/session-runtime/media-api.mjs +5 -2
  349. package/src/session-runtime/model-recency.mjs +1 -0
  350. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  351. package/src/session-runtime/model-route/set-route.mjs +61 -1
  352. package/src/session-runtime/owned-automation-run.mjs +8 -1
  353. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  354. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  355. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  356. package/src/session-runtime/route-resolution.mjs +16 -8
  357. package/src/session-runtime/route-state.mjs +1 -59
  358. package/src/session-runtime/routine-home-turn.mjs +5 -3
  359. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  360. package/src/session-runtime/runtime-paths.mjs +11 -5
  361. package/src/session-runtime/runtime-review-api.mjs +4 -1
  362. package/src/session-runtime/runtime-tunables.mjs +6 -0
  363. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  364. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  365. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  366. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  367. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
  368. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  369. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  370. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
  371. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  372. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  373. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  374. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  375. package/src/session-runtime/services/agent-tool.mjs +6 -0
  376. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  377. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  378. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  379. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  380. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  381. package/src/session-runtime/services/provider-admin.mjs +26 -6
  382. package/src/session-runtime/services/schedule-access.mjs +33 -4
  383. package/src/session-runtime/services/seeds.mjs +4 -3
  384. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  385. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  386. package/src/session-runtime/services/worktree-access.mjs +27 -0
  387. package/src/session-runtime/session-create/materialize.mjs +2 -3
  388. package/src/session-runtime/session-create/session-options.mjs +15 -2
  389. package/src/session-runtime/session-hooks.mjs +9 -17
  390. package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
  391. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  392. package/src/session-runtime/setup-tool/executor.mjs +12 -2
  393. package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
  394. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  395. package/src/session-runtime/skills-api.mjs +12 -5
  396. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
  397. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  398. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  399. package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
  400. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  401. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  402. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  403. package/src/session-runtime/tool-policy-refresh.mjs +48 -369
  404. package/src/session-runtime/tool-surface.mjs +10 -4
  405. package/src/session-runtime/turn/session-ops.mjs +8 -0
  406. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  407. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  408. package/src/session-runtime/turn/turn-run.mjs +7 -2
  409. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  410. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
  411. package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
  412. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  413. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  414. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  415. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  416. package/src/session-runtime/workspace/transitions.mjs +279 -67
  417. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  418. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  419. package/src/standalone/daemon-agent-control.mjs +4 -1
  420. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  421. package/src/standalone/daemon-session-control.mjs +173 -15
  422. package/src/standalone/daemon-telemetry.mjs +9 -0
  423. package/src/standalone/daemon.mjs +33 -10
  424. package/src/standalone/local-session-runtime.mjs +33 -16
  425. package/src/standalone/session-client.mjs +7 -2
  426. package/src/standalone/session-protocol.mjs +8 -0
  427. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  428. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  429. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  430. package/src/standalone/session-service.mjs +21 -0
  431. package/src/standalone/session-state-projection.mjs +19 -9
  432. package/src/standalone/session-tool-output.mjs +36 -9
  433. package/src/standalone/staged-import.mjs +47 -0
  434. package/src/tui/App.jsx +2 -0
  435. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  436. package/src/tui/app/message-selector.mjs +15 -2
  437. package/src/tui/app/model-options.mjs +16 -2
  438. package/src/tui/app/model-picker.mjs +2 -1
  439. package/src/tui/app/route-pickers.mjs +3 -1
  440. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  441. package/src/tui/app/slash-commands.mjs +1 -1
  442. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  443. package/src/tui/dist/index.mjs +617 -369
  444. package/src/tui/paste-attachments.mjs +0 -1
  445. package/src/tui/paste-text-policy.mjs +4 -5
  446. package/src/tui/prompt-history-store.mjs +16 -21
  447. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  448. package/src/tui/session/context-state.mjs +4 -0
  449. package/src/tui/session/notice-surface.mjs +3 -0
  450. package/src/tui/session/session-api/intake/abort.mjs +52 -18
  451. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  452. package/src/tui/session/session-api/media.mjs +2 -1
  453. package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
  454. package/src/tui/session/session-api/routes-write.mjs +203 -0
  455. package/src/tui/session/session-api/routes.mjs +23 -247
  456. package/src/tui/session/session-api/settings.mjs +14 -1
  457. package/src/tui/session/session-flow/drain.mjs +28 -3
  458. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  459. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  460. package/src/tui/session/session-flow/steering.mjs +8 -3
  461. package/src/tui/session/tool-approval.mjs +142 -14
  462. package/src/tui/session/transcript-intake.mjs +2 -0
  463. package/src/tui/session/turn.mjs +23 -14
  464. package/src/tui/transcript-tool-failures.mjs +8 -2
  465. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  466. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  467. package/src/rules/agent-mode/ROUTINE.md +0 -6
  468. package/src/session-runtime/room-member-gate.mjs +0 -48
@@ -509,6 +509,25 @@ export function providerInputExcludesCache(name) {
509
509
  export function providerNativePdf(name) {
510
510
  return providerTakesNativePdf(providers.get(String(name || '').toLowerCase()));
511
511
  }
512
+ /**
513
+ * Whether the named provider sends image input to its models: false only for
514
+ * an adapter that declares `takesImages = false` (the text-only local
515
+ * runtime, which lowers every image to a placeholder). A pure lookup; an
516
+ * unknown provider is treated as taking images, as attachments are.
517
+ */
518
+ export function providerReadsImages(name) {
519
+ return providers.get(String(name || '').toLowerCase())?.takesImages !== false;
520
+ }
521
+ /**
522
+ * The user's image-input override for one model of a custom provider: true or
523
+ * false as set, undefined when unset (auto) or the provider is not a loaded
524
+ * custom one. A pure lookup; it never instantiates a provider.
525
+ */
526
+ export function providerModelVisionOverride(name, model) {
527
+ const overrides = providers.get(String(name || '').toLowerCase())?.customConfig?.modelOverrides;
528
+ const value = overrides && Object.hasOwn(overrides, model) ? overrides[model]?.supportsVision : undefined;
529
+ return typeof value === 'boolean' ? value : undefined;
530
+ }
512
531
  /**
513
532
  * Whether the named provider's adapter declares `consumesReadWindows`: it
514
533
  * needs the structured clip report of read results. A pure lookup; a proxy
@@ -9,7 +9,7 @@ import { OpenAICompatProvider, OPENAI_COMPAT_PRESETS } from './openai-compat.mjs
9
9
  import { originBoundFetch } from './lib/origin-bound-fetch.mjs';
10
10
  import { withInclusiveUsage } from './lib/inclusive-usage.mjs';
11
11
  import { getModelMetadataSync } from './model-catalog.mjs';
12
- import { resolveWireRoute, sendOnRoute } from './wire-route.mjs';
12
+ import { resolveWireRoute, sendOnRoute, sendWireRoute } from './wire-route.mjs';
13
13
 
14
14
  // A Files API file-ID image anywhere in the history (tool results nest content).
15
15
  function hasFileIdImage(value, depth = 0) {
@@ -66,6 +66,10 @@ export class GatewayMessagesEngine extends AnthropicProvider {
66
66
  _sendOnce(messages, model, tools, opts) {
67
67
  return super._sendOnce(messages, model, tools, gatewayMessagesOpts(this.config, model, messages, opts));
68
68
  }
69
+ // Same ceiling a send resolves, so a retry never asks above the gateway's limit.
70
+ escalatedMaxTokens(model, opts) {
71
+ return super.escalatedMaxTokens(model, gatewayMessagesOpts(this.config, model, [], opts));
72
+ }
69
73
  _createClient(apiKey) {
70
74
  const client = super._createClient(apiKey);
71
75
  if (this.config.authScheme === 'bearer') {
@@ -138,6 +142,14 @@ export class WireGatewayProvider {
138
142
  });
139
143
  }
140
144
 
145
+ // A higher one-shot max_tokens exists only on the Messages wire; the
146
+ // route is the same one the send itself resolves. Other wires: null.
147
+ async escalatedMaxTokens(model, opts, tools) {
148
+ const useModel = model || this.openai.defaultModel;
149
+ const route = await sendWireRoute(this, useModel, tools, opts);
150
+ return route?.wire === 'messages' ? this.anthropic.escalatedMaxTokens(useModel, opts) : null;
151
+ }
152
+
141
153
  reloadApiKey() {
142
154
  this.openai.reloadApiKey();
143
155
  this.anthropic.reloadApiKey();
@@ -4,6 +4,7 @@ import { clean, hasOwn } from './session-text.mjs';
4
4
  import { normalizeEffortInput, normalizeSavedEffort } from './effort.mjs';
5
5
  import { routeFastKey, fastPreferenceFor } from './model-capabilities.mjs';
6
6
  import { normalizeContextTokens } from '../../../shared/llm/default-context-window.mjs';
7
+ import { normalizeCompactionSummaryMode } from '../session/compact/summary-mode.mjs';
7
8
 
8
9
  const DEFAULT_PROVIDER = 'anthropic-oauth';
9
10
  export const DEFAULT_MODEL = '';
@@ -191,12 +192,23 @@ const AUTO_CLEAR_DEFAULT_IDLE_MS = 60 * 60 * 1000;
191
192
  // reusable. Gemini, xAI and Mistral
192
193
  // caches run ~1h, Groq's implicit cache persists ~2h, and DeepSeek's
193
194
  // implicit cache is evicted after "a few hours to a few days" (kept at 24h).
194
- // OpenAI (direct and OAuth/Codex) guarantees a 30-minute minimum prompt-cache
195
- // TTL on GPT-5.6+ (refreshed on use; no 24h retention there), so the sweep
196
- // matches that window. Older OpenAI models that still take
197
- // prompt_cache_retention='24h' are not distinguished: these call sites only
198
- // know the provider, not the model, so 30m applies to the whole provider.
195
+ // OpenAI public API (GPT-5.6+ Responses, cache-strategy.mjs): the moving tail
196
+ // is written with ttl "30m" and only the Lead-class stable prefix with "12h",
197
+ // so the sweep matches the 30m tail — a clear after that costs nothing extra
198
+ // (the tail is cold anyway) and the cleared session restarts on the warm 12h
199
+ // prefix. Earlier OpenAI models (prompt_cache_retention, typically ~30 min)
200
+ // are not distinguished: these call sites only know the provider.
201
+ // OpenAI OAuth (Codex/ChatGPT login) keeps the cache of logged-in users
202
+ // indefinitely (2026-10-20), so its built-in window is unlimited: no idle
203
+ // clear; compaction happens only at the context limit or on request.
199
204
  // Unknown/unrecognized providers fall back to the 'default' 1h entry.
205
+ //
206
+ // Unlimited ("never auto-clear") is stored as 0 in providerIdleMs — a value
207
+ // older builds dropped, so no older config carries it — and resolves to
208
+ // AUTO_CLEAR_UNLIMITED_MS, which every idle comparison treats as never
209
+ // reached. Input also accepts 'unlimited' / 'never'.
210
+ export const AUTO_CLEAR_UNLIMITED = 0;
211
+ export const AUTO_CLEAR_UNLIMITED_MS = Number.MAX_SAFE_INTEGER;
200
212
  const AUTO_CLEAR_PROVIDER_IDLE_MS = Object.freeze({
201
213
  anthropic: 60 * 60 * 1000,
202
214
  'anthropic-oauth': 60 * 60 * 1000,
@@ -205,20 +217,48 @@ const AUTO_CLEAR_PROVIDER_IDLE_MS = Object.freeze({
205
217
  openai: 30 * 60 * 1000,
206
218
  deepseek: 24 * 60 * 60 * 1000,
207
219
  xai: 60 * 60 * 1000,
208
- 'openai-oauth': 30 * 60 * 1000,
220
+ 'openai-oauth': AUTO_CLEAR_UNLIMITED,
209
221
  mistral: 60 * 60 * 1000,
210
222
  default: AUTO_CLEAR_DEFAULT_IDLE_MS,
211
223
  });
212
224
  const AUTO_CLEAR_PROVIDER_IDS = Object.freeze(Object.keys(AUTO_CLEAR_PROVIDER_IDLE_MS));
213
225
 
226
+ // Completed-agent retention where it must differ from the idle window. Reap
227
+ // frees in-memory sessions and worker-index rows (resources), so it stays
228
+ // finite even where the cache is permanent: openai-oauth uses the 1h product
229
+ // default, which covers Lead follow-ups (24/25 Lead waits were 5–27 min).
230
+ const AGENT_TERMINAL_REAP_MS = Object.freeze({ 'openai-oauth': 60 * 60 * 1000 });
231
+
232
+ /** A stored/requested idle value: AUTO_CLEAR_UNLIMITED, a window ≥ 1 min, or
233
+ * null when absent or invalid. */
234
+ export function normalizeAutoClearIdleValue(value) {
235
+ if (value === null || value === undefined || typeof value === 'boolean') return null;
236
+ if (typeof value === 'string') {
237
+ const text = value.trim().toLowerCase();
238
+ if (!text) return null;
239
+ if (text === 'unlimited' || text === 'never') return AUTO_CLEAR_UNLIMITED;
240
+ }
241
+ const idleMs = Number(value);
242
+ if (!Number.isFinite(idleMs) || idleMs < 0) return null;
243
+ if (idleMs === 0) return AUTO_CLEAR_UNLIMITED;
244
+ return Math.max(60_000, Math.round(idleMs));
245
+ }
246
+
247
+ const resolvedIdleMs = (stored) => (stored === AUTO_CLEAR_UNLIMITED ? AUTO_CLEAR_UNLIMITED_MS : stored);
248
+
249
+ /** Whether a resolved idle window means "never auto-clear". */
250
+ export function isAutoClearUnlimited(idleMs) {
251
+ return Number(idleMs) >= AUTO_CLEAR_UNLIMITED_MS;
252
+ }
253
+
214
254
  function normalizeAutoClearProviderIdleMs(value = {}) {
215
255
  const raw = value && typeof value === 'object' ? value : {};
216
256
  const out = {};
217
257
  for (const [key, ms] of Object.entries(raw)) {
218
258
  const provider = clean(key).toLowerCase();
219
- const idleMs = Number(ms);
220
- if (!provider || !Number.isFinite(idleMs) || idleMs <= 0) continue;
221
- out[provider] = Math.max(60_000, Math.round(idleMs));
259
+ const idleMs = normalizeAutoClearIdleValue(ms);
260
+ if (!provider || idleMs === null) continue;
261
+ out[provider] = idleMs;
222
262
  }
223
263
  return out;
224
264
  }
@@ -228,49 +268,71 @@ function normalizeAutoClearProviderIdleMs(value = {}) {
228
268
  export function autoClearIdleMsForProvider(provider, providerIdleMs = null) {
229
269
  const overrides = normalizeAutoClearProviderIdleMs(providerIdleMs);
230
270
  const key = clean(provider).toLowerCase();
231
- if (overrides[key]) return overrides[key];
232
- if (key && AUTO_CLEAR_PROVIDER_IDLE_MS[key]) return AUTO_CLEAR_PROVIDER_IDLE_MS[key];
233
- if (overrides.default) return overrides.default;
271
+ if (key && Object.hasOwn(overrides, key)) return resolvedIdleMs(overrides[key]);
272
+ if (key && Object.hasOwn(AUTO_CLEAR_PROVIDER_IDLE_MS, key)) return resolvedIdleMs(AUTO_CLEAR_PROVIDER_IDLE_MS[key]);
273
+ if (Object.hasOwn(overrides, 'default')) return resolvedIdleMs(overrides.default);
234
274
  return AUTO_CLEAR_PROVIDER_IDLE_MS.default;
235
275
  }
236
276
 
237
- export function autoClearProviderDefaults(providerIdleMs = null) {
277
+ // One row per built-in provider plus every provider in `extraProviders` (the
278
+ // providers the user has enabled or routes to), before the 'default' row.
279
+ // A provider without a built-in entry resolves like any unknown provider.
280
+ export function autoClearProviderDefaults(providerIdleMs = null, extraProviders = []) {
238
281
  const overrides = normalizeAutoClearProviderIdleMs(providerIdleMs);
239
- return AUTO_CLEAR_PROVIDER_IDS.map((provider) => {
240
- const builtInMs = AUTO_CLEAR_PROVIDER_IDLE_MS[provider] ?? AUTO_CLEAR_PROVIDER_IDLE_MS.default;
241
- const idleMs = overrides[provider] ?? builtInMs;
282
+ const ids = AUTO_CLEAR_PROVIDER_IDS.filter((id) => id !== 'default');
283
+ for (const value of extraProviders || []) {
284
+ const id = clean(value).toLowerCase();
285
+ if (id && !ids.includes(id) && id !== 'default') ids.push(id);
286
+ }
287
+ for (const id of Object.keys(overrides)) if (!ids.includes(id) && id !== 'default') ids.push(id);
288
+ ids.push('default');
289
+ return ids.map((provider) => {
290
+ const builtIn = Object.hasOwn(AUTO_CLEAR_PROVIDER_IDLE_MS, provider)
291
+ ? AUTO_CLEAR_PROVIDER_IDLE_MS[provider]
292
+ : AUTO_CLEAR_PROVIDER_IDLE_MS.default;
293
+ const idleMs = Object.hasOwn(overrides, provider)
294
+ ? resolvedIdleMs(overrides[provider])
295
+ : provider === 'default' || Object.hasOwn(AUTO_CLEAR_PROVIDER_IDLE_MS, provider)
296
+ ? resolvedIdleMs(builtIn)
297
+ : autoClearIdleMsForProvider(provider, overrides);
242
298
  return {
243
299
  provider,
244
300
  idleMs,
245
- builtInMs,
246
- custom: overrides[provider] != null,
301
+ builtInMs: resolvedIdleMs(builtIn),
302
+ unlimited: isAutoClearUnlimited(idleMs),
303
+ builtInUnlimited: builtIn === AUTO_CLEAR_UNLIMITED,
304
+ custom: Object.hasOwn(overrides, provider),
247
305
  };
248
306
  });
249
307
  }
250
308
 
251
309
  // Agent terminal retention is deliberately narrower than Auto Clear's general
252
- // resolution: listed providers use their provider cache lifetime. Unlisted
310
+ // resolution: listed providers use their provider cache lifetime, or the
311
+ // finite AGENT_TERMINAL_REAP_MS entry where that cache is permanent. Unlisted
253
312
  // providers use the Advanced "default" row, or the built-in default when no
254
- // override is configured, so completed agents are still eventually reclaimed.
313
+ // override is configured, so completed agents are still eventually reclaimed;
314
+ // an unlimited override never disables reaping.
255
315
  export function resolveAgentTerminalReapMs(config, provider) {
256
316
  const key = clean(provider).toLowerCase();
257
317
  const raw = config?.autoClear && typeof config.autoClear === 'object' ? config.autoClear : {};
258
318
  const overrides = normalizeAutoClearProviderIdleMs(raw.providerIdleMs);
319
+ const finite = (value) => (value > 0 ? value : null);
259
320
  if (!key || key === 'default' || !Object.hasOwn(AUTO_CLEAR_PROVIDER_IDLE_MS, key)) {
260
- return overrides.default ?? AUTO_CLEAR_PROVIDER_IDLE_MS.default;
321
+ return finite(overrides.default) ?? AUTO_CLEAR_PROVIDER_IDLE_MS.default;
261
322
  }
262
- return overrides[key] ?? AUTO_CLEAR_PROVIDER_IDLE_MS[key];
323
+ return finite(overrides[key]) ?? AGENT_TERMINAL_REAP_MS[key] ?? AUTO_CLEAR_PROVIDER_IDLE_MS[key];
263
324
  }
264
325
 
265
326
  // Resolve the effective auto-clear idle window for a config + provider:
266
327
  // a provider-specific override (autoClear.providerIdleMs[provider]) wins;
267
328
  // else the global user-set idleMs (autoClear.idleMs); else the provider's
268
329
  // built-in window, then providerIdleMs.default / the built-in 1h default.
330
+ // Unlimited resolves to AUTO_CLEAR_UNLIMITED_MS.
269
331
  export function resolveAutoClearIdleMs(config, provider) {
270
332
  const raw = config?.autoClear && typeof config.autoClear === 'object' ? config.autoClear : {};
271
333
  const key = clean(provider).toLowerCase();
272
- const providerOverride = key && key !== 'default' ? normalizeAutoClearProviderIdleMs(raw.providerIdleMs)[key] : null;
273
- if (providerOverride) return providerOverride;
334
+ const overrides = normalizeAutoClearProviderIdleMs(raw.providerIdleMs);
335
+ if (key && key !== 'default' && Object.hasOwn(overrides, key)) return resolvedIdleMs(overrides[key]);
274
336
  const idleMs = Number(raw.idleMs);
275
337
  if (Number.isFinite(idleMs) && idleMs > 0) return Math.max(60_000, Math.round(idleMs));
276
338
  return autoClearIdleMsForProvider(provider, raw.providerIdleMs);
@@ -350,12 +412,16 @@ export function normalizeCompactionConfig(value = {}) {
350
412
  delete next.recallConcurrency;
351
413
  delete next.recallCycle1DeadlineMs;
352
414
  delete next.enabled;
415
+ delete next.useMaintainer;
353
416
  return {
354
417
  ...next,
355
418
  auto: raw.auto !== false && raw.enabled !== false,
356
419
  // Compact a conversation on its current model before switching it to
357
420
  // another one in place (default on).
358
421
  onModelChange: raw.onModelChange !== false,
422
+ // Which model writes compaction summaries (smart / maintainer / self);
423
+ // replaces the previous release's boolean useMaintainer.
424
+ summaryMode: normalizeCompactionSummaryMode(raw),
359
425
  };
360
426
  }
361
427
 
@@ -1,11 +1,18 @@
1
1
  // Small filesystem read helpers used by session-runtime submodules.
2
2
  import { readdirSync, readFileSync } from 'node:fs';
3
+ import { holdsNoData } from '../../../shared/atomic-file.mjs';
3
4
 
4
5
  export { readJsonSafe } from '../../../shared/json-file.mjs';
5
6
 
7
+ // The default data dir of a loader factory: a path, or a resolver read per
8
+ // call so a later runtime start's data dir is honored.
9
+ export const defaultDirResolver = (dataDir) => (typeof dataDir === 'function' ? dataDir : () => dataDir);
10
+
6
11
  export function readTextSafe(path) {
7
12
  try {
8
- return readFileSync(path, 'utf8').trim();
13
+ const text = readFileSync(path, 'utf8');
14
+ // A crash can leave NUL bytes (String.trim keeps them): no data is no text.
15
+ return holdsNoData(text) ? '' : text.trim();
9
16
  } catch {
10
17
  return '';
11
18
  }
@@ -133,7 +133,7 @@ export function createGoalMutations(ctx) {
133
133
  await write(
134
134
  join(root, 'history', id, `${assertSessionId(goal.id)}.json`),
135
135
  { version: GOAL_FILE_VERSION, goal },
136
- { lock: true, secret: true, fsync: false, timeoutMs: 2_000 }
136
+ { lock: true, secret: true, timeoutMs: 2_000 }
137
137
  );
138
138
  };
139
139
 
@@ -65,6 +65,10 @@ export function createGoalRuntime({
65
65
  // context still owes, from the rules and state already delivered into it.
66
66
  // In memory on purpose: a restart is a fresh context that needs them again.
67
67
  continuationTiers: new Map(),
68
+ // Session -> Goal id whose turn ended in a stop (failed, cancelled, usage
69
+ // limit) the record could not save. The record still reads active, so
70
+ // without this a full disk would start turn after turn into the same error.
71
+ unsavedStops: new Map(),
68
72
  closed: false,
69
73
  };
70
74
  Object.assign(ctx, createGoalStore(ctx));
@@ -116,6 +120,7 @@ export function createGoalRuntime({
116
120
  ctx.observedGoals.clear();
117
121
  ctx.idleReviewTurns.clear();
118
122
  ctx.continuationTiers.clear();
123
+ ctx.unsavedStops.clear();
119
124
  listeners.clear();
120
125
  // Accepted writes keep their ordering until they settle. Callers may
121
126
  // await this barrier before releasing the session's backing resources.
@@ -1,6 +1,13 @@
1
- import { readFileSync, readdirSync, rmSync, statSync } from 'node:fs';
1
+ import { readdirSync, rmSync, statSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
2
3
  import { join } from 'node:path';
3
- import { writeJsonAtomicAsync } from '../../../shared/atomic-file.mjs';
4
+ import {
5
+ parseJsonText,
6
+ readJsonStateSync,
7
+ unsupportedState,
8
+ withFileLock,
9
+ writeJsonAtomicAsync,
10
+ } from '../../../shared/atomic-file.mjs';
4
11
  import { clean } from '../../../shared/clean.mjs';
5
12
  import {
6
13
  DEFAULT_COMPLETED_GOAL_TTL_MS,
@@ -87,31 +94,58 @@ export function reportGoalStorageError(error) {
87
94
  process.emitWarning(error?.message || String(error), { code: 'GOAL_STORAGE_ERROR' });
88
95
  }
89
96
 
97
+ // A damaged record (empty, NUL, torn, invalid) never blocks the Goal
98
+ // feature: readJsonStateSync keeps its bytes aside as `<file>.corrupt-<ts>`,
99
+ // restores the last good `.bak` when there is one, else the session has no
100
+ // Goal. A record from a newer build is left in place and reads as no Goal.
101
+ // Real I/O errors still throw.
90
102
  function readGoalRecordFile(path, sessionId, normalizeGoal, at) {
103
+ const record = readJsonStateSync(path, {
104
+ secret: true,
105
+ fallback: null,
106
+ validate(parsed) {
107
+ if (Number.isSafeInteger(parsed?.version) && parsed.version > GOAL_FILE_VERSION) {
108
+ throw unsupportedState(`Goal record version ${parsed.version} is newer than this build`);
109
+ }
110
+ if (
111
+ !parsed ||
112
+ typeof parsed !== 'object' ||
113
+ !Object.hasOwn(parsed, 'goal') ||
114
+ parsed.version !== GOAL_FILE_VERSION
115
+ ) {
116
+ throw new Error('unsupported or invalid Goal record');
117
+ }
118
+ if (parsed.goal !== null && (typeof parsed.goal !== 'object' || Array.isArray(parsed.goal)))
119
+ throw new Error('invalid Goal value');
120
+ return { version: GOAL_FILE_VERSION, goal: normalizeGoal(parsed.goal, sessionId, at) };
121
+ },
122
+ });
123
+ return record ?? { version: GOAL_FILE_VERSION, goal: null };
124
+ }
125
+
126
+ // A record written by a newer build reads as no Goal and stays in place; a
127
+ // write must not replace it (nor, through `.bak`, its backup). Caller holds the
128
+ // record's lock. Unreadable or unparsable content is damage, not a newer build.
129
+ async function refuseNewerRecord(path) {
91
130
  let text;
92
131
  try {
93
- text = readFileSync(path, 'utf8');
132
+ text = await readFile(path, 'utf8');
94
133
  } catch (error) {
95
- if (error?.code === 'ENOENT') return { version: GOAL_FILE_VERSION, goal: null };
134
+ if (error?.code === 'ENOENT') return;
96
135
  throw error;
97
136
  }
137
+ let parsed;
98
138
  try {
99
- const parsed = JSON.parse(text);
100
- if (
101
- !parsed ||
102
- typeof parsed !== 'object' ||
103
- !Object.hasOwn(parsed, 'goal') ||
104
- parsed.version !== GOAL_FILE_VERSION
105
- ) {
106
- throw new Error('unsupported or invalid Goal record');
107
- }
108
- if (parsed.goal !== null && (typeof parsed.goal !== 'object' || Array.isArray(parsed.goal)))
109
- throw new Error('invalid Goal value');
110
- return { version: GOAL_FILE_VERSION, goal: normalizeGoal(parsed.goal, sessionId, at) };
111
- } catch (cause) {
112
- throw new Error(
113
- `cannot read Goal record ${path}: ${cause.message}; original file preserved, repair or explicitly clear it before creating a Goal`,
114
- { cause }
139
+ parsed = parseJsonText(text);
140
+ } catch {
141
+ return;
142
+ }
143
+ if (Number.isSafeInteger(parsed?.version) && parsed.version > GOAL_FILE_VERSION) {
144
+ throw Object.assign(
145
+ new Error(
146
+ `Goal record ${path} was written by a newer build (version ${parsed.version} > ${GOAL_FILE_VERSION}); update Mixdog to change this Goal`
147
+ ),
148
+ { code: 'EGOALNEWERVERSION' }
115
149
  );
116
150
  }
117
151
  }
@@ -149,7 +183,7 @@ export function createGoalStorage({ pathFor, normalizeGoal, now, writeRecord = w
149
183
  try {
150
184
  record = readGoalRecordFile(path, id, normalizeGoal, now());
151
185
  } catch (error) {
152
- if (cached && TRANSIENT_READ_CODES.has(error?.cause?.code ?? error?.code)) {
186
+ if (cached && TRANSIENT_READ_CODES.has(error?.code)) {
153
187
  return structuredClone(cached.record);
154
188
  }
155
189
  throw error;
@@ -166,7 +200,19 @@ export function createGoalStorage({ pathFor, normalizeGoal, now, writeRecord = w
166
200
  const snapshot = structuredClone(record);
167
201
  writing.add(id);
168
202
  try {
169
- await writeRecord(pathFor(id), snapshot, { lock: true, secret: true, fsync: false, timeoutMs: 2_000 });
203
+ const path = pathFor(id);
204
+ // The newer-build check and the write share one lock hold, so nothing
205
+ // can publish a newer record between them. The writer's own lock
206
+ // request re-enters this hold.
207
+ await withFileLock(
208
+ `${path}.lock`,
209
+ async () => {
210
+ await refuseNewerRecord(path);
211
+ // Durable, with the replaced version kept as `.bak` for damage recovery.
212
+ await writeRecord(path, snapshot, { lock: true, secret: true, backup: true, timeoutMs: 2_000 });
213
+ },
214
+ { timeoutMs: 2_000 }
215
+ );
170
216
  // The write lock has already been released. A subsequent stat could
171
217
  // belong to another writer, so validate the file on the next read.
172
218
  cache.set(id, { record: snapshot, stamp: null });
@@ -112,7 +112,7 @@ const GOAL_TOOL_ACTIONS = {
112
112
  status(ctx, id) {
113
113
  // Same view the user sees. Reading the stored record here showed the
114
114
  // model Goals the user had already archived away.
115
- ctx.readRecord(id); // Corruption is an actionable error, not "no Goal".
115
+ ctx.readRecord(id); // A real I/O error is an actionable error, not "no Goal".
116
116
  return toolReply(ctx, id, ctx.visibleSnapshot(id), { full: true });
117
117
  },
118
118
  async create(ctx, id, args) {
@@ -114,6 +114,7 @@ export function createGoalTurnLifecycle(ctx) {
114
114
  turnStartedAt,
115
115
  idleReviewTurns,
116
116
  continuationTiers,
117
+ unsavedStops,
117
118
  withMutation,
118
119
  readRecord,
119
120
  commit,
@@ -163,7 +164,14 @@ export function createGoalTurnLifecycle(ctx) {
163
164
  applyTurnOutcome(goal, detail, at);
164
165
  }
165
166
  goal.updatedAt = at;
166
- const published = await commit(id, goal);
167
+ let published;
168
+ try {
169
+ published = await commit(id, goal);
170
+ } catch (error) {
171
+ if (goal.status !== 'active') unsavedStops.set(id, goal.id);
172
+ throw error;
173
+ }
174
+ unsavedStops.delete(id);
167
175
  if (automaticTurnWithoutActivity(detail, published)) idleReviewTurns.set(id, idleReviewKey(published));
168
176
  return published;
169
177
  });
@@ -189,6 +197,7 @@ export function createGoalTurnLifecycle(ctx) {
189
197
  if (goal?.status !== 'active') return { run: false, reason: goal?.status || 'missing', goal };
190
198
  if (runningAgentWork(agentStatus)) return { run: false, reason: 'agent-running', goal };
191
199
  const id = assertSessionId(sessionId);
200
+ if (unsavedStops.get(id) === goal.id) return { run: false, reason: 'unsaved-stop', goal };
192
201
  const answered = idleReviewTurns.get(id) === idleReviewKey(goal);
193
202
  if (settledDurationWait(goal)) {
194
203
  // The model already answered this exact list with no new work, so the
@@ -25,13 +25,11 @@ export const AGENT_MODE_DELEGATION = `# Delegation
25
25
  Handle conversation yourself. Delegate specialised work to sub-agents. Hand
26
26
  substantial coding to a new agent task session (\`session create\`) with
27
27
  a self-contained brief: start it when already requested, otherwise suggest it. Then relay the
28
- result in your own words. Work sent to an existing session (\`session send\`)
29
- reports back only when its result says reportsBack: true; promise to report
30
- only then.`;
28
+ result in your own words. Promise a follow-up only when the session result allows it.`;
31
29
 
32
- /** Mixy only: it also routes requests to the user's contact agents. */
33
- export const MIXY_CONTACTS_INTRO = `You also coordinate the user's contact agents: route a request to the
34
- contact best suited for it with the same kind of brief.`;
30
+ /** Mixy only: the user's other bots, its contact catalog's intro. */
31
+ export const MIXY_CONTACTS_INTRO = `The user's other bots. Hand one a request with \`session send\` to its chat and a
32
+ self-contained brief.`;
35
33
 
36
34
  export function orchestrationInstructions(value) {
37
35
  const mode = normalizeOrchestrationMode(value);
@@ -0,0 +1,81 @@
1
+ // A route's context-window selection against provider windows: the share and
2
+ // window a route names, and what a session is created with for it. Shared by
3
+ // every session builder (spawned agents, hidden-role dispatch, the Lead).
4
+ import { providerCachedModelMetadataSync } from '../providers/provider-catalog-cache.mjs';
5
+ import { contextWindowRange, normalizeContextTokens } from '../../../shared/llm/default-context-window.mjs';
6
+ import { clean } from './session-text.mjs';
7
+
8
+ // The persisted provider model rows are the SAME source the picker sized its
9
+ // slider against, so they carry both windows (openai-oauth: 272k default /
10
+ // 1M max). Reading them keeps a percentage saved against the picker's scale
11
+ // meaningful; anything that only knows a single window would silently rescale
12
+ // it (30% of 272k instead of 30% of 1M).
13
+ const clampPercent = (percent) => Math.max(10, Math.min(100, percent));
14
+
15
+ function cachedRouteWindows(provider, model) {
16
+ const row = providerCachedModelMetadataSync(provider, model)?.rawProviderModel || null;
17
+ if (!row) return null;
18
+ const contextWindow = Number(row.contextWindow ?? row.context_window ?? row.max_input_tokens) || 0;
19
+ const maxContextWindow = Number(row.maxContextWindow ?? row.max_context_window) || 0;
20
+ return contextWindow > 0 || maxContextWindow > 0 ? { contextWindow, maxContextWindow } : null;
21
+ }
22
+
23
+ export function resolveRouteContextState(targetRoute = {}, modelMeta = null, windowLookup = cachedRouteWindows) {
24
+ // Only providers that implement getCachedModelInfo (openai-oauth, cursor,
25
+ // openai-compat, opencode-go) hand lookupModelMeta a window; every other one
26
+ // (anthropic-oauth, grok-oauth, gemini, …) gets the bare `{ id, provider }`
27
+ // placeholder. Reading the window from that alone yields 0, which dropped the
28
+ // saved context percentage for the entire session — while resolveSessionContextMeta
29
+ // went on to size that session from the provider/catalog window (Claude Opus 5:
30
+ // a full 1M rather than the selected 500k). Read the cached provider row here so
31
+ // the percentage and the session boundary cannot disagree.
32
+ const windowMeta =
33
+ Number(modelMeta?.contextWindow) > 0 || Number(modelMeta?.maxContextWindow) > 0
34
+ ? modelMeta
35
+ : windowLookup?.(clean(targetRoute?.provider), clean(targetRoute?.model)) || modelMeta;
36
+ const { defaultWindow, maxWindow } = contextWindowRange({
37
+ provider: clean(targetRoute?.provider),
38
+ contextWindow: windowMeta?.contextWindow,
39
+ maxContextWindow: windowMeta?.maxContextWindow,
40
+ });
41
+ if (!maxWindow) {
42
+ return { contextPercent: undefined, contextDefaultPercent: undefined, selectedContextWindow: undefined };
43
+ }
44
+ const contextDefaultPercent = clampPercent(Math.round((defaultWindow / maxWindow) * 10) * 10);
45
+ // An exact token selection overrides the 10% stops. It is clamped to the
46
+ // selectable range; one that lands on the default window is the default.
47
+ const requestedTokens = normalizeContextTokens(targetRoute?.contextTokens);
48
+ if (requestedTokens) {
49
+ const contextTokens = Math.max(Math.ceil(maxWindow * 0.1), Math.min(maxWindow, requestedTokens));
50
+ if (contextTokens === defaultWindow) {
51
+ return { contextPercent: contextDefaultPercent, contextDefaultPercent, selectedContextWindow: defaultWindow };
52
+ }
53
+ const contextPercent = clampPercent(Math.round((contextTokens / maxWindow) * 10) * 10);
54
+ return { contextPercent, contextDefaultPercent, selectedContextWindow: contextTokens, contextTokens };
55
+ }
56
+ const requested = Number(targetRoute?.contextPercent);
57
+ const contextPercent =
58
+ Number.isFinite(requested) && requested > 0 ? clampPercent(Math.round(requested / 10) * 10) : contextDefaultPercent;
59
+ const selectedContextWindow =
60
+ contextPercent === contextDefaultPercent
61
+ ? defaultWindow
62
+ : Math.max(1, Math.floor((maxWindow * contextPercent) / 100));
63
+ return { contextPercent, contextDefaultPercent, selectedContextWindow };
64
+ }
65
+
66
+ /**
67
+ * The session fields (`contextPercent`, `selectedContextWindow`, `contextTokens`)
68
+ * a route's own context choice creates a session with, from its resolved
69
+ * `state`. A model whose window is not known resolves to nothing: the choice
70
+ * then travels as stored (the session sizes it from the provider's window)
71
+ * instead of being dropped for the model's default. Only set fields are returned.
72
+ */
73
+ export function contextSelectionFromState(state, route) {
74
+ const windowKnown = state.contextDefaultPercent !== undefined;
75
+ const chosen = {
76
+ contextPercent: state.contextPercent ?? route.contextPercent,
77
+ selectedContextWindow: state.selectedContextWindow,
78
+ contextTokens: state.contextTokens ?? (windowKnown ? undefined : route.contextTokens),
79
+ };
80
+ return Object.fromEntries(Object.entries(chosen).filter(([, value]) => value));
81
+ }
@@ -1,6 +1,7 @@
1
1
  // Session message/preview text helpers. Pure, no runtime-closure deps.
2
2
  import { clean } from '../../../shared/clean.mjs';
3
3
  import { stripInjectedBlocks } from '../../../shared/injected-display-text.mjs';
4
+ import { CONTROL_MARKER_RE } from '../../../shared/tool-execution-contract.mjs';
4
5
 
5
6
  export function sessionMessageText(content) {
6
7
  if (content == null) return '';
@@ -42,7 +43,7 @@ function messageContextText(message) {
42
43
  const SYNTHETIC_SESSION_TEXT_PATTERNS = Object.freeze([
43
44
  /^\[mixdog-runtime\]/i,
44
45
  // User-cancel + process-restart control rows must never title/preview a session.
45
- /^\[(?:truncated|request interrupted by (?:user(?: for tool use)?|process restart))\]$/i,
46
+ CONTROL_MARKER_RE,
46
47
  /^a previous model worked on this task and produced the compacted handoff summary below\b/i,
47
48
  // Compact/auto-clear re-seed handoff variants: these lead the FIRST user
48
49
  // message of every post-compaction session, so titling from them painted