mixdog 1.3.5 → 1.4.1

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 (515) hide show
  1. package/NOTICE.md +2 -2
  2. package/README.md +2 -2
  3. package/package.json +2 -11
  4. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  5. package/scripts/daemon-stub.mjs +1 -1
  6. package/scripts/gate-local.mjs +16 -5
  7. package/scripts/latest-guard.mjs +21 -9
  8. package/scripts/lib/isolated-test-env.mjs +10 -5
  9. package/scripts/lib/run-node-tests.mjs +37 -37
  10. package/scripts/lib/scoped-temp.d.mts +16 -0
  11. package/scripts/lib/scoped-temp.mjs +123 -0
  12. package/scripts/lib/stale-temp.mjs +44 -0
  13. package/scripts/native-release-status.mjs +2 -2
  14. package/scripts/post-release-check.mjs +1 -6
  15. package/scripts/pptx-kit-index.mjs +17 -4
  16. package/scripts/recall-history-cases.json +1 -1
  17. package/scripts/suite-health.mjs +20 -2
  18. package/scripts/test-data-dir.mjs +10 -7
  19. package/scripts/test.mjs +7 -11
  20. package/src/agents/reviewer/AGENT.md +7 -11
  21. package/src/agents/reviewer/agent.json +1 -1
  22. package/src/app.mjs +4 -0
  23. package/src/defaults/shipped-history.json +22 -1
  24. package/src/defaults/skills/browser-use/SKILL.md +62 -12
  25. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  26. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  27. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  28. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  29. package/src/defaults/skills/computer-use/SKILL.md +35 -9
  30. package/src/defaults/skills/setup/SKILL.md +22 -0
  31. package/src/defaults/skills/setup/references/actions.md +39 -5
  32. package/src/defaults/skills/setup/references/surfaces.md +11 -2
  33. package/src/defaults/skills/visualize/SKILL.md +2 -1
  34. package/src/lib/rules-builder.cjs +39 -20
  35. package/src/output-styles/common.md +2 -2
  36. package/src/output-styles/simple.md +1 -1
  37. package/src/rules/agent/AGENT.md +11 -10
  38. package/src/rules/lead/LEAD.md +23 -20
  39. package/src/rules/shared/05-tool-calls.md +5 -6
  40. package/src/rules/shared/10-tool-workflow.md +4 -0
  41. package/src/rules/shared/45-destructive.md +0 -2
  42. package/src/rules/shared/50-execution.md +2 -0
  43. package/src/rules/shared/60-verification.md +7 -5
  44. package/src/rules/shared/70-delivery.md +7 -9
  45. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -6
  46. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  47. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  48. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  49. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  50. package/src/runtime/agent/orchestrator/config.mjs +17 -1
  51. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  52. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +73 -68
  53. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +57 -2
  54. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  55. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
  56. package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
  57. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +5 -2
  58. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +192 -133
  59. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
  60. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  61. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +7 -3
  62. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  63. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  66. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  67. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  70. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +159 -69
  71. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  72. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +70 -33
  73. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  74. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  75. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
  76. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  77. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  78. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  79. package/src/runtime/agent/orchestrator/providers/cursor.mjs +25 -13
  80. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +1 -1
  81. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  82. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +210 -20
  83. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  84. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  85. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  87. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  88. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  89. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  90. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  91. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  92. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  93. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
  94. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  96. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  98. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  103. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  104. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  105. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  106. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  107. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +136 -5
  109. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  110. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  111. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  112. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  113. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +20 -12
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +48 -22
  117. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  118. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  119. package/src/runtime/agent/orchestrator/providers/registry.mjs +38 -0
  120. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  121. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  122. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +49 -16
  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 +7 -6
  127. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +9 -3
  128. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  129. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  130. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -1
  131. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  132. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  133. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +798 -0
  134. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  135. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  136. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  137. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  138. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +280 -45
  139. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  140. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  141. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  142. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +163 -22
  143. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +132 -17
  144. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  145. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  146. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +90 -22
  147. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
  148. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
  149. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
  150. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  151. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
  152. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +51 -25
  153. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +37 -3
  154. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
  155. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
  156. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  157. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  158. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +31 -3
  159. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +20 -3
  160. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  161. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +5 -4
  162. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  163. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  164. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +49 -21
  165. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  166. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
  167. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  168. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  169. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  170. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  171. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  172. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  173. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  174. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +24 -14
  175. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  176. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  177. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +130 -0
  178. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +27 -9
  179. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +81 -8
  180. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  181. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +27 -8
  182. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +12 -5
  183. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  184. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +9 -4
  185. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  186. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  187. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +21 -5
  188. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +9 -0
  189. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +24 -9
  190. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  191. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +150 -0
  192. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  193. package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
  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-delete.mjs +7 -0
  203. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  204. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
  206. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  207. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +81 -22
  208. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +92 -3
  209. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  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/transcript-restore/restore.mjs +79 -3
  214. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  215. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  216. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  217. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  218. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  219. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  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/spawn-env.mjs +4 -2
  225. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +31 -6
  226. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +110 -26
  227. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  228. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +31 -5
  229. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +22 -8
  230. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  231. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  232. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  233. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  234. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  235. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  236. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  237. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  238. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  239. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  240. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  241. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  242. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +26 -4
  243. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  244. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  245. package/src/runtime/browser-bridge/client.mjs +73 -8
  246. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  247. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  248. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  249. package/src/runtime/channels/lib/config.mjs +6 -61
  250. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  251. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  252. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  253. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  254. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  255. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  256. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  257. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  258. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  259. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  260. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  261. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  262. package/src/runtime/channels/lib/scheduler.mjs +33 -147
  263. package/src/runtime/channels/lib/schedules-db.mjs +34 -11
  264. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  265. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
  266. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  267. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  268. package/src/runtime/channels/lib/whisper-port.mjs +10 -1
  269. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  270. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  271. package/src/runtime/channels/lib/worker-main.mjs +10 -34
  272. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  273. package/src/runtime/computer-bridge/actions.mjs +9 -1
  274. package/src/runtime/computer-bridge/client.mjs +180 -8
  275. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  276. package/src/runtime/computer-bridge/error-recovery.mjs +28 -2
  277. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  278. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  279. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  280. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  281. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  282. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  283. package/src/runtime/connectors/google-mcp.mjs +10 -12
  284. package/src/runtime/local-provider/server-process.mjs +2 -1
  285. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  286. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  287. package/src/runtime/memory/lib/embedding-worker.mjs +6 -3
  288. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  289. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  290. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  291. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  292. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  293. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  294. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  295. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  296. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  297. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  298. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  299. package/src/runtime/office/quality/assurance-structure-docx.mjs +3 -1
  300. package/src/runtime/office/quality/design-review.mjs +1 -5
  301. package/src/runtime/office/quality/document-brief.mjs +1 -5
  302. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  303. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  304. package/src/runtime/session-search/session-search-db.mjs +73 -9
  305. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  306. package/src/runtime/shared/abort-race.mjs +89 -0
  307. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  308. package/src/runtime/shared/agent-kind.mjs +4 -3
  309. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  310. package/src/runtime/shared/bounded-download.mjs +8 -3
  311. package/src/runtime/shared/config.mjs +2 -13
  312. package/src/runtime/shared/connector-accounts.mjs +20 -10
  313. package/src/runtime/shared/connector-catalog.d.mts +29 -0
  314. package/src/runtime/shared/connector-catalog.mjs +150 -0
  315. package/src/runtime/shared/connector-triggers.mjs +22 -19
  316. package/src/runtime/shared/defaults-separation.mjs +2 -17
  317. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  318. package/src/runtime/shared/err-text.mjs +24 -15
  319. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  320. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  321. package/src/runtime/shared/file-permissions.d.mts +0 -8
  322. package/src/runtime/shared/file-permissions.mjs +0 -11
  323. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  324. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  325. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  326. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  327. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  328. package/src/runtime/shared/idle-auto-clear-gate.mjs +126 -0
  329. package/src/runtime/shared/llm/cost.mjs +5 -1
  330. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  331. package/src/runtime/shared/loopback-listener.mjs +25 -2
  332. package/src/runtime/shared/mixy-presence.mjs +22 -0
  333. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  334. package/src/runtime/shared/model-route-chain.mjs +22 -4
  335. package/src/runtime/shared/open-url.mjs +10 -0
  336. package/src/runtime/shared/orchestration.d.mts +2 -0
  337. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  338. package/src/runtime/shared/profile-config.cjs +1 -1
  339. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  340. package/src/runtime/shared/prompt-origin.mjs +21 -0
  341. package/src/runtime/shared/provider-accounts.mjs +7 -1
  342. package/src/runtime/shared/provider-display-name.mjs +29 -0
  343. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  344. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  345. package/src/runtime/shared/routine-trigger.mjs +10 -0
  346. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  347. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  348. package/src/runtime/shared/session-activity-signal.mjs +48 -1
  349. package/src/runtime/shared/session-scratch.mjs +37 -0
  350. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  351. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  352. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  353. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  354. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  355. package/src/runtime/shared/tool-result-summary.mjs +2 -15
  356. package/src/runtime/shared/tool-surface.mjs +3 -0
  357. package/src/runtime/shared/tool-work-units.mjs +2 -0
  358. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  359. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  360. package/src/runtime/shared/worktrees/git.mjs +15 -0
  361. package/src/runtime/shared/worktrees/manager.mjs +87 -38
  362. package/src/runtime/shared/worktrees/test-support.mjs +14 -13
  363. package/src/runtime/terminal-bridge/client.mjs +10 -1
  364. package/src/runtime/visualize-bridge/client.mjs +29 -8
  365. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  366. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  367. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  368. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  369. package/src/session-runtime/agent-home-delivery.mjs +489 -185
  370. package/src/session-runtime/agent-task-sessions.mjs +467 -229
  371. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  372. package/src/session-runtime/boot/apis.mjs +9 -0
  373. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  374. package/src/session-runtime/boot/providers.mjs +3 -0
  375. package/src/session-runtime/boot/tools.mjs +11 -2
  376. package/src/session-runtime/bot-status.mjs +12 -6
  377. package/src/session-runtime/bot-target.mjs +38 -0
  378. package/src/session-runtime/channel-config-api.mjs +6 -1
  379. package/src/session-runtime/config-lifecycle.mjs +6 -0
  380. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  381. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  382. package/src/session-runtime/conversation-import/discovery.mjs +271 -25
  383. package/src/session-runtime/conversation-import/service.mjs +26 -5
  384. package/src/session-runtime/handoff-attachments.mjs +82 -31
  385. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +144 -41
  386. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -0
  387. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  388. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +8 -2
  389. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  390. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  391. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  392. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  393. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  394. package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
  395. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  396. package/src/session-runtime/mixy-heartbeat.mjs +153 -33
  397. package/src/session-runtime/model-route/set-route.mjs +11 -2
  398. package/src/session-runtime/notification-bus.mjs +31 -117
  399. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  400. package/src/session-runtime/notification-listeners.mjs +76 -0
  401. package/src/session-runtime/owned-automation-run.mjs +2 -1
  402. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  403. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  404. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  405. package/src/session-runtime/provider-models.mjs +4 -2
  406. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  407. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  408. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  409. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  410. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  411. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  412. package/src/session-runtime/resource-recall-api.mjs +6 -1
  413. package/src/session-runtime/routine-owner.mjs +40 -9
  414. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  415. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  416. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  417. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  418. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  419. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  420. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +13 -4
  421. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  422. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  423. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  424. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  425. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  426. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  427. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  428. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  429. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  430. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  431. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  432. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  433. package/src/session-runtime/services/channel-admin.mjs +7 -13
  434. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  435. package/src/session-runtime/services/provider-admin.mjs +2 -1
  436. package/src/session-runtime/services/seeds.mjs +0 -28
  437. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  438. package/src/session-runtime/session-create/session-options.mjs +1 -0
  439. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  440. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  441. package/src/session-runtime/settings-system-api.mjs +13 -1
  442. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  443. package/src/session-runtime/setup-tool/executor.mjs +145 -22
  444. package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
  445. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  446. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
  447. package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
  448. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  449. package/src/session-runtime/tool-policy/policy-stage.mjs +17 -2
  450. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  451. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  452. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -4
  453. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  454. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  455. package/src/session-runtime/workspace/transitions.mjs +25 -0
  456. package/src/session-runtime/workspace/worktree-tool.mjs +26 -9
  457. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  458. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  459. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  460. package/src/standalone/channel-transport.mjs +1 -1
  461. package/src/standalone/daemon-channels-loader.mjs +10 -8
  462. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  463. package/src/standalone/daemon-session-control/request.mjs +51 -0
  464. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  465. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  466. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  467. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  468. package/src/standalone/daemon-session-control.mjs +142 -190
  469. package/src/standalone/daemon.mjs +26 -3
  470. package/src/standalone/session-client.mjs +7 -3
  471. package/src/standalone/session-protocol.mjs +5 -0
  472. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  473. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  474. package/src/standalone/session-service/quota-resume.mjs +3 -0
  475. package/src/standalone/session-service/retention/busy.mjs +65 -1
  476. package/src/standalone/session-service/session-calls/session-action.mjs +30 -2
  477. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  478. package/src/standalone/session-tool-output.mjs +15 -7
  479. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  480. package/src/tui/app/app-view/floating-panel.jsx +7 -4
  481. package/src/tui/app/model-options.mjs +3 -28
  482. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  483. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  484. package/src/tui/app/provider-setup/oauth-actions.mjs +38 -20
  485. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  486. package/src/tui/app/provider-setup-picker.mjs +9 -5
  487. package/src/tui/components/QuestionPanel.jsx +13 -4
  488. package/src/tui/dist/index.mjs +133 -92
  489. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  490. package/src/tui/session/initial-state.mjs +3 -0
  491. package/src/tui/session/notice-surface.mjs +7 -1
  492. package/src/tui/session/queue-helpers.mjs +3 -42
  493. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  494. package/src/tui/session/session-api/routes-write.mjs +5 -1
  495. package/src/tui/session/session-api/routes.mjs +1 -0
  496. package/src/tui/session/session-api/settings.mjs +17 -1
  497. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  498. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  499. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  500. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  501. package/src/tui/session/session-flow/drain.mjs +118 -149
  502. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  503. package/src/tui/session/session-flow/queue/take-entries.mjs +16 -2
  504. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  505. package/src/tui/session/tool-approval-request.mjs +39 -0
  506. package/src/tui/session/tool-approval-settle.mjs +62 -0
  507. package/src/tui/session/tool-approval.mjs +69 -115
  508. package/src/tui/session/tui-steering-persist.mjs +8 -1
  509. package/src/tui/session/turn-stream.mjs +19 -2
  510. package/src/tui/session/turn.mjs +1 -0
  511. package/src/ui/statusline-agents.mjs +1 -3
  512. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  513. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  514. package/scripts/code-graph-description-contract.mjs +0 -188
  515. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -10,7 +10,9 @@
10
10
  * enqueue adds nothing and a crash between posting and dequeuing never posts
11
11
  * twice (the posted message id is derived from the run id). One drainer per
12
12
  * home appends every queued result FIFO once the thread is idle; posting never
13
- * 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
14
16
  * queued until it does; a deleted owner's results move to Mixy's home. Each
15
17
  * home keeps at most ROUTINE_LIMITS.homeQueueLimit results (the oldest drop
16
18
  * behind one "n omitted" notice), and posted run ids stay in a ledger so a
@@ -58,6 +60,7 @@ import {
58
60
  routineSummary,
59
61
  } from '../runtime/agent/orchestrator/session/routine-result.mjs';
60
62
  import { readJsonState, updateJsonAtomic } from '../runtime/shared/atomic-file.mjs';
63
+ import { endHandoff, noteHandoff } from '../runtime/shared/delegated-approval-host.mjs';
61
64
  import { ROUTINE_LIMITS } from '../runtime/shared/routine-limits.mjs';
62
65
  import { submissionIdsOf, submissionOutcome } from './agent-task-sessions.mjs';
63
66
  import {
@@ -65,9 +68,36 @@ import {
65
68
  heldRequestNotice,
66
69
  } from '../runtime/agent/orchestrator/session/human-gate/held-message.mjs';
67
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
+
68
78
  /** A queued item's stored message and live notice: a routine result, or a
69
79
  * held request of an unattended run (session-runtime/held-requests.mjs). */
70
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) {
71
101
  // The agent's own words from a turn it ran elsewhere (mixy-heartbeat.mjs):
72
102
  // one ordinary reply of the agent in its thread.
73
103
  if (item.kind === 'agent-message') {
@@ -79,14 +109,9 @@ function deliveredRow(item, messageId) {
79
109
  return { message, notice: heldRequestNotice(message) };
80
110
  }
81
111
  if (item.kind === 'agent-task') {
82
- // A turn's end (not its start or a wait for the user) wakes the agent —
83
- // except past the round-trip cap with another agent's own thread, where
84
- // each wake could answer the other agent again without end.
85
- const capped =
86
- (isAgentHomeSessionId(item.taskSessionId) || isRoomThreadSessionId(item.taskSessionId)) &&
87
- (item.round || 0) > TASK_LOOP_CAP;
112
+ const capped = taskWakeCapped(item);
88
113
  const message = agentTaskMessage({ ...item, messageId, ...(capped ? { woken: false } : {}) });
89
- return { message, notice: routineResultNotice(message), wake: item.roundTrip === true && !capped };
114
+ return { message, notice: routineResultNotice(message), wake: wakesAgent(item) };
90
115
  }
91
116
  const message = routineResultMessage({ ...item, messageId });
92
117
  return { message, notice: routineResultNotice(message) };
@@ -152,14 +177,14 @@ export function agentHomeLiveSurface() {
152
177
  // even into a thread that was reset ("대화 새로 시작") since.
153
178
  const DELIVERED_LEDGER = 500;
154
179
  // A target's round-trip count left idle this long starts over.
155
- export const ROUND_TTL_MS = 30 * 24 * 60 * 60 * 1000;
180
+ const ROUND_TTL_MS = 30 * 24 * 60 * 60 * 1000;
156
181
  // Per home: round-trip targets counted (least recently counted drop first),
157
182
  // reporting turns watched at once, and posted results owed a wake.
158
183
  export const QUEUE_CAPS = Object.freeze({ roundTargets: 100, watches: 200, wakeRunIds: 100 });
159
184
  // Waits before the 2nd, 3rd and 4th try of a relay (wake) turn that did not finish.
160
- 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]);
161
186
  // Refused wake submissions of one batch, a minute apart, before it is given up.
162
- export const WAKE_REFUSAL_LIMIT = 10;
187
+ const WAKE_REFUSAL_LIMIT = 10;
163
188
 
164
189
  /** `homeId`'s wake obligations in `wakes`: posted task results not in a batch
165
190
  * yet (`owed`), those past the cap collapsed into one marker (`overflow`:
@@ -264,6 +289,25 @@ function withDequeued(doc, homeId, runIds, omitted) {
264
289
  return { ...doc, homes, omitted: omittedNow, delivered };
265
290
  }
266
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
+
267
311
  /** The persisted queue at `filePath`: `{ homes: { [homeSessionId]: item[] },
268
312
  * omitted: { [homeSessionId]: { id, count } }, delivered: { [homeSessionId]:
269
313
  * runId[] } }`. A home keeps at most `limit` items: past it the oldest drop,
@@ -288,6 +332,9 @@ export function createRoutineResultQueue(
288
332
  rounds: section(doc, 'rounds'),
289
333
  // Reporting turns not yet queued, by submission id (agent-task-sessions.mjs).
290
334
  watches: section(doc, 'watches'),
335
+ // The bot-sent turns that hold no watch (reports off, or a watch that could
336
+ // not be stored), by submission id: work a restart re-attaches.
337
+ owned: section(doc, 'owned'),
291
338
  // Posted task results whose wake turn was not taken yet, per home.
292
339
  wakes: section(doc, 'wakes'),
293
340
  });
@@ -299,16 +346,23 @@ export function createRoutineResultQueue(
299
346
  }
300
347
  /** Read-modify-write under the file lock (`mutator(doc)` returns the next
301
348
  * doc, or nothing to keep the file); every write also retires stale and
302
- * surplus round counts. */
303
- function update(mutator) {
304
- return updateJsonAtomic(
349
+ * surplus round counts. The watches a write added or removed, by any path
350
+ * (a report, a turn never taken, a cap eviction, a pruned home), start or
351
+ * end their hand-offs (delegated-approval-host.mjs). */
352
+ async function update(mutator) {
353
+ let watches = null;
354
+ const result = await updateJsonAtomic(
305
355
  filePath,
306
356
  (raw) => {
307
- const next = mutator(parts(raw));
357
+ const doc = parts(raw);
358
+ const next = mutator(doc);
359
+ watches = { before: doc.watches, after: next ? section(next, 'watches') : doc.watches };
308
360
  return next ? { ...next, rounds: tidyRounds(next.rounds) } : undefined;
309
361
  },
310
362
  { backup: true }
311
363
  );
364
+ if (watches) syncHandoffs(watches.before, watches.after);
365
+ return result;
312
366
  }
313
367
  const appended = (doc, homeId, items, omittedCount = 0) => appendedItems(doc, homeId, items, limit, omittedCount);
314
368
  return {
@@ -423,9 +477,14 @@ export function createRoutineResultQueue(
423
477
  return (await read()).omitted[homeId] || null;
424
478
  },
425
479
  /** Queue `item` for `homeId`; `unwatch` (run ids) drops those watches in the
426
- * same write, also when the item was queued or posted before. */
427
- async add(homeId, item, { unwatch = [] } = {}) {
428
- await update((doc) => {
480
+ * same write, also when the item was queued or posted before.
481
+ * `progressPosted`: the thread holds the progress update a final report
482
+ * names (withoutRepeatedProgress). `progressed` (`{ runId, turnId }`):
483
+ * watch `runId` reported turn `turnId` as progress — its durable mark,
484
+ * stored in the same write (agent-task-sessions.mjs progressedThrough). */
485
+ async add(homeId, queued, { unwatch = [], progressPosted = false, progressed = null } = {}) {
486
+ await update((stored) => {
487
+ const { doc, item } = withoutRepeatedProgress(stored, homeId, queued, progressPosted);
429
488
  let next = null;
430
489
  if (item.roundTrip !== true) next = appended(doc, homeId, [item]);
431
490
  else {
@@ -443,11 +502,19 @@ export function createRoutineResultQueue(
443
502
  next = { ...next, rounds: { ...doc.rounds, [homeId]: counted } };
444
503
  }
445
504
  }
446
- const dropped = unwatch.filter((runId) => doc.watches[runId]);
447
- if (!dropped.length) return next ?? undefined;
448
- const watches = { ...doc.watches };
449
- for (const runId of dropped) delete watches[runId];
450
- return { ...(next || doc), watches };
505
+ let watches = doc.watches;
506
+ const marked = progressed?.turnId && watches[progressed.runId];
507
+ if (marked && marked.progressed !== progressed.turnId) {
508
+ watches = { ...watches, [progressed.runId]: { ...marked, progressed: progressed.turnId } };
509
+ }
510
+ const dropped = unwatch.filter((runId) => watches[runId]);
511
+ if (dropped.length) {
512
+ watches = { ...watches };
513
+ for (const runId of dropped) delete watches[runId];
514
+ }
515
+ const base = next || doc;
516
+ if (watches === doc.watches) return base === stored ? undefined : base;
517
+ return { ...base, watches };
451
518
  });
452
519
  },
453
520
  /** Keep `watch` ({ runId, agentId, homeSessionId, taskSessionId, title,
@@ -469,9 +536,12 @@ export function createRoutineResultQueue(
469
536
  });
470
537
  },
471
538
  watchCap: caps.watches,
472
- /** Every stored watch, oldest first. */
539
+ /** Every stored watch, oldest first; each is a live hand-off (after a
540
+ * restart, this read brings them back). */
473
541
  async watches() {
474
- return Object.values((await read()).watches);
542
+ const watches = Object.values((await read()).watches);
543
+ for (const watch of watches) noteHandoff(watch ?? {});
544
+ return watches;
475
545
  },
476
546
  /** The watch of `runId` follows the turn submitted as `submissionId`: a
477
547
  * room member's one turn answering several sends, or a fresh turn
@@ -492,6 +562,26 @@ export function createRoutineResultQueue(
492
562
  return { ...doc, watches };
493
563
  });
494
564
  },
565
+ /** Keep `work` ({ runId, agentId, taskSessionId, owner, at }): a bot-sent
566
+ * turn that reports nothing, until its work ends. It is no watch: it
567
+ * owes no report, starts no hand-off and takes no part in a watch cap. */
568
+ async own(work) {
569
+ await update((doc) => ({ ...doc, owned: { ...doc.owned, [work.runId]: work } }));
570
+ },
571
+ /** Every stored record of bot-sent work that reports nothing, oldest first. */
572
+ async owned() {
573
+ return Object.values((await read()).owned);
574
+ },
575
+ /** Drop the records of `runIds` (a run id or a list; their work ended), in one write. */
576
+ async disown(runIds) {
577
+ const ids = [runIds].flat();
578
+ await update((doc) => {
579
+ if (!ids.some((runId) => doc.owned[runId])) return undefined;
580
+ const owned = { ...doc.owned };
581
+ for (const runId of ids) delete owned[runId];
582
+ return { ...doc, owned };
583
+ });
584
+ },
495
585
  /** The user spoke in `homeId`: its round trips count from zero again. */
496
586
  async resetRounds(homeId) {
497
587
  await update((doc) => {
@@ -537,7 +627,7 @@ const messageIdOf = (runId) => `routine_${runId}`;
537
627
  * or false (logged) when the surface threw. */
538
628
  function noticeLive(live, log, homeId, notice) {
539
629
  try {
540
- return live()?.notice?.(homeId, notice);
630
+ return live()?.notice?.(homeId, { ...notice, posted: true });
541
631
  } catch (error) {
542
632
  log(`live notice in ${homeId} failed: ${error?.message || error}`);
543
633
  return false;
@@ -571,6 +661,13 @@ function routineResultItem(row, result) {
571
661
  };
572
662
  }
573
663
 
664
+ /** End the hand-off of each watch in `before` and not `after`, and start
665
+ * each one `after` added (both keyed by run id). */
666
+ function syncHandoffs(before, after) {
667
+ for (const runId of Object.keys(before)) if (!after[runId]) endHandoff(runId);
668
+ for (const [runId, watch] of Object.entries(after)) if (!before[runId]) noteHandoff(watch ?? {});
669
+ }
670
+
574
671
  /** The run ids of `watches` a home at its watch `cap` gives up before
575
672
  * storing `watch`: its oldest whose target sessions are proven deleted. */
576
673
  function watchesToDrop(watches, watch, cap, sessionDeleted) {
@@ -594,6 +691,9 @@ function homeProvenDeleted(homeId, agentPresence, roomPresence) {
594
691
  return Boolean(roomId) && roomPresence(roomId) === 'deleted';
595
692
  }
596
693
 
694
+ // Task report statuses that post a row and wake nobody.
695
+ const QUIET_TASK_STATUSES = new Set(['started', 'blocked', 'working']);
696
+
597
697
  /** The queued item of one task report (enqueueTask). */
598
698
  function taskReportItem({
599
699
  runId,
@@ -607,6 +707,7 @@ function taskReportItem({
607
707
  hop = 0,
608
708
  watchId = '',
609
709
  watchIds = [],
710
+ progressRunId = '',
610
711
  }) {
611
712
  // The model-facing reply keeps its lines up to REPLY_LIMIT; the row's
612
713
  // one-line summary is derived from it (agentTaskMessage).
@@ -630,7 +731,12 @@ function taskReportItem({
630
731
  ...(watchIds.length > 1 ? { sendIds: watchIds.map(String) } : {}),
631
732
  // The files the turn produced (handoff-attachments.mjs collectTurnArtifacts).
632
733
  ...(Array.isArray(files) && files.length ? { files, ...(omitted > 0 ? { omitted } : {}) } : {}),
633
- roundTrip: status !== 'started' && status !== 'blocked',
734
+ // The progress update that carried this final report's answer, if one
735
+ // did: the queue keeps the answer from being repeated (withoutRepeatedProgress).
736
+ ...(progressRunId ? { progressRunId: String(progressRunId) } : {}),
737
+ // Only a turn's end wakes the agent and counts a round trip: not its
738
+ // start, a wait for the user, or a progress update (status 'working').
739
+ roundTrip: !QUIET_TASK_STATUSES.has(status),
634
740
  ...(hop > 0 ? { hop } : {}),
635
741
  at: Date.now(),
636
742
  };
@@ -644,7 +750,7 @@ function postQueuedRows(session, items, omitted) {
644
750
  const fresh = [];
645
751
  const omittedId = omitted ? messageIdOf(omitted.id) : '';
646
752
  if (omitted && !posted.has(omittedId)) {
647
- const message = routineOmittedMessage({ count: omitted.count, messageId: omittedId });
753
+ const message = postedMessage(routineOmittedMessage({ count: omitted.count, messageId: omittedId }));
648
754
  session.messages.push(message);
649
755
  fresh.push({ message, notice: routineResultNotice(message) });
650
756
  }
@@ -716,15 +822,17 @@ function createHomeRelay({
716
822
  log(`the relay turn in ${homeId} ${why}; its results stay posted unrelayed`);
717
823
  const messageId = messageIdOf(`relay_${batch.id}`);
718
824
  if (!session.messages.some((message) => message?.meta?.transcript?.messageId === messageId)) {
719
- const message = agentTaskRelayFailedMessage({
720
- count: batch.runIds.length + (batch.overflow?.count || 0),
721
- tries,
722
- messageId,
723
- });
825
+ const message = postedMessage(
826
+ agentTaskRelayFailedMessage({
827
+ count: batch.runIds.length + (batch.overflow?.count || 0),
828
+ tries,
829
+ messageId,
830
+ })
831
+ );
724
832
  session.messages.push(message);
725
833
  session.updatedAt = Date.now();
726
834
  await saveSession(session);
727
- noticeLive(live, log, homeId, { ...routineResultNotice(message), messageId });
835
+ noticeLive(live, log, homeId, { ...routineResultNotice(message), messageId, at: message.meta.transcript.at });
728
836
  }
729
837
  await queue.woke(homeId, batch.id);
730
838
  return true;
@@ -807,6 +915,200 @@ function createHomeRelay({
807
915
  return { wakeOwed };
808
916
  }
809
917
 
918
+ /** Whether home thread `sessionId` is busy: a turn in flight in this process,
919
+ * or the `live()` surface says so (false when it threw). */
920
+ const homeBusy = (turnInFlight, live) => (sessionId) => {
921
+ if (turnInFlight(sessionId)) return true;
922
+ try {
923
+ return live()?.busy?.(sessionId) === true;
924
+ } catch {
925
+ return false;
926
+ }
927
+ };
928
+
929
+ /** `later(homeId, ms)`: drain `homeId` again after `ms`, one retry timer per
930
+ * home armed at a time. */
931
+ function createRetryTimer(retryLater, drain) {
932
+ const waiting = new Set(); // homes with a retry timer armed
933
+ return function later(homeId, ms) {
934
+ if (waiting.has(homeId)) return;
935
+ waiting.add(homeId);
936
+ retryLater(() => {
937
+ waiting.delete(homeId);
938
+ void drain(homeId);
939
+ }, ms);
940
+ };
941
+ }
942
+
943
+ /** The held requests shown live in a busy thread. A held request waits on the
944
+ * user, possibly raised by the very turn that keeps its thread busy (a
945
+ * routine turn): `show` shows it live at once; the stored row (same
946
+ * messageId) replaces it once the thread is idle, and `forget` lets a later
947
+ * one show again. */
948
+ function createHeldLiveDisplay(live, log) {
949
+ const shownLive = new Set(); // held-request message ids shown live in a busy thread
950
+ return {
951
+ show(homeId, items) {
952
+ for (const item of items) {
953
+ if (item.kind !== 'held-request') continue;
954
+ const messageId = messageIdOf(item.runId);
955
+ if (shownLive.has(messageId)) continue;
956
+ shownLive.add(messageId);
957
+ noticeLive(live, log, homeId, { ...deliveredRow(item, messageId).notice, messageId, at: item.at });
958
+ }
959
+ },
960
+ forget(items) {
961
+ for (const item of items) shownLive.delete(messageIdOf(item.runId));
962
+ },
963
+ };
964
+ }
965
+
966
+ /** The queues a drain drops unposted instead of retrying them. */
967
+ function createQueueDrops({ queue, log, homeAgent, homeDeleted, sessionDeleted }) {
968
+ /** A room thread proven gone: its queued rows are dropped, never retried. */
969
+ async function dropGoneRoomThread(homeId, items, omitted) {
970
+ if (!isRoomThreadSessionId(homeId) || !(homeDeleted(homeId) || sessionDeleted(homeId))) return false;
971
+ log(`${items.length} task report(s) for the deleted ${homeId} are dropped`);
972
+ await dropQueued(queue, homeId, items, omitted);
973
+ return true;
974
+ }
975
+
976
+ /** A room log's queued rows: task reports go to member sessions now, so what
977
+ * an earlier version left under the log's id is dropped, never posted. */
978
+ async function dropRoomLogQueue(logId, items, omitted) {
979
+ log(`${items.length} legacy task report(s) queued for the room log ${logId} are dropped`);
980
+ await dropQueued(queue, logId, items, omitted);
981
+ }
982
+
983
+ /** A missing home of an agent that can never have one (no bot): its queued
984
+ * rows are dropped with a log line, never retried. */
985
+ async function dropHomeless(homeId, items, omitted) {
986
+ const agentId = agentIdOfHomeSession(homeId);
987
+ if (!agentId || agentId === MIXY_AGENT_ID || homeAgent(agentId) !== 'none') return false;
988
+ log(`${items.length} result(s) for ${homeId} are dropped: ${agentId} is not a bot, so it has no chat`);
989
+ await dropQueued(queue, homeId, items, omitted);
990
+ return true;
991
+ }
992
+
993
+ return { dropGoneRoomThread, dropRoomLogQueue, dropHomeless };
994
+ }
995
+
996
+ /** The durable intake of a home: each enqueue persists its row for the home
997
+ * thread, then starts draining it, and resolves once it is queued. */
998
+ function createHomeEnqueue(queue, drain, { postingFor, postedIn }) {
999
+ /** Persist `result` ({ runId, kind, ok, text, runSessionId }) of a schedule
1000
+ * or webhook `row` for its owner's home, then start draining it. Resolves
1001
+ * once it is queued. */
1002
+ async function enqueue(row, result) {
1003
+ const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
1004
+ if (!result?.runId) throw new Error('routine result: runId required');
1005
+ await queue.add(homeId, routineResultItem(row, result));
1006
+ void drain(homeId);
1007
+ return homeId;
1008
+ }
1009
+
1010
+ /** Persist one held-request row (`held`: normalizeHeldRequest fields) for
1011
+ * `agentId`'s home thread, then start draining it. Each status posts once. */
1012
+ async function enqueueHeld(agentId, held) {
1013
+ const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
1014
+ if (!held?.id) throw new Error('held request: id required');
1015
+ await queue.add(homeId, {
1016
+ runId: `${held.id}_${held.status || 'pending'}`,
1017
+ kind: 'held-request',
1018
+ held,
1019
+ at: Date.now(),
1020
+ });
1021
+ void drain(homeId);
1022
+ return homeId;
1023
+ }
1024
+
1025
+ /** Persist one report of a coding task session `agentId` created (session
1026
+ * create) for its home thread, then start draining it. `status` 'started'
1027
+ * and 'blocked' only post a line; a turn's end also wakes the agent. */
1028
+ async function enqueueTask(agentId, report) {
1029
+ const { runId, taskSessionId, watchId = '', watchIds = [], from = '' } = report;
1030
+ const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
1031
+ if (!runId || !taskSessionId) throw new Error('task report: runId and taskSessionId required');
1032
+ const item = taskReportItem(report);
1033
+ const unwatch = [...new Set([watchId, ...watchIds].filter(Boolean).map(String))];
1034
+ // A room member's own work reports to its member session, never its home.
1035
+ const target = roomMemberOfSession(from)?.agentId === agentId ? from : homeId;
1036
+ if (item.progressRunId) {
1037
+ // Whether its answer already went out as that progress update is
1038
+ // decided in the thread's posting turn, against the thread itself: a
1039
+ // row being posted (or posted before a crash, not dequeued yet) counts
1040
+ // as posted, never as still queued.
1041
+ await postingFor(target, async () =>
1042
+ queue.add(target, item, { unwatch, progressPosted: postedIn(target, item.progressRunId) })
1043
+ );
1044
+ } else {
1045
+ // A progress update moves its send's watch mark (`progressed`) to the
1046
+ // turn it carries, in the same write: a restart never reports it again.
1047
+ const progressed = report.progressWatchId
1048
+ ? { runId: String(report.progressWatchId), turnId: String(report.answerId || '') }
1049
+ : null;
1050
+ await queue.add(target, item, { unwatch, progressed });
1051
+ }
1052
+ void drain(target);
1053
+ return target;
1054
+ }
1055
+
1056
+ /** Persist `text`, the agent's own reply from turn `runId` it ran outside
1057
+ * its thread, for `agentId`'s home thread, then start draining it. It posts
1058
+ * once, as the agent's message; no turn follows. */
1059
+ async function enqueueMessage(agentId, { runId, text }) {
1060
+ const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
1061
+ if (!runId || !String(text || '').trim()) throw new Error('agent message: runId and text required');
1062
+ await queue.add(homeId, { runId: String(runId), kind: 'agent-message', text: String(text).trim(), at: Date.now() });
1063
+ void drain(homeId);
1064
+ return homeId;
1065
+ }
1066
+
1067
+ return { enqueue, enqueueHeld, enqueueTask, enqueueMessage };
1068
+ }
1069
+
1070
+ /** The queue's bookkeeping across processes: report watches and the restart
1071
+ * recovery that retires deleted homes and drains what was left queued. */
1072
+ function createHomeBookkeeping({ queue, log, homeDeleted, sessionDeleted, drain }) {
1073
+ /** Retire the bookkeeping of deleted homes (and stale round counts). */
1074
+ async function prune() {
1075
+ await queue.prune((await queue.bookkeptHomes()).filter(homeDeleted));
1076
+ }
1077
+
1078
+ /** Store `watch`; a home at its watch cap first gives up its oldest watches
1079
+ * whose target sessions are proven deleted, and refuses the watch (so the
1080
+ * send claims no report) when that frees too few. */
1081
+ async function watchTask(watch) {
1082
+ const drop = watchesToDrop(await queue.watches(), watch, queue.watchCap, sessionDeleted);
1083
+ if (drop.length) {
1084
+ log(`${watch.homeSessionId} is at its watch cap: dropped ${drop.length} watch(es) of deleted sessions`);
1085
+ }
1086
+ await queue.watch(watch, { drop });
1087
+ }
1088
+
1089
+ /** Drain every queue left by an earlier process, after retiring stale bookkeeping. */
1090
+ async function recover() {
1091
+ try {
1092
+ await prune();
1093
+ } catch (error) {
1094
+ log(`pruning the routine results failed: ${error?.message || error}`);
1095
+ }
1096
+ // The stored watches name the hand-offs an earlier process left open:
1097
+ // reading them notes each one now, so a target that asks again after the
1098
+ // restart reaches the chat that handed it the work.
1099
+ try {
1100
+ await queue.watches();
1101
+ } catch (error) {
1102
+ log(`reading the report watches failed: ${error?.message || error}`);
1103
+ }
1104
+ const homes = await queue.pendingHomes();
1105
+ for (const homeId of homes) void drain(homeId);
1106
+ return homes;
1107
+ }
1108
+
1109
+ return { watchTask, recover };
1110
+ }
1111
+
810
1112
  export function createAgentHomeDelivery({
811
1113
  loadSession,
812
1114
  saveSession,
@@ -828,6 +1130,9 @@ export function createAgentHomeDelivery({
828
1130
  // at all. 'none' (proven: a workflow agent such as Reviewer) drops the rows
829
1131
  // queued for its missing home instead of waiting on it forever.
830
1132
  homeAgent = () => 'unknown',
1133
+ // Whether the representative bot exists (mixy-presence.mjs): while it does
1134
+ // not, heartbeat reports queued for its home are dropped.
1135
+ mixyExists = async () => true,
831
1136
  // Waits before each new try of a relay turn that did not finish; past the
832
1137
  // last one the results stay posted with a note and nobody is woken.
833
1138
  wakeRetryDelaysMs = WAKE_RETRY_DELAYS_MS,
@@ -840,19 +1145,20 @@ export function createAgentHomeDelivery({
840
1145
  roomRelay = (sessionId, prompt, options) => agentRoomHost().relay(sessionId, prompt, options),
841
1146
  log = (line) => process.stderr.write(`mixdog routines: ${line}\n`),
842
1147
  }) {
843
- const busy = (sessionId) => {
844
- if (turnInFlight(sessionId)) return true;
845
- try {
846
- return live()?.busy?.(sessionId) === true;
847
- } catch {
848
- return false;
849
- }
850
- };
1148
+ const busy = homeBusy(turnInFlight, live);
851
1149
  const draining = new Map(); // home → the running pass
852
1150
  const again = new Set(); // homes enqueued to while their pass ran
853
- const waiting = new Set(); // homes with a retry timer armed
854
- const shownLive = new Set(); // held-request message ids shown live in a busy thread
855
1151
  const waitLogged = new Set(); // missing homes whose wait was logged
1152
+ const later = createRetryTimer(retryLater, drain);
1153
+ const heldLive = createHeldLiveDisplay(live, log);
1154
+ const homeDeleted = (homeId) => homeProvenDeleted(homeId, agentPresence, roomPresence);
1155
+ const { dropGoneRoomThread, dropRoomLogQueue, dropHomeless } = createQueueDrops({
1156
+ queue,
1157
+ log,
1158
+ homeAgent,
1159
+ homeDeleted,
1160
+ sessionDeleted,
1161
+ });
856
1162
  const relay = createHomeRelay({
857
1163
  queue,
858
1164
  saveSession,
@@ -867,61 +1173,123 @@ export function createAgentHomeDelivery({
867
1173
  busyRetryMs,
868
1174
  missingRetryMs,
869
1175
  });
870
-
871
- /** A held request waits on the user, possibly raised by the very turn that
872
- * keeps its thread busy (a routine turn): show it live at once; the stored
873
- * row (same messageId) replaces it once the thread is idle. */
874
- function showHeldLive(homeId, items) {
875
- for (const item of items) {
876
- if (item.kind !== 'held-request') continue;
877
- const messageId = messageIdOf(item.runId);
878
- if (shownLive.has(messageId)) continue;
879
- shownLive.add(messageId);
880
- noticeLive(live, log, homeId, { ...deliveredRow(item, messageId).notice, messageId });
881
- }
1176
+ // Per thread, one posting turn at a time: push queued rows, save the
1177
+ // thread, dequeue them (the drain, the turn prelude), and a final report's
1178
+ // check whether its answer already went out as a progress update. No pass
1179
+ // ever dequeues rows another pushed but has not saved yet, and a row being
1180
+ // posted is never mistaken for one still queued.
1181
+ const postingChains = new Map();
1182
+ function postingFor(homeId, fn) {
1183
+ const run = (postingChains.get(homeId) || Promise.resolve()).then(fn);
1184
+ const tail = run.then(
1185
+ () => {},
1186
+ () => {}
1187
+ );
1188
+ postingChains.set(homeId, tail);
1189
+ void tail.then(() => {
1190
+ if (postingChains.get(homeId) === tail) postingChains.delete(homeId);
1191
+ });
1192
+ return run;
882
1193
  }
1194
+ /** Whether queued row `runId` is in thread `homeId` (posted, saved or not). */
1195
+ const postedIn = (homeId, runId) => {
1196
+ const messages = loadSession(homeId)?.messages;
1197
+ const messageId = messageIdOf(runId);
1198
+ return Array.isArray(messages) && messages.some((message) => message?.meta?.transcript?.messageId === messageId);
1199
+ };
1200
+ const intake = createHomeEnqueue(queue, drain, { postingFor, postedIn });
1201
+ const { watchTask, recover } = createHomeBookkeeping({ queue, log, homeDeleted, sessionDeleted, drain });
883
1202
 
884
- function later(homeId, ms) {
885
- if (waiting.has(homeId)) return;
886
- waiting.add(homeId);
887
- retryLater(() => {
888
- waiting.delete(homeId);
889
- void drain(homeId);
890
- }, ms);
1203
+ /** Append `items` (and the omission notice `omitted`) not posted before to
1204
+ * `homeId`'s thread `session`, store it and show the new rows live.
1205
+ * Returns the run ids owed a wake (postQueuedRows). Nothing awaits before
1206
+ * the push, so no turn can start in between. */
1207
+ async function postRows(homeId, session, items, omitted) {
1208
+ if (!Array.isArray(session.messages)) session.messages = [];
1209
+ const { fresh, toWake } = postQueuedRows(session, items, omitted);
1210
+ heldLive.forget(items);
1211
+ // The caller dequeues `items` (and `omitted`) next: the thread holding
1212
+ // them is saved first, also when an earlier pass pushed them but its save
1213
+ // failed (they are in the thread in memory, not on disk).
1214
+ if (items.length || omitted) {
1215
+ if (fresh.length) session.updatedAt = Date.now();
1216
+ await saveSession(session);
1217
+ }
1218
+ for (const { message, notice } of fresh) {
1219
+ const { messageId, at } = message.meta.transcript;
1220
+ noticeLive(live, log, homeId, { ...notice, messageId, at });
1221
+ }
1222
+ return toWake;
891
1223
  }
892
1224
 
893
- /** A room thread proven gone: its queued rows are dropped, never retried. */
894
- async function dropGoneRoomThread(homeId, items, omitted) {
895
- if (!isRoomThreadSessionId(homeId) || !(homeDeleted(homeId) || sessionDeleted(homeId))) return false;
896
- log(`${items.length} task report(s) for the deleted ${homeId} are dropped`);
897
- await dropQueued(queue, homeId, items, omitted);
898
- return true;
1225
+ /** Right before a turn of thread `homeId` takes its prompt (whatever started
1226
+ * it: the user, a routine, a wake): post its queued rows that wake nobody —
1227
+ * progress updates of the sessions its agent handed work to, start and wait
1228
+ * lines, held requests, routine results — in queue order, so the turn reads
1229
+ * them before its prompt. Rows that wake the agent stay for the drain after
1230
+ * the turn, which posts them and wakes it as before. The caller starts the
1231
+ * turn, so the thread's busy state is its own. Rows the queue dropped past
1232
+ * its limit are announced first by the "n omitted" notice, retired in the
1233
+ * same write that dequeues the posted rows (as the drain does). It runs
1234
+ * in the thread's posting turn (postingFor), never beside the drain's. A
1235
+ * failure is logged; the turn goes on and the drain posts the rows later. */
1236
+ async function postBeforeTurn(homeId) {
1237
+ try {
1238
+ await postingFor(homeId, async () => {
1239
+ const items = (await queue.items(homeId)).filter((item) => !wakesAgent(item));
1240
+ const omitted = await queue.omitted(homeId);
1241
+ if (!items.length && !omitted) return;
1242
+ const session = loadSession(homeId);
1243
+ if (!session || session.closed === true) return;
1244
+ await postRows(homeId, session, items, omitted);
1245
+ await queue.remove(
1246
+ homeId,
1247
+ items.map((item) => item.runId),
1248
+ omitted
1249
+ );
1250
+ });
1251
+ } catch (error) {
1252
+ log(`posting the rows owed before a turn of ${homeId} failed: ${error?.message || error}`);
1253
+ }
899
1254
  }
900
1255
 
901
- /** A room log's queued rows: task reports go to member sessions now, so what
902
- * an earlier version left under the log's id is dropped, never posted. */
903
- async function dropRoomLogQueue(logId, items, omitted) {
904
- log(`${items.length} legacy task report(s) queued for the room log ${logId} are dropped`);
905
- await dropQueued(queue, logId, items, omitted);
1256
+ /** Mixy's heartbeat reports queued for her home while she does not exist
1257
+ * are dropped, never posted once she is created (issue #243); so is the
1258
+ * omission notice when nothing else waits (the rows it counts were these
1259
+ * reports too). Returns what is left to post. */
1260
+ async function dropHeartbeatReports(homeId, items, omitted) {
1261
+ const kept = items.filter((item) => !isHeartbeatReport(item));
1262
+ const retired = kept.length ? null : omitted;
1263
+ log(`${items.length - kept.length} heartbeat report(s) for ${homeId} are dropped: Mixy was never created`);
1264
+ await queue.remove(
1265
+ homeId,
1266
+ items.filter(isHeartbeatReport).map((item) => item.runId),
1267
+ retired
1268
+ );
1269
+ return { items: kept, omitted: retired ? null : omitted };
906
1270
  }
907
1271
 
908
- /** A missing home of an agent that can never have one (no bot): its queued
909
- * rows are dropped with a log line, never retried. */
910
- async function dropHomeless(homeId, items, omitted) {
911
- const agentId = agentIdOfHomeSession(homeId);
912
- if (!agentId || agentId === MIXY_AGENT_ID || homeAgent(agentId) !== 'none') return false;
913
- log(`${items.length} result(s) for ${homeId} are dropped: ${agentId} is not a bot, so it has no chat`);
914
- await dropQueued(queue, homeId, items, omitted);
915
- return true;
1272
+ /** Post every queued result of `homeId` if its thread is idle, in its
1273
+ * posting turn, then wake the agent for what it owes; else retry later.
1274
+ * The wake runs outside the posting turn: the turn it starts posts first
1275
+ * (postBeforeTurn). */
1276
+ async function drainOnce(homeId) {
1277
+ const session = await postingFor(homeId, () => postIdle(homeId));
1278
+ if (session) await relay.wakeOwed(homeId, session);
916
1279
  }
917
1280
 
918
- /** Post every queued result of `homeId` if its thread is idle; else retry later. */
919
- async function drainOnce(homeId) {
920
- const items = await queue.items(homeId);
921
- const omitted = await queue.omitted(homeId);
1281
+ /** drainOnce's posting: the thread it posted to; nothing when nothing is
1282
+ * owed or the pass ends here (moved, busy, missing, dropped). */
1283
+ async function postIdle(homeId) {
1284
+ let items = await queue.items(homeId);
1285
+ let omitted = await queue.omitted(homeId);
922
1286
  const owed = owesWake(await queue.wakes(homeId));
923
1287
  if (!items.length && !omitted && !owed) return;
924
1288
  if (roomIdOfSession(homeId)) return dropRoomLogQueue(homeId, items, omitted);
1289
+ if (homeId === MIXY_HOME && items.some(isHeartbeatReport) && !(await mixyExists())) {
1290
+ ({ items, omitted } = await dropHeartbeatReports(homeId, items, omitted));
1291
+ if (!items.length && !omitted && !owed) return;
1292
+ }
925
1293
  const agentId = agentIdOfHomeSession(homeId);
926
1294
  if (agentId && agentId !== MIXY_AGENT_ID && agentPresence(agentId) === 'deleted') {
927
1295
  // The owner is gone (its home archived): Mixy, the default owner, gets them.
@@ -933,7 +1301,7 @@ export function createAgentHomeDelivery({
933
1301
  return;
934
1302
  }
935
1303
  if (busy(homeId)) {
936
- showHeldLive(homeId, items);
1304
+ heldLive.show(homeId, items);
937
1305
  return later(homeId, busyRetryMs);
938
1306
  }
939
1307
  // From here to the push nothing awaits, so no turn can start in between.
@@ -948,16 +1316,7 @@ export function createAgentHomeDelivery({
948
1316
  }
949
1317
  return later(homeId, missingRetryMs);
950
1318
  }
951
- if (!Array.isArray(session.messages)) session.messages = [];
952
- const { fresh, toWake } = postQueuedRows(session, items, omitted);
953
- for (const item of items) shownLive.delete(messageIdOf(item.runId));
954
- if (fresh.length) {
955
- session.updatedAt = Date.now();
956
- await saveSession(session);
957
- for (const { message, notice } of fresh) {
958
- noticeLive(live, log, homeId, { ...notice, messageId: message.meta.transcript.messageId });
959
- }
960
- }
1319
+ const toWake = await postRows(homeId, session, items, omitted);
961
1320
  // Dequeuing and owing the wake are one write: no restart between posting
962
1321
  // and waking loses the wake.
963
1322
  const { overflowed } = await queue.remove(
@@ -967,7 +1326,7 @@ export function createAgentHomeDelivery({
967
1326
  { wake: toWake }
968
1327
  );
969
1328
  if (overflowed) log(`${homeId} is at its wake cap: ${overflowed} result(s) ride the next wake as a count`);
970
- await relay.wakeOwed(homeId, session);
1329
+ return session;
971
1330
  }
972
1331
 
973
1332
  /** The single drainer of `homeId`; a call during a pass runs one more pass. */
@@ -994,59 +1353,6 @@ export function createAgentHomeDelivery({
994
1353
  return pass;
995
1354
  }
996
1355
 
997
- /** Persist `result` ({ runId, kind, ok, text, runSessionId }) of a schedule
998
- * or webhook `row` for its owner's home, then start draining it. Resolves
999
- * once it is queued. */
1000
- async function enqueue(row, result) {
1001
- const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
1002
- if (!result?.runId) throw new Error('routine result: runId required');
1003
- await queue.add(homeId, routineResultItem(row, result));
1004
- void drain(homeId);
1005
- return homeId;
1006
- }
1007
-
1008
- /** Persist one held-request row (`held`: normalizeHeldRequest fields) for
1009
- * `agentId`'s home thread, then start draining it. Each status posts once. */
1010
- async function enqueueHeld(agentId, held) {
1011
- const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
1012
- if (!held?.id) throw new Error('held request: id required');
1013
- await queue.add(homeId, {
1014
- runId: `${held.id}_${held.status || 'pending'}`,
1015
- kind: 'held-request',
1016
- held,
1017
- at: Date.now(),
1018
- });
1019
- void drain(homeId);
1020
- return homeId;
1021
- }
1022
-
1023
- /** Persist one report of a coding task session `agentId` created (session
1024
- * create) for its home thread, then start draining it. `status` 'started'
1025
- * and 'blocked' only post a line; a turn's end also wakes the agent. */
1026
- async function enqueueTask(agentId, report) {
1027
- const { runId, taskSessionId, watchId = '', watchIds = [], from = '' } = report;
1028
- const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
1029
- if (!runId || !taskSessionId) throw new Error('task report: runId and taskSessionId required');
1030
- const item = taskReportItem(report);
1031
- const unwatch = [...new Set([watchId, ...watchIds].filter(Boolean).map(String))];
1032
- // A room member's own work reports to its member session, never its home.
1033
- const target = roomMemberOfSession(from)?.agentId === agentId ? from : homeId;
1034
- await queue.add(target, item, { unwatch });
1035
- void drain(target);
1036
- return target;
1037
- }
1038
-
1039
- /** Persist `text`, the agent's own reply from turn `runId` it ran outside
1040
- * its thread, for `agentId`'s home thread, then start draining it. It posts
1041
- * once, as the agent's message; no turn follows. */
1042
- async function enqueueMessage(agentId, { runId, text }) {
1043
- const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
1044
- if (!runId || !String(text || '').trim()) throw new Error('agent message: runId and text required');
1045
- await queue.add(homeId, { runId: String(runId), kind: 'agent-message', text: String(text).trim(), at: Date.now() });
1046
- void drain(homeId);
1047
- return homeId;
1048
- }
1049
-
1050
1356
  /** Show `run`'s live-only row (routineRunNotice: `running`, or a result whose
1051
1357
  * delivery failed) in `row`'s owner home when it is open and idle. Nothing
1052
1358
  * is stored and no turn or unread mark follows; the posted result (same
@@ -1058,48 +1364,19 @@ export function createAgentHomeDelivery({
1058
1364
  return noticeLive(live, log, homeId, notice) === true;
1059
1365
  }
1060
1366
 
1061
- const homeDeleted = (homeId) => homeProvenDeleted(homeId, agentPresence, roomPresence);
1062
-
1063
- /** Retire the bookkeeping of deleted homes (and stale round counts). */
1064
- async function prune() {
1065
- await queue.prune((await queue.bookkeptHomes()).filter(homeDeleted));
1066
- }
1067
-
1068
- /** Store `watch`; a home at its watch cap first gives up its oldest watches
1069
- * whose target sessions are proven deleted, and refuses the watch (so the
1070
- * send claims no report) when that frees too few. */
1071
- async function watchTask(watch) {
1072
- const drop = watchesToDrop(await queue.watches(), watch, queue.watchCap, sessionDeleted);
1073
- if (drop.length) {
1074
- log(`${watch.homeSessionId} is at its watch cap: dropped ${drop.length} watch(es) of deleted sessions`);
1075
- }
1076
- await queue.watch(watch, { drop });
1077
- }
1078
-
1079
- /** Drain every queue left by an earlier process, after retiring stale bookkeeping. */
1080
- async function recover() {
1081
- try {
1082
- await prune();
1083
- } catch (error) {
1084
- log(`pruning the routine results failed: ${error?.message || error}`);
1085
- }
1086
- const homes = await queue.pendingHomes();
1087
- for (const homeId of homes) void drain(homeId);
1088
- return homes;
1089
- }
1090
-
1091
1367
  return {
1092
- enqueue,
1093
- enqueueHeld,
1094
- enqueueTask,
1095
- enqueueMessage,
1368
+ ...intake,
1096
1369
  announce,
1097
1370
  drain,
1371
+ postBeforeTurn,
1098
1372
  recover,
1099
1373
  watchTask,
1100
1374
  unwatchTask: (runId) => queue.unwatch(runId),
1101
1375
  followTask: (runId, submissionId) => queue.followWatch(runId, submissionId),
1102
1376
  taskWatches: () => queue.watches(),
1377
+ ownTask: (work) => queue.own(work),
1378
+ disownTask: (runId) => queue.disown(runId),
1379
+ ownedTasks: () => queue.owned(),
1103
1380
  resetRounds: (homeId) => queue.resetRounds(homeId),
1104
1381
  };
1105
1382
  }
@@ -1136,6 +1413,9 @@ async function processDelivery() {
1136
1413
  // A home chat exists only for bots; only a definition read successfully
1137
1414
  // can prove an agent is none (readHomeAgent).
1138
1415
  homeAgent: (agentId) => readHomeAgent({ dataDir: getPluginData(), shippedRoot: mixdogRoot(), id: agentId }),
1416
+ // The one rule (mixy-presence.mjs; routine-owner.mjs imports this
1417
+ // module, hence late).
1418
+ mixyExists: async () => (await import('./routine-owner.mjs')).readMixyExists(),
1139
1419
  // Deleting a session unlinks its record (session-delete.mjs) and leaves
1140
1420
  // no tombstone: only an absent file with no in-memory copy proves it.
1141
1421
  // An unreadable file probes 'unreadable' and stays.
@@ -1192,6 +1472,21 @@ export async function followAgentTask(runId, submissionId) {
1192
1472
  return (await processDelivery()).followTask(runId, submissionId);
1193
1473
  }
1194
1474
 
1475
+ /** Keep the record of one bot-sent turn that reports nothing until its work ends. */
1476
+ export async function ownAgentTask(work) {
1477
+ return (await processDelivery()).ownTask(work);
1478
+ }
1479
+
1480
+ /** Drop the record of one bot-sent turn that reports nothing (its work ended). */
1481
+ export async function disownAgentTask(runId) {
1482
+ return (await processDelivery()).disownTask(runId);
1483
+ }
1484
+
1485
+ /** The bot-sent turns that report nothing and whose work was not seen to end. */
1486
+ export async function agentOwnedTasks() {
1487
+ return (await processDelivery()).ownedTasks();
1488
+ }
1489
+
1195
1490
  /** A user's own message in `homeId`: its round trips with every target restart. */
1196
1491
  export async function resetAgentTaskRounds(homeId) {
1197
1492
  return (await processDelivery()).resetRounds(homeId);
@@ -1202,6 +1497,15 @@ export async function agentTaskWatches() {
1202
1497
  return (await processDelivery()).taskWatches();
1203
1498
  }
1204
1499
 
1500
+ /** The session turn prelude (session-turn-prelude.mjs) of the daemon: before a
1501
+ * turn of an agent's home thread or a room member session starts, its queued
1502
+ * rows that wake nobody post first (postBeforeTurn). Null for any other
1503
+ * session: nothing to wait for. */
1504
+ export function postBeforeAgentTurn(sessionId) {
1505
+ if (!isAgentHomeSessionId(sessionId) && !roomMemberOfSession(sessionId)) return null;
1506
+ return processDelivery().then((delivery) => delivery.postBeforeTurn(sessionId));
1507
+ }
1508
+
1205
1509
  /** Daemon start: post the results an earlier process left queued. */
1206
1510
  export async function recoverScheduleResults() {
1207
1511
  return (await processDelivery()).recover();