@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2

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 (596) hide show
  1. package/AGENTS.md +9 -3
  2. package/ARCHITECTURE.md +32 -10
  3. package/docs/architecture/memory.md +43 -5
  4. package/docs/architecture/security.md +96 -152
  5. package/docs/architecture/turn-actor.md +70 -0
  6. package/docs/browser-use-architecture-phase2.md +128 -56
  7. package/docs/flux-turn-detection-spike.md +248 -0
  8. package/docs/guardian-request-flow.md +35 -0
  9. package/docs/stt-provider-onboarding.md +3 -1
  10. package/knip.json +3 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  24. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  25. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  26. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  27. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  28. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  29. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  30. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  31. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  32. package/openapi.yaml +289 -113
  33. package/package.json +1 -1
  34. package/scripts/voice-ttft-spike.ts +3 -3
  35. package/scripts/write-plugin-api-shim.ts +10 -0
  36. package/src/__tests__/app-compiler.test.ts +38 -3
  37. package/src/__tests__/app-control-flow.test.ts +1 -0
  38. package/src/__tests__/approval-routes-http.test.ts +2 -2
  39. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  40. package/src/__tests__/attachments-store.test.ts +21 -12
  41. package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
  42. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  43. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  44. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  45. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  46. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  47. package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
  48. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  49. package/src/__tests__/checker.test.ts +20 -314
  50. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  51. package/src/__tests__/compaction-events.test.ts +8 -10
  52. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  53. package/src/__tests__/config-schema.test.ts +25 -10
  54. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  55. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  56. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  57. package/src/__tests__/conversation-agent-loop.test.ts +32 -21
  58. package/src/__tests__/conversation-attachments.test.ts +0 -1
  59. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  61. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  63. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  64. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  65. package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
  66. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  67. package/src/__tests__/conversation-queue.test.ts +154 -4
  68. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  69. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  71. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  75. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  82. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  83. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  85. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  86. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  87. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  88. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  89. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  90. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  91. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  92. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  93. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  94. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  95. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  96. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  97. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  98. package/src/__tests__/document-sync-tags.test.ts +0 -75
  99. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  100. package/src/__tests__/edit-propagation.test.ts +107 -11
  101. package/src/__tests__/file-ops-service.test.ts +163 -30
  102. package/src/__tests__/filesystem-tools.test.ts +23 -24
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  104. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  105. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  106. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  107. package/src/__tests__/host-file-read-tool.test.ts +16 -19
  108. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  109. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  110. package/src/__tests__/injector-chain.test.ts +63 -41
  111. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  112. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  113. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  114. package/src/__tests__/llm-schema.test.ts +5 -2
  115. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  116. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  117. package/src/__tests__/media-generate-image.test.ts +131 -21
  118. package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
  119. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  120. package/src/__tests__/mtime-cache.test.ts +3 -1
  121. package/src/__tests__/non-member-access-request.test.ts +0 -20
  122. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  123. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  124. package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
  125. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  126. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  127. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  128. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  129. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  130. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
  131. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  132. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  133. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  134. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  135. package/src/__tests__/qdrant-manager.test.ts +14 -1
  136. package/src/__tests__/reaction-persistence.test.ts +200 -7
  137. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  138. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  139. package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
  140. package/src/__tests__/run-due-schedules.test.ts +21 -0
  141. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  142. package/src/__tests__/schedule-routes.test.ts +23 -0
  143. package/src/__tests__/schedule-store.test.ts +17 -0
  144. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  145. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  146. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  147. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  148. package/src/__tests__/skills.test.ts +32 -0
  149. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  150. package/src/__tests__/starter-task-flow.test.ts +5 -4
  151. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  152. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  153. package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
  154. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  155. package/src/__tests__/subagent-tools.test.ts +81 -101
  156. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  157. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  158. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  159. package/src/__tests__/tool-executor.test.ts +68 -39
  160. package/src/__tests__/turn-events-store.test.ts +43 -0
  161. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  162. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  163. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  164. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  165. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  166. package/src/__tests__/user-plugin-loader.test.ts +3 -1
  167. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  168. package/src/__tests__/visible-app-context.test.ts +16 -9
  169. package/src/__tests__/voice-config-update.test.ts +40 -0
  170. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  171. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  172. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  173. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  174. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  175. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  176. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  177. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  178. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  179. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  180. package/src/acp/__tests__/auth-required.test.ts +161 -0
  181. package/src/acp/acp-claude-oauth.ts +19 -2
  182. package/src/acp/agent-process.test.ts +100 -0
  183. package/src/acp/agent-process.ts +29 -26
  184. package/src/acp/auth-required.ts +102 -0
  185. package/src/acp/session-manager.test.ts +119 -0
  186. package/src/acp/session-manager.ts +76 -3
  187. package/src/api/events/acp-auth-required.ts +55 -0
  188. package/src/api/events/host-file.ts +2 -2
  189. package/src/api/index.ts +7 -0
  190. package/src/api/surfaces.ts +12 -3
  191. package/src/apps/app-store.ts +3 -0
  192. package/src/bundler/package-resolver.ts +2 -30
  193. package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
  194. package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
  195. package/src/calls/call-controller.ts +19 -3
  196. package/src/calls/call-setup-flow.ts +0 -1
  197. package/src/calls/media-stream-stt-session.ts +15 -0
  198. package/src/calls/voice-session-bridge.ts +115 -41
  199. package/src/calls/voice-triage-escalate.ts +105 -2
  200. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  201. package/src/channels/config.ts +13 -0
  202. package/src/channels/plugin-channel-declarations.ts +108 -0
  203. package/src/channels/types.ts +30 -0
  204. package/src/cli/AGENTS.md +5 -2
  205. package/src/cli/bundled-modules.ts +29 -0
  206. package/src/cli/commands/credentials.help.ts +2 -2
  207. package/src/cli/commands/db/repair.ts +4 -8
  208. package/src/cli/commands/domain.ts +6 -3
  209. package/src/cli/commands/email.ts +6 -3
  210. package/src/cli/commands/inference-providers.ts +1 -1
  211. package/src/cli/commands/keys.ts +8 -3
  212. package/src/cli/commands/mcp.help.ts +13 -4
  213. package/src/cli/commands/mcp.ts +9 -0
  214. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  215. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  216. package/src/cli/commands/memory/index.help.ts +84 -28
  217. package/src/cli/commands/memory/index.ts +2 -0
  218. package/src/cli/commands/memory/memory-v2.ts +58 -54
  219. package/src/cli/commands/memory/memory-v3.ts +64 -0
  220. package/src/cli/commands/memory/memory-validate.ts +18 -0
  221. package/src/cli/commands/plugins.ts +85 -36
  222. package/src/cli/commands/schedules.ts +35 -1
  223. package/src/cli/commands/stt.help.ts +27 -2
  224. package/src/cli/commands/trust.ts +3 -14
  225. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  226. package/src/cli/lib/bundled-marketplace.json +14 -1
  227. package/src/cli/lib/upgrade-plugin.ts +42 -0
  228. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  229. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  230. package/src/config/__tests__/default-provider.test.ts +6 -1
  231. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  232. package/src/config/assistant-feature-flags.ts +36 -15
  233. package/src/config/balanced-model-experiment.ts +35 -0
  234. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  235. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  236. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  237. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  238. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  240. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  241. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  242. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  243. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  244. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  245. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  246. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  247. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  248. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  249. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  250. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  251. package/src/config/call-site-defaults.ts +11 -5
  252. package/src/config/default-profile-catalog.ts +179 -16
  253. package/src/config/default-profile-names.ts +4 -1
  254. package/src/config/default-provider-resolution.ts +4 -0
  255. package/src/config/feature-flag-registry.json +12 -12
  256. package/src/config/llm-context-resolution.ts +11 -3
  257. package/src/config/llm-resolver.ts +28 -1
  258. package/src/config/profile-materialization.ts +70 -22
  259. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  260. package/src/config/schemas/call-site-catalog.ts +4 -4
  261. package/src/config/schemas/live-voice.ts +57 -23
  262. package/src/config/schemas/llm.ts +59 -32
  263. package/src/config/schemas/mcp.ts +23 -0
  264. package/src/config/schemas/plugin-updates.ts +6 -2
  265. package/src/config/schemas/stt.ts +1 -0
  266. package/src/config/skills.ts +9 -2
  267. package/src/context/outbound-sanitize.ts +96 -1
  268. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  269. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  270. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  271. package/src/daemon/conversation-agent-loop.ts +31 -19
  272. package/src/daemon/conversation-messaging.ts +5 -1
  273. package/src/daemon/conversation-notifiers.ts +20 -10
  274. package/src/daemon/conversation-process.ts +20 -6
  275. package/src/daemon/conversation-runtime-assembly.ts +12 -6
  276. package/src/daemon/conversation-store.ts +14 -4
  277. package/src/daemon/conversation-surfaces.ts +75 -26
  278. package/src/daemon/conversation-tool-setup.ts +3 -6
  279. package/src/daemon/conversation.ts +154 -51
  280. package/src/daemon/doordash-steps.ts +2 -2
  281. package/src/daemon/lifecycle.ts +14 -1
  282. package/src/daemon/mcp-reload-service.ts +36 -6
  283. package/src/daemon/process-message.ts +13 -26
  284. package/src/daemon/providers-setup.ts +6 -3
  285. package/src/daemon/trust-context-types.ts +29 -0
  286. package/src/daemon/trust-context.ts +12 -7
  287. package/src/daemon/wake-conversation-ops.ts +3 -2
  288. package/src/daemon/windows-compiled-entry.ts +4 -0
  289. package/src/documents/document-store.ts +143 -240
  290. package/src/hooks/hook-loader.ts +3 -3
  291. package/src/hooks/registry.ts +103 -45
  292. package/src/hooks/types.ts +5 -0
  293. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  294. package/src/inbound/oauth-callback-url.ts +61 -0
  295. package/src/ipc/gateway-client.test.ts +1 -1
  296. package/src/ipc/gateway-client.ts +14 -21
  297. package/src/ipc/gateway-flag-listener.ts +17 -3
  298. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  299. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  300. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
  301. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  302. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  303. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  304. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  305. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  306. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  307. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  308. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  309. package/src/live-voice/live-voice-archive.ts +2 -0
  310. package/src/live-voice/live-voice-manager.ts +16 -3
  311. package/src/live-voice/live-voice-metrics.ts +57 -32
  312. package/src/live-voice/live-voice-photo.ts +1 -2
  313. package/src/live-voice/live-voice-session.ts +598 -323
  314. package/src/live-voice/progress-narration.ts +277 -0
  315. package/src/live-voice/protocol.ts +21 -1
  316. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  317. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  318. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  319. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  320. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  321. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  322. package/src/mcp/client.ts +16 -11
  323. package/src/mcp/effective-config.ts +113 -0
  324. package/src/mcp/manager.ts +11 -6
  325. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  326. package/src/mcp/mcp-oauth-provider.ts +205 -240
  327. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  328. package/src/monitoring/control.ts +1 -0
  329. package/src/monitoring/db-integrity-sample.ts +4 -5
  330. package/src/monitoring/plugin-auto-update.ts +128 -24
  331. package/src/notifications/AGENTS.md +2 -0
  332. package/src/notifications/approval-card-data.ts +33 -0
  333. package/src/notifications/signal.ts +1 -0
  334. package/src/permissions/AGENTS.md +16 -0
  335. package/src/permissions/checker.test.ts +75 -122
  336. package/src/permissions/checker.ts +44 -560
  337. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  338. package/src/permissions/confirmation-guardian-request.ts +2 -2
  339. package/src/permissions/prompter.ts +1 -5
  340. package/src/permissions/question-guardian-request.test.ts +14 -6
  341. package/src/permissions/question-guardian-request.ts +1 -2
  342. package/src/persistence/attachments-store.ts +8 -1
  343. package/src/persistence/bookmark-crud.ts +3 -7
  344. package/src/persistence/conversation-attention-store.ts +16 -45
  345. package/src/persistence/conversation-crud.ts +33 -4
  346. package/src/persistence/conversation-lineage.ts +9 -0
  347. package/src/persistence/conversation-queries.ts +182 -52
  348. package/src/persistence/delivery-crud.ts +150 -36
  349. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  350. package/src/persistence/external-conversation-store.ts +32 -4
  351. package/src/persistence/llm-request-log-store.ts +4 -10
  352. package/src/persistence/llm-usage-store.ts +8 -3
  353. package/src/persistence/message-reads.test.ts +197 -0
  354. package/src/persistence/message-reads.ts +211 -0
  355. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  356. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  357. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  358. package/src/persistence/real-user-turn-filter.ts +27 -3
  359. package/src/persistence/schema/documents.ts +4 -4
  360. package/src/persistence/steps.ts +9 -0
  361. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  362. package/src/plugin-api/constants.ts +8 -0
  363. package/src/plugin-api/conversation-turn.ts +195 -5
  364. package/src/plugin-api/index.ts +38 -6
  365. package/src/plugin-api/resolve-credential.ts +3 -2
  366. package/src/plugin-api/store-credential.ts +146 -0
  367. package/src/plugin-api/vision-support.test.ts +1 -1
  368. package/src/plugin-api/webhook-url.ts +13 -11
  369. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  370. package/src/plugins/defaults/main.ts +21 -7
  371. package/src/plugins/defaults/memory/AGENTS.md +15 -3
  372. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  373. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  374. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  375. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  376. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  377. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  378. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  379. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  380. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  381. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  382. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  383. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  384. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
  385. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  386. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  387. package/src/plugins/defaults/memory/indexer.ts +3 -1
  388. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  389. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  390. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  391. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  392. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  393. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
  394. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  395. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  396. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  397. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  398. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  399. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  400. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
  401. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
  402. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  403. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  404. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  405. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  406. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  407. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  408. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  409. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
  410. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  411. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  412. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  413. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  414. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  415. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  416. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  417. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  418. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  419. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  420. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  421. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  422. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  423. package/src/plugins/defaults/memory/worker.ts +6 -3
  424. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  425. package/src/plugins/external-plugin-loader.ts +47 -0
  426. package/src/plugins/mcp-servers.ts +361 -0
  427. package/src/plugins/mtime-cache.ts +37 -46
  428. package/src/plugins/pipeline.ts +14 -7
  429. package/src/plugins/plugin-execution-context.ts +35 -11
  430. package/src/plugins/worker-plugin-surface.ts +33 -0
  431. package/src/prompts/templates/system-sections.ts +0 -7
  432. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  433. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  434. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  435. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  436. package/src/providers/__tests__/retry-callsite.test.ts +25 -2
  437. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  438. package/src/providers/anthropic/client.ts +332 -241
  439. package/src/providers/call-site-routing.ts +30 -3
  440. package/src/providers/connection-resolution.ts +194 -11
  441. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
  442. package/src/providers/inference/adapter-factory.ts +3 -0
  443. package/src/providers/inference/auth.ts +6 -0
  444. package/src/providers/inference/connection-availability.ts +24 -2
  445. package/src/providers/inference/connections.ts +2 -0
  446. package/src/providers/model-catalog.ts +3 -3
  447. package/src/providers/model-intents.ts +28 -8
  448. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
  449. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  450. package/src/providers/openai/chat-completions-provider.ts +133 -36
  451. package/src/providers/openai/codex-models.ts +2 -1
  452. package/src/providers/provider-send-message.ts +32 -3
  453. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  454. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
  455. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  456. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  457. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  458. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
  459. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  460. package/src/providers/speech-to-text/resolve.ts +25 -2
  461. package/src/routes/control.ts +1 -0
  462. package/src/routes/route-host-client.ts +1 -0
  463. package/src/routes/route-host-protocol.ts +7 -0
  464. package/src/routes/worker.ts +48 -15
  465. package/src/runtime/AGENTS.md +16 -17
  466. package/src/runtime/access-request-helper.ts +9 -12
  467. package/src/runtime/agent-wake.ts +18 -15
  468. package/src/runtime/effective-capabilities.test.ts +43 -0
  469. package/src/runtime/effective-capabilities.ts +27 -0
  470. package/src/runtime/guardian-reply-router.ts +6 -2
  471. package/src/runtime/pre-first-message-gate.ts +4 -0
  472. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  473. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
  474. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  475. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  476. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  477. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  478. package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
  479. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  480. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  481. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  482. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  483. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
  484. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  485. package/src/runtime/routes/channel-route-shared.ts +0 -6
  486. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  487. package/src/runtime/routes/conversation-list-routes.ts +181 -23
  488. package/src/runtime/routes/conversation-management-routes.ts +17 -12
  489. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  490. package/src/runtime/routes/conversation-routes.ts +43 -29
  491. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  492. package/src/runtime/routes/credential-routes.ts +26 -82
  493. package/src/runtime/routes/default-provider-routes.ts +15 -0
  494. package/src/runtime/routes/documents-routes.ts +3 -222
  495. package/src/runtime/routes/inbound-message-handler.ts +21 -45
  496. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  497. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  498. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  499. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  500. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  501. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
  502. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  503. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
  504. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
  505. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
  506. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  507. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  508. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  509. package/src/runtime/routes/log-export-routes.ts +3 -0
  510. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  511. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  512. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  513. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  514. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  515. package/src/runtime/routes/plugins-routes.ts +38 -8
  516. package/src/runtime/routes/schedule-routes.ts +94 -7
  517. package/src/runtime/routes/settings-routes.ts +6 -6
  518. package/src/runtime/routes/stt-routes.ts +31 -25
  519. package/src/runtime/routes/surface-action-routes.ts +20 -19
  520. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  521. package/src/runtime/routes/user-route-dispatcher.ts +69 -15
  522. package/src/runtime/routes/user-route-import.ts +108 -0
  523. package/src/runtime/routes/user-route-resolution.ts +18 -1
  524. package/src/runtime/routes/workspace-routes.ts +0 -9
  525. package/src/runtime/routes/workspace-utils.ts +3 -13
  526. package/src/runtime/services/conversation-serializer.ts +7 -2
  527. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  528. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  529. package/src/schedule/plugin-schedule-availability.ts +58 -0
  530. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  531. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  532. package/src/schedule/schedule-store.ts +5 -1
  533. package/src/schedule/scheduler.ts +9 -4
  534. package/src/schedule/worker-control.ts +1 -0
  535. package/src/schedule/worker.ts +6 -0
  536. package/src/security/oauth2.ts +6 -22
  537. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  538. package/src/stt/__tests__/types.test.ts +94 -0
  539. package/src/stt/daemon-batch-transcriber.ts +10 -0
  540. package/src/stt/stt-stream-session.ts +8 -4
  541. package/src/stt/types.ts +103 -0
  542. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  543. package/src/subagent/consult-context.ts +11 -11
  544. package/src/subagent/consult-prompt.ts +26 -35
  545. package/src/subagent/manager.ts +21 -40
  546. package/src/subagent/notify.ts +7 -1
  547. package/src/subagent/types.ts +15 -12
  548. package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
  549. package/src/tools/acp/spawn.test.ts +97 -0
  550. package/src/tools/acp/spawn.ts +38 -4
  551. package/src/tools/credentials/ref-parse.ts +35 -0
  552. package/src/tools/credentials/resolve.ts +4 -10
  553. package/src/tools/credentials/store.ts +168 -0
  554. package/src/tools/document/document-tool.ts +12 -3
  555. package/src/tools/executor.ts +53 -15
  556. package/src/tools/filesystem/read.ts +27 -10
  557. package/src/tools/host-filesystem/read.ts +27 -15
  558. package/src/tools/network/url-safety.ts +9 -16
  559. package/src/tools/permission-checker.ts +43 -52
  560. package/src/tools/registry.ts +2 -1
  561. package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
  562. package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
  563. package/src/tools/shared/filesystem/types.ts +5 -5
  564. package/src/tools/skills/scaffold-managed.ts +25 -7
  565. package/src/tools/subagent/spawn.ts +28 -88
  566. package/src/tools/tool-types.ts +26 -13
  567. package/src/tools/types.ts +6 -6
  568. package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
  569. package/src/tools/workflows/run-workflow.ts +1 -2
  570. package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
  571. package/src/tts/reasoning-tag-filter.ts +89 -0
  572. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  573. package/src/util/logger.ts +16 -0
  574. package/src/util/think-tag-stream.ts +95 -0
  575. package/src/util/worker-process.ts +37 -4
  576. package/src/windows-compiled-cli.ts +32 -0
  577. package/src/windows-compiled-entry.ts +4 -0
  578. package/src/windows-compiled-logger.ts +6 -0
  579. package/src/windows-compiled-worker-entry.ts +29 -0
  580. package/src/workspace/byok-default-profile-ensure.ts +76 -24
  581. package/src/workspace/custom-profile-ensure.ts +4 -24
  582. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  583. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  584. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  585. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  586. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  587. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  588. package/src/workspace/migrations/registry.ts +10 -0
  589. package/src/workspace/provider-commit-message-generator.ts +7 -5
  590. package/src/__tests__/document-workspace-file.test.ts +0 -467
  591. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  592. package/src/live-voice/front-decision.ts +0 -476
  593. package/src/permissions/ipc-risk-types.ts +0 -143
  594. package/src/permissions/risk-types.ts +0 -76
  595. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  596. package/src/subagent/consult-transcript.ts +0 -90
@@ -19,6 +19,7 @@ import type { McpConfig, McpServerConfig } from "../../config/schemas/mcp.js";
19
19
  import { estimateToolDefinitionTokens } from "../../context/token-estimator.js";
20
20
  import { reloadMcpServers } from "../../daemon/mcp-reload-service.js";
21
21
  import { McpClient } from "../../mcp/client.js";
22
+ import { getMcpServerManager } from "../../mcp/manager.js";
22
23
  import { orchestrateMcpOAuthConnect } from "../../mcp/mcp-auth-orchestrator.js";
23
24
  import { getMcpAuthState } from "../../mcp/mcp-auth-state.js";
24
25
  import {
@@ -30,6 +31,7 @@ import {
30
31
  deleteMcpOAuthCredentials,
31
32
  hasMcpOAuthTokens,
32
33
  } from "../../mcp/mcp-oauth-provider.js";
34
+ import { readPluginMcpServers } from "../../plugins/mcp-servers.js";
33
35
  import { getMcpToolsByServer } from "../../tools/registry.js";
34
36
  import { getLogger } from "../../util/logger.js";
35
37
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
@@ -224,8 +226,33 @@ interface McpServerEntry {
224
226
  authHeaderName?: string;
225
227
  allowedTools?: string[];
226
228
  blockedTools?: string[];
229
+ /**
230
+ * Where the definition came from: the workspace `config.json` or a
231
+ * plugin's `mcp.json`. A plugin server is read-only from the CLI's
232
+ * perspective, since `mcp remove` and `mcp update` act on the workspace
233
+ * config, so the caller needs to be able to tell the two apart.
234
+ */
235
+ source: "workspace" | "plugin";
236
+ /** Plugin that declared this server. Present only when `source` is `plugin`. */
237
+ pluginName?: string;
227
238
  }
228
239
 
240
+ /**
241
+ * Status reported for a plugin-declared server the MCP manager holds no
242
+ * live client for — it has not been started yet, or its connection failed.
243
+ *
244
+ * A plugin server is never health-checked the way a workspace server is.
245
+ * `checkMachineReadableHealth` constructs its own `McpClient`, which
246
+ * resolves `mcp:<serverId>:headers` and `mcp:<serverId>:tokens` from the
247
+ * credential store, and a plugin controls both its server key and its URL,
248
+ * so probing one would send workspace-owned credentials to an endpoint the
249
+ * plugin chose whenever an id happens to match a stored key. Skipping the
250
+ * probe also avoids spawning a plugin's declared stdio command as a side
251
+ * effect of listing. The manager's own client — started with credentials
252
+ * isolated — is the only thing consulted instead.
253
+ */
254
+ const PLUGIN_SERVER_STATUS = "declared";
255
+
229
256
  function detectAuthType(headers: Record<string, string>): "bearer" | "api-key" {
230
257
  const authValue = headers["Authorization"] ?? headers["authorization"];
231
258
  if (authValue?.startsWith("Bearer ")) {
@@ -240,65 +267,129 @@ async function handleMcpList(_args: {
240
267
  const raw = loadRawConfig();
241
268
  const mcpConfig = raw.mcp as Partial<McpConfig> | undefined;
242
269
  const servers = mcpConfig?.servers ?? {};
243
- const entries = Object.entries(servers) as [string, McpServerConfig][];
244
-
245
- const results: McpServerEntry[] = await Promise.all(
246
- entries
247
- .filter(([, config]) => config && typeof config === "object")
248
- .map(async ([id, config]) => {
249
- const enabled = config.enabled !== false;
250
- let status: string;
251
- if (!enabled) {
252
- status = "disabled";
253
- } else {
254
- status = await checkMachineReadableHealth(id, config);
255
- }
256
- const hasOAuth =
257
- config.transport.type !== "stdio"
258
- ? await hasMcpOAuthTokens(id)
259
- : false;
260
-
261
- // Check credential store for stored static auth headers
262
- const storedHeaders = await getMcpHeaders(id);
263
- // Also check legacy config-level headers
264
- const configHeaders =
265
- config.transport.type !== "stdio"
266
- ? config.transport.headers
267
- : undefined;
268
- const effectiveHeaders = storedHeaders ?? configHeaders;
269
- const hasStaticAuth =
270
- !!effectiveHeaders && Object.keys(effectiveHeaders).length > 0;
271
- const authType: "none" | "bearer" | "api-key" = hasStaticAuth
272
- ? detectAuthType(effectiveHeaders!)
273
- : "none";
274
- const authHeaderName =
275
- authType === "api-key" && effectiveHeaders
276
- ? Object.keys(effectiveHeaders).find(
277
- (k) => k.toLowerCase() !== "authorization",
278
- )
279
- : undefined;
280
-
281
- // Strip headers from transport — never return secrets
282
- const { headers: _stripped, ...safeTransport } =
283
- config.transport as Record<string, unknown>;
284
-
285
- return {
286
- id,
287
- status,
288
- transport: safeTransport as McpServerEntry["transport"],
289
- enabled,
290
- defaultRiskLevel: config.defaultRiskLevel ?? "high",
291
- hasOAuth,
292
- hasStaticAuth,
293
- authType,
294
- ...(authHeaderName && { authHeaderName }),
295
- ...(config.allowedTools && { allowedTools: config.allowedTools }),
296
- ...(config.blockedTools && { blockedTools: config.blockedTools }),
297
- };
298
- }),
270
+ const configEntries = (
271
+ Object.entries(servers) as [string, McpServerConfig][]
272
+ ).filter(([, config]) => config && typeof config === "object");
273
+
274
+ const workspaceEntries: McpServerEntry[] = await Promise.all(
275
+ configEntries.map(async ([id, config]) => {
276
+ const enabled = config.enabled !== false;
277
+ let status: string;
278
+ if (!enabled) {
279
+ status = "disabled";
280
+ } else {
281
+ status = await checkMachineReadableHealth(id, config);
282
+ }
283
+ const hasOAuth =
284
+ config.transport.type !== "stdio" ? await hasMcpOAuthTokens(id) : false;
285
+
286
+ // Check credential store for stored static auth headers
287
+ const storedHeaders = await getMcpHeaders(id);
288
+ // Also check legacy config-level headers
289
+ const configHeaders =
290
+ config.transport.type !== "stdio"
291
+ ? config.transport.headers
292
+ : undefined;
293
+ const effectiveHeaders = storedHeaders ?? configHeaders;
294
+ const hasStaticAuth =
295
+ !!effectiveHeaders && Object.keys(effectiveHeaders).length > 0;
296
+ const authType: "none" | "bearer" | "api-key" = hasStaticAuth
297
+ ? detectAuthType(effectiveHeaders!)
298
+ : "none";
299
+ const authHeaderName =
300
+ authType === "api-key" && effectiveHeaders
301
+ ? Object.keys(effectiveHeaders).find(
302
+ (k) => k.toLowerCase() !== "authorization",
303
+ )
304
+ : undefined;
305
+
306
+ // Strip headers from transport, never return secrets
307
+ const { headers: _stripped, ...safeTransport } =
308
+ config.transport as Record<string, unknown>;
309
+
310
+ return {
311
+ id,
312
+ status,
313
+ transport: safeTransport as McpServerEntry["transport"],
314
+ enabled,
315
+ defaultRiskLevel: config.defaultRiskLevel ?? "high",
316
+ hasOAuth,
317
+ hasStaticAuth,
318
+ authType,
319
+ ...(authHeaderName && { authHeaderName }),
320
+ ...(config.allowedTools && { allowedTools: config.allowedTools }),
321
+ ...(config.blockedTools && { blockedTools: config.blockedTools }),
322
+ source: "workspace" as const,
323
+ };
324
+ }),
299
325
  );
300
326
 
301
- return { servers: results };
327
+ return {
328
+ servers: [...workspaceEntries, ...listPluginServerEntries(configEntries)],
329
+ };
330
+ }
331
+
332
+ /**
333
+ * Project plugin-declared servers onto listing entries.
334
+ *
335
+ * Nothing here touches the credential store or the network: a plugin
336
+ * server carries no auth state the assistant owns, and its status comes
337
+ * from the manager's in-memory client rather than a probe (see
338
+ * {@link PLUGIN_SERVER_STATUS}).
339
+ */
340
+ function listPluginServerEntries(
341
+ configEntries: [string, McpServerConfig][],
342
+ ): McpServerEntry[] {
343
+ const { servers, issues } = readPluginMcpServers();
344
+ for (const issue of issues) {
345
+ log.warn(
346
+ {
347
+ plugin: issue.pluginName,
348
+ ...(issue.serverKey && { serverKey: issue.serverKey }),
349
+ },
350
+ `Plugin MCP declaration problem: ${issue.message}`,
351
+ );
352
+ }
353
+
354
+ // A workspace entry outranks a plugin's declaration of the same id: the
355
+ // user's own configuration is the more specific statement, and it is the
356
+ // one they can edit. `buildEffectiveMcpConfig` applies the same
357
+ // precedence, so this listing describes the set the daemon actually runs.
358
+ const workspaceIds = new Set(configEntries.map(([id]) => id));
359
+ const manager = getMcpServerManager();
360
+
361
+ return servers
362
+ .filter((server) => {
363
+ if (!workspaceIds.has(server.id)) {
364
+ return true;
365
+ }
366
+ log.warn(
367
+ { plugin: server.pluginName, serverId: server.id },
368
+ "Plugin MCP server shadowed by a workspace server of the same id; skipping",
369
+ );
370
+ return false;
371
+ })
372
+ .map((server) => {
373
+ const { headers: _stripped, ...safeTransport } = server.config
374
+ .transport as Record<string, unknown>;
375
+ return {
376
+ id: server.id,
377
+ status: manager.getClient(server.id)?.isConnected
378
+ ? "connected"
379
+ : PLUGIN_SERVER_STATUS,
380
+ transport: safeTransport as McpServerEntry["transport"],
381
+ enabled: true,
382
+ defaultRiskLevel: server.config.defaultRiskLevel,
383
+ // A plugin server has no assistant-owned credentials. Resolving
384
+ // these against `mcp:<id>:*` would report, and could disclose, a
385
+ // workspace credential that happens to share the id.
386
+ hasOAuth: false,
387
+ hasStaticAuth: false,
388
+ authType: "none" as const,
389
+ source: "plugin" as const,
390
+ pluginName: server.pluginName,
391
+ };
392
+ });
302
393
  }
303
394
 
304
395
  // ---------------------------------------------------------------------------
@@ -47,6 +47,7 @@ interface MockConversation {
47
47
  contextCompactedAt: number | null;
48
48
  sentMessages: AssistantEvent[];
49
49
  sendToClient: (msg: AssistantEvent) => void;
50
+ emit: (msg: AssistantEvent) => void;
50
51
  getMessages: () => unknown[];
51
52
  }
52
53
 
@@ -64,6 +65,7 @@ function makeConversation(id = "conv-playground-test"): MockConversation {
64
65
  contextCompactedAt: null,
65
66
  sentMessages,
66
67
  sendToClient: (msg) => sentMessages.push(msg),
68
+ emit: (msg) => sentMessages.push(msg),
67
69
  getMessages: () => [],
68
70
  };
69
71
  }
@@ -75,6 +75,9 @@ function makeFakeConversation(
75
75
  sendToClient: (msg: AssistantEvent) => {
76
76
  sent.push(msg);
77
77
  },
78
+ emit: (msg: AssistantEvent) => {
79
+ sent.push(msg);
80
+ },
78
81
  } as unknown as Conversation;
79
82
 
80
83
  return { conversation: fake, sent, state };
@@ -72,7 +72,7 @@ export const ROUTES: RouteDefinition[] = [
72
72
  if (circuitOpenForMs === 0) {
73
73
  if (circuit.compactionCircuitOpenUntil !== null) {
74
74
  circuit.compactionCircuitOpenUntil = null;
75
- conversation.sendToClient({
75
+ conversation.emit({
76
76
  type: "compaction_circuit_closed",
77
77
  conversationId: conversation.conversationId,
78
78
  });
@@ -80,7 +80,7 @@ export const ROUTES: RouteDefinition[] = [
80
80
  } else {
81
81
  const openUntil = Date.now() + circuitOpenForMs;
82
82
  circuit.compactionCircuitOpenUntil = openUntil;
83
- conversation.sendToClient({
83
+ conversation.emit({
84
84
  type: "compaction_circuit_open",
85
85
  conversationId: conversation.conversationId,
86
86
  reason: "3_consecutive_failures",
@@ -52,7 +52,7 @@ export const ROUTES: RouteDefinition[] = [
52
52
  circuit.consecutiveCompactionFailures = 0;
53
53
  if (circuit.compactionCircuitOpenUntil !== null) {
54
54
  circuit.compactionCircuitOpenUntil = null;
55
- conversation.sendToClient({
55
+ conversation.emit({
56
56
  type: "compaction_circuit_closed",
57
57
  conversationId: conversation.conversationId,
58
58
  });
@@ -85,6 +85,7 @@ import {
85
85
  } from "../../cli/lib/uninstall-plugin.js";
86
86
  import {
87
87
  PluginMergeBaselineError,
88
+ PluginNotCuratedError,
88
89
  PluginNotUpgradableError,
89
90
  upgradePlugin,
90
91
  } from "../../cli/lib/upgrade-plugin.js";
@@ -606,6 +607,12 @@ const pluginUpgradeRequestSchema = z.object({
606
607
  .describe(
607
608
  "How to reconcile local edits with the pin. `overwrite` (default) discards local edits and re-installs the pin; `ours`/`theirs` three-way merge, resolving conflicting hunks toward the local edit or the pin respectively; `assistant` is not yet supported.",
608
609
  ),
610
+ marketplaceOnly: z
611
+ .boolean()
612
+ .optional()
613
+ .describe(
614
+ "Refuse the upgrade (409) when the marketplace does not claim this plugin, instead of advancing it to whatever its recorded GitHub ref now resolves to. Set by unattended callers such as the automatic update sweep, for which running code no curator reviewed is never an acceptable outcome. Defaults to false.",
615
+ ),
609
616
  });
610
617
 
611
618
  const pluginUpgradeResponseSchema = z.object({
@@ -1465,6 +1472,11 @@ async function handleUpgradePlugin({
1465
1472
  typeof body.strategy === "string"
1466
1473
  ? (body.strategy as PluginUpgradeStrategy)
1467
1474
  : undefined;
1475
+ // Enforced here, not just by whoever decided to call: `upgradePlugin` fetches
1476
+ // the catalog itself, so an entry that vanishes between a caller's own check
1477
+ // and this handler would otherwise reroute a curated upgrade onto the
1478
+ // install's mutable recorded ref.
1479
+ const marketplaceOnly = body.marketplaceOnly === true;
1468
1480
 
1469
1481
  // Like install, the upgrade target ref is the curated marketplace pin
1470
1482
  // (resolved inside `upgradePlugin` via `inspectPlugin`), never a
@@ -1498,7 +1510,7 @@ async function handleUpgradePlugin({
1498
1510
  // upgraded revision adds is surfaced to the user by the schedule
1499
1511
  // reconciler's `schedule.declared` notification when it arms.
1500
1512
  const result = await upgradePlugin(
1501
- { name, dryRun, strategy },
1513
+ { name, dryRun, strategy, marketplaceOnly },
1502
1514
  {
1503
1515
  fetch: globalThis.fetch.bind(globalThis),
1504
1516
  // Tear the old version down BEFORE the staged tree replaces its
@@ -1568,6 +1580,12 @@ async function handleUpgradePlugin({
1568
1580
  if (err instanceof PluginNotUpgradableError) {
1569
1581
  throw new ConflictError(err.message);
1570
1582
  }
1583
+ // A `marketplaceOnly` caller asked about an install the catalog does not
1584
+ // claim. Retrying changes nothing; only a human upgrading it deliberately
1585
+ // (without the flag) can move it, so 409 rather than a retryable 503.
1586
+ if (err instanceof PluginNotCuratedError) {
1587
+ throw new ConflictError(err.message);
1588
+ }
1571
1589
  // A rate-limited or temporarily-down source (the plugin repo or the
1572
1590
  // marketplace catalog) is a retryable outage, not a conflict — 503.
1573
1591
  if (err instanceof PluginSourceUnavailableError) {
@@ -1625,7 +1643,7 @@ function mapTogglePluginError(err: unknown): RouteError {
1625
1643
 
1626
1644
  /**
1627
1645
  * Converge plugin-declared schedules against the sentinel this route just
1628
- * wrote, so a toggled plugin's rows disarm or re-arm now rather than at the
1646
+ * wrote, so a disabled plugin's rows disarm now rather than at the
1629
1647
  * reconciler's next backstop sweep.
1630
1648
  *
1631
1649
  * Fire-and-forget: the toggle itself already succeeded on disk, so a reconcile
@@ -1653,15 +1671,27 @@ function reconcilePluginSchedulesInBackground(): void {
1653
1671
  * names WHICH resource is stale, not the new value. The origin client id is
1654
1672
  * threaded through for self-echo suppression.
1655
1673
  */
1656
- function handleEnablePlugin({ pathParams = {}, headers }: RouteHandlerArgs) {
1674
+ async function handleEnablePlugin({
1675
+ pathParams = {},
1676
+ headers,
1677
+ }: RouteHandlerArgs) {
1657
1678
  try {
1658
1679
  enablePlugin(pathParams.name ?? "");
1659
- publishPluginsChanged(getOriginClientId(headers));
1660
- reconcilePluginSchedulesInBackground();
1661
- return { ok: true };
1662
1680
  } catch (err) {
1663
1681
  throw mapTogglePluginError(err);
1664
1682
  }
1683
+ // A plugin the boot scan skipped for its sentinel has no hooks, tools or
1684
+ // MCP servers in the caches, so clearing the sentinel is not enough to
1685
+ // bring it up. Run the same imperative source reconcile the install and
1686
+ // upgrade routes use, which activates the plugin and then converges its
1687
+ // schedules and MCP servers. Awaited, like the install route, so a client
1688
+ // that lists tools right after enabling sees the plugin up; the reconcile
1689
+ // contains its own failures, so a broken plugin cannot turn a successful
1690
+ // enable into a route error. The invalidation publishes after it for the
1691
+ // same reason: refetching clients should see the post-activation state.
1692
+ await reconcilePluginSourcesNow();
1693
+ publishPluginsChanged(getOriginClientId(headers));
1694
+ return { ok: true };
1665
1695
  }
1666
1696
 
1667
1697
  function handleDisablePlugin({ pathParams = {}, headers }: RouteHandlerArgs) {
@@ -2039,7 +2069,7 @@ export const ROUTES: RouteDefinition[] = [
2039
2069
  },
2040
2070
  summary: "Upgrade a plugin to its source's current revision",
2041
2071
  description:
2042
- 'Move an installed plugin to its source\'s current revision, re-materializing it under `<workspaceDir>/plugins/<name>/`. A marketplace plugin advances to the curated pin; a plugin installed directly from a GitHub URL (untrusted, not in the marketplace) advances to whatever its recorded ref now resolves to — a pinned SHA is immutable (a no-op), a branch/tag/HEAD moves as upstream does — re-materialized verbatim with no curated adapter overlay, exactly as the original untrusted install was. The target ref is never taken from the request (no caller-supplied ref), mirroring `plugins install`\'s curation boundary. A no-op (`outcome: "already-up-to-date"`) when the installed commit already equals the target; pass `dryRun` to preview the move (`outcome: "would-upgrade"`) without touching the install. Installs lacking provenance are re-pinned to the current SHA. The upgraded code is picked up live on the next read (no restart required). `strategy` controls how local edits are reconciled: `overwrite` (default) discards them and re-installs the target wholesale; `ours`/`theirs`/`assistant` perform a three-way merge against the re-materialized install commit, carrying non-conflicting edits from both sides forward and resolving conflicting hunks toward the local edit (`ours`) or the target (`theirs`), or writing git conflict markers into the file and reporting them in `conflicts`/`binaryConflicts` for the assistant to resolve (`assistant`). A merge strategy whose install-time baseline cannot be reconstructed returns 409. Mirrors the CLI\'s `assistant plugins upgrade <name> [--strategy <s>]`.',
2072
+ 'Move an installed plugin to its source\'s current revision, re-materializing it under `<workspaceDir>/plugins/<name>/`. A marketplace plugin advances to the curated pin; a plugin installed directly from a GitHub URL (untrusted, not in the marketplace) advances to whatever its recorded ref now resolves to — a pinned SHA is immutable (a no-op), a branch/tag/HEAD moves as upstream does — re-materialized verbatim with no curated adapter overlay, exactly as the original untrusted install was. The target ref is never taken from the request (no caller-supplied ref), mirroring `plugins install`\'s curation boundary. A no-op (`outcome: "already-up-to-date"`) when the installed commit already equals the target; pass `dryRun` to preview the move (`outcome: "would-upgrade"`) without touching the install. Installs lacking provenance are re-pinned to the current SHA. The upgraded code is picked up live on the next read (no restart required). `strategy` controls how local edits are reconciled: `overwrite` (default) discards them and re-installs the target wholesale; `ours`/`theirs`/`assistant` perform a three-way merge against the re-materialized install commit, carrying non-conflicting edits from both sides forward and resolving conflicting hunks toward the local edit (`ours`) or the target (`theirs`), or writing git conflict markers into the file and reporting them in `conflicts`/`binaryConflicts` for the assistant to resolve (`assistant`). A merge strategy whose install-time baseline cannot be reconstructed returns 409. Pass `marketplaceOnly` to refuse the untrusted direct path outright (409), which is what the unattended auto-update sweep does. Mirrors the CLI\'s `assistant plugins upgrade <name> [--strategy <s>]`.',
2043
2073
  tags: ["plugins"],
2044
2074
  pathParams: [
2045
2075
  {
@@ -2062,7 +2092,7 @@ export const ROUTES: RouteDefinition[] = [
2062
2092
  },
2063
2093
  "409": {
2064
2094
  description:
2065
- "The install has neither a marketplace entry nor a recorded GitHub source to advance to, or a merge strategy was requested whose install-time baseline cannot be reconstructed.",
2095
+ "The install has neither a marketplace entry nor a recorded GitHub source to advance to, a `marketplaceOnly` upgrade was requested for an install the marketplace does not claim, or a merge strategy was requested whose install-time baseline cannot be reconstructed.",
2066
2096
  },
2067
2097
  "503": {
2068
2098
  description:
@@ -5,6 +5,9 @@
5
5
  * the shared ROUTES array.
6
6
  */
7
7
 
8
+ import { statSync } from "node:fs";
9
+ import { join } from "node:path";
10
+
8
11
  import { z } from "zod";
9
12
 
10
13
  import { getOrCreateConversation } from "../../daemon/conversation-store.js";
@@ -15,9 +18,10 @@ import {
15
18
  getUsageCostForRun,
16
19
  listRunConversationIds,
17
20
  } from "../../persistence/llm-usage-store.js";
21
+ import { isPluginDisabled } from "../../plugins/disabled-state.js";
18
22
  import { isDeferSchedule } from "../../schedule/defer-provenance.js";
19
23
  import { validateScheduleInferenceProfile } from "../../schedule/inference-profile.js";
20
- import { declarationExistsOnDisk } from "../../schedule/plugin-schedule-declarations.js";
24
+ import { pluginScheduleSourceAvailable } from "../../schedule/plugin-schedule-availability.js";
21
25
  import { isPluginSchedulesEnabled } from "../../schedule/plugin-schedules-gate.js";
22
26
  import {
23
27
  describeRRuleExpression,
@@ -38,6 +42,7 @@ import {
38
42
  getLastScheduleConversationId,
39
43
  getSchedule,
40
44
  getScheduleRuns,
45
+ isEngineLatched,
41
46
  listSchedules,
42
47
  resolveScheduleConversationGroupId,
43
48
  type ScheduleJob,
@@ -49,6 +54,7 @@ import { buildWakeScheduleOptions } from "../../schedule/wake-schedule-options.j
49
54
  import { initializeTools } from "../../tools/registry.js";
50
55
  import { UserError } from "../../util/errors.js";
51
56
  import { getLogger } from "../../util/logger.js";
57
+ import { getWorkspacePluginsDir } from "../../util/platform.js";
52
58
  import { normalizeCapabilityManifest } from "../../workflows/capabilities.js";
53
59
  import { getWorkflowRunManager } from "../../workflows/run-manager.js";
54
60
  import { isOwnerCaller } from "../auth/owner-caller.js";
@@ -108,6 +114,76 @@ async function assertWakeMutationAllowed(
108
114
  // Response schemas (shared by all schedule routes)
109
115
  // ---------------------------------------------------------------------------
110
116
 
117
+ const DISARM_REASONS = [
118
+ "user_disabled",
119
+ "plugin_removed",
120
+ "plugin_disabled",
121
+ "declaration_removed",
122
+ "declaration_disabled",
123
+ ] as const;
124
+
125
+ type DisarmReason = (typeof DISARM_REASONS)[number];
126
+
127
+ /**
128
+ * Why a plugin-sourced schedule sits disarmed, so a client can say so instead
129
+ * of showing an off row with no explanation.
130
+ *
131
+ * Only a sourced row that is off can have one, which also bounds the disk
132
+ * probes below to those rows. The first matching cause wins, ordered so the
133
+ * most specific answer comes first: the user's own override outranks anything
134
+ * the plugin's files say, a plugin that is gone outranks one that is merely
135
+ * disabled, and a declaration that is gone outranks one that is still there.
136
+ * `declaration_disabled` is the fallback the plugin's own files account for:
137
+ * a declared `enabled: false`, a declaration too broken to arm, or a manifest
138
+ * that no longer parses.
139
+ *
140
+ * Two off states never get a reason, because in neither did the plugin's files
141
+ * turn the schedule off. A row the engine latched (a fired or cancelled
142
+ * one-shot, a bounded recurrence the claim path exhausted) is a schedule that
143
+ * finished, not one that is paused. And when the `plugin-schedules` kill switch
144
+ * is off the reconciler disarms every sourced row regardless of what its
145
+ * declaration says, so no per-row cause is the true one. Both return null, and
146
+ * clients show the row like any other schedule that is off.
147
+ */
148
+ function deriveDisarmReason(
149
+ job: Pick<
150
+ ScheduleJob,
151
+ | "sourceKey"
152
+ | "enabled"
153
+ | "userEnabled"
154
+ | "status"
155
+ | "nextRunAt"
156
+ | "lastRunAt"
157
+ >,
158
+ ): DisarmReason | null {
159
+ if (job.sourceKey === null || job.enabled) {
160
+ return null;
161
+ }
162
+ if (job.userEnabled === false) {
163
+ return "user_disabled";
164
+ }
165
+ if (isEngineLatched(job) || !isPluginSchedulesEnabled()) {
166
+ return null;
167
+ }
168
+ const match = /^plugin:([^/]+)\/(.+)$/.exec(job.sourceKey);
169
+ if (!match) {
170
+ return null;
171
+ }
172
+ const [, pluginName, scheduleName] = match;
173
+ const pluginDir = join(getWorkspacePluginsDir(), pluginName!);
174
+ if (!statSync(pluginDir, { throwIfNoEntry: false })?.isDirectory()) {
175
+ return "plugin_removed";
176
+ }
177
+ if (isPluginDisabled(pluginName!)) {
178
+ return "plugin_disabled";
179
+ }
180
+ const declarationDir = join(pluginDir, "schedules", scheduleName!);
181
+ if (!statSync(declarationDir, { throwIfNoEntry: false })?.isDirectory()) {
182
+ return "declaration_removed";
183
+ }
184
+ return "declaration_disabled";
185
+ }
186
+
111
187
  const scheduleSchema = z.object({
112
188
  id: z.string(),
113
189
  name: z.string(),
@@ -150,6 +226,12 @@ const scheduleSchema = z.object({
150
226
  .describe(
151
227
  "User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value applies. Always null for user-created schedules.",
152
228
  ),
229
+ disarmReason: z
230
+ .enum(DISARM_REASONS)
231
+ .nullable()
232
+ .describe(
233
+ "Why a plugin-sourced schedule is off: the user turned it off, the plugin is gone, the plugin is disabled, the declaration is gone, or the plugin's own files turned it off. Null whenever the schedule is on, and always null for user-created schedules.",
234
+ ),
153
235
  isOneShot: z.boolean(),
154
236
  // A deferred wake ("remind me about this tomorrow") is an ordinary schedule
155
237
  // row created by the defer path, distinguishable only by `createdBy`, which
@@ -302,6 +384,7 @@ function serializeSchedule(
302
384
  workflowName: j.workflowName,
303
385
  sourceKey: j.sourceKey,
304
386
  userEnabled: j.userEnabled,
387
+ disarmReason: deriveDisarmReason(j),
305
388
  isOneShot: isOneShotForDisplay(j),
306
389
  isDeferred: isDeferSchedule(j.createdBy),
307
390
  };
@@ -1240,15 +1323,19 @@ async function handleRunScheduleNow(
1240
1323
 
1241
1324
  // A plugin-sourced row runs the plugin's own script or prompt, so run-now is
1242
1325
  // only offered while that plugin is something the runtime would activate.
1243
- // `declarationExistsOnDisk` is the same probe the enable path uses: it covers
1244
- // a disabled plugin, an unreadable or invalid manifest, and a declaration
1245
- // that is simply gone. Turning the feature flag off retires the surface
1246
- // wholesale and takes the same path. The row can still be armed at this
1247
- // point, because the reconciler that disarms it runs on its own schedule.
1326
+ // `pluginScheduleSourceAvailable` covers a plugin the daemon never
1327
+ // activated, a disabled plugin, an unreadable or invalid manifest, and a
1328
+ // declaration that is simply gone. Turning the feature flag off retires the
1329
+ // surface wholesale and takes the same path. The row can still be armed at
1330
+ // this point, because the reconciler that disarms it runs on its own
1331
+ // schedule.
1332
+ // Routes are served by the daemon, so the activation half of the probe reads
1333
+ // a live ledger here. Paths that run in worker processes cannot ask that
1334
+ // question and use the on-disk probe alone.
1248
1335
  if (
1249
1336
  schedule.sourceKey !== null &&
1250
1337
  (!isPluginSchedulesEnabled() ||
1251
- !(await declarationExistsOnDisk(schedule.sourceKey)))
1338
+ !(await pluginScheduleSourceAvailable(schedule.sourceKey)))
1252
1339
  ) {
1253
1340
  throw new BadRequestError(
1254
1341
  "This schedule's plugin is disabled or no longer declares it, so it cannot be run.",
@@ -37,7 +37,6 @@ import {
37
37
  import {
38
38
  check,
39
39
  classifyRisk,
40
- generateAllowlistOptions,
41
40
  generateScopeOptions,
42
41
  } from "../../permissions/checker.js";
43
42
  import { resolveGuardianPersonaPath } from "../../prompts/persona-resolver.js";
@@ -710,19 +709,22 @@ async function handleToolPermissionSimulate({ body = {} }: RouteHandlerArgs) {
710
709
  isInteractive === false ? "headless" : "conversation";
711
710
  const policyContext = { executionTarget, executionContext } as const;
712
711
 
713
- const { level: riskLevel } = await classifyRisk(
712
+ const classification = await classifyRisk(
714
713
  toolName,
715
714
  input,
716
715
  workingDir,
717
716
  undefined,
718
717
  manifestOverride,
719
718
  );
719
+ const riskLevel = classification.level;
720
720
  const result = await check(
721
721
  toolName,
722
722
  input,
723
723
  workingDir,
724
724
  policyContext,
725
725
  manifestOverride,
726
+ undefined,
727
+ classification,
726
728
  );
727
729
 
728
730
  if (isInteractive === false && result.decision === "prompt") {
@@ -743,11 +745,9 @@ async function handleToolPermissionSimulate({ body = {} }: RouteHandlerArgs) {
743
745
  | undefined;
744
746
 
745
747
  if (result.decision === "prompt") {
746
- const allowlistOptions = await generateAllowlistOptions(toolName, input);
747
- const scopeOptions = generateScopeOptions(workingDir, toolName);
748
748
  promptPayload = {
749
- allowlistOptions,
750
- scopeOptions,
749
+ allowlistOptions: classification.allowlistOptions ?? [],
750
+ scopeOptions: generateScopeOptions(workingDir, toolName),
751
751
  persistentDecisionsAllowed: true,
752
752
  };
753
753
  }