@vellumai/assistant 0.9.0 → 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 (572) hide show
  1. package/ARCHITECTURE.md +18 -34
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +976 -63
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  28. package/src/__tests__/app-compiler.test.ts +15 -1
  29. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  32. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  33. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  34. package/src/__tests__/btw-routes.test.ts +6 -8
  35. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  36. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  37. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  38. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  39. package/src/__tests__/checker.test.ts +0 -3
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  41. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  44. package/src/__tests__/config-schema.test.ts +35 -0
  45. package/src/__tests__/config-watcher.test.ts +0 -18
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  47. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  48. package/src/__tests__/contacts-tools.test.ts +29 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  53. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  54. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  55. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  56. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  57. package/src/__tests__/conversation-title-service.test.ts +62 -0
  58. package/src/__tests__/credential-broker.test.ts +449 -1
  59. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  60. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  61. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  62. package/src/__tests__/credential-routes.test.ts +360 -0
  63. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  64. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  65. package/src/__tests__/disk-usage.test.ts +65 -0
  66. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  67. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  68. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  69. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  71. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  72. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  73. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  74. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  75. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  76. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  77. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  78. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  79. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  80. package/src/__tests__/identity-routes.test.ts +0 -189
  81. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  82. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  83. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  84. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  85. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  86. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  87. package/src/__tests__/llm-resolver.test.ts +70 -24
  88. package/src/__tests__/llm-schema.test.ts +1 -0
  89. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  90. package/src/__tests__/mcp-health-check.test.ts +6 -7
  91. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  92. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  93. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  94. package/src/__tests__/path-policy.test.ts +34 -0
  95. package/src/__tests__/persona-resolver.test.ts +49 -14
  96. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  97. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  98. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  99. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  100. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  101. package/src/__tests__/reaction-persistence.test.ts +150 -29
  102. package/src/__tests__/registry.test.ts +2 -7
  103. package/src/__tests__/relay-server.test.ts +285 -0
  104. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  105. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  106. package/src/__tests__/schedule-routes.test.ts +0 -30
  107. package/src/__tests__/schedule-tools.test.ts +2 -18
  108. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  109. package/src/__tests__/skill-execute-input.test.ts +51 -1
  110. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  111. package/src/__tests__/skills.test.ts +51 -0
  112. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  113. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  114. package/src/__tests__/subagent-tools.test.ts +266 -0
  115. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  116. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  117. package/src/__tests__/title-generate-hook.test.ts +100 -3
  118. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  119. package/src/__tests__/token-manager.test.ts +519 -0
  120. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  121. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  122. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  123. package/src/__tests__/tool-executor.test.ts +0 -79
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  126. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  127. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  128. package/src/__tests__/twilio-routes.test.ts +81 -1
  129. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  130. package/src/__tests__/weak-open-model.test.ts +30 -0
  131. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  132. package/src/__tests__/workspace-greetings.test.ts +152 -0
  133. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  134. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  136. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  137. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  138. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  139. package/src/agent/loop.ts +49 -29
  140. package/src/api/README.md +6 -6
  141. package/src/api/events/tool-result.ts +6 -0
  142. package/src/api/events/workflow-completed.ts +53 -0
  143. package/src/api/events/workflow-leaf-finished.ts +38 -0
  144. package/src/api/events/workflow-leaf-started.ts +35 -0
  145. package/src/api/events/workflow-progress.ts +32 -0
  146. package/src/api/events/workflow-started.ts +31 -0
  147. package/src/api/index.ts +40 -0
  148. package/src/api/responses/conversation-message.ts +28 -4
  149. package/src/api/responses/home.ts +26 -4
  150. package/src/api/responses/workflow-journal.ts +53 -0
  151. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  152. package/src/approvals/guardian-decision-primitive.ts +26 -3
  153. package/src/approvals/guardian-request-resolvers.ts +183 -80
  154. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  155. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  156. package/src/calls/call-pointer-messages.ts +10 -4
  157. package/src/calls/channel-admission-reader.ts +104 -0
  158. package/src/calls/guardian-dispatch.ts +17 -45
  159. package/src/calls/media-stream-server.ts +84 -2
  160. package/src/calls/relay-access-wait.ts +1 -1
  161. package/src/calls/relay-server.ts +66 -0
  162. package/src/calls/relay-setup-router.ts +82 -1
  163. package/src/calls/twilio-routes.ts +17 -8
  164. package/src/calls/voice-session-bridge.ts +2 -2
  165. package/src/cli/commands/clients.ts +3 -0
  166. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  167. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  168. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  169. package/src/cli/commands/memory/index.ts +30 -0
  170. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  171. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  172. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  173. package/src/cli/commands/oauth/status.test.ts +36 -0
  174. package/src/cli/commands/oauth/status.ts +23 -3
  175. package/src/cli/commands/plugins.ts +197 -4
  176. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  177. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  178. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  179. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  181. package/src/cli/lib/diff-plugin.ts +346 -0
  182. package/src/cli/lib/inspect-plugin.ts +12 -1
  183. package/src/cli/lib/install-from-github.ts +105 -17
  184. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  185. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  186. package/src/cli/lib/plugin-surfaces.ts +104 -0
  187. package/src/cli/lib/upgrade-plugin.ts +298 -10
  188. package/src/cli/program.ts +2 -6
  189. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  190. package/src/config/assistant-feature-flags.ts +22 -7
  191. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  192. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  193. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  194. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  195. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  196. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  197. package/src/config/bundled-tool-registry.ts +2 -7
  198. package/src/config/call-site-defaults.ts +15 -2
  199. package/src/config/feature-flag-registry.json +46 -31
  200. package/src/config/inference-profile-validation.ts +26 -0
  201. package/src/config/llm-resolver.ts +3 -0
  202. package/src/config/loader.ts +4 -0
  203. package/src/config/memory-v3-gate.ts +11 -0
  204. package/src/config/profile-order.ts +28 -0
  205. package/src/config/schema.ts +8 -6
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  207. package/src/config/schemas/call-site-catalog.ts +7 -0
  208. package/src/config/schemas/channels.ts +11 -0
  209. package/src/config/schemas/elevenlabs.ts +0 -1
  210. package/src/config/schemas/llm.ts +31 -0
  211. package/src/config/schemas/memory-lifecycle.ts +3 -7
  212. package/src/config/schemas/memory-v3.ts +6 -0
  213. package/src/config/schemas/platform.ts +0 -8
  214. package/src/config/schemas/services.ts +18 -0
  215. package/src/config/seed-inference-profiles.ts +109 -44
  216. package/src/config/skills.ts +21 -0
  217. package/src/config/sync-gated-profiles.ts +220 -0
  218. package/src/contacts/contact-store.ts +89 -106
  219. package/src/contacts/contacts-write.ts +5 -22
  220. package/src/contacts/types.ts +0 -1
  221. package/src/context/compactor.ts +88 -54
  222. package/src/context/strip-injections.ts +58 -10
  223. package/src/context/token-estimator.ts +1 -1
  224. package/src/credential-execution/process-manager.ts +55 -14
  225. package/src/credential-execution/prompted-credential.ts +2 -3
  226. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  227. package/src/daemon/config-watcher.ts +0 -4
  228. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  229. package/src/daemon/conversation-agent-loop.ts +114 -22
  230. package/src/daemon/conversation-history.ts +1 -1
  231. package/src/daemon/conversation-lifecycle.ts +3 -5
  232. package/src/daemon/conversation-process.ts +13 -5
  233. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  234. package/src/daemon/conversation-slash.ts +2 -23
  235. package/src/daemon/conversation-surfaces.ts +26 -0
  236. package/src/daemon/conversation-tool-setup.ts +27 -14
  237. package/src/daemon/conversation.ts +66 -14
  238. package/src/daemon/disk-pressure-policy.ts +5 -3
  239. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  240. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  241. package/src/daemon/handlers/config-a2a.ts +0 -2
  242. package/src/daemon/handlers/config-channels.ts +15 -16
  243. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  244. package/src/daemon/handlers/conversations.ts +107 -0
  245. package/src/daemon/host-browser-proxy.ts +41 -0
  246. package/src/daemon/lifecycle.ts +55 -27
  247. package/src/daemon/message-provenance.ts +2 -0
  248. package/src/daemon/message-types/contacts.ts +0 -1
  249. package/src/daemon/message-types/conversations.ts +3 -3
  250. package/src/daemon/message-types/sync.ts +0 -1
  251. package/src/daemon/message-types/web-activity.ts +7 -1
  252. package/src/daemon/message-types/workflows.ts +83 -1
  253. package/src/daemon/orphan-reaper.test.ts +0 -19
  254. package/src/daemon/orphan-reaper.ts +2 -24
  255. package/src/daemon/server.ts +0 -10
  256. package/src/daemon/tool-setup-types.ts +4 -0
  257. package/src/daemon/trust-context.ts +1 -1
  258. package/src/events/tool-audit-listener.ts +2 -2
  259. package/src/home/feed-source-enrichment.test.ts +151 -0
  260. package/src/home/feed-source-enrichment.ts +176 -0
  261. package/src/home/relationship-state.ts +2 -4
  262. package/src/instrument.ts +18 -6
  263. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  264. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  265. package/src/ipc/assistant-server.ts +37 -4
  266. package/src/ipc/gateway-flag-listener.ts +18 -2
  267. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  268. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  269. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  270. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  271. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  272. package/src/memory/auth-fallback-events-store.ts +2 -2
  273. package/src/memory/auto-analysis-enqueue.ts +3 -5
  274. package/src/memory/bookmark-crud.ts +1 -2
  275. package/src/memory/canonical-guardian-store.ts +39 -1
  276. package/src/memory/conversation-crud.ts +9 -4
  277. package/src/memory/conversation-key-store.ts +17 -2
  278. package/src/memory/conversation-title-service.ts +64 -7
  279. package/src/memory/db-init.ts +17 -17
  280. package/src/memory/embedding-backend.ts +38 -1
  281. package/src/memory/embedding-billing-breaker.ts +96 -0
  282. package/src/memory/jobs-store.ts +25 -13
  283. package/src/memory/jobs-worker.ts +54 -1
  284. package/src/memory/lifecycle-events-store.ts +2 -2
  285. package/src/memory/memory-retrospective-constants.ts +4 -4
  286. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  287. package/src/memory/memory-retrospective-job.ts +28 -227
  288. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  289. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  290. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  291. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  292. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  293. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  294. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  295. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  296. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  297. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  298. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  299. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  300. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  301. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  302. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  303. package/src/memory/migrations/index.ts +6 -0
  304. package/src/memory/migrations/run-migrations.ts +41 -0
  305. package/src/memory/migrations/validate-migration-state.ts +1 -1
  306. package/src/memory/onboarding-events-store.ts +3 -3
  307. package/src/memory/schema/contacts.ts +0 -5
  308. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  309. package/src/memory/skill-loaded-events-store.ts +2 -2
  310. package/src/memory/tool-executed-events-store.test.ts +7 -7
  311. package/src/memory/turn-trace-store.test.ts +736 -0
  312. package/src/memory/turn-trace-store.ts +364 -0
  313. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  314. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  315. package/src/memory/v2/consolidation-job.ts +2 -2
  316. package/src/memory/v2/skill-content.ts +25 -7
  317. package/src/memory/v2/skill-store.ts +7 -1
  318. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  319. package/src/memory/v3-eval/eval-packets.ts +546 -0
  320. package/src/messaging/providers/slack/adapter.ts +1 -1
  321. package/src/messaging/providers/slack/api.ts +31 -0
  322. package/src/messaging/providers/slack/send.test.ts +114 -2
  323. package/src/messaging/providers/slack/send.ts +30 -7
  324. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  325. package/src/messaging/providers/slack/withdraw.ts +161 -0
  326. package/src/notifications/AGENTS.md +2 -0
  327. package/src/notifications/access-request-copy.ts +72 -59
  328. package/src/notifications/adapters/shared.ts +29 -0
  329. package/src/notifications/adapters/slack.ts +58 -103
  330. package/src/notifications/adapters/telegram.ts +2 -20
  331. package/src/notifications/approval-card-data.ts +333 -0
  332. package/src/notifications/broadcaster.ts +16 -3
  333. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  334. package/src/notifications/copy-composer.ts +3 -3
  335. package/src/notifications/decision-engine.ts +4 -2
  336. package/src/notifications/destination-resolver.ts +4 -6
  337. package/src/notifications/guardian-question-mode.ts +10 -0
  338. package/src/notifications/home-feed-side-effect.ts +7 -16
  339. package/src/notifications/notification-utils.ts +19 -20
  340. package/src/notifications/signal.ts +79 -43
  341. package/src/notifications/types.ts +98 -121
  342. package/src/oauth/AGENTS.md +5 -24
  343. package/src/permissions/checker.test.ts +51 -0
  344. package/src/permissions/checker.ts +185 -26
  345. package/src/permissions/ipc-risk-types.ts +24 -0
  346. package/src/permissions/question-prompter.test.ts +27 -0
  347. package/src/permissions/question-prompter.ts +4 -0
  348. package/src/platform/client.test.ts +119 -0
  349. package/src/platform/client.ts +66 -0
  350. package/src/platform/consent-cache.test.ts +267 -0
  351. package/src/platform/consent-cache.ts +174 -0
  352. package/src/plugin-api/constants.ts +1 -1
  353. package/src/plugin-api/index.ts +33 -1
  354. package/src/plugin-api/model-profiles.ts +33 -0
  355. package/src/plugin-api/types.ts +50 -2
  356. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  357. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  358. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  359. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  360. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  361. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  362. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  363. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  364. package/src/plugins/defaults/advisor/config.ts +21 -0
  365. package/src/plugins/defaults/advisor/consult.ts +93 -0
  366. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  367. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  368. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  369. package/src/plugins/defaults/advisor/package.json +14 -0
  370. package/src/plugins/defaults/advisor/steering.ts +67 -0
  371. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  372. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  373. package/src/plugins/defaults/index.ts +60 -0
  374. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  375. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  376. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  377. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  378. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  379. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  380. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  381. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  382. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  384. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  385. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  386. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  387. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  388. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  389. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  390. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  391. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  392. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  393. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  394. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  395. package/src/prompts/persona-resolver.ts +14 -4
  396. package/src/prompts/templates/system-sections.ts +7 -2
  397. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  398. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  399. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  400. package/src/providers/atlascloud/client.ts +85 -0
  401. package/src/providers/fetch-provider-catalog.ts +85 -0
  402. package/src/providers/inference/adapter-factory.ts +3 -0
  403. package/src/providers/model-catalog.ts +58 -0
  404. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  405. package/src/providers/openai/chat-completions-provider.ts +7 -0
  406. package/src/providers/openai/responses-provider.ts +10 -0
  407. package/src/providers/provider-send-message.ts +11 -3
  408. package/src/providers/retry.ts +53 -12
  409. package/src/providers/search-provider-catalog.ts +10 -0
  410. package/src/providers/weak-open-model.ts +22 -0
  411. package/src/runtime/AGENTS.md +0 -1
  412. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  413. package/src/runtime/__tests__/client-health.test.ts +44 -0
  414. package/src/runtime/access-request-helper.ts +21 -53
  415. package/src/runtime/actor-trust-resolver.ts +59 -63
  416. package/src/runtime/agent-wake.ts +52 -0
  417. package/src/runtime/assistant-event-hub.ts +18 -4
  418. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  419. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  420. package/src/runtime/btw-sidechain.ts +3 -6
  421. package/src/runtime/capabilities.test.ts +120 -0
  422. package/src/runtime/capabilities.ts +197 -0
  423. package/src/runtime/channel-approval-types.ts +22 -45
  424. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  425. package/src/runtime/channel-retry-sweep.ts +1 -0
  426. package/src/runtime/channel-verification-service.ts +3 -3
  427. package/src/runtime/client-health.ts +26 -0
  428. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  429. package/src/runtime/effective-capabilities.test.ts +128 -0
  430. package/src/runtime/effective-capabilities.ts +84 -0
  431. package/src/runtime/guardian-reply-router.ts +106 -21
  432. package/src/runtime/invite-redemption-service.ts +9 -25
  433. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  434. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  435. package/src/runtime/pending-interactions.ts +15 -0
  436. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  437. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  438. package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
  439. package/src/runtime/routes/app-routes.ts +1 -1
  440. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  441. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  442. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  443. package/src/runtime/routes/btw-routes.ts +1 -27
  444. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  445. package/src/runtime/routes/client-routes.ts +10 -0
  446. package/src/runtime/routes/contact-routes.ts +31 -8
  447. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  448. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  449. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  450. package/src/runtime/routes/conversation-routes.ts +39 -14
  451. package/src/runtime/routes/credential-routes.ts +40 -16
  452. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  453. package/src/runtime/routes/events-routes.ts +1 -3
  454. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  455. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  456. package/src/runtime/routes/home-feed-routes.ts +8 -3
  457. package/src/runtime/routes/identity-routes.ts +1 -296
  458. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  459. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  460. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  461. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  462. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  463. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  464. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  465. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  466. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  467. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  468. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  469. package/src/runtime/routes/index.ts +2 -0
  470. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  471. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  472. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  473. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  474. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  475. package/src/runtime/routes/notification-routes.ts +122 -133
  476. package/src/runtime/routes/platform-routes.ts +2 -2
  477. package/src/runtime/routes/plugins-routes.ts +202 -3
  478. package/src/runtime/routes/schedule-routes.ts +0 -22
  479. package/src/runtime/routes/secret-routes.ts +10 -0
  480. package/src/runtime/routes/surface-action-routes.ts +2 -1
  481. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  482. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  483. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  484. package/src/runtime/routes/workflow-routes.ts +131 -29
  485. package/src/runtime/routes/workspace-greetings.ts +55 -0
  486. package/src/runtime/sync/resource-sync-events.ts +1 -11
  487. package/src/runtime/tool-grant-request-helper.ts +18 -16
  488. package/src/runtime/trust-context-resolver.ts +8 -5
  489. package/src/schedule/inference-profile.ts +2 -14
  490. package/src/schedule/schedule-store.ts +1 -1
  491. package/src/schedule/scheduler-types.ts +5 -1
  492. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  493. package/src/security/secret-patterns.ts +3 -0
  494. package/src/subagent/manager.ts +17 -4
  495. package/src/subagent/types.ts +6 -0
  496. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  497. package/src/telemetry/trace-collection-policy.ts +30 -0
  498. package/src/telemetry/types.ts +89 -0
  499. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  500. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  501. package/src/tools/AGENTS.md +3 -3
  502. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  503. package/src/tools/browser/browser-execution.ts +30 -19
  504. package/src/tools/document/document-tool.ts +2 -3
  505. package/src/tools/executor.ts +5 -3
  506. package/src/tools/host-terminal/host-shell.ts +5 -4
  507. package/src/tools/memory/register.ts +2 -2
  508. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  509. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  510. package/src/tools/network/web-fetch.ts +372 -1
  511. package/src/tools/network/web-search-error.ts +1 -1
  512. package/src/tools/network/web-search.ts +213 -10
  513. package/src/tools/permission-checker.ts +4 -3
  514. package/src/tools/registry.ts +20 -0
  515. package/src/tools/schedule/create.ts +7 -12
  516. package/src/tools/schedule/update.ts +4 -11
  517. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  518. package/src/tools/side-effects.ts +2 -17
  519. package/src/tools/skills/execute.ts +33 -0
  520. package/src/tools/subagent/spawn.ts +61 -12
  521. package/src/tools/terminal/shell.ts +10 -4
  522. package/src/tools/tool-approval-handler.ts +18 -13
  523. package/src/tools/tool-manifest.ts +0 -2
  524. package/src/tools/types.ts +9 -0
  525. package/src/tools/ui-surface/definitions.ts +64 -3
  526. package/src/tools/verification-control-plane-policy.ts +3 -1
  527. package/src/tools/workflows/run-workflow.test.ts +8 -18
  528. package/src/tools/workflows/run-workflow.ts +1 -0
  529. package/src/util/disk-usage.ts +78 -23
  530. package/src/util/platform.ts +10 -3
  531. package/src/watcher/telemetry.ts +2 -2
  532. package/src/workflows/capabilities.ts +2 -3
  533. package/src/workflows/engine.test.ts +175 -1
  534. package/src/workflows/engine.ts +82 -0
  535. package/src/workflows/journal-store.test.ts +70 -0
  536. package/src/workflows/journal-store.ts +18 -3
  537. package/src/workflows/run-manager.test.ts +171 -28
  538. package/src/workflows/run-manager.ts +66 -24
  539. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  540. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  541. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  542. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  543. package/src/workspace/migrations/registry.ts +8 -0
  544. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  545. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  546. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  547. package/src/__tests__/credential-vault.test.ts +0 -1706
  548. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  549. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  550. package/src/cli/commands/__tests__/task.test.ts +0 -914
  551. package/src/cli/commands/task.ts +0 -771
  552. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  553. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  554. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  555. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  556. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  557. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  558. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  559. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  560. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  561. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  562. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  563. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  564. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  565. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  566. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  567. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  568. package/src/memory/preloaded-apps.ts +0 -116
  569. package/src/notifications/tool-approval-copy.ts +0 -142
  570. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  571. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  572. package/src/tools/credentials/vault.ts +0 -712
@@ -0,0 +1,72 @@
1
+ import { getLogger } from "../../util/logger.js";
2
+ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
3
+
4
+ const log = getLogger("migration-291");
5
+
6
+ /**
7
+ * Restores original platform-provided casing into address from external_user_id
8
+ * for channels where the raw platform ID is the canonical identity (Slack,
9
+ * Telegram, etc.). Phone and WhatsApp channels are excluded because their
10
+ * canonical form (E.164) may differ from the raw external_user_id.
11
+ *
12
+ * Idempotent: rows where address already equals external_user_id are no-ops.
13
+ */
14
+ export function migrateContactChannelsRenormalizeAddresses(
15
+ database: DrizzleDb,
16
+ ): void {
17
+ const raw = getSqliteFrom(database);
18
+
19
+ // A later migration drops external_user_id; once it has run there is nothing
20
+ // to renormalize from. This step re-runs on every startup, so skip when the
21
+ // column is absent rather than referencing it and failing.
22
+ const cols = raw.prepare("PRAGMA table_info(contact_channels)").all() as {
23
+ name: string;
24
+ }[];
25
+ if (!cols.some((c) => c.name === "external_user_id")) {
26
+ return;
27
+ }
28
+
29
+ // Remove rows that would block normalization due to cross-column collisions.
30
+ raw.run(/*sql*/ `
31
+ DELETE FROM contact_channels
32
+ WHERE external_user_id IS NULL
33
+ AND id IN (
34
+ SELECT blocker.id
35
+ FROM contact_channels AS blocker
36
+ INNER JOIN contact_channels AS normalizer
37
+ ON normalizer.type = blocker.type
38
+ AND normalizer.external_user_id = blocker.address COLLATE NOCASE
39
+ AND normalizer.address != normalizer.external_user_id
40
+ AND normalizer.external_user_id IS NOT NULL
41
+ AND normalizer.id != blocker.id
42
+ )
43
+ `);
44
+
45
+ // Non-phone, non-email channels: restore original platform casing from
46
+ // external_user_id. Phone/WhatsApp are excluded because their canonical
47
+ // form (E.164 with '+' prefix) may differ from the raw external_user_id.
48
+ const nonEmailResult = raw.run(/*sql*/ `
49
+ UPDATE OR IGNORE contact_channels
50
+ SET address = external_user_id
51
+ WHERE external_user_id IS NOT NULL
52
+ AND address != external_user_id
53
+ AND type NOT IN ('email', 'phone', 'whatsapp')
54
+ `);
55
+
56
+ // Email channels: ensure address is lowercased (canonical per RFC 5321).
57
+ const emailResult = raw.run(/*sql*/ `
58
+ UPDATE OR IGNORE contact_channels
59
+ SET address = LOWER(external_user_id)
60
+ WHERE type = 'email'
61
+ AND external_user_id IS NOT NULL
62
+ AND address != LOWER(external_user_id)
63
+ `);
64
+
65
+ const totalChanges = nonEmailResult.changes + emailResult.changes;
66
+ if (totalChanges > 0) {
67
+ log.info(
68
+ { rowsUpdated: totalChanges },
69
+ "Re-normalized contact_channels addresses from external_user_id",
70
+ );
71
+ }
72
+ }
@@ -0,0 +1,67 @@
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 * as schema from "../schema.js";
7
+ import { migrateScheduleDefaultNoReuseConversation } from "./292-schedule-default-no-reuse-conversation.js";
8
+
9
+ function createTestDb() {
10
+ const sqlite = new Database(":memory:");
11
+ // Post-210 shape: reuse_conversation column present (trimmed to the columns
12
+ // the migration and assertions touch).
13
+ sqlite.exec(/*sql*/ `
14
+ CREATE TABLE cron_jobs (
15
+ id TEXT PRIMARY KEY,
16
+ name TEXT NOT NULL,
17
+ message TEXT NOT NULL,
18
+ next_run_at INTEGER NOT NULL,
19
+ created_by TEXT NOT NULL,
20
+ created_at INTEGER NOT NULL,
21
+ updated_at INTEGER NOT NULL,
22
+ reuse_conversation INTEGER NOT NULL DEFAULT 0
23
+ )
24
+ `);
25
+ return { sqlite, db: drizzle(sqlite, { schema }) };
26
+ }
27
+
28
+ function insertJob(sqlite: Database, id: string, reuse: number) {
29
+ sqlite
30
+ .query(
31
+ /*sql*/ `INSERT INTO cron_jobs
32
+ (id, name, message, next_run_at, created_by, created_at, updated_at, reuse_conversation)
33
+ VALUES (?, ?, 'msg', 1000, 'agent', 1000, 1000, ?)`,
34
+ )
35
+ .run(id, id, reuse);
36
+ }
37
+
38
+ function reuseValue(sqlite: Database, id: string): number {
39
+ return (
40
+ sqlite
41
+ .query("SELECT reuse_conversation FROM cron_jobs WHERE id = ?")
42
+ .get(id) as { reuse_conversation: number }
43
+ ).reuse_conversation;
44
+ }
45
+
46
+ describe("migration 292: schedules default to no conversation reuse", () => {
47
+ test("flips existing reuse_conversation=1 rows to 0", () => {
48
+ const { sqlite, db } = createTestDb();
49
+ insertJob(sqlite, "reusing", 1);
50
+ insertJob(sqlite, "already-fresh", 0);
51
+
52
+ migrateScheduleDefaultNoReuseConversation(db);
53
+
54
+ expect(reuseValue(sqlite, "reusing")).toBe(0);
55
+ expect(reuseValue(sqlite, "already-fresh")).toBe(0);
56
+ });
57
+
58
+ test("is idempotent — re-run is a no-op", () => {
59
+ const { sqlite, db } = createTestDb();
60
+ insertJob(sqlite, "reusing", 1);
61
+
62
+ migrateScheduleDefaultNoReuseConversation(db);
63
+ expect(() => migrateScheduleDefaultNoReuseConversation(db)).not.toThrow();
64
+
65
+ expect(reuseValue(sqlite, "reusing")).toBe(0);
66
+ });
67
+ });
@@ -0,0 +1,25 @@
1
+ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
2
+
3
+ /**
4
+ * Disable conversation reuse on all existing schedules.
5
+ *
6
+ * Recurring schedules previously defaulted to `reuse_conversation = 1`, so
7
+ * every fire appended to one long-lived conversation. That unbounded,
8
+ * self-similar transcript is a drift hazard for weaker models (it primes them
9
+ * to repeat or extend the prior run) and grows per-fire token cost without
10
+ * adding correctness — durable cross-run state already lives in workspace files
11
+ * and memory. Schedules now default to a fresh conversation per fire; this
12
+ * aligns existing rows with that contract.
13
+ *
14
+ * Idempotent: the guarded UPDATE matches nothing once every row is already 0.
15
+ */
16
+ export function migrateScheduleDefaultNoReuseConversation(
17
+ database: DrizzleDb,
18
+ ): void {
19
+ const raw = getSqliteFrom(database);
20
+ raw
21
+ .query(
22
+ /*sql*/ `UPDATE cron_jobs SET reuse_conversation = 0 WHERE reuse_conversation != 0`,
23
+ )
24
+ .run();
25
+ }
@@ -0,0 +1,32 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ /**
5
+ * Add nullable `input_tokens` / `output_tokens` columns to `workflow_journal`.
6
+ *
7
+ * Persists per-leaf token usage so the journal route can attribute usage to each
8
+ * leaf. The client then computes run-level token metrics from the per-leaf sum
9
+ * (a single source of truth), counting each leaf exactly once regardless of
10
+ * whether its usage arrives via a live `leaf_finished` event or a journal
11
+ * backfill — which avoids the undercount that arose when a mid-run journal
12
+ * aggregate counted a leaf the journal could not itself attribute.
13
+ *
14
+ * Nullable — legacy rows and non-completed leaves (failures, nested
15
+ * `workflow`-kind entries) stay NULL and contribute zero to the sum.
16
+ *
17
+ * Idempotent — each ALTER is wrapped so a re-run (column already present) is a
18
+ * no-op.
19
+ */
20
+ export function migrateWorkflowJournalLeafTokens(database: DrizzleDb): void {
21
+ const raw = getSqliteFrom(database);
22
+ try {
23
+ raw.exec(`ALTER TABLE workflow_journal ADD COLUMN input_tokens INTEGER`);
24
+ } catch {
25
+ /* Column already exists */
26
+ }
27
+ try {
28
+ raw.exec(`ALTER TABLE workflow_journal ADD COLUMN output_tokens INTEGER`);
29
+ } catch {
30
+ /* Column already exists */
31
+ }
32
+ }
@@ -0,0 +1,31 @@
1
+ import { getLogger } from "../../util/logger.js";
2
+ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
3
+
4
+ const log = getLogger("migration-294");
5
+
6
+ /**
7
+ * Drops the `external_user_id` column and its index from `contact_channels`.
8
+ *
9
+ * `address` is the single canonical identity column; `external_user_id` is
10
+ * redundant. The index `idx_contact_channels_type_ext_user` must be dropped
11
+ * first — SQLite refuses to drop a column referenced by an index.
12
+ *
13
+ * Idempotent: skips if the column has already been dropped.
14
+ */
15
+ export function migrateDropExternalUserId(database: DrizzleDb): void {
16
+ const raw = getSqliteFrom(database);
17
+
18
+ const cols = raw.prepare("PRAGMA table_info(contact_channels)").all() as {
19
+ name: string;
20
+ }[];
21
+ const hasColumn = cols.some((c) => c.name === "external_user_id");
22
+
23
+ if (!hasColumn) {
24
+ log.info("external_user_id column already absent — skipping");
25
+ return;
26
+ }
27
+
28
+ raw.run("DROP INDEX IF EXISTS idx_contact_channels_type_ext_user");
29
+ raw.run("ALTER TABLE contact_channels DROP COLUMN external_user_id");
30
+ log.info("Dropped external_user_id column from contact_channels");
31
+ }
@@ -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
  });