mixdog 1.3.5 → 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 (341) hide show
  1. package/NOTICE.md +2 -2
  2. package/README.md +2 -2
  3. package/package.json +2 -11
  4. package/scripts/daemon-stub.mjs +1 -1
  5. package/scripts/gate-local.mjs +4 -2
  6. package/scripts/native-release-status.mjs +2 -2
  7. package/scripts/recall-history-cases.json +1 -1
  8. package/scripts/suite-health.mjs +10 -1
  9. package/scripts/test.mjs +7 -11
  10. package/src/agents/reviewer/AGENT.md +7 -11
  11. package/src/agents/reviewer/agent.json +1 -1
  12. package/src/defaults/shipped-history.json +17 -1
  13. package/src/defaults/skills/browser-use/SKILL.md +59 -11
  14. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  15. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  16. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  17. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  18. package/src/defaults/skills/computer-use/SKILL.md +33 -9
  19. package/src/defaults/skills/setup/SKILL.md +22 -0
  20. package/src/defaults/skills/setup/references/actions.md +39 -5
  21. package/src/defaults/skills/setup/references/surfaces.md +11 -2
  22. package/src/defaults/skills/visualize/SKILL.md +2 -1
  23. package/src/lib/rules-builder.cjs +10 -8
  24. package/src/rules/agent/AGENT.md +6 -6
  25. package/src/rules/lead/LEAD.md +18 -20
  26. package/src/rules/shared/05-tool-calls.md +5 -6
  27. package/src/rules/shared/10-tool-workflow.md +2 -0
  28. package/src/rules/shared/60-verification.md +7 -5
  29. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  30. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  31. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  32. package/src/runtime/agent/orchestrator/config.mjs +17 -1
  33. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
  35. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
  36. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  37. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
  38. package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
  39. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +181 -133
  40. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
  41. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  42. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  43. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  44. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  45. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +145 -66
  46. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  47. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
  48. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  49. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
  50. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  51. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  52. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -13
  53. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  54. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  55. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  56. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  57. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  58. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  61. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  64. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  65. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  66. package/src/runtime/agent/orchestrator/providers/registry.mjs +26 -0
  67. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  68. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  69. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
  70. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  71. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  72. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  73. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  75. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  76. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
  77. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +133 -22
  78. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  79. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  80. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
  81. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
  82. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
  83. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  84. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
  85. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
  86. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
  87. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
  88. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
  90. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -2
  91. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  92. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +3 -2
  93. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  94. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  95. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  97. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
  98. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  99. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  100. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  101. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  102. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  103. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
  104. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  105. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  106. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
  107. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
  108. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  109. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
  110. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
  112. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  113. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
  114. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
  115. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  116. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
  117. package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
  118. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  120. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  121. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  122. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  123. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  124. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  125. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  126. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  127. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
  128. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
  129. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
  130. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
  131. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  132. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  133. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  134. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  135. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +18 -1
  136. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  137. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  138. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  141. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  142. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  143. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  144. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
  145. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
  146. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  147. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  148. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  149. package/src/runtime/browser-bridge/client.mjs +73 -8
  150. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  151. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  152. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  153. package/src/runtime/channels/lib/config.mjs +6 -61
  154. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  155. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  156. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  157. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  158. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  159. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  160. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  161. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  162. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  163. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  164. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  165. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  166. package/src/runtime/channels/lib/scheduler.mjs +33 -147
  167. package/src/runtime/channels/lib/schedules-db.mjs +34 -11
  168. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  169. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
  170. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  171. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  172. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  173. package/src/runtime/channels/lib/worker-main.mjs +10 -34
  174. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  175. package/src/runtime/computer-bridge/actions.mjs +9 -1
  176. package/src/runtime/computer-bridge/client.mjs +180 -8
  177. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  178. package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
  179. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  180. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  181. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  182. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  183. package/src/runtime/connectors/google-mcp.mjs +10 -12
  184. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  185. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  186. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  187. package/src/runtime/session-search/session-search-db.mjs +73 -9
  188. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  189. package/src/runtime/shared/abort-race.mjs +89 -0
  190. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  191. package/src/runtime/shared/agent-kind.mjs +4 -3
  192. package/src/runtime/shared/bounded-download.mjs +8 -3
  193. package/src/runtime/shared/config.mjs +2 -13
  194. package/src/runtime/shared/connector-accounts.mjs +20 -10
  195. package/src/runtime/shared/connector-catalog.d.mts +29 -0
  196. package/src/runtime/shared/connector-catalog.mjs +150 -0
  197. package/src/runtime/shared/connector-triggers.mjs +22 -19
  198. package/src/runtime/shared/defaults-separation.mjs +2 -14
  199. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  200. package/src/runtime/shared/err-text.mjs +24 -15
  201. package/src/runtime/shared/file-permissions.d.mts +0 -8
  202. package/src/runtime/shared/file-permissions.mjs +0 -11
  203. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  204. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  205. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  206. package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
  207. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  208. package/src/runtime/shared/mixy-presence.mjs +22 -0
  209. package/src/runtime/shared/orchestration.d.mts +2 -0
  210. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  211. package/src/runtime/shared/profile-config.cjs +1 -1
  212. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  213. package/src/runtime/shared/prompt-origin.mjs +21 -0
  214. package/src/runtime/shared/provider-accounts.mjs +7 -1
  215. package/src/runtime/shared/provider-display-name.mjs +29 -0
  216. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  217. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  218. package/src/runtime/shared/routine-trigger.mjs +10 -0
  219. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  220. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  221. package/src/runtime/shared/session-activity-signal.mjs +34 -1
  222. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  223. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  224. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  225. package/src/runtime/shared/tool-result-summary.mjs +1 -14
  226. package/src/runtime/shared/worktrees/test-support.mjs +9 -4
  227. package/src/runtime/terminal-bridge/client.mjs +10 -1
  228. package/src/runtime/visualize-bridge/client.mjs +27 -8
  229. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  230. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  231. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  232. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  233. package/src/session-runtime/agent-home-delivery.mjs +448 -185
  234. package/src/session-runtime/agent-task-sessions.mjs +329 -180
  235. package/src/session-runtime/boot/apis.mjs +9 -0
  236. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  237. package/src/session-runtime/boot/providers.mjs +3 -0
  238. package/src/session-runtime/boot/tools.mjs +6 -1
  239. package/src/session-runtime/config-lifecycle.mjs +6 -0
  240. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  241. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
  242. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
  243. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  244. package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
  245. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  246. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  247. package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
  248. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  249. package/src/session-runtime/mixy-heartbeat.mjs +153 -33
  250. package/src/session-runtime/notification-bus.mjs +31 -117
  251. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  252. package/src/session-runtime/notification-listeners.mjs +76 -0
  253. package/src/session-runtime/owned-automation-run.mjs +2 -1
  254. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  255. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  256. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  257. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  258. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  259. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  260. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  261. package/src/session-runtime/routine-owner.mjs +40 -9
  262. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  263. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
  264. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  265. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  266. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  267. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  268. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  269. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  270. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  271. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  272. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  273. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  274. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  275. package/src/session-runtime/services/channel-admin.mjs +7 -13
  276. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  277. package/src/session-runtime/services/seeds.mjs +0 -28
  278. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  279. package/src/session-runtime/session-create/session-options.mjs +1 -0
  280. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  281. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  282. package/src/session-runtime/settings-system-api.mjs +13 -1
  283. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  284. package/src/session-runtime/setup-tool/executor.mjs +145 -22
  285. package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
  286. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  287. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
  288. package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
  289. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  290. package/src/session-runtime/tool-policy/policy-stage.mjs +16 -2
  291. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  292. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  293. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  294. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  295. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  296. package/src/standalone/channel-transport.mjs +1 -1
  297. package/src/standalone/daemon-channels-loader.mjs +10 -8
  298. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  299. package/src/standalone/daemon-session-control/request.mjs +51 -0
  300. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  301. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  302. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  303. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  304. package/src/standalone/daemon-session-control.mjs +124 -188
  305. package/src/standalone/daemon.mjs +26 -3
  306. package/src/standalone/session-client.mjs +7 -3
  307. package/src/standalone/session-protocol.mjs +5 -0
  308. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  309. package/src/standalone/session-service/quota-resume.mjs +3 -0
  310. package/src/standalone/session-service/retention/busy.mjs +65 -1
  311. package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
  312. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  313. package/src/standalone/session-tool-output.mjs +15 -7
  314. package/src/tui/app/model-options.mjs +3 -28
  315. package/src/tui/dist/index.mjs +58 -64
  316. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  317. package/src/tui/session/initial-state.mjs +3 -0
  318. package/src/tui/session/notice-surface.mjs +7 -1
  319. package/src/tui/session/queue-helpers.mjs +3 -42
  320. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  321. package/src/tui/session/session-api/routes-write.mjs +5 -1
  322. package/src/tui/session/session-api/routes.mjs +1 -0
  323. package/src/tui/session/session-api/settings.mjs +17 -1
  324. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  325. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  326. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  327. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  328. package/src/tui/session/session-flow/drain.mjs +118 -149
  329. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  330. package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
  331. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  332. package/src/tui/session/tool-approval-request.mjs +39 -0
  333. package/src/tui/session/tool-approval-settle.mjs +62 -0
  334. package/src/tui/session/tool-approval.mjs +69 -115
  335. package/src/tui/session/tui-steering-persist.mjs +8 -1
  336. package/src/tui/session/turn-stream.mjs +19 -2
  337. package/src/tui/session/turn.mjs +1 -0
  338. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  339. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  340. package/scripts/code-graph-description-contract.mjs +0 -188
  341. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -0,0 +1,104 @@
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
+ // When a session's prompt cache was last used by a successful cache warm (a
48
+ // read with no write), registered by the warmer (cache-warm.mjs, from its
49
+ // durable touch store); 0 when none.
50
+ let lastCacheWarmUse = () => 0;
51
+
52
+ /** Register the source of a session's last successful cache-warm use
53
+ * (`(sessionId) => epoch ms | 0`); null restores none. */
54
+ export function setCacheWarmUseSource(source) {
55
+ lastCacheWarmUse = typeof source === 'function' ? source : () => 0;
56
+ }
57
+
58
+ /**
59
+ * When the session was last active (0: no model-visible conversation): the
60
+ * latest of its own activity and the last successful cache warm. Idleness is
61
+ * measured from the last time the cached prompt was actually used, and a warm
62
+ * that read the cache without writing used it; the session's own timestamps
63
+ * are never moved.
64
+ */
65
+ export function sessionActivityTimestamp(session, fallback = 0) {
66
+ if (!hasModelVisibleConversation(session)) return 0;
67
+ const own = timestampMs(session?.lastUsedAt) || timestampMs(session?.updatedAt) || timestampMs(fallback);
68
+ return Math.max(own, timestampMs(lastCacheWarmUse(session?.id)));
69
+ }
70
+
71
+ // A zero is often an unavailable/stale meter field, not an authoritative
72
+ // measurement. Do not let it mask the sibling live estimate.
73
+ function contextUsageBelowThreshold(runtime, minContextPercent) {
74
+ if (!(minContextPercent > 0)) return false;
75
+ const status = runtime.contextStatus?.() || null;
76
+ const usedTokens = Math.max(
77
+ 0,
78
+ Number(status?.usedTokens) || 0,
79
+ Number(status?.currentEstimatedTokens) || 0,
80
+ Number(status?.compaction?.currentEstimatedTokens) || 0
81
+ );
82
+ const triggerTokens = Number(
83
+ status?.compaction?.triggerTokens ||
84
+ status?.compaction?.autoCompactTokenLimit ||
85
+ runtime.session?.autoCompactTokenLimit ||
86
+ 0
87
+ );
88
+ if (!Number.isFinite(usedTokens) || !Number.isFinite(triggerTokens) || !(usedTokens > 0 && triggerTokens > 0)) {
89
+ return true;
90
+ }
91
+ return (usedTokens / triggerTokens) * 100 < minContextPercent;
92
+ }
93
+
94
+ /**
95
+ * Whether the idle gate clears the thread before a submit at `at`: `cfg` is
96
+ * the auto-clear state (`enabled`, `idleMs`, `minContextPercent`), the
97
+ * activity is `activityAt` (sessionActivityTimestamp) and `runtime` answers
98
+ * `contextStatus()` for the thread. The submit gate adds its own transient
99
+ * conditions (busy, queued input, a clear already running).
100
+ */
101
+ export function idleAutoClearDue({ cfg, activityAt, at, runtime }) {
102
+ if (!cfg?.enabled || !activityAt || at - activityAt < cfg.idleMs) return false;
103
+ return !contextUsageBelowThreshold(runtime, Number(cfg.minContextPercent ?? 10));
104
+ }
@@ -22,7 +22,7 @@ export function usageRollupDayKey(ts) {
22
22
  // background runners. An unknown source counts as conversation on purpose: a
23
23
  // new runner showing up in the user's totals is visible and correctable, while
24
24
  // silently dropping the user's own turns is neither.
25
- const BACKGROUND_SOURCES = new Set(['memory-cycle', 'schedule', 'webhook', 'native-web-search']);
25
+ const BACKGROUND_SOURCES = new Set(['memory-cycle', 'schedule', 'webhook', 'native-web-search', 'cache-warm']);
26
26
 
27
27
  export function isConversationUsageSource(sourceType) {
28
28
  const id = typeof sourceType === 'string' ? sourceType.trim().toLowerCase() : '';
@@ -0,0 +1,22 @@
1
+ // Whether the representative bot (Mixy) exists: the one rule. The desktop
2
+ // answers from it (getLeadStatus `exists`, main/mixy-first.ts), and so do
3
+ // Mixy's routines (routine-owner.mjs `no-mixy`), her heartbeat and the
4
+ // heartbeat reports queued for her chat (mixy-heartbeat.mjs,
5
+ // agent-home-delivery.mjs), all through routine-owner.mjs readMixyExists.
6
+ //
7
+ // `created: false` (Mixy's agent.json) is no. Otherwise she exists on
8
+ // evidence that the user made or uses her:
9
+ // - a name (the 대표 에이전트 dialog always sets one; her profile can);
10
+ // - a message the user wrote in her thread (`homeChatted`,
11
+ // agent-kind.mjs homeThreadEvidence);
12
+ // - a schedule or webhook she owns (`ownsRoutines`): the user set it up,
13
+ // and since v1.3.0 it ran for her whether or not she was named.
14
+ // Without any of them she does not exist: v1.3.0's untouched automatic
15
+ // `{"created": true}`, and an undecided profile (`created` null). Her own
16
+ // posts in her thread, heartbeat reports and routine results, are no
17
+ // evidence. `ownsRoutines` is read only when nothing else decides.
18
+ export async function mixyExists(status, ownsRoutines) {
19
+ if (status?.created === false) return false;
20
+ if (String(status?.name ?? '').trim() || status?.homeChatted === true) return true;
21
+ return (await ownsRoutines()) === true;
22
+ }
@@ -0,0 +1,2 @@
1
+ export type OrchestrationMode = 'none' | 'focused' | 'balanced' | 'swarm';
2
+ export function configuredOrchestrationMode(config: unknown): OrchestrationMode;
@@ -1,21 +1,32 @@
1
- /** Lazy per-dataDir PG handle: one in-flight init, bootstrap lock, retry after failure. */
1
+ import { createSharedAbortable } from './abort-race.mjs';
2
+
3
+ /** Lazy per-dataDir PG handle: one in-flight init, bootstrap lock, retry after
4
+ * failure. `signal` withdraws a caller; the init (a PG runtime install
5
+ * included) is cancelled once every caller has (createSharedAbortable). */
2
6
  export function createPgSchemaDb({ schema, ddl, defaultDataDir, ensurePg, withLock }) {
3
7
  const ready = new Map();
4
- return async function getDb(dataDir = defaultDataDir()) {
5
- if (ready.has(dataDir)) return ready.get(dataDir);
6
- const p = (async () => {
7
- const { db, pool } = await ensurePg(dataDir, { schema });
8
- // Serialize CREATE TABLE across concurrent first-boot processes on the
9
- // same cluster-global advisory lock the adapter uses for schema bootstrap.
10
- await withLock(pool, () => db.exec(ddl));
11
- return db;
12
- })();
13
- ready.set(dataDir, p);
14
- try {
15
- return await p;
16
- } catch (err) {
17
- ready.delete(dataDir); // let the next call retry after a transient failure
18
- throw err;
8
+ return function getDb(dataDir = defaultDataDir(), { signal = null } = {}) {
9
+ let shared = ready.get(dataDir);
10
+ if (!shared) {
11
+ const entry = createSharedAbortable(
12
+ async (initSignal) => {
13
+ const { db, pool } = await ensurePg(dataDir, { schema, signal: initSignal });
14
+ initSignal.throwIfAborted();
15
+ // Serialize CREATE TABLE across concurrent first-boot processes on the
16
+ // same cluster-global advisory lock the adapter uses for schema bootstrap.
17
+ await withLock(pool, () => db.exec(ddl));
18
+ return db;
19
+ },
20
+ {
21
+ // Let the next call retry after a transient failure or a cancellation.
22
+ onSettled: (failed) => {
23
+ if (failed && ready.get(dataDir) === entry) ready.delete(dataDir);
24
+ },
25
+ }
26
+ );
27
+ shared = entry;
28
+ ready.set(dataDir, entry);
19
29
  }
30
+ return shared.join(signal);
20
31
  };
21
32
  }
@@ -5,7 +5,7 @@
5
5
  const PROFILE_LANGUAGES = Object.freeze([
6
6
  { id: 'system', label: 'System (locale)', prompt: null },
7
7
  { id: 'en', label: 'English', prompt: 'English' },
8
- { id: 'ko', label: 'Korean', prompt: 'Korean' },
8
+ { id: 'ko', label: '한국어', prompt: 'Korean' },
9
9
  { id: 'ja', label: '日本語', prompt: 'Japanese (日本語)' },
10
10
  { id: 'zh-Hans', label: '中文(简体)', prompt: 'Simplified Chinese (简体中文)' },
11
11
  { id: 'zh-Hant', label: '中文(繁體)', prompt: 'Traditional Chinese (繁體中文)' },
@@ -0,0 +1,4 @@
1
+ export declare const PROMPT_ORIGIN_CHANNEL: 'channel';
2
+ export declare const PROMPT_ORIGIN_AGENT_REPORT: 'agent-report';
3
+ export type PromptOrigin = typeof PROMPT_ORIGIN_CHANNEL | typeof PROMPT_ORIGIN_AGENT_REPORT;
4
+ export declare function promptOriginOf(value: unknown): PromptOrigin | null;
@@ -0,0 +1,21 @@
1
+ // A submitted prompt's provenance, carried apart from its presentation mode
2
+ // (`origin` on the submit options, the queue entry, the turn input and the
3
+ // stored user message's meta). Dependency-free: the desktop main process
4
+ // stamps it too (agent-room-turn.ts).
5
+
6
+ /** Real external activity presented as a task notification: an inbound
7
+ * channel message (Discord, Telegram, webhook event rules …). */
8
+ export const PROMPT_ORIGIN_CHANNEL = 'channel';
9
+
10
+ /** A turn the runtime starts because another bot or agent reported back: a
11
+ * bot-home wake (daemon.mjs `wake`) or a group-room member's relay of its
12
+ * reported work (agent-room-turn.ts). System-caused, though presented as a
13
+ * prompt. */
14
+ export const PROMPT_ORIGIN_AGENT_REPORT = 'agent-report';
15
+
16
+ const KNOWN_ORIGINS = new Set([PROMPT_ORIGIN_CHANNEL, PROMPT_ORIGIN_AGENT_REPORT]);
17
+
18
+ /** `value` when it is a known provenance, else null. */
19
+ export function promptOriginOf(value) {
20
+ return KNOWN_ORIGINS.has(value) ? value : null;
21
+ }
@@ -15,7 +15,7 @@ export const ACCOUNT_PROVIDERS = Object.freeze([
15
15
  'antigravity-oauth',
16
16
  'opencode-go-oauth',
17
17
  ]);
18
- const MAX_ACCOUNTS = 20;
18
+ export const MAX_ACCOUNTS = 20;
19
19
  // `dataDir` defaults to this process's data dir; an isolated runtime passes
20
20
  // the host's dir explicitly while its own MIXDOG_DATA_DIR points elsewhere.
21
21
  const file = (dataDir = resolvePluginData()) => join(dataDir, 'provider-accounts.json');
@@ -597,6 +597,12 @@ export function providerAccountExhaustedForModelOnly(row, now = Date.now(), mode
597
597
  // enough that yesterday's number cannot send every fallback to one account.
598
598
  const USAGE_EVIDENCE_MAX_AGE_MS = 60 * 60_000;
599
599
 
600
+ /** Whether a usage reading taken at `checkedAt` may still steer a decision. */
601
+ export function quotaReadingFresh(checkedAt, now = Date.now()) {
602
+ const at = Number(checkedAt);
603
+ return at > 0 && now - at <= USAGE_EVIDENCE_MAX_AGE_MS;
604
+ }
605
+
600
606
  // Remaining headroom across every window that applies to this model, or null
601
607
  // when nothing recent measured the account. The fullest window decides: an
602
608
  // account with 4% of its burst window left cannot serve now, however untouched
@@ -0,0 +1,29 @@
1
+ // Display names for provider ids; an unknown id displays as itself.
2
+ const PROVIDER_DISPLAY_NAMES = new Map([
3
+ ['openai-oauth', 'OpenAI OAuth'],
4
+ ['anthropic-oauth', 'Anthropic OAuth'],
5
+ ['grok-oauth', 'Grok OAuth'],
6
+ ['muse-code-oauth', 'Muse Code'],
7
+ ['command-code-oauth', 'Command Code'],
8
+ ['mimo-oauth', 'MiMo'],
9
+ ['cursor-oauth', 'Cursor OAuth'],
10
+ ['devin-oauth', 'Devin'],
11
+ ['antigravity-oauth', 'Antigravity OAuth'],
12
+ ['openai', 'OpenAI API'],
13
+ ['openai-api', 'OpenAI API'],
14
+ ['anthropic', 'Anthropic API'],
15
+ ['anthropic-api', 'Anthropic API'],
16
+ ['gemini', 'Gemini API'],
17
+ ['gemini-api', 'Gemini API'],
18
+ ['xai', 'xAI API'],
19
+ ['xai-api', 'xAI API'],
20
+ ['deepseek', 'DeepSeek API'],
21
+ ['deepseek-api', 'DeepSeek API'],
22
+ ['opencode-go-oauth', 'OpenCode Go OAuth'],
23
+ ['openrouter', 'OpenRouter'],
24
+ ['mixdog-local', 'Local Provider'],
25
+ ['default', 'Default'],
26
+ ]);
27
+
28
+ export const providerDisplayName = (provider) =>
29
+ PROVIDER_DISPLAY_NAMES.get(String(provider || '').toLowerCase()) ?? (provider || 'Provider');
@@ -8,6 +8,8 @@ export const TIMEOUT_PHASE = Object.freeze({
8
8
  FIRST_CHUNK: 'first-chunk',
9
9
  // A stream that already produced output went silent.
10
10
  IDLE: 'idle',
11
+ // A sign-in token refresh did not answer: an auth problem, not a slow model.
12
+ AUTH: 'auth',
11
13
  });
12
14
 
13
15
  /** A timeout before the model produced anything: headers or first chunk. */
@@ -4,6 +4,8 @@ export interface RoutineTrigger {
4
4
  event?: string;
5
5
  }
6
6
 
7
+ export declare const ROUTINE_QUIET: 'ROUTINE_QUIET';
8
+ export declare function isRoutineQuietReply(text: unknown): boolean;
7
9
  export declare function routineTriggerHeader(input: {
8
10
  kind: string;
9
11
  name: string;
@@ -9,6 +9,15 @@ import { localTimestampParts } from './time-format.mjs';
9
9
  const HEADER_PREFIX = '[Routine trigger]';
10
10
  const KINDS = new Set(['schedule', 'webhook']);
11
11
 
12
+ /** The whole reply of a routine turn with nothing new to tell: kept in the
13
+ * record, never painted, previewed, counted unread or notified. */
14
+ export const ROUTINE_QUIET = 'ROUTINE_QUIET';
15
+
16
+ /** Whether a reply is exactly the routine quiet sentinel. */
17
+ export function isRoutineQuietReply(text) {
18
+ return String(text ?? '').trim() === ROUTINE_QUIET;
19
+ }
20
+
12
21
  /** The first line of a routine turn's prompt: which routine fired, when (local
13
22
  * time and zone: the turn gets no other date), and how to treat the turn.
14
23
  * It is the routine's only conduct text. Only the turn's content carries it,
@@ -24,6 +33,7 @@ export function routineTriggerHeader({ kind, name, project = '', now = new Date(
24
33
  'Payments and permanent deletes are refused here: report that and suggest doing it from the chat.',
25
34
  'If a sign-in, code or CAPTCHA blocks you, stop and say which site needs the user.',
26
35
  'Reply in this chat in plain words, without tool names; if you handed work off, say it started — its result arrives here later.',
36
+ `Tell what is new in a fitting tone; if nothing is worth telling, reply with exactly ${ROUTINE_QUIET}.`,
27
37
  ...(project ? [`Project folder: ${project}`] : []),
28
38
  ].join(' ');
29
39
  }
@@ -1,15 +1,7 @@
1
1
  export interface SensitiveScope {
2
+ /** A Mixdog home or data folder, as path segments; only its secret subset is denied. */
2
3
  root: string[];
3
- /** Subfolders of the root that stay reachable. */
4
- except: readonly string[];
5
- /** Whole tree (a registered private root) or Mixdog's secret subset. */
6
- whole: boolean;
7
4
  }
8
- export function sensitivePathSegments(path: string): string[];
9
- export function matchesSensitivePath(
10
- path: string,
11
- within?: readonly SensitiveScope[],
12
- options?: { envTemplates?: boolean }
13
- ): boolean;
5
+ export function matchesSensitivePath(path: string, within?: readonly SensitiveScope[]): boolean;
14
6
  export function mixdogSensitiveScopes(extraData?: readonly string[]): SensitiveScope[];
15
7
  export function isSensitiveLocalPath(path: string, options?: { extraData?: readonly string[] }): boolean;
@@ -1,8 +1,7 @@
1
- // The credential / secret file deny list, shared by every lane that hands a
2
- // local file to someone else: the desktop's remote (paired-device) file lanes
3
- // (apps/desktop/src/main/remote-sensitive-paths.ts adds its registered private
4
- // roots and async canonicalization) and the runtime's attachment hand-offs
5
- // between bots and agents (session-runtime/handoff-attachments.mjs).
1
+ // The credential / secret file deny list of the runtime's attachment
2
+ // hand-offs between bots and agents (session-runtime/handoff-attachments.mjs):
3
+ // an automatic transfer never carries a secret-bearing file. A paired device
4
+ // is the user's own and gets no such list (it opens what the desktop opens).
6
5
  //
7
6
  // Matching is on SEGMENTS of a path, lower-cased on every platform, so a
8
7
  // denied name cannot hide behind letter case, trailing dots/spaces or an NTFS
@@ -113,10 +112,6 @@ const CREDENTIAL_FILES = new Set([
113
112
  * checked in often enough with real values that no name is exempt) and
114
113
  * `<stage>.env`. */
115
114
  const ENV_FILE = /^(?:\.env.*|[^.]{1,64}(?:\.[^.]{1,64}){0,3}\.env)$/;
116
- /** The dotenv templates a person may still open on purpose from a paired
117
- * device (the desktop's remote file lanes, `envTemplates: true`); automatic
118
- * transfers between bots and agents never get this allowance. */
119
- const ENV_TEMPLATES = new Set(['.env.example', '.env.sample', '.env.template', '.env.dist']);
120
115
  /** Credential JSON a cloud or OAuth client writes or downloads:
121
116
  * `credentials.json`, `*credentials*.json`, `service-account*.json`,
122
117
  * `client_secret*.json`. */
@@ -143,10 +138,9 @@ function backupOriginal(name) {
143
138
 
144
139
  /** A file whose own name says it holds a secret, wherever it lives: a
145
140
  * credential file, a dotenv file, a key or keystore, or a backup of one. */
146
- function secretFileName(name, { envTemplates = false } = {}) {
141
+ function secretFileName(name) {
147
142
  const original = backupOriginal(name);
148
- if (CREDENTIAL_FILES.has(original) || CREDENTIAL_JSON.test(original)) return true;
149
- if (ENV_FILE.test(original)) return !(envTemplates && ENV_TEMPLATES.has(original));
143
+ if (CREDENTIAL_FILES.has(original) || CREDENTIAL_JSON.test(original) || ENV_FILE.test(original)) return true;
150
144
  const dot = original.lastIndexOf('.');
151
145
  if (dot >= 0 && KEY_EXTENSIONS.has(original.slice(dot))) return true;
152
146
  return SSH_PRIVATE_KEY.test(original);
@@ -156,14 +150,7 @@ function secretFileName(name, { envTemplates = false } = {}) {
156
150
  // (tool results, shell output, media), which stay reachable; only the files
157
151
  // and folders that carry provider tokens, API keys, pairing and bridge secrets
158
152
  // are denied.
159
- const MIXDOG_SECRET_DIRECTORIES = new Set([
160
- 'secrets',
161
- 'mcp-oauth',
162
- 'provider-accounts',
163
- 'telegram',
164
- 'relay-data',
165
- 'dev-profile',
166
- ]);
153
+ const MIXDOG_SECRET_DIRECTORIES = new Set(['secrets', 'mcp-oauth', 'provider-accounts', 'relay-data', 'dev-profile']);
167
154
  const MIXDOG_SECRET_FILE =
168
155
  /^\.?(mixdog-config|provider-accounts|relay-hook-device|auth\.json|[a-z0-9-]+-oauth(-credentials)?\.json|[a-z0-9-]+-bridge\.json)/;
169
156
  const MIXDOG_SECRET_EXTENSIONS = new Set(['.dpapi', '.token']);
@@ -177,7 +164,7 @@ function mixdogSecret(rest) {
177
164
 
178
165
  /** The segments of a path as the rules compare them: no extended-path prefix,
179
166
  * no NTFS stream suffix, no trailing dots or spaces, lower case. */
180
- export function sensitivePathSegments(path) {
167
+ function sensitivePathSegments(path) {
181
168
  return String(path)
182
169
  .replace(/^[\\/]{2}[?.][\\/]/, '')
183
170
  .split(/[\\/]+/)
@@ -198,30 +185,21 @@ function hasRun(segments, run) {
198
185
  }
199
186
 
200
187
  /**
201
- * Whether `path` is denied under `within`: scopes `{ root: segments, except:
202
- * subfolder names, whole }` — Mixdog's home and data folders (`whole: false`,
203
- * only their secret subset is denied) and registered private roots (`whole:
204
- * true`, everything but `except`). Pass both the lexical and canonical form.
205
- * `envTemplates`: a user-initiated remote open may read the dotenv
206
- * templates; nothing else gets that allowance.
188
+ * Whether `path` is denied under `within`: scopes `{ root: segments }` —
189
+ * Mixdog's home and data folders, where only their secret subset is denied.
190
+ * Pass both the lexical and canonical form.
207
191
  */
208
- export function matchesSensitivePath(path, within = [], { envTemplates = false } = {}) {
192
+ export function matchesSensitivePath(path, within = []) {
209
193
  const segments = sensitivePathSegments(resolve(path));
210
194
  const name = segments[segments.length - 1] ?? '';
211
195
  if (segments.some((segment) => SECRET_DIRECTORIES.has(segment))) return true;
212
- if (secretFileName(name, { envTemplates }) || SECRET_SEQUENCES.some((run) => hasRun(segments, run))) return true;
213
- const containing = within.filter(
214
- ({ root }) => root.length > 0 && root.every((segment, index) => segments[index] === segment)
196
+ if (secretFileName(name) || SECRET_SEQUENCES.some((run) => hasRun(segments, run))) return true;
197
+ return within.some(
198
+ ({ root }) =>
199
+ root.length > 0 &&
200
+ root.every((segment, index) => segments[index] === segment) &&
201
+ mixdogSecret(segments.slice(root.length))
215
202
  );
216
- // Mixdog's home and data folders follow Mixdog's own rule even inside a
217
- // registered private root: a dev or isolated profile keeps them in the app
218
- // profile, and the tool output and media there stay reachable.
219
- const own = containing.filter(({ whole }) => !whole);
220
- if (own.length) return own.some(({ root }) => mixdogSecret(segments.slice(root.length)));
221
- return containing.some(({ root, except }) => {
222
- const rest = segments.slice(root.length);
223
- return rest.length > 0 && !except.includes(rest[0]);
224
- });
225
203
  }
226
204
 
227
205
  function realOrSelf(path) {
@@ -241,7 +219,7 @@ export function mixdogSensitiveScopes(extraData = []) {
241
219
  const scopes = [];
242
220
  for (const root of roots) {
243
221
  for (const form of new Set([resolve(root), realOrSelf(resolve(root))])) {
244
- scopes.push({ root: sensitivePathSegments(form), except: [], whole: false });
222
+ scopes.push({ root: sensitivePathSegments(form) });
245
223
  }
246
224
  }
247
225
  return scopes;
@@ -13,7 +13,40 @@ export function onSessionActivityChange(listener) {
13
13
  }
14
14
 
15
15
  export function emitSessionActivityChange(sessionId = '') {
16
- for (const listener of [...listeners]) {
16
+ emit(listeners, sessionId);
17
+ }
18
+
19
+ // A turn started on a session: the one point every turn passes through, whoever
20
+ // started it. Unlike the activity pulse it never fires on a settle or a wait.
21
+ const turnStartListeners = new Set();
22
+
23
+ export function onSessionTurnStart(listener) {
24
+ if (typeof listener !== 'function') return () => {};
25
+ turnStartListeners.add(listener);
26
+ return () => turnStartListeners.delete(listener);
27
+ }
28
+
29
+ export function emitSessionTurnStart(sessionId = '') {
30
+ emit(turnStartListeners, sessionId);
31
+ }
32
+
33
+ // A live session started or stopped showing an approval card waiting on the
34
+ // user (its runtime state's `toolApproval`): the session catalog, which
35
+ // stamps that on each row, listens and re-reads.
36
+ const approvalCardListeners = new Set();
37
+
38
+ export function onSessionApprovalCardChange(listener) {
39
+ if (typeof listener !== 'function') return () => {};
40
+ approvalCardListeners.add(listener);
41
+ return () => approvalCardListeners.delete(listener);
42
+ }
43
+
44
+ export function emitSessionApprovalCardChange(sessionId = '') {
45
+ emit(approvalCardListeners, sessionId);
46
+ }
47
+
48
+ function emit(targets, sessionId) {
49
+ for (const listener of [...targets]) {
17
50
  try {
18
51
  listener(String(sessionId || ''));
19
52
  } catch {
@@ -4,7 +4,7 @@ export type SessionSidebarChange =
4
4
  | { archived: true; cascade: false }
5
5
  | { forgotten: true };
6
6
  export type SessionSidebarHost = (sessionId: unknown, change: unknown) => unknown;
7
- export type SessionSidebarState = { archived: Record<string, number> };
7
+ export type SessionSidebarState = { archived: Record<string, number>; favorites?: Record<string, number> };
8
8
  export function registerSessionSidebarHost(
9
9
  host: SessionSidebarHost,
10
10
  options?: { state?: (() => SessionSidebarState | Promise<SessionSidebarState>) | null }
@@ -41,11 +41,15 @@ export async function applySessionSidebarChange(sessionId, change) {
41
41
  await active.host(sessionId, change);
42
42
  }
43
43
 
44
- /** The active desktop's flags, `{ archived }` (session id → epoch-ms stamp);
45
- * null when no desktop (or one without a state reader) is attached. */
44
+ /** The active desktop's flags, `{ archived, favorites? }` (session id →
45
+ * epoch-ms stamp; `favorites` only from a desktop that reports them); null
46
+ * when no desktop (or one without a state reader) is attached. */
46
47
  export async function readSessionSidebarState() {
47
48
  const active = hosts().at(-1);
48
49
  if (!active?.state) return null;
49
50
  const state = await active.state();
50
- return { archived: { ...state?.archived } };
51
+ return {
52
+ archived: { ...state?.archived },
53
+ ...(state?.favorites && typeof state.favorites === 'object' ? { favorites: { ...state.favorites } } : {}),
54
+ };
51
55
  }
@@ -0,0 +1,18 @@
1
+ // What must land in a session's transcript right before its next turn's
2
+ // prompt, whoever starts that turn: the daemon registers one prelude
3
+ // (agent-home-delivery.mjs postBeforeAgentTurn) and the prompt queue's drain
4
+ // (tui/session/session-flow/drain.mjs) awaits it before presenting a batch.
5
+
6
+ let prelude = null;
7
+
8
+ /** Register `fn(sessionId)` (null or a promise per turn); a non-function clears it. */
9
+ export function setSessionTurnPrelude(fn) {
10
+ prelude = typeof fn === 'function' ? fn : null;
11
+ }
12
+
13
+ /** The prelude's work before a turn of `sessionId`: a promise, or null when
14
+ * there is none (no await, so a turn with nothing owed starts as before). */
15
+ export function sessionTurnPrelude(sessionId) {
16
+ if (!prelude || !sessionId) return null;
17
+ return prelude(String(sessionId)) || null;
18
+ }
@@ -526,20 +526,7 @@ function summarizeCodeGraphResult({ text, translate }) {
526
526
  return null;
527
527
  }
528
528
 
529
- function summarizeFetchResult({ text, trimmed, args, normalized, translate }) {
530
- // Channel `fetch` (Discord message fetch — args carry channel/messageId/limit,
531
- // never url/uri) is not a WEB fetch: the status/size probes would miss and
532
- // the result fall to raw JSON, so it takes the generic JSON/text summarizer.
533
- if (normalized === 'fetch') {
534
- const a = parseToolArgs(args);
535
- const isChannelFetch =
536
- !firstText(a.url, a.uri) && Boolean(firstText(a.channel, a.channelId, a.chatId, a.messageId) || a.limit != null);
537
- if (isChannelFetch) {
538
- const n = countNonEmptyLines(text);
539
- if (trimmed && looksLineOriented(text) && n > 0) return countLabel(translate, n, 'message');
540
- return summarizeGenericResult(text, translate);
541
- }
542
- }
529
+ function summarizeFetchResult({ text }) {
543
530
  // Status: require a status-like context (HTTP NNN, "Status: NNN",
544
531
  // or "NNN OK"/"NNN Not Found") rather than any bare 3-digit number.
545
532
  const status =
@@ -1,6 +1,7 @@
1
1
  // Temporary git repositories and data dirs for the worktree suites.
2
+ import '../git-direct.test-support.mjs';
2
3
  import { execFileSync } from 'node:child_process';
3
- import { chmodSync, cpSync, existsSync, mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
4
+ import { appendFileSync, chmodSync, cpSync, existsSync, mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
4
5
  import { tmpdir } from 'node:os';
5
6
  import { dirname, join } from 'node:path';
6
7
 
@@ -67,9 +68,13 @@ function repoTemplate(commit) {
67
68
  if (existing && existsSync(existing)) return existing;
68
69
  const dir = tempDir('mixdog-wt-template-');
69
70
  git(dir, 'init', '-q', '-b', 'main');
70
- git(dir, 'config', 'user.name', 'Mixdog Test');
71
- git(dir, 'config', 'user.email', 'mixdog@example.com');
72
- git(dir, 'config', 'core.autocrlf', 'false');
71
+ // One config write instead of a `git config` process per key. Auto
72
+ // maintenance is off, so no commit (the test's or a merge's) starts a
73
+ // `git maintenance run` child.
74
+ appendFileSync(
75
+ join(dir, '.git', 'config'),
76
+ '[user]\n\tname = Mixdog Test\n\temail = mixdog@example.com\n[core]\n\tautocrlf = false\n[maintenance]\n\tauto = false\n'
77
+ );
73
78
  if (commit) commitFile(dir, 'README.md', 'hello\n', 'initial');
74
79
  templates.set(commit, dir);
75
80
  return dir;
@@ -262,10 +262,19 @@ function oldestRowsOverCap(rows) {
262
262
  function formatRun(value) {
263
263
  const cursor = Number.isSafeInteger(value?.cursor) ? value.cursor : 0;
264
264
  const tab = value?.tab !== undefined ? ` tab=${value.tab}` : '';
265
+ let where = '';
266
+ if (value?.shown === true) where = 'The terminal is shown in the desktop window.';
267
+ else if (value?.shown === false) {
268
+ where =
269
+ 'The terminal is not shown in the desktop window right now (a paired phone may still show it); it keeps running.';
270
+ }
265
271
  return [
266
272
  `Terminal run: ${value?.id || `tab ${value?.tab}`} · ${tabLabel(value)} · cursor=${cursor}`,
267
273
  `Command sent to the side terminal. Read its output with action=read${tab} since=${cursor}.`,
268
- ].join('\n');
274
+ where,
275
+ ]
276
+ .filter(Boolean)
277
+ .join('\n');
269
278
  }
270
279
 
271
280
  async function readJson(response) {