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
@@ -3,9 +3,12 @@ import { estimateMessagesTokens } from '../context-utils.mjs';
3
3
  import { estimateTokens } from '../token-estimate.mjs';
4
4
  import { persistToolResultArtifactSync, previewToolResultArtifact } from '../tool-result-offload.mjs';
5
5
  import {
6
+ EXECUTION_TEXT_SOURCE,
6
7
  isActualUserInstructionMessage,
8
+ isCompactConversationRow,
7
9
  isProtectedContextAckMessage,
8
10
  latestActualUserInstructionIndex,
11
+ pendingTaskNotificationIndexes,
9
12
  } from './messages.mjs';
10
13
  import { staleObservations, supersedeObservation } from './observation-supersession.mjs';
11
14
  import { carryMessageId, withoutMessageIdsReplacer } from '../message-ids.mjs';
@@ -168,9 +171,10 @@ function shrinkGroup(group, archive, level) {
168
171
  });
169
172
  }
170
173
 
171
- // Walk from the anchor: kept groups verbatim, user instructions, and the
172
- // conversational assistant text the mode allows.
173
- function assembleTail(messages, kept, { anchor, firstKept, preserveConversation }) {
174
+ // Walk from the anchor: kept groups verbatim, user instructions, unanswered
175
+ // task notifications, and the conversation the mode allows (rule-only:
176
+ // assistant text and runtime conversation rows).
177
+ function assembleTail(messages, kept, { anchor, firstKept, preserveConversation, pending }) {
174
178
  const tail = [];
175
179
  for (let i = Math.max(0, anchor); i < messages.length; i += 1) {
176
180
  const group = kept.get(i);
@@ -184,8 +188,16 @@ function assembleTail(messages, kept, { anchor, firstKept, preserveConversation
184
188
  // Omitted execution stays in the archive; its conversational text
185
189
  // remains verbatim without replaying an orphaned call or reasoning.
186
190
  if (message.toolCalls?.length) {
187
- // The text-only copy is this message's rewrite: it keeps its id.
188
- if (message.content) tail.push(carryMessageId({ role: 'assistant', content: message.content }, message));
191
+ // The text-only copy is this message's rewrite: it keeps its id, and
192
+ // its marker keeps it from reading as a final reply.
193
+ if (message.content) {
194
+ tail.push(
195
+ carryMessageId(
196
+ { role: 'assistant', content: message.content, meta: { source: EXECUTION_TEXT_SOURCE } },
197
+ message
198
+ )
199
+ );
200
+ }
189
201
  } else {
190
202
  tail.push(message);
191
203
  }
@@ -193,6 +205,8 @@ function assembleTail(messages, kept, { anchor, firstKept, preserveConversation
193
205
  }
194
206
  if (
195
207
  isActualUserInstructionMessage(message) ||
208
+ pending.has(i) ||
209
+ (preserveConversation && isCompactConversationRow(message)) ||
196
210
  (i >= firstKept && message?.role === 'assistant' && !message.toolCalls?.length)
197
211
  ) {
198
212
  tail.push(message);
@@ -201,6 +215,19 @@ function assembleTail(messages, kept, { anchor, firstKept, preserveConversation
201
215
  return tail;
202
216
  }
203
217
 
218
+ // The reminder naming the new dump and the earlier ones it supersedes.
219
+ function recoveryReminder(archive, previousRecovery) {
220
+ const earlier = [...new Set(previousRecovery.flatMap(recoveryArchivePaths))]
221
+ .filter((path) => path !== archive.path)
222
+ .slice(-MAX_EARLIER_DUMPS);
223
+ const earlierLine = earlier.length ? `\nEarlier dumps (older first): ${earlier.join(EARLIER_DUMPS_SEPARATOR)}` : '';
224
+ return {
225
+ role: 'user',
226
+ meta: { source: EXECUTION_RECOVERY_SOURCE, synthetic: true },
227
+ content: `<system-reminder>\nSome execution history was omitted or shortened to fit the tool-history budget. This is not an instruction to repeat those calls. The original calls, outcomes, and earlier recovery references are available at ${archive.path} (sha256:${archive.sha256}). The dump is JSONL: line N+1 is message index N; an "archivedMessage" line names the file holding that message, so search its directory too. Read only missing evidence when needed.${earlierLine}\n</system-reminder>`,
228
+ };
229
+ }
230
+
204
231
  export function buildExecutionTail(
205
232
  transcript,
206
233
  { contextWindow, sessionId, preserveConversation = false, preview = false } = {}
@@ -225,17 +252,7 @@ export function buildExecutionTail(
225
252
  const dump = persist({ sessionId, toolCallId: 'compact-execution', channel: 'compact-execution', content: text });
226
253
  if (!dump) throw new Error('compact: execution history could not be archived; original context preserved');
227
254
  archive = dump;
228
- const earlier = [...new Set(previousRecovery.flatMap(recoveryArchivePaths))]
229
- .filter((path) => path !== archive.path)
230
- .slice(-MAX_EARLIER_DUMPS);
231
- const earlierLine = earlier.length ? `\nEarlier dumps (older first): ${earlier.join(EARLIER_DUMPS_SEPARATOR)}` : '';
232
- recovery = [
233
- {
234
- role: 'user',
235
- meta: { source: EXECUTION_RECOVERY_SOURCE, synthetic: true },
236
- content: `<system-reminder>\nSome execution history was omitted or shortened to fit the tool-history budget. This is not an instruction to repeat those calls. The original calls, outcomes, and earlier recovery references are available at ${archive.path} (sha256:${archive.sha256}). The dump is JSONL: line N+1 is message index N; an "archivedMessage" line names the file holding that message, so search its directory too. Read only missing evidence when needed.${earlierLine}\n</system-reminder>`,
237
- },
238
- ];
255
+ recovery = [recoveryReminder(archive, previousRecovery)];
239
256
  };
240
257
  // An observation a later one of the same page or window replaced keeps only
241
258
  // its outcome and a reference to the archived original.
@@ -294,13 +311,22 @@ export function buildExecutionTail(
294
311
  throw new Error('compact: latest execution group cannot fit the tool-history budget; original context preserved');
295
312
  }
296
313
  const latest = latestActualUserInstructionIndex(messages);
314
+ const pendingIndexes = pendingTaskNotificationIndexes(messages);
315
+ const pending = new Set(pendingIndexes);
297
316
  const firstKept = kept.size ? Math.min(...kept.keys()) : messages.length;
298
317
  const latestKept = latest < 0 ? firstKept : latest;
299
- const anchor = preserveConversation ? 0 : requestStart(messages, Math.min(firstKept, latestKept));
300
- const tail = assembleTail(messages, kept, { anchor, firstKept, preserveConversation });
318
+ const anchor = preserveConversation
319
+ ? 0
320
+ : Math.min(requestStart(messages, Math.min(firstKept, latestKept)), ...pendingIndexes);
321
+ const tail = assembleTail(messages, kept, { anchor, firstKept, preserveConversation, pending });
301
322
  // With no execution to retain, keep the existing latest-request contract.
302
323
  if (!preserveConversation && !groups.length && !previousRecovery.length) {
303
- return { messages: latest < 0 ? [] : [messages[latest]], toolTokens: 0, toolBudget: budget, retainedGroups: 0 };
324
+ return {
325
+ messages: messages.filter((_message, index) => index === latest || pending.has(index)),
326
+ toolTokens: 0,
327
+ toolBudget: budget,
328
+ retainedGroups: 0,
329
+ };
304
330
  }
305
331
  return {
306
332
  messages: [...recovery, ...tail],
@@ -65,6 +65,9 @@ export async function summarizeOnSharedPrefix({ sessionId, route, conversationIn
65
65
  try {
66
66
  response = await route.provider.send(messages, request.model, request.tools.length ? request.tools : undefined, {
67
67
  ...opts,
68
+ // Usage-ledger source, as on the isolated request (a background
69
+ // session's own sourceType in `opts.session` still wins there).
70
+ requestKind: 'compaction',
68
71
  transportScope,
69
72
  admissionOwner: sessionId,
70
73
  signal: requestSignal,
@@ -10,6 +10,7 @@ import {
10
10
  isProtectedContextUserMessage,
11
11
  isSummaryMessage,
12
12
  } from './user-message-kind.mjs';
13
+ import { CONTROL_MARKER_RE } from '../../../../shared/tool-execution-contract.mjs';
13
14
 
14
15
  export function compactHeader(oldHistory) {
15
16
  // Stable display ids are bookkeeping, not conversation: the header names
@@ -53,6 +54,49 @@ export function isProtectedContextAckMessage(m) {
53
54
  );
54
55
  }
55
56
 
57
+ // The text-only copy rule-only Compact keeps of an omitted tool-call turn.
58
+ // It is execution, not a final reply, though it no longer carries its calls.
59
+ export const EXECUTION_TEXT_SOURCE = 'compact-execution-text';
60
+
61
+ // Delivered task notifications (a background agent's or task's completion,
62
+ // stored as a role:'user' turn with source 'task-notification') the model has
63
+ // not answered yet: every one after the latest final assistant reply. They
64
+ // are turn input but neither a user instruction nor summarized conversation,
65
+ // so a Compact must carry them verbatim or the report is lost.
66
+ export function pendingTaskNotificationIndexes(messages) {
67
+ const list = Array.isArray(messages) ? messages : [];
68
+ const pending = [];
69
+ for (let index = list.length - 1; index >= 0; index -= 1) {
70
+ const message = list[index];
71
+ if (
72
+ message?.role === 'assistant' &&
73
+ !message.toolCalls?.length &&
74
+ !isProtectedContextAckMessage(message) &&
75
+ message.meta?.source !== EXECUTION_TEXT_SOURCE
76
+ ) {
77
+ break;
78
+ }
79
+ if (message?.role === 'user' && message.meta?.source === 'task-notification') pending.unshift(index);
80
+ }
81
+ return pending;
82
+ }
83
+
84
+ // Runtime rows that are conversation, not instructions: task and channel
85
+ // notifications (answered or not), routine and task results posted into a
86
+ // home thread, and turn interruption markers. Rule-only Compact keeps them
87
+ // with the dialogue; the summary reads them as data.
88
+ const CONVERSATION_RUNTIME_SOURCES = new Set(['task-notification', 'schedule-result']);
89
+
90
+ export function isCompactConversationRow(message) {
91
+ if (message?.role !== 'user') return false;
92
+ if (CONVERSATION_RUNTIME_SOURCES.has(String(message.meta?.source || ''))) return true;
93
+ return (
94
+ typeof message.content === 'string' &&
95
+ CONTROL_MARKER_RE.test(message.content.trim()) &&
96
+ !isActualUserInstructionMessage(message)
97
+ );
98
+ }
99
+
56
100
  function referenceFilesManifestMessage(message) {
57
101
  if (message?.role !== 'user' || typeof message.content !== 'string') return null;
58
102
  const text = message.content.trimStart();
@@ -5,16 +5,19 @@ import { estimateMessagesTokens, reconcileDedupStubs, sanitizeToolPairs } from '
5
5
  import { HANDOFF_TIMEOUT_MAX_MS, SUMMARY_OUTPUT_TOKENS, compactDebugLog } from './constants.mjs';
6
6
  import {
7
7
  contentText,
8
+ redactRawSecretString,
8
9
  redactToolCallSecretsInMessages,
9
10
  safeEstimateMessagesTokens,
10
11
  textByteLength,
11
12
  } from './text-utils.mjs';
12
13
  import {
13
14
  contextAckMessage,
15
+ isCompactConversationRow,
14
16
  isProtectedContextAckMessage,
15
17
  isSummaryMessage,
16
18
  latestActualUserInstructionIndex,
17
19
  latestActualUserInstructionMessage,
20
+ pendingTaskNotificationIndexes,
18
21
  splitProtectedContext,
19
22
  } from './messages.mjs';
20
23
  import { activeTurnContinuationMessage } from './continuation.mjs';
@@ -25,6 +28,8 @@ import { stripRuntimeUserContext, withRuntimeUserContext } from '../runtime-user
25
28
  import { carryMessageId, messageIdOf } from '../message-ids.mjs';
26
29
  import { normalizeIngestRole, shouldExcludeIngestMessage } from '../../../../memory/lib/session-ingest.mjs';
27
30
  import { codexWireSendOpts } from '../manager/session-id.mjs';
31
+ import { messageHasReferencableMedia, referenceMessageMedia } from '../../../../attachments/attachment-reference.mjs';
32
+ import { messageAttachmentTokens } from '../attachment-preflight.mjs';
28
33
  import { rebaseCompactedEffortConfiguration } from '../../providers/effort-configuration.mjs';
29
34
  import {
30
35
  COMPACTION_SYSTEM_PROMPT,
@@ -73,8 +78,13 @@ function splitFreshSource(messages) {
73
78
  // Budgeting, AI input and rule-only retention use this same projection.
74
79
  // Keep current-turn reminders; remove only proven runtime additions from
75
80
  // older requests. Unmarked legacy text remains part of the summary budget.
81
+ // Older requests also carry their attachments by reference from here on:
82
+ // the file stays on disk under the path the reference names, so the
83
+ // compacted context keeps the pointer instead of the whole document.
76
84
  live: conversation
77
- .map((message, index) => (index === latestUserIndex ? message : stripRuntimeUserContext(message)))
85
+ .map((message, index) =>
86
+ index === latestUserIndex ? message : referenceMessageMedia(stripRuntimeUserContext(message))
87
+ )
78
88
  .filter((message) => !isSummaryMessage(message)),
79
89
  sanitized,
80
90
  };
@@ -95,28 +105,76 @@ function chunkConversationMessage(role, content, source = {}) {
95
105
  return out;
96
106
  }
97
107
 
98
- function pureConversationForHandoff(messages) {
108
+ // What the summary reads of runtime conversation rows and tool calls. The
109
+ // rows stay whole in rule-only Compact; the calls stay in the execution tail
110
+ // and archive.
111
+ const SUMMARY_RUNTIME_ROW_CHARS = 4_000;
112
+ const SUMMARY_TOOL_ARGS_CHARS = 200;
113
+ const SUMMARY_TOOL_OUTCOME_CHARS = 300;
114
+
115
+ function boundedSummaryText(text, limit) {
116
+ return text.length > limit ? `${text.slice(0, limit)}… [${text.length - limit} more chars]` : text;
117
+ }
118
+
119
+ function toolCallSummaryLine(call, result) {
120
+ const rawArgs = typeof call.arguments === 'string' ? call.arguments : JSON.stringify(call.arguments ?? {});
121
+ const args = boundedSummaryText(rawArgs.replace(/\s+/g, ' '), SUMMARY_TOOL_ARGS_CHARS);
122
+ // Arguments arrive redacted (splitFreshSource); the outcome is redacted
123
+ // here, before any cut can split a secret from its key.
124
+ const outcome = redactRawSecretString(contentText(result?.content)).trim();
125
+ // An ask_user result is the user's own answer: never cut.
126
+ const shown =
127
+ call.name === 'ask_user' ? outcome : boundedSummaryText(outcome.replace(/\s+/g, ' '), SUMMARY_TOOL_OUTCOME_CHARS);
128
+ return `[tool call] ${call.name}(${args}) → ${result ? shown || '(empty result)' : '(no result)'}`;
129
+ }
130
+
131
+ // The conversation Compact measures and summarizes: user and assistant text
132
+ // (the memory-ingest projection) plus runtime conversation rows. `summary`
133
+ // bounds those rows and adds one line per tool call with its outcome.
134
+ function compactionConversationRows(messages, { summary = false } = {}) {
135
+ const list = messages || [];
136
+ const results = new Map(list.filter((m) => m?.role === 'tool').map((m) => [m.toolCallId, m]));
99
137
  const out = [];
100
- for (const message of messages || []) {
138
+ for (const message of list) {
101
139
  if (!message || typeof message !== 'object') continue;
102
- const role = normalizeIngestRole(message.role);
103
- if (!role || shouldExcludeIngestMessage(message)) continue;
104
140
  const content = contentText(message.content).trim();
105
- if (!content) continue;
106
- out.push(...chunkConversationMessage(role, content, message));
141
+ if (isCompactConversationRow(message)) {
142
+ // A notification can carry a background command's output: the summary
143
+ // reads it redacted like a tool outcome.
144
+ const text = summary ? boundedSummaryText(redactRawSecretString(content), SUMMARY_RUNTIME_ROW_CHARS) : content;
145
+ if (text) out.push(...chunkConversationMessage('user', text, message));
146
+ continue;
147
+ }
148
+ const role = normalizeIngestRole(message.role);
149
+ if (role && content && !shouldExcludeIngestMessage(message)) {
150
+ out.push(...chunkConversationMessage(role, content, message));
151
+ }
152
+ if (summary && message.role === 'assistant' && message.toolCalls?.length) {
153
+ const lines = message.toolCalls.map((call) => toolCallSummaryLine(call, results.get(call.id)));
154
+ out.push(...chunkConversationMessage('assistant', lines.join('\n'), message));
155
+ }
107
156
  }
108
157
  return out;
109
158
  }
110
159
 
111
- export function conversationCompactionInput(messages) {
160
+ /** The conversation Compact measures against its summary threshold, or with
161
+ * `summary`, the input the summary is written from. */
162
+ export function conversationCompactionInput(messages, { summary = false } = {}) {
112
163
  const source = splitFreshSource(messages);
113
- const latest = latestActualUserInstructionIndex(source.live);
164
+ // The turn input (the latest actual request and unanswered notifications)
165
+ // is mandatory and must never be replaced by a summary. Skills and runtime
166
+ // injections are excluded by the same conversation projection used by
167
+ // summary generation.
168
+ const turnInput = new Set([
169
+ latestActualUserInstructionIndex(source.live),
170
+ ...pendingTaskNotificationIndexes(source.live),
171
+ ]);
114
172
  return [
115
173
  ...(source.previousSummaryMessage ? [source.previousSummaryMessage] : []),
116
- // The latest actual request is mandatory and must never be replaced by
117
- // a summary. Tool outputs, skills and runtime injections are excluded
118
- // by the same conversation projection used by summary generation.
119
- ...pureConversationForHandoff(source.live.filter((_, index) => index !== latest)),
174
+ ...compactionConversationRows(
175
+ source.live.filter((_, index) => !turnInput.has(index)),
176
+ { summary }
177
+ ),
120
178
  ];
121
179
  }
122
180
 
@@ -135,6 +193,9 @@ function summarySendOpts(opts) {
135
193
  // Fast mode is opt-in (it needs separate account credits); the summary
136
194
  // follows the route/session choice and never enables it on its own.
137
195
  fast: opts.fast ?? opts.sendOpts?.fast ?? false,
196
+ // Usage-ledger source for a foreground session's summary call (a
197
+ // background session's own sourceType still wins there).
198
+ requestKind: 'compaction',
138
199
  maxOutputTokens: opts.maxOutputTokens || SUMMARY_OUTPUT_TOKENS,
139
200
  providerState: undefined,
140
201
  onToolCall: undefined,
@@ -219,7 +280,8 @@ export async function generateFreshHandoffSummary(provider, messages, model, bud
219
280
  const startedAt = Date.now();
220
281
  const budget = effectiveBudget(budgetTokens, opts);
221
282
  const source = splitFreshSource(messages);
222
- let head = opts.filterOldHistoryForIngest === true ? pureConversationForHandoff(source.live) : source.live;
283
+ let head =
284
+ opts.filterOldHistoryForIngest === true ? compactionConversationRows(source.live, { summary: true }) : source.live;
223
285
  if (head.length === 0 && !source.previousSummary) {
224
286
  throw new Error('generateFreshHandoffSummary: no compactable session history');
225
287
  }
@@ -331,7 +393,12 @@ function prependLatestUserContext(message, prefix) {
331
393
  if (replacesGoalState) {
332
394
  suffix = suffix.replace(/<system-reminder>\s*<goal_state>[\s\S]*?<\/goal_state>\s*<\/system-reminder>\s*/gi, '');
333
395
  }
334
- return withRuntimeUserContext(bare, { prefix: `${text}\n\n`, suffix });
396
+ // The kept suffix still carries the turn's agent list reminder: it stays told.
397
+ const told = bare !== message && message.meta.agentListing ? { agentListing: message.meta.agentListing } : null;
398
+ return withRuntimeUserContext(told ? { ...bare, meta: { ...bare.meta, ...told } } : bare, {
399
+ prefix: `${text}\n\n`,
400
+ suffix,
401
+ });
335
402
  }
336
403
 
337
404
  // The retained execution tail plus the current-turn messages that must
@@ -350,8 +417,11 @@ function freshContextTail(source, budgetTokens, preserveConversation, opts) {
350
417
  const retainedTail = execution.messages.map((message, index) =>
351
418
  index === latestIndex ? prependLatestUserContext(message, opts.latestUserPrefix) : message
352
419
  );
420
+ // The turn's input: the latest real request or an unanswered notification
421
+ // (a bot home thread may hold no human request at all).
422
+ const turnInput = !!latestUser || pendingTaskNotificationIndexes(source.live).length > 0;
353
423
  const activeTurnContinuation =
354
- latestUser && opts.activeTurn === true ? activeTurnContinuationMessage(source.live) : null;
424
+ turnInput && opts.activeTurn === true ? activeTurnContinuationMessage(source.live) : null;
355
425
  // Older compacted sessions may have only synthetic Goal turns left.
356
426
  // Preserve the current snapshot without inventing a human instruction
357
427
  // to attach it to or resurrecting every historical Goal reminder.
@@ -366,6 +436,7 @@ function freshContextTail(source, budgetTokens, preserveConversation, opts) {
366
436
  return {
367
437
  execution,
368
438
  latestUser,
439
+ turnInput,
369
440
  retainedTail,
370
441
  activeTurnContinuation,
371
442
  volatileTail: [
@@ -398,20 +469,57 @@ function skillLoaderPair(live, bodyIndex, name, retainedCallIds) {
398
469
  return [];
399
470
  }
400
471
 
472
+ const SKILL_RELOAD_SOURCE = 'compact-skill-reload';
473
+
474
+ function skillReloadReminder(name) {
475
+ return {
476
+ role: 'user',
477
+ content: `[mixdog-runtime] Skill "${name}" was active, but its instructions did not fit after Compact. Reload it with the Skill tool before relying on them.`,
478
+ meta: { source: SKILL_RELOAD_SOURCE, synthetic: true, skill: name },
479
+ };
480
+ }
481
+
401
482
  // Preserve complete, most-recent skill bodies from the durable transcript.
402
483
  // Never summarize/truncate operating instructions or displace the handoff
403
- // and current task. An omitted body remains eligible for a normal reload.
484
+ // and current task. An omitted body leaves a one-line reload reminder, which
485
+ // stands through later Compacts until the skill is loaded again. Reminders
486
+ // are charged to the same budget: every skill's reminder is reserved before
487
+ // any body is restored, and restoring a body releases its own reminder.
404
488
  function restoreSkillBodies(live, skillBudget, retainedTail) {
405
489
  const retainedCallIds = new Set(retainedTail.filter((m) => m?.role === 'tool').map((m) => m.toolCallId));
490
+ const bodies = latestSkillBodies(live);
491
+ const loaded = new Set(bodies.map((body) => body.name));
492
+ const reload = live
493
+ .filter((m) => m?.meta?.source === SKILL_RELOAD_SOURCE && !loaded.has(m.meta.skill))
494
+ .map((m) => m.meta.skill);
495
+ const reminderCost = (name) => estimateMessagesTokens([skillReloadReminder(name)]);
496
+ let reserved = [...new Set([...reload, ...loaded])].reduce((sum, name) => sum + reminderCost(name), 0);
497
+ const dropped = [];
406
498
  const restoredSkills = [];
407
499
  let skillTokens = 0;
408
- for (const { name, message } of latestSkillBodies(live).reverse()) {
500
+ for (const { name, message } of bodies.reverse()) {
409
501
  const restored = [...skillLoaderPair(live, live.lastIndexOf(message), name, retainedCallIds), message];
410
502
  const cost = estimateMessagesTokens(restored);
411
- if (skillTokens + cost > skillBudget) continue;
503
+ const own = reminderCost(name);
504
+ if (skillTokens + cost + reserved - own > skillBudget) {
505
+ dropped.unshift(name);
506
+ continue;
507
+ }
412
508
  restoredSkills.unshift(...restored);
413
509
  skillTokens += cost;
510
+ reserved -= own;
414
511
  }
512
+ // Newest skill first; only a budget too small for the reminders themselves
513
+ // leaves one out.
514
+ const pending = [...new Set([...reload, ...dropped])];
515
+ const reminded = new Set();
516
+ for (const name of pending.toReversed()) {
517
+ const cost = reminderCost(name);
518
+ if (skillTokens + cost > skillBudget) continue;
519
+ reminded.add(name);
520
+ skillTokens += cost;
521
+ }
522
+ restoredSkills.push(...pending.filter((name) => reminded.has(name)).map(skillReloadReminder));
415
523
  return { restoredSkills, skillTokens };
416
524
  }
417
525
 
@@ -538,7 +646,7 @@ function freshContextLayout(source, budgetTokens, opts) {
538
646
  ? fitFreshContextSummaryMessage(source.live, handoffText, Number.MAX_SAFE_INTEGER)
539
647
  : source.previousSummaryMessage;
540
648
  const tail = freshContextTail(source, budgetTokens, !replaceConversation, opts);
541
- const stableAck = tail.latestUser && summaryMessage ? stableAckMessage(source.previousAck, tail.volatileTail) : null;
649
+ const stableAck = tail.turnInput && summaryMessage ? stableAckMessage(source.previousAck, tail.volatileTail) : null;
542
650
  const stableHead = [
543
651
  ...source.protectedPrefix,
544
652
  ...(summaryMessage ? [summaryMessage] : []),
@@ -548,15 +656,43 @@ function freshContextLayout(source, budgetTokens, opts) {
548
656
  return { handoffText, summaryMessage, tail, stableHead, mandatory, mandatoryCost: estimateMessagesTokens(mandatory) };
549
657
  }
550
658
 
551
- function assertMandatoryFits(budget, mandatoryCost, summaryMessage, contextWindow) {
552
- if (mandatoryCost > budget || (summaryMessage && mandatoryCost === budget)) {
553
- const window = Number(contextWindow) > 0 ? ` (currently ${contextWindow} tokens)` : '';
554
- throw new Error(
555
- `freshContextCompactMessages: mandatory session context/latest instruction exceeds compact budget=${budget} ` +
556
- `(mandatory=${mandatoryCost}); the model context window${window} is too small for the tool definitions ` +
557
- 'and session context. Increase the model context size.'
558
- );
559
- }
659
+ function mandatoryOverflows(budget, mandatoryCost, summaryMessage) {
660
+ return mandatoryCost > budget || (summaryMessage && mandatoryCost === budget);
661
+ }
662
+
663
+ export const LATEST_TURN_EXCEEDS_CONTEXT = 'LATEST_TURN_EXCEEDS_CONTEXT';
664
+
665
+ // Says what does not fit and what would make it fit. A larger window is only
666
+ // offered when one would actually hold the required size.
667
+ function assertMandatoryFits(budget, mandatoryCost, summaryMessage, contextWindow, latestUser) {
668
+ if (!mandatoryOverflows(budget, mandatoryCost, summaryMessage)) return;
669
+ // Fixed sentences with the sizes inline: the desktop re-speaks each one
670
+ // through its catalog (error-notice-copy / transcript-failure-text).
671
+ const count = (value) => Number(value).toLocaleString('en-US');
672
+ const window = Number(contextWindow) > 0 ? `${count(contextWindow)}-token` : 'current';
673
+ const attachments = messageAttachmentTokens(latestUser);
674
+ const cause =
675
+ attachments > 0
676
+ ? `Its attachments alone are about ${count(attachments)} tokens.`
677
+ : 'The latest message and protected session context (system rules, tool definitions) are that large.';
678
+ const error = new Error(
679
+ `The latest message cannot fit this model (${LATEST_TURN_EXCEEDS_CONTEXT}): it needs about ` +
680
+ `${count(mandatoryCost)} tokens, but the ${window} window leaves ${count(budget)} for it. ${cause} ` +
681
+ 'Shorten the message or move large pasted text into a file the model can read. ' +
682
+ `A larger context window helps only if it holds ${count(mandatoryCost)} tokens plus room for the answer.`
683
+ );
684
+ error.code = LATEST_TURN_EXCEEDS_CONTEXT;
685
+ throw error;
686
+ }
687
+
688
+ // The source with the latest request's attachments sent by reference. Same
689
+ // source when it carries none.
690
+ function withLatestAttachmentsReferenced(source) {
691
+ const latest = latestActualUserInstructionIndex(source.live);
692
+ if (latest < 0 || !messageHasReferencableMedia(source.live[latest])) return source;
693
+ const live = source.live.slice();
694
+ live[latest] = referenceMessageMedia(live[latest]);
695
+ return { ...source, live };
560
696
  }
561
697
 
562
698
  function assertHandoffFits(summaryMessage, room) {
@@ -584,14 +720,26 @@ export function freshContextCompactMessages(messages, budgetTokens, opts = {}) {
584
720
  });
585
721
  }
586
722
 
587
- const source = splitFreshSource(baseSanitized);
588
- const { handoffText, summaryMessage, tail, stableHead, mandatory, mandatoryCost } = freshContextLayout(
589
- source,
590
- budgetTokens,
591
- opts
592
- );
593
- const budget = freshContextBudget({ targetBudget, budgetTokens, mandatoryCost, summaryMessage, opts });
594
- assertMandatoryFits(budget, mandatoryCost, summaryMessage, opts.contextWindow);
723
+ let source = splitFreshSource(baseSanitized);
724
+ let layout = freshContextLayout(source, budgetTokens, opts);
725
+ let budget = freshContextBudget({ targetBudget, budgetTokens, ...layout, opts });
726
+ // The latest request is mandatory and kept verbatim — except its
727
+ // attachments: when they are what does not fit (or the provider already
728
+ // refused this request as too long), they travel by reference instead of
729
+ // failing the turn, and every later turn, forever.
730
+ if (
731
+ opts.referenceLatestAttachments === true ||
732
+ mandatoryOverflows(budget, layout.mandatoryCost, layout.summaryMessage)
733
+ ) {
734
+ const referenced = withLatestAttachmentsReferenced(source);
735
+ if (referenced !== source) {
736
+ source = referenced;
737
+ layout = freshContextLayout(source, budgetTokens, opts);
738
+ budget = freshContextBudget({ targetBudget, budgetTokens, ...layout, opts });
739
+ }
740
+ }
741
+ const { handoffText, summaryMessage, tail, stableHead, mandatory, mandatoryCost } = layout;
742
+ assertMandatoryFits(budget, mandatoryCost, summaryMessage, opts.contextWindow, tail.latestUser);
595
743
  const handoff = handoffRoomFor(budget, mandatoryCost, opts);
596
744
  const summaryContent = String(summaryMessage?.content || '');
597
745
  assertHandoffFits(summaryMessage, handoff.room);
@@ -0,0 +1,16 @@
1
+ // compaction.summaryMode — which model writes a compaction summary
2
+ // (loop/fresh-context.mjs compactionSummaryPlan):
3
+ // smart warm prompt cache: fork the conversation's own cached request;
4
+ // cold: the Maintainer, else the subscription provider's lighter
5
+ // model, else the conversation's own model (isolated)
6
+ // maintainer the Maintainer (isolated), else the conversation's own model
7
+ // self the conversation's own model: fork when warm, isolated when cold
8
+ export const COMPACTION_SUMMARY_MODES = Object.freeze(['smart', 'maintainer', 'self']);
9
+
10
+ /** The saved mode; the previous release's boolean `useMaintainer:false`
11
+ * reads as 'self', true or absent as the 'smart' default. */
12
+ export function normalizeCompactionSummaryMode(compaction) {
13
+ const mode = compaction?.summaryMode;
14
+ if (COMPACTION_SUMMARY_MODES.includes(mode)) return mode;
15
+ return compaction?.useMaintainer === false ? 'self' : 'smart';
16
+ }
@@ -0,0 +1,87 @@
1
+ // What the compaction summary route choice reads: whether the session's
2
+ // prompt cache is still warm (a fork of its last request would read it), and
3
+ // the lighter model of a subscription provider.
4
+ import { cacheCapabilityForProvider, providerTailCacheLifetimeMs } from '../../agent-runtime/cache-strategy.mjs';
5
+ import { getModelMetadataSync } from '../../../../shared/llm/model-catalog.mjs';
6
+ import {
7
+ _compareVersion as compareAnthropicVersion,
8
+ _loadModelCache as loadAnthropicCatalogCache,
9
+ } from '../../providers/anthropic-model-resolve.mjs';
10
+ import { gptVersion } from '../../providers/openai-model-capabilities.mjs';
11
+ import { loadSavedCodexCatalog } from '../../providers/openai-oauth-catalog-store.mjs';
12
+ import { compactionForkRequest } from '../side-question-store.mjs';
13
+
14
+ // A provider outside the Anthropic/OpenAI lifetimes whose catalog marks prompt
15
+ // caching: the shortest common cache lifetime (Gemini's default TTL).
16
+ const CATALOG_PROMPT_CACHE_LIFETIME_MS = 5 * 60_000;
17
+ // The cache is read when the fork is sent, after the route choice: a request
18
+ // this close to its lifetime counts as cold.
19
+ const WARM_MARGIN_MIN_MS = 30_000;
20
+ const WARM_MARGIN_RATIO = 0.1;
21
+
22
+ /** How long the cache a saved request wrote or read stays readable, in ms:
23
+ * Infinity when it does not expire (openai-oauth), 0 when it is not cached.
24
+ * Anthropic: the request's message-tail tier (5m / 1h); OpenAI: the 30m tail
25
+ * that holds the conversation (the Lead's 12h breakpoint covers only the
26
+ * stable tools/instructions prefix). */
27
+ function requestCacheLifetimeMs(request) {
28
+ const name = request?.provider?.name;
29
+ if (name === 'openai-oauth') return Infinity;
30
+ if (name === 'openai' || cacheCapabilityForProvider(name) === 'explicit-breakpoint') {
31
+ return providerTailCacheLifetimeMs(name, request.opts?.cacheStrategy) ?? 0;
32
+ }
33
+ const info = request.provider.getCachedModelInfo?.(request.model) || getModelMetadataSync(request.model, name);
34
+ return info?.supportsPromptCaching ? CATALOG_PROMPT_CACHE_LIFETIME_MS : 0;
35
+ }
36
+
37
+ /** Whether the session's last settled request is still inside the cache
38
+ * lifetime it used, with a safety margin. No forkable request = cold. */
39
+ export function compactionCacheWarm(sessionId, now = Date.now()) {
40
+ const request = compactionForkRequest(sessionId);
41
+ if (!request) return false;
42
+ const lifetime = requestCacheLifetimeMs(request);
43
+ if (lifetime === Infinity) return true;
44
+ const margin = Math.max(WARM_MARGIN_MIN_MS, lifetime * WARM_MARGIN_RATIO);
45
+ return now - Number(request.sentAt) < lifetime - margin;
46
+ }
47
+
48
+ function compareGptVersion(a, b) {
49
+ const [aMajor, aMinor] = gptVersion(a);
50
+ const [bMajor, bMinor] = gptVersion(b);
51
+ // Same version: the undated alias over a dated snapshot.
52
+ return aMajor - bMajor || aMinor - bMinor || b.length - a.length;
53
+ }
54
+
55
+ // The lighter family per subscription provider: its newest catalog model.
56
+ // API-key providers bill per token and get no lighter step. `catalog` reads
57
+ // the provider's saved catalog only (disk cache, no auth or network on the
58
+ // compaction path): Anthropic's within its 24h TTL, Codex's stale or not.
59
+ const LIGHTER_FAMILY = Object.freeze({
60
+ 'anthropic-oauth': {
61
+ catalog: loadAnthropicCatalogCache,
62
+ matches: (row) => row.family === 'haiku',
63
+ compare: compareAnthropicVersion,
64
+ },
65
+ 'openai-oauth': {
66
+ catalog: loadSavedCodexCatalog,
67
+ matches: (row, id) => id.includes('-luna') && gptVersion(id) !== null,
68
+ compare: compareGptVersion,
69
+ },
70
+ });
71
+
72
+ /** The newest lighter-family model the saved catalog of `providerName` lists
73
+ * for this account, or null (not a subscription provider, no saved catalog,
74
+ * none listed). */
75
+ export async function lighterSubscriptionModel(providerName) {
76
+ const family = LIGHTER_FAMILY[providerName];
77
+ if (!family) return null;
78
+ const catalog = await family.catalog();
79
+ if (!Array.isArray(catalog)) return null;
80
+ let best = null;
81
+ for (const row of catalog) {
82
+ const id = String(row?.id || '');
83
+ if (!id || !family.matches(row, id)) continue;
84
+ if (!best || family.compare(id, best) > 0) best = id;
85
+ }
86
+ return best;
87
+ }
@@ -59,6 +59,8 @@ Rules:
59
59
  - Use terse bullets, not prose paragraphs.
60
60
  - Preserve exact file paths, commands, error strings, and identifiers when known.
61
61
  - Use the same language as the active user thread when it is clear.
62
+ - Record work as Done or In Progress only when a tool call and its result in the history show it (an agent spawn/send result, an edit, a command outcome). An assistant's stated intent with no matching tool call is not started: list it under Next Steps as "announced, not started".
63
+ - Attribute every decision, answer, and version/choice to its source: the user (message or ask_user answer), a named agent's report, or the assistant. Never credit an agent or the assistant with a user's answer.
62
64
  - Do not mention the summary process or that context was compacted.`;
63
65
 
64
66
  // The final user turn of a summary forked from the session's own cached