@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
@@ -0,0 +1,20 @@
1
+ import { getLogger } from "../../util/logger.js";
2
+ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
3
+
4
+ const log = getLogger("migration-295");
5
+
6
+ /**
7
+ * Drops the `approval_prompt_ts_tracker` table.
8
+ *
9
+ * Guardian approval-by-reaction no longer scopes reactions through a bespoke
10
+ * `(channel, chat, ts)` tracker. The canonical guardian delivery record
11
+ * (`canonical_guardian_deliveries.destination_message_id`) is now the single
12
+ * mapping from a delivered approval card to its request, so this table is dead.
13
+ *
14
+ * Idempotent: `DROP TABLE IF EXISTS` is a no-op once the table is gone.
15
+ */
16
+ export function dropApprovalPromptTsTrackerTable(database: DrizzleDb): void {
17
+ const raw = getSqliteFrom(database);
18
+ raw.run("DROP TABLE IF EXISTS approval_prompt_ts_tracker");
19
+ log.info("Dropped approval_prompt_ts_tracker table");
20
+ }
@@ -0,0 +1,110 @@
1
+ import { existsSync, rmSync, writeFileSync } from "node:fs";
2
+ import { Database } from "bun:sqlite";
3
+ import { afterEach, describe, expect, test } from "bun:test";
4
+
5
+ import { drizzle } from "drizzle-orm/bun-sqlite";
6
+
7
+ import { getWorkspaceConfigPath } from "../../util/platform.js";
8
+ import * as schema from "../schema.js";
9
+ import { migrateRewriteBalancedEconomyProfilePins } from "./296-rewrite-balanced-economy-profile-pins.js";
10
+
11
+ function writeWorkspaceConfig(config: Record<string, unknown>): void {
12
+ writeFileSync(getWorkspaceConfigPath(), JSON.stringify(config));
13
+ }
14
+
15
+ afterEach(() => {
16
+ const path = getWorkspaceConfigPath();
17
+ if (existsSync(path)) rmSync(path);
18
+ });
19
+
20
+ function createTestDb(withColumns = true) {
21
+ const sqlite = new Database(":memory:");
22
+ sqlite.exec(/*sql*/ `
23
+ CREATE TABLE conversations (
24
+ id TEXT PRIMARY KEY
25
+ ${withColumns ? ", inference_profile TEXT" : ""}
26
+ )
27
+ `);
28
+ sqlite.exec(/*sql*/ `
29
+ CREATE TABLE cron_jobs (
30
+ id TEXT PRIMARY KEY
31
+ ${withColumns ? ", inference_profile TEXT" : ""}
32
+ )
33
+ `);
34
+ return { sqlite, db: drizzle(sqlite, { schema }) };
35
+ }
36
+
37
+ function pin(sqlite: Database, table: string, id: string): string | null {
38
+ return (
39
+ sqlite
40
+ .query(`SELECT inference_profile FROM ${table} WHERE id = ?`)
41
+ .get(id) as { inference_profile: string | null }
42
+ ).inference_profile;
43
+ }
44
+
45
+ describe("migration 296: rewrite balanced-economy profile pins", () => {
46
+ test("rewrites balanced-economy pins to balanced on conversations and schedules", () => {
47
+ const { sqlite, db } = createTestDb();
48
+ sqlite.run(
49
+ `INSERT INTO conversations (id, inference_profile) VALUES ('c1', 'balanced-economy'), ('c2', 'quality-optimized'), ('c3', NULL)`,
50
+ );
51
+ sqlite.run(
52
+ `INSERT INTO cron_jobs (id, inference_profile) VALUES ('j1', 'balanced-economy'), ('j2', 'balanced')`,
53
+ );
54
+
55
+ migrateRewriteBalancedEconomyProfilePins(db);
56
+
57
+ expect(pin(sqlite, "conversations", "c1")).toBe("balanced");
58
+ // Unrelated pins and NULLs are left untouched.
59
+ expect(pin(sqlite, "conversations", "c2")).toBe("quality-optimized");
60
+ expect(pin(sqlite, "conversations", "c3")).toBeNull();
61
+ expect(pin(sqlite, "cron_jobs", "j1")).toBe("balanced");
62
+ expect(pin(sqlite, "cron_jobs", "j2")).toBe("balanced");
63
+ });
64
+
65
+ test("is idempotent — re-run is a no-op", () => {
66
+ const { sqlite, db } = createTestDb();
67
+ sqlite.run(
68
+ `INSERT INTO conversations (id, inference_profile) VALUES ('c1', 'balanced-economy')`,
69
+ );
70
+
71
+ migrateRewriteBalancedEconomyProfilePins(db);
72
+ expect(() => migrateRewriteBalancedEconomyProfilePins(db)).not.toThrow();
73
+ expect(pin(sqlite, "conversations", "c1")).toBe("balanced");
74
+ });
75
+
76
+ test("skips a table that has no inference_profile column", () => {
77
+ const { db } = createTestDb(false);
78
+ expect(() => migrateRewriteBalancedEconomyProfilePins(db)).not.toThrow();
79
+ });
80
+
81
+ test("leaves pins alone when balanced-economy is a user-owned profile", () => {
82
+ // The workspace migration keeps a user-owned profile of this name, so its
83
+ // pins still resolve and must not be switched to balanced.
84
+ writeWorkspaceConfig({
85
+ llm: { profiles: { "balanced-economy": { source: "user" } } },
86
+ });
87
+ const { sqlite, db } = createTestDb();
88
+ sqlite.run(
89
+ `INSERT INTO conversations (id, inference_profile) VALUES ('c1', 'balanced-economy')`,
90
+ );
91
+
92
+ migrateRewriteBalancedEconomyProfilePins(db);
93
+
94
+ expect(pin(sqlite, "conversations", "c1")).toBe("balanced-economy");
95
+ });
96
+
97
+ test("rewrites pins when balanced-economy is the managed profile in config", () => {
98
+ writeWorkspaceConfig({
99
+ llm: { profiles: { "balanced-economy": { source: "managed" } } },
100
+ });
101
+ const { sqlite, db } = createTestDb();
102
+ sqlite.run(
103
+ `INSERT INTO conversations (id, inference_profile) VALUES ('c1', 'balanced-economy')`,
104
+ );
105
+
106
+ migrateRewriteBalancedEconomyProfilePins(db);
107
+
108
+ expect(pin(sqlite, "conversations", "c1")).toBe("balanced");
109
+ });
110
+ });
@@ -0,0 +1,68 @@
1
+ import { loadRawConfig } from "../../config/loader.js";
2
+ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
3
+
4
+ const OLD_PROFILE = "balanced-economy";
5
+ const NEW_PROFILE = "balanced";
6
+
7
+ // Active inference-profile pin columns: the per-conversation override and the
8
+ // per-schedule override. Audit/telemetry tables (tool_invocations,
9
+ // llm_usage_events, skill_loaded_events) also carry an `inference_profile`, but
10
+ // those record what actually ran and must keep their historical value.
11
+ const PIN_TABLES = ["conversations", "cron_jobs"] as const;
12
+
13
+ /**
14
+ * Rewrite persisted `balanced-economy` inference-profile pins to `balanced`.
15
+ *
16
+ * The managed `balanced-economy` profile folds into `balanced` (both resolve to
17
+ * MiniMax M3 on Fireworks). The workspace-config migration removes the profile
18
+ * definition, but a profile key can also be pinned outside config.json — on a
19
+ * conversation (`conversations.inference_profile`) or a schedule
20
+ * (`cron_jobs.inference_profile`). Left unrewritten those pins dangle, and
21
+ * `resolveCallSiteConfig` treats an unresolvable override as a silent
22
+ * fall-through to the active/default profile — dropping the user's intended
23
+ * MiniMax route. `balanced-economy` is a reserved managed-profile key, so every
24
+ * persisted pin refers to the now-folded profile.
25
+ *
26
+ * Ownership guard: the companion workspace migration keeps a `balanced-economy`
27
+ * profile the user owns (`source !== "managed"`) intact, so its pins still
28
+ * resolve and must not be touched. Memory migrations run before workspace
29
+ * migrations on boot, so the profile is still in config here either way; gate on
30
+ * its source rather than its presence. A reserved managed key (or no profile at
31
+ * all) means every pin is stale and safe to rewrite.
32
+ *
33
+ * Idempotent: the WHERE clause matches nothing once rewritten. The PRAGMA guard
34
+ * skips a table an install hasn't created the column on yet.
35
+ */
36
+ export function migrateRewriteBalancedEconomyProfilePins(
37
+ database: DrizzleDb,
38
+ ): void {
39
+ if (isUserOwnedEconomyProfile()) return;
40
+
41
+ const raw = getSqliteFrom(database);
42
+
43
+ for (const table of PIN_TABLES) {
44
+ const cols = raw.prepare(`PRAGMA table_info(${table})`).all() as {
45
+ name: string;
46
+ }[];
47
+ if (!cols.some((c) => c.name === "inference_profile")) continue;
48
+
49
+ raw
50
+ .prepare(
51
+ `UPDATE ${table} SET inference_profile = ? WHERE inference_profile = ?`,
52
+ )
53
+ .run(NEW_PROFILE, OLD_PROFILE);
54
+ }
55
+ }
56
+
57
+ /** True when `balanced-economy` exists in config as a user-owned profile. */
58
+ function isUserOwnedEconomyProfile(): boolean {
59
+ const llm = asObject(loadRawConfig().llm);
60
+ const profile = asObject(asObject(llm?.profiles)?.[OLD_PROFILE]);
61
+ return profile !== null && profile.source !== "managed";
62
+ }
63
+
64
+ function asObject(value: unknown): Record<string, unknown> | null {
65
+ return value !== null && typeof value === "object" && !Array.isArray(value)
66
+ ? (value as Record<string, unknown>)
67
+ : null;
68
+ }
@@ -0,0 +1,154 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { describe, expect, test } from "bun:test";
3
+
4
+ import { drizzle } from "drizzle-orm/bun-sqlite";
5
+
6
+ import { getSqliteFrom } from "../../db-connection.js";
7
+ import * as schema from "../../schema.js";
8
+ import { migrateDropLegacyMemberGuardianTables } from "../131-drop-legacy-member-guardian-tables.js";
9
+
10
+ function createTestDb() {
11
+ const sqlite = new Database(":memory:");
12
+ sqlite.exec("PRAGMA journal_mode=WAL");
13
+ return drizzle(sqlite, { schema });
14
+ }
15
+
16
+ function bootstrap(db: ReturnType<typeof createTestDb>): void {
17
+ const raw = getSqliteFrom(db);
18
+ raw.exec(/*sql*/ `
19
+ CREATE TABLE contacts (
20
+ id TEXT PRIMARY KEY,
21
+ display_name TEXT NOT NULL,
22
+ role TEXT NOT NULL DEFAULT 'contact',
23
+ principal_id TEXT,
24
+ created_at INTEGER NOT NULL,
25
+ updated_at INTEGER NOT NULL
26
+ )
27
+ `);
28
+ raw.exec(/*sql*/ `
29
+ CREATE TABLE contact_channels (
30
+ id TEXT PRIMARY KEY,
31
+ contact_id TEXT NOT NULL,
32
+ type TEXT NOT NULL,
33
+ address TEXT NOT NULL,
34
+ external_user_id TEXT,
35
+ external_chat_id TEXT,
36
+ status TEXT NOT NULL DEFAULT 'unverified',
37
+ policy TEXT NOT NULL DEFAULT 'allow',
38
+ invite_id TEXT,
39
+ revoked_reason TEXT,
40
+ blocked_reason TEXT,
41
+ last_seen_at INTEGER,
42
+ verified_at INTEGER,
43
+ verified_via TEXT,
44
+ created_at INTEGER NOT NULL,
45
+ updated_at INTEGER
46
+ )
47
+ `);
48
+ raw.exec(/*sql*/ `
49
+ CREATE INDEX idx_contact_channels_type_ext_user
50
+ ON contact_channels(type, external_user_id)
51
+ `);
52
+ }
53
+
54
+ function createLegacyTables(raw: Database): void {
55
+ raw.exec(/*sql*/ `
56
+ CREATE TABLE channel_guardian_bindings (
57
+ id TEXT PRIMARY KEY,
58
+ channel TEXT NOT NULL,
59
+ status TEXT NOT NULL,
60
+ guardian_external_user_id TEXT,
61
+ guardian_principal_id TEXT,
62
+ guardian_delivery_chat_id TEXT,
63
+ metadata_json TEXT,
64
+ verified_at INTEGER,
65
+ verified_via TEXT,
66
+ created_at INTEGER NOT NULL
67
+ )
68
+ `);
69
+ raw.exec(/*sql*/ `
70
+ CREATE TABLE assistant_ingress_members (
71
+ id TEXT PRIMARY KEY,
72
+ source_channel TEXT NOT NULL,
73
+ external_user_id TEXT,
74
+ external_chat_id TEXT,
75
+ display_name TEXT,
76
+ username TEXT,
77
+ status TEXT NOT NULL,
78
+ policy TEXT,
79
+ invite_id TEXT,
80
+ revoked_reason TEXT,
81
+ blocked_reason TEXT,
82
+ last_seen_at INTEGER,
83
+ created_at INTEGER NOT NULL,
84
+ updated_at INTEGER
85
+ )
86
+ `);
87
+ }
88
+
89
+ function tableExists(raw: Database, name: string): boolean {
90
+ return !!raw
91
+ .prepare(`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?`)
92
+ .get(name);
93
+ }
94
+
95
+ function seedLegacyRows(raw: Database): void {
96
+ raw.run(
97
+ `INSERT INTO channel_guardian_bindings
98
+ (id, channel, status, guardian_external_user_id, guardian_principal_id,
99
+ guardian_delivery_chat_id, metadata_json, verified_at, verified_via, created_at)
100
+ VALUES ('g1','telegram','active','U-guardian','prin-1','chat-1',NULL,1000,'telegram',1000)`,
101
+ );
102
+ raw.run(
103
+ `INSERT INTO assistant_ingress_members
104
+ (id, source_channel, external_user_id, external_chat_id, display_name,
105
+ username, status, policy, invite_id, revoked_reason, blocked_reason,
106
+ last_seen_at, created_at, updated_at)
107
+ VALUES ('m1','slack','U-member',NULL,'Member One','member1','active','allow',
108
+ NULL,NULL,NULL,2000,2000,2000)`,
109
+ );
110
+ }
111
+
112
+ describe("migration 131 — drop legacy member/guardian tables", () => {
113
+ test("syncs stragglers then drops the legacy tables when the column is present", () => {
114
+ const db = createTestDb();
115
+ const raw = getSqliteFrom(db);
116
+ bootstrap(db);
117
+ createLegacyTables(raw);
118
+ seedLegacyRows(raw);
119
+
120
+ migrateDropLegacyMemberGuardianTables(db);
121
+
122
+ // The straggler rows were synced into contact_channels.
123
+ const synced = raw
124
+ .prepare(`SELECT external_user_id FROM contact_channels ORDER BY id`)
125
+ .all() as { external_user_id: string | null }[];
126
+ expect(synced.map((r) => r.external_user_id).sort()).toEqual([
127
+ "U-guardian",
128
+ "U-member",
129
+ ]);
130
+
131
+ // The legacy tables were removed.
132
+ expect(tableExists(raw, "channel_guardian_bindings")).toBe(false);
133
+ expect(tableExists(raw, "assistant_ingress_members")).toBe(false);
134
+ });
135
+
136
+ test("drops the legacy tables without throwing when external_user_id is absent (re-run after migration 294)", () => {
137
+ const db = createTestDb();
138
+ const raw = getSqliteFrom(db);
139
+ bootstrap(db);
140
+ createLegacyTables(raw);
141
+ seedLegacyRows(raw);
142
+
143
+ // Simulate a later startup where migration 294 has already dropped the
144
+ // index + column. The sync references external_user_id, so 131 must skip it
145
+ // and still drop the tables rather than failing on every boot.
146
+ raw.run("DROP INDEX IF EXISTS idx_contact_channels_type_ext_user");
147
+ raw.run("ALTER TABLE contact_channels DROP COLUMN external_user_id");
148
+
149
+ expect(() => migrateDropLegacyMemberGuardianTables(db)).not.toThrow();
150
+
151
+ expect(tableExists(raw, "channel_guardian_bindings")).toBe(false);
152
+ expect(tableExists(raw, "assistant_ingress_members")).toBe(false);
153
+ });
154
+ });
@@ -568,4 +568,35 @@ describe("migration 287 — dedup case collisions + drop ext_user indexes", () =
568
568
  // Original casing preserved
569
569
  expect(channels[0]!.address).toBe("U999");
570
570
  });
571
+
572
+ test("no-op when external_user_id column is absent (re-run after migration 294)", () => {
573
+ const db = createTestDb();
574
+ const raw = getSqliteFrom(db);
575
+ bootstrap(db);
576
+
577
+ insertContact(raw, "c1");
578
+ insertChannel(raw, {
579
+ id: "ch1",
580
+ contactId: "c1",
581
+ type: "slack",
582
+ address: "U999",
583
+ externalUserId: "U999",
584
+ status: "active",
585
+ updatedAt: 1000,
586
+ });
587
+
588
+ // Simulate a later startup where migration 294 has already dropped the
589
+ // index and column. Migration steps re-run on every startup, so this must
590
+ // tolerate the dropped column rather than throwing "no such column".
591
+ raw.run("DROP INDEX IF EXISTS idx_contact_channels_type_ext_user");
592
+ raw.run("ALTER TABLE contact_channels DROP COLUMN external_user_id");
593
+
594
+ expect(() => migrateContactChannelsUniqueExtUser(db)).not.toThrow();
595
+
596
+ const rows = raw
597
+ .prepare("SELECT id, address FROM contact_channels")
598
+ .all() as { id: string; address: string }[];
599
+ expect(rows).toHaveLength(1);
600
+ expect(rows[0]!.address).toBe("U999");
601
+ });
571
602
  });
@@ -308,4 +308,34 @@ describe("migration 291 — renormalize addresses", () => {
308
308
  expect(rows).toHaveLength(1);
309
309
  expect(rows[0].address).toBe("U12345ABC");
310
310
  });
311
+
312
+ test("no-op when external_user_id column is absent (re-run after migration 294)", () => {
313
+ const db = createTestDb();
314
+ bootstrap(db);
315
+ const raw = getSqliteFrom(db);
316
+
317
+ insertContact(raw, "c1");
318
+ insertChannel(raw, {
319
+ id: "ch1",
320
+ contactId: "c1",
321
+ type: "slack",
322
+ address: "u12345abc",
323
+ externalUserId: "U12345ABC",
324
+ status: "active",
325
+ });
326
+
327
+ // Simulate a later startup where migration 294 has already dropped the
328
+ // index and column. Migration steps re-run on every startup, so this must
329
+ // tolerate the dropped column rather than throwing "no such column".
330
+ raw.run("DROP INDEX IF EXISTS idx_contact_channels_type_ext_user");
331
+ raw.run("ALTER TABLE contact_channels DROP COLUMN external_user_id");
332
+
333
+ expect(() => migrateContactChannelsRenormalizeAddresses(db)).not.toThrow();
334
+
335
+ const rows = raw
336
+ .prepare("SELECT id, address FROM contact_channels")
337
+ .all() as { id: string; address: string }[];
338
+ expect(rows).toHaveLength(1);
339
+ expect(rows[0].address).toBe("u12345abc");
340
+ });
311
341
  });
@@ -283,6 +283,11 @@ export { migrateBackfillOriginChannelFromBindings } from "./288-backfill-origin-
283
283
  export { migrateContactChannelsUniqueExtUser } from "./289-contact-channels-unique-ext-user.js";
284
284
  export { migrateScheduleCapabilities } from "./290-schedule-capabilities.js";
285
285
  export { migrateContactChannelsRenormalizeAddresses } from "./291-contact-channels-renormalize-addresses.js";
286
+ export { migrateScheduleDefaultNoReuseConversation } from "./292-schedule-default-no-reuse-conversation.js";
287
+ export { migrateWorkflowJournalLeafTokens } from "./293-workflow-journal-leaf-tokens.js";
288
+ export { migrateDropExternalUserId } from "./294-drop-external-user-id.js";
289
+ export { dropApprovalPromptTsTrackerTable } from "./295-drop-approval-prompt-ts-tracker.js";
290
+ export { migrateRewriteBalancedEconomyProfilePins } from "./296-rewrite-balanced-economy-profile-pins.js";
286
291
  export {
287
292
  MIGRATION_REGISTRY,
288
293
  type MigrationRegistryEntry,
@@ -1,7 +1,7 @@
1
1
  import { and, asc, eq, gt, or } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
- import { getConfig } from "../config/loader.js";
4
+ import { getCachedShareAnalytics } from "../platform/consent-cache.js";
5
5
  import {
6
6
  ACTIVATION_AB_VARIANT,
7
7
  ACTIVATION_FUNNEL_VERSION,
@@ -58,7 +58,7 @@ function insertOnboardingEvent(event: OnboardingEvent): OnboardingEvent {
58
58
  export function recordOnboardingEvent(
59
59
  params: RecordOnboardingEventParams,
60
60
  ): OnboardingEvent | null {
61
- if (!getConfig().collectUsageData) return null;
61
+ if (!getCachedShareAnalytics()) return null;
62
62
  return insertOnboardingEvent({
63
63
  id: uuid(),
64
64
  createdAt: Date.now(),
@@ -93,7 +93,7 @@ export function recordActivationEvent(params: {
93
93
  userId?: string | null;
94
94
  abVariant?: string;
95
95
  }): OnboardingEvent | null {
96
- if (!getConfig().collectUsageData) return null;
96
+ if (!getCachedShareAnalytics()) return null;
97
97
  const createdAt = Date.now();
98
98
  return insertOnboardingEvent({
99
99
  id: uuid(),
@@ -26,7 +26,6 @@ export const contactChannels = sqliteTable(
26
26
  isPrimary: integer("is_primary", { mode: "boolean" })
27
27
  .notNull()
28
28
  .default(false),
29
- externalUserId: text("external_user_id"), // channel-native user ID (e.g., Telegram numeric ID, E.164 phone)
30
29
  externalChatId: text("external_chat_id"), // delivery/notification routing address (e.g., Telegram chat ID)
31
30
  status: text("status").notNull().default("unverified"), // 'active' | 'pending' | 'revoked' | 'blocked' | 'unverified'
32
31
  policy: text("policy").notNull().default("allow"), // 'allow' | 'deny' | 'escalate'
@@ -8,18 +8,10 @@ mock.module("../util/logger.js", () => ({
8
8
  }),
9
9
  }));
10
10
 
11
- let collectUsageData = true;
12
-
13
- mock.module("../config/loader.js", () => ({
14
- getConfig: () => ({
15
- ui: {},
16
- model: "test",
17
- provider: "test",
18
- memory: { enabled: false },
19
- rateLimit: { maxRequestsPerMinute: 0 },
20
- secretDetection: { enabled: false },
21
- collectUsageData,
22
- }),
11
+ let shareAnalytics = true;
12
+
13
+ mock.module("../platform/consent-cache.js", () => ({
14
+ getCachedShareAnalytics: () => shareAnalytics,
23
15
  }));
24
16
 
25
17
  import { getDb } from "./db-connection.js";
@@ -42,12 +34,12 @@ function insertEvent(
42
34
 
43
35
  describe("skill-loaded-events-store", () => {
44
36
  beforeEach(() => {
45
- collectUsageData = true;
37
+ shareAnalytics = true;
46
38
  getDb().delete(skillLoadedEvents).run();
47
39
  });
48
40
 
49
- test("honors the collectUsageData opt-out (records nothing)", () => {
50
- collectUsageData = false;
41
+ test("honors the share_analytics opt-out (records nothing)", () => {
42
+ shareAnalytics = false;
51
43
  recordSkillLoadedEvent({ skillName: "web-research" });
52
44
  expect(queryUnreportedSkillLoadedEvents(0, undefined, 10)).toHaveLength(0);
53
45
  });
@@ -1,7 +1,7 @@
1
1
  import { and, asc, eq, gt, or } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
- import { getConfig } from "../config/loader.js";
4
+ import { getCachedShareAnalytics } from "../platform/consent-cache.js";
5
5
  import type { UsageAttributionColumns } from "../usage/attribution.js";
6
6
  import { getDb } from "./db-connection.js";
7
7
  import { skillLoadedEvents } from "./schema.js";
@@ -38,7 +38,7 @@ export interface SkillLoadedEvent {
38
38
  * opt-out, matching the rest of telemetry).
39
39
  */
40
40
  export function recordSkillLoadedEvent(record: SkillLoadedEventRecord): void {
41
- if (!getConfig().collectUsageData) return;
41
+ if (!getCachedShareAnalytics()) return;
42
42
  const db = getDb();
43
43
  db.insert(skillLoadedEvents)
44
44
  .values({
@@ -8,12 +8,12 @@ mock.module("../util/logger.js", () => ({
8
8
  }),
9
9
  }));
10
10
 
11
- // Toggle for the collectUsageData opt-out gate the audit listener consults
11
+ // Toggle for the share_analytics opt-out gate the audit listener consults
12
12
  // when populating the telemetry columns.
13
- let collectUsageData = true;
13
+ let shareAnalytics = true;
14
14
 
15
- mock.module("../config/loader.js", () => ({
16
- getConfig: () => ({ collectUsageData }),
15
+ mock.module("../platform/consent-cache.js", () => ({
16
+ getCachedShareAnalytics: () => shareAnalytics,
17
17
  }));
18
18
 
19
19
  import {
@@ -43,7 +43,7 @@ function insertInvocation(
43
43
 
44
44
  describe("tool-executed-events-store", () => {
45
45
  beforeEach(() => {
46
- collectUsageData = true;
46
+ shareAnalytics = true;
47
47
  getDb().delete(toolInvocations).run();
48
48
  });
49
49
 
@@ -136,10 +136,10 @@ describe("tool-executed-events-store", () => {
136
136
 
137
137
  listener(executedEvent("t-opted-in-before"));
138
138
 
139
- collectUsageData = false;
139
+ shareAnalytics = false;
140
140
  listener(executedEvent("t-opted-out"));
141
141
 
142
- collectUsageData = true;
142
+ shareAnalytics = true;
143
143
  listener(executedEvent("t-opted-in-after"));
144
144
 
145
145
  // Mid-session opt-out flip: only rows recorded while opted in project.