@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
@@ -33,6 +33,7 @@
33
33
 
34
34
  import * as Sentry from "@sentry/node";
35
35
 
36
+ import { recordWatchdogEvent } from "../memory/watchdog-events-store.js";
36
37
  import { getLogger } from "../util/logger.js";
37
38
 
38
39
  const log = getLogger("event-loop-watchdog");
@@ -74,11 +75,37 @@ export function evaluateTick(
74
75
  return { blockedMs, exceeded: blockedMs >= thresholdMs };
75
76
  }
76
77
 
78
+ /**
79
+ * Check name emitted for event-loop block events. The platform's
80
+ * `watchdog__event_loop_blocking_daily` admin query filters `check_name` to
81
+ * this exact string, so it is the primary group-by dimension downstream —
82
+ * keep it stable.
83
+ */
84
+ export const EVENT_LOOP_BLOCKED_CHECK_NAME = "event_loop_blocked";
85
+
77
86
  function reportBlock(blockedMs: number, thresholdMs: number): void {
78
87
  log.warn(
79
88
  { blockedMs, thresholdMs, tickIntervalMs: TICK_INTERVAL_MS },
80
89
  "event loop blocked",
81
90
  );
91
+ // Persist a `watchdog` telemetry event so the platform can surface
92
+ // event-loop blocking in the infrastructure admin chart. `recordWatchdogEvent`
93
+ // no-ops when usage-data collection is disabled (the event is dropped to
94
+ // honor the opt-out), so the watchdog runs unconditionally without leaking
95
+ // health data for opted-out owners. Never let a telemetry failure escape
96
+ // the timer callback — wrap it alongside the Sentry capture below.
97
+ try {
98
+ recordWatchdogEvent({
99
+ checkName: EVENT_LOOP_BLOCKED_CHECK_NAME,
100
+ value: blockedMs,
101
+ detail: {
102
+ threshold_ms: thresholdMs,
103
+ tick_interval_ms: TICK_INTERVAL_MS,
104
+ },
105
+ });
106
+ } catch {
107
+ // Never let a telemetry failure escape the timer callback.
108
+ }
82
109
  try {
83
110
  Sentry.withScope((scope) => {
84
111
  scope.setLevel("warning");
@@ -88,7 +115,7 @@ function reportBlock(blockedMs: number, thresholdMs: number): void {
88
115
  threshold_ms: thresholdMs,
89
116
  tick_interval_ms: TICK_INTERVAL_MS,
90
117
  });
91
- Sentry.captureMessage("event_loop_blocked");
118
+ Sentry.captureMessage(EVENT_LOOP_BLOCKED_CHECK_NAME);
92
119
  });
93
120
  } catch {
94
121
  // Never let a telemetry failure escape the timer callback.
@@ -19,24 +19,20 @@
19
19
  * dropped from the registry via {@link unregisterPlugin} so none of its
20
20
  * hooks participate in the turn lifecycle. This is the primary mechanism for
21
21
  * shipping experimental plugins behind a feature flag.
22
- * 4. Resolves the plugin's `manifest.requiresCredential` entries via the
23
- * credential store helper ({@link getSecureKeyAsync}). In Docker mode
24
- * that helper goes through the CES HTTP API transparently; in local mode
25
- * it hits the encrypted file store / CES RPC backend.
26
- * 5. Validates the config block under `plugins.<name>` against
22
+ * 4. Validates the config block under `plugins.<name>` against
27
23
  * `manifest.config` if the manifest supplies a parser-like validator
28
24
  * (Zod schemas with `.parse()` are supported; anything else is passed
29
25
  * through untouched).
30
- * 6. Creates `<workspaceDir>/plugins-data/<plugin>/` on demand for per-plugin
31
- * writable state and exposes it via {@link PluginInitContext.pluginStorageDir}.
32
- * 7. For each surviving plugin, registers its contributed tools and routes
26
+ * 5. Creates `<workspaceDir>/plugins-data/<plugin>/` on demand for per-plugin
27
+ * writable state and exposes it via {@link InitContext.pluginStorageDir}.
28
+ * 6. For each surviving plugin, registers its contributed tools and routes
33
29
  * into their global registries via {@link registerPluginTools} and
34
30
  * {@link registerSkillRoute}. Contributions land BEFORE `init()` so
35
31
  * the plugin's hook can observe a registry where its own model-visible
36
32
  * surface is already wired — useful for plugins that want to attach
37
33
  * metadata, warm caches, or otherwise interact with their own
38
34
  * contributions during initialization.
39
- * 8. Awaits `plugin.init(ctx)` sequentially. An init failure is contained to
35
+ * 7. Awaits `plugin.init(ctx)` sequentially. An init failure is contained to
40
36
  * the offending plugin: its already-registered tools and routes are rolled
41
37
  * back, it is dropped from the registry, the failure is logged, and
42
38
  * bootstrap continues with the remaining plugins. A single plugin's failure
@@ -68,7 +64,7 @@ import { getRegisteredPlugins, unregisterPlugin } from "../plugins/registry.js";
68
64
  import {
69
65
  type Plugin,
70
66
  PluginExecutionError,
71
- type PluginShutdownContext,
67
+ type ShutdownContext,
72
68
  } from "../plugins/types.js";
73
69
  import { loadUserPlugins } from "../plugins/user-loader.js";
74
70
  import {
@@ -76,7 +72,6 @@ import {
76
72
  type SkillRouteHandle,
77
73
  unregisterSkillRoute,
78
74
  } from "../runtime/skill-route-registry.js";
79
- import { getSecureKeyAsync } from "../security/secure-keys.js";
80
75
  import {
81
76
  registerPluginTools,
82
77
  unregisterPluginTools,
@@ -88,25 +83,6 @@ import { registerShutdownHook } from "./shutdown-registry.js";
88
83
 
89
84
  const log = getLogger("plugins-bootstrap");
90
85
 
91
- /**
92
- * Resolve one credential value. Returns the raw secret string or throws a
93
- * {@link PluginExecutionError} tagged with the plugin name so the caller can
94
- * fail startup with clear attribution.
95
- */
96
- async function resolveCredentialOrThrow(
97
- pluginName: string,
98
- credentialKey: string,
99
- ): Promise<string> {
100
- const value = await getSecureKeyAsync(credentialKey);
101
- if (value === undefined || value === "") {
102
- throw new PluginExecutionError(
103
- `plugin ${pluginName} requires credential "${credentialKey}" but the credential store returned no value`,
104
- pluginName,
105
- );
106
- }
107
- return value;
108
- }
109
-
110
86
  /**
111
87
  * Validate a plugin config block. If the manifest supplies a parser-like
112
88
  * validator (Zod schemas expose `.parse()`), use it. Otherwise pass the
@@ -246,8 +222,8 @@ export async function bootstrapPlugins(): Promise<void> {
246
222
  // Shutdown context is identical for every plugin in this boot — construct
247
223
  // once and reuse across the per-plugin teardown and the normal shutdown
248
224
  // hook below. Only `assistantVersion` is exposed today; future additions
249
- // live on {@link PluginShutdownContext}.
250
- const shutdownContext: PluginShutdownContext = {
225
+ // live on {@link ShutdownContext}.
226
+ const shutdownContext: ShutdownContext = {
251
227
  assistantVersion: APP_VERSION,
252
228
  };
253
229
 
@@ -271,7 +247,14 @@ export async function bootstrapPlugins(): Promise<void> {
271
247
  // out-of-band kill switch — the operator creates a directory named
272
248
  // after the plugin's manifest name (e.g. `plugins/default-advisor/`)
273
249
  // and drops a `.disabled` file inside it. Runs before init so no
274
- // hooks, tools, or routes from the disabled plugin are ever wired.
250
+ // tools or routes from the disabled plugin are ever wired.
251
+ //
252
+ // Unlike the feature-flag path above, we do NOT call
253
+ // `unregisterPlugin(name)` here. The plugin's hooks stay in the hook
254
+ // registry and are filtered at read time by `isPluginDisabled` in
255
+ // `getHooksFor`. This means `assistant plugins enable <name>` takes
256
+ // effect on the next turn without a restart — the hooks are already
257
+ // registered, they just need the sentinel removed to be included.
275
258
  const disabledSentinelPath = join(
276
259
  getWorkspacePluginsDir(),
277
260
  name,
@@ -282,7 +265,6 @@ export async function bootstrapPlugins(): Promise<void> {
282
265
  { plugin: name, sentinel: disabledSentinelPath },
283
266
  `skipping plugin ${name}: disabled via .disabled sentinel`,
284
267
  );
285
- unregisterPlugin(name);
286
268
  continue;
287
269
  }
288
270
 
@@ -346,11 +328,6 @@ async function initializePlugin(
346
328
  let initCompleted = false;
347
329
 
348
330
  try {
349
- const credentials: Record<string, string> = {};
350
- for (const key of plugin.manifest.requiresCredential ?? []) {
351
- credentials[key] = await resolveCredentialOrThrow(name, key);
352
- }
353
-
354
331
  const config = validatePluginConfig(
355
332
  name,
356
333
  plugin.manifest.config,
@@ -359,7 +336,6 @@ async function initializePlugin(
359
336
 
360
337
  const initContext = {
361
338
  config,
362
- credentials,
363
339
  logger: log.child({ plugin: name }),
364
340
  pluginStorageDir: ensurePluginStorageDir(name),
365
341
  assistantVersion: APP_VERSION,
@@ -437,7 +413,7 @@ async function initializePlugin(
437
413
  async function teardownPlugin(
438
414
  active: ActivePlugin,
439
415
  reason: string,
440
- shutdownContext: PluginShutdownContext,
416
+ shutdownContext: ShutdownContext,
441
417
  ): Promise<void> {
442
418
  const { plugin, routeHandles } = active;
443
419
  const name = plugin.manifest.name;
@@ -174,7 +174,6 @@ describe("acceptA2AInvite", () => {
174
174
  expect(contact).not.toBeNull();
175
175
  expect(contact!.channels).toHaveLength(1);
176
176
  expect(contact!.channels[0]!.type).toBe("a2a");
177
- expect(contact!.channels[0]!.status).toBe("active");
178
177
 
179
178
  // Verify assistant metadata
180
179
  const metadata = getAssistantContactMetadata(result.contactId!);
@@ -100,8 +100,6 @@ describe("completeA2AInvite", () => {
100
100
  expect(contact!.displayName).toBe("Acceptor Bot");
101
101
  expect(contact!.channels).toHaveLength(1);
102
102
  expect(contact!.channels[0]!.type).toBe("a2a");
103
- expect(contact!.channels[0]!.status).toBe("active");
104
- expect(contact!.channels[0]!.policy).toBe("allow");
105
103
  });
106
104
 
107
105
  test("contact channel address is acceptor.assistantId.toLowerCase()", () => {
@@ -71,8 +71,6 @@ describe("redeemA2AInvite", () => {
71
71
  expect(contact!.displayName).toBe("Sender Bot");
72
72
  expect(contact!.channels).toHaveLength(1);
73
73
  expect(contact!.channels[0]!.type).toBe("a2a");
74
- expect(contact!.channels[0]!.status).toBe("active");
75
- expect(contact!.channels[0]!.policy).toBe("allow");
76
74
  });
77
75
 
78
76
  test("idempotency: already-connected sender returns alreadyConnected", () => {
@@ -129,4 +127,29 @@ describe("redeemA2AInvite", () => {
129
127
  const result = redeemA2AInvite({ sender: SENDER });
130
128
  expect(result.success).toBe(true);
131
129
  });
130
+
131
+ test("activeConnections counts a2a channel existence, not status", () => {
132
+ const result = redeemA2AInvite({ sender: SENDER });
133
+ expect(result.success).toBe(true);
134
+
135
+ expect(getA2AConfig().activeConnections).toBe(1);
136
+
137
+ // Readiness is existence-based: a non-active stored status still counts.
138
+ const sqlite = getSqlite();
139
+ sqlite.run("UPDATE contact_channels SET status = 'unverified'");
140
+ invalidateConfigCache();
141
+ expect(getA2AConfig().activeConnections).toBe(1);
142
+ });
143
+
144
+ test("already-connected guard fires regardless of stored channel status", () => {
145
+ const first = redeemA2AInvite({ sender: SENDER });
146
+ expect(first.success).toBe(true);
147
+
148
+ const sqlite = getSqlite();
149
+ sqlite.run("UPDATE contact_channels SET status = 'revoked'");
150
+
151
+ const second = redeemA2AInvite({ sender: SENDER });
152
+ expect(second.alreadyConnected).toBe(true);
153
+ expect(second.contactId).toBe(first.contactId);
154
+ });
132
155
  });
@@ -0,0 +1,220 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { GuardianDelivery } from "@vellumai/gateway-client";
4
+
5
+ import type { ContactChannel } from "../../../contacts/types.js";
6
+
7
+ let mockGuardians: GuardianDelivery[] | null = null;
8
+ let mockBinding: { guardianExternalUserId: string; guardianDeliveryChatId: string } | null = null;
9
+ let mockContactChannel: { channel: ContactChannel } | null = null;
10
+ let mockChannel: ContactChannel | null = null;
11
+ let mockGwContactChannels: Array<{ id: string; status: string; verifiedAt: number | null }> | null =
12
+ null;
13
+ let ipcCalls: Array<{ method: string; payload: unknown }> = [];
14
+
15
+ mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
16
+ getGuardianDelivery: async (input?: { channelTypes?: string[] }) => {
17
+ if (mockGuardians == null) return null;
18
+ if (!input?.channelTypes) return mockGuardians;
19
+ return mockGuardians.filter((g) =>
20
+ input.channelTypes!.includes(g.channelType),
21
+ );
22
+ },
23
+ guardianForChannel: (
24
+ list: GuardianDelivery[],
25
+ channelType: string,
26
+ ) => list.find((g) => g.channelType === channelType && g.status === "active"),
27
+ }));
28
+
29
+ mock.module("../../../contacts/contact-store.js", () => ({
30
+ findContactChannel: () => mockContactChannel,
31
+ getChannelById: () => mockChannel,
32
+ getContact: () => ({ id: "contact-1", displayName: "Pat" }),
33
+ }));
34
+
35
+ mock.module("../../../contacts/contact-events.js", () => ({
36
+ emitContactChange: () => {},
37
+ onContactChange: () => {},
38
+ }));
39
+
40
+ mock.module("../../../ipc/gateway-client.js", () => ({
41
+ ipcCallPersistent: async (method: string, payload: unknown) => {
42
+ ipcCalls.push({ method, payload });
43
+ if (method === "contacts_get_rich") {
44
+ if (mockGwContactChannels == null) return { ok: true, contact: null };
45
+ return {
46
+ ok: true,
47
+ contact: {
48
+ id: "contact-1",
49
+ displayName: "Pat",
50
+ role: "contact",
51
+ interactionCount: 0,
52
+ createdAt: 0,
53
+ updatedAt: 0,
54
+ channels: mockGwContactChannels.map((c) => ({
55
+ id: c.id,
56
+ contactId: "contact-1",
57
+ type: "telegram",
58
+ address: "user-123",
59
+ isPrimary: true,
60
+ externalUserId: null,
61
+ status: c.status,
62
+ policy: "allow",
63
+ verifiedAt: c.verifiedAt,
64
+ verifiedVia: null,
65
+ lastSeenAt: null,
66
+ interactionCount: 0,
67
+ lastInteraction: null,
68
+ revokedReason: null,
69
+ blockedReason: null,
70
+ })),
71
+ },
72
+ };
73
+ }
74
+ return {
75
+ ok: true,
76
+ didWrite: true,
77
+ channel: {
78
+ id: "ch-1",
79
+ contactId: "contact-1",
80
+ type: "telegram",
81
+ address: "user-123",
82
+ status: "revoked",
83
+ revokedReason: "guardian_binding_revoked",
84
+ },
85
+ };
86
+ },
87
+ ipcCall: async () => null,
88
+ }));
89
+
90
+ mock.module("../../../runtime/channel-verification-service.js", () => ({
91
+ getGuardianBinding: () => mockBinding,
92
+ revokeBinding: () => true,
93
+ revokePendingSessions: () => {},
94
+ createOutboundSession: () => ({
95
+ sessionId: "sess",
96
+ secret: "code",
97
+ expiresAt: Date.now() + 1000,
98
+ }),
99
+ countRecentSendsToDestination: () => 0,
100
+ isGuardianBoundForChannel: async () => false,
101
+ updateSessionDelivery: () => {},
102
+ }));
103
+
104
+ mock.module("../../../runtime/verification-outbound-actions.js", () => ({
105
+ cancelOutbound: () => {},
106
+ deliverVerificationEmail: () => {},
107
+ deliverVerificationSlack: () => {},
108
+ deliverVerificationTelegram: () => {},
109
+ DESTINATION_RATE_WINDOW_MS: 1000,
110
+ MAX_SENDS_PER_DESTINATION_WINDOW: 5,
111
+ normalizeTelegramDestination: (d: string) => d,
112
+ resendOutbound: () => ({}),
113
+ startOutbound: async () => ({}),
114
+ }));
115
+
116
+ import {
117
+ revokeVerificationForChannel,
118
+ verifyTrustedContact,
119
+ } from "../config-channels.js";
120
+
121
+ function channel(overrides: Partial<ContactChannel> = {}): ContactChannel {
122
+ return {
123
+ id: "ch-1",
124
+ contactId: "contact-1",
125
+ type: "telegram",
126
+ address: "user-123",
127
+ isPrimary: true,
128
+ externalChatId: "chat-123",
129
+ inviteId: null,
130
+ lastSeenAt: null,
131
+ interactionCount: 0,
132
+ lastInteraction: null,
133
+ updatedAt: null,
134
+ createdAt: 0,
135
+ ...overrides,
136
+ };
137
+ }
138
+
139
+ function delivery(overrides: Partial<GuardianDelivery> = {}): GuardianDelivery {
140
+ return {
141
+ channelType: "telegram",
142
+ contactId: "contact-1",
143
+ address: "user-123",
144
+ externalChatId: "chat-123",
145
+ status: "active",
146
+ verifiedAt: 1700000000,
147
+ ...overrides,
148
+ };
149
+ }
150
+
151
+ describe("revokeVerificationForChannel", () => {
152
+ beforeEach(() => {
153
+ mockGuardians = null;
154
+ mockBinding = { guardianExternalUserId: "user-123", guardianDeliveryChatId: "chat-123" };
155
+ mockContactChannel = { channel: channel() };
156
+ ipcCalls = [];
157
+ });
158
+
159
+ test("relays mark_channel_revoked when the gateway delivery is live", async () => {
160
+ mockGuardians = [delivery({ status: "active" })];
161
+ await revokeVerificationForChannel("telegram");
162
+ expect(ipcCalls.map((c) => c.method)).toContain("mark_channel_revoked");
163
+ });
164
+
165
+ test("skips a redundant revoke when the gateway delivery is already revoked", async () => {
166
+ // The gateway (SoT) says revoked — the gate must follow the gateway and
167
+ // not relay regardless of local state.
168
+ mockContactChannel = { channel: channel() };
169
+ mockGuardians = [delivery({ status: "revoked" })];
170
+ await revokeVerificationForChannel("telegram");
171
+ expect(ipcCalls.map((c) => c.method)).not.toContain("mark_channel_revoked");
172
+ });
173
+
174
+ test("skips the relay when the gateway has no delivery for the channel", async () => {
175
+ mockGuardians = [];
176
+ await revokeVerificationForChannel("telegram");
177
+ expect(ipcCalls.map((c) => c.method)).not.toContain("mark_channel_revoked");
178
+ });
179
+
180
+ test("skips the relay when the gateway is unreachable", async () => {
181
+ mockGuardians = null;
182
+ await revokeVerificationForChannel("telegram");
183
+ expect(ipcCalls.map((c) => c.method)).not.toContain("mark_channel_revoked");
184
+ });
185
+ });
186
+
187
+ describe("verifyTrustedContact already-verified gate", () => {
188
+ beforeEach(() => {
189
+ mockGuardians = null;
190
+ mockGwContactChannels = null;
191
+ mockChannel = channel();
192
+ });
193
+
194
+ test("short-circuits when the gateway contact channel is active and verified", async () => {
195
+ // Arbitrary trusted contact (non-guardian) — read from the contact-channel
196
+ // gateway read, which covers all contacts.
197
+ mockGwContactChannels = [
198
+ { id: "ch-1", status: "active", verifiedAt: 1700000000 },
199
+ ];
200
+ const result = await verifyTrustedContact("ch-1", "assistant-1");
201
+ expect(result.success).toBe(false);
202
+ expect(result.error).toBe("already_verified");
203
+ });
204
+
205
+ test("does not short-circuit when the gateway channel has no verifiedAt", async () => {
206
+ // The gateway channel is unverified — proceed regardless of local state.
207
+ mockChannel = channel();
208
+ mockGwContactChannels = [
209
+ { id: "ch-1", status: "pending", verifiedAt: null },
210
+ ];
211
+ const result = await verifyTrustedContact("ch-1", "assistant-1");
212
+ expect(result.error).not.toBe("already_verified");
213
+ });
214
+
215
+ test("does not short-circuit when the gateway has no matching channel", async () => {
216
+ mockGwContactChannels = [];
217
+ const result = await verifyTrustedContact("ch-1", "assistant-1");
218
+ expect(result.error).not.toBe("already_verified");
219
+ });
220
+ });
@@ -96,13 +96,11 @@ export function getA2AConfig(): A2AConfigResult {
96
96
  const config = getConfig();
97
97
  const enabled = config.a2a?.enabled ?? false;
98
98
 
99
+ // a2a is peer binding outside the human-trust ACL model — the gateway has no
100
+ // canonical a2a channel status, so channel existence is the readiness signal.
99
101
  const contacts = searchContacts({ channelType: "a2a" });
100
102
  const activeConnections = contacts.reduce((count, c) => {
101
- return (
102
- count +
103
- c.channels.filter((ch) => ch.type === "a2a" && ch.status === "active")
104
- .length
105
- );
103
+ return count + c.channels.filter((ch) => ch.type === "a2a").length;
106
104
  }, 0);
107
105
 
108
106
  return { success: true, enabled, activeConnections };
@@ -270,12 +268,9 @@ export function redeemA2AInvite(params: {
270
268
  setA2AConfig();
271
269
  }
272
270
 
273
- // 2. Check for existing active contact with this sender
271
+ // 2. Check for existing contact with this sender (a2a binding existence)
274
272
  const existing = findContactByAddress("a2a", params.sender.assistantId);
275
- if (
276
- existing &&
277
- existing.channels.some((ch) => ch.type === "a2a" && ch.status === "active")
278
- ) {
273
+ if (existing && existing.channels.some((ch) => ch.type === "a2a")) {
279
274
  return { success: true, alreadyConnected: true, contactId: existing.id };
280
275
  }
281
276
 
@@ -373,10 +368,7 @@ export async function acceptA2AInvite(params: {
373
368
  // 2. Short-circuit if already connected — avoids a network round-trip
374
369
  // and consuming a token on the sender side.
375
370
  const existing = findContactByAddress("a2a", params.senderAssistantId);
376
- if (
377
- existing &&
378
- existing.channels.some((ch) => ch.type === "a2a" && ch.status === "active")
379
- ) {
371
+ if (existing && existing.channels.some((ch) => ch.type === "a2a")) {
380
372
  return { success: true, alreadyConnected: true, contactId: existing.id };
381
373
  }
382
374