mixdog 1.3.4 → 1.4.0

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 (472) hide show
  1. package/LICENSES/oh-my-pi-MIT.txt +23 -0
  2. package/NOTICE.md +17 -2
  3. package/README.md +11 -0
  4. package/package.json +3 -11
  5. package/scripts/daemon-stub.mjs +1 -1
  6. package/scripts/embed-google-oauth-client.mjs +49 -0
  7. package/scripts/gate-local.mjs +4 -2
  8. package/scripts/lib/test-spawn-ledger.mjs +46 -1
  9. package/scripts/native-release-status.mjs +2 -2
  10. package/scripts/recall-history-cases.json +1 -1
  11. package/scripts/suite-health.mjs +10 -1
  12. package/scripts/test.mjs +12 -11
  13. package/src/agents/reviewer/AGENT.md +7 -11
  14. package/src/agents/reviewer/agent.json +1 -1
  15. package/src/defaults/shipped-history.json +25 -1
  16. package/src/defaults/skills/browser-use/SKILL.md +77 -17
  17. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  18. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  19. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  20. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  21. package/src/defaults/skills/computer-use/SKILL.md +33 -9
  22. package/src/defaults/skills/setup/SKILL.md +29 -2
  23. package/src/defaults/skills/setup/references/actions.md +117 -28
  24. package/src/defaults/skills/setup/references/surfaces.md +77 -35
  25. package/src/defaults/skills/setup-guide/SKILL.md +3 -3
  26. package/src/defaults/skills/visualize/SKILL.md +2 -1
  27. package/src/lib/keychain-cjs.cjs +359 -29
  28. package/src/lib/rules-builder.cjs +10 -8
  29. package/src/rules/agent/AGENT.md +6 -6
  30. package/src/rules/agent-mode/AGENT_MODE.md +10 -2
  31. package/src/rules/agent-mode/MIXY.md +12 -0
  32. package/src/rules/lead/LEAD.md +18 -20
  33. package/src/rules/shared/05-tool-calls.md +5 -6
  34. package/src/rules/shared/10-tool-workflow.md +2 -0
  35. package/src/rules/shared/60-verification.md +7 -5
  36. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  37. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
  38. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  39. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  40. package/src/runtime/agent/orchestrator/config.mjs +34 -0
  41. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  42. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
  43. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
  44. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  45. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +177 -73
  46. package/src/runtime/agent/orchestrator/mcp/client.mjs +25 -2
  47. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
  48. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
  49. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
  50. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
  51. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
  52. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
  53. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +230 -137
  54. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +64 -7
  55. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
  56. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  59. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  60. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +160 -69
  61. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
  63. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  64. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
  65. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
  66. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
  67. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
  68. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
  69. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +142 -104
  70. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
  73. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
  74. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  75. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -6
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
  78. package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
  79. package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
  80. package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
  81. package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
  82. package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
  83. package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
  84. package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
  85. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
  86. package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
  87. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
  88. package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
  89. package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
  90. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
  91. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
  92. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
  93. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
  94. package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
  95. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
  97. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  98. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
  99. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  100. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  101. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  103. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +19 -1
  104. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  109. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  110. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  111. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  113. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
  114. package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
  115. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  116. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  117. package/src/runtime/agent/orchestrator/providers/registry.mjs +43 -3
  118. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  119. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  120. package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
  121. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
  122. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  123. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  124. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  125. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  126. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
  127. package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
  128. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
  129. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  130. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  131. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  132. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
  133. package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
  134. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
  135. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
  136. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
  137. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
  138. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
  139. package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
  140. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
  141. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +148 -25
  142. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  143. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  144. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -0
  145. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +47 -9
  146. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -2
  147. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  148. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +14 -2
  149. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
  151. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
  152. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +11 -2
  153. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
  154. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +51 -3
  155. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
  156. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -2
  157. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  158. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +14 -4
  159. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  160. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  161. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  163. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +23 -5
  164. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
  165. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  166. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  167. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
  168. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  169. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
  170. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
  171. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  172. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  173. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
  174. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  175. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  176. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
  177. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
  178. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  179. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
  180. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
  182. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
  183. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  184. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
  185. package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
  186. package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
  187. package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
  188. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
  189. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  190. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
  191. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
  192. package/src/runtime/agent/orchestrator/session/routine-result.mjs +47 -11
  193. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
  194. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  195. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  196. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  197. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  198. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  199. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  200. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  201. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  202. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  203. package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
  204. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +133 -18
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
  206. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
  207. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
  208. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  209. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
  210. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  211. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  212. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  213. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
  214. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
  215. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +25 -2
  216. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  217. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  218. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  219. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  222. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  224. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
  225. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
  226. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  227. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  228. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  229. package/src/runtime/browser-bridge/client.mjs +73 -8
  230. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  231. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  232. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  233. package/src/runtime/channels/lib/config.mjs +6 -61
  234. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  235. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  236. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  237. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  238. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  239. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  240. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  241. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  242. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  243. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  244. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  245. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  246. package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
  247. package/src/runtime/channels/lib/scheduler.mjs +135 -154
  248. package/src/runtime/channels/lib/schedules-db.mjs +83 -11
  249. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  250. package/src/runtime/channels/lib/tool-dispatch.mjs +14 -2
  251. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  252. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  253. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  254. package/src/runtime/channels/lib/worker-main.mjs +19 -34
  255. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  256. package/src/runtime/computer-bridge/actions.mjs +9 -1
  257. package/src/runtime/computer-bridge/client.mjs +180 -8
  258. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  259. package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
  260. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  261. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  262. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  263. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  264. package/src/runtime/connectors/google-mcp.mjs +10 -12
  265. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  266. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  267. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  268. package/src/runtime/office/com/com-adapter.mjs +17 -1
  269. package/src/runtime/office/com/office-com-host.ps1 +51 -1
  270. package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
  271. package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
  272. package/src/runtime/office/pdf/document-preview.mjs +326 -8
  273. package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
  274. package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
  275. package/src/runtime/office/portable/portable-soffice.mjs +45 -5
  276. package/src/runtime/session-search/session-search-db.mjs +73 -9
  277. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  278. package/src/runtime/shared/abort-race.mjs +89 -0
  279. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  280. package/src/runtime/shared/agent-kind.mjs +4 -3
  281. package/src/runtime/shared/bot-hops.mjs +62 -2
  282. package/src/runtime/shared/bounded-download.mjs +8 -3
  283. package/src/runtime/shared/config.mjs +51 -35
  284. package/src/runtime/shared/connector-accounts.mjs +20 -10
  285. package/src/runtime/shared/connector-catalog.d.mts +33 -2
  286. package/src/runtime/shared/connector-catalog.mjs +166 -8
  287. package/src/runtime/shared/connector-triggers.mjs +22 -19
  288. package/src/runtime/shared/defaults-separation.mjs +2 -14
  289. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  290. package/src/runtime/shared/developer-options.mjs +32 -1
  291. package/src/runtime/shared/err-text.mjs +24 -15
  292. package/src/runtime/shared/file-permissions.d.mts +0 -8
  293. package/src/runtime/shared/file-permissions.mjs +0 -11
  294. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  295. package/src/runtime/shared/google-oauth-client.generated.json +4 -0
  296. package/src/runtime/shared/google-oauth-client.mjs +34 -9
  297. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  298. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  299. package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
  300. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
  301. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
  302. package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
  303. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  304. package/src/runtime/shared/llm/well-formed.mjs +66 -0
  305. package/src/runtime/shared/mixy-presence.mjs +22 -0
  306. package/src/runtime/shared/orchestration.d.mts +2 -0
  307. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  308. package/src/runtime/shared/profile-config.cjs +1 -1
  309. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  310. package/src/runtime/shared/prompt-origin.mjs +21 -0
  311. package/src/runtime/shared/provider-accounts.mjs +231 -59
  312. package/src/runtime/shared/provider-api-key.mjs +19 -8
  313. package/src/runtime/shared/provider-display-name.mjs +29 -0
  314. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  315. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  316. package/src/runtime/shared/routine-trigger.mjs +52 -0
  317. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  318. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  319. package/src/runtime/shared/session-activity-signal.mjs +34 -1
  320. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  321. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  322. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  323. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  324. package/src/runtime/shared/tool-result-summary.mjs +1 -14
  325. package/src/runtime/shared/transcript-metadata.mjs +38 -0
  326. package/src/runtime/shared/user-data-guard.mjs +30 -2
  327. package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
  328. package/src/runtime/shared/worktrees/test-support.mjs +9 -4
  329. package/src/runtime/terminal-bridge/client.mjs +10 -1
  330. package/src/runtime/visualize-bridge/client.mjs +27 -8
  331. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  332. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  333. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  334. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  335. package/src/session-runtime/agent-home-delivery.mjs +486 -179
  336. package/src/session-runtime/agent-room-delivery.mjs +28 -1
  337. package/src/session-runtime/agent-task-sessions.mjs +561 -199
  338. package/src/session-runtime/boot/apis.mjs +9 -0
  339. package/src/session-runtime/boot/begin.mjs +3 -2
  340. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  341. package/src/session-runtime/boot/providers.mjs +3 -0
  342. package/src/session-runtime/boot/tools.mjs +8 -1
  343. package/src/session-runtime/bot-chain.mjs +32 -0
  344. package/src/session-runtime/bot-status.mjs +7 -4
  345. package/src/session-runtime/channel-config-api.mjs +63 -17
  346. package/src/session-runtime/config-lifecycle.mjs +6 -0
  347. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  348. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
  349. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
  350. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
  351. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
  352. package/src/session-runtime/internal-tool-executor.mjs +4 -0
  353. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  354. package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
  355. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  356. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  357. package/src/session-runtime/mcp-glue/server-catalog.mjs +61 -3
  358. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  359. package/src/session-runtime/mixy-heartbeat.mjs +927 -0
  360. package/src/session-runtime/model-route/route-tuning.mjs +10 -6
  361. package/src/session-runtime/notification-bus.mjs +31 -117
  362. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  363. package/src/session-runtime/notification-listeners.mjs +76 -0
  364. package/src/session-runtime/owned-automation-run.mjs +35 -13
  365. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  366. package/src/session-runtime/provider-auth/login.mjs +7 -0
  367. package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
  368. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  369. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  370. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  371. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  372. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  373. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  374. package/src/session-runtime/routine-home-turn.mjs +33 -6
  375. package/src/session-runtime/routine-owner.mjs +40 -9
  376. package/src/session-runtime/schedule-session-run.mjs +7 -3
  377. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  378. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
  379. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
  380. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  381. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  382. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  383. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  384. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  385. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  386. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  387. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  388. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  389. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  390. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  391. package/src/session-runtime/services/channel-admin.mjs +73 -31
  392. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  393. package/src/session-runtime/services/channel-worker.mjs +1 -1
  394. package/src/session-runtime/services/provider-admin.mjs +128 -25
  395. package/src/session-runtime/services/schedule-access.mjs +14 -0
  396. package/src/session-runtime/services/seeds.mjs +0 -28
  397. package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
  398. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  399. package/src/session-runtime/session-create/session-options.mjs +8 -4
  400. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  401. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  402. package/src/session-runtime/settings-system-api.mjs +28 -1
  403. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  404. package/src/session-runtime/setup-tool/executor.mjs +187 -29
  405. package/src/session-runtime/setup-tool/extended-actions.mjs +33 -2
  406. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  407. package/src/session-runtime/setup-tool/settings-contract.mjs +68 -6
  408. package/src/session-runtime/setup-tool/tool-defs.mjs +92 -6
  409. package/src/session-runtime/task-session-route.mjs +99 -0
  410. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  411. package/src/session-runtime/tool-policy/policy-stage.mjs +21 -4
  412. package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
  413. package/src/session-runtime/tool-surface.mjs +4 -0
  414. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  415. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
  416. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  417. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
  418. package/src/session-runtime/workspace/transitions.mjs +10 -2
  419. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  420. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  421. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  422. package/src/standalone/channel-transport.mjs +1 -1
  423. package/src/standalone/daemon-channels-loader.mjs +10 -8
  424. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  425. package/src/standalone/daemon-session-control/request.mjs +51 -0
  426. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  427. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  428. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  429. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  430. package/src/standalone/daemon-session-control.mjs +161 -157
  431. package/src/standalone/daemon.mjs +33 -2
  432. package/src/standalone/session-client.mjs +7 -3
  433. package/src/standalone/session-protocol.mjs +10 -0
  434. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
  435. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  436. package/src/standalone/session-service/quota-resume.mjs +3 -0
  437. package/src/standalone/session-service/retention/busy.mjs +65 -1
  438. package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
  439. package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
  440. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  441. package/src/standalone/session-service.mjs +13 -0
  442. package/src/standalone/session-tool-output.mjs +21 -10
  443. package/src/tui/app/model-options.mjs +4 -27
  444. package/src/tui/dist/index.mjs +81 -65
  445. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  446. package/src/tui/session/initial-state.mjs +3 -0
  447. package/src/tui/session/notice-surface.mjs +23 -1
  448. package/src/tui/session/queue-helpers.mjs +3 -42
  449. package/src/tui/session/session-api/integrations.mjs +1 -0
  450. package/src/tui/session/session-api/lifecycle.mjs +8 -0
  451. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  452. package/src/tui/session/session-api/routes-write.mjs +5 -1
  453. package/src/tui/session/session-api/routes.mjs +1 -0
  454. package/src/tui/session/session-api/settings.mjs +30 -2
  455. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  456. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  457. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  458. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  459. package/src/tui/session/session-flow/drain.mjs +118 -149
  460. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  461. package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
  462. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  463. package/src/tui/session/tool-approval-request.mjs +39 -0
  464. package/src/tui/session/tool-approval-settle.mjs +62 -0
  465. package/src/tui/session/tool-approval.mjs +69 -115
  466. package/src/tui/session/tui-steering-persist.mjs +8 -1
  467. package/src/tui/session/turn-stream.mjs +36 -3
  468. package/src/tui/session/turn.mjs +23 -2
  469. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  470. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  471. package/scripts/code-graph-description-contract.mjs +0 -188
  472. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -11,22 +11,29 @@
11
11
  * appended as ONE segment to the session's sidecar, followed by the
12
12
  * compaction divider.
13
13
  *
14
- * The commit only selects the dropped messages and allocates the segment
15
- * number (`session.compaction.archive.nextSeq`, saved with the commit); the
16
- * projection and the write run on a later event-loop turn, serialized per
17
- * session and fenced against a delete. Nothing here touches
18
- * `session.messages`.
14
+ * The commit selects the dropped messages, restores their rows (the one
15
+ * visibility decision: a segment number is claimed only for a non-empty row
16
+ * set) and allocates the segment number
17
+ * (`session.compaction.archive.nextSeq`, saved with the commit); the
18
+ * namespacing and the write run on a later event-loop turn, serialized per
19
+ * session and fenced against a delete. A write the fence refuses is kept
20
+ * (retried in place in the session's queue) while the session may still come
21
+ * back to be written; a session that is really gone releases its number.
22
+ * Nothing here touches `session.messages`.
19
23
  */
20
24
  import { randomBytes } from 'node:crypto';
21
- import { existsSync, unlinkSync } from 'node:fs';
22
- import { open, readFile } from 'node:fs/promises';
25
+ import { closeSync, existsSync, fsyncSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
26
+ import { open, readdir, readFile } from 'node:fs/promises';
23
27
  import { basename, join } from 'node:path';
24
- import { renameWithRetrySync } from '../../../shared/atomic-file.mjs';
28
+ import { quarantinePath, renameWithRetrySync } from '../../../shared/atomic-file.mjs';
25
29
  import { writeStderrBestEffort } from '../../../shared/best-effort-stderr.mjs';
26
30
 
27
- import { sanitizeContentForStoredHistory } from '../providers/media-normalization.mjs';
28
31
  import { getStoreDir } from './store/session-paths.mjs';
29
32
  import { forgetSessionSaveBaseline } from './store/save-worker.mjs';
33
+ import { _hasPendingPersistence } from './store/pending-saves.mjs';
34
+ import { _messagesForDisk } from './store/serialize.mjs';
35
+ import { subscribeSessionPersistence } from './store/session-persistence-events.mjs';
36
+ import { registerSessionPurgeHook } from './store/session-observers.mjs';
30
37
  import { messageIdOf, stampMessageIds } from './message-ids.mjs';
31
38
  import { restoreTranscriptItems } from './transcript-restore/restore.mjs';
32
39
  import { isAgentModeSession } from './agent-mode.mjs';
@@ -134,7 +141,7 @@ export function compactionArchiveProjection(before, after) {
134
141
  /** What a live view needs to find the rows of what left the context: the
135
142
  * stable ids whose text rows went (projection `textIds`) and the tool calls
136
143
  * that went (dropped results and calls no kept copy carries). */
137
- export function droppedIdentitySummary(projection) {
144
+ function droppedIdentitySummary(projection) {
138
145
  const callIds = new Set();
139
146
  for (const message of projection.messages) {
140
147
  for (const call of toolCallsOf(message)) if (callIdOf(call)) callIds.add(callIdOf(call));
@@ -155,17 +162,24 @@ export function stampCompactionMessageIds(messages, sessionId) {
155
162
  }
156
163
  // ── Segment projection and publication ───────────────────────────────────────
157
164
 
158
- function storedHistoryMessage(message) {
159
- const content = sanitizeContentForStoredHistory(message.content);
160
- return content === message.content ? message : { ...message, content };
165
+ /** The rows what left the context (compactionArchiveProjection messages)
166
+ * restores to as the session store would keep them on disk — media stripped
167
+ * and the session's sensitive values redacted, by the store's own projection
168
+ * (`_messagesForDisk`, sensitive-projection.mjs) — the single visibility
169
+ * decision of the archive: a segment exists exactly when this is non-empty.
170
+ * Evaluated again at publication, so the redaction is the one in force then. */
171
+ function segmentRows(messages, sessionId) {
172
+ return restoreTranscriptItems(_messagesForDisk(messages, sessionId), { sessionId });
161
173
  }
162
174
 
163
- /** The display items of one segment: what left the context
164
- * (compactionArchiveProjection messages) as stored history restores it,
165
- * namespaced and marked archived, then the divider. Empty when nothing
166
- * visible left. */
175
+ /** The display items of one segment: what left the context as stored history
176
+ * restores it, namespaced and marked archived, then the divider. Empty when
177
+ * nothing visible left. */
167
178
  export function compactionSegmentItems(messages, { sessionId, seq, at, divider = true }) {
168
- const restored = restoreTranscriptItems(messages.map(storedHistoryMessage), { sessionId });
179
+ return namespacedSegmentItems(segmentRows(messages, sessionId), { sessionId, seq, at, divider });
180
+ }
181
+
182
+ function namespacedSegmentItems(restored, { sessionId, seq, at, divider }) {
169
183
  if (!restored.length) return [];
170
184
  const prefix = `hist_${sessionId}_`;
171
185
  const items = restored.map((item) => {
@@ -212,19 +226,46 @@ export function _setCompactionArchivePublishHook(hook) {
212
226
  publishHook = typeof hook === 'function' ? hook : null;
213
227
  }
214
228
 
229
+ let parkHook = null;
230
+ /** Test seam: runs when a refused publication parks on the session's
231
+ * persistence signal (its wait is already registered). */
232
+ export function _setCompactionArchiveParkHook(hook) {
233
+ parkHook = typeof hook === 'function' ? hook : null;
234
+ }
235
+
215
236
  let syncFile = (handle) => handle.sync();
216
237
  /** Test seam: replaces the temp file's fsync (null restores it). */
217
238
  export function _setCompactionArchiveFsync(sync) {
218
239
  syncFile = typeof sync === 'function' ? sync : (handle) => handle.sync();
219
240
  }
220
241
 
242
+ const realFs = { readFileSync, readdir };
243
+ let fsOps = realFs;
244
+ /** Test seam: replaces the pending-sidecar read (`readFileSync`) and the store
245
+ * listing (`readdir`); null restores them. */
246
+ export function _setCompactionArchiveFs(ops) {
247
+ fsOps = ops ? { ...realFs, ...ops } : realFs;
248
+ }
249
+
221
250
  const recordExists = (dir, sessionId) => existsSync(join(dir, `${sessionId}.json`));
222
251
 
252
+ /** Why a segment cannot be published now, or null. A delete that bumped the
253
+ * epoch since `epoch` was captured is final. A missing record is final too
254
+ * unless a save of that session is still pending — that save is what creates
255
+ * it, so the write is kept (`retry`). */
256
+ function publicationRefusal(dir, sessionId, epoch) {
257
+ if (compactionArchiveDeleteEpoch(sessionId) !== epoch) return { reason: 'session deleted', retry: false };
258
+ if (recordExists(dir, sessionId)) return null;
259
+ return _hasPendingPersistence(sessionId)
260
+ ? { reason: 'session record missing while its save is pending', retry: true }
261
+ : { reason: 'session record missing', retry: false };
262
+ }
263
+
223
264
  /** Write `text` to a private temp file and fsync it — a failed write or
224
265
  * fsync fails the publication — then publish it with a synchronous fence
225
266
  * check + rename: a delete that bumped the epoch (or removed the record)
226
267
  * since `epoch` was captured wins. Any outcome but a rename removes the temp
227
- * file. */
268
+ * file. Returns null once published, else the fence's refusal. */
228
269
  async function publishArchive({ dir, path, sessionId, text, epoch }) {
229
270
  const tmp = join(dir, `${basename(path)}.mixdog-tmp-${randomBytes(4).toString('hex')}`);
230
271
  const removeTmp = () => {
@@ -248,9 +289,10 @@ async function publishArchive({ dir, path, sessionId, text, epoch }) {
248
289
  throw error;
249
290
  }
250
291
  // No await between the fence and the rename.
251
- if (compactionArchiveDeleteEpoch(sessionId) !== epoch || !recordExists(dir, sessionId)) {
292
+ const refusal = publicationRefusal(dir, sessionId, epoch);
293
+ if (refusal) {
252
294
  removeTmp();
253
- return false;
295
+ return refusal;
254
296
  }
255
297
  try {
256
298
  renameWithRetrySync(tmp, path);
@@ -258,14 +300,17 @@ async function publishArchive({ dir, path, sessionId, text, epoch }) {
258
300
  removeTmp();
259
301
  throw error;
260
302
  }
261
- return true;
303
+ return null;
262
304
  }
263
305
 
306
+ /** Appends one segment: `{ segments }` once published, else the fence's
307
+ * refusal `{ reason, retry }`. */
264
308
  async function appendSegment({ sessionId, seq, at, items, clearedThrough, maxBytes, epoch }) {
265
309
  const dir = getStoreDir();
266
310
  const path = compactionArchivePath(dir, sessionId);
267
311
  // A deleted session must not regrow its archive.
268
- if (!path || compactionArchiveDeleteEpoch(sessionId) !== epoch || !recordExists(dir, sessionId)) return null;
312
+ const early = publicationRefusal(dir, sessionId, epoch);
313
+ if (early) return early;
269
314
  let text = '';
270
315
  try {
271
316
  text = await readFile(path, 'utf8');
@@ -286,20 +331,374 @@ async function appendSegment({ sessionId, seq, at, items, clearedThrough, maxByt
286
331
  total -= Buffer.byteLength(existing.shift().line) + 1;
287
332
  }
288
333
  const lines = [...existing.map((entry) => entry.line), segment.line];
289
- if (!(await publishArchive({ dir, path, sessionId, text: `${lines.join('\n')}\n`, epoch }))) return null;
290
- return lines.map((line) => compactionSegmentHead(line)).filter(Boolean);
334
+ const refusal = await publishArchive({ dir, path, sessionId, text: `${lines.join('\n')}\n`, epoch });
335
+ if (refusal) return refusal;
336
+ return { segments: lines.map((line) => compactionSegmentHead(line)).filter(Boolean) };
337
+ }
338
+
339
+ function logArchiveSkip(sessionId, seq, what, reason) {
340
+ writeStderrBestEffort(`[session] compaction archive write ${what} (sess=${sessionId}, seq=${seq}): ${reason}\n`);
341
+ }
342
+
343
+ /** The session is really gone: give the claimed number back unless something
344
+ * newer was claimed after it or a clear already covers it. */
345
+ function releaseClaimedSeq(session, seq) {
346
+ const archive = session.compaction?.archive;
347
+ if (!archive || typeof archive !== 'object') return;
348
+ if (archive.nextSeq === seq + 1 && !(Number(archive.clearedThrough) >= seq)) archive.nextSeq = seq;
349
+ }
350
+
351
+ // ── Unpublished segments ─────────────────────────────────────────────────────
352
+ // A segment's job lives in `unpublished[id]` (ascending seq) from the moment
353
+ // its number is claimed until it is published or its session is really gone.
354
+ // A failed write, or one that cannot be published yet, leaves the job there:
355
+ // the next archive write or the next successful save of the session retries it
356
+ // first. The moment a job is parked or has failed it is also written to a
357
+ // pending sidecar `<id>.compaction-archive.pending` (removed once published or
358
+ // when the session is deleted), which survives a crash or kill; the daemon's
359
+ // startup scan (recoverPendingCompactionArchives) and the session's next
360
+ // save/compaction pick it up. The sidecar holds the rows' source messages in their stored
361
+ // (disk-projected) form.
362
+ // A sidecar is only ever rewritten or removed by a process that has loaded it
363
+ // (`recovered`): a read that fails for any reason but absence leaves the
364
+ // session unrecovered, its compaction refused, and the sidecar untouched.
365
+ const COMPACTION_PENDING_SUFFIX = '.compaction-archive.pending';
366
+ const unpublished = new Map();
367
+ const recovered = new Set();
368
+ const sidecarOnDisk = new Set();
369
+ const waiters = new Map();
370
+
371
+ const jobsOf = (id) => {
372
+ let list = unpublished.get(id);
373
+ if (!list) {
374
+ list = [];
375
+ unpublished.set(id, list);
376
+ }
377
+ return list;
378
+ };
379
+
380
+ const pendingSidecarPath = (dir, id) => join(dir, `${id}${COMPACTION_PENDING_SUFFIX}`);
381
+
382
+ /** Rewrites the pending sidecar to the session's unpublished jobs (removes it
383
+ * when none are left). Synchronous: it also runs at process exit. */
384
+ function persistPending(id) {
385
+ // Jobs of an unrecovered sidecar are unknown here: rewriting or removing it
386
+ // would drop them.
387
+ if (!recovered.has(id)) {
388
+ writeStderrBestEffort(
389
+ `[session] compaction archive pending sidecar not rewritten before it was read (sess=${id})\n`
390
+ );
391
+ return false;
392
+ }
393
+ const path = pendingSidecarPath(getStoreDir(), id);
394
+ // A job of a deleted incarnation (older delete epoch) is never kept.
395
+ const epoch = compactionArchiveDeleteEpoch(id);
396
+ const jobs = (unpublished.get(id) ?? []).filter((job) => job.epoch === epoch);
397
+ if (!jobs.length) {
398
+ if (sidecarOnDisk.delete(id)) {
399
+ try {
400
+ unlinkSync(path);
401
+ } catch {
402
+ /* already gone */
403
+ }
404
+ }
405
+ return true;
406
+ }
407
+ const tmp = `${path}.mixdog-tmp-${randomBytes(4).toString('hex')}`;
408
+ try {
409
+ const text = JSON.stringify({
410
+ v: 1,
411
+ jobs: jobs.map(({ seq, at, divider, clearedThrough, maxBytes, messages }) => ({
412
+ seq,
413
+ at,
414
+ divider,
415
+ clearedThrough,
416
+ maxBytes,
417
+ messages: _messagesForDisk(messages, id),
418
+ })),
419
+ });
420
+ const fd = openSync(tmp, 'wx', 0o600);
421
+ try {
422
+ writeFileSync(fd, text, 'utf8');
423
+ fsyncSync(fd);
424
+ } finally {
425
+ closeSync(fd);
426
+ }
427
+ renameWithRetrySync(tmp, path);
428
+ sidecarOnDisk.add(id);
429
+ return true;
430
+ } catch (error) {
431
+ try {
432
+ unlinkSync(tmp);
433
+ } catch {
434
+ /* never created */
435
+ }
436
+ writeStderrBestEffort(
437
+ `[session] compaction archive pending sidecar failed (sess=${id}): ${error?.message || error}\n`
438
+ );
439
+ return false;
440
+ }
441
+ }
442
+
443
+ /** The jobs a sidecar's text holds, or null when the text is not a job list
444
+ * (damaged: unparseable, or not `{ jobs: [{ seq, messages }, …] }`). */
445
+ function parsePendingJobs(text) {
446
+ let doc;
447
+ try {
448
+ doc = JSON.parse(text);
449
+ } catch {
450
+ return null;
451
+ }
452
+ const jobs = doc?.jobs;
453
+ return Array.isArray(jobs) && jobs.every((job) => Number.isInteger(job?.seq) && Array.isArray(job.messages))
454
+ ? jobs
455
+ : null;
456
+ }
457
+
458
+ /** Loads the jobs a previous process left in the session's pending sidecar,
459
+ * once per process. The session counts as recovered only after the sidecar
460
+ * was loaded or is confirmed absent (ENOENT): any other read failure throws
461
+ * and leaves it unrecovered (see persistPending). A damaged sidecar is moved
462
+ * aside whole as `<file>.corrupt-<ts>`, like every damaged state file
463
+ * (atomic-file.mjs); its bytes are kept, and a failure to move it throws. */
464
+ function recoverPending(id) {
465
+ if (recovered.has(id)) return;
466
+ const path = pendingSidecarPath(getStoreDir(), id);
467
+ let text;
468
+ try {
469
+ text = fsOps.readFileSync(path, 'utf8');
470
+ } catch (error) {
471
+ if (error?.code !== 'ENOENT') throw error;
472
+ recovered.add(id);
473
+ return;
474
+ }
475
+ const saved = parsePendingJobs(text);
476
+ if (!saved) {
477
+ const aside = quarantinePath(path);
478
+ renameWithRetrySync(path, aside);
479
+ writeStderrBestEffort(`[session] compaction archive pending sidecar damaged (sess=${id}); kept as ${aside}\n`);
480
+ recovered.add(id);
481
+ return;
482
+ }
483
+ const list = jobsOf(id);
484
+ const epoch = compactionArchiveDeleteEpoch(id);
485
+ for (const job of saved) {
486
+ if (list.some((held) => held.seq === job.seq)) continue;
487
+ list.push({ ...job, id, session: null, epoch, segments: null });
488
+ }
489
+ list.sort((a, b) => a.seq - b.seq);
490
+ sidecarOnDisk.add(id);
491
+ recovered.add(id);
492
+ }
493
+
494
+ /** recoverPending for callers that cannot refuse (a save event, the startup
495
+ * scan): a failure is reported and leaves the session unrecovered. Returns
496
+ * whether the session is recovered. */
497
+ function tryRecoverPending(id) {
498
+ try {
499
+ recoverPending(id);
500
+ return true;
501
+ } catch (error) {
502
+ writeStderrBestEffort(
503
+ `[session] compaction archive pending sidecar unreadable (sess=${id}): ${error?.message || error}; left untouched\n`
504
+ );
505
+ return false;
506
+ }
507
+ }
508
+
509
+ /** Removes the session's pending sidecar from disk, whatever is in memory. */
510
+ function removePendingSidecar(id) {
511
+ sidecarOnDisk.delete(id);
512
+ try {
513
+ unlinkSync(pendingSidecarPath(getStoreDir(), id));
514
+ } catch {
515
+ /* none on disk */
516
+ }
517
+ }
518
+
519
+ /**
520
+ * One-shot scan after daemon startup: every pending sidecar a previous process
521
+ * left whose session record still exists is loaded and its segments are
522
+ * published through the normal write queue (oldest first). Returns how many
523
+ * sessions had segments to publish. A store directory that does not exist is
524
+ * empty; any other listing failure is thrown. A session whose sidecar cannot
525
+ * be read is reported and skipped, the others still recover.
526
+ */
527
+ export async function recoverPendingCompactionArchives() {
528
+ const dir = getStoreDir();
529
+ let names;
530
+ try {
531
+ names = await fsOps.readdir(dir);
532
+ } catch (error) {
533
+ if (error?.code === 'ENOENT') return 0;
534
+ throw error;
535
+ }
536
+ let count = 0;
537
+ for (const name of names) {
538
+ if (!name.endsWith(COMPACTION_PENDING_SUFFIX)) continue;
539
+ const id = name.slice(0, -COMPACTION_PENDING_SUFFIX.length);
540
+ if (!compactionArchivePath('', id) || !recordExists(dir, id)) continue;
541
+ if (!tryRecoverPending(id)) continue;
542
+ if (!unpublished.get(id)?.length) continue;
543
+ count += 1;
544
+ void enqueueFlush(id);
545
+ }
546
+ return count;
547
+ }
548
+
549
+ /** Settles with 'landed' | 'closed' | 'deleted' at the session's next such event. */
550
+ function waitForSessionEvent(id) {
551
+ return new Promise((resolve) => {
552
+ let set = waiters.get(id);
553
+ if (!set) {
554
+ set = new Set();
555
+ waiters.set(id, set);
556
+ }
557
+ set.add(resolve);
558
+ });
559
+ }
560
+
561
+ function wakeWaiters(id, event) {
562
+ const set = waiters.get(id);
563
+ if (!set) return;
564
+ waiters.delete(id);
565
+ for (const resolve of set) resolve(event);
566
+ }
567
+
568
+ /** Gives up on one job (or all of the session's) with a logged reason. */
569
+ function dropJobs(id, reason, only = null) {
570
+ const list = unpublished.get(id);
571
+ if (!list) return;
572
+ const dropped = only ? list.filter((job) => job === only) : list.splice(0);
573
+ if (only) list.splice(list.indexOf(only), 1);
574
+ if (!list.length) unpublished.delete(id);
575
+ for (const job of [...dropped].reverse()) {
576
+ logArchiveSkip(id, job.seq, 'skipped', reason);
577
+ if (job.session) releaseClaimedSeq(job.session, job.seq);
578
+ }
579
+ if (sidecarOnDisk.has(id)) persistPending(id);
580
+ }
581
+
582
+ /** Appends one job's segment: `{ segments }` once published, else a refusal
583
+ * `{ reason, retry, single }`. Rows are projected here, at publication. */
584
+ async function publishJob(job) {
585
+ const items = namespacedSegmentItems(segmentRows(job.messages, job.id), {
586
+ sessionId: job.id,
587
+ seq: job.seq,
588
+ at: job.at,
589
+ divider: job.divider,
590
+ });
591
+ if (!items.length) return { reason: 'no visible rows at publication', retry: false, single: true };
592
+ return appendSegment({
593
+ sessionId: job.id,
594
+ seq: job.seq,
595
+ at: job.at,
596
+ items,
597
+ clearedThrough: job.clearedThrough,
598
+ maxBytes: job.maxBytes,
599
+ epoch: job.epoch,
600
+ });
601
+ }
602
+
603
+ /** Publishes the session's unpublished jobs oldest first, inside its write
604
+ * queue. Stops at the first job that cannot be published yet; it stays. */
605
+ async function flushUnpublished(id) {
606
+ await new Promise((resolve) => setImmediate(resolve));
607
+ for (;;) {
608
+ const list = unpublished.get(id);
609
+ const job = list?.[0];
610
+ if (!job) {
611
+ unpublished.delete(id);
612
+ return;
613
+ }
614
+ let outcome;
615
+ try {
616
+ outcome = await publishJob(job);
617
+ } catch (error) {
618
+ if (unpublished.get(id) !== list || list[0] !== job) return;
619
+ writeStderrBestEffort(
620
+ `[session] compaction archive write failed (sess=${id}, seq=${job.seq}): ${error?.message || error}; kept for retry\n`
621
+ );
622
+ return;
623
+ }
624
+ if (unpublished.get(id) !== list || list[0] !== job) return;
625
+ if (outcome.segments) {
626
+ list.shift();
627
+ job.segments = outcome.segments;
628
+ const archive = job.session?.compaction?.archive;
629
+ // Small metadata only; a later clear may have hidden this segment.
630
+ if (archive && typeof archive === 'object' && !(Number(archive.clearedThrough) >= job.seq)) {
631
+ archive.segments = outcome.segments;
632
+ }
633
+ if (sidecarOnDisk.has(id)) persistPending(id);
634
+ continue;
635
+ }
636
+ if (outcome.retry) {
637
+ // The session's own save is what creates its record: wait for that save
638
+ // to land (no polling). A delete ends the wait; a close keeps the jobs
639
+ // for the next process.
640
+ if (!job.deferred) logArchiveSkip(id, job.seq, 'deferred', `${outcome.reason}; waiting for it`);
641
+ job.deferred = true;
642
+ const waiting = waitForSessionEvent(id);
643
+ parkHook?.({ sessionId: id, seq: job.seq });
644
+ const event = await waiting;
645
+ if (event !== 'landed') return;
646
+ continue;
647
+ }
648
+ dropJobs(id, outcome.reason, outcome.single ? job : null);
649
+ if (outcome.single) continue;
650
+ return;
651
+ }
652
+ }
653
+
654
+ function enqueueFlush(id) {
655
+ const task = (pendingWrites.get(id) || Promise.resolve())
656
+ .then(() => flushUnpublished(id))
657
+ .catch((error) => {
658
+ writeStderrBestEffort(`[session] compaction archive write failed (sess=${id}): ${error?.message || error}\n`);
659
+ });
660
+ pendingWrites.set(id, task);
661
+ void task.finally(() => {
662
+ if (pendingWrites.get(id) === task) pendingWrites.delete(id);
663
+ });
664
+ return task;
665
+ }
666
+
667
+ subscribeSessionPersistence((id, event) => {
668
+ wakeWaiters(id, event);
669
+ if (event !== 'landed') return;
670
+ if (!tryRecoverPending(id)) return;
671
+ if (unpublished.get(id)?.length) void enqueueFlush(id);
672
+ });
673
+
674
+ registerSessionPurgeHook((id) => {
675
+ wakeWaiters(id, 'deleted');
676
+ dropJobs(id, 'session deleted');
677
+ // A sidecar a previous process left is dropped too, in-memory jobs or not.
678
+ removePendingSidecar(id);
679
+ recovered.delete(id);
680
+ });
681
+
682
+ /** Test seam: forget all in-memory state (a restart); sidecars stay on disk. */
683
+ export function _resetCompactionArchiveState() {
684
+ unpublished.clear();
685
+ recovered.clear();
686
+ sidecarOnDisk.clear();
687
+ waiters.clear();
291
688
  }
292
689
 
293
690
  /**
294
- * Archive what a committed compaction dropped. Synchronous part: select the
295
- * dropped messages, capture the delete fence and allocate the segment number
296
- * on `session.compaction.archive`; the rest runs later. Returns
297
- * `{ dropped, identities, written }` — the dropped message count, their
298
- * identities for a live view (droppedIdentitySummary) and the pending write
299
- * (resolves to the retained segment list) — or null when nothing visible was
300
- * dropped (a rewrite-only compaction).
691
+ * Archive what a compaction drops, write-ahead. Synchronous part: select the
692
+ * dropped messages, capture the delete fence, allocate the segment number on
693
+ * `session.compaction.archive` and make the job durable in the pending
694
+ * sidecar — call it BEFORE the shortened transcript is adopted (and so can be
695
+ * saved). When the sidecar cannot be written the claim is given back and this
696
+ * throws: the caller must keep the old transcript. Returns `{ dropped,
697
+ * identities, start }` — `start()` queues the publication and returns the
698
+ * pending write (resolves to the retained segment list) — or null when
699
+ * nothing visible was dropped (a rewrite-only compaction).
301
700
  */
302
- export function scheduleCompactionArchive({
701
+ export function prepareCompactionArchive({
303
702
  session,
304
703
  sessionId = null,
305
704
  before,
@@ -312,40 +711,58 @@ export function scheduleCompactionArchive({
312
711
  const projection = compactionArchiveProjection(before, after);
313
712
  // Only what the transcript shows is archived; a pass that took out nothing
314
713
  // visible (a rewrite, control rows, hidden context) leaves the view live.
315
- // Short-circuits on the first visible message.
316
- if (!projection.messages.some((message) => restoreTranscriptItems([message], { sessionId: id }).length > 0)) {
317
- return null;
318
- }
714
+ // The rows are decided here, once, as the segment will hold them, so a
715
+ // number is claimed only for a segment that has items.
716
+ const rows = segmentRows(projection.messages, id);
717
+ if (!rows.length) return null;
319
718
  const dropped = droppedByCompaction(before, after);
719
+ // Segments a previous run left unpublished (pending sidecar) hold their
720
+ // numbers. A sidecar that cannot be read throws before anything is claimed:
721
+ // the caller keeps the old transcript, and the sidecar is left as it is.
722
+ recoverPending(id);
320
723
  if (!session.compaction || typeof session.compaction !== 'object') session.compaction = {};
321
724
  const previous =
322
725
  session.compaction.archive && typeof session.compaction.archive === 'object' ? session.compaction.archive : {};
323
- const seq = Math.max(1, Math.floor(Number(previous.nextSeq) || 1));
726
+ const held = unpublished.get(id)?.at(-1)?.seq ?? 0;
727
+ const seq = Math.max(1, held + 1, Math.floor(Number(previous.nextSeq) || 1));
324
728
  const clearedThrough = Math.max(0, Math.floor(Number(previous.clearedThrough) || 0));
325
729
  const epoch = compactionArchiveDeleteEpoch(id);
326
730
  session.compaction.archive = { ...previous, nextSeq: seq + 1 };
327
731
  const divider = !isAgentModeSession(session);
328
- const run = async () => {
329
- await new Promise((resolve) => setImmediate(resolve));
330
- const items = compactionSegmentItems(projection.messages, { sessionId: id, seq, at, divider });
331
- if (!items.length) return null;
332
- const segments = await appendSegment({ sessionId: id, seq, at, items, clearedThrough, maxBytes, epoch });
333
- const archive = session.compaction?.archive;
334
- // Small metadata only; a later clear may have hidden this segment.
335
- if (segments && archive && typeof archive === 'object' && !(Number(archive.clearedThrough) >= seq)) {
336
- archive.segments = segments;
337
- }
338
- return segments;
732
+ const job = {
733
+ id,
734
+ session,
735
+ seq,
736
+ at,
737
+ divider,
738
+ messages: projection.messages,
739
+ clearedThrough,
740
+ maxBytes,
741
+ epoch,
742
+ segments: null,
339
743
  };
340
- const task = (pendingWrites.get(id) || Promise.resolve()).then(run).catch((error) => {
341
- writeStderrBestEffort(`[session] compaction archive write failed (sess=${id}): ${error?.message || error}\n`);
342
- return null;
343
- });
344
- pendingWrites.set(id, task);
345
- void task.finally(() => {
346
- if (pendingWrites.get(id) === task) pendingWrites.delete(id);
347
- });
348
- return { dropped: dropped.length, identities: droppedIdentitySummary(projection), written: task };
744
+ const list = jobsOf(id);
745
+ list.push(job);
746
+ if (!persistPending(id)) {
747
+ list.pop();
748
+ if (!list.length) unpublished.delete(id);
749
+ releaseClaimedSeq(session, seq);
750
+ logArchiveSkip(id, seq, 'aborted', 'pending sidecar not written; compaction not applied');
751
+ throw new Error(`compaction archive: pending sidecar for ${id} could not be written`);
752
+ }
753
+ return {
754
+ dropped: dropped.length,
755
+ identities: droppedIdentitySummary(projection),
756
+ start: () => enqueueFlush(id).then(() => job.segments),
757
+ };
758
+ }
759
+
760
+ /** prepareCompactionArchive + start, for a caller that adopts the transcript
761
+ * itself first. Resolves `written` as before. */
762
+ export function scheduleCompactionArchive(args) {
763
+ const prepared = prepareCompactionArchive(args);
764
+ if (!prepared) return null;
765
+ return { dropped: prepared.dropped, identities: prepared.identities, written: prepared.start() };
349
766
  }
350
767
 
351
768
  /** Hide everything archived so far (a clear): later reads and the next write