@vellumai/assistant 0.10.2 → 0.10.3-dev.202606252046.9075fd5

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 (546) hide show
  1. package/bun.lock +20 -0
  2. package/docs/workspace-tools.md +42 -33
  3. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  5. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  6. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
  7. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  8. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  9. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
  12. package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
  13. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/openapi.yaml +196 -56
  15. package/package.json +4 -1
  16. package/scripts/test.sh +36 -15
  17. package/src/__tests__/actor-token-service.test.ts +36 -14
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  19. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  21. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  22. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  23. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  24. package/src/__tests__/approval-cascade.test.ts +2 -0
  25. package/src/__tests__/assistant-attachments.test.ts +42 -0
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  27. package/src/__tests__/btw-routes.test.ts +2 -0
  28. package/src/__tests__/build-persisted-content.test.ts +2 -0
  29. package/src/__tests__/call-controller.test.ts +19 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +21 -26
  31. package/src/__tests__/channel-delivery-store.test.ts +28 -0
  32. package/src/__tests__/channel-guardian.test.ts +164 -78
  33. package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
  34. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  35. package/src/__tests__/code-search-tool.test.ts +585 -0
  36. package/src/__tests__/compaction-events.test.ts +2 -0
  37. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  38. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  39. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  40. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  41. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  43. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  44. package/src/__tests__/config-loader-backfill.test.ts +123 -10
  45. package/src/__tests__/config-schema.test.ts +1 -0
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  47. package/src/__tests__/consult-deadline.test.ts +60 -0
  48. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  49. package/src/__tests__/contact-store-user-file.test.ts +7 -10
  50. package/src/__tests__/contacts-relay-reads.test.ts +19 -24
  51. package/src/__tests__/contacts-write.test.ts +0 -2
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +134 -0
  57. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  58. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  61. package/src/__tests__/conversation-fork-crud.test.ts +354 -24
  62. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  63. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  64. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  65. package/src/__tests__/conversation-pairing.test.ts +2 -0
  66. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  67. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  68. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  69. package/src/__tests__/conversation-queue.test.ts +91 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  72. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  73. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  74. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  75. package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
  76. package/src/__tests__/conversation-title-service.test.ts +2 -0
  77. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  78. package/src/__tests__/conversation-usage.test.ts +2 -0
  79. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  80. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  81. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  82. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  83. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  84. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  85. package/src/__tests__/db-test-helpers.ts +5 -4
  86. package/src/__tests__/delete-propagation.test.ts +5 -3
  87. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  88. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  89. package/src/__tests__/dm-backfill.test.ts +6 -4
  90. package/src/__tests__/dm-persistence.test.ts +2 -0
  91. package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
  92. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  93. package/src/__tests__/exploration-drift-hook.test.ts +3 -2
  94. package/src/__tests__/filing-service.test.ts +2 -0
  95. package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
  96. package/src/__tests__/guardian-dispatch.test.ts +140 -1
  97. package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
  98. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  99. package/src/__tests__/guardian-routing-state.test.ts +6 -10
  100. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  101. package/src/__tests__/heartbeat-service.test.ts +2 -0
  102. package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
  103. package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
  104. package/src/__tests__/helpers/mock-logger.ts +1 -0
  105. package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
  106. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  107. package/src/__tests__/host-bash-routes.test.ts +31 -41
  108. package/src/__tests__/host-browser-routes.test.ts +26 -32
  109. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  110. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  111. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  112. package/src/__tests__/http-user-message-parity.test.ts +290 -8
  113. package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
  114. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  115. package/src/__tests__/invite-redemption-service.test.ts +471 -53
  116. package/src/__tests__/invite-routes-http.test.ts +34 -0
  117. package/src/__tests__/invite-service-ipc.test.ts +65 -2
  118. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  119. package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
  120. package/src/__tests__/llm-usage-store.test.ts +25 -0
  121. package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
  122. package/src/__tests__/mcp-health-check.test.ts +2 -1
  123. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  124. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  125. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  126. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  127. package/src/__tests__/mtime-cache.test.ts +275 -5
  128. package/src/__tests__/native-web-search.test.ts +2 -0
  129. package/src/__tests__/non-member-access-request.test.ts +191 -17
  130. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  131. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  132. package/src/__tests__/notification-deep-link.test.ts +6 -0
  133. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  134. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  135. package/src/__tests__/openai-provider.test.ts +22 -12
  136. package/src/__tests__/openai-responses-provider.test.ts +12 -2
  137. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  138. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  139. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  140. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  141. package/src/__tests__/persona-resolver.test.ts +75 -45
  142. package/src/__tests__/plugin-bootstrap.test.ts +21 -83
  143. package/src/__tests__/plugin-disabled-state.test.ts +190 -0
  144. package/src/__tests__/plugin-pipeline.test.ts +96 -0
  145. package/src/__tests__/plugin-route-contribution.test.ts +6 -19
  146. package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
  147. package/src/__tests__/plugin-types.test.ts +2 -4
  148. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  149. package/src/__tests__/process-message-display-content.test.ts +2 -0
  150. package/src/__tests__/provider-error-scenarios.test.ts +5 -4
  151. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  152. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
  153. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  154. package/src/__tests__/reaction-persistence.test.ts +51 -4
  155. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  156. package/src/__tests__/registry.test.ts +4 -1
  157. package/src/__tests__/relay-server.test.ts +758 -32
  158. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
  159. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  160. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  161. package/src/__tests__/settings-routes.test.ts +32 -0
  162. package/src/__tests__/skills.test.ts +44 -0
  163. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  164. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
  165. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  166. package/src/__tests__/stt-hints.test.ts +49 -15
  167. package/src/__tests__/subagent-detail.test.ts +27 -0
  168. package/src/__tests__/subagent-disposal.test.ts +65 -0
  169. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  170. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  171. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  172. package/src/__tests__/subagent-role-registry.test.ts +24 -6
  173. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  174. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  175. package/src/__tests__/subagent-tools.test.ts +398 -1
  176. package/src/__tests__/suggestion-routes.test.ts +2 -0
  177. package/src/__tests__/thread-backfill.test.ts +3 -3
  178. package/src/__tests__/title-generate-hook.test.ts +2 -0
  179. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  180. package/src/__tests__/tool-executor.test.ts +16 -11
  181. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  182. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  183. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  184. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  185. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
  186. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
  187. package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
  188. package/src/__tests__/trusted-contact-verification.test.ts +79 -54
  189. package/src/__tests__/twilio-routes.test.ts +96 -0
  190. package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
  191. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  192. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  193. package/src/__tests__/voice-invite-redemption.test.ts +216 -20
  194. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  195. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  196. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
  197. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  198. package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
  199. package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
  200. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  201. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
  202. package/src/agent/loop-exclusive-tool.test.ts +154 -0
  203. package/src/agent/loop-native-web-search.test.ts +200 -0
  204. package/src/agent/loop.ts +164 -1
  205. package/src/api/constants/sse-replay.ts +41 -0
  206. package/src/api/events/conversation-notice.ts +26 -0
  207. package/src/api/index.ts +19 -1
  208. package/src/api/responses/llm-request-log-entry.ts +29 -0
  209. package/src/api/responses/subagent-detail.ts +17 -0
  210. package/src/api/surfaces.ts +39 -3
  211. package/src/approvals/guardian-channel-delivery.ts +30 -0
  212. package/src/approvals/guardian-expiry-notifier.ts +148 -0
  213. package/src/approvals/guardian-request-resolvers.ts +17 -15
  214. package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
  215. package/src/calls/call-domain.ts +3 -3
  216. package/src/calls/guardian-dispatch.ts +14 -9
  217. package/src/calls/inbound-trust-reader.ts +24 -2
  218. package/src/calls/media-stream-server.ts +21 -0
  219. package/src/calls/relay-access-wait.ts +6 -6
  220. package/src/calls/relay-server.ts +188 -51
  221. package/src/calls/relay-setup-router.ts +47 -17
  222. package/src/calls/relay-verification.ts +4 -4
  223. package/src/calls/stt-hints.ts +9 -12
  224. package/src/calls/twilio-routes.ts +14 -4
  225. package/src/channels/types.ts +10 -20
  226. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  227. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  228. package/src/cli/commands/cache.ts +194 -181
  229. package/src/cli/commands/contacts.ts +10 -7
  230. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  231. package/src/cli/commands/db/status.ts +37 -1
  232. package/src/cli/commands/mcp.ts +252 -218
  233. package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
  234. package/src/cli/commands/memory/index.ts +2 -0
  235. package/src/cli/commands/memory/worker.ts +242 -0
  236. package/src/cli/commands/plugins.ts +125 -13
  237. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  238. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  239. package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
  240. package/src/cli/lib/list-installed-plugins.ts +179 -1
  241. package/src/cli/lib/publish-plugin.ts +633 -0
  242. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  243. package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
  244. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  245. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  246. package/src/config/bundled-skills/subagent/SKILL.md +17 -2
  247. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  248. package/src/config/call-site-defaults.ts +0 -6
  249. package/src/config/feature-flag-registry.json +5 -13
  250. package/src/config/llm-resolver.ts +0 -3
  251. package/src/config/loader.ts +38 -5
  252. package/src/config/prune-seeded-callsite-defaults.ts +110 -0
  253. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  254. package/src/config/schemas/call-site-catalog.ts +0 -7
  255. package/src/config/schemas/heartbeat.ts +2 -5
  256. package/src/config/schemas/llm.ts +3 -12
  257. package/src/config/schemas/memory-lifecycle.ts +12 -0
  258. package/src/config/schemas/memory-v3.ts +7 -0
  259. package/src/config/schemas/memory.ts +4 -0
  260. package/src/config/schemas/timeouts.ts +8 -0
  261. package/src/config/seed-inference-profiles.ts +30 -34
  262. package/src/config/skills.ts +27 -5
  263. package/src/config/sync-gated-profiles.ts +12 -16
  264. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
  265. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  266. package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
  267. package/src/contacts/contact-store.ts +27 -237
  268. package/src/contacts/contacts-write.ts +18 -55
  269. package/src/contacts/gateway-channel-read.ts +51 -0
  270. package/src/contacts/guardian-delivery-reader.ts +223 -0
  271. package/src/contacts/member-write-relay.ts +183 -0
  272. package/src/contacts/types.ts +3 -15
  273. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
  274. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  275. package/src/daemon/assistant-attachments.ts +27 -4
  276. package/src/daemon/conversation-agent-loop.ts +28 -0
  277. package/src/daemon/conversation-notices.ts +60 -0
  278. package/src/daemon/conversation-process.ts +35 -16
  279. package/src/daemon/conversation-surfaces.ts +111 -38
  280. package/src/daemon/conversation-tool-setup.ts +71 -30
  281. package/src/daemon/conversation.ts +23 -1
  282. package/src/daemon/disk-pressure-guard.ts +12 -2
  283. package/src/daemon/event-loop-watchdog.ts +28 -1
  284. package/src/daemon/external-plugins-bootstrap.ts +17 -41
  285. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
  287. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
  288. package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
  289. package/src/daemon/handlers/config-a2a.ts +6 -14
  290. package/src/daemon/handlers/config-channels.ts +67 -26
  291. package/src/daemon/handlers/conversations.ts +77 -0
  292. package/src/daemon/lifecycle.ts +16 -0
  293. package/src/daemon/mcp-reload-service.ts +10 -0
  294. package/src/daemon/memory-v2-startup.test.ts +72 -0
  295. package/src/daemon/memory-v2-startup.ts +87 -19
  296. package/src/daemon/message-types/conversations.ts +2 -0
  297. package/src/daemon/message-types/surfaces.ts +12 -12
  298. package/src/daemon/server.ts +0 -4
  299. package/src/daemon/shutdown-handlers.ts +20 -0
  300. package/src/daemon/tool-setup-types.ts +9 -0
  301. package/src/heartbeat/heartbeat-service.ts +5 -0
  302. package/src/home/relationship-state-writer.ts +5 -0
  303. package/src/hooks/hook-loader.ts +341 -0
  304. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  305. package/src/ipc/assistant-server.ts +2 -2
  306. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
  307. package/src/mcp/client.ts +15 -1
  308. package/src/mcp/mcp-auth-orchestrator.ts +6 -1
  309. package/src/mcp/mcp-header-store.ts +134 -0
  310. package/src/mcp/mcp-oauth-provider.ts +19 -8
  311. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  312. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  313. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  314. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  315. package/src/memory/compaction-ledger-store.ts +107 -0
  316. package/src/memory/conversation-crud.ts +129 -61
  317. package/src/memory/db-connection.ts +22 -3
  318. package/src/memory/db-init.ts +37 -503
  319. package/src/memory/db-singleton.ts +6 -4
  320. package/src/memory/jobs-worker.ts +104 -0
  321. package/src/memory/llm-request-log-store.ts +26 -1
  322. package/src/memory/llm-usage-store.ts +48 -20
  323. package/src/memory/memory-retrospective-job.ts +14 -8
  324. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
  325. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  326. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  327. package/src/memory/migrations/302-create-compaction-events.ts +107 -0
  328. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
  329. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  330. package/src/memory/migrations/run-migrations.ts +106 -10
  331. package/src/memory/migrations/validate-migration-state.ts +105 -67
  332. package/src/memory/prompt-override.ts +129 -0
  333. package/src/memory/schema/contacts.ts +6 -2
  334. package/src/memory/schema/conversations.ts +37 -0
  335. package/src/memory/schema/infrastructure.ts +20 -0
  336. package/src/memory/steps.ts +1294 -0
  337. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  338. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  339. package/src/memory/v2/cli-command-store.ts +75 -38
  340. package/src/memory/v2/prompts/consolidation.ts +13 -82
  341. package/src/memory/v2/prompts/router.ts +21 -93
  342. package/src/memory/v2/skill-store.ts +68 -31
  343. package/src/memory/watchdog-events-store.ts +87 -0
  344. package/src/memory/worker-control.ts +204 -0
  345. package/src/memory/worker-process.ts +89 -0
  346. package/src/messaging/channel-binding-metadata.ts +31 -0
  347. package/src/messaging/provider-types.ts +8 -0
  348. package/src/messaging/providers/slack/binding-metadata.ts +60 -0
  349. package/src/notifications/__tests__/broadcaster.test.ts +8 -8
  350. package/src/notifications/__tests__/connected-channels.test.ts +86 -0
  351. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  352. package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
  353. package/src/notifications/broadcaster.ts +8 -1
  354. package/src/notifications/decision-engine.ts +15 -7
  355. package/src/notifications/destination-resolver.ts +53 -25
  356. package/src/notifications/emit-signal.ts +34 -15
  357. package/src/onboarding/checkin-event.test.ts +222 -0
  358. package/src/onboarding/checkin-event.ts +321 -0
  359. package/src/onboarding/schedule-checkin.ts +190 -0
  360. package/src/permissions/question-prompter.test.ts +1 -1
  361. package/src/permissions/question-prompter.ts +7 -4
  362. package/src/plugin-api/index.ts +12 -12
  363. package/src/plugin-api/types.ts +12 -14
  364. package/src/plugin-api/vision-support.test.ts +28 -4
  365. package/src/plugin-api/vision-support.ts +66 -31
  366. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
  367. package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
  368. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
  369. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
  370. package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
  371. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
  372. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  373. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
  374. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
  375. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  376. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
  377. package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
  378. package/src/plugins/defaults/index.ts +0 -35
  379. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
  380. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
  381. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
  382. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
  384. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
  385. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
  386. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
  387. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
  388. package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
  389. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  390. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  391. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
  392. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
  393. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
  394. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
  395. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
  396. package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
  397. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
  398. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
  399. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
  400. package/src/plugins/disabled-state.ts +31 -0
  401. package/src/plugins/external-plugin-loader.ts +2 -2
  402. package/src/plugins/mtime-cache.ts +186 -330
  403. package/src/plugins/pipeline.ts +111 -13
  404. package/src/plugins/registry.ts +59 -16
  405. package/src/plugins/surface-import.ts +121 -0
  406. package/src/plugins/types.ts +7 -9
  407. package/src/prompts/persona-resolver.ts +43 -11
  408. package/src/providers/anthropic/client.ts +5 -0
  409. package/src/providers/call-site-routing.ts +45 -0
  410. package/src/providers/model-catalog.ts +16 -0
  411. package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
  412. package/src/providers/openai/api-error-normalization.ts +270 -0
  413. package/src/providers/openai/chat-completions-provider.ts +37 -83
  414. package/src/providers/openai/responses-provider.ts +50 -46
  415. package/src/providers/openrouter/client.ts +5 -0
  416. package/src/providers/provider-send-message.ts +10 -0
  417. package/src/providers/ratelimit.ts +10 -0
  418. package/src/providers/registry.ts +1 -1
  419. package/src/providers/retry.ts +10 -0
  420. package/src/providers/types.ts +22 -0
  421. package/src/providers/usage-tracking.ts +10 -0
  422. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  423. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
  424. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  425. package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
  426. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  427. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
  428. package/src/runtime/access-request-helper.ts +18 -39
  429. package/src/runtime/actor-trust-resolver.ts +46 -19
  430. package/src/runtime/anchored-guardian.test.ts +109 -0
  431. package/src/runtime/anchored-guardian.ts +86 -0
  432. package/src/runtime/assistant-event-hub.ts +1 -1
  433. package/src/runtime/assistant-stream-state.ts +9 -2
  434. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  435. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  436. package/src/runtime/channel-verification-service.ts +56 -31
  437. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  438. package/src/runtime/guardian-vellum-migration.ts +69 -8
  439. package/src/runtime/invite-redemption-service.ts +213 -187
  440. package/src/runtime/local-actor-identity.test.ts +108 -0
  441. package/src/runtime/local-actor-identity.ts +85 -13
  442. package/src/runtime/local-principal-trust.ts +52 -0
  443. package/src/runtime/member-verdict-cache.ts +0 -0
  444. package/src/runtime/pending-interactions.ts +11 -1
  445. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  446. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  447. package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
  448. package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
  449. package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
  450. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
  451. package/src/runtime/routes/browser-routes.ts +1 -1
  452. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
  453. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  454. package/src/runtime/routes/contact-routes.ts +48 -57
  455. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  456. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  457. package/src/runtime/routes/conversation-query-routes.ts +72 -0
  458. package/src/runtime/routes/conversation-routes.ts +84 -85
  459. package/src/runtime/routes/events-routes.ts +2 -2
  460. package/src/runtime/routes/global-search-routes.ts +3 -1
  461. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  462. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  463. package/src/runtime/routes/host-bash-routes.ts +5 -4
  464. package/src/runtime/routes/host-browser-routes.ts +9 -11
  465. package/src/runtime/routes/host-cu-routes.ts +5 -4
  466. package/src/runtime/routes/host-file-routes.ts +5 -4
  467. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  468. package/src/runtime/routes/http-adapter.ts +16 -1
  469. package/src/runtime/routes/identity-routes.ts +3 -2
  470. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  471. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  472. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
  473. package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
  474. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  475. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  476. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  477. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
  478. package/src/runtime/routes/index.ts +2 -0
  479. package/src/runtime/routes/llm-context-normalization.ts +83 -0
  480. package/src/runtime/routes/mcp-auth-routes.ts +171 -19
  481. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  482. package/src/runtime/routes/migration-routes.ts +4 -1
  483. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  484. package/src/runtime/routes/settings-routes.ts +8 -3
  485. package/src/runtime/routes/subagents-routes.ts +5 -0
  486. package/src/runtime/routes/surface-action-routes.ts +42 -56
  487. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  488. package/src/runtime/services/conversation-serializer.ts +13 -58
  489. package/src/runtime/tool-grant-request-helper.ts +3 -3
  490. package/src/runtime/trust-verdict-consumer.ts +111 -40
  491. package/src/runtime/verification-outbound-actions.ts +18 -18
  492. package/src/signals/user-message.ts +16 -0
  493. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  494. package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
  495. package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
  496. package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
  497. package/src/subagent/index.ts +1 -1
  498. package/src/subagent/manager.ts +254 -33
  499. package/src/subagent/types.ts +11 -4
  500. package/src/telemetry/types.ts +34 -1
  501. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  502. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  503. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  504. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  505. package/src/tools/executor.ts +4 -4
  506. package/src/tools/filesystem/search.ts +543 -0
  507. package/src/tools/registry.ts +38 -0
  508. package/src/tools/shared/filesystem/path-policy.ts +12 -5
  509. package/src/tools/subagent/consult-deadline.ts +49 -0
  510. package/src/tools/subagent/spawn.ts +234 -5
  511. package/src/tools/tool-approval-handler.ts +1 -1
  512. package/src/tools/tool-defaults.ts +9 -2
  513. package/src/tools/tool-manifest.ts +3 -0
  514. package/src/tools/types.ts +131 -25
  515. package/src/tools/workspace-tools/loader.ts +348 -244
  516. package/src/util/errors.ts +26 -1
  517. package/src/util/logger.ts +9 -0
  518. package/src/util/platform.ts +19 -0
  519. package/src/util/telemetry-db-path.ts +24 -0
  520. package/src/workflows/library.test.ts +140 -0
  521. package/src/workflows/library.ts +82 -28
  522. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  523. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  524. package/src/workspace/migrations/031-drop-user-md.ts +232 -148
  525. package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
  526. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
  527. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  528. package/src/workspace/migrations/registry.ts +4 -0
  529. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  530. package/src/daemon/workspace-tools-watcher.ts +0 -328
  531. package/src/memory/migrations/registry.ts +0 -573
  532. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  533. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  534. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  535. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
  536. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  537. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  538. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  539. package/src/plugins/defaults/advisor/config.ts +0 -21
  540. package/src/plugins/defaults/advisor/consult.ts +0 -93
  541. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  542. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  543. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  544. package/src/plugins/defaults/advisor/package.json +0 -14
  545. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
  546. package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
@@ -4,9 +4,10 @@
4
4
  * A "hook" is a named lifecycle event (`user-prompt-submit`, `post-tool-use`,
5
5
  * ...) that every registered plugin may handle. The runner walks each plugin's
6
6
  * hook for a given event in registration order, threading a context value
7
- * through the chain so hooks can observe and transform it. A hook either
8
- * mutates the context in place (returning `void`) or returns a partial
9
- * context whose fields are merged onto the threaded value.
7
+ * through the chain so hooks can observe and transform it. Each hook receives
8
+ * an isolated draft of the current context. A hook either mutates the draft in
9
+ * place (returning `void`) or returns a partial context whose fields are merged
10
+ * onto the draft. Failed hook drafts are discarded.
10
11
  *
11
12
  * `getHooksFor` is now async — it pulls user-land hooks from the mtime
12
13
  * cache (filesystem-as-truth) and default plugin hooks from the registry
@@ -16,34 +17,131 @@
16
17
  */
17
18
 
18
19
  import type { HookName } from "../plugin-api/constants.js";
20
+ import { getLogger } from "../util/logger.js";
19
21
  import { getHooksFor } from "./registry.js";
22
+ import type { HookFunction } from "./types.js";
20
23
 
21
24
  // ─── Hook runner ────────────────────────────────────────────────────────────
22
25
 
26
+ const log = getLogger("plugin-pipeline");
27
+
28
+ function isPluginLogger(value: unknown): value is {
29
+ info: unknown;
30
+ warn: unknown;
31
+ error: unknown;
32
+ debug: unknown;
33
+ } {
34
+ return (
35
+ value !== null &&
36
+ typeof value === "object" &&
37
+ typeof (value as { info?: unknown }).info === "function" &&
38
+ typeof (value as { warn?: unknown }).warn === "function" &&
39
+ typeof (value as { error?: unknown }).error === "function" &&
40
+ typeof (value as { debug?: unknown }).debug === "function"
41
+ );
42
+ }
43
+
44
+ function isPlainObject(value: object): boolean {
45
+ const prototype = Object.getPrototypeOf(value);
46
+ return prototype === Object.prototype || prototype === null;
47
+ }
48
+
49
+ function cloneHookValue<T>(value: T, seen = new WeakMap<object, unknown>()): T {
50
+ if (value === null || typeof value !== "object") return value;
51
+ if (value instanceof Error || isPluginLogger(value)) return value;
52
+
53
+ const existing = seen.get(value);
54
+ if (existing !== undefined) return existing as T;
55
+
56
+ if (Array.isArray(value)) {
57
+ const copy: unknown[] = [];
58
+ seen.set(value, copy);
59
+ for (const item of value) {
60
+ copy.push(cloneHookValue(item, seen));
61
+ }
62
+ return copy as T;
63
+ }
64
+
65
+ if (value instanceof Date) {
66
+ return new Date(value.getTime()) as T;
67
+ }
68
+
69
+ if (value instanceof Map) {
70
+ const copy = new Map();
71
+ seen.set(value, copy);
72
+ for (const [key, mapValue] of value) {
73
+ copy.set(cloneHookValue(key, seen), cloneHookValue(mapValue, seen));
74
+ }
75
+ return copy as T;
76
+ }
77
+
78
+ if (value instanceof Set) {
79
+ const copy = new Set();
80
+ seen.set(value, copy);
81
+ for (const item of value) {
82
+ copy.add(cloneHookValue(item, seen));
83
+ }
84
+ return copy as T;
85
+ }
86
+
87
+ if (!isPlainObject(value)) return value;
88
+
89
+ const copy: Record<PropertyKey, unknown> = {};
90
+ seen.set(value, copy);
91
+ for (const key of Reflect.ownKeys(value)) {
92
+ copy[key] = cloneHookValue(
93
+ (value as Record<PropertyKey, unknown>)[key],
94
+ seen,
95
+ );
96
+ }
97
+ return copy as T;
98
+ }
99
+
23
100
  /**
24
101
  * Execute a hook chain: walk every registered plugin's hook for `name` in
25
102
  * registration order, threading `initialCtx` through each. Hooks may either
26
- * mutate the context in place (returning `void`) or return a partial context
27
- * whose fields are merged onto the threaded value — keys the hook returns
28
- * overwrite the running context, every other field is preserved. The final
29
- * context after the chain settles is returned.
103
+ * mutate their draft context in place (returning `void`) or return a partial
104
+ * context whose fields are merged onto the draft — keys the hook returns
105
+ * overwrite the running context, every other field is preserved. If a hook
106
+ * throws, its draft is discarded and the next hook receives the last
107
+ * successfully committed context. The final context after the chain settles is
108
+ * returned.
30
109
  *
31
110
  * @param name The hook identifier — pick one from {@link HOOKS}.
32
111
  * @param initialCtx Context the first hook receives.
33
112
  * @returns The final context after the chain settles. Same reference as
34
- * `initialCtx` when no plugin registers `name`, and when every
35
- * chained hook returns `void` (mutation-in-place style).
113
+ * `initialCtx` when no plugin registers `name`.
36
114
  */
37
115
  export async function runHook<TCtx>(
38
116
  name: HookName,
39
117
  initialCtx: TCtx,
40
118
  ): Promise<TCtx> {
41
- const hooks = await getHooksFor<TCtx>(name);
119
+ let hooks: HookFunction<TCtx>[];
120
+ try {
121
+ hooks = await getHooksFor<TCtx>(name);
122
+ } catch (err) {
123
+ log.error(
124
+ { err, hookName: name },
125
+ "plugin hook discovery failed — proceeding without hooks",
126
+ );
127
+ return initialCtx;
128
+ }
129
+
42
130
  let active = initialCtx;
43
131
  for (const hook of hooks) {
44
- const result = await hook(active);
45
- if (result !== undefined) {
46
- active = { ...active, ...result };
132
+ const draft = cloneHookValue(active);
133
+ try {
134
+ const result = await hook(draft);
135
+ if (result !== undefined) {
136
+ active = { ...draft, ...result };
137
+ } else {
138
+ active = draft;
139
+ }
140
+ } catch (err) {
141
+ log.error(
142
+ { err, hookName: name },
143
+ "plugin hook failed — proceeding with current context",
144
+ );
47
145
  }
48
146
  }
49
147
  return active;
@@ -19,11 +19,12 @@
19
19
  * Design doc: `.private/plans/agent-plugin-system.md`.
20
20
  */
21
21
 
22
+ import { isPluginDisabled } from "./disabled-state.js";
22
23
  import { getUserHooksFor } from "./mtime-cache.js";
23
24
  import {
25
+ type HookFunction,
24
26
  type Plugin,
25
27
  PluginExecutionError,
26
- type PluginHookFn,
27
28
  } from "./types.js";
28
29
 
29
30
  // ─── Internal state ──────────────────────────────────────────────────────────
@@ -35,6 +36,25 @@ import {
35
36
  */
36
37
  const registeredPlugins = new Map<string, Plugin>();
37
38
 
39
+ /**
40
+ * Hook registry — the per-surface registry for default plugin hooks. Each
41
+ * hook name maps to an ordered list of `{fn, pluginName}` entries, one per
42
+ * plugin that contributes that hook. The list order matches registration
43
+ * order (i.e. {@link getAllDefaultPlugins} array order), which fixes
44
+ * hook-chain ordering the same way the old `registeredPlugins` map did.
45
+ *
46
+ * Populated by {@link registerPlugin} alongside `registeredPlugins` and
47
+ * depleted by {@link unregisterPlugin}. {@link getHooksFor} reads from this
48
+ * map (not `registeredPlugins`) so it can filter disabled plugins at read
49
+ * time via {@link isPluginDisabled} — the key mechanism that makes
50
+ * `assistant plugins disable default-*` take effect immediately in a
51
+ * running assistant.
52
+ */
53
+ const hookRegistry = new Map<
54
+ string,
55
+ Array<{ fn: HookFunction; pluginName: string }>
56
+ >();
57
+
38
58
  /**
39
59
  * Latch that closes the per-boot registration window. Flipped to `true` by
40
60
  * {@link closeRegistration} once `loadUserPlugins()` has returned. After that,
@@ -126,6 +146,22 @@ export function registerPlugin(plugin: Plugin): void {
126
146
  }
127
147
 
128
148
  registeredPlugins.set(name, plugin);
149
+
150
+ // Register each hook into the per-surface hook registry. `getHooksFor`
151
+ // reads from this map (filtered by `isPluginDisabled`) rather than from
152
+ // `registeredPlugins`, so toggling the `.disabled` sentinel at runtime
153
+ // takes effect on the next turn without mutating the plugin registry.
154
+ if (plugin.hooks) {
155
+ for (const [hookName, fn] of Object.entries(plugin.hooks)) {
156
+ if (typeof fn !== "function") continue;
157
+ let list = hookRegistry.get(hookName);
158
+ if (!list) {
159
+ list = [];
160
+ hookRegistry.set(hookName, list);
161
+ }
162
+ list.push({ fn: fn as HookFunction, pluginName: name });
163
+ }
164
+ }
129
165
  }
130
166
 
131
167
  // ─── Queries ─────────────────────────────────────────────────────────────────
@@ -151,20 +187,22 @@ export function getRegisteredPlugins(): Plugin[] {
151
187
  * files. First-party default plugin hooks are read synchronously from the
152
188
  * registry and prepended.
153
189
  *
154
- * The `TCtx` generic mirrors {@link PluginHookFn}'s — callers parameterize
190
+ * The `TCtx` generic mirrors {@link HookFunction}'s — callers parameterize
155
191
  * over the concrete context type their hook receives. Hooks that mutate
156
192
  * the context in place return `void`; hooks that return a new context
157
193
  * replace the threaded value for the next hook in the chain.
158
194
  */
159
195
  export async function getHooksFor<TCtx = unknown>(
160
196
  name: string,
161
- ): Promise<PluginHookFn<TCtx>[]> {
162
- // First-party defaults from the registry (synchronous).
163
- const defaultHooks: PluginHookFn<TCtx>[] = [];
164
- for (const plugin of registeredPlugins.values()) {
165
- const hook = plugin.hooks?.[name];
166
- if (hook) {
167
- defaultHooks.push(hook as PluginHookFn<TCtx>);
197
+ ): Promise<HookFunction<TCtx>[]> {
198
+ // First-party defaults from the hook registry, filtered by the `.disabled`
199
+ // sentinel at read time. This is what makes `assistant plugins disable
200
+ // default-*` take effect immediately in a running assistant: the hooks stay
201
+ // registered but are filtered out on the next turn.
202
+ const defaultHooks: HookFunction<TCtx>[] = [];
203
+ for (const entry of hookRegistry.get(name) ?? []) {
204
+ if (!isPluginDisabled(entry.pluginName)) {
205
+ defaultHooks.push(entry.fn as HookFunction<TCtx>);
168
206
  }
169
207
  }
170
208
 
@@ -199,14 +237,18 @@ export function closeRegistration(): void {
199
237
  */
200
238
  export function unregisterPlugin(name: string): void {
201
239
  registeredPlugins.delete(name);
202
- }
203
-
204
- export function getRegisteredPlugin(name: string): Plugin | undefined {
205
- return registeredPlugins.get(name);
206
- }
207
240
 
208
- export function setRegisteredPlugin(plugin: Plugin): void {
209
- registeredPlugins.set(plugin.manifest.name, plugin);
241
+ // Remove all hooks contributed by this plugin from the hook registry.
242
+ // Used by the bootstrap failure path (init threw) and the feature-flag
243
+ // skip path — both are boot-time decisions where the plugin's hooks should
244
+ // never participate in the turn lifecycle.
245
+ for (const [, list] of hookRegistry) {
246
+ for (let i = list.length - 1; i >= 0; i--) {
247
+ if (list[i]!.pluginName === name) {
248
+ list.splice(i, 1);
249
+ }
250
+ }
251
+ }
210
252
  }
211
253
 
212
254
  // ─── Test hooks ──────────────────────────────────────────────────────────────
@@ -228,6 +270,7 @@ export function resetPluginRegistryForTests(): void {
228
270
  );
229
271
  }
230
272
  registeredPlugins.clear();
273
+ hookRegistry.clear();
231
274
  // Re-open the registration window so subsequent tests can register plugins
232
275
  // again. Without this, the latch set by a prior `closeRegistration()` call
233
276
  // would leak across test cases and reject legitimate registrations.
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Shared low-level import machinery for user-surface files (plugin tools,
3
+ * plugin hooks, and standalone workspace hooks).
4
+ *
5
+ * Both the plugin/tool cache (`mtime-cache.ts`) and the hook cache
6
+ * (`../hooks/hook-loader.ts`) re-import surface files keyed by mtime. This
7
+ * module owns the pieces they share — mtime stat, the per-process import
8
+ * timeout, and a timeout-guarded, concurrency-deduplicated dynamic import —
9
+ * so neither surface duplicates the logic and the import-timeout knob is
10
+ * configured in exactly one place.
11
+ *
12
+ * Kept dependency-light (only the logger and `importDefault`) so it can sit
13
+ * below both caches without introducing an import cycle.
14
+ */
15
+
16
+ import { statSync } from "node:fs";
17
+
18
+ import { getLogger } from "../util/logger.js";
19
+ import { importDefault } from "./external-plugin-loader.js";
20
+
21
+ const log = getLogger("surface-import");
22
+
23
+ /**
24
+ * Per-process upper bound on how long a single surface import may take.
25
+ * Set once at boot by `populateCacheAtBoot`; read by every re-import. A
26
+ * plugin/hook with a hanging top-level `await` is skipped rather than
27
+ * blocking the daemon.
28
+ */
29
+ let importTimeoutMs = 10_000;
30
+
31
+ /** Override the surface import timeout (called once at boot). */
32
+ export function setSurfaceImportTimeout(ms: number): void {
33
+ importTimeoutMs = ms;
34
+ }
35
+
36
+ /** Current surface import timeout in milliseconds. */
37
+ export function getSurfaceImportTimeout(): number {
38
+ return importTimeoutMs;
39
+ }
40
+
41
+ /**
42
+ * Get the mtimeMs of a file, or 0 if the file doesn't exist or can't be
43
+ * stat'd. Callers treat 0 as "absent" (cache-evict / skip).
44
+ */
45
+ export function getMtime(filePath: string): number {
46
+ try {
47
+ return statSync(filePath).mtimeMs;
48
+ } catch {
49
+ return 0;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * In-flight import promises, keyed by file path. Prevents duplicate
55
+ * `import()` calls when multiple readers request the same surface
56
+ * concurrently.
57
+ */
58
+ const inflight = new Map<string, Promise<unknown>>();
59
+
60
+ /**
61
+ * Import a module's default export with a timeout. If the import doesn't
62
+ * resolve within `timeoutMs`, logs a warning and returns `undefined` so a
63
+ * hanging module doesn't block daemon startup indefinitely. Defaults to the
64
+ * module-level {@link getSurfaceImportTimeout}.
65
+ */
66
+ export async function importWithTimeout<T>(
67
+ filePath: string,
68
+ timeoutMs: number = importTimeoutMs,
69
+ ): Promise<T | undefined> {
70
+ let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
71
+ try {
72
+ const timeoutSentinel = Symbol("import-timeout");
73
+ const importPromise = importWithDedup<T>(filePath);
74
+ const timeoutPromise = new Promise<typeof timeoutSentinel>((resolve) => {
75
+ timeoutHandle = setTimeout(() => resolve(timeoutSentinel), timeoutMs);
76
+ });
77
+ const result = await Promise.race([importPromise, timeoutPromise]);
78
+ if (result === timeoutSentinel) {
79
+ importPromise.catch(() => {
80
+ /* swallow — late rejection from abandoned import */
81
+ });
82
+ log.warn(
83
+ { filePath, timeoutMs },
84
+ `Import timed out after ${timeoutMs}ms — skipping surface`,
85
+ );
86
+ return undefined;
87
+ }
88
+ return result as T;
89
+ } finally {
90
+ if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Import a module's default export, deduplicating concurrent imports for
96
+ * the same file path. This prevents two readers from triggering duplicate
97
+ * `import()` calls when they request the same surface simultaneously.
98
+ *
99
+ * Note: Bun caches `import()` by resolved path within a process and does not
100
+ * bust on query-string or hash changes, so the dedup is about avoiding
101
+ * redundant async work, not cache-busting. A content edit to an existing
102
+ * file is only picked up after a process restart (added and removed files
103
+ * are picked up live, since discovery is by directory listing).
104
+ */
105
+ async function importWithDedup<T>(filePath: string): Promise<T> {
106
+ let promise = inflight.get(filePath);
107
+ if (promise === undefined) {
108
+ promise = importDefault<T>(filePath);
109
+ inflight.set(filePath, promise);
110
+ }
111
+ try {
112
+ return (await promise) as T;
113
+ } finally {
114
+ inflight.delete(filePath);
115
+ }
116
+ }
117
+
118
+ /** Clear in-flight import state. Test-only. */
119
+ export function clearSurfaceImportInflight(): void {
120
+ inflight.clear();
121
+ }
@@ -19,7 +19,7 @@ import type {
19
19
  } from "../daemon/conversation-runtime-assembly.js";
20
20
  import type { TrustContext } from "../daemon/trust-context.js";
21
21
  import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
22
- import type { PluginHookFn } from "../plugin-api/types.js";
22
+ import type { HookFunction } from "../plugin-api/types.js";
23
23
  import type { Message } from "../providers/types.js";
24
24
  import type { SkillRoute } from "../runtime/skill-route-registry.js";
25
25
  import type { Tool } from "../tools/types.js";
@@ -47,8 +47,6 @@ export interface PluginManifest {
47
47
  * own version at load time.
48
48
  */
49
49
  version: string;
50
- /** Credential keys the plugin needs resolved before `init()` runs. */
51
- requiresCredential?: string[];
52
50
  /**
53
51
  * Assistant feature-flag keys that must all be enabled for this plugin to
54
52
  * activate. Checked by `bootstrapPlugins` via `isAssistantFeatureFlagEnabled`
@@ -69,9 +67,9 @@ export interface PluginManifest {
69
67
  // existing internal call sites keep working. Plugin authors import these from
70
68
  // `@vellumai/plugin-api`.
71
69
  export type {
72
- PluginHookFn,
73
- PluginInitContext,
74
- PluginShutdownContext,
70
+ HookFunction,
71
+ InitContext,
72
+ ShutdownContext,
75
73
  } from "../plugin-api/types.js";
76
74
 
77
75
  // ─── Memory-graph result ─────────────────────────────────────────────────────
@@ -345,14 +343,14 @@ export type PluginRouteRegistration = SkillRoute;
345
343
  * unknown keys are forward-compat scaffolding.
346
344
  *
347
345
  * See `assistant/src/daemon/external-plugins-bootstrap.ts` for the
348
- * full lifecycle, and {@link PluginHookFn} for the per-entry signature.
346
+ * full lifecycle, and {@link HookFunction} for the per-entry signature.
349
347
  */
350
348
  // The map stores hooks for arbitrary keys with arbitrary context shapes.
351
349
  // `any` (rather than `unknown`) is required so concrete plugin signatures
352
- // like `(ctx: PluginInitContext) => Promise<void>` and `() => Promise<void>`
350
+ // like `(ctx: InitContext) => Promise<void>` and `() => Promise<void>`
353
351
  // both assign in/out of slot entries under strict-function-types contravariance.
354
352
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
355
- export type PluginHooks = Record<string, PluginHookFn<any>>;
353
+ export type PluginHooks = Record<string, HookFunction<any>>;
356
354
 
357
355
  /**
358
356
  * A registered plugin. Every field besides `manifest` is optional — a plugin
@@ -1,11 +1,14 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { basename, dirname, join } from "node:path";
3
3
 
4
+ import type { GuardianDelivery } from "@vellumai/gateway-client";
5
+
6
+ import { findContactByAddress } from "../contacts/contact-store.js";
4
7
  import {
5
- findContactByAddress,
6
- findGuardianForChannel,
7
- listGuardianChannels,
8
- } from "../contacts/contact-store.js";
8
+ anyGuardian,
9
+ guardianForChannel,
10
+ peekCachedGuardianDelivery,
11
+ } from "../contacts/guardian-delivery-reader.js";
9
12
  import type { ChannelCapabilities } from "../daemon/conversation-runtime-assembly.js";
10
13
  import type { TrustContext } from "../daemon/trust-context.js";
11
14
  import { getLogger } from "../util/logger.js";
@@ -87,8 +90,38 @@ function resolveGuardianUserFile(trustContext: TrustContext): string | null {
87
90
  return guardianContact.userFile ?? "guardian.md";
88
91
  }
89
92
  }
90
- const guardian = findGuardianForChannel(trustContext.sourceChannel);
91
- return guardian ? (guardian.contact.userFile ?? "guardian.md") : null;
93
+ const guardian = peekGuardianForChannel(trustContext.sourceChannel);
94
+ return guardian ? (guardianDeliveryUserFile(guardian) ?? "guardian.md") : null;
95
+ }
96
+
97
+ /**
98
+ * Resolve the local INFO `userFile` for a gateway guardian delivery. The
99
+ * gateway carries identity (channel + address) but not local INFO, so we join
100
+ * the local contact by the guardian's channel address. Returns `undefined` when
101
+ * no local contact matches.
102
+ */
103
+ function guardianDeliveryUserFile(
104
+ guardian: GuardianDelivery,
105
+ ): string | undefined {
106
+ const contact = findContactByAddress(
107
+ guardian.channelType,
108
+ guardian.address,
109
+ );
110
+ return contact?.userFile ?? undefined;
111
+ }
112
+
113
+ /** Active guardian for a channel from the IO-free delivery cache. */
114
+ function peekGuardianForChannel(
115
+ channelType: string,
116
+ ): GuardianDelivery | undefined {
117
+ const cached = peekCachedGuardianDelivery({ channelTypes: [channelType] });
118
+ return cached ? guardianForChannel(cached, channelType) : undefined;
119
+ }
120
+
121
+ /** First guardian across all channels from the IO-free delivery cache. */
122
+ function peekAnyGuardian(): GuardianDelivery | undefined {
123
+ const cached = peekCachedGuardianDelivery();
124
+ return cached ? anyGuardian(cached) : undefined;
92
125
  }
93
126
 
94
127
  /**
@@ -102,12 +135,11 @@ function resolveUserFilename(
102
135
 
103
136
  try {
104
137
  if (trustContext === undefined) {
105
- // Desktop / native (no gateway) — resolve via guardian contact,
106
- // preferring the vellum-channel guardian when multiple exist.
107
- const vellumGuardian = findGuardianForChannel("vellum");
108
- const guardian = vellumGuardian ?? listGuardianChannels();
138
+ // Desktop / native — resolve via the gateway guardian delivery cache,
139
+ // preferring the vellum-channel guardian, then any guardian.
140
+ const guardian = peekGuardianForChannel("vellum") ?? peekAnyGuardian();
109
141
  if (guardian) {
110
- filename = guardian.contact.userFile ?? "guardian.md";
142
+ filename = guardianDeliveryUserFile(guardian) ?? "guardian.md";
111
143
  }
112
144
  } else if (trustContext.requesterExternalUserId) {
113
145
  // Channel-routed request — look up contact by channel identity
@@ -786,6 +786,11 @@ export class AnthropicProvider implements Provider {
786
786
  this.useNativeWebSearch = options.useNativeWebSearch ?? false;
787
787
  }
788
788
 
789
+ /** See {@link Provider.supportsNativeWebSearch}. */
790
+ get supportsNativeWebSearch(): boolean {
791
+ return this.useNativeWebSearch;
792
+ }
793
+
789
794
  async sendMessage(
790
795
  messages: Message[],
791
796
  options?: SendMessageOptions,
@@ -34,6 +34,7 @@ import {
34
34
  } from "./connection-resolution.js";
35
35
  import { listConnections } from "./inference/connections.js";
36
36
  import type { ProvidersConfig } from "./registry.js";
37
+ import { shouldUseNativeWebSearch } from "./registry.js";
37
38
  import type {
38
39
  Message,
39
40
  Provider,
@@ -43,6 +44,9 @@ import type {
43
44
 
44
45
  export class CallSiteRoutingProvider implements Provider {
45
46
  public readonly tokenEstimationProvider?: string;
47
+ // Forward native web-search capability so it survives the wrapper chain
48
+ // (callers like the advisor consult gate on it). Fixed at construction.
49
+ public readonly supportsNativeWebSearch?: boolean;
46
50
 
47
51
  // Per-call async context that tracks which provider is currently executing.
48
52
  // Using AsyncLocalStorage instead of a plain instance field means concurrent
@@ -94,6 +98,7 @@ export class CallSiteRoutingProvider implements Provider {
94
98
  ) => Promise<Provider | null>,
95
99
  ) {
96
100
  this.tokenEstimationProvider = defaultProvider.tokenEstimationProvider;
101
+ this.supportsNativeWebSearch = defaultProvider.supportsNativeWebSearch;
97
102
  if (defaultProvider.countInputTokens) {
98
103
  this.countInputTokens =
99
104
  defaultProvider.countInputTokens.bind(defaultProvider);
@@ -126,6 +131,46 @@ export class CallSiteRoutingProvider implements Provider {
126
131
  : doSend();
127
132
  }
128
133
 
134
+ /**
135
+ * Native web-search capability of the provider/model THIS call routes to.
136
+ *
137
+ * `selectProvider` picks the transport from the routed connection, but each
138
+ * leaf provider's static `supportsNativeWebSearch` was fixed to the DEFAULT
139
+ * (provider, model) at boot. Resolving the call-site here — same
140
+ * `resolveCallSiteConfig` inputs `selectProvider` uses — and recomputing
141
+ * `shouldUseNativeWebSearch(resolved.provider, resolved.model)` yields the
142
+ * capability of the routed target instead of the construction-time default.
143
+ *
144
+ * Falls back to the default provider's static flag when no `callSite` is set
145
+ * (the legacy short-circuit `selectProvider` also takes).
146
+ *
147
+ * Known limitation: this reports the *resolved* target's capability and does
148
+ * not replay `selectProvider`'s async soft-credential fallback. If the routed
149
+ * connection has a transient credential failure at send time, `selectProvider`
150
+ * falls back to the default provider while this probe still reports the routed
151
+ * target — so a non-native default + native routed target with a credential
152
+ * blip can attach `web_search` to the fallback non-native provider. The probe
153
+ * stays sync (the loop assembles tools synchronously) and the worst case is
154
+ * bounded: the advisor consult that hits it degrades benignly (the unhandled
155
+ * tool surfaces as a caught failure → "(advisor unavailable)"), not a crash.
156
+ */
157
+ supportsNativeWebSearchFor(options?: SendMessageOptions): boolean {
158
+ const callSite = options?.config?.callSite;
159
+ if (!callSite) {
160
+ return this.defaultProvider.supportsNativeWebSearch === true;
161
+ }
162
+ const resolved = resolveCallSiteConfig(callSite, getConfig().llm, {
163
+ overrideProfile: options?.config?.overrideProfile,
164
+ forceOverrideProfile: options?.config?.forceOverrideProfile,
165
+ selectionSeed: options?.config?.selectionSeed,
166
+ });
167
+ return shouldUseNativeWebSearch(
168
+ getConfig(),
169
+ resolved.provider,
170
+ resolved.model,
171
+ );
172
+ }
173
+
129
174
  /**
130
175
  * Pick the provider to route this call through.
131
176
  *
@@ -760,6 +760,22 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
760
760
  maxEffort: "max",
761
761
  pricing: { inputPer1mTokens: 1.74, outputPer1mTokens: 3.48 },
762
762
  },
763
+ {
764
+ id: "accounts/fireworks/models/deepseek-v4-flash",
765
+ displayName: "DeepSeek V4 Flash",
766
+ contextWindowTokens: 1040000,
767
+ maxOutputTokens: 131072,
768
+ supportsThinking: true,
769
+ supportsCaching: true,
770
+ supportsVision: false,
771
+ supportsToolUse: true,
772
+ maxEffort: "max",
773
+ pricing: {
774
+ inputPer1mTokens: 0.14,
775
+ outputPer1mTokens: 0.28,
776
+ cacheReadPer1mTokens: 0.03,
777
+ },
778
+ },
763
779
  ],
764
780
  defaultModel: "accounts/fireworks/models/kimi-k2p5",
765
781
  apiKeyUrl: "https://fireworks.ai/account/api-keys",