@vellumai/assistant 0.10.2-dev.202606250318.5e7cfb0 → 0.10.2

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 (430) hide show
  1. package/bun.lock +0 -20
  2. package/docs/workspace-tools.md +33 -42
  3. package/eslint-rules/cli-no-daemon-internals.js +0 -6
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +0 -31
  5. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +0 -44
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +0 -14
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +0 -17
  8. package/node_modules/@vellumai/service-contracts/package.json +0 -1
  9. package/node_modules/@vellumai/service-contracts/src/index.ts +0 -1
  10. package/openapi.yaml +0 -155
  11. package/package.json +1 -4
  12. package/scripts/test.sh +15 -36
  13. package/src/__tests__/actor-token-service.test.ts +14 -36
  14. package/src/__tests__/agent-loop-override-profile.test.ts +0 -1
  15. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -2
  16. package/src/__tests__/agent-wake-override-profile.test.ts +0 -2
  17. package/src/__tests__/annotate-activity-metadata.test.ts +0 -2
  18. package/src/__tests__/annotate-risk-options.test.ts +0 -2
  19. package/src/__tests__/approval-cascade.test.ts +0 -2
  20. package/src/__tests__/assistant-attachments.test.ts +0 -42
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
  22. package/src/__tests__/btw-routes.test.ts +0 -2
  23. package/src/__tests__/build-persisted-content.test.ts +0 -2
  24. package/src/__tests__/call-controller.test.ts +0 -19
  25. package/src/__tests__/channel-guardian.test.ts +58 -94
  26. package/src/__tests__/channel-reply-delivery.test.ts +0 -2
  27. package/src/__tests__/compaction-events.test.ts +0 -2
  28. package/src/__tests__/compaction.benchmark.test.ts +0 -2
  29. package/src/__tests__/compactor-call-site-logging.test.ts +0 -2
  30. package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -2
  31. package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -2
  32. package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -2
  33. package/src/__tests__/compactor-web-search-strip.test.ts +0 -2
  34. package/src/__tests__/config-loader-backfill.test.ts +10 -123
  35. package/src/__tests__/config-schema.test.ts +0 -1
  36. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -31
  37. package/src/__tests__/contacts-relay-reads.test.ts +15 -13
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -2
  39. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +0 -2
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -2
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  42. package/src/__tests__/conversation-agent-loop.test.ts +0 -134
  43. package/src/__tests__/conversation-analysis-routes.test.ts +0 -2
  44. package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -2
  45. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -2
  46. package/src/__tests__/conversation-history-web-search.test.ts +0 -2
  47. package/src/__tests__/conversation-load-history-repair.test.ts +0 -2
  48. package/src/__tests__/conversation-load-history-stripped.test.ts +0 -2
  49. package/src/__tests__/conversation-pairing.test.ts +0 -2
  50. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -2
  51. package/src/__tests__/conversation-process-callsite.test.ts +0 -2
  52. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -2
  53. package/src/__tests__/conversation-queue.test.ts +0 -91
  54. package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -14
  55. package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -14
  56. package/src/__tests__/conversation-slash-queue.test.ts +0 -2
  57. package/src/__tests__/conversation-slash-unknown.test.ts +0 -2
  58. package/src/__tests__/conversation-speed-override.test.ts +0 -2
  59. package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -29
  60. package/src/__tests__/conversation-title-service.test.ts +0 -2
  61. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -47
  62. package/src/__tests__/conversation-usage.test.ts +0 -2
  63. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -2
  64. package/src/__tests__/conversation-workspace-injection.test.ts +0 -2
  65. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -2
  66. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  67. package/src/__tests__/db-migration-rollback.test.ts +171 -205
  68. package/src/__tests__/db-test-helpers.ts +4 -5
  69. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -4
  70. package/src/__tests__/disk-pressure-guard.test.ts +0 -41
  71. package/src/__tests__/dm-persistence.test.ts +0 -2
  72. package/src/__tests__/emit-signal-routing-intent.test.ts +5 -10
  73. package/src/__tests__/events-dev-bypass-actor.test.ts +1 -7
  74. package/src/__tests__/exploration-drift-hook.test.ts +2 -3
  75. package/src/__tests__/filing-service.test.ts +0 -2
  76. package/src/__tests__/guardian-binding-drift-heal.test.ts +10 -75
  77. package/src/__tests__/guardian-dispatch.test.ts +1 -95
  78. package/src/__tests__/guardian-outbound-http.test.ts +0 -13
  79. package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -2
  80. package/src/__tests__/heartbeat-service.test.ts +0 -2
  81. package/src/__tests__/helpers/channel-test-adapter.ts +7 -1
  82. package/src/__tests__/host-app-control-routes.test.ts +30 -24
  83. package/src/__tests__/host-bash-routes.test.ts +41 -31
  84. package/src/__tests__/host-browser-routes.test.ts +32 -26
  85. package/src/__tests__/host-cu-routes-targeted.test.ts +33 -25
  86. package/src/__tests__/host-file-routes-targeted.test.ts +52 -40
  87. package/src/__tests__/host-transfer-routes-targeted.test.ts +43 -31
  88. package/src/__tests__/http-user-message-parity.test.ts +8 -290
  89. package/src/__tests__/inbound-invite-redemption.test.ts +0 -28
  90. package/src/__tests__/inbound-slack-persistence.test.ts +0 -2
  91. package/src/__tests__/invite-redemption-service.test.ts +0 -198
  92. package/src/__tests__/llm-context-normalization.test.ts +0 -105
  93. package/src/__tests__/llm-request-log-error-payload.test.ts +9 -71
  94. package/src/__tests__/llm-usage-store.test.ts +0 -25
  95. package/src/__tests__/mcp-health-check.test.ts +1 -2
  96. package/src/__tests__/media-stream-server-integration.test.ts +0 -127
  97. package/src/__tests__/memory-retrieval-hook.test.ts +0 -2
  98. package/src/__tests__/messaging-send-tool.test.ts +0 -2
  99. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  100. package/src/__tests__/mtime-cache.test.ts +5 -146
  101. package/src/__tests__/native-web-search.test.ts +0 -2
  102. package/src/__tests__/non-member-access-request.test.ts +17 -189
  103. package/src/__tests__/notification-broadcaster.test.ts +0 -4
  104. package/src/__tests__/notification-decision-recipient-context.test.ts +32 -33
  105. package/src/__tests__/notification-deep-link.test.ts +0 -6
  106. package/src/__tests__/notification-guardian-path.test.ts +0 -19
  107. package/src/__tests__/openai-provider.test.ts +12 -22
  108. package/src/__tests__/openai-responses-provider.test.ts +2 -12
  109. package/src/__tests__/outbound-slack-persistence.test.ts +0 -2
  110. package/src/__tests__/pending-interactions-resolved-event.test.ts +4 -7
  111. package/src/__tests__/persistence-secret-redaction.test.ts +0 -2
  112. package/src/__tests__/plugin-bootstrap.test.ts +73 -3
  113. package/src/__tests__/plugin-route-contribution.test.ts +17 -4
  114. package/src/__tests__/plugin-tool-contribution.test.ts +18 -3
  115. package/src/__tests__/plugin-types.test.ts +2 -0
  116. package/src/__tests__/process-message-background-slack.test.ts +0 -2
  117. package/src/__tests__/process-message-display-content.test.ts +0 -2
  118. package/src/__tests__/provider-error-scenarios.test.ts +4 -5
  119. package/src/__tests__/provider-usage-tracking.test.ts +0 -39
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -2
  121. package/src/__tests__/registry.test.ts +1 -4
  122. package/src/__tests__/relay-server.test.ts +25 -694
  123. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -0
  124. package/src/__tests__/secret-ingress-http.test.ts +0 -14
  125. package/src/__tests__/send-endpoint-busy.test.ts +8 -30
  126. package/src/__tests__/skills.test.ts +0 -44
  127. package/src/__tests__/slack-inbound-verification.test.ts +2 -47
  128. package/src/__tests__/stt-hints.test.ts +13 -44
  129. package/src/__tests__/subagent-detail.test.ts +0 -27
  130. package/src/__tests__/subagent-disposal.test.ts +0 -65
  131. package/src/__tests__/subagent-notify-parent.test.ts +0 -2
  132. package/src/__tests__/subagent-role-registry.test.ts +2 -7
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +0 -2
  134. package/src/__tests__/subagent-tools.test.ts +0 -2
  135. package/src/__tests__/suggestion-routes.test.ts +0 -2
  136. package/src/__tests__/title-generate-hook.test.ts +0 -2
  137. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -2
  138. package/src/__tests__/tool-executor.test.ts +11 -16
  139. package/src/__tests__/tool-preview-lifecycle.test.ts +0 -2
  140. package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -2
  141. package/src/__tests__/tool-start-timestamp.test.ts +0 -2
  142. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  143. package/src/__tests__/twilio-routes.test.ts +0 -96
  144. package/src/__tests__/ui-file-upload-surface.test.ts +0 -86
  145. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  146. package/src/__tests__/voice-invite-redemption.test.ts +0 -33
  147. package/src/__tests__/web-search-backend-failure.test.ts +0 -2
  148. package/src/__tests__/workspace-migration-remove-hooks.test.ts +35 -14
  149. package/src/__tests__/workspace-tool-loader.test.ts +2 -195
  150. package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
  151. package/src/agent/loop.ts +0 -56
  152. package/src/api/index.ts +1 -19
  153. package/src/api/responses/llm-request-log-entry.ts +0 -29
  154. package/src/api/responses/subagent-detail.ts +0 -17
  155. package/src/api/surfaces.ts +3 -39
  156. package/src/approvals/guardian-request-resolvers.ts +11 -1
  157. package/src/calls/__tests__/relay-setup-router.test.ts +4 -262
  158. package/src/calls/call-domain.ts +3 -3
  159. package/src/calls/guardian-dispatch.ts +8 -10
  160. package/src/calls/inbound-trust-reader.ts +1 -17
  161. package/src/calls/media-stream-server.ts +0 -21
  162. package/src/calls/relay-server.ts +50 -167
  163. package/src/calls/relay-setup-router.ts +7 -37
  164. package/src/calls/relay-verification.ts +4 -4
  165. package/src/calls/stt-hints.ts +12 -9
  166. package/src/calls/twilio-routes.ts +4 -14
  167. package/src/channels/types.ts +20 -10
  168. package/src/cli/commands/__tests__/cache.test.ts +1 -8
  169. package/src/cli/commands/cache.ts +181 -194
  170. package/src/cli/commands/db/__tests__/repair.test.ts +5 -6
  171. package/src/cli/commands/db/status.ts +1 -37
  172. package/src/cli/commands/mcp.ts +218 -252
  173. package/src/cli/commands/memory/index.ts +0 -2
  174. package/src/cli/commands/plugins.ts +3 -75
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +0 -102
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +1 -160
  177. package/src/cli/lib/list-installed-plugins.ts +1 -179
  178. package/src/config/__tests__/sync-gated-profiles.test.ts +3 -11
  179. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +17 -27
  180. package/src/config/bundled-skills/contacts/tools/contact-search.ts +3 -13
  181. package/src/config/bundled-skills/subagent/SKILL.md +1 -1
  182. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  183. package/src/config/feature-flag-registry.json +13 -5
  184. package/src/config/loader.ts +5 -38
  185. package/src/config/schemas/__tests__/memory-v3.test.ts +0 -1
  186. package/src/config/schemas/memory-lifecycle.ts +0 -12
  187. package/src/config/schemas/memory-v3.ts +0 -7
  188. package/src/config/schemas/memory.ts +0 -4
  189. package/src/config/schemas/timeouts.ts +0 -8
  190. package/src/config/seed-inference-profiles.ts +11 -21
  191. package/src/config/skills.ts +5 -27
  192. package/src/config/sync-gated-profiles.ts +13 -12
  193. package/src/contacts/contacts-write.ts +0 -3
  194. package/src/daemon/assistant-attachments.ts +4 -27
  195. package/src/daemon/conversation-agent-loop.ts +0 -28
  196. package/src/daemon/conversation-process.ts +16 -35
  197. package/src/daemon/conversation-surfaces.ts +38 -111
  198. package/src/daemon/conversation-tool-setup.ts +16 -50
  199. package/src/daemon/conversation.ts +1 -13
  200. package/src/daemon/disk-pressure-guard.ts +2 -12
  201. package/src/daemon/event-loop-watchdog.ts +1 -28
  202. package/src/daemon/external-plugins-bootstrap.ts +34 -4
  203. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -25
  204. package/src/daemon/handlers/config-a2a.ts +14 -6
  205. package/src/daemon/handlers/config-channels.ts +22 -78
  206. package/src/daemon/handlers/conversations.ts +0 -77
  207. package/src/daemon/lifecycle.ts +0 -4
  208. package/src/daemon/mcp-reload-service.ts +0 -10
  209. package/src/daemon/memory-v2-startup.test.ts +0 -72
  210. package/src/daemon/memory-v2-startup.ts +19 -87
  211. package/src/daemon/message-types/conversations.ts +0 -2
  212. package/src/daemon/message-types/surfaces.ts +12 -12
  213. package/src/daemon/server.ts +4 -0
  214. package/src/daemon/shutdown-handlers.ts +0 -20
  215. package/src/daemon/tool-setup-types.ts +0 -9
  216. package/src/daemon/workspace-tools-watcher.ts +328 -0
  217. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  218. package/src/ipc/assistant-server.ts +2 -2
  219. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  220. package/src/mcp/client.ts +1 -15
  221. package/src/mcp/mcp-auth-orchestrator.ts +1 -6
  222. package/src/mcp/mcp-oauth-provider.ts +8 -19
  223. package/src/memory/__tests__/memory-retrospective-job.test.ts +0 -8
  224. package/src/memory/conversation-crud.ts +0 -38
  225. package/src/memory/db-connection.ts +3 -22
  226. package/src/memory/db-init.ts +502 -36
  227. package/src/memory/db-singleton.ts +4 -6
  228. package/src/memory/jobs-worker.ts +0 -58
  229. package/src/memory/llm-request-log-store.ts +1 -26
  230. package/src/memory/llm-usage-store.ts +20 -48
  231. package/src/memory/memory-retrospective-job.ts +8 -9
  232. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +56 -130
  233. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  234. package/src/memory/migrations/registry.ts +573 -0
  235. package/src/memory/migrations/run-migrations.ts +6 -90
  236. package/src/memory/migrations/validate-migration-state.ts +66 -101
  237. package/src/memory/schema/conversations.ts +0 -9
  238. package/src/memory/schema/infrastructure.ts +0 -20
  239. package/src/memory/v2/__tests__/cli-command-store.test.ts +0 -25
  240. package/src/memory/v2/__tests__/skill-store.test.ts +0 -80
  241. package/src/memory/v2/cli-command-store.ts +38 -75
  242. package/src/memory/v2/prompts/consolidation.ts +82 -13
  243. package/src/memory/v2/prompts/router.ts +93 -21
  244. package/src/memory/v2/skill-store.ts +31 -68
  245. package/src/notifications/__tests__/broadcaster.test.ts +8 -16
  246. package/src/notifications/__tests__/decision-engine.test.ts +9 -78
  247. package/src/notifications/broadcaster.ts +1 -8
  248. package/src/notifications/decision-engine.ts +7 -15
  249. package/src/notifications/destination-resolver.ts +24 -68
  250. package/src/notifications/emit-signal.ts +14 -39
  251. package/src/permissions/question-prompter.test.ts +1 -1
  252. package/src/permissions/question-prompter.ts +4 -7
  253. package/src/plugin-api/index.ts +6 -6
  254. package/src/plugin-api/types.ts +5 -3
  255. package/src/plugin-api/vision-support.test.ts +4 -28
  256. package/src/plugin-api/vision-support.ts +31 -66
  257. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -161
  258. package/src/plugins/defaults/advisor/consult.ts +6 -110
  259. package/src/plugins/defaults/advisor/steering.ts +2 -14
  260. package/src/plugins/defaults/advisor/tools/advisor.ts +5 -32
  261. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +1 -2
  262. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +7 -47
  263. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +11 -10
  264. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +20 -12
  265. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +11 -42
  266. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +3 -33
  267. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +4 -48
  268. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +8 -4
  269. package/src/plugins/defaults/memory-v3-shadow/injector.ts +15 -43
  270. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +2 -11
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -77
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +11 -12
  273. package/src/plugins/mtime-cache.ts +291 -76
  274. package/src/plugins/pipeline.ts +13 -111
  275. package/src/plugins/types.ts +2 -0
  276. package/src/providers/anthropic/client.ts +0 -5
  277. package/src/providers/call-site-routing.ts +0 -4
  278. package/src/providers/model-catalog.ts +0 -16
  279. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  280. package/src/providers/openai/chat-completions-provider.ts +83 -37
  281. package/src/providers/openai/responses-provider.ts +46 -50
  282. package/src/providers/openrouter/client.ts +0 -5
  283. package/src/providers/provider-send-message.ts +0 -4
  284. package/src/providers/ratelimit.ts +0 -4
  285. package/src/providers/retry.ts +0 -4
  286. package/src/providers/types.ts +0 -9
  287. package/src/providers/usage-tracking.ts +0 -4
  288. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +3 -335
  289. package/src/runtime/access-request-helper.ts +39 -19
  290. package/src/runtime/actor-trust-resolver.ts +2 -2
  291. package/src/runtime/assistant-event-hub.ts +1 -1
  292. package/src/runtime/assistant-stream-state.ts +2 -9
  293. package/src/runtime/auth/require-bound-guardian.ts +11 -21
  294. package/src/runtime/channel-verification-service.ts +31 -56
  295. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  296. package/src/runtime/guardian-vellum-migration.ts +7 -66
  297. package/src/runtime/invite-redemption-service.ts +187 -198
  298. package/src/runtime/local-actor-identity.ts +11 -76
  299. package/src/runtime/pending-interactions.ts +1 -11
  300. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +5 -56
  301. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  302. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +0 -187
  303. package/src/runtime/routes/browser-routes.ts +1 -1
  304. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -13
  305. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  306. package/src/runtime/routes/contact-routes.ts +32 -8
  307. package/src/runtime/routes/conversation-cli-routes.ts +5 -4
  308. package/src/runtime/routes/conversation-list-routes.ts +7 -4
  309. package/src/runtime/routes/conversation-query-routes.ts +0 -72
  310. package/src/runtime/routes/conversation-routes.ts +85 -84
  311. package/src/runtime/routes/events-routes.ts +2 -2
  312. package/src/runtime/routes/global-search-routes.ts +1 -3
  313. package/src/runtime/routes/guardian-action-routes.ts +5 -4
  314. package/src/runtime/routes/host-app-control-routes.ts +4 -5
  315. package/src/runtime/routes/host-bash-routes.ts +4 -5
  316. package/src/runtime/routes/host-browser-routes.ts +11 -9
  317. package/src/runtime/routes/host-cu-routes.ts +4 -5
  318. package/src/runtime/routes/host-file-routes.ts +4 -5
  319. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  320. package/src/runtime/routes/http-adapter.ts +1 -1
  321. package/src/runtime/routes/identity-routes.ts +2 -3
  322. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  323. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +5 -97
  324. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +49 -61
  325. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -16
  326. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  327. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +8 -21
  328. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +3 -14
  329. package/src/runtime/routes/index.ts +0 -2
  330. package/src/runtime/routes/llm-context-normalization.ts +0 -83
  331. package/src/runtime/routes/mcp-auth-routes.ts +19 -171
  332. package/src/runtime/routes/migration-rollback-routes.ts +3 -4
  333. package/src/runtime/routes/migration-routes.ts +1 -4
  334. package/src/runtime/routes/subagents-routes.ts +0 -5
  335. package/src/runtime/routes/surface-action-routes.ts +56 -42
  336. package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -1
  337. package/src/runtime/services/conversation-serializer.ts +9 -7
  338. package/src/runtime/tool-grant-request-helper.ts +3 -3
  339. package/src/runtime/trust-verdict-consumer.ts +9 -85
  340. package/src/runtime/verification-outbound-actions.ts +18 -18
  341. package/src/signals/user-message.ts +0 -16
  342. package/src/subagent/manager.ts +0 -9
  343. package/src/subagent/types.ts +3 -3
  344. package/src/telemetry/types.ts +1 -34
  345. package/src/telemetry/usage-telemetry-reporter.test.ts +2 -3
  346. package/src/telemetry/usage-telemetry-reporter.ts +3 -87
  347. package/src/tools/ask-question/ask-question-tool.test.ts +0 -29
  348. package/src/tools/ask-question/ask-question-tool.ts +0 -13
  349. package/src/tools/executor.ts +4 -4
  350. package/src/tools/registry.ts +0 -18
  351. package/src/tools/shared/filesystem/path-policy.ts +5 -12
  352. package/src/tools/tool-approval-handler.ts +1 -1
  353. package/src/tools/tool-defaults.ts +2 -9
  354. package/src/tools/tool-manifest.ts +0 -3
  355. package/src/tools/types.ts +2 -17
  356. package/src/tools/workspace-tools/loader.ts +244 -348
  357. package/src/util/errors.ts +1 -26
  358. package/src/util/platform.ts +0 -5
  359. package/src/workflows/library.test.ts +0 -140
  360. package/src/workflows/library.ts +28 -82
  361. package/src/workspace/migrations/017-seed-persona-dirs.ts +34 -3
  362. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +24 -3
  363. package/src/workspace/migrations/048-remove-workspace-hooks.ts +66 -14
  364. package/src/workspace/migrations/registry.ts +0 -2
  365. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +0 -91
  366. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +0 -48
  367. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +0 -28
  368. package/node_modules/@vellumai/service-contracts/src/channels.ts +0 -41
  369. package/src/__tests__/code-search-tool.test.ts +0 -585
  370. package/src/__tests__/guardian-expiry-notifier.test.ts +0 -282
  371. package/src/__tests__/mcp-config-secret-boundary.test.ts +0 -390
  372. package/src/__tests__/plugin-pipeline.test.ts +0 -96
  373. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +0 -102
  374. package/src/__tests__/steer-on-enqueue-question.test.ts +0 -181
  375. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +0 -208
  376. package/src/agent/loop-exclusive-tool.test.ts +0 -150
  377. package/src/api/constants/sse-replay.ts +0 -41
  378. package/src/api/events/conversation-notice.ts +0 -26
  379. package/src/approvals/guardian-channel-delivery.ts +0 -30
  380. package/src/approvals/guardian-expiry-notifier.ts +0 -148
  381. package/src/cli/commands/memory/__tests__/worker.test.ts +0 -302
  382. package/src/cli/commands/memory/worker.ts +0 -175
  383. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -143
  384. package/src/config/prune-seeded-callsite-defaults.ts +0 -110
  385. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -129
  386. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +0 -312
  387. package/src/contacts/__tests__/member-write-relay.test.ts +0 -202
  388. package/src/contacts/guardian-delivery-reader.ts +0 -223
  389. package/src/contacts/member-write-relay.ts +0 -189
  390. package/src/daemon/conversation-notices.ts +0 -60
  391. package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -225
  392. package/src/hooks/hook-loader.ts +0 -341
  393. package/src/mcp/mcp-header-store.ts +0 -134
  394. package/src/memory/__tests__/301-create-watchdog-events.test.ts +0 -110
  395. package/src/memory/__tests__/prompt-override.test.ts +0 -192
  396. package/src/memory/__tests__/watchdog-events-store.test.ts +0 -161
  397. package/src/memory/migrations/300-add-processing-started-at.ts +0 -30
  398. package/src/memory/migrations/301-create-watchdog-events.ts +0 -45
  399. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +0 -224
  400. package/src/memory/prompt-override.ts +0 -129
  401. package/src/memory/steps.ts +0 -573
  402. package/src/memory/watchdog-events-store.ts +0 -87
  403. package/src/memory/worker-control.ts +0 -118
  404. package/src/memory/worker-process.ts +0 -72
  405. package/src/notifications/__tests__/connected-channels.test.ts +0 -114
  406. package/src/notifications/__tests__/destination-resolver.test.ts +0 -256
  407. package/src/onboarding/checkin-event.test.ts +0 -222
  408. package/src/onboarding/checkin-event.ts +0 -321
  409. package/src/onboarding/schedule-checkin.ts +0 -190
  410. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
  411. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
  412. package/src/plugins/defaults/advisor/context-pack.ts +0 -288
  413. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +0 -146
  414. package/src/plugins/surface-import.ts +0 -121
  415. package/src/providers/openai/__tests__/api-error-normalization.test.ts +0 -321
  416. package/src/providers/openai/api-error-normalization.ts +0 -270
  417. package/src/runtime/__tests__/channel-verification-service.test.ts +0 -133
  418. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -181
  419. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +0 -66
  420. package/src/runtime/__tests__/local-principal-trust.test.ts +0 -164
  421. package/src/runtime/anchored-guardian.test.ts +0 -156
  422. package/src/runtime/anchored-guardian.ts +0 -135
  423. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +0 -99
  424. package/src/runtime/local-principal-trust.ts +0 -52
  425. package/src/runtime/routes/__tests__/contact-routes.test.ts +0 -212
  426. package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -93
  427. package/src/runtime/routes/onboarding-checkin-routes.ts +0 -86
  428. package/src/tools/filesystem/search.ts +0 -543
  429. package/src/util/telemetry-db-path.ts +0 -24
  430. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +0 -134
@@ -1,270 +0,0 @@
1
- import type OpenAI from "openai";
2
-
3
- /**
4
- * Normalized view of an OpenAI-compatible `APIError`. The SDK reads
5
- * `error.message` and renders bodies it can't parse as "(no body)", so
6
- * Django `{"detail": "..."}` payloads from the managed runtime proxy and
7
- * OpenRouter's nested `error.metadata.raw` get dropped. We capture the raw
8
- * non-2xx body in a `fetch` wrapper and reconstruct the useful fields here.
9
- */
10
- export interface NormalizedOpenAIAPIError {
11
- message: string;
12
- detail?: string;
13
- requestId?: string;
14
- apiErrorCode?: string;
15
- apiErrorType?: string;
16
- apiErrorParam?: string;
17
- /**
18
- * The captured raw upstream non-2xx body, verbatim (possibly truncated to
19
- * MAX_CAPTURED_BODY_CHARS). Carried so callers can persist the actual
20
- * provider payload for the inspector's Raw tab instead of only the
21
- * extracted fields. Absent for retryable (429/5xx) errors, whose bodies
22
- * `captureRawErrorBodyFetch` intentionally doesn't drain.
23
- */
24
- rawBody?: string;
25
- }
26
-
27
- const MAX_DETAIL_CHARS = 2000;
28
- const REQUEST_ID_HEADERS = [
29
- "x-request-id",
30
- "x-openrouter-request-id",
31
- "openai-request-id",
32
- "x-amzn-requestid",
33
- ] as const;
34
-
35
- // The OpenAI SDK keeps only the `.error` key of a parsed JSON error body and
36
- // discards the rest, so a Django-proxy `{ "detail": … }` payload never reaches
37
- // the thrown APIError. captureRawErrorBodyFetch stashes the raw body in a
38
- // WeakMap keyed by the response's headers object — which the SDK passes through
39
- // to `APIError.headers` by reference — so it stays correlated to its own
40
- // request, with no shared provider state for concurrent calls to clobber. A
41
- // WeakMap (rather than a synthetic header) keeps the body out of the SDK's
42
- // debug header logging and is reclaimed automatically with the response.
43
- const capturedErrorBodies = new WeakMap<object, string>();
44
- // Bound the stored body so a huge HTML error page can't balloon memory; the
45
- // message is re-truncated to MAX_DETAIL_CHARS downstream anyway.
46
- const MAX_CAPTURED_BODY_CHARS = 16_384;
47
-
48
- /**
49
- * SDK `fetch` option that captures non-2xx response bodies. Install via the
50
- * OpenAI client `fetch` option; safe to share across requests and instances
51
- * because the captured body is keyed per-response in a WeakMap, not stored on
52
- * any shared field.
53
- */
54
- export const captureRawErrorBodyFetch = async (
55
- url: RequestInfo | URL,
56
- init?: RequestInit,
57
- ): Promise<Response> => {
58
- const res = await globalThis.fetch(url, init);
59
- if (res.ok) return res;
60
- // Don't drain bodies the SDK will retry: reading a large or slow upstream
61
- // error page on every attempt would delay those retries and buffer the whole
62
- // body. We still capture terminal (non-retryable) errors — that's where the
63
- // actionable upstream detail lives (unsupported model, invalid key, malformed
64
- // request, etc.).
65
- if (sdkWillRetry(res)) return res;
66
- // clone() so reading the body leaves the SDK's own read of `res` intact.
67
- const body = await res
68
- .clone()
69
- .text()
70
- .catch(() => undefined);
71
- if (!body) return res;
72
- capturedErrorBodies.set(
73
- res.headers,
74
- body.length > MAX_CAPTURED_BODY_CHARS
75
- ? body.slice(0, MAX_CAPTURED_BODY_CHARS)
76
- : body,
77
- );
78
- return res;
79
- };
80
-
81
- /**
82
- * Mirror the OpenAI SDK's `shouldRetry` predicate so this wrapper never drains
83
- * a body the SDK is about to retry. The SDK retries more than 429/5xx: an
84
- * explicit `x-should-retry` header (which also overrides 429/5xx to *not*
85
- * retry), plus 408 (request timeout) and 409 (lock timeout). Keep in sync with
86
- * `openai/client.js` `shouldRetry`.
87
- */
88
- function sdkWillRetry(res: Response): boolean {
89
- const shouldRetryHeader = res.headers.get("x-should-retry");
90
- if (shouldRetryHeader === "true") return true;
91
- if (shouldRetryHeader === "false") return false;
92
- return (
93
- res.status === 408 ||
94
- res.status === 409 ||
95
- res.status === 429 ||
96
- res.status >= 500
97
- );
98
- }
99
-
100
- export function normalizeOpenAIAPIError(
101
- error: InstanceType<typeof OpenAI.APIError>,
102
- rawBody: string | undefined = readCapturedErrorBody(error.headers),
103
- ): NormalizedOpenAIAPIError {
104
- // Prefer the captured raw body (intact upstream payload) over the SDK's
105
- // already-parsed `error.error`, which may have collapsed the detail. But if
106
- // the captured body didn't parse as a JSON object — e.g. it was truncated
107
- // past MAX_CAPTURED_BODY_CHARS into an invalid prefix — fall back to the
108
- // SDK's parsed object so we don't drop code/type/param it already extracted.
109
- const parsedRaw = parseBody(rawBody);
110
- const sdkError = (error as { error?: unknown }).error;
111
- const parsed = asRecord(parsedRaw) ?? sdkError ?? parsedRaw;
112
- const body = extractBody(parsed);
113
-
114
- const message =
115
- body.message ||
116
- stripLeadingStatus(error.message ?? "", error.status) ||
117
- "Request failed";
118
-
119
- const out: NormalizedOpenAIAPIError = { message };
120
- if (body.detail && body.detail !== message) out.detail = body.detail;
121
- const code = body.apiErrorCode ?? scalar((error as { code?: unknown }).code);
122
- const type = body.apiErrorType ?? scalar((error as { type?: unknown }).type);
123
- const param =
124
- body.apiErrorParam ?? scalar((error as { param?: unknown }).param);
125
- if (code) out.apiErrorCode = code;
126
- if (type) out.apiErrorType = type;
127
- if (param) out.apiErrorParam = param;
128
- const requestId = readHeader(error.headers);
129
- if (requestId) out.requestId = requestId;
130
- if (rawBody) out.rawBody = rawBody;
131
- return out;
132
- }
133
-
134
- export function formatNormalizedOpenAIAPIError(
135
- providerLabel: string,
136
- status: number | undefined,
137
- n: NormalizedOpenAIAPIError,
138
- ): string {
139
- const statusLabel =
140
- typeof status === "number" ? String(status) : "unknown status";
141
- const extras = [
142
- n.detail,
143
- n.apiErrorCode && `code=${n.apiErrorCode}`,
144
- n.apiErrorType && `type=${n.apiErrorType}`,
145
- n.apiErrorParam && `param=${n.apiErrorParam}`,
146
- n.requestId && `request_id=${n.requestId}`,
147
- ].filter((v): v is string => Boolean(v));
148
- const suffix = extras.length > 0 ? ` [${extras.join("; ")}]` : "";
149
- return `${providerLabel} API error (${statusLabel}): ${n.message}${suffix}`;
150
- }
151
-
152
- interface BodyDetails {
153
- message?: string;
154
- detail?: string;
155
- apiErrorCode?: string;
156
- apiErrorType?: string;
157
- apiErrorParam?: string;
158
- }
159
-
160
- function extractBody(body: unknown): BodyDetails {
161
- if (typeof body === "string") return { message: trunc(body.trim()) };
162
- const rec = asRecord(body);
163
- if (!rec) return {};
164
-
165
- // OpenAI/OpenRouter nest under `error`; Django puts `detail` at the top.
166
- // A plain `error: "string"` is the whole message but may still carry
167
- // sibling code/type/param, so fall through to the metadata extraction below.
168
- const err = asRecord(rec.error) ?? rec;
169
-
170
- let message =
171
- str(rec.error) ?? str(err.message) ?? str(err.detail) ?? str(rec.detail);
172
- let detail: string | undefined;
173
-
174
- // OpenRouter: the real downstream error lives in metadata.raw while the
175
- // top-level message is a generic "Provider returned error".
176
- const meta = asRecord(err.metadata);
177
- const raw = str(meta?.raw);
178
- const provider = str(meta?.provider_name);
179
- if (raw && message && /^provider returned error$/i.test(message)) {
180
- message = raw;
181
- } else if (raw && raw !== message) {
182
- detail = raw;
183
- }
184
- if (provider) {
185
- detail = detail
186
- ? `${detail}; provider=${provider}`
187
- : `provider=${provider}`;
188
- }
189
-
190
- return {
191
- ...(message ? { message: trunc(message) } : {}),
192
- ...(detail ? { detail: trunc(detail) } : {}),
193
- ...withScalar("apiErrorCode", err.code ?? rec.code),
194
- ...withScalar("apiErrorType", err.type ?? rec.type),
195
- ...withScalar("apiErrorParam", err.param ?? rec.param),
196
- };
197
- }
198
-
199
- function parseBody(raw: string | undefined): unknown {
200
- const trimmed = raw?.trim();
201
- if (!trimmed) return undefined;
202
- try {
203
- return JSON.parse(trimmed);
204
- } catch {
205
- return trimmed; // non-JSON body (HTML error page, plain text)
206
- }
207
- }
208
-
209
- function stripLeadingStatus(
210
- message: string,
211
- status: number | undefined,
212
- ): string {
213
- const trimmed = message.trim();
214
- // SDK sentinel for an unparseable/empty body — carries no signal, so let the
215
- // caller fall back to "Request failed" rather than surface SDK phrasing.
216
- if (/^\d*\s*status code \(no body\)$/i.test(trimmed)) return "";
217
- if (typeof status !== "number") return trimmed;
218
- return trimmed.replace(new RegExp(`^${status}\\s+`), "").trim() || trimmed;
219
- }
220
-
221
- function trunc(s: string): string {
222
- return s.length > MAX_DETAIL_CHARS ? `${s.slice(0, MAX_DETAIL_CHARS)}…` : s;
223
- }
224
-
225
- function withScalar(key: keyof BodyDetails, value: unknown): BodyDetails {
226
- const s = scalar(value);
227
- return s ? { [key]: s } : {};
228
- }
229
-
230
- function scalar(value: unknown): string | undefined {
231
- if (typeof value === "string" && value.length > 0) return value;
232
- if (typeof value === "number" && Number.isFinite(value)) return String(value);
233
- return undefined;
234
- }
235
-
236
- function str(value: unknown): string | undefined {
237
- return typeof value === "string" && value.length > 0 ? value : undefined;
238
- }
239
-
240
- function asRecord(value: unknown): Record<string, unknown> | undefined {
241
- return value && typeof value === "object"
242
- ? (value as Record<string, unknown>)
243
- : undefined;
244
- }
245
-
246
- function readHeader(headers: unknown): string | undefined {
247
- return readHeaderValue(headers, REQUEST_ID_HEADERS);
248
- }
249
-
250
- function readHeaderValue(
251
- headers: unknown,
252
- names: readonly string[],
253
- ): string | undefined {
254
- if (!headers) return undefined;
255
- const get = (headers as { get?: unknown }).get;
256
- const getter = typeof get === "function" ? get.bind(headers) : undefined;
257
- for (const name of names) {
258
- const raw = getter
259
- ? (getter(name) as string | null)
260
- : (asRecord(headers)?.[name] ?? asRecord(headers)?.[name.toLowerCase()]);
261
- if (typeof raw === "string" && raw.length > 0) return raw;
262
- }
263
- return undefined;
264
- }
265
-
266
- function readCapturedErrorBody(headers: unknown): string | undefined {
267
- return headers && typeof headers === "object"
268
- ? capturedErrorBodies.get(headers)
269
- : undefined;
270
- }
@@ -1,133 +0,0 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- // Gateway guardian-delivery list drives both getGuardianBinding and isGuardian:
4
- // null = couldn't determine, [] = authoritative unbound, one active entry =
5
- // bound. Tests set this to mirror the gateway-owned ACL state.
6
- let mockGuardianList: Array<Record<string, unknown>> | null = [];
7
- const cachedCalls: Array<{ channelTypes?: string[] } | undefined> = [];
8
-
9
- const resolveList = (input?: { channelTypes?: string[] }) => {
10
- cachedCalls.push(input);
11
- return Promise.resolve(mockGuardianList);
12
- };
13
-
14
- mock.module("../../contacts/guardian-delivery-reader.js", () => ({
15
- getGuardianDelivery: resolveList,
16
- getGuardianDeliveryFresh: resolveList,
17
- guardianForChannel: (
18
- list: Array<{ channelType: string; status: string }>,
19
- channelType: string,
20
- ) => list.find((g) => g.channelType === channelType && g.status === "active"),
21
- }));
22
-
23
- const { getGuardianBinding, isGuardian } = await import(
24
- "../channel-verification-service.js"
25
- );
26
-
27
- const TELEGRAM_DELIVERY = {
28
- channelType: "telegram",
29
- contactId: "contact-1",
30
- principalId: "principal-1",
31
- displayName: "Guardian",
32
- address: "guardian-handle",
33
- externalChatId: "chat-1",
34
- status: "active",
35
- verifiedAt: 1700,
36
- };
37
-
38
- describe("getGuardianBinding", () => {
39
- beforeEach(() => {
40
- mockGuardianList = [];
41
- cachedCalls.length = 0;
42
- });
43
-
44
- test("filters delivery by the requested channel type", async () => {
45
- await getGuardianBinding("asst-1", "telegram");
46
- expect(cachedCalls).toEqual([{ channelTypes: ["telegram"] }]);
47
- });
48
-
49
- test("returns null when no guardian is bound", async () => {
50
- mockGuardianList = [];
51
- expect(await getGuardianBinding("asst-1", "telegram")).toBeNull();
52
- });
53
-
54
- test("returns null when the gateway is unreachable", async () => {
55
- mockGuardianList = null;
56
- expect(await getGuardianBinding("asst-1", "telegram")).toBeNull();
57
- });
58
-
59
- test("synthesizes the binding from the gateway delivery", async () => {
60
- mockGuardianList = [TELEGRAM_DELIVERY];
61
-
62
- const binding = await getGuardianBinding("asst-1", "telegram");
63
-
64
- expect(binding).not.toBeNull();
65
- expect(binding?.assistantId).toBe("asst-1");
66
- expect(binding?.channel).toBe("telegram");
67
- expect(binding?.id).toBe("contact-1");
68
- expect(binding?.guardianPrincipalId).toBe("principal-1");
69
- expect(binding?.guardianExternalUserId).toBe("guardian-handle");
70
- expect(binding?.guardianDeliveryChatId).toBe("chat-1");
71
- expect(binding?.verifiedAt).toBe(1700);
72
- expect(binding?.status).toBe("active");
73
- expect(binding?.verifiedVia).toBe("verified");
74
- });
75
-
76
- test("falls back to empty strings for absent optional delivery fields", async () => {
77
- mockGuardianList = [
78
- {
79
- channelType: "telegram",
80
- contactId: "contact-2",
81
- address: "addr",
82
- status: "active",
83
- },
84
- ];
85
-
86
- const binding = await getGuardianBinding("asst-1", "telegram");
87
-
88
- expect(binding?.guardianPrincipalId).toBe("");
89
- expect(binding?.guardianDeliveryChatId).toBe("");
90
- expect(binding?.verifiedAt).toBe(0);
91
- });
92
-
93
- test("ignores deliveries for a different channel", async () => {
94
- mockGuardianList = [TELEGRAM_DELIVERY];
95
- expect(await getGuardianBinding("asst-1", "phone")).toBeNull();
96
- });
97
- });
98
-
99
- describe("isGuardian", () => {
100
- beforeEach(() => {
101
- mockGuardianList = [];
102
- cachedCalls.length = 0;
103
- });
104
-
105
- test("returns true when the address matches the gateway guardian", async () => {
106
- mockGuardianList = [TELEGRAM_DELIVERY];
107
- expect(await isGuardian("asst-1", "telegram", "guardian-handle")).toBe(true);
108
- });
109
-
110
- test("compares case-insensitively", async () => {
111
- mockGuardianList = [TELEGRAM_DELIVERY];
112
- expect(await isGuardian("asst-1", "telegram", "GUARDIAN-HANDLE")).toBe(true);
113
- });
114
-
115
- test("returns false for a non-matching address", async () => {
116
- mockGuardianList = [TELEGRAM_DELIVERY];
117
- expect(await isGuardian("asst-1", "telegram", "someone-else")).toBe(false);
118
- });
119
-
120
- test("returns false when no guardian is bound", async () => {
121
- mockGuardianList = [];
122
- expect(await isGuardian("asst-1", "telegram", "guardian-handle")).toBe(
123
- false,
124
- );
125
- });
126
-
127
- test("returns false when the gateway is unreachable", async () => {
128
- mockGuardianList = null;
129
- expect(await isGuardian("asst-1", "telegram", "guardian-handle")).toBe(
130
- false,
131
- );
132
- });
133
- });
@@ -1,181 +0,0 @@
1
- /**
2
- * Unit tests for the narrow reset-drift trust-recovery helper
3
- * `reResolveTrustOnResetDrift`.
4
- *
5
- * The real helper runs against mocked leaf deps: the gateway guardian read
6
- * (`getGuardianDelivery`/`guardianForChannel`), the local-mirror heal
7
- * (`findGuardianForChannel`/`updateContactPrincipalAndChannel`, which the real
8
- * `healGuardianBindingDrift` drives), and the local trust resolver
9
- * (`resolveTrustContext`). Heal invocations are observed via the contact-store
10
- * write mock.
11
- */
12
- import { beforeEach, describe, expect, mock, test } from "bun:test";
13
-
14
- mock.module("../../util/logger.js", () => ({
15
- getLogger: () =>
16
- new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
17
- }));
18
-
19
- let mockGuardianList: Array<Record<string, unknown>> | null = [];
20
-
21
- mock.module("../../contacts/guardian-delivery-reader.js", () => ({
22
- getGuardianDelivery: async () => mockGuardianList,
23
- guardianForChannel: (
24
- list: Array<Record<string, unknown>>,
25
- channelType: string,
26
- ) => list.find((g) => g.channelType === channelType && g.status === "active"),
27
- }));
28
-
29
- // Local mirror the real heal reads/writes. `findGuardianForChannel` returns the
30
- // stored guardian; `updateContactPrincipalAndChannel` records heal writes.
31
- let mockLocalGuardian: {
32
- contact: { id: string; principalId: string };
33
- channel: { id: string };
34
- } | null = null;
35
- const healWrites: Array<{ principalId: string }> = [];
36
-
37
- mock.module("../../contacts/contact-store.js", () => ({
38
- findGuardianForChannel: () => mockLocalGuardian,
39
- updateContactPrincipalAndChannel: (
40
- _contactId: string,
41
- _channelId: string,
42
- principalId: string,
43
- ) => {
44
- healWrites.push({ principalId });
45
- return true;
46
- },
47
- }));
48
-
49
- // The local trust resolver returns guardian for the actor; the gate threads
50
- // sourceChannel via the real withSourceChannel wrapper.
51
- mock.module("../../runtime/trust-context-resolver.js", () => ({
52
- resolveTrustContext: (input: { actorExternalId?: string }) => ({
53
- trustClass: "guardian",
54
- sourceChannel: "vellum",
55
- resolvedActor: input.actorExternalId,
56
- }),
57
- withSourceChannel: (sourceChannel: unknown, ctx: Record<string, unknown>) => ({
58
- ...ctx,
59
- sourceChannel,
60
- }),
61
- }));
62
-
63
- const { reResolveTrustOnResetDrift } = await import(
64
- "../guardian-vellum-migration.js"
65
- );
66
-
67
- function gatewayGuardian(principalId: string): Record<string, unknown> {
68
- return {
69
- channelType: "vellum",
70
- contactId: "guardian-contact",
71
- principalId,
72
- address: principalId,
73
- status: "active",
74
- };
75
- }
76
-
77
- function localGuardian(principalId: string) {
78
- return {
79
- contact: { id: "contact-1", principalId },
80
- channel: { id: "channel-1" },
81
- };
82
- }
83
-
84
- describe("reResolveTrustOnResetDrift", () => {
85
- beforeEach(() => {
86
- mockGuardianList = [];
87
- mockLocalGuardian = null;
88
- healWrites.length = 0;
89
- });
90
-
91
- test("reset drift: heals and returns the re-resolved guardian ctx", async () => {
92
- // Stale local mirror still holds the pre-reset principal; the incoming JWT
93
- // carries the old one. Heal repairs the mirror toward the incoming actor.
94
- mockGuardianList = [gatewayGuardian("vellum-principal-new")];
95
- mockLocalGuardian = localGuardian("vellum-principal-stale");
96
-
97
- const ctx = await reResolveTrustOnResetDrift(
98
- "vellum-principal-old",
99
- "vellum",
100
- );
101
-
102
- expect(ctx?.trustClass).toBe("guardian");
103
- expect(healWrites).toEqual([{ principalId: "vellum-principal-old" }]);
104
- });
105
-
106
- test("repeat drift where heal no-ops still returns the guardian ctx", async () => {
107
- // Local mirror already matches the incoming principal, so heal's write is
108
- // skipped, but the gate still passes and the re-resolve yields guardian.
109
- mockGuardianList = [gatewayGuardian("vellum-principal-new")];
110
- mockLocalGuardian = localGuardian("vellum-principal-old");
111
-
112
- const ctx = await reResolveTrustOnResetDrift(
113
- "vellum-principal-old",
114
- "vellum",
115
- );
116
-
117
- expect(ctx?.trustClass).toBe("guardian");
118
- expect(healWrites).toEqual([]);
119
- });
120
-
121
- test("gateway unreachable (null): returns null, heal not called", async () => {
122
- mockGuardianList = null;
123
- mockLocalGuardian = localGuardian("vellum-principal-old");
124
-
125
- const ctx = await reResolveTrustOnResetDrift(
126
- "vellum-principal-old",
127
- "vellum",
128
- );
129
-
130
- expect(ctx).toBeNull();
131
- expect(healWrites).toEqual([]);
132
- });
133
-
134
- test("empty/revoked gateway (no active guardian): returns null, heal not called", async () => {
135
- mockGuardianList = [];
136
- mockLocalGuardian = localGuardian("vellum-principal-old");
137
-
138
- const ctx = await reResolveTrustOnResetDrift(
139
- "vellum-principal-old",
140
- "vellum",
141
- );
142
-
143
- expect(ctx).toBeNull();
144
- expect(healWrites).toEqual([]);
145
- });
146
-
147
- test("gateway guardian is a real (non vellum-principal-*) id: returns null", async () => {
148
- mockGuardianList = [gatewayGuardian("user@example.com")];
149
- mockLocalGuardian = localGuardian("vellum-principal-old");
150
-
151
- const ctx = await reResolveTrustOnResetDrift(
152
- "vellum-principal-old",
153
- "vellum",
154
- );
155
-
156
- expect(ctx).toBeNull();
157
- expect(healWrites).toEqual([]);
158
- });
159
-
160
- test("incoming principal is not vellum-principal-*: returns null", async () => {
161
- mockGuardianList = [gatewayGuardian("vellum-principal-new")];
162
- mockLocalGuardian = localGuardian("vellum-principal-old");
163
-
164
- const ctx = await reResolveTrustOnResetDrift("user@example.com", "vellum");
165
-
166
- expect(ctx).toBeNull();
167
- expect(healWrites).toEqual([]);
168
- });
169
-
170
- test("threads sourceChannel into the returned ctx", async () => {
171
- mockGuardianList = [gatewayGuardian("vellum-principal-new")];
172
- mockLocalGuardian = localGuardian("vellum-principal-old");
173
-
174
- const ctx = await reResolveTrustOnResetDrift(
175
- "vellum-principal-old",
176
- "telegram",
177
- );
178
-
179
- expect(ctx?.sourceChannel).toBe("telegram");
180
- });
181
- });
@@ -1,66 +0,0 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- // Gateway guardian-delivery list: null = couldn't determine (transport failure
4
- // OR gateway-side resolver/DB error), [] = authoritative unbound, one active
5
- // entry = bound.
6
- let mockGuardianList: Array<Record<string, unknown>> | null = [];
7
- const freshCalls: Array<{ channelTypes?: string[] } | undefined> = [];
8
-
9
- mock.module("../../contacts/guardian-delivery-reader.js", () => ({
10
- // Existence guard reads fresh (uncached); the binding/identity reads use the
11
- // cached variant. The service imports both, so both must be stubbed.
12
- getGuardianDeliveryFresh: (input?: { channelTypes?: string[] }) => {
13
- freshCalls.push(input);
14
- return Promise.resolve(mockGuardianList);
15
- },
16
- getGuardianDelivery: () => Promise.resolve(mockGuardianList),
17
- guardianForChannel: (
18
- list: Array<{ channelType: string; status: string }>,
19
- channelType: string,
20
- ) => list.find((g) => g.channelType === channelType && g.status === "active"),
21
- }));
22
-
23
- const { isGuardianBoundForChannel } = await import(
24
- "../channel-verification-service.js"
25
- );
26
-
27
- describe("isGuardianBoundForChannel", () => {
28
- beforeEach(() => {
29
- mockGuardianList = [];
30
- freshCalls.length = 0;
31
- });
32
-
33
- test("reads fresh so a stale cached empty list can't mask a present guardian", async () => {
34
- await isGuardianBoundForChannel("telegram");
35
- expect(freshCalls).toEqual([{ channelTypes: ["telegram"] }]);
36
- });
37
-
38
- test("returns false when no guardian is bound", async () => {
39
- mockGuardianList = [];
40
- expect(await isGuardianBoundForChannel("telegram")).toBe(false);
41
- });
42
-
43
- test("returns true when a guardian is bound", async () => {
44
- mockGuardianList = [{ channelType: "telegram", status: "active" }];
45
- expect(await isGuardianBoundForChannel("telegram")).toBe(true);
46
- });
47
-
48
- test("null list (gateway unreachable) is treated as bound", async () => {
49
- mockGuardianList = null;
50
- expect(await isGuardianBoundForChannel("telegram")).toBe(true);
51
- });
52
-
53
- test("gateway resolver error (null, not []) is treated as bound — no duplicate", async () => {
54
- // A gateway-side DB/resolver error now reaches the reader as null (the
55
- // handler no longer swallows it into an empty list), so the guard's
56
- // null fail-safe applies and reports bound instead of mis-reading the
57
- // error as "no guardian" and allowing a duplicate binding.
58
- mockGuardianList = null;
59
- expect(await isGuardianBoundForChannel("telegram")).toBe(true);
60
- });
61
-
62
- test("genuine empty ([], not null) reports unbound so first-bind is allowed", async () => {
63
- mockGuardianList = [];
64
- expect(await isGuardianBoundForChannel("telegram")).toBe(false);
65
- });
66
- });