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
@@ -1,30 +1,33 @@
1
1
  // Curated selection triggers for catalog connectors (MCP servers). A catalog
2
2
  // entry carries its line as `trigger`; the deferred-tool manifest lists each
3
3
  // connected server once as `- mcp__<server id>: <trigger>`, so one line must
4
- // say what the connector is for (plus a close negative where useful), in
5
- // English, at most CONNECTOR_TRIGGER_MAX_CHARS characters.
4
+ // say WHEN to call the server (the user requests it covers, plus a close
5
+ // negative where useful), not list its capabilities; plain ASCII English, at
6
+ // most CONNECTOR_TRIGGER_MAX_CHARS characters.
6
7
  export const CONNECTOR_TRIGGER_MAX_CHARS = 100;
7
8
 
8
9
  export const CONNECTOR_TRIGGERS = Object.freeze({
9
10
  // Not a catalog connector (Mixdog uses the signed-in GitHub CLI), but a
10
11
  // user-added `github` MCP server still gets this line.
11
- github: 'GitHub repos, issues, pull requests, Actions and code search; not local Git history or diffs.',
12
- notion: 'Notion pages, databases and workspace search; create or update team docs and notes.',
13
- linear: 'Linear issues, projects, cycles and comments; create, triage or update tickets.',
14
- sentry: 'Sentry issues, events, stack traces and releases; triage production errors.',
15
- gmail: 'Gmail: send email; with an app password also search, read, archive, label, star, trash.',
16
- 'google-calendar': 'Google Calendar events on the primary calendar; list, search, create, update or delete.',
17
- 'google-drive': 'Google Drive files Mixdog created or was given; search, read or create; not local files.',
18
- jira: 'Jira issues, projects, sprints and comments; JQL search, create or transition tickets.',
19
- confluence: 'Confluence spaces and pages; search, read or edit the team wiki; not Jira tickets.',
20
- dropbox: 'Dropbox files and folders; search, read, upload or share cloud files; not local files.',
21
- trello: 'Trello boards, lists and cards; create, move or update cards.',
22
- airtable: 'Airtable bases, tables and records; query, create or update rows.',
23
- stripe: 'Stripe customers, payments, invoices, subscriptions and refunds; billing data and actions.',
24
- vercel: 'Vercel projects, deployments, domains and logs; deploy status and build errors.',
25
- supabase: 'Supabase projects: Postgres tables and SQL, auth, storage and edge functions.',
26
- postgres: 'PostgreSQL schemas, tables and SQL queries on a connected database; not local files.',
27
- 'hugging-face': 'Hugging Face Hub models, datasets, Spaces and papers; not local GGUF model setup.',
12
+ github: 'GitHub issues, pull requests, Actions runs and repo code search; not local Git history.',
13
+ notion: 'Finding, reading or updating Notion pages, databases and team notes.',
14
+ linear: 'Looking up, creating or triaging Linear issues, projects and cycles.',
15
+ sentry: 'Investigating production errors, stack traces and releases in Sentry.',
16
+ // The line never names an account: that arrives with the server's
17
+ // instructions when its tools are loaded.
18
+ gmail: 'Gmail email: sending, reading, searching or organizing mail.',
19
+ 'google-calendar': 'Google Calendar: checking, creating or editing events.',
20
+ 'google-drive': 'Google Drive: finding, reading or creating files Mixdog made or was given; not local files.',
21
+ jira: 'Looking up, creating or transitioning Jira tickets, sprints and JQL searches.',
22
+ confluence: 'Searching, reading or editing Confluence wiki pages; not Jira tickets.',
23
+ dropbox: 'Finding, reading, uploading or sharing Dropbox files; not local files.',
24
+ trello: 'Viewing, creating or moving Trello boards, lists and cards.',
25
+ airtable: 'Querying or updating Airtable bases, tables and records.',
26
+ stripe: 'Checking or acting on Stripe payments, invoices, subscriptions and refunds.',
27
+ vercel: 'Checking Vercel deployments, domains, logs and build errors.',
28
+ supabase: 'Working with Supabase Postgres tables, SQL, auth, storage or edge functions.',
29
+ postgres: 'Running SQL or inspecting schemas on a connected PostgreSQL database; not local files.',
30
+ 'hugging-face': 'Finding Hugging Face Hub models, datasets, Spaces or papers; not local GGUF setup.',
28
31
  });
29
32
 
30
33
  // Server ids differ only in separators and case (google_calendar, huggingface).
@@ -40,13 +40,12 @@ const AGENT_SECTION_DEFAULTS = Object.freeze({
40
40
  const MODULE_DEFAULTS = Object.freeze({ enabled: true });
41
41
 
42
42
  export const CHANNELS_DEFAULTS = Object.freeze({
43
- access: Object.freeze({ dmPolicy: 'allowlist', allowFrom: [], channels: {} }),
44
43
  webhook: Object.freeze({ enabled: true, port: 3333 }),
45
44
  });
46
45
 
47
46
  // Disabled-agent roster shipped before `advisor` joined the defaults
48
47
  // (worker … writer plus the Maintainer). Stored full lists are read against it.
49
- export const PREVIOUS_DISABLED_AGENT_ROSTER = Object.freeze([
48
+ const PREVIOUS_DISABLED_AGENT_ROSTER = Object.freeze([
50
49
  'front-worker',
51
50
  'heavy-worker',
52
51
  'maintainer',
@@ -168,17 +167,6 @@ function removeLegacyKeys(root, dataDir) {
168
167
  if (isPlainObject(agent.agents)) agent.agents = withoutRetiredAgentRoutes(agent.agents, dataDir);
169
168
  next.agent = agent;
170
169
  }
171
- const channelEntries = next.channels?.access?.channels;
172
- if (isPlainObject(channelEntries)) {
173
- const channels = Object.fromEntries(
174
- Object.entries(channelEntries).map(([id, entry]) => {
175
- if (!isPlainObject(entry)) return [id, entry];
176
- const { requireMention: _retired, ...rest } = entry;
177
- return [id, rest];
178
- })
179
- );
180
- next.channels = { ...next.channels, access: { ...next.channels.access, channels } };
181
- }
182
170
  return next;
183
171
  }
184
172
 
@@ -188,10 +176,7 @@ function removeLegacyKeys(root, dataDir) {
188
176
  * not set the marker. `stripRouteDefaults` drops shipped presets and
189
177
  * maintenance routes (their defaults live with the agent config).
190
178
  */
191
- export function separateDefaultsInConfig(
192
- root,
193
- { stripRouteDefaults = (agent) => agent, dataDir = resolvePluginData() } = {}
194
- ) {
179
+ function separateDefaultsInConfig(root, { stripRouteDefaults = (agent) => agent, dataDir = resolvePluginData() } = {}) {
195
180
  const from = Number(root?.[DEFAULTS_VERSION_KEY]) || 0;
196
181
  let next = { ...(root || {}) };
197
182
  if (from < 1) {
@@ -6,14 +6,86 @@
6
6
  // owner, and the copy is withdrawn as soon as the owner settles (answered in
7
7
  // either place, timed out, its turn ended). Like held-request-host.mjs this
8
8
  // registry is process-wide and dependency-free.
9
+ // Hand-offs take the same path: a session chats handed work to (`session`
10
+ // send / create from a bot conversation, agent-task-sessions.mjs) shows its
11
+ // own approvals in EVERY chat whose hand-off to it still owes its report, one
12
+ // copy per chat, all settling the one request. The report watch store
13
+ // (session-runtime/agent-home-delivery.mjs) notes and ends hand-offs as its
14
+ // watches come and go, whatever removes them; a chat
15
+ // whose last hand-off to the session ends loses its copy, the others keep
16
+ // theirs.
9
17
 
10
18
  const KEY = Symbol.for('mixdog.delegatedApprovalHost.v1');
11
19
 
12
20
  function registry() {
13
- globalThis[KEY] ??= { queues: new Set(), pending: new Map(), opener: null };
21
+ globalThis[KEY] ??= { queues: new Set(), pending: new Map(), opener: null, handoffs: new Map(), ended: new Set() };
14
22
  return globalThis[KEY];
15
23
  }
16
24
 
25
+ /** The chats with a live hand-off to `sessionId`, each once (in noting order)
26
+ * with the name its latest hand-off gave the session: Map chat → `{ title }`. */
27
+ export function handoffChatsOf(sessionId) {
28
+ const chats = new Map();
29
+ for (const handoff of registry().handoffs.values()) {
30
+ if (handoff.sessionId !== sessionId) continue;
31
+ chats.delete(handoff.surfaceSessionId);
32
+ chats.set(handoff.surfaceSessionId, { title: handoff.title });
33
+ }
34
+ return chats;
35
+ }
36
+
37
+ /** Bring each pending request of `sessionId` that hand-offs reach to exactly
38
+ * the chats now handing it work: a new chat gets its copy, a chat whose
39
+ * hand-offs all ended loses only its own. */
40
+ function syncHandoffCopies(sessionId) {
41
+ const { pending } = registry();
42
+ const chats = handoffChatsOf(sessionId);
43
+ for (const asked of pending.values()) {
44
+ if (!asked.copies || asked.sessionId !== sessionId) continue;
45
+ for (const [chat, copy] of asked.copies) {
46
+ if (chats.has(chat)) continue;
47
+ asked.copies.delete(chat);
48
+ for (const queue of liveQueuesOf(chat)) queue.withdraw(copy.id, null);
49
+ }
50
+ for (const [chat, handoff] of chats) {
51
+ if (asked.copies.has(chat)) continue;
52
+ const copy = { id: asked.id, sessionId: chat, request: asked.copyFor(chat, handoff), answer: asked.answer };
53
+ asked.copies.set(chat, copy);
54
+ deliver(copy, () => pending.get(asked.id) === asked && asked.copies.get(chat) === copy);
55
+ }
56
+ }
57
+ }
58
+
59
+ // Run ids of hand-offs that ended: never noted again (a watch read before it
60
+ // was removed must not bring it back, however many hand-offs ended since).
61
+ // Run ids are never reused, so an ended id is final for the life of the
62
+ // process and there is nothing to age out: no count decides what is
63
+ // forgotten. One id string is kept per ended hand-off.
64
+
65
+ /** Chat `homeSessionId` handed session `taskSessionId` work (`title`: the
66
+ * target's name in that chat) under report watch `runId`. */
67
+ export function noteHandoff({ runId, taskSessionId, homeSessionId, title }) {
68
+ if (!runId || !taskSessionId || !homeSessionId || taskSessionId === homeSessionId) return;
69
+ if (registry().ended.has(String(runId))) return;
70
+ registry().handoffs.set(String(runId), {
71
+ sessionId: String(taskSessionId),
72
+ surfaceSessionId: String(homeSessionId),
73
+ title: String(title || ''),
74
+ });
75
+ syncHandoffCopies(String(taskSessionId));
76
+ }
77
+
78
+ /** The report watch `runId` is gone (its work reported back, was never
79
+ * taken, or the watch was retired): its hand-off ends for good. */
80
+ export function endHandoff(runId) {
81
+ const { handoffs, ended } = registry();
82
+ ended.add(String(runId));
83
+ const handoff = handoffs.get(String(runId));
84
+ if (!handoff) return;
85
+ handoffs.delete(String(runId));
86
+ syncHandoffCopies(handoff.sessionId);
87
+ }
88
+
17
89
  /** `queue`: `{ sessionId() → string, disposed() → boolean, offer(delegated),
18
90
  * withdraw(id, result) }` — one session runtime's approval queue. */
19
91
  export function registerApprovalQueue(queue) {
@@ -46,20 +118,36 @@ function offerTo(sessionId, delegated) {
46
118
  return live.length > 0;
47
119
  }
48
120
 
49
- /** Show `delegated` (`{ id, sessionId, request, answer(...finishArgs) → boolean }`,
50
- * `sessionId` being the top-level conversation) there, now or once it loads. */
51
- export function delegateApproval(delegated) {
52
- const { pending, opener } = registry();
53
- pending.set(delegated.id, delegated);
54
- if (offerTo(delegated.sessionId, delegated) || typeof opener !== 'function') return;
121
+ /** Offer `copy` in its chat now, or once that chat loads while `current()`
122
+ * still holds. */
123
+ function deliver(copy, current) {
124
+ const { opener } = registry();
125
+ if (offerTo(copy.sessionId, copy) || typeof opener !== 'function') return;
55
126
  void Promise.resolve()
56
- .then(() => opener(delegated.sessionId))
127
+ .then(() => opener(copy.sessionId))
57
128
  .then(() => {
58
- if (pending.get(delegated.id) === delegated) offerTo(delegated.sessionId, delegated);
129
+ if (current()) offerTo(copy.sessionId, copy);
59
130
  })
60
131
  .catch(() => {});
61
132
  }
62
133
 
134
+ /** Show `delegated` (`{ id, sessionId, request, answer(...finishArgs) → boolean }`,
135
+ * `sessionId` being the top-level conversation) there, now or once it loads. */
136
+ export function delegateApproval(delegated) {
137
+ const { pending } = registry();
138
+ pending.set(delegated.id, delegated);
139
+ deliver(delegated, () => pending.get(delegated.id) === delegated);
140
+ }
141
+
142
+ /** A top-level session's request (`{ id, sessionId, request, answer }`,
143
+ * `sessionId` being the asking session) that every chat handing it work
144
+ * shows too, now and while it waits: `copyFor(chat, { title })` is the
145
+ * request as that chat shows it. */
146
+ export function delegateToHandoffs(asked) {
147
+ registry().pending.set(asked.id, { ...asked, copies: new Map() });
148
+ syncHandoffCopies(asked.sessionId);
149
+ }
150
+
63
151
  /** The owner settled `id`: every copy goes away. `result` (`{ approved, device }`)
64
152
  * lets the copy say who answered. */
65
153
  export function withdrawDelegatedApproval(id, result = null) {
@@ -68,7 +156,11 @@ export function withdrawDelegatedApproval(id, result = null) {
68
156
  for (const queue of queues) queue.withdraw(id, result);
69
157
  }
70
158
 
71
- /** The pending delegated approvals a top-level session should show. */
159
+ /** The pending delegated approvals (and hand-off copies) a top-level session
160
+ * should show. */
72
161
  export function delegatedApprovalsFor(sessionId) {
73
- return [...registry().pending.values()].filter((delegated) => delegated.sessionId === sessionId);
162
+ return [...registry().pending.values()].flatMap((delegated) => {
163
+ if (delegated.copies) return delegated.copies.has(sessionId) ? [delegated.copies.get(sessionId)] : [];
164
+ return delegated.sessionId === sessionId ? [delegated] : [];
165
+ });
74
166
  }
@@ -206,24 +206,25 @@ function hasTypedEvidence(error) {
206
206
  );
207
207
  }
208
208
 
209
- // The window a provider timeout names (providerTimeoutError's message), or 0.
210
- const timedOutMs = (text) => Number(/timed out after (\d+)ms/i.exec(text)?.[1]) || 0;
209
+ // The window a provider timeout names (providerTimeoutError's message, or a
210
+ // first-byte watchdog's "within Nms"), or 0.
211
+ const timedOutMs = (text) => Number(/(?:timed out after|within) (\d+)ms/i.exec(text)?.[1]) || 0;
211
212
 
212
213
  // A provider timeout before the model produced anything (isFirstByteTimeout):
213
- // the model is slow, not the connection.
214
- function stillThinkingText(error, text) {
214
+ // the model is slow, not the connection. The provider name comes from the
215
+ // caller's own provider config, never from the error.
216
+ function stillThinkingText(error, text, providerLabel) {
215
217
  const ms = Number(error.waitedMs) || timedOutMs(text);
216
- const provider =
217
- typeof error.providerLabel === 'string' && error.providerLabel ? error.providerLabel : 'the provider';
218
+ const provider = typeof providerLabel === 'string' && providerLabel ? providerLabel : 'the provider';
218
219
  const attempts = Number(error.attempts) > 0 ? `; attempts: ${Math.floor(Number(error.attempts))}` : '';
219
220
  return `Model still thinking: no output in ${formatDurationMs(ms)}; ${provider} sent nothing yet${attempts}.`;
220
221
  }
221
222
 
222
223
  // Stamped provider timeout phases and stream cuts. Null when the error names
223
224
  // neither, so the text rules below decide.
224
- function typedStreamFailureText(error, text) {
225
+ function typedStreamFailureText(error, text, providerLabel) {
225
226
  if (!error || typeof error !== 'object') return null;
226
- if (isFirstByteTimeout(error)) return stillThinkingText(error, text);
227
+ if (isFirstByteTimeout(error)) return stillThinkingText(error, text, providerLabel);
227
228
  if (error.timeoutPhase === TIMEOUT_PHASE.IDLE) {
228
229
  const ms = timedOutMs(text);
229
230
  return ms > 0 ? `No progress ${formatDurationMs(ms)}.` : 'No progress.';
@@ -267,7 +268,7 @@ export function presentErrorText(error, options = {}) {
267
268
  if (embeddedError) text = embeddedError;
268
269
  text = oneLine(stripErrorPrefix(text));
269
270
  if (!text) return 'Unknown error';
270
- const streamFailure = typedStreamFailureText(error, text);
271
+ const streamFailure = typedStreamFailureText(error, text, options.providerLabel);
271
272
  if (streamFailure) return streamFailure;
272
273
 
273
274
  if (code === 'PROVIDER_PREFIX_MUTATION' || name === 'ProviderPrefixMutationError') {
@@ -369,12 +370,20 @@ export function presentErrorText(error, options = {}) {
369
370
  return capText(safeErrorDetails(text) || 'Unknown error', max);
370
371
  }
371
372
 
372
- /** Why a provider request is being retried, as one short status phrase. */
373
- export function retryReasonText(error, options = {}) {
374
- return presentErrorText(error, {
375
- surface: options.surface || 'request',
376
- max: options.max ?? 120,
377
- }).replace(/\.$/, '');
373
+ /**
374
+ * Our own wording for a typed timeout retry (`first_response_timeout` /
375
+ * `stalled`), built only from the error's stamps and the window in its
376
+ * message; null for any other class. No error text is ever echoed.
377
+ */
378
+ export function retryTimeoutText(error, reasonClass, providerLabel = null) {
379
+ const text = String(error?.message ?? '');
380
+ const ms = timedOutMs(text);
381
+ if (reasonClass === 'first_response_timeout') {
382
+ if (isFirstByteTimeout(error)) return stillThinkingText(error, text, providerLabel).replace(/\.$/, '');
383
+ return ms > 0 ? `No first response ${formatDurationMs(ms)}` : 'No first response';
384
+ }
385
+ if (reasonClass === 'stalled') return ms > 0 ? `No progress ${formatDurationMs(ms)}` : 'No progress';
386
+ return null;
378
387
  }
379
388
 
380
389
  /** The one retry status line every surface shows: `reason · retry n/max in 15s`. */
@@ -0,0 +1,16 @@
1
+ import type { Server } from 'node:net';
2
+
3
+ export const FETCH_BLOCKED_PORTS: ReadonlySet<number>;
4
+
5
+ export const FETCH_SAFE_ATTEMPTS: number;
6
+
7
+ export function listenOnFetchSafePort(
8
+ server: Server,
9
+ host: string,
10
+ onListening: () => void,
11
+ options?: {
12
+ attempts?: number;
13
+ isCurrent?: () => boolean;
14
+ portOf?: (server: Server) => number;
15
+ }
16
+ ): void;
@@ -0,0 +1,50 @@
1
+ // Ports the WHATWG Fetch spec blocks ("bad port"): fetch (undici) and Chromium
2
+ // refuse to connect to them, so a loopback server that a fetch client or a
3
+ // browser context must reach cannot live on one. https://fetch.spec.whatwg.org/#port-blocking
4
+ export const FETCH_BLOCKED_PORTS = new Set([
5
+ 0, 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110,
6
+ 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389, 427, 465, 512, 513, 514, 515, 526, 530, 531, 532,
7
+ 540, 548, 554, 556, 563, 587, 601, 636, 989, 990, 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061,
8
+ 6000, 6566, 6665, 6666, 6667, 6668, 6669, 6679, 6697, 10080,
9
+ ]);
10
+
11
+ /** How many more OS-assigned ports a listener takes after a blocked one. */
12
+ export const FETCH_SAFE_ATTEMPTS = 16;
13
+
14
+ const assignedPort = (server) => {
15
+ const address = server.address();
16
+ return address && typeof address === 'object' ? address.port : 0;
17
+ };
18
+
19
+ /**
20
+ * `server.listen(0, host)` that never settles on a fetch-blocked port: when the
21
+ * OS assigns one, the server is closed and listened again, at most `attempts`
22
+ * more times. `onListening` runs once, on a safe port. When the attempts run
23
+ * out the server is closed and an 'error' is emitted, like a failed listen.
24
+ * `isCurrent` lets a caller that stopped the server meanwhile cancel the retry;
25
+ * `portOf` reads the assigned port.
26
+ */
27
+ export function listenOnFetchSafePort(
28
+ server,
29
+ host,
30
+ onListening,
31
+ { attempts = FETCH_SAFE_ATTEMPTS, isCurrent = () => true, portOf = assignedPort } = {}
32
+ ) {
33
+ let retries = attempts;
34
+ const attempt = () => {
35
+ server.listen(0, host, () => {
36
+ if (!FETCH_BLOCKED_PORTS.has(portOf(server))) {
37
+ onListening();
38
+ return;
39
+ }
40
+ const exhausted = retries <= 0;
41
+ retries -= 1;
42
+ server.close(() => {
43
+ if (!isCurrent()) return;
44
+ if (exhausted) server.emit('error', new Error('no fetch-safe loopback port was assigned'));
45
+ else attempt();
46
+ });
47
+ });
48
+ };
49
+ attempt();
50
+ }
@@ -3,14 +3,6 @@ export function enforceOwnerOnlyAclWin32(targetPath: string, options?: { fresh?:
3
3
  /** Test hook: the live per-process ACL helper, or null. */
4
4
  export function ownerOnlyAclHelperForTests(): import('node:child_process').ChildProcess | null;
5
5
 
6
- /** Test/diagnostic hook: helper attachment, pending requests, starts in the restart window. */
7
- export function ownerOnlyAclHelperStats(): {
8
- alive: boolean;
9
- pid: number | null;
10
- pending: number;
11
- recentStarts: number;
12
- };
13
-
14
6
  /** Non-fresh calls verify (and tighten) the real DACL on every call through a shared helper. */
15
7
  export function enforceOwnerOnlyAclWin32Async(targetPath: string, options?: { fresh?: boolean }): Promise<void>;
16
8
 
@@ -267,17 +267,6 @@ export async function prewarmOwnerOnlyAclWin32({ bridge = true } = {}) {
267
267
  }
268
268
  }
269
269
 
270
- /** Test/diagnostic hook: whether a helper is attached, its requests awaiting
271
- * an answer, and the starts inside the current restart window. */
272
- export function ownerOnlyAclHelperStats() {
273
- return {
274
- alive: aclHelper !== null,
275
- pid: aclHelper?.child.pid ?? null,
276
- pending: aclHelper?.pending.size ?? 0,
277
- recentStarts: aclHelperStartTimes.length,
278
- };
279
- }
280
-
281
270
  /** Test hook: the live helper process, or null. */
282
271
  export function ownerOnlyAclHelperForTests() {
283
272
  return aclHelper?.child ?? null;
@@ -0,0 +1,19 @@
1
+ // Side-effect import for the git-heavy suites. Import it before anything that
2
+ // spawns git or snapshots process.env.
3
+ //
4
+ // On Windows `git` on PATH is usually Git for Windows' cmd\git.exe launcher,
5
+ // which starts the real git.exe as a second process, so every spawn pays two
6
+ // process creations (~75 ms instead of ~40 ms). These suites spawn hundreds of
7
+ // git processes, test and code under test alike (the git runner worker shares
8
+ // process.env), so the real binary's directory (`git --exec-path`, where git
9
+ // itself resolves its subcommands) goes first on PATH. Same git, same config;
10
+ // only the launcher hop is skipped. Elsewhere `git` already is the binary.
11
+ import { spawnSync } from 'node:child_process';
12
+ import { existsSync } from 'node:fs';
13
+ import { delimiter, join, normalize } from 'node:path';
14
+
15
+ if (process.platform === 'win32') {
16
+ const probe = spawnSync('git', ['--exec-path'], { encoding: 'utf8', windowsHide: true });
17
+ const dir = probe.status === 0 ? normalize(probe.stdout.trim()) : '';
18
+ if (dir && existsSync(join(dir, 'git.exe'))) process.env.PATH = `${dir}${delimiter}${process.env.PATH}`;
19
+ }
@@ -8,7 +8,8 @@ export interface HandoffFile {
8
8
  id?: string;
9
9
  }
10
10
  export function normalizeHandoffFiles(files: unknown): HandoffFile[];
11
- export function artifactLines(files: unknown, omitted?: number, options?: { ids?: boolean }): string[];
11
+ export function artifactLines(files: unknown, omitted?: number): string[];
12
+ export function producedFileLines(files: unknown, omitted?: number): string[];
12
13
  export interface HandoffRecord {
13
14
  id: string;
14
15
  attachmentRef: string;
@@ -31,20 +31,30 @@ export function normalizeHandoffFiles(files) {
31
31
  }));
32
32
  }
33
33
 
34
- /** The model-facing list of `files`, empty when there are none. `ids`: the
35
- * row also records the stored files as its attachments (a task report), so
36
- * their ids can be forwarded; a plain-text handoff (an agent completion)
37
- * lists paths only — an id named in text is never trusted. */
38
- export function artifactLines(files, omitted = 0, { ids = true } = {}) {
34
+ /** An agent completion's compact list of files it produced (name and path
35
+ * only; the Lead forwards or links by path), empty when there are none. */
36
+ export function producedFileLines(files, omitted = 0) {
39
37
  const list = normalizeHandoffFiles(files);
40
38
  if (!list.length) return [];
41
- const attachable = (file) => (ids ? ` [attachment:${file.id}]` : ' (attachable: forward it by path)');
42
39
  return [
43
- `Files from this turn (forward one with attachments: [${ids ? 'id or path' : 'path'}]; link a path to show it):`,
40
+ 'Files produced:',
41
+ ...list.map((file) => `- ${oneLine(file.name)} — ${oneLine(file.path)}`),
42
+ ...(omitted > 0 ? [`- …and ${omitted} more`] : []),
43
+ ];
44
+ }
45
+
46
+ /** The model-facing list of a bot task report's `files`, empty when there are
47
+ * none; the row also records the stored files as its attachments, so their
48
+ * ids can be forwarded. */
49
+ export function artifactLines(files, omitted = 0) {
50
+ const list = normalizeHandoffFiles(files);
51
+ if (!list.length) return [];
52
+ return [
53
+ 'Files from this turn (forward one with attachments: [id or path]; link a path to show it):',
44
54
  ...list.map(
45
55
  (file) =>
46
56
  `- ${oneLine(file.name)} (${oneLine(file.mimeType)}, ${sizeLabel(file.size)}) — ${oneLine(file.path)}${
47
- file.id ? attachable(file) : ' (too large or unsupported to attach; link the path)'
57
+ file.id ? ` [attachment:${file.id}]` : ' (too large or unsupported to attach; link the path)'
48
58
  }`
49
59
  ),
50
60
  ...(omitted > 0 ? [`- …and ${omitted} more file(s)`] : []),
@@ -0,0 +1,4 @@
1
+ export const HEARTBEAT_INTERVAL_CHOICES: readonly number[];
2
+ export const DEFAULT_HEARTBEAT_INTERVAL_MINUTES: number;
3
+ export function normalizeHeartbeatIntervalMinutes(value: unknown): number;
4
+ export function requireHeartbeatIntervalMinutes(value: unknown): number;
@@ -0,0 +1,19 @@
1
+ // Mixy's heartbeat interval setting (`heartbeatIntervalMinutes` in the config):
2
+ // one of a few fixed choices, shared by the runtime, the desktop's IPC check
3
+ // and the Settings picker. "Off" is the separate `heartbeat` switch.
4
+ export const HEARTBEAT_INTERVAL_CHOICES = Object.freeze([30, 60, 120, 240]);
5
+ export const DEFAULT_HEARTBEAT_INTERVAL_MINUTES = 60;
6
+
7
+ /** `value` when it is one of the choices, else the default (a stored value
8
+ * that is no longer, or never was, a choice reads as the default). */
9
+ export function normalizeHeartbeatIntervalMinutes(value) {
10
+ return HEARTBEAT_INTERVAL_CHOICES.includes(value) ? value : DEFAULT_HEARTBEAT_INTERVAL_MINUTES;
11
+ }
12
+
13
+ /** `value` when it is one of the choices; throws otherwise (a write). */
14
+ export function requireHeartbeatIntervalMinutes(value) {
15
+ if (!HEARTBEAT_INTERVAL_CHOICES.includes(value)) {
16
+ throw new Error(`heartbeat interval must be one of ${HEARTBEAT_INTERVAL_CHOICES.join(', ')} minutes`);
17
+ }
18
+ return value;
19
+ }
@@ -0,0 +1,126 @@
1
+ // idle-auto-clear-gate.mjs — the idle auto-clear predicate, shared by the
2
+ // submit gate (tui/session/session-flow/auto-clear.mjs); the cache warmer
3
+ // (agent/orchestrator/session/cache-warm.mjs) registers its successful warms,
4
+ // which count as cache use (sessionActivityTimestamp). A thread is cleared before
5
+ // a submit when auto-clear is on, it has been idle for at least the window
6
+ // since its activity timestamp, and its context is not below the minimum
7
+ // share of the compaction trigger.
8
+ import { attachmentTextForPart, isAttachmentReference } from '../attachments/store.mjs';
9
+
10
+ export function promptContentText(content) {
11
+ if (typeof content === 'string') return content;
12
+ if (Array.isArray(content)) {
13
+ return content
14
+ .map((part) => {
15
+ if (typeof part === 'string') return part;
16
+ if (part?.type === 'text') return isAttachmentReference(part) ? attachmentTextForPart(part) : part.text || '';
17
+ if (part?.type === 'image') return '[Image]';
18
+ if (part?.type === 'file') return '[File]';
19
+ return part?.text || '';
20
+ })
21
+ .filter(Boolean)
22
+ .join('\n');
23
+ }
24
+ return String(content ?? '');
25
+ }
26
+
27
+ function timestampMs(value) {
28
+ if (value == null || value === '') return 0;
29
+ const n = Number(value);
30
+ if (Number.isFinite(n) && n > 0) return n;
31
+ const parsed = Date.parse(String(value));
32
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
33
+ }
34
+
35
+ function hasModelVisibleConversation(session) {
36
+ const messages = Array.isArray(session?.messages) ? session.messages : [];
37
+ return messages.some((message) => {
38
+ const role = message?.role;
39
+ if (role !== 'user' && role !== 'assistant' && role !== 'tool') return false;
40
+ const text = promptContentText(message.content).trim();
41
+ if (role === 'user' && text.startsWith('<system-reminder>')) return false;
42
+ if (role === 'assistant' && text === '.' && !Array.isArray(message.toolCalls)) return false;
43
+ return !!text || role === 'assistant' || role === 'tool';
44
+ });
45
+ }
46
+
47
+ // A session's last successful cache-warm use (a read with no write),
48
+ // registered by the warmer (cache-warm.mjs, from its durable touch store):
49
+ // `{ at, provider, model, accountId }` — when, and the route and account whose
50
+ // cached prefix it read — or a bare epoch-ms time; null/0 when none.
51
+ let lastCacheWarmUse = () => null;
52
+
53
+ /** Register the source of a session's last successful cache-warm use
54
+ * (`(sessionId) => { at, provider, model, accountId } | epoch ms | null`);
55
+ * null restores none. */
56
+ export function setCacheWarmUseSource(source) {
57
+ lastCacheWarmUse = typeof source === 'function' ? source : () => null;
58
+ }
59
+
60
+ /** When a successful cache warm last used the session's prompt cache (epoch
61
+ * ms), or 0: the warm read and refreshed the cached prefix without moving
62
+ * any of the session's own timestamps. */
63
+ export function lastCacheWarmUseAt(sessionId) {
64
+ const use = sessionId ? lastCacheWarmUse(sessionId) : null;
65
+ return timestampMs(use && typeof use === 'object' ? use.at : use);
66
+ }
67
+
68
+ /** When a successful cache warm last used the prefix a request on `provider`
69
+ * / `model` / `accountId` reads (epoch ms), or 0: a warm on another route or
70
+ * account refreshed an entry this request cannot read, and a use without a
71
+ * recorded identity matches nothing. */
72
+ export function cacheWarmUseFor(sessionId, { provider, model, accountId = '' } = {}) {
73
+ const use = sessionId ? lastCacheWarmUse(sessionId) : null;
74
+ if (!use || typeof use !== 'object' || !use.provider) return 0;
75
+ const matches =
76
+ use.provider === provider && use.model === model && String(use.accountId || '') === String(accountId || '');
77
+ return matches ? timestampMs(use.at) : 0;
78
+ }
79
+
80
+ /**
81
+ * When the session was last active (0: no model-visible conversation): the
82
+ * latest of its own activity and the last successful cache warm. Idleness is
83
+ * measured from the last time the cached prompt was actually used, and a warm
84
+ * that read the cache without writing used it; the session's own timestamps
85
+ * are never moved.
86
+ */
87
+ export function sessionActivityTimestamp(session, fallback = 0) {
88
+ if (!hasModelVisibleConversation(session)) return 0;
89
+ const own = timestampMs(session?.lastUsedAt) || timestampMs(session?.updatedAt) || timestampMs(fallback);
90
+ return Math.max(own, lastCacheWarmUseAt(session?.id));
91
+ }
92
+
93
+ // A zero is often an unavailable/stale meter field, not an authoritative
94
+ // measurement. Do not let it mask the sibling live estimate.
95
+ function contextUsageBelowThreshold(runtime, minContextPercent) {
96
+ if (!(minContextPercent > 0)) return false;
97
+ const status = runtime.contextStatus?.() || null;
98
+ const usedTokens = Math.max(
99
+ 0,
100
+ Number(status?.usedTokens) || 0,
101
+ Number(status?.currentEstimatedTokens) || 0,
102
+ Number(status?.compaction?.currentEstimatedTokens) || 0
103
+ );
104
+ const triggerTokens = Number(
105
+ status?.compaction?.triggerTokens ||
106
+ status?.compaction?.autoCompactTokenLimit ||
107
+ runtime.session?.autoCompactTokenLimit ||
108
+ 0
109
+ );
110
+ if (!Number.isFinite(usedTokens) || !Number.isFinite(triggerTokens) || !(usedTokens > 0 && triggerTokens > 0)) {
111
+ return true;
112
+ }
113
+ return (usedTokens / triggerTokens) * 100 < minContextPercent;
114
+ }
115
+
116
+ /**
117
+ * Whether the idle gate clears the thread before a submit at `at`: `cfg` is
118
+ * the auto-clear state (`enabled`, `idleMs`, `minContextPercent`), the
119
+ * activity is `activityAt` (sessionActivityTimestamp) and `runtime` answers
120
+ * `contextStatus()` for the thread. The submit gate adds its own transient
121
+ * conditions (busy, queued input, a clear already running).
122
+ */
123
+ export function idleAutoClearDue({ cfg, activityAt, at, runtime }) {
124
+ if (!cfg?.enabled || !activityAt || at - activityAt < cfg.idleMs) return false;
125
+ return !contextUsageBelowThreshold(runtime, Number(cfg.minContextPercent ?? 10));
126
+ }