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
@@ -41,6 +41,10 @@ import {
41
41
  removeRetiredAgentFields,
42
42
  } from './config-storage.mjs';
43
43
  import { oauthCredentialProbeState, isOAuthProviderAvailable } from './providers/oauth-credential-probes.mjs';
44
+ import {
45
+ DEFAULT_HEARTBEAT_INTERVAL_MINUTES,
46
+ normalizeHeartbeatIntervalMinutes,
47
+ } from '../../shared/heartbeat-interval.mjs';
44
48
  import { normalizeDeveloperConfig } from '../../shared/developer-options.mjs';
45
49
  import { normalizeWorktreeSettings } from '../../shared/worktrees/settings.mjs';
46
50
  import { normalizePluginMcpIntents } from './runtime-core/plugin-mcp.mjs';
@@ -295,8 +299,13 @@ export function loadConfig(options = {}) {
295
299
  recap: recapConfig,
296
300
  // The model may title the session (session tool, action=rename); default on.
297
301
  aiSessionTitles: raw.aiSessionTitles !== false,
298
- // Mixy's 2-hourly heartbeat check (session-runtime/mixy-heartbeat.mjs); default on.
302
+ // Mixy's heartbeat check (session-runtime/mixy-heartbeat.mjs); default on.
299
303
  heartbeat: raw.heartbeat !== false,
304
+ // Minutes between its checks, one of a fixed set (shared/heartbeat-interval.mjs);
305
+ // anything else reads as the default.
306
+ heartbeatIntervalMinutes: normalizeHeartbeatIntervalMinutes(raw.heartbeatIntervalMinutes),
307
+ // Prompt-cache warming (session/cache-warm.mjs); default on.
308
+ promptCacheWarming: raw?.promptCacheWarming !== false,
300
309
  // Bots may leave an emoji reaction on the user's message (react tool); default on.
301
310
  reactions: raw.reactions !== false,
302
311
  // Bots page layout (order, hidden groups, pins, retention) and the shared
@@ -351,6 +360,7 @@ export function loadConfig(options = {}) {
351
360
  shell: {},
352
361
  update: {},
353
362
  recap: { enabled: true },
363
+ promptCacheWarming: true,
354
364
  worktree: normalizeWorktreeSettings(null),
355
365
  developer: {},
356
366
  modules: {},
@@ -482,8 +492,14 @@ function buildAgentSaveBuilder(config) {
482
492
  else delete next.aiSessionTitles;
483
493
  if (config.heartbeat === false) next.heartbeat = false;
484
494
  else delete next.heartbeat;
495
+ // Only a non-default choice is stored.
496
+ const heartbeatMinutes = normalizeHeartbeatIntervalMinutes(config.heartbeatIntervalMinutes);
497
+ if (heartbeatMinutes !== DEFAULT_HEARTBEAT_INTERVAL_MINUTES) next.heartbeatIntervalMinutes = heartbeatMinutes;
498
+ else delete next.heartbeatIntervalMinutes;
485
499
  if (config.reactions === false) next.reactions = false;
486
500
  else delete next.reactions;
501
+ if (config.promptCacheWarming === false) next.promptCacheWarming = false;
502
+ else delete next.promptCacheWarming;
487
503
  if (Array.isArray(config.botOrder) && config.botOrder.length) next.botOrder = [...config.botOrder];
488
504
  else delete next.botOrder;
489
505
  if (config.leadAvatar && typeof config.leadAvatar === 'object') next.leadAvatar = { ...config.leadAvatar };
@@ -1,6 +1,7 @@
1
1
  export {
2
2
  applyInitialDeferredToolManifestToBp2,
3
3
  buildDeferredToolManifest,
4
+ resetBp2StableContext,
4
5
  stripDeferredToolManifestBlock,
5
6
  } from './deferred-tools.mjs';
6
7
  export {
@@ -11,11 +12,13 @@ export {
11
12
  collectPromptSkillsCached,
12
13
  collectSkills,
13
14
  collectSkillsCached,
15
+ currentSkillDelivery,
14
16
  filterSkillsExcludingDisabled,
15
17
  invalidateSkillsCache,
16
18
  isSkillDisabled,
17
19
  loadSkillResource,
18
20
  skillBodyPresentInSession,
21
+ skillLoadRefusal,
19
22
  skillMissingFeature,
20
23
  skillOutsideScope,
21
24
  skillEnabledFlags,
@@ -1,7 +1,11 @@
1
- // Deferred-pool and MCP-instruction manifests injected into BP2.
2
- import { accountOfInstructions } from '../../../shared/connector-accounts.mjs';
1
+ // Deferred-pool manifest injected into BP2. MCP server instruction bodies are
2
+ // not injected: a server's one-line entry is its when-to-use trigger (accounts
3
+ // one line per server), and its instructions arrive with its tools
4
+ // on load_tool. The <mcp-instructions> block is still recognized so older
5
+ // prompts can shed it.
3
6
  import { connectorTrigger } from '../../../shared/connector-triggers.mjs';
4
7
  import { parseMcpToolName, titleCaseMcpServer } from '../../../shared/tool-primitives.mjs';
8
+ import { SKILL_TRIGGER_LISTING_MAX } from '../../../shared/skill-document.mjs';
5
9
  import { isToolBridgeEnabled, TOOL_BRIDGE_GUIDANCE } from '../runtime-core/tool-exposure.mjs';
6
10
 
7
11
  export function compactPromptManifestText(value, max = 250) {
@@ -24,13 +28,24 @@ const MCP_INSTRUCTIONS_BLOCK_RE = /(\n\n---\n*)?<mcp-instructions>[\s\S]*?<\/mcp
24
28
  // Tool and MCP server names enter the manifests only as plain identifiers
25
29
  // (no markup can be smuggled into the prompt through a name).
26
30
  const MANIFEST_NAME_SAFE_RE = /^[A-Za-z0-9_.:-]+$/;
27
- const MCP_INSTRUCTION_MAX_CHARS = 600;
28
31
 
29
32
  function sanitizeManifestName(name) {
30
33
  const text = String(name || '').trim();
31
34
  return MANIFEST_NAME_SAFE_RE.test(text) ? text : '';
32
35
  }
33
36
 
37
+ const MCP_INSTRUCTION_MAX_CHARS = 600;
38
+
39
+ /** A server's instruction text without markup, capped at MCP_INSTRUCTION_MAX_CHARS. */
40
+ export function sanitizeMcpInstructionText(text, max = MCP_INSTRUCTION_MAX_CHARS) {
41
+ const stripped = String(text || '')
42
+ .replace(/[<>]/g, '')
43
+ .trim();
44
+ if (!stripped) return '';
45
+ const cap = Math.max(1, Number(max) || MCP_INSTRUCTION_MAX_CHARS);
46
+ return stripped.length > cap ? `${stripped.slice(0, Math.max(1, cap - 3))}...` : stripped;
47
+ }
48
+
34
49
  function skillRoutedToolNames(messages) {
35
50
  const names = new Set();
36
51
  for (const message of Array.isArray(messages) ? messages : []) {
@@ -63,7 +78,8 @@ function hasDeferredToolManifestBlock(text) {
63
78
  * wire tools). Each entry is either a bare name string or `{ name, description }`;
64
79
  * output lines are `- name: description` (description omitted when absent),
65
80
  * mirroring the available-skills manifest so the model calls deferred tools
66
- * directly. Descriptions are compacted and stripped of `<`/`>`.
81
+ * directly. Descriptions are compacted to the skill listing's trigger cap
82
+ * (skill-document.mjs SKILL_TRIGGER_LISTING_MAX) and stripped of `<`/`>`.
67
83
  * Empty pool → '' (caller omits the block).
68
84
  */
69
85
  export function buildDeferredToolManifest(entries) {
@@ -77,7 +93,7 @@ export function buildDeferredToolManifest(entries) {
77
93
  const description =
78
94
  typeof entry === 'string'
79
95
  ? ''
80
- : compactPromptManifestText(String(entry?.description || '').replace(/[<>]/g, ''), 100);
96
+ : compactPromptManifestText(String(entry?.description || '').replace(/[<>]/g, ''), SKILL_TRIGGER_LISTING_MAX);
81
97
  list.push({ name, description });
82
98
  }
83
99
  if (!list.length) return '';
@@ -90,49 +106,6 @@ export function buildDeferredToolManifest(entries) {
90
106
  ].join('\n');
91
107
  }
92
108
 
93
- function sanitizeMcpInstructionText(text, max = MCP_INSTRUCTION_MAX_CHARS) {
94
- const stripped = String(text || '')
95
- .replace(/[<>]/g, '')
96
- .trim();
97
- if (!stripped) return '';
98
- const cap = Math.max(1, Number(max) || MCP_INSTRUCTION_MAX_CHARS);
99
- return stripped.length > cap ? `${stripped.slice(0, Math.max(1, cap - 3))}...` : stripped;
100
- }
101
-
102
- /**
103
- * Per-server MCP initialize instructions for deferred-pool tools only.
104
- * Empty when no instructions or no matching deferred MCP tools → omit block.
105
- * Emits ONLY the server heading + instruction body: tool names are never
106
- * listed — <available-deferred-tools> names each server once (mcp__<server>).
107
- */
108
- function buildMcpInstructionsManifest(mcpServerInstructions, poolNames, catalog = []) {
109
- const map = mcpServerInstructions && typeof mcpServerInstructions === 'object' ? mcpServerInstructions : {};
110
- const pool = [
111
- ...new Set((Array.isArray(poolNames) ? poolNames : []).map((name) => sanitizeManifestName(name)).filter(Boolean)),
112
- ];
113
- // Tool definitions carry their server (shortened names cannot be parsed).
114
- const byName = new Map();
115
- for (const tool of Array.isArray(catalog) ? catalog : []) {
116
- const name = String(tool?.name || '').trim();
117
- if (name && !byName.has(name)) byName.set(name, tool);
118
- }
119
- const deferredServers = new Set();
120
- for (const name of pool) {
121
- const server = parseMcpToolName(byName.get(name) || name)?.server;
122
- if (server) deferredServers.add(server);
123
- }
124
- const servers = [...deferredServers]
125
- .filter((server) => sanitizeManifestName(server) && sanitizeMcpInstructionText(map[server]))
126
- .sort((a, b) => a.localeCompare(b));
127
- if (!servers.length) return '';
128
- const lines = ['<mcp-instructions>'];
129
- for (const server of servers) {
130
- lines.push(`## ${sanitizeManifestName(server)}`, sanitizeMcpInstructionText(map[server]));
131
- }
132
- lines.push('</mcp-instructions>');
133
- return lines.join('\n');
134
- }
135
-
136
109
  export function stripDeferredToolManifestBlock(text) {
137
110
  return String(text || '')
138
111
  .replace(DEFERRED_TOOLS_BLOCK_RE, '')
@@ -144,9 +117,8 @@ export function stripDeferredToolManifestBlock(text) {
144
117
  // Rebuild path: replace the FIRST previously-injected <available-deferred-tools>
145
118
  // block (with its leading `---` separator) with the fresh manifest IN PLACE, so
146
119
  // the block keeps its original position and no sibling BP2 block (skills
147
- // manifest, agent rules, …) is reordered or dropped. The fresh manifest already
148
- // carries the mcp-instructions companion, so any pre-existing standalone one is
149
- // removed first to avoid duplication.
120
+ // manifest, agent rules, …) is reordered or dropped. A legacy <mcp-instructions>
121
+ // block left by older sessions is removed first.
150
122
  function rebuildDeferredToolManifestBlock(text, manifest) {
151
123
  let out = String(text || '').replace(MCP_INSTRUCTIONS_BLOCK_RE, '');
152
124
  let replaced = false;
@@ -183,24 +155,50 @@ const firstSentence = (text) => {
183
155
  return (flat.split(/(?<=[.!?])\s/, 1)[0] || flat).replace(/[.!?]+$/, '');
184
156
  };
185
157
 
186
- // A catalog connector's curated trigger; else the server's MCP instructions;
187
- // else its tools' leading descriptions (name order); else its name. Derived
188
- // lines are compacted to at most 100 characters, ellipsis included.
158
+ // A catalog connector's curated when-to-use trigger; else the first sentence
159
+ // of the server's MCP instructions; else its tools' leading descriptions (name
160
+ // order); else its name. Never the account: that is part of the instruction
161
+ // text delivered when the server's tools are loaded. Derived lines are
162
+ // compacted to at most 98 characters, ellipsis included.
189
163
  function mcpServerTrigger(server, tools, instructions) {
190
- // A multi-account connector's server names its account (connector-accounts.mjs).
191
- const account = accountOfInstructions(instructions?.[server]);
192
164
  const curated = connectorTrigger(server);
193
- if (curated) return account ? `${curated} Account: ${account}` : curated;
194
- const instruction = String(instructions?.[server] || '').replace(/[<>]/g, '');
165
+ if (curated) return curated;
166
+ // The account sentence ("Account: <address>. …", connector-accounts.mjs) is
167
+ // not a use line; it reaches the model with the loaded server's instructions.
168
+ const instruction = String(instructions?.[server] || '')
169
+ .replace(/[<>]/g, '')
170
+ .replace(/^\s*Account: \S+?\.\s+/, '');
195
171
  const described = [...tools]
196
172
  .sort((a, b) => String(a?.name || '').localeCompare(String(b?.name || '')))
197
173
  .map((tool) => firstSentence(String(tool?.description || '').replace(/[<>]/g, '')))
198
174
  .filter(Boolean)
199
175
  .join('; ');
200
- const derived = instruction.trim() ? instruction : described;
176
+ const derived = firstSentence(instruction) || described;
201
177
  return derived ? compactPromptManifestText(derived, 98) : `${titleCaseMcpServer(server)} MCP server tools.`;
202
178
  }
203
179
 
180
+ // `mcp__<server>` as the manifest names it, or '' for an unsafe server name.
181
+ export const mcpServerManifestLabel = (server) => sanitizeManifestName(mcpServerLineName(server));
182
+
183
+ /**
184
+ * One `{ label: 'mcp__<server>', trigger }` line per server of `servers` (Map
185
+ * server → its tools). The BP2 manifest and the deferred-tool delta both
186
+ * render these; a server with an unsafe name gets none.
187
+ */
188
+ export function mcpServerLines(servers, instructions) {
189
+ const lines = [];
190
+ for (const [server, tools] of servers) {
191
+ const label = mcpServerManifestLabel(server);
192
+ if (!label) continue;
193
+ const trigger = compactPromptManifestText(
194
+ String(mcpServerTrigger(server, tools, instructions)).replace(/[<>]/g, ''),
195
+ SKILL_TRIGGER_LISTING_MAX
196
+ );
197
+ lines.push({ label, trigger });
198
+ }
199
+ return lines;
200
+ }
201
+
204
202
  function deferredManifestEntries(session, pool) {
205
203
  const byName = catalogByName(session);
206
204
  const skillRoutedNames = skillRoutedToolNames(session.messages);
@@ -221,11 +219,8 @@ function deferredManifestEntries(session, pool) {
221
219
  description: skillRoutedNames.has(key) ? '' : String(tool?.trigger || tool?.description || ''),
222
220
  });
223
221
  }
224
- for (const [server, tools] of servers) {
225
- entries.push({
226
- name: mcpServerLineName(server),
227
- description: mcpServerTrigger(server, tools, session.mcpServerInstructions),
228
- });
222
+ for (const line of mcpServerLines(servers, session.mcpServerInstructions)) {
223
+ entries.push({ name: line.label, description: line.trigger });
229
224
  }
230
225
  return entries;
231
226
  }
@@ -241,7 +236,7 @@ export function listedDeferredToolNames(session, poolNames, rendered) {
241
236
  }
242
237
 
243
238
  function composeDeferredBp2Manifest(session, pool) {
244
- // Bridge exposure gets the native roster and MCP instructions (the pool is
239
+ // Bridge exposure gets the native roster (the pool is
245
240
  // already limited to what the session's policy lets it run), followed by
246
241
  // the static bridge guidance. A tool-free bridge session gets nothing.
247
242
  const bridge = isToolBridgeEnabled(session);
@@ -249,8 +244,6 @@ function composeDeferredBp2Manifest(session, pool) {
249
244
  const parts = [];
250
245
  const deferredManifest = buildDeferredToolManifest(deferredManifestEntries(session, pool));
251
246
  if (deferredManifest) parts.push(deferredManifest);
252
- const mcpManifest = buildMcpInstructionsManifest(session.mcpServerInstructions, pool, session.deferredToolCatalog);
253
- if (mcpManifest) parts.push(mcpManifest);
254
247
  if (bridge) parts.push(TOOL_BRIDGE_GUIDANCE);
255
248
  return parts.join('\n\n');
256
249
  }
@@ -292,6 +285,18 @@ function resolveDeferredBp2Index(session) {
292
285
  return idx;
293
286
  }
294
287
 
288
+ /**
289
+ * Give BP2 `stableContext` (profile meta + skill manifest) and no deferred
290
+ * manifest. The caller re-applies that manifest
291
+ * (applyInitialDeferredToolManifestToBp2 with `rebuild`), which appends it
292
+ * after the stable part exactly as session creation laid it out.
293
+ */
294
+ export function resetBp2StableContext(session, stableContext) {
295
+ if (!Array.isArray(session?.messages)) return;
296
+ const idx = resolveDeferredBp2Index(session);
297
+ if (idx >= 0) setSystemContent(session, idx, String(stableContext || ''));
298
+ }
299
+
295
300
  function writeDeferredManifestAt(session, idx, manifest, rebuild) {
296
301
  for (let i = 0; i < session.messages.length; i++) {
297
302
  if (i === idx || session.messages[i]?.role !== 'system' || typeof session.messages[i].content !== 'string')
@@ -304,6 +304,31 @@ export function skillOutsideScopeError(name) {
304
304
  return `Error: skill "${name}" is not available in this project (its project scope excludes the current folder)`;
305
305
  }
306
306
 
307
+ /** Why the Skill tool refuses `name` at `cwd` now (missing feature, switched
308
+ * off, outside the project scope), or null when nothing refuses it. `config`
309
+ * and `skills` let a batch of checks share one config read and one scan. */
310
+ export function skillLoadRefusal(name, cwd, config = null, skills = null) {
311
+ const missing = skillMissingFeature(name, config, skills);
312
+ if (missing) {
313
+ return `Error: skill "${name}" needs the ${missing} built-in feature, which is not installed or is switched off in Extensions → Plugin → Built-in`;
314
+ }
315
+ if (isSkillDisabled(name, config, skills)) return `Error: skill "${name}" is disabled`;
316
+ if (skillOutsideScope(name, cwd, config, skills)) return skillOutsideScopeError(name);
317
+ return null;
318
+ }
319
+
320
+ /**
321
+ * What the Skill tool would deliver for `name` at `cwd` now: the injected
322
+ * `<skill>` body and the resource it came from, or null when the skill no
323
+ * longer loads there (refused by skillLoadRefusal, or removed).
324
+ */
325
+ export function currentSkillDelivery(name, cwd, config = null, skills = null) {
326
+ if (skillLoadRefusal(name, cwd, config, skills)) return null;
327
+ const resource = loadSkillResource(name, cwd);
328
+ if (!resource) return null;
329
+ return { body: buildSkillResultEnvelope(name, resource.content, resource.dir), resource };
330
+ }
331
+
307
332
  /**
308
333
  * Skills a session may see: not switched off, feature available, and — when
309
334
  * `cwd` is given — inside the skill's (or its plugin's) project scope. Without
@@ -348,11 +373,16 @@ const _skillsCache = new Map();
348
373
  // every plain-file gate path are stat'ed on every call as before.
349
374
  const _ROOT_WATCH_LIMIT = 64;
350
375
  const _rootWatches = new Map(); // root -> { watcher, dirty, text, settled }
376
+ // Bumped by every closed root watch; part of each cached SKILL.md text stamp.
377
+ let _skillTextGeneration = 0;
351
378
 
352
379
  function _closeRootWatch(root) {
353
380
  const watch = _rootWatches.get(root);
354
381
  if (!watch) return;
355
382
  _rootWatches.delete(root);
383
+ // Any change under a skill root (or a lost watcher, or an invalidation)
384
+ // makes the next load re-read SKILL.md text, whatever the file stamps say.
385
+ _skillTextGeneration += 1;
356
386
  try {
357
387
  watch.watcher.close();
358
388
  } catch {
@@ -452,6 +482,26 @@ export function skillExecutionDir(dir, source) {
452
482
  return String(dir).replace(/([\\/])runtime\.asar(?=[\\/]|$)/, '$1runtime.asar.unpacked');
453
483
  }
454
484
 
485
+ // SKILL.md text per catalog entry, with the stamp it was read at: the
486
+ // file's own mtime and size (any depth) plus the skills watcher generation
487
+ // (an edit that keeps both, e.g. inside one timestamp tick).
488
+ const _skillFileText = new WeakMap();
489
+
490
+ function skillFileText(skill) {
491
+ let stamp;
492
+ try {
493
+ const stat = statSync(skill.filePath);
494
+ stamp = `${_skillTextGeneration}:${stat.mtimeMs}:${stat.size}`;
495
+ } catch {
496
+ return null;
497
+ }
498
+ const cached = _skillFileText.get(skill);
499
+ if (cached?.stamp === stamp) return cached.text;
500
+ const text = readSafe(skill.filePath);
501
+ _skillFileText.set(skill, { stamp, text });
502
+ return text;
503
+ }
504
+
455
505
  /**
456
506
  * Load full skill content plus its on-disk directory (for base-dir + ${MIXDOG_SKILL_DIR};
457
507
  * ${MIXDOG_SKILL_NODE} is resolved at render time in buildSkillResultEnvelope).
@@ -462,7 +512,10 @@ export function loadSkillResource(name, cwd) {
462
512
  const skills = collectSkillsCached(cwd);
463
513
  const skill = skills.find((s) => s.name === skillName);
464
514
  if (!skill) return null;
465
- const content = readSafe(skill.filePath);
515
+ // The catalog's freshness signature does not see every nested SKILL.md, so
516
+ // the text follows the file itself: one stat per load, re-read only when
517
+ // that file's own mtime or size moved.
518
+ const content = skillFileText(skill);
466
519
  if (content == null) return null;
467
520
  try {
468
521
  return {
@@ -203,7 +203,7 @@ export function readProjectConnectorsFile(path) {
203
203
  });
204
204
  }
205
205
 
206
- export function projectConnectorPolicies(dataDir = resolvePluginData()) {
206
+ function projectConnectorPolicies(dataDir = resolvePluginData()) {
207
207
  return cachedRead(join(dataDir, PROJECT_CONNECTORS_FILE), readProjectConnectorsFile);
208
208
  }
209
209
 
@@ -219,7 +219,7 @@ export function connectorToolPermission(policy, toolOrName) {
219
219
  return policy?.tools?.[mcp.server]?.[mcp.tool] || connectorToolDefault(serverConnector(mcp.server), mcp.tool);
220
220
  }
221
221
 
222
- const PERMISSION_RANK = Object.freeze({ disabled: 0, needs_approval: 1, auto: 2 });
222
+ export const PERMISSION_RANK = Object.freeze({ disabled: 0, needs_approval: 1, auto: 2 });
223
223
  export const NO_WIDEN_MESSAGE = 'This change would widen what the bot may do without asking; make it on the desktop.';
224
224
 
225
225
  /** Whether replacing the stored policy `before` (a stored value, a parsed
@@ -229,7 +229,7 @@ export const NO_WIDEN_MESSAGE = 'This change would widen what the bot may do wit
229
229
  * default, which is `needs_approval` for a catalog connector's high-risk
230
230
  * tools and `auto` otherwise). A legacy allow list or an unreadable policy is
231
231
  * not compared: it counts as widening. */
232
- export function connectorPolicyWidens(before, after) {
232
+ function connectorPolicyWidens(before, after) {
233
233
  let old;
234
234
  try {
235
235
  old = before == null ? { off: [], tools: {} } : parseConnectorPolicy(before);