mixdog 1.3.4 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (472) hide show
  1. package/LICENSES/oh-my-pi-MIT.txt +23 -0
  2. package/NOTICE.md +17 -2
  3. package/README.md +11 -0
  4. package/package.json +3 -11
  5. package/scripts/daemon-stub.mjs +1 -1
  6. package/scripts/embed-google-oauth-client.mjs +49 -0
  7. package/scripts/gate-local.mjs +4 -2
  8. package/scripts/lib/test-spawn-ledger.mjs +46 -1
  9. package/scripts/native-release-status.mjs +2 -2
  10. package/scripts/recall-history-cases.json +1 -1
  11. package/scripts/suite-health.mjs +10 -1
  12. package/scripts/test.mjs +12 -11
  13. package/src/agents/reviewer/AGENT.md +7 -11
  14. package/src/agents/reviewer/agent.json +1 -1
  15. package/src/defaults/shipped-history.json +25 -1
  16. package/src/defaults/skills/browser-use/SKILL.md +77 -17
  17. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  18. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  19. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  20. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  21. package/src/defaults/skills/computer-use/SKILL.md +33 -9
  22. package/src/defaults/skills/setup/SKILL.md +29 -2
  23. package/src/defaults/skills/setup/references/actions.md +117 -28
  24. package/src/defaults/skills/setup/references/surfaces.md +77 -35
  25. package/src/defaults/skills/setup-guide/SKILL.md +3 -3
  26. package/src/defaults/skills/visualize/SKILL.md +2 -1
  27. package/src/lib/keychain-cjs.cjs +359 -29
  28. package/src/lib/rules-builder.cjs +10 -8
  29. package/src/rules/agent/AGENT.md +6 -6
  30. package/src/rules/agent-mode/AGENT_MODE.md +10 -2
  31. package/src/rules/agent-mode/MIXY.md +12 -0
  32. package/src/rules/lead/LEAD.md +18 -20
  33. package/src/rules/shared/05-tool-calls.md +5 -6
  34. package/src/rules/shared/10-tool-workflow.md +2 -0
  35. package/src/rules/shared/60-verification.md +7 -5
  36. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  37. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
  38. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  39. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  40. package/src/runtime/agent/orchestrator/config.mjs +34 -0
  41. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  42. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
  43. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
  44. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  45. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +177 -73
  46. package/src/runtime/agent/orchestrator/mcp/client.mjs +25 -2
  47. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
  48. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
  49. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
  50. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
  51. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
  52. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
  53. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +230 -137
  54. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +64 -7
  55. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
  56. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  59. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  60. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +160 -69
  61. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
  63. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  64. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
  65. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
  66. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
  67. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
  68. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
  69. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +142 -104
  70. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
  73. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
  74. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  75. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -6
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
  78. package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
  79. package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
  80. package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
  81. package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
  82. package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
  83. package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
  84. package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
  85. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
  86. package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
  87. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
  88. package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
  89. package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
  90. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
  91. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
  92. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
  93. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
  94. package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
  95. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
  97. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  98. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
  99. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  100. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  101. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  103. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +19 -1
  104. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  109. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  110. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  111. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  113. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
  114. package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
  115. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  116. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  117. package/src/runtime/agent/orchestrator/providers/registry.mjs +43 -3
  118. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  119. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  120. package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
  121. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
  122. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  123. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  124. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  125. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  126. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
  127. package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
  128. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
  129. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  130. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  131. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  132. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
  133. package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
  134. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
  135. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
  136. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
  137. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
  138. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
  139. package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
  140. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
  141. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +148 -25
  142. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  143. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  144. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -0
  145. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +47 -9
  146. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -2
  147. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  148. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +14 -2
  149. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
  151. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
  152. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +11 -2
  153. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
  154. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +51 -3
  155. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
  156. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -2
  157. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  158. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +14 -4
  159. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  160. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  161. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  163. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +23 -5
  164. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
  165. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  166. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  167. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
  168. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  169. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
  170. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
  171. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  172. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  173. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
  174. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  175. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  176. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
  177. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
  178. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  179. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
  180. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
  182. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
  183. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  184. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
  185. package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
  186. package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
  187. package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
  188. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
  189. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  190. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
  191. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
  192. package/src/runtime/agent/orchestrator/session/routine-result.mjs +47 -11
  193. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
  194. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  195. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  196. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  197. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  198. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  199. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  200. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  201. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  202. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  203. package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
  204. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +133 -18
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
  206. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
  207. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
  208. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  209. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
  210. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  211. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  212. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  213. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
  214. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
  215. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +25 -2
  216. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  217. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  218. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  219. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  222. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  224. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
  225. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
  226. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  227. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  228. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  229. package/src/runtime/browser-bridge/client.mjs +73 -8
  230. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  231. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  232. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  233. package/src/runtime/channels/lib/config.mjs +6 -61
  234. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  235. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  236. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  237. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  238. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  239. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  240. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  241. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  242. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  243. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  244. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  245. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  246. package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
  247. package/src/runtime/channels/lib/scheduler.mjs +135 -154
  248. package/src/runtime/channels/lib/schedules-db.mjs +83 -11
  249. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  250. package/src/runtime/channels/lib/tool-dispatch.mjs +14 -2
  251. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  252. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  253. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  254. package/src/runtime/channels/lib/worker-main.mjs +19 -34
  255. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  256. package/src/runtime/computer-bridge/actions.mjs +9 -1
  257. package/src/runtime/computer-bridge/client.mjs +180 -8
  258. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  259. package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
  260. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  261. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  262. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  263. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  264. package/src/runtime/connectors/google-mcp.mjs +10 -12
  265. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  266. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  267. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  268. package/src/runtime/office/com/com-adapter.mjs +17 -1
  269. package/src/runtime/office/com/office-com-host.ps1 +51 -1
  270. package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
  271. package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
  272. package/src/runtime/office/pdf/document-preview.mjs +326 -8
  273. package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
  274. package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
  275. package/src/runtime/office/portable/portable-soffice.mjs +45 -5
  276. package/src/runtime/session-search/session-search-db.mjs +73 -9
  277. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  278. package/src/runtime/shared/abort-race.mjs +89 -0
  279. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  280. package/src/runtime/shared/agent-kind.mjs +4 -3
  281. package/src/runtime/shared/bot-hops.mjs +62 -2
  282. package/src/runtime/shared/bounded-download.mjs +8 -3
  283. package/src/runtime/shared/config.mjs +51 -35
  284. package/src/runtime/shared/connector-accounts.mjs +20 -10
  285. package/src/runtime/shared/connector-catalog.d.mts +33 -2
  286. package/src/runtime/shared/connector-catalog.mjs +166 -8
  287. package/src/runtime/shared/connector-triggers.mjs +22 -19
  288. package/src/runtime/shared/defaults-separation.mjs +2 -14
  289. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  290. package/src/runtime/shared/developer-options.mjs +32 -1
  291. package/src/runtime/shared/err-text.mjs +24 -15
  292. package/src/runtime/shared/file-permissions.d.mts +0 -8
  293. package/src/runtime/shared/file-permissions.mjs +0 -11
  294. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  295. package/src/runtime/shared/google-oauth-client.generated.json +4 -0
  296. package/src/runtime/shared/google-oauth-client.mjs +34 -9
  297. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  298. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  299. package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
  300. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
  301. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
  302. package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
  303. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  304. package/src/runtime/shared/llm/well-formed.mjs +66 -0
  305. package/src/runtime/shared/mixy-presence.mjs +22 -0
  306. package/src/runtime/shared/orchestration.d.mts +2 -0
  307. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  308. package/src/runtime/shared/profile-config.cjs +1 -1
  309. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  310. package/src/runtime/shared/prompt-origin.mjs +21 -0
  311. package/src/runtime/shared/provider-accounts.mjs +231 -59
  312. package/src/runtime/shared/provider-api-key.mjs +19 -8
  313. package/src/runtime/shared/provider-display-name.mjs +29 -0
  314. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  315. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  316. package/src/runtime/shared/routine-trigger.mjs +52 -0
  317. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  318. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  319. package/src/runtime/shared/session-activity-signal.mjs +34 -1
  320. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  321. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  322. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  323. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  324. package/src/runtime/shared/tool-result-summary.mjs +1 -14
  325. package/src/runtime/shared/transcript-metadata.mjs +38 -0
  326. package/src/runtime/shared/user-data-guard.mjs +30 -2
  327. package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
  328. package/src/runtime/shared/worktrees/test-support.mjs +9 -4
  329. package/src/runtime/terminal-bridge/client.mjs +10 -1
  330. package/src/runtime/visualize-bridge/client.mjs +27 -8
  331. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  332. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  333. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  334. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  335. package/src/session-runtime/agent-home-delivery.mjs +486 -179
  336. package/src/session-runtime/agent-room-delivery.mjs +28 -1
  337. package/src/session-runtime/agent-task-sessions.mjs +561 -199
  338. package/src/session-runtime/boot/apis.mjs +9 -0
  339. package/src/session-runtime/boot/begin.mjs +3 -2
  340. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  341. package/src/session-runtime/boot/providers.mjs +3 -0
  342. package/src/session-runtime/boot/tools.mjs +8 -1
  343. package/src/session-runtime/bot-chain.mjs +32 -0
  344. package/src/session-runtime/bot-status.mjs +7 -4
  345. package/src/session-runtime/channel-config-api.mjs +63 -17
  346. package/src/session-runtime/config-lifecycle.mjs +6 -0
  347. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  348. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
  349. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
  350. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
  351. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
  352. package/src/session-runtime/internal-tool-executor.mjs +4 -0
  353. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  354. package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
  355. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  356. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  357. package/src/session-runtime/mcp-glue/server-catalog.mjs +61 -3
  358. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  359. package/src/session-runtime/mixy-heartbeat.mjs +927 -0
  360. package/src/session-runtime/model-route/route-tuning.mjs +10 -6
  361. package/src/session-runtime/notification-bus.mjs +31 -117
  362. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  363. package/src/session-runtime/notification-listeners.mjs +76 -0
  364. package/src/session-runtime/owned-automation-run.mjs +35 -13
  365. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  366. package/src/session-runtime/provider-auth/login.mjs +7 -0
  367. package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
  368. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  369. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  370. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  371. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  372. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  373. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  374. package/src/session-runtime/routine-home-turn.mjs +33 -6
  375. package/src/session-runtime/routine-owner.mjs +40 -9
  376. package/src/session-runtime/schedule-session-run.mjs +7 -3
  377. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  378. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
  379. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
  380. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  381. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  382. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  383. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  384. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  385. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  386. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  387. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  388. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  389. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  390. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  391. package/src/session-runtime/services/channel-admin.mjs +73 -31
  392. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  393. package/src/session-runtime/services/channel-worker.mjs +1 -1
  394. package/src/session-runtime/services/provider-admin.mjs +128 -25
  395. package/src/session-runtime/services/schedule-access.mjs +14 -0
  396. package/src/session-runtime/services/seeds.mjs +0 -28
  397. package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
  398. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  399. package/src/session-runtime/session-create/session-options.mjs +8 -4
  400. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  401. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  402. package/src/session-runtime/settings-system-api.mjs +28 -1
  403. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  404. package/src/session-runtime/setup-tool/executor.mjs +187 -29
  405. package/src/session-runtime/setup-tool/extended-actions.mjs +33 -2
  406. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  407. package/src/session-runtime/setup-tool/settings-contract.mjs +68 -6
  408. package/src/session-runtime/setup-tool/tool-defs.mjs +92 -6
  409. package/src/session-runtime/task-session-route.mjs +99 -0
  410. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  411. package/src/session-runtime/tool-policy/policy-stage.mjs +21 -4
  412. package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
  413. package/src/session-runtime/tool-surface.mjs +4 -0
  414. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  415. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
  416. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  417. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
  418. package/src/session-runtime/workspace/transitions.mjs +10 -2
  419. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  420. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  421. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  422. package/src/standalone/channel-transport.mjs +1 -1
  423. package/src/standalone/daemon-channels-loader.mjs +10 -8
  424. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  425. package/src/standalone/daemon-session-control/request.mjs +51 -0
  426. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  427. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  428. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  429. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  430. package/src/standalone/daemon-session-control.mjs +161 -157
  431. package/src/standalone/daemon.mjs +33 -2
  432. package/src/standalone/session-client.mjs +7 -3
  433. package/src/standalone/session-protocol.mjs +10 -0
  434. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
  435. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  436. package/src/standalone/session-service/quota-resume.mjs +3 -0
  437. package/src/standalone/session-service/retention/busy.mjs +65 -1
  438. package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
  439. package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
  440. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  441. package/src/standalone/session-service.mjs +13 -0
  442. package/src/standalone/session-tool-output.mjs +21 -10
  443. package/src/tui/app/model-options.mjs +4 -27
  444. package/src/tui/dist/index.mjs +81 -65
  445. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  446. package/src/tui/session/initial-state.mjs +3 -0
  447. package/src/tui/session/notice-surface.mjs +23 -1
  448. package/src/tui/session/queue-helpers.mjs +3 -42
  449. package/src/tui/session/session-api/integrations.mjs +1 -0
  450. package/src/tui/session/session-api/lifecycle.mjs +8 -0
  451. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  452. package/src/tui/session/session-api/routes-write.mjs +5 -1
  453. package/src/tui/session/session-api/routes.mjs +1 -0
  454. package/src/tui/session/session-api/settings.mjs +30 -2
  455. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  456. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  457. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  458. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  459. package/src/tui/session/session-flow/drain.mjs +118 -149
  460. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  461. package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
  462. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  463. package/src/tui/session/tool-approval-request.mjs +39 -0
  464. package/src/tui/session/tool-approval-settle.mjs +62 -0
  465. package/src/tui/session/tool-approval.mjs +69 -115
  466. package/src/tui/session/tui-steering-persist.mjs +8 -1
  467. package/src/tui/session/turn-stream.mjs +36 -3
  468. package/src/tui/session/turn.mjs +23 -2
  469. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  470. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  471. package/scripts/code-graph-description-contract.mjs +0 -188
  472. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -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;
@@ -20,16 +20,76 @@ export const hopOf = (value) => (Number.isInteger(value) && value > 0 ? value :
20
20
  * depth: bot-descended work resumes where it was, independent work at 0. */
21
21
  export const sendDepth = ({ hop, target } = {}) => Math.max(0, hopOf(hop) - (target === 'session' ? 1 : 0));
22
22
 
23
- /** Stamp the hop of the submission that starts the turn (else 0) on `session`. */
23
+ // The bots of a chain (`botChain`): the agent ids that handed the work on,
24
+ // oldest first, the sender last. A bot's send carries the chain of its turn
25
+ // plus itself in the transcript meta of the message it delivers (like `hop`);
26
+ // the turn that message starts or joins is in that chain (`turnBotChain`), and
27
+ // a send or spawn from it may never reach a bot already in it, nor itself
28
+ // (assertBotChainTarget). A turn the user, a routine or a notification
29
+ // started begins a chain of its own.
30
+ export const botChainOf = (value) =>
31
+ Array.isArray(value) ? [...new Set(value.filter((id) => typeof id === 'string' && id))] : [];
32
+
33
+ // The chain of a submission the runtime takes without transcript meta of its
34
+ // own (a group-room member's turn, submitted by the desktop room host under
35
+ // the send's submission id), recorded by the sender before it is submitted.
36
+ const chainsBySubmission = new Map();
37
+ const MAX_SUBMISSION_CHAINS = 1000;
38
+
39
+ /** Record that submission `submissionId` starts or joins a turn of `chain`. */
40
+ export function rememberSubmissionBotChain(submissionId, chain) {
41
+ const id = String(submissionId || '').trim();
42
+ const bots = botChainOf(chain);
43
+ if (!id || !bots.length) return;
44
+ chainsBySubmission.set(id, bots);
45
+ while (chainsBySubmission.size > MAX_SUBMISSION_CHAINS) {
46
+ chainsBySubmission.delete(chainsBySubmission.keys().next().value);
47
+ }
48
+ }
49
+
50
+ /** The chain a submission's transcript meta carries, else the one recorded for its ids. */
51
+ function submissionBotChain(transcriptMeta) {
52
+ const carried = botChainOf(transcriptMeta?.botChain);
53
+ if (carried.length) return carried;
54
+ const merged = Array.isArray(transcriptMeta?.submissionIds) ? transcriptMeta.submissionIds : [];
55
+ const ids = [transcriptMeta?.checkpointId, ...merged];
56
+ return botChainOf(ids.flatMap((id) => chainsBySubmission.get(String(id || '')) || []));
57
+ }
58
+
59
+ /** Stamp the hop and bot chain of the submission that starts the turn (else 0 and none) on `session`. */
24
60
  export function stampTurnHop(session, transcriptMeta) {
25
61
  session.turnHop = hopOf(transcriptMeta?.hop);
62
+ session.turnBotChain = submissionBotChain(transcriptMeta);
26
63
  }
27
64
 
28
- /** A bot's message steered into the running turn raises the turn's hop. */
65
+ /** A bot's message steered into the running turn raises the turn's hop and joins its chain. */
29
66
  export function raiseTurnHop(session, transcriptMeta) {
30
67
  const hop = hopOf(transcriptMeta?.hop);
31
68
  if (session && hop > hopOf(session.turnHop)) session.turnHop = hop;
69
+ const chain = submissionBotChain(transcriptMeta);
70
+ if (session && chain.length) session.turnBotChain = botChainOf([...turnBotChainOf(session), ...chain]);
32
71
  }
33
72
 
34
73
  /** The hop of the turn `session` is in. */
35
74
  export const turnHopOf = (session) => hopOf(session?.turnHop);
75
+
76
+ /** The bot chain of the turn `session` is in. */
77
+ export const turnBotChainOf = (session) => botChainOf(session?.turnBotChain);
78
+
79
+ /** The one check of every bot-to-bot hand-off (session send, a room seat,
80
+ * a contact agent spawn): `target`, the bot the work would reach, is
81
+ * neither `self` (the bot handing it on) nor already in `chain`. */
82
+ export function assertBotChainTarget(target, { chain = [], self = '' } = {}, prefix = 'session') {
83
+ if (!target) return;
84
+ if (target === self) {
85
+ throw new Error(
86
+ `${prefix}: the contact agent "${target}" cannot hand work to itself; do it yourself or delegate to a sub-agent`
87
+ );
88
+ }
89
+ if (chain.includes(target)) {
90
+ const path = [...chain, ...(self && chain.at(-1) !== self ? [self] : []), target];
91
+ throw new Error(
92
+ `${prefix}: the contact agent "${target}" is already in this delegation chain (${path.join(' → ')}); do not hand the work back, answer or delegate to a sub-agent instead`
93
+ );
94
+ }
95
+ }
@@ -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;
@@ -35,6 +35,7 @@ const {
35
35
  setSecret: _setSecret,
36
36
  deleteSecret: _deleteSecret,
37
37
  hasSecret: _hasSecret,
38
+ hasFallbackSecret: _hasFallbackSecret,
38
39
  setSecretAsync: _setSecretAsyncImpl,
39
40
  deleteSecretAsync: _deleteSecretAsyncImpl,
40
41
  keychainStatus: _keychainStatus,
@@ -149,23 +150,12 @@ function readConfigRawCached() {
149
150
  return raw;
150
151
  }
151
152
 
152
- // Canonical on-disk channel shape. Provider-specific ids are durable. Schedule
153
- // arrays moved to the scheduler DB and prompt injection moved to runtime hooks,
154
- // 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.
155
155
  export function canonicalizeStoredChannelsConfig(value = {}) {
156
156
  const next = isPlainObject(value) ? { ...value } : {};
157
157
  delete next.promptInjection;
158
158
  delete next.schedules;
159
- delete next.nonInteractive;
160
- delete next.interactive;
161
-
162
- // Messaging (Discord/Telegram) is retired: scrub the provider selector and
163
- // per-provider routing sections from stored config.
164
- delete next.backend;
165
- delete next.provider;
166
- delete next.discord;
167
- delete next.telegram;
168
- delete next.channel;
169
159
  return next;
170
160
  }
171
161
 
@@ -523,6 +513,18 @@ export function readSection(section) {
523
513
  return stripGeneratedMarker(readAll()[section] ?? null) ?? {};
524
514
  }
525
515
 
516
+ /**
517
+ * One section straight from the file, read-only: never served from the read
518
+ * cache, restored from a backup, quarantined or written. A missing, unreadable
519
+ * or malformed config throws. For checks that must fail closed, where a stale
520
+ * value restored from a backup must not count (developerOptionEnabledStrict).
521
+ */
522
+ export function readSectionStrict(section) {
523
+ const parsed = parseJsonText(readFileSync(configPath(), 'utf8'));
524
+ if (!isPlainObject(parsed)) throw new SyntaxError('config root must be a JSON object');
525
+ return stripGeneratedMarker(parsed[section] ?? null) ?? {};
526
+ }
527
+
526
528
  export function readConfig() {
527
529
  return stripGeneratedMarker(readAll()) ?? {};
528
530
  }
@@ -641,14 +643,17 @@ export function updateSectionAsync(section, updater) {
641
643
  }
642
644
 
643
645
  // ── Secret account names ─────────────────────────────────────────────────────
644
- // Canonical account strings stored in the OS keychain.
646
+ // Canonical account strings stored in the OS keychain (or, on a Linux host with
647
+ // no usable keychain provider, in the private secrets file behind the same
648
+ // get/set/delete calls: keychain-cjs.cjs "Linux fallback file").
645
649
  export const SECRET_ACCOUNTS = Object.freeze({
646
650
  agentApiKey: (provider) => `agent.${provider}.apiKey`,
647
651
  openaiUsageSessionKey: 'agent.openai.usageSessionKey',
648
652
  });
649
653
 
650
654
  // ── Secret-aware getters ─────────────────────────────────────────────────────
651
- // Read order: ENV MIXDOG_<UPPER_SNAKE> → OS keychain → null.
655
+ // Read order: ENV MIXDOG_<UPPER_SNAKE> → OS keychain → Linux fallback secrets
656
+ // file (read by _getSecret when the keychain has nothing) → null.
652
657
 
653
658
  function _envKey(account) {
654
659
  // 'agent.openai.apiKey' → 'MIXDOG_AGENT_OPENAI_APIKEY'
@@ -676,7 +681,9 @@ export function getOpenAIUsageSessionKey() {
676
681
  }
677
682
 
678
683
  /**
679
- * Persist a secret to the OS keychain. Throws on failure.
684
+ * Persist a secret to the OS keychain; on a Linux host with no keychain provider
685
+ * (secret-tool/libsecret missing, no Secret Service) to the owner-only secrets
686
+ * file in the data dir instead. Throws on failure.
680
687
  * Never writes to mixdog-config.json.
681
688
  */
682
689
  export function saveSecret(account, value) {
@@ -697,25 +704,25 @@ export async function saveSecretAsync(account, value) {
697
704
  }
698
705
 
699
706
  function saveSecretError(account, err) {
700
- {
701
- // On WSL/headless Linux (and any host missing a usable keychain provider,
702
- // e.g. secret-tool/libsecret not installed or no running Secret Service) the OS
703
- // write throws a cryptic provider error. Surface an actionable message that
704
- // points at the env-var read path the getters already honor, instead of
705
- // silently writing the plaintext secret to mixdog-config.json.
706
- const envKey = _envKey(account);
707
- const agentMatch = String(account || '').match(/^agent\.([^.]+)\.apiKey$/);
708
- const stdEnv = agentMatch ? AGENT_PROVIDER_ENV[agentMatch[1]] : null;
709
- const envHint = stdEnv ? `${stdEnv} (or ${envKey})` : envKey;
710
- const e = new Error(
711
- `[config] could not save secret to the OS keychain: ${err?.message ? err.message : err}\n` +
712
- ` Set the ${envHint} environment variable instead — the runtime reads it directly.`
713
- );
714
- e.cause = err;
715
- return e;
716
- }
707
+ // Reached for a locked keyring, a non-Linux keychain failure, or a Linux host
708
+ // with no keychain provider whose fallback secrets file could not be written
709
+ // (keychain-cjs.cjs). Surface an actionable message that points at the
710
+ // env-var read path the getters already honor, instead of silently writing
711
+ // the plaintext secret to mixdog-config.json. The fallback is a separate
712
+ // owner-only file, never the config.
713
+ const envKey = _envKey(account);
714
+ const agentMatch = String(account || '').match(/^agent\.([^.]+)\.apiKey$/);
715
+ const stdEnv = agentMatch ? AGENT_PROVIDER_ENV[agentMatch[1]] : null;
716
+ const envHint = stdEnv ? `${stdEnv} (or ${envKey})` : envKey;
717
+ const e = new Error(
718
+ `[config] could not save secret to the OS keychain: ${err?.message ? err.message : err}\n` +
719
+ ` Set the ${envHint} environment variable instead — the runtime reads it directly.`
720
+ );
721
+ e.cause = err;
722
+ return e;
717
723
  }
718
724
 
725
+ /** Remove a secret from the OS keychain (if present) and the Linux fallback file. */
719
726
  export function deleteSecret(account) {
720
727
  _deleteSecret(account);
721
728
  }
@@ -741,13 +748,16 @@ export function onKeychainRecovered(listener) {
741
748
  return typeof _onKeychainRecovered === 'function' ? _onKeychainRecovered(listener) : () => {};
742
749
  }
743
750
 
744
- /** Keyring health for the UI: { state: ok|locked|missing|error|unknown, message }. */
751
+ /** Keyring health for the UI: { state: ok|locked|missing|unreachable|error|unknown, message, fallback }.
752
+ * `fallback` is true when no keychain provider exists on this Linux host and
753
+ * secrets live in the private secrets file: working storage, not a failure. */
745
754
  export function getKeychainStatus() {
746
755
  return typeof _keychainStatus === 'function' ? _keychainStatus() : { state: 'unknown', message: '' };
747
756
  }
748
757
 
749
758
  /**
750
- * Whether a secret is stored in the OS keychain for `account` (ignores env).
759
+ * Whether a secret is stored in the OS keychain (or Linux fallback file) for
760
+ * `account` (ignores env).
751
761
  * Lets the setup UI show "Set" WITHOUT ever sending the secret value to the
752
762
  * browser.
753
763
  */
@@ -760,4 +770,10 @@ export function hasStoredSecret(account) {
760
770
  }
761
771
  }
762
772
 
773
+ /** Whether `account`'s stored secret currently lives in the Linux fallback file
774
+ * (never true on Windows/macOS). Lets the UI label where a key is kept. */
775
+ export function isFallbackSecret(account) {
776
+ return typeof _hasFallbackSecret === 'function' ? !!_hasFallbackSecret(account) : false;
777
+ }
778
+
763
779
  export { configPath };
@@ -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
  }