@vellumai/assistant 0.9.1-staging.1 → 0.10.0-staging.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 (433) hide show
  1. package/docs/activation-funnel-telemetry.md +24 -18
  2. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  3. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  4. package/node_modules/@vellumai/gateway-client/src/index.ts +15 -0
  5. package/openapi.yaml +852 -15
  6. package/package.json +1 -1
  7. package/src/__tests__/access-request-card-view.test.ts +98 -0
  8. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  9. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +59 -7
  10. package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  12. package/src/__tests__/app-compiler.test.ts +15 -1
  13. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  14. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  15. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  16. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  18. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  19. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  20. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +174 -30
  22. package/src/__tests__/config-schema.test.ts +35 -0
  23. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  24. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  25. package/src/__tests__/contacts-tools.test.ts +29 -0
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  27. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  28. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  29. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  30. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  31. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  32. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  33. package/src/__tests__/conversation-title-service.test.ts +62 -0
  34. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  35. package/src/__tests__/credential-prompt-route.test.ts +1 -0
  36. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  37. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  38. package/src/__tests__/disk-usage.test.ts +65 -0
  39. package/src/__tests__/dynamic-page-surface.test.ts +51 -0
  40. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  41. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  42. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  43. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  44. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  45. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  46. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  47. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  48. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  49. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  50. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  51. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  52. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  53. package/src/__tests__/invite-redemption-service.test.ts +0 -3
  54. package/src/__tests__/llm-catalog-parity.test.ts +30 -1
  55. package/src/__tests__/llm-resolver.test.ts +21 -0
  56. package/src/__tests__/llm-schema.test.ts +1 -0
  57. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  58. package/src/__tests__/mcp-health-check.test.ts +6 -7
  59. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  60. package/src/__tests__/path-policy.test.ts +34 -0
  61. package/src/__tests__/persona-resolver.test.ts +38 -0
  62. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  63. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  64. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  65. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  66. package/src/__tests__/reaction-persistence.test.ts +150 -29
  67. package/src/__tests__/relay-server.test.ts +285 -0
  68. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  69. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  70. package/src/__tests__/skill-execute-input.test.ts +5 -0
  71. package/src/__tests__/skills.test.ts +51 -0
  72. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  73. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  74. package/src/__tests__/subagent-tools.test.ts +150 -0
  75. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  76. package/src/__tests__/title-generate-hook.test.ts +100 -3
  77. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  78. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  79. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  80. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  81. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  82. package/src/__tests__/trusted-contact-verification.test.ts +2 -4
  83. package/src/__tests__/twilio-routes.test.ts +81 -1
  84. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  85. package/src/__tests__/weak-open-model.test.ts +30 -0
  86. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  87. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  88. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  89. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  90. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  91. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  92. package/src/agent/loop.ts +33 -33
  93. package/src/api/events/tool-result.ts +6 -0
  94. package/src/api/events/workflow-completed.ts +53 -0
  95. package/src/api/events/workflow-leaf-finished.ts +38 -0
  96. package/src/api/events/workflow-leaf-started.ts +35 -0
  97. package/src/api/events/workflow-progress.ts +32 -0
  98. package/src/api/events/workflow-started.ts +31 -0
  99. package/src/api/index.ts +40 -0
  100. package/src/api/responses/conversation-message.ts +26 -0
  101. package/src/api/responses/home.ts +26 -0
  102. package/src/api/responses/workflow-journal.ts +53 -0
  103. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  104. package/src/approvals/guardian-decision-primitive.ts +26 -3
  105. package/src/approvals/guardian-request-resolvers.ts +181 -78
  106. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  107. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  108. package/src/calls/call-pointer-messages.ts +10 -4
  109. package/src/calls/channel-admission-reader.ts +104 -0
  110. package/src/calls/guardian-dispatch.ts +17 -45
  111. package/src/calls/media-stream-server.ts +84 -2
  112. package/src/calls/relay-server.ts +66 -0
  113. package/src/calls/relay-setup-router.ts +82 -1
  114. package/src/calls/twilio-routes.ts +17 -8
  115. package/src/cli/commands/clients.ts +3 -0
  116. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  117. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  118. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  119. package/src/cli/commands/memory/index.ts +30 -0
  120. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  121. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  122. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  123. package/src/cli/commands/oauth/status.test.ts +36 -0
  124. package/src/cli/commands/oauth/status.ts +23 -3
  125. package/src/cli/commands/plugins.ts +57 -5
  126. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  127. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
  128. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  129. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
  130. package/src/cli/lib/inspect-plugin.ts +12 -1
  131. package/src/cli/lib/merge-plugin-tree.ts +149 -49
  132. package/src/cli/lib/plugin-surfaces.ts +104 -0
  133. package/src/cli/lib/upgrade-plugin.ts +64 -36
  134. package/src/cli/program.ts +2 -4
  135. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  136. package/src/config/assistant-feature-flags.ts +22 -7
  137. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  138. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  139. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  140. package/src/config/bundled-skills/workflows/SKILL.md +14 -7
  141. package/src/config/call-site-defaults.ts +3 -0
  142. package/src/config/feature-flag-registry.json +49 -18
  143. package/src/config/llm-resolver.ts +3 -0
  144. package/src/config/memory-v3-gate.ts +11 -0
  145. package/src/config/schema.ts +8 -6
  146. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  147. package/src/config/schemas/call-site-catalog.ts +7 -0
  148. package/src/config/schemas/channels.ts +11 -0
  149. package/src/config/schemas/llm.ts +31 -0
  150. package/src/config/schemas/memory-lifecycle.ts +3 -7
  151. package/src/config/schemas/memory-v3.ts +6 -0
  152. package/src/config/schemas/services.ts +18 -0
  153. package/src/config/seed-inference-profiles.ts +94 -34
  154. package/src/config/skills.ts +21 -0
  155. package/src/config/sync-gated-profiles.ts +220 -0
  156. package/src/contacts/contact-store.ts +2 -10
  157. package/src/contacts/contacts-write.ts +1 -2
  158. package/src/contacts/types.ts +0 -1
  159. package/src/context/compactor.ts +86 -52
  160. package/src/context/strip-injections.ts +58 -10
  161. package/src/context/token-estimator.ts +1 -1
  162. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  163. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  164. package/src/daemon/conversation-agent-loop.ts +100 -19
  165. package/src/daemon/conversation-history.ts +1 -1
  166. package/src/daemon/conversation-lifecycle.ts +3 -5
  167. package/src/daemon/conversation-process.ts +13 -5
  168. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  169. package/src/daemon/conversation-surfaces.ts +26 -0
  170. package/src/daemon/conversation-tool-setup.ts +16 -11
  171. package/src/daemon/conversation.ts +64 -14
  172. package/src/daemon/disk-pressure-policy.ts +5 -3
  173. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  174. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  175. package/src/daemon/handlers/config-a2a.ts +0 -2
  176. package/src/daemon/handlers/config-channels.ts +5 -10
  177. package/src/daemon/handlers/config-slack-channel.ts +20 -0
  178. package/src/daemon/handlers/conversations.ts +107 -0
  179. package/src/daemon/host-browser-proxy.ts +41 -0
  180. package/src/daemon/lifecycle.ts +55 -20
  181. package/src/daemon/message-provenance.ts +2 -0
  182. package/src/daemon/message-types/contacts.ts +0 -1
  183. package/src/daemon/message-types/web-activity.ts +7 -1
  184. package/src/daemon/message-types/workflows.ts +83 -1
  185. package/src/daemon/tool-setup-types.ts +4 -0
  186. package/src/daemon/trust-context.ts +1 -1
  187. package/src/events/tool-audit-listener.ts +2 -2
  188. package/src/home/feed-source-enrichment.test.ts +151 -0
  189. package/src/home/feed-source-enrichment.ts +176 -0
  190. package/src/instrument.ts +18 -6
  191. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  192. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  193. package/src/ipc/assistant-server.ts +37 -4
  194. package/src/ipc/gateway-flag-listener.ts +18 -2
  195. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  196. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  197. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  198. package/src/memory/__tests__/memory-retrospective-job.test.ts +34 -0
  199. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  200. package/src/memory/auth-fallback-events-store.ts +2 -2
  201. package/src/memory/auto-analysis-enqueue.ts +3 -5
  202. package/src/memory/canonical-guardian-store.ts +39 -1
  203. package/src/memory/conversation-crud.ts +9 -4
  204. package/src/memory/conversation-key-store.ts +17 -2
  205. package/src/memory/conversation-title-service.ts +64 -7
  206. package/src/memory/db-init.ts +10 -0
  207. package/src/memory/embedding-backend.ts +15 -1
  208. package/src/memory/jobs-worker.ts +2 -1
  209. package/src/memory/lifecycle-events-store.ts +2 -2
  210. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  211. package/src/memory/memory-retrospective-job.ts +9 -0
  212. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  213. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  214. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  215. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
  216. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  217. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  218. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  219. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  220. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  221. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  222. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  223. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  224. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  225. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
  226. package/src/memory/migrations/index.ts +5 -0
  227. package/src/memory/onboarding-events-store.ts +3 -3
  228. package/src/memory/schema/contacts.ts +0 -1
  229. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  230. package/src/memory/skill-loaded-events-store.ts +2 -2
  231. package/src/memory/tool-executed-events-store.test.ts +7 -7
  232. package/src/memory/turn-trace-store.test.ts +736 -0
  233. package/src/memory/turn-trace-store.ts +364 -0
  234. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  235. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  236. package/src/memory/v2/consolidation-job.ts +2 -2
  237. package/src/memory/v2/skill-content.ts +25 -7
  238. package/src/memory/v2/skill-store.ts +7 -1
  239. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  240. package/src/memory/v3-eval/eval-packets.ts +546 -0
  241. package/src/messaging/providers/slack/api.ts +31 -0
  242. package/src/messaging/providers/slack/send.test.ts +114 -2
  243. package/src/messaging/providers/slack/send.ts +30 -7
  244. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  245. package/src/messaging/providers/slack/withdraw.ts +161 -0
  246. package/src/notifications/AGENTS.md +2 -0
  247. package/src/notifications/access-request-copy.ts +72 -59
  248. package/src/notifications/adapters/slack.ts +55 -73
  249. package/src/notifications/approval-card-data.ts +333 -0
  250. package/src/notifications/broadcaster.ts +6 -2
  251. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  252. package/src/notifications/copy-composer.ts +3 -3
  253. package/src/notifications/decision-engine.ts +4 -2
  254. package/src/notifications/destination-resolver.ts +4 -6
  255. package/src/notifications/guardian-question-mode.ts +10 -0
  256. package/src/notifications/home-feed-side-effect.ts +3 -13
  257. package/src/notifications/notification-utils.ts +2 -1
  258. package/src/notifications/signal.ts +79 -43
  259. package/src/notifications/types.ts +98 -128
  260. package/src/permissions/checker.test.ts +51 -0
  261. package/src/permissions/checker.ts +185 -26
  262. package/src/permissions/ipc-risk-types.ts +24 -0
  263. package/src/permissions/question-prompter.test.ts +27 -0
  264. package/src/permissions/question-prompter.ts +4 -0
  265. package/src/platform/client.test.ts +119 -0
  266. package/src/platform/client.ts +66 -0
  267. package/src/platform/consent-cache.test.ts +267 -0
  268. package/src/platform/consent-cache.ts +174 -0
  269. package/src/plugin-api/index.ts +27 -0
  270. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  271. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  272. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  273. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  274. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  275. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  276. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  277. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  278. package/src/plugins/defaults/advisor/config.ts +21 -0
  279. package/src/plugins/defaults/advisor/consult.ts +93 -0
  280. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  281. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  282. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  283. package/src/plugins/defaults/advisor/package.json +14 -0
  284. package/src/plugins/defaults/advisor/steering.ts +67 -0
  285. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  286. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  287. package/src/plugins/defaults/index.ts +35 -0
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  290. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  291. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +75 -7
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  298. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  299. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +37 -4
  300. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  301. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  302. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  303. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -12
  304. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  305. package/src/prompts/persona-resolver.ts +12 -2
  306. package/src/prompts/templates/system-sections.ts +7 -2
  307. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  308. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  309. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  310. package/src/providers/atlascloud/client.ts +85 -0
  311. package/src/providers/fetch-provider-catalog.ts +85 -0
  312. package/src/providers/inference/adapter-factory.ts +3 -0
  313. package/src/providers/model-catalog.ts +58 -0
  314. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  315. package/src/providers/openai/chat-completions-provider.ts +7 -0
  316. package/src/providers/openai/responses-provider.ts +10 -0
  317. package/src/providers/provider-send-message.ts +11 -3
  318. package/src/providers/retry.ts +53 -12
  319. package/src/providers/search-provider-catalog.ts +10 -0
  320. package/src/providers/weak-open-model.ts +22 -0
  321. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  322. package/src/runtime/__tests__/client-health.test.ts +44 -0
  323. package/src/runtime/access-request-helper.ts +21 -53
  324. package/src/runtime/actor-trust-resolver.ts +49 -21
  325. package/src/runtime/agent-wake.ts +52 -0
  326. package/src/runtime/assistant-event-hub.ts +18 -4
  327. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  328. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  329. package/src/runtime/capabilities.test.ts +120 -0
  330. package/src/runtime/capabilities.ts +197 -0
  331. package/src/runtime/channel-approval-types.ts +5 -1
  332. package/src/runtime/channel-retry-sweep.ts +1 -0
  333. package/src/runtime/channel-verification-service.ts +1 -2
  334. package/src/runtime/client-health.ts +26 -0
  335. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  336. package/src/runtime/effective-capabilities.test.ts +128 -0
  337. package/src/runtime/effective-capabilities.ts +84 -0
  338. package/src/runtime/guardian-reply-router.ts +106 -21
  339. package/src/runtime/invite-redemption-service.ts +6 -22
  340. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  341. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  342. package/src/runtime/pending-interactions.ts +15 -0
  343. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  344. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  345. package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -13
  346. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  347. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  348. package/src/runtime/routes/client-routes.ts +10 -0
  349. package/src/runtime/routes/contact-routes.ts +31 -8
  350. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  351. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  352. package/src/runtime/routes/conversation-routes.ts +37 -12
  353. package/src/runtime/routes/events-routes.ts +1 -3
  354. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  355. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  356. package/src/runtime/routes/home-feed-routes.ts +8 -3
  357. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  358. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
  359. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  360. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  361. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  362. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  363. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  364. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  365. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  366. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  367. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  368. package/src/runtime/routes/index.ts +2 -0
  369. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  370. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  371. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  372. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  373. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  374. package/src/runtime/routes/notification-routes.ts +122 -133
  375. package/src/runtime/routes/platform-routes.ts +2 -2
  376. package/src/runtime/routes/plugins-routes.ts +40 -7
  377. package/src/runtime/routes/secret-routes.ts +10 -0
  378. package/src/runtime/routes/surface-action-routes.ts +2 -1
  379. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  380. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  381. package/src/runtime/routes/workflow-routes.test.ts +225 -1
  382. package/src/runtime/routes/workflow-routes.ts +131 -1
  383. package/src/runtime/tool-grant-request-helper.ts +18 -16
  384. package/src/runtime/trust-context-resolver.ts +8 -5
  385. package/src/schedule/schedule-store.ts +1 -1
  386. package/src/schedule/scheduler-types.ts +5 -1
  387. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  388. package/src/security/secret-patterns.ts +3 -0
  389. package/src/subagent/manager.ts +11 -4
  390. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  391. package/src/telemetry/trace-collection-policy.ts +30 -0
  392. package/src/telemetry/types.ts +89 -0
  393. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  394. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  395. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  396. package/src/tools/browser/browser-execution.ts +29 -18
  397. package/src/tools/document/document-tool.ts +2 -3
  398. package/src/tools/executor.ts +5 -3
  399. package/src/tools/host-terminal/host-shell.ts +5 -4
  400. package/src/tools/memory/register.ts +2 -2
  401. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  402. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  403. package/src/tools/network/web-fetch.ts +372 -1
  404. package/src/tools/network/web-search.ts +213 -10
  405. package/src/tools/permission-checker.ts +3 -2
  406. package/src/tools/registry.ts +20 -0
  407. package/src/tools/schedule/create.ts +4 -3
  408. package/src/tools/schedule/update.ts +2 -1
  409. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  410. package/src/tools/skills/execute.ts +1 -2
  411. package/src/tools/subagent/spawn.ts +37 -13
  412. package/src/tools/terminal/shell.ts +10 -4
  413. package/src/tools/tool-approval-handler.ts +17 -10
  414. package/src/tools/types.ts +9 -0
  415. package/src/tools/ui-surface/definitions.ts +25 -2
  416. package/src/tools/verification-control-plane-policy.ts +3 -1
  417. package/src/tools/workflows/run-workflow.ts +1 -0
  418. package/src/util/disk-usage.ts +78 -23
  419. package/src/util/platform.ts +8 -1
  420. package/src/watcher/telemetry.ts +2 -2
  421. package/src/workflows/engine.test.ts +175 -1
  422. package/src/workflows/engine.ts +82 -0
  423. package/src/workflows/journal-store.test.ts +70 -0
  424. package/src/workflows/journal-store.ts +18 -3
  425. package/src/workflows/run-manager.test.ts +171 -3
  426. package/src/workflows/run-manager.ts +64 -0
  427. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  428. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  429. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  430. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  431. package/src/workspace/migrations/registry.ts +8 -0
  432. package/src/notifications/tool-approval-copy.ts +0 -142
  433. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.9.1-staging.1",
3
+ "version": "0.10.0-staging.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,98 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ buildAccessRequestCardView,
5
+ parseAccessRequestPayload,
6
+ } from "../notifications/access-request-copy.js";
7
+
8
+ function view(raw: Record<string, unknown>) {
9
+ return buildAccessRequestCardView(parseAccessRequestPayload(raw));
10
+ }
11
+
12
+ const TAB = String.fromCharCode(9);
13
+
14
+ describe("buildAccessRequestCardView", () => {
15
+ test("prefers actorDisplayName, falls back to senderIdentifier then 'Someone'", () => {
16
+ expect(
17
+ view({ actorDisplayName: "Alice", senderIdentifier: "U999" }).displayName,
18
+ ).toBe("Alice");
19
+ expect(view({ senderIdentifier: "U999" }).displayName).toBe("U999");
20
+ expect(view({}).displayName).toBe("Someone");
21
+ });
22
+
23
+ test("sanitizes identity fields (strips control characters)", () => {
24
+ const v = view({
25
+ actorDisplayName: `Al${TAB}ice`,
26
+ actorUsername: `a${TAB}lice`,
27
+ actorExternalId: `U9${TAB}99`,
28
+ });
29
+ expect(v.displayName).toBe("Al ice");
30
+ expect(v.username).toBe("a lice");
31
+ expect(v.externalId).toBe("U9 99");
32
+ });
33
+
34
+ test("username and externalId are undefined when absent", () => {
35
+ const v = view({ actorDisplayName: "Alice" });
36
+ expect(v.username).toBeUndefined();
37
+ expect(v.externalId).toBeUndefined();
38
+ });
39
+
40
+ test("detects Slack DM conversations", () => {
41
+ expect(
42
+ view({ sourceChannel: "slack", conversationExternalId: "D01XYZ" })
43
+ .isSlackDm,
44
+ ).toBe(true);
45
+ expect(
46
+ view({ sourceChannel: "slack", conversationExternalId: "C01ABC" })
47
+ .isSlackDm,
48
+ ).toBe(false);
49
+ expect(
50
+ view({ sourceChannel: "telegram", conversationExternalId: "D01XYZ" })
51
+ .isSlackDm,
52
+ ).toBe(false);
53
+ });
54
+
55
+ test("builds a Slack permalink only with slack source + conversation + ts", () => {
56
+ expect(
57
+ view({
58
+ sourceChannel: "slack",
59
+ conversationExternalId: "C01ABC",
60
+ messageTs: "1700000000.000100",
61
+ }).messagePermalink,
62
+ ).toBe("https://slack.com/archives/C01ABC/p1700000000000100");
63
+ expect(
64
+ view({ sourceChannel: "slack", conversationExternalId: "C01ABC" })
65
+ .messagePermalink,
66
+ ).toBeUndefined();
67
+ expect(
68
+ view({
69
+ sourceChannel: "telegram",
70
+ conversationExternalId: "C01ABC",
71
+ messageTs: "1.2",
72
+ }).messagePermalink,
73
+ ).toBeUndefined();
74
+ });
75
+
76
+ test("sanitizes message preview and yields undefined when blank after sanitizing", () => {
77
+ expect(view({ messagePreview: " hello " }).messagePreview).toBe("hello");
78
+ // Blank / control-character-only previews sanitize to empty → undefined
79
+ // (no empty quote block is rendered downstream).
80
+ expect(view({ messagePreview: "" }).messagePreview).toBeUndefined();
81
+ expect(view({ messagePreview: " " }).messagePreview).toBeUndefined();
82
+ expect(view({}).messagePreview).toBeUndefined();
83
+ });
84
+
85
+ test("collects trust/security warnings", () => {
86
+ const v = view({
87
+ isStranger: true,
88
+ isRestricted: true,
89
+ previousMemberStatus: "revoked",
90
+ });
91
+ expect(v.warnings).toEqual([
92
+ "This user was previously revoked.",
93
+ "External Slack user (not in this workspace).",
94
+ "Guest / restricted account.",
95
+ ]);
96
+ expect(view({}).warnings).toEqual([]);
97
+ });
98
+ });
@@ -1,9 +1,7 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import {
4
- buildAccessRequestSeedContentBlocks,
5
- parseAccessRequestPayload,
6
- } from "../notifications/access-request-copy.js";
3
+ import { parseAccessRequestPayload } from "../notifications/access-request-copy.js";
4
+ import { buildAccessRequestSeedContentBlocks } from "../notifications/approval-card-data.js";
7
5
 
8
6
  describe("buildAccessRequestSeedContentBlocks", () => {
9
7
  const basePayload: Record<string, unknown> = {
@@ -43,7 +43,6 @@ const PHONE = "+15559871234";
43
43
  function makeContact(
44
44
  role: "guardian" | "contact" = "contact",
45
45
  status: "unverified" | "active" = "unverified",
46
- externalUserId: string | null = null,
47
46
  ): ContactWithChannels {
48
47
  const channelId = "ch-test";
49
48
  return {
@@ -64,7 +63,6 @@ function makeContact(
64
63
  contactId: "contact-test",
65
64
  type: "phone",
66
65
  address: PHONE,
67
- externalUserId,
68
66
  externalChatId: null,
69
67
  isPrimary: true,
70
68
  status,
@@ -94,7 +92,7 @@ describe("resolveActorTrust — address fallback", () => {
94
92
 
95
93
  test("finds unverified channel via address when externalUserId is null", () => {
96
94
  // Simulate a contact registered by name-capture: address set, externalUserId null.
97
- _byAddress = makeContact("contact", "unverified", null);
95
+ _byAddress = makeContact("contact", "unverified");
98
96
 
99
97
  const result = resolveActorTrust({
100
98
  assistantId: "asst-1",
@@ -106,12 +104,13 @@ describe("resolveActorTrust — address fallback", () => {
106
104
  expect(result.memberRecord).not.toBeNull();
107
105
  expect(result.memberRecord?.contact.displayName).toBe("Patrick Test");
108
106
  expect(result.memberRecord?.channel.status).toBe("unverified");
109
- // trustClass is 'unknown' for an unverified member (correct not yet active)
110
- expect(result.trustClass).toBe("unknown");
107
+ // trustClass is 'unverified_contact' for a member whose channel is
108
+ // pending or unverified — known to the guardian but not yet verified.
109
+ expect(result.trustClass).toBe("unverified_contact");
111
110
  });
112
111
 
113
112
  test("address lookup is the sole member resolution path", () => {
114
- _byAddress = makeContact("contact", "active", null);
113
+ _byAddress = makeContact("contact", "active");
115
114
 
116
115
  const result = resolveActorTrust({
117
116
  assistantId: "asst-1",
@@ -141,7 +140,7 @@ describe("resolveActorTrust — address fallback", () => {
141
140
  test("address-found active channel elevates trust to trusted_contact", () => {
142
141
  // An active channel found via address (e.g. after manual verify without externalUserId set)
143
142
  // should still yield trusted_contact trust class.
144
- _byAddress = makeContact("contact", "active", null);
143
+ _byAddress = makeContact("contact", "active");
145
144
 
146
145
  const result = resolveActorTrust({
147
146
  assistantId: "asst-1",
@@ -154,4 +153,57 @@ describe("resolveActorTrust — address fallback", () => {
154
153
  expect(result.memberRecord?.channel.status).toBe("active");
155
154
  expect(result.trustClass).toBe("trusted_contact");
156
155
  });
156
+
157
+ test("pending-status member is classified as unverified_contact", () => {
158
+ // Mirrors the unverified branch but for `pending` status (e.g. a phone
159
+ // contact registered by name-capture awaiting the DTMF challenge).
160
+ const contact = makeContact("contact", "unverified");
161
+ // Override status to "pending" — makeContact only accepts unverified/active
162
+ contact.channels[0]!.status = "pending";
163
+ _byAddress = contact;
164
+
165
+ const result = resolveActorTrust({
166
+ assistantId: "asst-1",
167
+ sourceChannel: "phone",
168
+ conversationExternalId: PHONE,
169
+ actorExternalId: PHONE,
170
+ });
171
+
172
+ expect(result.memberRecord?.channel.status).toBe("pending");
173
+ expect(result.trustClass).toBe("unverified_contact");
174
+ });
175
+
176
+ test("blocked-status member is classified as unknown (not unverified_contact)", () => {
177
+ // Hard-deny statuses (blocked, revoked) stay `unknown` — admission-layer
178
+ // re-checks channel.status and emits the hard-deny reasons.
179
+ const contact = makeContact("contact", "unverified");
180
+ contact.channels[0]!.status = "blocked";
181
+ _byAddress = contact;
182
+
183
+ const result = resolveActorTrust({
184
+ assistantId: "asst-1",
185
+ sourceChannel: "phone",
186
+ conversationExternalId: PHONE,
187
+ actorExternalId: PHONE,
188
+ });
189
+
190
+ expect(result.memberRecord?.channel.status).toBe("blocked");
191
+ expect(result.trustClass).toBe("unknown");
192
+ });
193
+
194
+ test("revoked-status member is classified as unknown", () => {
195
+ const contact = makeContact("contact", "unverified");
196
+ contact.channels[0]!.status = "revoked";
197
+ _byAddress = contact;
198
+
199
+ const result = resolveActorTrust({
200
+ assistantId: "asst-1",
201
+ sourceChannel: "phone",
202
+ conversationExternalId: PHONE,
203
+ actorExternalId: PHONE,
204
+ });
205
+
206
+ expect(result.memberRecord?.channel.status).toBe("revoked");
207
+ expect(result.trustClass).toBe("unknown");
208
+ });
157
209
  });
@@ -1,12 +1,13 @@
1
1
  /**
2
- * The compaction summarizer's input routing.
2
+ * The compaction summarizer's input.
3
3
  *
4
- * Budget-gate compaction summarizes the *injected* history so the summary
5
- * call's prompt prefix matches the agent's warm prefix cache (a cache read
6
- * rather than a fresh cache write). Overflow recovery summarizes the
7
- * injection-*stripped* history because its emergency split keeps an
8
- * un-stripped verbatim prefix that the post-compaction re-injection would
9
- * otherwise double-stack.
4
+ * Both the budget gate and overflow recovery summarize the *injected* history
5
+ * (the genuine conversation as the agent saw it) so the summary call's prompt
6
+ * prefix matches the agent's warm prefix cache a cache read rather than a
7
+ * fresh cache write. The agent loop hands the full injected history to
8
+ * compaction unconditionally; the post-compaction re-injection hook owns
9
+ * injection idempotency by stripping the tail's per-turn blocks before
10
+ * re-applying them.
10
11
  */
11
12
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
13
 
@@ -46,17 +47,17 @@ const testPostCompactPlugin = {
46
47
  const CONVERSATION_ID = "compaction-strip-conversation";
47
48
 
48
49
  /**
49
- * A runtime `<workspace>` injection block. `stripInjectionsForCompaction`
50
- * matches its full `{ prefix: "<workspace>\n", suffix: "\n</workspace>" }`
51
- * wrapper and drops it from user-message content.
50
+ * A runtime `<workspace>` injection block. Its presence in the summarizer's
51
+ * input proves compaction received the injected history rather than a stripped
52
+ * copy.
52
53
  */
53
54
  const WORKSPACE_INJECTION =
54
55
  "<workspace>\nActive workspace: project-x\n</workspace>";
55
56
  const TURN_BODY = "Hello there, this is the turn body.";
56
57
 
57
58
  /**
58
- * A user turn carrying a real text block plus a runtime injection block.
59
- * Stripping drops the injection block and keeps the real one.
59
+ * A user turn carrying a real text block plus a runtime injection block, both
60
+ * of which ride into compaction's input as-is.
60
61
  */
61
62
  const injectedUserMessage: Message = {
62
63
  role: "user",
@@ -184,7 +185,7 @@ describe("AgentLoop compaction summarizer input", () => {
184
185
  expect(events.some((e) => e.type === "history_stripped")).toBe(true);
185
186
  });
186
187
 
187
- test("overflow-driven compaction summarizes the injection-stripped history", async () => {
188
+ test("overflow-driven compaction summarizes the injected history", async () => {
188
189
  // GIVEN a history whose user turn carries a runtime-injection block
189
190
  const capture: CompactionInputCapture = { budget: null, overflow: null };
190
191
 
@@ -229,10 +230,10 @@ describe("AgentLoop compaction summarizer input", () => {
229
230
  ...installCapturingManager(capture),
230
231
  });
231
232
 
232
- // THEN overflow recovery received the history with the injection removed
233
+ // THEN overflow recovery received the injected history, injection intact
233
234
  expect(capture.overflow).not.toBeNull();
234
- expect(serialize(capture.overflow)).not.toContain("<workspace>");
235
- // AND the real turn body survives the strip
235
+ expect(serialize(capture.overflow)).toContain("<workspace>");
236
+ // AND the real turn body is present alongside the injection
236
237
  expect(serialize(capture.overflow)).toContain(TURN_BODY);
237
238
  // AND the budget summarizer was not invoked
238
239
  expect(capture.budget).toBeNull();
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Verifies that the `memory-v3-live` cache-anchor signal surfaces on every
3
- * `SendMessageOptions.config` the loop emits. When the flag is on, the latest
2
+ * Verifies that the memory-v3-live cache-anchor signal surfaces on every
3
+ * `SendMessageOptions.config` the loop emits. When v3-live is on, the latest
4
4
  * user message carries a volatile `<memory>` block, so the loop sets
5
5
  * `providerConfig.mutableLatestUserMessage` to tell the provider to anchor its
6
6
  * long-TTL cache breakpoint on the most recent STABLE message instead.
7
7
  *
8
- * The loop reads the flag directly where it assembles `providerConfig`, so the
9
- * signal is sourced from the flag rather than a run option. When the flag is
10
- * off the field is omitted (not `false`/`undefined`) so the wire stays
11
- * byte-identical to today.
8
+ * The loop reads `config.memory.v3.live` directly where it assembles
9
+ * `providerConfig`, so the signal is sourced from config rather than a run
10
+ * option. When v3-live is off the field is omitted (not `false`/`undefined`) so
11
+ * the wire stays byte-identical to today.
12
12
  */
13
13
 
14
14
  import { afterEach, describe, expect, mock, test } from "bun:test";
@@ -19,11 +19,14 @@ mock.module("../util/logger.js", () => ({
19
19
  getLogger: () => makeMockLogger(),
20
20
  }));
21
21
 
22
+ // AgentLoop reads the v3-live gate (`config.memory.v3.live`) via
23
+ // `isMemoryV3Live` to decide the cache-anchor signal; drive it per-test.
24
+ let memoryV3LiveSlot = false;
25
+ mock.module("../config/memory-v3-gate.js", () => ({
26
+ isMemoryV3Live: () => memoryV3LiveSlot,
27
+ }));
28
+
22
29
  import { AgentLoop } from "../agent/loop.js";
23
- import {
24
- clearCachedOverrides,
25
- setCachedOverrides,
26
- } from "../config/feature-flag-cache.js";
27
30
  import type {
28
31
  Message,
29
32
  Provider,
@@ -82,12 +85,12 @@ function makeRecordingProvider(responses: ProviderResponse[]): {
82
85
 
83
86
  describe("AgentLoop.run — mutableLatestUserMessage from memory-v3-live", () => {
84
87
  afterEach(() => {
85
- clearCachedOverrides();
88
+ memoryV3LiveSlot = false;
86
89
  });
87
90
 
88
91
  test("sets mutableLatestUserMessage on every LLM call when memory-v3-live is on (multi-turn)", async () => {
89
92
  // GIVEN memory-v3-live is enabled
90
- setCachedOverrides({ "memory-v3-live": true }, { fromGateway: true });
93
+ memoryV3LiveSlot = true;
91
94
 
92
95
  // AND a provider that records the config of each LLM call across a tool round-trip
93
96
  const { provider, configs } = makeRecordingProvider([
@@ -131,7 +134,7 @@ describe("AgentLoop.run — mutableLatestUserMessage from memory-v3-live", () =>
131
134
 
132
135
  test("omits mutableLatestUserMessage when memory-v3-live is off (flag-off byte-identity)", async () => {
133
136
  // GIVEN memory-v3-live is off (no override; registry default is false)
134
- clearCachedOverrides();
137
+ memoryV3LiveSlot = false;
135
138
 
136
139
  // AND a provider that records the config of each LLM call
137
140
  const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
@@ -2,9 +2,17 @@ import { existsSync } from "node:fs";
2
2
  import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import { afterAll, beforeAll, describe, expect, test } from "bun:test";
5
+ import {
6
+ afterAll,
7
+ beforeAll,
8
+ describe,
9
+ expect,
10
+ setDefaultTimeout,
11
+ test,
12
+ } from "bun:test";
6
13
 
7
14
  import { compileApp } from "../bundler/app-compiler.js";
15
+ import { ensureCompilerTools } from "../bundler/compiler-tools.js";
8
16
  import {
9
17
  ALLOWED_PACKAGES,
10
18
  getCacheDir,
@@ -19,8 +27,14 @@ import {
19
27
 
20
28
  let tempDir: string;
21
29
 
30
+ // Compiler-tool download (esbuild + preact from npm) can take 10s+ on a cold
31
+ // CI cache. Raise the file-level default so beforeAll isn't killed mid-install.
32
+ // Each test file runs in its own process, so this doesn't leak.
33
+ setDefaultTimeout(30_000);
34
+
22
35
  beforeAll(async () => {
23
36
  tempDir = await mkdtemp(join(tmpdir(), "app-compiler-test-"));
37
+ await ensureCompilerTools();
24
38
  });
25
39
 
26
40
  afterAll(async () => {
@@ -7,18 +7,10 @@ mock.module("../util/logger.js", () => ({
7
7
  }),
8
8
  }));
9
9
 
10
- let collectUsageData = true;
10
+ let shareAnalytics = true;
11
11
 
12
- mock.module("../config/loader.js", () => ({
13
- getConfig: () => ({
14
- ui: {},
15
- model: "test",
16
- provider: "test",
17
- memory: { enabled: false },
18
- rateLimit: { maxRequestsPerMinute: 0 },
19
- secretDetection: { enabled: false },
20
- collectUsageData,
21
- }),
12
+ mock.module("../platform/consent-cache.js", () => ({
13
+ getCachedShareAnalytics: () => shareAnalytics,
22
14
  }));
23
15
 
24
16
  import {
@@ -53,7 +45,7 @@ const SAMPLE: AuthFallbackCount[] = [
53
45
 
54
46
  describe("auth-fallback-events-store", () => {
55
47
  beforeEach(() => {
56
- collectUsageData = true;
48
+ shareAnalytics = true;
57
49
  resetTable();
58
50
  });
59
51
 
@@ -78,8 +70,8 @@ describe("auth-fallback-events-store", () => {
78
70
  });
79
71
  });
80
72
 
81
- test("honors the collectUsageData opt-out (records nothing)", () => {
82
- collectUsageData = false;
73
+ test("honors the share_analytics opt-out (records nothing)", () => {
74
+ shareAnalytics = false;
83
75
  const recorded = recordAuthFallbackCounts(1000, 2000, SAMPLE);
84
76
  expect(recorded).toBe(0);
85
77
  expect(queryUnreportedAuthFallbackEvents(0, undefined, 100).length).toBe(0);
@@ -343,6 +343,34 @@ describe("addPointerMessage", () => {
343
343
  expect(processorCalled).toBe(true);
344
344
  });
345
345
 
346
+ test("unverified_contact provenance round-trips through the metadata schema and is treated as trusted audience", async () => {
347
+ // Persisted unverified_contact metadata must survive the schema parse so
348
+ // downstream consumers (e.g. memory write gate, pointer audience trust)
349
+ // see the durable trust snapshot rather than a silently-dropped undefined.
350
+ const convId = "conv-ptr-uvc-provenance";
351
+ ensureConversation(convId);
352
+ await addMessage(convId, "user", "hello", {
353
+ metadata: { provenanceTrustClass: "unverified_contact" },
354
+ });
355
+
356
+ // Confirm the durable snapshot round-trips through the schema parser.
357
+ const { getConversationRecentProvenanceTrustClass } = await import(
358
+ "../memory/conversation-crud.js"
359
+ );
360
+ expect(getConversationRecentProvenanceTrustClass(convId)).toBe(
361
+ "unverified_contact",
362
+ );
363
+
364
+ // And that pointer-audience trust treats it identically to trusted_contact.
365
+ let processorCalled = false;
366
+ setPointerMessageProcessor(async () => {
367
+ processorCalled = true;
368
+ });
369
+
370
+ await addPointerMessage(convId, "completed", "+15559876543");
371
+ expect(processorCalled).toBe(true);
372
+ });
373
+
346
374
  test("unknown provenance trust class does not grant trusted audience", () => {
347
375
  const convId = "conv-ptr-unknown-provenance";
348
376
  ensureConversation(convId);
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Cancel contract: a user cancel raises the conversation's AbortController and
3
+ * defers clearing the `processing` flag to the in-flight turn's `finally`.
4
+ *
5
+ * `cancelGeneration` no longer force-clears `processing` itself. Abort now
6
+ * propagates into the provider call and tool execution — and is backed by the
7
+ * agent loop's abort watchdog — so a cancelled turn always reaches its
8
+ * `finally` within a bounded time and tears down its own state there. The
9
+ * watchdog-driven path (turn reaches `finally`, processing clears) is covered
10
+ * by `conversation-agent-loop.test.ts`; this file pins the handler-side
11
+ * contract: cancel signals abort and leaves the flag to the turn.
12
+ */
13
+ import { afterEach, describe, expect, test } from "bun:test";
14
+
15
+ import type { Conversation } from "../daemon/conversation.js";
16
+ import {
17
+ deleteConversation,
18
+ setConversation,
19
+ } from "../daemon/conversation-registry.js";
20
+ import { cancelGeneration } from "../daemon/handlers/conversations.js";
21
+
22
+ interface CancelledConversation {
23
+ isProcessing: () => boolean;
24
+ setProcessingCalls: () => boolean[];
25
+ abortCount: () => number;
26
+ }
27
+
28
+ /**
29
+ * Register a conversation whose in-flight turn is processing. The fake records
30
+ * abort calls and any `setProcessing` writes so the test can assert that
31
+ * `cancelGeneration` signals abort without flipping the flag itself.
32
+ */
33
+ function registerProcessingTurn(id: string): CancelledConversation {
34
+ let processing = true;
35
+ let abortCount = 0;
36
+ const setProcessingCalls: boolean[] = [];
37
+ const fake = {
38
+ isProcessing: () => processing,
39
+ setProcessing: (value: boolean) => {
40
+ setProcessingCalls.push(value);
41
+ processing = value;
42
+ },
43
+ abort: () => {
44
+ abortCount += 1;
45
+ },
46
+ };
47
+ setConversation(id, fake as unknown as Conversation);
48
+ return {
49
+ isProcessing: () => processing,
50
+ setProcessingCalls: () => setProcessingCalls,
51
+ abortCount: () => abortCount,
52
+ };
53
+ }
54
+
55
+ describe("cancelGeneration", () => {
56
+ const conversationId = "cancel-clears-processing-test-conversation";
57
+
58
+ afterEach(() => {
59
+ deleteConversation(conversationId);
60
+ });
61
+
62
+ test("raises abort and defers clearing processing to the turn's finally", () => {
63
+ // GIVEN a registered conversation that is processing
64
+ const fake = registerProcessingTurn(conversationId);
65
+ expect(fake.isProcessing()).toBe(true);
66
+
67
+ // WHEN the user cancels generation
68
+ const cancelled = cancelGeneration(conversationId);
69
+
70
+ // THEN the cancel is acknowledged
71
+ expect(cancelled).toBe(true);
72
+ // AND the abort signal is raised on the conversation
73
+ expect(fake.abortCount()).toBe(1);
74
+ // AND cancelGeneration does NOT force-clear the flag itself — the in-flight
75
+ // turn's `finally` owns that teardown once abort drives it there.
76
+ expect(fake.setProcessingCalls()).not.toContain(false);
77
+ expect(fake.isProcessing()).toBe(true);
78
+ });
79
+
80
+ test("returns false for a conversation that is not registered", () => {
81
+ // GIVEN no conversation registered under the id
82
+
83
+ // WHEN the user cancels generation for the unknown id
84
+ const cancelled = cancelGeneration("cancel-clears-processing-unknown-id");
85
+
86
+ // THEN the cancel reports that nothing was found
87
+ expect(cancelled).toBe(false);
88
+ });
89
+ });
@@ -224,14 +224,12 @@ function ensureTestContact(): void {
224
224
  {
225
225
  type: "telegram",
226
226
  address: "telegram-user-default",
227
- externalUserId: "telegram-user-default",
228
227
  status: "active",
229
228
  policy: "allow",
230
229
  },
231
230
  {
232
231
  type: "slack",
233
232
  address: "slack-user-default",
234
- externalUserId: "slack-user-default",
235
233
  status: "active",
236
234
  policy: "allow",
237
235
  },
@@ -2054,7 +2052,6 @@ describe("requester cancel of guardian-gated pending request", () => {
2054
2052
  {
2055
2053
  type: "telegram",
2056
2054
  address: "requester-cancel-user",
2057
- externalUserId: "requester-cancel-user",
2058
2055
  status: "active",
2059
2056
  policy: "allow",
2060
2057
  },
@@ -2981,7 +2978,6 @@ describe("trusted-contact self-approval blocked before guardian approval row exi
2981
2978
  {
2982
2979
  type: "telegram",
2983
2980
  address: "tc-selfapproval-user",
2984
- externalUserId: "tc-selfapproval-user",
2985
2981
  status: "active",
2986
2982
  policy: "allow",
2987
2983
  },
@@ -97,7 +97,6 @@ function seedTrustedContact(policy: "allow" | "escalate" = "allow"): void {
97
97
  {
98
98
  type: "telegram",
99
99
  address: "telegram-user-1",
100
- externalUserId: "telegram-user-1",
101
100
  status: "active",
102
101
  policy,
103
102
  },
@@ -237,14 +236,13 @@ describe("channel inbound disk pressure gate", () => {
237
236
  expect(db.select().from(messages).all()).toHaveLength(0);
238
237
  });
239
238
 
240
- test("blocks non-guardian Slack reactions before persistence while locked", async () => {
239
+ test("blocks non-guardian Slack reactions silently (no reply) before persistence while locked", async () => {
241
240
  upsertContact({
242
241
  displayName: "Example Slack User",
243
242
  channels: [
244
243
  {
245
244
  type: "slack",
246
245
  address: "slack-user-1",
247
- externalUserId: "slack-user-1",
248
246
  status: "active",
249
247
  policy: "allow",
250
248
  },
@@ -276,18 +274,10 @@ describe("channel inbound disk pressure gate", () => {
276
274
  reason: "trusted-contact",
277
275
  });
278
276
  expect(processMessage).not.toHaveBeenCalled();
279
- expect(deliverChannelReplyMock.mock.calls).toEqual([
280
- [
281
- "https://gateway.test/deliver/slack",
282
- {
283
- chatId: "slack-channel-1",
284
- text: expectedRemoteBlockReply,
285
- assistantId: "self",
286
- ephemeral: true,
287
- user: "slack-user-1",
288
- },
289
- ],
290
- ]);
277
+ // Reactions are blocked silently during disk pressure: a passive signal
278
+ // has nothing to "try again", so no block reply is delivered (unlike the
279
+ // message path, which does reply).
280
+ expect(deliverChannelReplyMock).not.toHaveBeenCalled();
291
281
 
292
282
  const db = getDb();
293
283
  const event = db
@@ -52,7 +52,7 @@ mock.module("../memory/v2/skill-store.js", () => ({
52
52
  // ---------------------------------------------------------------------------
53
53
 
54
54
  const { registerMemoryV2Command } =
55
- await import("../cli/commands/memory-v2.js");
55
+ await import("../cli/commands/memory/memory-v2.js");
56
56
  const { ROUTES: memoryV2Routes, MEMORY_V2_DISABLED_CODE } =
57
57
  await import("../runtime/routes/memory-v2-routes.js");
58
58
  const { RouteError } = await import("../runtime/routes/errors.js");
@@ -68,9 +68,8 @@ function buildProgram(): Command {
68
68
  writeErr: () => {},
69
69
  writeOut: () => {},
70
70
  });
71
- // The registrar creates the `memory` parent itself, so callers don't
72
- // need to stub one.
73
- registerMemoryV2Command(program);
71
+ const memory = program.command("memory");
72
+ registerMemoryV2Command(memory);
74
73
  return program;
75
74
  }
76
75