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
@@ -1,15 +1,18 @@
1
1
  /**
2
2
  * Rows posted into an agent's home thread (`sess_home_<owner>`) from outside
3
3
  * its turns: held requests of unattended runs, reports of task sessions the
4
- * agent created, and routine results left by runs from before routines ran as
5
- * turns in the thread itself (routine-home-turn.mjs).
4
+ * agent created, the agent's own reply of a turn it ran in a hidden session
5
+ * (Mixy's heartbeat), and routine results left by runs from before routines
6
+ * ran as turns in the thread itself (routine-home-turn.mjs).
6
7
  *
7
8
  * Delivery is durable. The finished run is first ENQUEUED in a persisted
8
9
  * per-home queue (`routine-results.json`), keyed by the run id, so a retried
9
10
  * enqueue adds nothing and a crash between posting and dequeuing never posts
10
11
  * twice (the posted message id is derived from the run id). One drainer per
11
12
  * home appends every queued result FIFO once the thread is idle; posting never
12
- * starts a turn. A home thread that does not exist yet keeps its results
13
+ * starts a turn. Rows that wake nobody (a task session's progress updates,
14
+ * among others) also post right before any turn of the thread starts
15
+ * (postBeforeTurn), so that turn reads them ahead of its prompt. A home thread that does not exist yet keeps its results
13
16
  * queued until it does; a deleted owner's results move to Mixy's home. Each
14
17
  * home keeps at most ROUTINE_LIMITS.homeQueueLimit results (the oldest drop
15
18
  * behind one "n omitted" notice), and posted run ids stay in a ledger so a
@@ -57,6 +60,7 @@ import {
57
60
  routineSummary,
58
61
  } from '../runtime/agent/orchestrator/session/routine-result.mjs';
59
62
  import { readJsonState, updateJsonAtomic } from '../runtime/shared/atomic-file.mjs';
63
+ import { endHandoff, noteHandoff } from '../runtime/shared/delegated-approval-host.mjs';
60
64
  import { ROUTINE_LIMITS } from '../runtime/shared/routine-limits.mjs';
61
65
  import { submissionIdsOf, submissionOutcome } from './agent-task-sessions.mjs';
62
66
  import {
@@ -64,22 +68,50 @@ import {
64
68
  heldRequestNotice,
65
69
  } from '../runtime/agent/orchestrator/session/human-gate/held-message.mjs';
66
70
 
71
+ /** `message` marked as posted outside any turn (`meta.transcript.posted`):
72
+ * its row is a turn of its own, never part of the rows around it
73
+ * (transcript-turn-keys.mjs). */
74
+ function postedMessage(message) {
75
+ return { ...message, meta: { ...message.meta, transcript: { ...message.meta?.transcript, posted: true } } };
76
+ }
77
+
67
78
  /** A queued item's stored message and live notice: a routine result, or a
68
79
  * held request of an unattended run (session-runtime/held-requests.mjs). */
69
80
  function deliveredRow(item, messageId) {
81
+ const row = queuedRow(item, messageId);
82
+ return { ...row, message: postedMessage(row.message) };
83
+ }
84
+
85
+ /** A task report past the round-trip cap with another agent's own thread:
86
+ * each wake could answer the other agent again without end. */
87
+ const taskWakeCapped = (item) =>
88
+ (isAgentHomeSessionId(item.taskSessionId) || isRoomThreadSessionId(item.taskSessionId)) &&
89
+ (item.round || 0) > TASK_LOOP_CAP;
90
+
91
+ /** Whether queued `item` wakes the agent once posted: a task turn's end (not
92
+ * its start, a wait for the user or a progress update), unless capped. */
93
+ const wakesAgent = (item) => item.kind === 'agent-task' && item.roundTrip === true && !taskWakeCapped(item);
94
+
95
+ const MIXY_HOME = agentHomeSessionId(MIXY_AGENT_ID);
96
+ /** A heartbeat report: Mixy's own reply from a check turn (mixy-heartbeat.mjs
97
+ * wakeMixy, its only sender of agent messages). */
98
+ const isHeartbeatReport = (item) => item?.kind === 'agent-message';
99
+
100
+ function queuedRow(item, messageId) {
101
+ // The agent's own words from a turn it ran elsewhere (mixy-heartbeat.mjs):
102
+ // one ordinary reply of the agent in its thread.
103
+ if (item.kind === 'agent-message') {
104
+ const message = { role: 'assistant', content: item.text, meta: { transcript: { at: item.at, messageId } } };
105
+ return { message, notice: { text: item.text, tone: 'info', assistant: true } };
106
+ }
70
107
  if (item.kind === 'held-request') {
71
108
  const message = heldRequestMessage({ ...item.held, at: item.at, messageId });
72
109
  return { message, notice: heldRequestNotice(message) };
73
110
  }
74
111
  if (item.kind === 'agent-task') {
75
- // A turn's end (not its start or a wait for the user) wakes the agent —
76
- // except past the round-trip cap with another agent's own thread, where
77
- // each wake could answer the other agent again without end.
78
- const capped =
79
- (isAgentHomeSessionId(item.taskSessionId) || isRoomThreadSessionId(item.taskSessionId)) &&
80
- (item.round || 0) > TASK_LOOP_CAP;
112
+ const capped = taskWakeCapped(item);
81
113
  const message = agentTaskMessage({ ...item, messageId, ...(capped ? { woken: false } : {}) });
82
- return { message, notice: routineResultNotice(message), wake: item.roundTrip === true && !capped };
114
+ return { message, notice: routineResultNotice(message), wake: wakesAgent(item) };
83
115
  }
84
116
  const message = routineResultMessage({ ...item, messageId });
85
117
  return { message, notice: routineResultNotice(message) };
@@ -124,9 +156,11 @@ export function scheduleOwnerAgentId(schedule) {
124
156
  }
125
157
 
126
158
  let liveSurface = null;
127
- /** `{ busy(sessionId), notice(sessionId, notice), wake(sessionId, prompt, { id }),
128
- * submit(sessionId, prompt, options), abort(sessionId) }` from the session
129
- * service; `wake` starts one hidden turn with submission `id` (resolves
159
+ /** `{ busy(sessionId), notice(sessionId, notice), react(sessionId, messageId,
160
+ * reactions), wake(sessionId, prompt, { id }), submit(sessionId, prompt,
161
+ * options), abort(sessionId) }` from the session service; `react` shows a
162
+ * message's new reactions in the runtime hosting it (message-reactions.mjs),
163
+ * `wake` starts one hidden turn with submission `id` (resolves
130
164
  * false when it was refused, else `{ settled }`: a promise of the turn's end
131
165
  * status, 'done' when it finished), `submit` queues a routine turn
132
166
  * (resolves whether it was accepted), `abort` stops the running turn. */
@@ -143,14 +177,14 @@ export function agentHomeLiveSurface() {
143
177
  // even into a thread that was reset ("대화 새로 시작") since.
144
178
  const DELIVERED_LEDGER = 500;
145
179
  // A target's round-trip count left idle this long starts over.
146
- export const ROUND_TTL_MS = 30 * 24 * 60 * 60 * 1000;
180
+ const ROUND_TTL_MS = 30 * 24 * 60 * 60 * 1000;
147
181
  // Per home: round-trip targets counted (least recently counted drop first),
148
182
  // reporting turns watched at once, and posted results owed a wake.
149
183
  export const QUEUE_CAPS = Object.freeze({ roundTargets: 100, watches: 200, wakeRunIds: 100 });
150
184
  // Waits before the 2nd, 3rd and 4th try of a relay (wake) turn that did not finish.
151
- export const WAKE_RETRY_DELAYS_MS = Object.freeze([30_000, 120_000, 600_000]);
185
+ const WAKE_RETRY_DELAYS_MS = Object.freeze([30_000, 120_000, 600_000]);
152
186
  // Refused wake submissions of one batch, a minute apart, before it is given up.
153
- export const WAKE_REFUSAL_LIMIT = 10;
187
+ const WAKE_REFUSAL_LIMIT = 10;
154
188
 
155
189
  /** `homeId`'s wake obligations in `wakes`: posted task results not in a batch
156
190
  * yet (`owed`), those past the cap collapsed into one marker (`overflow`:
@@ -255,6 +289,25 @@ function withDequeued(doc, homeId, runIds, omitted) {
255
289
  return { ...doc, homes, omitted: omittedNow, delivered };
256
290
  }
257
291
 
292
+ /** `{ doc, item }`: task report `item` for `homeId` and the `doc` it joins,
293
+ * so that a final report never repeats progress update `progressRunId` that
294
+ * carried the same answer. Posted (`posted`: the thread holds it, or the
295
+ * ledger says so), the final report points to it instead of repeating its
296
+ * text (the row's one-line summary stays); only queued, that update is
297
+ * dropped and the final report carries the answer in its place. */
298
+ function withoutRepeatedProgress(doc, homeId, item, posted) {
299
+ const { progressRunId, ...report } = item;
300
+ if (!progressRunId) return { doc, item };
301
+ if (posted || (doc.delivered[homeId] || []).includes(progressRunId)) {
302
+ const { text: _text, textChars: _textChars, ...rest } = report;
303
+ return { doc, item: { ...rest, text: '', answerAbove: true } };
304
+ }
305
+ const queued = doc.homes[homeId] || [];
306
+ if (!queued.some((row) => row.runId === progressRunId)) return { doc, item: report };
307
+ const homes = { ...doc.homes, [homeId]: queued.filter((row) => row.runId !== progressRunId) };
308
+ return { doc: { ...doc, homes }, item: report };
309
+ }
310
+
258
311
  /** The persisted queue at `filePath`: `{ homes: { [homeSessionId]: item[] },
259
312
  * omitted: { [homeSessionId]: { id, count } }, delivered: { [homeSessionId]:
260
313
  * runId[] } }`. A home keeps at most `limit` items: past it the oldest drop,
@@ -290,16 +343,23 @@ export function createRoutineResultQueue(
290
343
  }
291
344
  /** Read-modify-write under the file lock (`mutator(doc)` returns the next
292
345
  * doc, or nothing to keep the file); every write also retires stale and
293
- * surplus round counts. */
294
- function update(mutator) {
295
- return updateJsonAtomic(
346
+ * surplus round counts. The watches a write added or removed, by any path
347
+ * (a report, a turn never taken, a cap eviction, a pruned home), start or
348
+ * end their hand-offs (delegated-approval-host.mjs). */
349
+ async function update(mutator) {
350
+ let watches = null;
351
+ const result = await updateJsonAtomic(
296
352
  filePath,
297
353
  (raw) => {
298
- const next = mutator(parts(raw));
354
+ const doc = parts(raw);
355
+ const next = mutator(doc);
356
+ watches = { before: doc.watches, after: next ? section(next, 'watches') : doc.watches };
299
357
  return next ? { ...next, rounds: tidyRounds(next.rounds) } : undefined;
300
358
  },
301
359
  { backup: true }
302
360
  );
361
+ if (watches) syncHandoffs(watches.before, watches.after);
362
+ return result;
303
363
  }
304
364
  const appended = (doc, homeId, items, omittedCount = 0) => appendedItems(doc, homeId, items, limit, omittedCount);
305
365
  return {
@@ -413,10 +473,15 @@ export function createRoutineResultQueue(
413
473
  async omitted(homeId) {
414
474
  return (await read()).omitted[homeId] || null;
415
475
  },
416
- /** Queue `item` for `homeId`; `unwatch` drops that watch in the same write,
417
- * also when the item was queued or posted before. */
418
- async add(homeId, item, { unwatch = '' } = {}) {
419
- await update((doc) => {
476
+ /** Queue `item` for `homeId`; `unwatch` (run ids) drops those watches in the
477
+ * same write, also when the item was queued or posted before.
478
+ * `progressPosted`: the thread holds the progress update a final report
479
+ * names (withoutRepeatedProgress). `progressed` (`{ runId, turnId }`):
480
+ * watch `runId` reported turn `turnId` as progress — its durable mark,
481
+ * stored in the same write (agent-task-sessions.mjs progressedThrough). */
482
+ async add(homeId, queued, { unwatch = [], progressPosted = false, progressed = null } = {}) {
483
+ await update((stored) => {
484
+ const { doc, item } = withoutRepeatedProgress(stored, homeId, queued, progressPosted);
420
485
  let next = null;
421
486
  if (item.roundTrip !== true) next = appended(doc, homeId, [item]);
422
487
  else {
@@ -434,10 +499,19 @@ export function createRoutineResultQueue(
434
499
  next = { ...next, rounds: { ...doc.rounds, [homeId]: counted } };
435
500
  }
436
501
  }
437
- if (!unwatch || !doc.watches[unwatch]) return next ?? undefined;
438
- const watches = { ...doc.watches };
439
- delete watches[unwatch];
440
- return { ...(next || doc), watches };
502
+ let watches = doc.watches;
503
+ const marked = progressed?.turnId && watches[progressed.runId];
504
+ if (marked && marked.progressed !== progressed.turnId) {
505
+ watches = { ...watches, [progressed.runId]: { ...marked, progressed: progressed.turnId } };
506
+ }
507
+ const dropped = unwatch.filter((runId) => watches[runId]);
508
+ if (dropped.length) {
509
+ watches = { ...watches };
510
+ for (const runId of dropped) delete watches[runId];
511
+ }
512
+ const base = next || doc;
513
+ if (watches === doc.watches) return base === stored ? undefined : base;
514
+ return { ...base, watches };
441
515
  });
442
516
  },
443
517
  /** Keep `watch` ({ runId, agentId, homeSessionId, taskSessionId, title,
@@ -459,9 +533,12 @@ export function createRoutineResultQueue(
459
533
  });
460
534
  },
461
535
  watchCap: caps.watches,
462
- /** Every stored watch, oldest first. */
536
+ /** Every stored watch, oldest first; each is a live hand-off (after a
537
+ * restart, this read brings them back). */
463
538
  async watches() {
464
- return Object.values((await read()).watches);
539
+ const watches = Object.values((await read()).watches);
540
+ for (const watch of watches) noteHandoff(watch ?? {});
541
+ return watches;
465
542
  },
466
543
  /** The watch of `runId` follows the turn submitted as `submissionId`: a
467
544
  * room member's one turn answering several sends, or a fresh turn
@@ -527,7 +604,7 @@ const messageIdOf = (runId) => `routine_${runId}`;
527
604
  * or false (logged) when the surface threw. */
528
605
  function noticeLive(live, log, homeId, notice) {
529
606
  try {
530
- return live()?.notice?.(homeId, notice);
607
+ return live()?.notice?.(homeId, { ...notice, posted: true });
531
608
  } catch (error) {
532
609
  log(`live notice in ${homeId} failed: ${error?.message || error}`);
533
610
  return false;
@@ -561,6 +638,13 @@ function routineResultItem(row, result) {
561
638
  };
562
639
  }
563
640
 
641
+ /** End the hand-off of each watch in `before` and not `after`, and start
642
+ * each one `after` added (both keyed by run id). */
643
+ function syncHandoffs(before, after) {
644
+ for (const runId of Object.keys(before)) if (!after[runId]) endHandoff(runId);
645
+ for (const [runId, watch] of Object.entries(after)) if (!before[runId]) noteHandoff(watch ?? {});
646
+ }
647
+
564
648
  /** The run ids of `watches` a home at its watch `cap` gives up before
565
649
  * storing `watch`: its oldest whose target sessions are proven deleted. */
566
650
  function watchesToDrop(watches, watch, cap, sessionDeleted) {
@@ -584,8 +668,24 @@ function homeProvenDeleted(homeId, agentPresence, roomPresence) {
584
668
  return Boolean(roomId) && roomPresence(roomId) === 'deleted';
585
669
  }
586
670
 
671
+ // Task report statuses that post a row and wake nobody.
672
+ const QUIET_TASK_STATUSES = new Set(['started', 'blocked', 'working']);
673
+
587
674
  /** The queued item of one task report (enqueueTask). */
588
- function taskReportItem({ runId, taskSessionId, title, status, text = '', target, files = [], omitted = 0, hop = 0 }) {
675
+ function taskReportItem({
676
+ runId,
677
+ taskSessionId,
678
+ title,
679
+ status,
680
+ text = '',
681
+ target,
682
+ files = [],
683
+ omitted = 0,
684
+ hop = 0,
685
+ watchId = '',
686
+ watchIds = [],
687
+ progressRunId = '',
688
+ }) {
589
689
  // The model-facing reply keeps its lines up to REPLY_LIMIT; the row's
590
690
  // one-line summary is derived from it (agentTaskMessage).
591
691
  const reply = boundedReply(text);
@@ -601,9 +701,19 @@ function taskReportItem({ runId, taskSessionId, title, status, text = '', target
601
701
  ...(reply.chars > reply.text.length ? { textChars: reply.chars } : {}),
602
702
  // Any session other than a task session the agent created.
603
703
  ...(target === 'session' ? { target } : {}),
704
+ // The send this report ends (the `sendId` the send's result named).
705
+ ...(watchId ? { sendId: String(watchId) } : {}),
706
+ // Every send this one turn answered (sends folded into a running turn
707
+ // report once); `sendId` is the first of them.
708
+ ...(watchIds.length > 1 ? { sendIds: watchIds.map(String) } : {}),
604
709
  // The files the turn produced (handoff-attachments.mjs collectTurnArtifacts).
605
710
  ...(Array.isArray(files) && files.length ? { files, ...(omitted > 0 ? { omitted } : {}) } : {}),
606
- roundTrip: status !== 'started' && status !== 'blocked',
711
+ // The progress update that carried this final report's answer, if one
712
+ // did: the queue keeps the answer from being repeated (withoutRepeatedProgress).
713
+ ...(progressRunId ? { progressRunId: String(progressRunId) } : {}),
714
+ // Only a turn's end wakes the agent and counts a round trip: not its
715
+ // start, a wait for the user, or a progress update (status 'working').
716
+ roundTrip: !QUIET_TASK_STATUSES.has(status),
607
717
  ...(hop > 0 ? { hop } : {}),
608
718
  at: Date.now(),
609
719
  };
@@ -617,7 +727,7 @@ function postQueuedRows(session, items, omitted) {
617
727
  const fresh = [];
618
728
  const omittedId = omitted ? messageIdOf(omitted.id) : '';
619
729
  if (omitted && !posted.has(omittedId)) {
620
- const message = routineOmittedMessage({ count: omitted.count, messageId: omittedId });
730
+ const message = postedMessage(routineOmittedMessage({ count: omitted.count, messageId: omittedId }));
621
731
  session.messages.push(message);
622
732
  fresh.push({ message, notice: routineResultNotice(message) });
623
733
  }
@@ -689,15 +799,17 @@ function createHomeRelay({
689
799
  log(`the relay turn in ${homeId} ${why}; its results stay posted unrelayed`);
690
800
  const messageId = messageIdOf(`relay_${batch.id}`);
691
801
  if (!session.messages.some((message) => message?.meta?.transcript?.messageId === messageId)) {
692
- const message = agentTaskRelayFailedMessage({
693
- count: batch.runIds.length + (batch.overflow?.count || 0),
694
- tries,
695
- messageId,
696
- });
802
+ const message = postedMessage(
803
+ agentTaskRelayFailedMessage({
804
+ count: batch.runIds.length + (batch.overflow?.count || 0),
805
+ tries,
806
+ messageId,
807
+ })
808
+ );
697
809
  session.messages.push(message);
698
810
  session.updatedAt = Date.now();
699
811
  await saveSession(session);
700
- noticeLive(live, log, homeId, { ...routineResultNotice(message), messageId });
812
+ noticeLive(live, log, homeId, { ...routineResultNotice(message), messageId, at: message.meta.transcript.at });
701
813
  }
702
814
  await queue.woke(homeId, batch.id);
703
815
  return true;
@@ -780,6 +892,200 @@ function createHomeRelay({
780
892
  return { wakeOwed };
781
893
  }
782
894
 
895
+ /** Whether home thread `sessionId` is busy: a turn in flight in this process,
896
+ * or the `live()` surface says so (false when it threw). */
897
+ const homeBusy = (turnInFlight, live) => (sessionId) => {
898
+ if (turnInFlight(sessionId)) return true;
899
+ try {
900
+ return live()?.busy?.(sessionId) === true;
901
+ } catch {
902
+ return false;
903
+ }
904
+ };
905
+
906
+ /** `later(homeId, ms)`: drain `homeId` again after `ms`, one retry timer per
907
+ * home armed at a time. */
908
+ function createRetryTimer(retryLater, drain) {
909
+ const waiting = new Set(); // homes with a retry timer armed
910
+ return function later(homeId, ms) {
911
+ if (waiting.has(homeId)) return;
912
+ waiting.add(homeId);
913
+ retryLater(() => {
914
+ waiting.delete(homeId);
915
+ void drain(homeId);
916
+ }, ms);
917
+ };
918
+ }
919
+
920
+ /** The held requests shown live in a busy thread. A held request waits on the
921
+ * user, possibly raised by the very turn that keeps its thread busy (a
922
+ * routine turn): `show` shows it live at once; the stored row (same
923
+ * messageId) replaces it once the thread is idle, and `forget` lets a later
924
+ * one show again. */
925
+ function createHeldLiveDisplay(live, log) {
926
+ const shownLive = new Set(); // held-request message ids shown live in a busy thread
927
+ return {
928
+ show(homeId, items) {
929
+ for (const item of items) {
930
+ if (item.kind !== 'held-request') continue;
931
+ const messageId = messageIdOf(item.runId);
932
+ if (shownLive.has(messageId)) continue;
933
+ shownLive.add(messageId);
934
+ noticeLive(live, log, homeId, { ...deliveredRow(item, messageId).notice, messageId, at: item.at });
935
+ }
936
+ },
937
+ forget(items) {
938
+ for (const item of items) shownLive.delete(messageIdOf(item.runId));
939
+ },
940
+ };
941
+ }
942
+
943
+ /** The queues a drain drops unposted instead of retrying them. */
944
+ function createQueueDrops({ queue, log, homeAgent, homeDeleted, sessionDeleted }) {
945
+ /** A room thread proven gone: its queued rows are dropped, never retried. */
946
+ async function dropGoneRoomThread(homeId, items, omitted) {
947
+ if (!isRoomThreadSessionId(homeId) || !(homeDeleted(homeId) || sessionDeleted(homeId))) return false;
948
+ log(`${items.length} task report(s) for the deleted ${homeId} are dropped`);
949
+ await dropQueued(queue, homeId, items, omitted);
950
+ return true;
951
+ }
952
+
953
+ /** A room log's queued rows: task reports go to member sessions now, so what
954
+ * an earlier version left under the log's id is dropped, never posted. */
955
+ async function dropRoomLogQueue(logId, items, omitted) {
956
+ log(`${items.length} legacy task report(s) queued for the room log ${logId} are dropped`);
957
+ await dropQueued(queue, logId, items, omitted);
958
+ }
959
+
960
+ /** A missing home of an agent that can never have one (no bot): its queued
961
+ * rows are dropped with a log line, never retried. */
962
+ async function dropHomeless(homeId, items, omitted) {
963
+ const agentId = agentIdOfHomeSession(homeId);
964
+ if (!agentId || agentId === MIXY_AGENT_ID || homeAgent(agentId) !== 'none') return false;
965
+ log(`${items.length} result(s) for ${homeId} are dropped: ${agentId} is not a bot, so it has no chat`);
966
+ await dropQueued(queue, homeId, items, omitted);
967
+ return true;
968
+ }
969
+
970
+ return { dropGoneRoomThread, dropRoomLogQueue, dropHomeless };
971
+ }
972
+
973
+ /** The durable intake of a home: each enqueue persists its row for the home
974
+ * thread, then starts draining it, and resolves once it is queued. */
975
+ function createHomeEnqueue(queue, drain, { postingFor, postedIn }) {
976
+ /** Persist `result` ({ runId, kind, ok, text, runSessionId }) of a schedule
977
+ * or webhook `row` for its owner's home, then start draining it. Resolves
978
+ * once it is queued. */
979
+ async function enqueue(row, result) {
980
+ const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
981
+ if (!result?.runId) throw new Error('routine result: runId required');
982
+ await queue.add(homeId, routineResultItem(row, result));
983
+ void drain(homeId);
984
+ return homeId;
985
+ }
986
+
987
+ /** Persist one held-request row (`held`: normalizeHeldRequest fields) for
988
+ * `agentId`'s home thread, then start draining it. Each status posts once. */
989
+ async function enqueueHeld(agentId, held) {
990
+ const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
991
+ if (!held?.id) throw new Error('held request: id required');
992
+ await queue.add(homeId, {
993
+ runId: `${held.id}_${held.status || 'pending'}`,
994
+ kind: 'held-request',
995
+ held,
996
+ at: Date.now(),
997
+ });
998
+ void drain(homeId);
999
+ return homeId;
1000
+ }
1001
+
1002
+ /** Persist one report of a coding task session `agentId` created (session
1003
+ * create) for its home thread, then start draining it. `status` 'started'
1004
+ * and 'blocked' only post a line; a turn's end also wakes the agent. */
1005
+ async function enqueueTask(agentId, report) {
1006
+ const { runId, taskSessionId, watchId = '', watchIds = [], from = '' } = report;
1007
+ const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
1008
+ if (!runId || !taskSessionId) throw new Error('task report: runId and taskSessionId required');
1009
+ const item = taskReportItem(report);
1010
+ const unwatch = [...new Set([watchId, ...watchIds].filter(Boolean).map(String))];
1011
+ // A room member's own work reports to its member session, never its home.
1012
+ const target = roomMemberOfSession(from)?.agentId === agentId ? from : homeId;
1013
+ if (item.progressRunId) {
1014
+ // Whether its answer already went out as that progress update is
1015
+ // decided in the thread's posting turn, against the thread itself: a
1016
+ // row being posted (or posted before a crash, not dequeued yet) counts
1017
+ // as posted, never as still queued.
1018
+ await postingFor(target, async () =>
1019
+ queue.add(target, item, { unwatch, progressPosted: postedIn(target, item.progressRunId) })
1020
+ );
1021
+ } else {
1022
+ // A progress update moves its send's watch mark (`progressed`) to the
1023
+ // turn it carries, in the same write: a restart never reports it again.
1024
+ const progressed = report.progressWatchId
1025
+ ? { runId: String(report.progressWatchId), turnId: String(report.answerId || '') }
1026
+ : null;
1027
+ await queue.add(target, item, { unwatch, progressed });
1028
+ }
1029
+ void drain(target);
1030
+ return target;
1031
+ }
1032
+
1033
+ /** Persist `text`, the agent's own reply from turn `runId` it ran outside
1034
+ * its thread, for `agentId`'s home thread, then start draining it. It posts
1035
+ * once, as the agent's message; no turn follows. */
1036
+ async function enqueueMessage(agentId, { runId, text }) {
1037
+ const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
1038
+ if (!runId || !String(text || '').trim()) throw new Error('agent message: runId and text required');
1039
+ await queue.add(homeId, { runId: String(runId), kind: 'agent-message', text: String(text).trim(), at: Date.now() });
1040
+ void drain(homeId);
1041
+ return homeId;
1042
+ }
1043
+
1044
+ return { enqueue, enqueueHeld, enqueueTask, enqueueMessage };
1045
+ }
1046
+
1047
+ /** The queue's bookkeeping across processes: report watches and the restart
1048
+ * recovery that retires deleted homes and drains what was left queued. */
1049
+ function createHomeBookkeeping({ queue, log, homeDeleted, sessionDeleted, drain }) {
1050
+ /** Retire the bookkeeping of deleted homes (and stale round counts). */
1051
+ async function prune() {
1052
+ await queue.prune((await queue.bookkeptHomes()).filter(homeDeleted));
1053
+ }
1054
+
1055
+ /** Store `watch`; a home at its watch cap first gives up its oldest watches
1056
+ * whose target sessions are proven deleted, and refuses the watch (so the
1057
+ * send claims no report) when that frees too few. */
1058
+ async function watchTask(watch) {
1059
+ const drop = watchesToDrop(await queue.watches(), watch, queue.watchCap, sessionDeleted);
1060
+ if (drop.length) {
1061
+ log(`${watch.homeSessionId} is at its watch cap: dropped ${drop.length} watch(es) of deleted sessions`);
1062
+ }
1063
+ await queue.watch(watch, { drop });
1064
+ }
1065
+
1066
+ /** Drain every queue left by an earlier process, after retiring stale bookkeeping. */
1067
+ async function recover() {
1068
+ try {
1069
+ await prune();
1070
+ } catch (error) {
1071
+ log(`pruning the routine results failed: ${error?.message || error}`);
1072
+ }
1073
+ // The stored watches name the hand-offs an earlier process left open:
1074
+ // reading them notes each one now, so a target that asks again after the
1075
+ // restart reaches the chat that handed it the work.
1076
+ try {
1077
+ await queue.watches();
1078
+ } catch (error) {
1079
+ log(`reading the report watches failed: ${error?.message || error}`);
1080
+ }
1081
+ const homes = await queue.pendingHomes();
1082
+ for (const homeId of homes) void drain(homeId);
1083
+ return homes;
1084
+ }
1085
+
1086
+ return { watchTask, recover };
1087
+ }
1088
+
783
1089
  export function createAgentHomeDelivery({
784
1090
  loadSession,
785
1091
  saveSession,
@@ -801,6 +1107,9 @@ export function createAgentHomeDelivery({
801
1107
  // at all. 'none' (proven: a workflow agent such as Reviewer) drops the rows
802
1108
  // queued for its missing home instead of waiting on it forever.
803
1109
  homeAgent = () => 'unknown',
1110
+ // Whether the representative bot exists (mixy-presence.mjs): while it does
1111
+ // not, heartbeat reports queued for its home are dropped.
1112
+ mixyExists = async () => true,
804
1113
  // Waits before each new try of a relay turn that did not finish; past the
805
1114
  // last one the results stay posted with a note and nobody is woken.
806
1115
  wakeRetryDelaysMs = WAKE_RETRY_DELAYS_MS,
@@ -813,19 +1122,20 @@ export function createAgentHomeDelivery({
813
1122
  roomRelay = (sessionId, prompt, options) => agentRoomHost().relay(sessionId, prompt, options),
814
1123
  log = (line) => process.stderr.write(`mixdog routines: ${line}\n`),
815
1124
  }) {
816
- const busy = (sessionId) => {
817
- if (turnInFlight(sessionId)) return true;
818
- try {
819
- return live()?.busy?.(sessionId) === true;
820
- } catch {
821
- return false;
822
- }
823
- };
1125
+ const busy = homeBusy(turnInFlight, live);
824
1126
  const draining = new Map(); // home → the running pass
825
1127
  const again = new Set(); // homes enqueued to while their pass ran
826
- const waiting = new Set(); // homes with a retry timer armed
827
- const shownLive = new Set(); // held-request message ids shown live in a busy thread
828
1128
  const waitLogged = new Set(); // missing homes whose wait was logged
1129
+ const later = createRetryTimer(retryLater, drain);
1130
+ const heldLive = createHeldLiveDisplay(live, log);
1131
+ const homeDeleted = (homeId) => homeProvenDeleted(homeId, agentPresence, roomPresence);
1132
+ const { dropGoneRoomThread, dropRoomLogQueue, dropHomeless } = createQueueDrops({
1133
+ queue,
1134
+ log,
1135
+ homeAgent,
1136
+ homeDeleted,
1137
+ sessionDeleted,
1138
+ });
829
1139
  const relay = createHomeRelay({
830
1140
  queue,
831
1141
  saveSession,
@@ -840,61 +1150,123 @@ export function createAgentHomeDelivery({
840
1150
  busyRetryMs,
841
1151
  missingRetryMs,
842
1152
  });
843
-
844
- /** A held request waits on the user, possibly raised by the very turn that
845
- * keeps its thread busy (a routine turn): show it live at once; the stored
846
- * row (same messageId) replaces it once the thread is idle. */
847
- function showHeldLive(homeId, items) {
848
- for (const item of items) {
849
- if (item.kind !== 'held-request') continue;
850
- const messageId = messageIdOf(item.runId);
851
- if (shownLive.has(messageId)) continue;
852
- shownLive.add(messageId);
853
- noticeLive(live, log, homeId, { ...deliveredRow(item, messageId).notice, messageId });
854
- }
1153
+ // Per thread, one posting turn at a time: push queued rows, save the
1154
+ // thread, dequeue them (the drain, the turn prelude), and a final report's
1155
+ // check whether its answer already went out as a progress update. No pass
1156
+ // ever dequeues rows another pushed but has not saved yet, and a row being
1157
+ // posted is never mistaken for one still queued.
1158
+ const postingChains = new Map();
1159
+ function postingFor(homeId, fn) {
1160
+ const run = (postingChains.get(homeId) || Promise.resolve()).then(fn);
1161
+ const tail = run.then(
1162
+ () => {},
1163
+ () => {}
1164
+ );
1165
+ postingChains.set(homeId, tail);
1166
+ void tail.then(() => {
1167
+ if (postingChains.get(homeId) === tail) postingChains.delete(homeId);
1168
+ });
1169
+ return run;
855
1170
  }
1171
+ /** Whether queued row `runId` is in thread `homeId` (posted, saved or not). */
1172
+ const postedIn = (homeId, runId) => {
1173
+ const messages = loadSession(homeId)?.messages;
1174
+ const messageId = messageIdOf(runId);
1175
+ return Array.isArray(messages) && messages.some((message) => message?.meta?.transcript?.messageId === messageId);
1176
+ };
1177
+ const intake = createHomeEnqueue(queue, drain, { postingFor, postedIn });
1178
+ const { watchTask, recover } = createHomeBookkeeping({ queue, log, homeDeleted, sessionDeleted, drain });
856
1179
 
857
- function later(homeId, ms) {
858
- if (waiting.has(homeId)) return;
859
- waiting.add(homeId);
860
- retryLater(() => {
861
- waiting.delete(homeId);
862
- void drain(homeId);
863
- }, ms);
1180
+ /** Append `items` (and the omission notice `omitted`) not posted before to
1181
+ * `homeId`'s thread `session`, store it and show the new rows live.
1182
+ * Returns the run ids owed a wake (postQueuedRows). Nothing awaits before
1183
+ * the push, so no turn can start in between. */
1184
+ async function postRows(homeId, session, items, omitted) {
1185
+ if (!Array.isArray(session.messages)) session.messages = [];
1186
+ const { fresh, toWake } = postQueuedRows(session, items, omitted);
1187
+ heldLive.forget(items);
1188
+ // The caller dequeues `items` (and `omitted`) next: the thread holding
1189
+ // them is saved first, also when an earlier pass pushed them but its save
1190
+ // failed (they are in the thread in memory, not on disk).
1191
+ if (items.length || omitted) {
1192
+ if (fresh.length) session.updatedAt = Date.now();
1193
+ await saveSession(session);
1194
+ }
1195
+ for (const { message, notice } of fresh) {
1196
+ const { messageId, at } = message.meta.transcript;
1197
+ noticeLive(live, log, homeId, { ...notice, messageId, at });
1198
+ }
1199
+ return toWake;
864
1200
  }
865
1201
 
866
- /** A room thread proven gone: its queued rows are dropped, never retried. */
867
- async function dropGoneRoomThread(homeId, items, omitted) {
868
- if (!isRoomThreadSessionId(homeId) || !(homeDeleted(homeId) || sessionDeleted(homeId))) return false;
869
- log(`${items.length} task report(s) for the deleted ${homeId} are dropped`);
870
- await dropQueued(queue, homeId, items, omitted);
871
- return true;
1202
+ /** Right before a turn of thread `homeId` takes its prompt (whatever started
1203
+ * it: the user, a routine, a wake): post its queued rows that wake nobody —
1204
+ * progress updates of the sessions its agent handed work to, start and wait
1205
+ * lines, held requests, routine results — in queue order, so the turn reads
1206
+ * them before its prompt. Rows that wake the agent stay for the drain after
1207
+ * the turn, which posts them and wakes it as before. The caller starts the
1208
+ * turn, so the thread's busy state is its own. Rows the queue dropped past
1209
+ * its limit are announced first by the "n omitted" notice, retired in the
1210
+ * same write that dequeues the posted rows (as the drain does). It runs
1211
+ * in the thread's posting turn (postingFor), never beside the drain's. A
1212
+ * failure is logged; the turn goes on and the drain posts the rows later. */
1213
+ async function postBeforeTurn(homeId) {
1214
+ try {
1215
+ await postingFor(homeId, async () => {
1216
+ const items = (await queue.items(homeId)).filter((item) => !wakesAgent(item));
1217
+ const omitted = await queue.omitted(homeId);
1218
+ if (!items.length && !omitted) return;
1219
+ const session = loadSession(homeId);
1220
+ if (!session || session.closed === true) return;
1221
+ await postRows(homeId, session, items, omitted);
1222
+ await queue.remove(
1223
+ homeId,
1224
+ items.map((item) => item.runId),
1225
+ omitted
1226
+ );
1227
+ });
1228
+ } catch (error) {
1229
+ log(`posting the rows owed before a turn of ${homeId} failed: ${error?.message || error}`);
1230
+ }
872
1231
  }
873
1232
 
874
- /** A room log's queued rows: task reports go to member sessions now, so what
875
- * an earlier version left under the log's id is dropped, never posted. */
876
- async function dropRoomLogQueue(logId, items, omitted) {
877
- log(`${items.length} legacy task report(s) queued for the room log ${logId} are dropped`);
878
- await dropQueued(queue, logId, items, omitted);
1233
+ /** Mixy's heartbeat reports queued for her home while she does not exist
1234
+ * are dropped, never posted once she is created (issue #243); so is the
1235
+ * omission notice when nothing else waits (the rows it counts were these
1236
+ * reports too). Returns what is left to post. */
1237
+ async function dropHeartbeatReports(homeId, items, omitted) {
1238
+ const kept = items.filter((item) => !isHeartbeatReport(item));
1239
+ const retired = kept.length ? null : omitted;
1240
+ log(`${items.length - kept.length} heartbeat report(s) for ${homeId} are dropped: Mixy was never created`);
1241
+ await queue.remove(
1242
+ homeId,
1243
+ items.filter(isHeartbeatReport).map((item) => item.runId),
1244
+ retired
1245
+ );
1246
+ return { items: kept, omitted: retired ? null : omitted };
879
1247
  }
880
1248
 
881
- /** A missing home of an agent that can never have one (no bot): its queued
882
- * rows are dropped with a log line, never retried. */
883
- async function dropHomeless(homeId, items, omitted) {
884
- const agentId = agentIdOfHomeSession(homeId);
885
- if (!agentId || agentId === MIXY_AGENT_ID || homeAgent(agentId) !== 'none') return false;
886
- log(`${items.length} result(s) for ${homeId} are dropped: ${agentId} is not a bot, so it has no chat`);
887
- await dropQueued(queue, homeId, items, omitted);
888
- return true;
1249
+ /** Post every queued result of `homeId` if its thread is idle, in its
1250
+ * posting turn, then wake the agent for what it owes; else retry later.
1251
+ * The wake runs outside the posting turn: the turn it starts posts first
1252
+ * (postBeforeTurn). */
1253
+ async function drainOnce(homeId) {
1254
+ const session = await postingFor(homeId, () => postIdle(homeId));
1255
+ if (session) await relay.wakeOwed(homeId, session);
889
1256
  }
890
1257
 
891
- /** Post every queued result of `homeId` if its thread is idle; else retry later. */
892
- async function drainOnce(homeId) {
893
- const items = await queue.items(homeId);
894
- const omitted = await queue.omitted(homeId);
1258
+ /** drainOnce's posting: the thread it posted to; nothing when nothing is
1259
+ * owed or the pass ends here (moved, busy, missing, dropped). */
1260
+ async function postIdle(homeId) {
1261
+ let items = await queue.items(homeId);
1262
+ let omitted = await queue.omitted(homeId);
895
1263
  const owed = owesWake(await queue.wakes(homeId));
896
1264
  if (!items.length && !omitted && !owed) return;
897
1265
  if (roomIdOfSession(homeId)) return dropRoomLogQueue(homeId, items, omitted);
1266
+ if (homeId === MIXY_HOME && items.some(isHeartbeatReport) && !(await mixyExists())) {
1267
+ ({ items, omitted } = await dropHeartbeatReports(homeId, items, omitted));
1268
+ if (!items.length && !omitted && !owed) return;
1269
+ }
898
1270
  const agentId = agentIdOfHomeSession(homeId);
899
1271
  if (agentId && agentId !== MIXY_AGENT_ID && agentPresence(agentId) === 'deleted') {
900
1272
  // The owner is gone (its home archived): Mixy, the default owner, gets them.
@@ -906,7 +1278,7 @@ export function createAgentHomeDelivery({
906
1278
  return;
907
1279
  }
908
1280
  if (busy(homeId)) {
909
- showHeldLive(homeId, items);
1281
+ heldLive.show(homeId, items);
910
1282
  return later(homeId, busyRetryMs);
911
1283
  }
912
1284
  // From here to the push nothing awaits, so no turn can start in between.
@@ -921,16 +1293,7 @@ export function createAgentHomeDelivery({
921
1293
  }
922
1294
  return later(homeId, missingRetryMs);
923
1295
  }
924
- if (!Array.isArray(session.messages)) session.messages = [];
925
- const { fresh, toWake } = postQueuedRows(session, items, omitted);
926
- for (const item of items) shownLive.delete(messageIdOf(item.runId));
927
- if (fresh.length) {
928
- session.updatedAt = Date.now();
929
- await saveSession(session);
930
- for (const { message, notice } of fresh) {
931
- noticeLive(live, log, homeId, { ...notice, messageId: message.meta.transcript.messageId });
932
- }
933
- }
1296
+ const toWake = await postRows(homeId, session, items, omitted);
934
1297
  // Dequeuing and owing the wake are one write: no restart between posting
935
1298
  // and waking loses the wake.
936
1299
  const { overflowed } = await queue.remove(
@@ -940,7 +1303,7 @@ export function createAgentHomeDelivery({
940
1303
  { wake: toWake }
941
1304
  );
942
1305
  if (overflowed) log(`${homeId} is at its wake cap: ${overflowed} result(s) ride the next wake as a count`);
943
- await relay.wakeOwed(homeId, session);
1306
+ return session;
944
1307
  }
945
1308
 
946
1309
  /** The single drainer of `homeId`; a call during a pass runs one more pass. */
@@ -967,48 +1330,6 @@ export function createAgentHomeDelivery({
967
1330
  return pass;
968
1331
  }
969
1332
 
970
- /** Persist `result` ({ runId, kind, ok, text, runSessionId }) of a schedule
971
- * or webhook `row` for its owner's home, then start draining it. Resolves
972
- * once it is queued. */
973
- async function enqueue(row, result) {
974
- const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
975
- if (!result?.runId) throw new Error('routine result: runId required');
976
- await queue.add(homeId, routineResultItem(row, result));
977
- void drain(homeId);
978
- return homeId;
979
- }
980
-
981
- /** Persist one held-request row (`held`: normalizeHeldRequest fields) for
982
- * `agentId`'s home thread, then start draining it. Each status posts once. */
983
- async function enqueueHeld(agentId, held) {
984
- const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
985
- if (!held?.id) throw new Error('held request: id required');
986
- await queue.add(homeId, {
987
- runId: `${held.id}_${held.status || 'pending'}`,
988
- kind: 'held-request',
989
- held,
990
- at: Date.now(),
991
- });
992
- void drain(homeId);
993
- return homeId;
994
- }
995
-
996
- /** Persist one report of a coding task session `agentId` created (session
997
- * create) for its home thread, then start draining it. `status` 'started'
998
- * and 'blocked' only post a line; a turn's end also wakes the agent. */
999
- async function enqueueTask(agentId, report) {
1000
- const { runId, taskSessionId, watchId = '', from = '' } = report;
1001
- const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
1002
- if (!runId || !taskSessionId) throw new Error('task report: runId and taskSessionId required');
1003
- const item = taskReportItem(report);
1004
- const unwatch = watchId ? String(watchId) : '';
1005
- // A room member's own work reports to its member session, never its home.
1006
- const target = roomMemberOfSession(from)?.agentId === agentId ? from : homeId;
1007
- await queue.add(target, item, { unwatch });
1008
- void drain(target);
1009
- return target;
1010
- }
1011
-
1012
1333
  /** Show `run`'s live-only row (routineRunNotice: `running`, or a result whose
1013
1334
  * delivery failed) in `row`'s owner home when it is open and idle. Nothing
1014
1335
  * is stored and no turn or unread mark follows; the posted result (same
@@ -1020,42 +1341,11 @@ export function createAgentHomeDelivery({
1020
1341
  return noticeLive(live, log, homeId, notice) === true;
1021
1342
  }
1022
1343
 
1023
- const homeDeleted = (homeId) => homeProvenDeleted(homeId, agentPresence, roomPresence);
1024
-
1025
- /** Retire the bookkeeping of deleted homes (and stale round counts). */
1026
- async function prune() {
1027
- await queue.prune((await queue.bookkeptHomes()).filter(homeDeleted));
1028
- }
1029
-
1030
- /** Store `watch`; a home at its watch cap first gives up its oldest watches
1031
- * whose target sessions are proven deleted, and refuses the watch (so the
1032
- * send claims no report) when that frees too few. */
1033
- async function watchTask(watch) {
1034
- const drop = watchesToDrop(await queue.watches(), watch, queue.watchCap, sessionDeleted);
1035
- if (drop.length) {
1036
- log(`${watch.homeSessionId} is at its watch cap: dropped ${drop.length} watch(es) of deleted sessions`);
1037
- }
1038
- await queue.watch(watch, { drop });
1039
- }
1040
-
1041
- /** Drain every queue left by an earlier process, after retiring stale bookkeeping. */
1042
- async function recover() {
1043
- try {
1044
- await prune();
1045
- } catch (error) {
1046
- log(`pruning the routine results failed: ${error?.message || error}`);
1047
- }
1048
- const homes = await queue.pendingHomes();
1049
- for (const homeId of homes) void drain(homeId);
1050
- return homes;
1051
- }
1052
-
1053
1344
  return {
1054
- enqueue,
1055
- enqueueHeld,
1056
- enqueueTask,
1345
+ ...intake,
1057
1346
  announce,
1058
1347
  drain,
1348
+ postBeforeTurn,
1059
1349
  recover,
1060
1350
  watchTask,
1061
1351
  unwatchTask: (runId) => queue.unwatch(runId),
@@ -1097,6 +1387,9 @@ async function processDelivery() {
1097
1387
  // A home chat exists only for bots; only a definition read successfully
1098
1388
  // can prove an agent is none (readHomeAgent).
1099
1389
  homeAgent: (agentId) => readHomeAgent({ dataDir: getPluginData(), shippedRoot: mixdogRoot(), id: agentId }),
1390
+ // The one rule (mixy-presence.mjs; routine-owner.mjs imports this
1391
+ // module, hence late).
1392
+ mixyExists: async () => (await import('./routine-owner.mjs')).readMixyExists(),
1100
1393
  // Deleting a session unlinks its record (session-delete.mjs) and leaves
1101
1394
  // no tombstone: only an absent file with no in-memory copy proves it.
1102
1395
  // An unreadable file probes 'unreadable' and stays.
@@ -1128,6 +1421,11 @@ export async function deliverHeldRequest(agentId, held) {
1128
1421
  return (await processDelivery()).enqueueHeld(agentId, held);
1129
1422
  }
1130
1423
 
1424
+ /** Queue the agent's own reply of a turn it ran outside its home thread. */
1425
+ export async function deliverAgentMessage(agentId, message) {
1426
+ return (await processDelivery()).enqueueMessage(agentId, message);
1427
+ }
1428
+
1131
1429
  /** Queue one report of a task session for `agentId`'s home thread. */
1132
1430
  export async function deliverAgentTask(agentId, report) {
1133
1431
  return (await processDelivery()).enqueueTask(agentId, report);
@@ -1158,6 +1456,15 @@ export async function agentTaskWatches() {
1158
1456
  return (await processDelivery()).taskWatches();
1159
1457
  }
1160
1458
 
1459
+ /** The session turn prelude (session-turn-prelude.mjs) of the daemon: before a
1460
+ * turn of an agent's home thread or a room member session starts, its queued
1461
+ * rows that wake nobody post first (postBeforeTurn). Null for any other
1462
+ * session: nothing to wait for. */
1463
+ export function postBeforeAgentTurn(sessionId) {
1464
+ if (!isAgentHomeSessionId(sessionId) && !roomMemberOfSession(sessionId)) return null;
1465
+ return processDelivery().then((delivery) => delivery.postBeforeTurn(sessionId));
1466
+ }
1467
+
1161
1468
  /** Daemon start: post the results an earlier process left queued. */
1162
1469
  export async function recoverScheduleResults() {
1163
1470
  return (await processDelivery()).recover();