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
@@ -2,19 +2,32 @@
2
2
  * Keeps the PG session-search copy (session-search-db.mjs) in step with the
3
3
  * session store: per-turn ingest, a once-per-run backfill of stored sessions,
4
4
  * and deletion. Everything is best-effort and never touches the memory runtime.
5
+ *
6
+ * A session's writes run one at a time, in the order they were issued
7
+ * (serialized). That order is what keeps a value marked sensitive out of the
8
+ * index: the replacement that redacts the stored rows
9
+ * (replaceSessionSearchMessages) locks only rows that already exist, so an
10
+ * earlier plaintext append still in flight would otherwise land after it and
11
+ * bring the value back. A write also redacts with the values known when it
12
+ * runs, not when it was issued.
5
13
  */
6
14
  import { readdirSync, statSync } from 'node:fs';
7
15
  import { join } from 'node:path';
8
16
  import { isAgentOnlySession } from '../agent/orchestrator/session/store-summary-visibility.mjs';
17
+ import { redactSensitiveText, sensitiveValuesVersion } from '../agent/orchestrator/session/sensitive-values.mjs';
18
+ import { redactSensitiveMessages } from '../agent/orchestrator/session/sensitive-projection.mjs';
9
19
  import {
10
20
  appendSessionSearchMessages,
11
21
  deleteSessionSearchMessages,
12
22
  readSessionSearchBackfillStamps,
13
23
  readSessionSearchSessionIds,
14
24
  recordSessionSearchBackfillStamp,
25
+ replaceSessionSearchMessages,
15
26
  } from './session-search-db.mjs';
16
27
 
17
- const lastIngestedLength = new Map();
28
+ /** sessionId → { length, version }: the transcript length and the
29
+ * sensitive-values version (sensitive-values.mjs) of the last stored ingest. */
30
+ const lastIngested = new Map();
18
31
  const LAST_INGESTED_LIMIT = 500;
19
32
 
20
33
  function logFailure(what, error) {
@@ -23,6 +36,87 @@ function logFailure(what, error) {
23
36
  } catch {}
24
37
  }
25
38
 
39
+ /** sessionId → { tail, controller }: the tail of the session's write queue
40
+ * (see serialized) and the lifetime its writes run under (closeSessionSearch
41
+ * aborts it). Present only while a write of the session is queued or running. */
42
+ const writeQueues = new Map();
43
+
44
+ /** The reason a closed session's writes stop with; never reported as a failure. */
45
+ class SessionSearchClosedError extends Error {
46
+ constructor(sessionId) {
47
+ super(`session search writes of ${sessionId} closed`);
48
+ this.name = 'AbortError';
49
+ }
50
+ }
51
+
52
+ const reportUnlessClosed = (what) => (error) => {
53
+ if (!(error instanceof SessionSearchClosedError)) logFailure(what, error);
54
+ };
55
+
56
+ /** Runs `task(signal)` once every earlier task of `sessionId` has settled; the
57
+ * task's own promise is returned, so its failure reaches the caller to report.
58
+ * The queue moves on past a failed task: that failure is the caller's, not the
59
+ * next write's. `signal` is the session's write lifetime. */
60
+ function serialized(sessionId, task) {
61
+ const queue = writeQueues.get(sessionId);
62
+ const controller = queue && !queue.controller.signal.aborted ? queue.controller : new AbortController();
63
+ // A write cancelled while queued never starts (rejects with the close reason).
64
+ const run = (queue?.tail ?? Promise.resolve()).then(() => {
65
+ controller.signal.throwIfAborted();
66
+ return task(controller.signal);
67
+ });
68
+ const tail = run.then(
69
+ () => {},
70
+ () => {}
71
+ );
72
+ const entry = { tail, controller };
73
+ writeQueues.set(sessionId, entry);
74
+ tail.then(() => {
75
+ if (writeQueues.get(sessionId) === entry) writeQueues.delete(sessionId);
76
+ });
77
+ return run;
78
+ }
79
+
80
+ /**
81
+ * Stops the session's search writes for a runtime close: the queued ones never
82
+ * start and the running one is cancelled — a PostgreSQL runtime install or
83
+ * start it is waiting on included, once no other writer still waits on it
84
+ * (createSharedAbortable). Resolves when the queue has settled, so nothing this
85
+ * session issued writes into the data dir afterwards. The rows are derived:
86
+ * the stored-session backfill restores what a cancelled write left out.
87
+ */
88
+ export async function closeSessionSearch(sessionId) {
89
+ const queue = writeQueues.get(sessionId);
90
+ if (!queue) return;
91
+ queue.controller.abort(new SessionSearchClosedError(sessionId));
92
+ await queue.tail;
93
+ }
94
+
95
+ /** Stores `messages`, the transcript as it was when the turn was ingested, as
96
+ * the session's search rows; runs in the session's write queue. */
97
+ async function storeTranscript(sessionId, messages, signal) {
98
+ // A turn always grows the transcript; an unchanged length means nothing
99
+ // new. This is only a shortcut: the insert itself is idempotent.
100
+ const length = messages.length;
101
+ const version = sensitiveValuesVersion(sessionId);
102
+ const last = lastIngested.get(sessionId);
103
+ if (last?.length === length && last.version === version) return;
104
+ // A value a sensitive fill marked is stored as a marker here too. While
105
+ // the session has values not yet applied to its stored rows (rows made
106
+ // before the mark, compacted ones included), the rows that hold one are
107
+ // replaced in one transaction instead of only appending.
108
+ const redacted = redactSensitiveMessages(sessionId, messages);
109
+ if (version && last?.version !== version) {
110
+ await replaceSessionSearchMessages(sessionId, messages, redacted, (text) => redactSensitiveText(sessionId, text), {
111
+ signal,
112
+ });
113
+ } else {
114
+ await appendSessionSearchMessages(sessionId, redacted, { signal });
115
+ }
116
+ if (lastIngested.size >= LAST_INGESTED_LIMIT) lastIngested.clear();
117
+ lastIngested.set(sessionId, { length, version });
118
+ }
119
+
26
120
  /** Fire-and-forget: store a completed turn's transcript. Never throws. */
27
121
  export function ingestSessionTurn(session) {
28
122
  try {
@@ -30,30 +124,24 @@ export function ingestSessionTurn(session) {
30
124
  const messages = session?.messages;
31
125
  if (!sessionId || !Array.isArray(messages) || messages.length === 0) return;
32
126
  if (isAgentOnlySession(session)) return;
33
- // A turn always grows the transcript; an unchanged length means nothing
34
- // new. This is only a shortcut: the insert itself is idempotent.
35
- const length = messages.length;
36
- if (lastIngestedLength.get(sessionId) === length) return;
37
- appendSessionSearchMessages(sessionId, messages).then(
38
- () => {
39
- if (lastIngestedLength.size >= LAST_INGESTED_LIMIT) lastIngestedLength.clear();
40
- lastIngestedLength.set(sessionId, length);
41
- },
42
- (error) => logFailure(`ingest ${sessionId}`, error)
127
+ // The write runs after the session's earlier ones, so it keeps the
128
+ // transcript of this call: compaction may rewrite the live array before then.
129
+ const transcript = messages.slice();
130
+ serialized(sessionId, (signal) => storeTranscript(sessionId, transcript, signal)).catch(
131
+ reportUnlessClosed(`ingest ${sessionId}`)
43
132
  );
44
133
  } catch (error) {
45
134
  logFailure('ingest', error);
46
135
  }
47
136
  }
48
137
 
49
- /** Best-effort removal of a deleted session's search rows. Never throws. */
138
+ /** Best-effort removal of a deleted session's search rows, after the
139
+ * session's pending writes. Never throws. */
50
140
  export async function forgetSessionSearch(sessionId) {
51
- lastIngestedLength.delete(sessionId);
52
- try {
53
- await deleteSessionSearchMessages(sessionId);
54
- } catch (error) {
55
- logFailure(`delete ${sessionId}`, error);
56
- }
141
+ await serialized(sessionId, async (signal) => {
142
+ lastIngested.delete(sessionId);
143
+ await deleteSessionSearchMessages(sessionId, { signal });
144
+ }).catch(reportUnlessClosed(`delete ${sessionId}`));
57
145
  }
58
146
 
59
147
  async function loadStoreDeps() {
@@ -89,8 +177,10 @@ export async function backfillSessionSearch({ deps } = {}) {
89
177
  if (stamps.get(sessionId) === stamp) continue;
90
178
  const session = readSession(dir, file);
91
179
  if (session?.id && !isAgentOnlySession(session)) {
92
- await appendSessionSearchMessages(session.id, session.messages);
93
- await recordSessionSearchBackfillStamp(session.id, stamp);
180
+ await serialized(session.id, async () => {
181
+ await appendSessionSearchMessages(session.id, redactSensitiveMessages(session.id, session.messages));
182
+ await recordSessionSearchBackfillStamp(session.id, stamp);
183
+ });
94
184
  ingested += 1;
95
185
  }
96
186
  } catch (error) {
@@ -50,6 +50,95 @@ export async function runAbortable(signal, task, fallback) {
50
50
  });
51
51
  }
52
52
 
53
+ /** Resolves after `ms`, or rejects with the abort reason once `signal` aborts. */
54
+ export function sleepUnlessAborted(ms, signal) {
55
+ if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));
56
+ return new Promise((resolve, reject) => {
57
+ if (signal.aborted) return reject(abortReason(signal));
58
+ const onAbort = () => {
59
+ clearTimeout(timer);
60
+ reject(abortReason(signal));
61
+ };
62
+ const timer = setTimeout(() => {
63
+ signal.removeEventListener('abort', onAbort);
64
+ resolve();
65
+ }, ms);
66
+ signal.addEventListener('abort', onAbort, { once: true });
67
+ });
68
+ }
69
+
70
+ /**
71
+ * One operation that several callers await. `start(signal)` runs once, when
72
+ * the first caller that still wants it joins — a live signal, or none — so a
73
+ * caller that is already aborted starts nothing. Its signal aborts only when
74
+ * every caller that joined with a signal has aborted and no caller joined
75
+ * without one (such a caller keeps it running). The caller whose abort cancels
76
+ * the work waits for the work to settle, its cleanup included, so nothing it
77
+ * started outlives it; any other aborted caller stops waiting at once and
78
+ * leaves the work to the callers still on it. `onSettled(failed)` runs once
79
+ * the started work settles; `promise` is that work, null until started.
80
+ */
81
+ export function createSharedAbortable(start, { onSettled = null } = {}) {
82
+ const controller = new AbortController();
83
+ let waiting = 0;
84
+ let pinned = false;
85
+ let promise = null;
86
+ const begin = () => {
87
+ if (!promise) {
88
+ promise = Promise.resolve().then(() => start(controller.signal));
89
+ promise.then(
90
+ () => onSettled?.(false),
91
+ () => onSettled?.(true)
92
+ );
93
+ }
94
+ return promise;
95
+ };
96
+ return {
97
+ get promise() {
98
+ return promise;
99
+ },
100
+ join(signal) {
101
+ if (!signal) {
102
+ pinned = true;
103
+ return begin();
104
+ }
105
+ if (signal.aborted) return Promise.reject(abortReason(signal));
106
+ waiting += 1;
107
+ const work = begin();
108
+ return new Promise((resolve, reject) => {
109
+ const onAbort = () => {
110
+ waiting -= 1;
111
+ const reason = abortReason(signal);
112
+ if (waiting > 0 || pinned) {
113
+ reject(reason);
114
+ return;
115
+ }
116
+ controller.abort(reason);
117
+ work.then(
118
+ () => reject(reason),
119
+ () => reject(reason)
120
+ );
121
+ };
122
+ signal.addEventListener('abort', onAbort, { once: true });
123
+ work.then(
124
+ (value) => {
125
+ if (signal.aborted) return;
126
+ signal.removeEventListener('abort', onAbort);
127
+ waiting -= 1;
128
+ resolve(value);
129
+ },
130
+ (error) => {
131
+ if (signal.aborted) return;
132
+ signal.removeEventListener('abort', onAbort);
133
+ waiting -= 1;
134
+ reject(error);
135
+ }
136
+ );
137
+ });
138
+ },
139
+ };
140
+ }
141
+
53
142
  /**
54
143
  * Bound best-effort cleanup without cancelling the underlying work. The
55
144
  * detached promise remains observed and may still finish in the background.
@@ -42,7 +42,6 @@ export const MASCOT_EXPRESSIONS: readonly string[];
42
42
  export const MASCOT_INKS: Readonly<Record<string, string>>;
43
43
  export const MIXY_AVATAR: Readonly<MascotAvatar>;
44
44
  export const AGENT_AVATAR_STATES: readonly AgentAvatarState[];
45
- export const AGENT_AVATAR_LATER_STATES: readonly string[];
46
45
  export const AGENT_AVATAR_IMAGE_FILE: RegExp;
47
46
  export const AGENT_AVATAR_STATE_FILE: RegExp;
48
47
  export const AGENT_AVATAR_DIR: string;
@@ -45,8 +45,10 @@ const homeEvidenceCache = new Map();
45
45
 
46
46
  /** What `agentId`'s home thread (sessions/sess_home_<id>.json) holds:
47
47
  * whether it exists, whether it has the creation greeting, and whether the
48
- * user and the agent talked (any user message that is not a runtime turn,
49
- * or any assistant message). A missing or unreadable file holds nothing. */
48
+ * user talked in it: a message the user wrote (a user message that is not a
49
+ * runtime turn or the greeting). The agent's own messages never count: Mixy's
50
+ * heartbeat posts its reports there unasked. A missing or unreadable file
51
+ * holds nothing. */
50
52
  export function homeThreadEvidence(dataDir, agentId) {
51
53
  const sessionId = agentHomeSessionId(agentId);
52
54
  if (!dataDir || !sessionId) return NO_HOME;
@@ -69,7 +71,6 @@ export function homeThreadEvidence(dataDir, agentId) {
69
71
  let greeted = false;
70
72
  let chatted = false;
71
73
  for (const message of messages) {
72
- if (message?.role === 'assistant') chatted = true;
73
74
  if (message?.role !== 'user') continue;
74
75
  const text = messageText(message.content);
75
76
  if (CREATION_GREETING.test(text)) greeted = true;
@@ -0,0 +1,56 @@
1
+ // Bot-owned work: the live work in a session that a bot sent. A bot's accepted
2
+ // `session` send or create (session-runtime/agent-task-sessions.mjs) is the
3
+ // bot's work in that session from its acceptance until it settles or is
4
+ // cancelled — whether or not it reports back (`notify: false`, a report watch
5
+ // that could not be stored). A turn nobody sent (the user typed it in the
6
+ // task session) is owned by no bot, so it never reads as a bot being busy.
7
+ // Process-wide and dependency-free like delegated-approval-host.mjs. The
8
+ // registry is only this process's cache: what keeps the work owned across a
9
+ // restart is stored apart (a reporting send's watch, any other send's record
10
+ // of its work, both in agent-home-delivery.mjs) and re-noted by
11
+ // agent-task-sessions.mjs recover. A change of which bots own a session's work
12
+ // is announced (session-activity-signal.mjs onSessionBotWorkChange), so the
13
+ // session catalog, which stamps them on each row, re-lists.
14
+ import { emitSessionBotWorkChange } from './session-activity-signal.mjs';
15
+
16
+ const KEY = Symbol.for('mixdog.botOwnedWork.v1');
17
+
18
+ /** Map run id → `{ sessionId, agentId }`. */
19
+ function registry() {
20
+ globalThis[KEY] ??= new Map();
21
+ return globalThis[KEY];
22
+ }
23
+
24
+ /** How many owned works of `agentId` run in `sessionId`. */
25
+ function worksOf(sessionId, agentId) {
26
+ let count = 0;
27
+ for (const work of registry().values()) if (work.sessionId === sessionId && work.agentId === agentId) count += 1;
28
+ return count;
29
+ }
30
+
31
+ /** Every session with owned work and the bots owning it (each once, in noting
32
+ * order): Map session → agent ids. */
33
+ export function botWorkBySession() {
34
+ const bySession = new Map();
35
+ for (const { sessionId, agentId } of registry().values()) {
36
+ const agents = bySession.get(sessionId) ?? [];
37
+ if (!agents.includes(agentId)) agents.push(agentId);
38
+ bySession.set(sessionId, agents);
39
+ }
40
+ return bySession;
41
+ }
42
+
43
+ /** Agent `agentId`'s submission `runId` to `sessionId` was accepted. */
44
+ export function noteBotWork({ runId, sessionId, agentId }) {
45
+ if (!runId || !sessionId || !agentId || registry().has(String(runId))) return;
46
+ registry().set(String(runId), { sessionId: String(sessionId), agentId: String(agentId) });
47
+ if (worksOf(String(sessionId), String(agentId)) === 1) emitSessionBotWorkChange(sessionId);
48
+ }
49
+
50
+ /** Submission `runId` settled or was cancelled: its work is nobody's now. */
51
+ export function endBotWork(runId) {
52
+ const work = registry().get(String(runId));
53
+ if (!work) return;
54
+ registry().delete(String(runId));
55
+ if (worksOf(work.sessionId, work.agentId) === 0) emitSessionBotWorkChange(work.sessionId);
56
+ }
@@ -1,7 +1,7 @@
1
1
  import { createWriteStream, rmSync } from 'node:fs';
2
2
  import { Transform } from 'node:stream';
3
3
  import { pipeline } from 'node:stream/promises';
4
- import { sleep } from './sleep.mjs';
4
+ import { sleepUnlessAborted } from './abort-race.mjs';
5
5
 
6
6
  export const MAX_NATIVE_BINARY_DOWNLOAD_BYTES = 256 * 1024 * 1024;
7
7
  const DEFAULT_RETRY_DELAYS_MS = [1000, 3000, 9000];
@@ -118,13 +118,17 @@ export async function downloadToFileWithRetry(
118
118
  timeoutMs = 180_000,
119
119
  retryDelaysMs = DEFAULT_RETRY_DELAYS_MS,
120
120
  onRetry = null,
121
+ // Cancels the request, its body and any retry wait; rejects with its reason.
122
+ signal = null,
121
123
  } = {}
122
124
  ) {
123
125
  const { maximum, expected } = downloadBounds(maxBytes, expectedBytes, label);
124
126
  let lastError;
125
127
  for (let attempt = 0; attempt <= retryDelaysMs.length; attempt += 1) {
128
+ signal?.throwIfAborted();
126
129
  try {
127
- const response = await fetchFn(url, { signal: AbortSignal.timeout(timeoutMs) });
130
+ const timeout = AbortSignal.timeout(timeoutMs);
131
+ const response = await fetchFn(url, { signal: signal ? AbortSignal.any([signal, timeout]) : timeout });
128
132
  try {
129
133
  if (!response.ok) throw new DownloadHttpError(response.status, httpLabel, url);
130
134
  return await streamResponseToFile(response, destPath, {
@@ -142,12 +146,13 @@ export async function downloadToFileWithRetry(
142
146
  }
143
147
  }
144
148
  } catch (error) {
149
+ signal?.throwIfAborted();
145
150
  lastError = error;
146
151
  if (error instanceof DownloadHttpError && error.terminal) throw error;
147
152
  const delayMs = retryDelaysMs[attempt];
148
153
  if (delayMs === undefined) break;
149
154
  onRetry?.({ attempt: attempt + 1, delayMs, error });
150
- await sleep(delayMs);
155
+ await sleepUnlessAborted(delayMs, signal);
151
156
  }
152
157
  }
153
158
  throw lastError;
@@ -150,23 +150,12 @@ function readConfigRawCached() {
150
150
  return raw;
151
151
  }
152
152
 
153
- // Canonical on-disk channel shape. Provider-specific ids are durable. Schedule
154
- // arrays moved to the scheduler DB and prompt injection moved to runtime hooks,
155
- // so neither belongs in config anymore.
153
+ // Canonical on-disk channel shape. Schedules live in the scheduler DB and
154
+ // prompt injection moved to runtime hooks, so neither belongs in config.
156
155
  export function canonicalizeStoredChannelsConfig(value = {}) {
157
156
  const next = isPlainObject(value) ? { ...value } : {};
158
157
  delete next.promptInjection;
159
158
  delete next.schedules;
160
- delete next.nonInteractive;
161
- delete next.interactive;
162
-
163
- // Messaging (Discord/Telegram) is retired: scrub the provider selector and
164
- // per-provider routing sections from stored config.
165
- delete next.backend;
166
- delete next.provider;
167
- delete next.discord;
168
- delete next.telegram;
169
- delete next.channel;
170
159
  return next;
171
160
  }
172
161
 
@@ -1,15 +1,25 @@
1
1
  // What a multi-account connector server tells the model about itself (MCP
2
- // `instructions`): the account it acts for, and to ask which one when the
3
- // user did not say and several are connected. The deferred-tool manifest
4
- // (context/deferred-tools.mjs) reads the leading "Account: <address>" back.
2
+ // `instructions`), in one line: the connected accounts, and that each tool
3
+ // takes `account` (asked of the user when several are connected and none was
4
+ // named). An account that cannot be used right now is listed with why.
5
5
 
6
- /** The MCP instructions of a `service` server acting for `account` ('' when unknown). */
7
- export function accountInstructions(service, account) {
8
- if (!account) return undefined;
9
- return `Account: ${account}. This server acts only for this ${service} account; other ${service} accounts are separate servers. If several are connected and the user did not name one, ask which account before acting.`;
6
+ /** The MCP instructions naming `accounts` (`{ email, note? }`, `note` a short
7
+ * reason it is unusable such as "needs sign-in"; one without an address is
8
+ * left out), or undefined when none has one. */
9
+ export function connectorAccountsInstructions(accounts) {
10
+ const named = (Array.isArray(accounts) ? accounts : []).filter((account) => account?.email);
11
+ if (!named.length) return undefined;
12
+ const list = named.map(({ email, note }) => (note ? `${email} (${note})` : email)).join(', ');
13
+ return named.length === 1
14
+ ? `Accounts: ${list}. \`account\` may be left out.`
15
+ : `Accounts: ${list}. Pass \`account\`; if the user named none and there are several, ask which.`;
10
16
  }
11
17
 
12
- /** The account a server's instructions name, or ''. */
13
- export function accountOfInstructions(instructions) {
14
- return /^Account: (\S+?)\. /.exec(String(instructions || ''))?.[1] || '';
18
+ /** The account addresses instructions from connectorAccountsInstructions name. */
19
+ export function accountsOfInstructions(instructions) {
20
+ const list = /^Accounts: (.+?)\. (?:`account`|Pass `account`)/.exec(String(instructions || ''))?.[1] || '';
21
+ return list
22
+ .split(', ')
23
+ .map((part) => part.replace(/ \(.*\)$/, '').trim())
24
+ .filter(Boolean);
15
25
  }
@@ -51,3 +51,32 @@ export function gmailAddress(value: unknown): string;
51
51
  export function gmailAppPasswordValue(value: unknown): string;
52
52
  export function gmailAppPasswordAccount(cfg: unknown): string;
53
53
  export function gmailAppPasswordServerInput(name: string, email: string, password: string): Record<string, unknown>;
54
+
55
+ /** One stored account of a multi-account connector server. */
56
+ export interface ConnectorAccountEntry {
57
+ name: string;
58
+ url: string;
59
+ headers?: Record<string, string>;
60
+ oauthClientId?: string;
61
+ /** `false`: the account is kept but neither connected nor called. */
62
+ enabled?: boolean;
63
+ }
64
+ export function connectorAccountsUrl(id: string): string;
65
+ export function connectorAccountsOf(cfg: unknown): ConnectorAccountEntry[];
66
+ export function connectorAccountServer(
67
+ cfg: Record<string, unknown>,
68
+ account: ConnectorAccountEntry
69
+ ): Record<string, unknown>;
70
+ export function connectorAccountServers(cfg: unknown): Record<string, unknown>[];
71
+ export function connectorAccountEnabled(account: ConnectorAccountEntry): boolean;
72
+ export function connectorLiveAccountServers(cfg: unknown): Record<string, unknown>[];
73
+ export function withConnectorAccountEnabled(
74
+ cfg: Record<string, unknown>,
75
+ name: string,
76
+ enabled: boolean
77
+ ): Record<string, unknown>;
78
+ export function connectorAccountEntry(name: string, cfg: Record<string, unknown>): ConnectorAccountEntry;
79
+ export function mergeConnectorAccountServers(servers: Record<string, unknown>): {
80
+ servers: Record<string, unknown>;
81
+ merged: { connector: string; name: string; from: string[] }[];
82
+ };
@@ -190,6 +190,156 @@ export function connectorMultiAccount(entry) {
190
190
  return entry?.backend?.kind === 'oauth' && entry.backend.oauthClient === 'google';
191
191
  }
192
192
 
193
+ // ── One server per multi-account connector ──────────────────────────────────
194
+ // A multi-account connector is ONE MCP server (`gmail`, `google-calendar`,
195
+ // `google-drive`) holding its accounts:
196
+ // { type: 'http', url: connectorAccountsUrl(id), connector: id,
197
+ // accounts: [{ name, url, headers?, oauthClientId? }, …] }
198
+ // Each account keeps the transport it had as a server of its own (the Google
199
+ // sign-in URL its tokens are stored under, or the app-password address and
200
+ // account header), so its keychain entries are untouched. Account names share
201
+ // the server-name space (`gmail`, `gmail-2`…): a name-based call (remove, sign
202
+ // in, sign out) that names an account acts on that account. The server is
203
+ // answered in-process (connectors/connector-accounts-mcp.mjs): one tool set
204
+ // with an `account` argument.
205
+ const ACCOUNT_FIELDS = ['url', 'headers', 'oauthClientId'];
206
+
207
+ /** The reserved (never resolvable) address of connector `id`'s accounts server. */
208
+ export function connectorAccountsUrl(id) {
209
+ return `https://connectors.mixdog.invalid/${encodeURIComponent(id)}/mcp`;
210
+ }
211
+
212
+ const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
213
+
214
+ /** The stored accounts of a multi-account connector server, [] for any other
215
+ * config (a legacy one-account server included). */
216
+ export function connectorAccountsOf(cfg) {
217
+ if (!isRecord(cfg) || !Array.isArray(cfg.accounts)) return [];
218
+ if (!connectorMultiAccount(connectorById(connectorOfServer(cfg)))) return [];
219
+ return cfg.accounts.filter(
220
+ (account) =>
221
+ isRecord(account) && typeof account.name === 'string' && account.name && typeof account.url === 'string'
222
+ );
223
+ }
224
+
225
+ /** The one-account server config of `account` of server `cfg` (the shape the
226
+ * account had as a server of its own), named by the account. */
227
+ export function connectorAccountServer(cfg, account) {
228
+ const out = { name: account.name, type: 'http' };
229
+ for (const field of ACCOUNT_FIELDS) if (account[field] !== undefined) out[field] = account[field];
230
+ out.connector = cfg.connector;
231
+ return out;
232
+ }
233
+
234
+ /** Every account of `cfg` as its one-account server config. */
235
+ export function connectorAccountServers(cfg) {
236
+ return connectorAccountsOf(cfg).map((account) => connectorAccountServer(cfg, account));
237
+ }
238
+
239
+ /** Whether stored account `account` is switched on (`enabled: false` is off). */
240
+ export const connectorAccountEnabled = (account) => account.enabled !== false;
241
+
242
+ /** The switched-on accounts of `cfg` as their one-account server configs: the
243
+ * ones the server connects and routes calls to. */
244
+ export function connectorLiveAccountServers(cfg) {
245
+ return connectorAccountsOf(cfg)
246
+ .filter(connectorAccountEnabled)
247
+ .map((account) => connectorAccountServer(cfg, account));
248
+ }
249
+
250
+ /**
251
+ * Server `cfg` with its account `name` switched on or off. Off flags the
252
+ * account (`enabled: false`) and switches the server off with its last
253
+ * switched-on account. On drops the flag (a switched-on account carries none)
254
+ * and switches the server on. Every other entry stays as stored.
255
+ */
256
+ export function withConnectorAccountEnabled(cfg, name, enabled) {
257
+ const accounts = cfg.accounts.map((account) => {
258
+ if (!isRecord(account) || account.name !== name) return account;
259
+ const { enabled: _enabled, ...rest } = account;
260
+ return enabled ? rest : { ...rest, enabled: false };
261
+ });
262
+ const next = { ...cfg, accounts };
263
+ if (enabled) return { ...next, enabled: true };
264
+ return connectorAccountsOf(next).some(connectorAccountEnabled) ? next : { ...next, enabled: false };
265
+ }
266
+
267
+ /** The stored account entry of one-account config `cfg` under `name`. */
268
+ export function connectorAccountEntry(name, cfg) {
269
+ const out = { name };
270
+ for (const field of ACCOUNT_FIELDS) if (cfg?.[field] !== undefined) out[field] = cfg[field];
271
+ return out;
272
+ }
273
+
274
+ const naturalOrder = (a, b) => a.localeCompare(b, 'en', { numeric: true });
275
+
276
+ /**
277
+ * Merges every multi-account connector's servers into one server holding the
278
+ * accounts: legacy one-account servers (`gmail`, `gmail-2`…, Calendar and
279
+ * Drive alike) and any second accounts server. The server keeps the name of
280
+ * an existing accounts server, else the connector id when a server has it,
281
+ * else the first name; its other fields come from that server. Accounts keep
282
+ * their names, transports and order (that server's first); a second entry
283
+ * for the same name or app-password address is dropped. The merged server is
284
+ * off only when every merged one was; otherwise the accounts of a merged
285
+ * server that was off stay off (`enabled: false` on the account), and an
286
+ * account's own `enabled: false` is kept. A server left with no switched-on
287
+ * account is off. Idempotent: `merged` is empty when
288
+ * nothing changed. Returns `{ servers, merged: [{ connector, name, from }] }`.
289
+ */
290
+ export function mergeConnectorAccountServers(input) {
291
+ const servers = isRecord(input) ? { ...input } : {};
292
+ const merged = [];
293
+ for (const entry of CONNECTOR_CATALOG.filter(connectorMultiAccount)) {
294
+ const names = Object.keys(servers)
295
+ .filter((name) => isRecord(servers[name]) && connectorOfServer(servers[name]) === entry.id)
296
+ .sort(naturalOrder);
297
+ if (!names.length) continue;
298
+ const grouped = names.filter((name) => Array.isArray(servers[name].accounts));
299
+ if (names.length === 1 && grouped.length === 1) continue;
300
+ const target = grouped[0] ?? (names.includes(entry.id) ? entry.id : names[0]);
301
+ const order = [target, ...names.filter((name) => name !== target)];
302
+ const off = order.every((name) => input[name].enabled === false);
303
+ const accounts = [];
304
+ const seenNames = new Set();
305
+ const seenAddresses = new Set();
306
+ for (const name of order) {
307
+ const cfg = servers[name];
308
+ const own = Array.isArray(cfg.accounts) ? connectorAccountsOf(cfg) : [connectorAccountEntry(name, cfg)];
309
+ // A merged server that was off keeps its accounts off, unless the whole
310
+ // connector is off (then re-enabling it brings every account back).
311
+ const serverOff = !off && cfg.enabled === false;
312
+ for (const account of own) {
313
+ const address = gmailAppPasswordAccount(connectorAccountServer(cfg, account));
314
+ if (seenNames.has(account.name) || (address && seenAddresses.has(address))) continue;
315
+ seenNames.add(account.name);
316
+ if (address) seenAddresses.add(address);
317
+ accounts.push(serverOff ? { ...account, enabled: false } : account);
318
+ }
319
+ }
320
+ const {
321
+ type: _type,
322
+ url: _url,
323
+ headers: _headers,
324
+ oauthClientId: _client,
325
+ accounts: _accounts,
326
+ enabled: _enabled,
327
+ ...rest
328
+ } = servers[target];
329
+ for (const name of order) delete servers[name];
330
+ servers[target] = {
331
+ ...rest,
332
+ type: 'http',
333
+ url: connectorAccountsUrl(entry.id),
334
+ connector: entry.id,
335
+ accounts,
336
+ ...(off || (accounts.length > 0 && !accounts.some(connectorAccountEnabled)) ? { enabled: false } : {}),
337
+ };
338
+ merged.push({ connector: entry.id, name: target, from: order });
339
+ }
340
+ return { servers, merged };
341
+ }
342
+
193
343
  /** Whether a new connection of `entry` goes by app password only (Gmail). */
194
344
  export function connectorAppPasswordOnly(entry) {
195
345
  return entry?.appPasswordOnly === true;