@vellumai/assistant 0.8.12 → 0.9.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 (442) hide show
  1. package/AGENTS.md +0 -14
  2. package/ARCHITECTURE.md +45 -45
  3. package/README.md +1 -1
  4. package/bun.lock +200 -154
  5. package/docs/architecture/integrations.md +3 -3
  6. package/docs/architecture/memory.md +2 -2
  7. package/docs/architecture/security.md +10 -10
  8. package/docs/runbook-trusted-contacts.md +12 -12
  9. package/docs/skills.md +6 -6
  10. package/docs/workflows-testing.md +221 -0
  11. package/docs/workflows.md +510 -0
  12. package/examples/plugins/echo/README.md +5 -5
  13. package/knip.json +2 -0
  14. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
  15. package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
  16. package/openapi.yaml +7197 -5708
  17. package/package.json +8 -4
  18. package/scripts/generate-openapi.ts +66 -114
  19. package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
  20. package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
  21. package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
  22. package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  24. package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
  25. package/src/__tests__/app-compiler.test.ts +7 -1
  26. package/src/__tests__/app-dir-path-guard.test.ts +27 -3
  27. package/src/__tests__/app-executors.test.ts +43 -0
  28. package/src/__tests__/approval-cascade.test.ts +0 -5
  29. package/src/__tests__/approval-routes-http.test.ts +91 -0
  30. package/src/__tests__/assistant-stream-state.test.ts +107 -0
  31. package/src/__tests__/browser-fill-credential.test.ts +3 -3
  32. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
  33. package/src/__tests__/compaction-events.test.ts +63 -7
  34. package/src/__tests__/compaction-trail-store.test.ts +74 -1
  35. package/src/__tests__/compaction.benchmark.test.ts +63 -41
  36. package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
  37. package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
  39. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
  40. package/src/__tests__/conversation-history-web-search.test.ts +7 -0
  41. package/src/__tests__/conversation-process-callsite.test.ts +0 -5
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
  43. package/src/__tests__/conversation-queue.test.ts +0 -5
  44. package/src/__tests__/conversation-slash-queue.test.ts +0 -5
  45. package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
  46. package/src/__tests__/conversation-speed-override.test.ts +0 -5
  47. package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
  48. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  49. package/src/__tests__/conversation-usage.test.ts +2 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
  52. package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
  53. package/src/__tests__/credential-broker-server-use.test.ts +2 -2
  54. package/src/__tests__/credential-broker.test.ts +1 -1
  55. package/src/__tests__/credential-prompt-route.test.ts +417 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault.test.ts +37 -0
  58. package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
  59. package/src/__tests__/dynamic-page-surface.test.ts +219 -0
  60. package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
  61. package/src/__tests__/gateway-flag-listener.test.ts +24 -7
  62. package/src/__tests__/guardian-action-sweep.test.ts +56 -219
  63. package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
  64. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  65. package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
  66. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
  67. package/src/__tests__/llm-resolver.test.ts +115 -0
  68. package/src/__tests__/managed-profile-guard.test.ts +6 -5
  69. package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
  70. package/src/__tests__/media-generate-image.test.ts +20 -9
  71. package/src/__tests__/mock-gateway-ipc.ts +23 -0
  72. package/src/__tests__/model-intents.test.ts +1 -1
  73. package/src/__tests__/normalize-onboarding.test.ts +26 -0
  74. package/src/__tests__/notification-decision-strategy.test.ts +4 -2
  75. package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
  76. package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
  77. package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
  78. package/src/__tests__/require-fresh-approval.test.ts +425 -1
  79. package/src/__tests__/resolve-app-id.test.ts +56 -0
  80. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
  81. package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
  82. package/src/__tests__/schedule-routes.test.ts +257 -4
  83. package/src/__tests__/schedule-store.test.ts +60 -0
  84. package/src/__tests__/schedule-tools.test.ts +247 -2
  85. package/src/__tests__/skill-execute-input.test.ts +85 -0
  86. package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
  87. package/src/__tests__/skills.test.ts +3 -3
  88. package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
  89. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  90. package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
  91. package/src/__tests__/system-prompt.test.ts +1 -1
  92. package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
  93. package/src/__tests__/task-scheduler.test.ts +299 -0
  94. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
  95. package/src/__tests__/tool-result-spool.test.ts +3 -1
  96. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
  97. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
  98. package/src/agent/compaction-circuit.ts +11 -0
  99. package/src/agent/loop.ts +181 -12
  100. package/src/api/constants/call-sites.ts +12 -0
  101. package/src/api/events/assistant-thinking-delta.ts +10 -0
  102. package/src/api/events/usage-update.ts +7 -0
  103. package/src/api/index.ts +4 -1
  104. package/src/api/responses/memory-v3-selection-log.ts +18 -11
  105. package/src/approvals/approval-primitive.ts +2 -2
  106. package/src/background-wake/background-wake-routes.test.ts +5 -2
  107. package/src/bundler/compiler-tools.ts +1 -1
  108. package/src/bundler/package-resolver.ts +0 -1
  109. package/src/calls/call-domain.ts +1 -1
  110. package/src/calls/guardian-action-sweep.ts +16 -93
  111. package/src/cli/AGENTS.md +4 -0
  112. package/src/cli/commands/__tests__/schedules.test.ts +430 -1
  113. package/src/cli/commands/credentials.ts +28 -24
  114. package/src/cli/commands/image-generation.ts +23 -9
  115. package/src/cli/commands/notifications.ts +1 -1
  116. package/src/cli/commands/plugins.ts +89 -46
  117. package/src/cli/commands/schedules.ts +384 -11
  118. package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
  119. package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
  120. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
  121. package/src/cli/lib/inspect-plugin.ts +62 -1
  122. package/src/cli/lib/install-from-github.ts +52 -5
  123. package/src/cli/lib/upgrade-plugin.ts +18 -0
  124. package/src/config/__tests__/workflows-schema.test.ts +60 -0
  125. package/src/config/bundled-skills/acp/SKILL.md +2 -2
  126. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  127. package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
  128. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
  129. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
  130. package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
  131. package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
  132. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
  133. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
  134. package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
  135. package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
  136. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
  137. package/src/config/bundled-skills/schedule/SKILL.md +7 -2
  138. package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
  139. package/src/config/bundled-skills/workflows/SKILL.md +214 -0
  140. package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
  141. package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
  142. package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
  143. package/src/config/bundled-tool-registry.ts +14 -2
  144. package/src/config/call-site-defaults.ts +5 -0
  145. package/src/config/feature-flag-registry.json +12 -4
  146. package/src/config/llm-context-resolution.ts +8 -0
  147. package/src/config/llm-resolver.ts +30 -0
  148. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
  149. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
  150. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
  151. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
  152. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
  153. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
  154. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
  155. package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
  156. package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
  157. package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
  158. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
  159. package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
  160. package/src/config/schema.ts +2 -0
  161. package/src/config/schemas/call-site-catalog.ts +7 -0
  162. package/src/config/schemas/heartbeat.ts +4 -1
  163. package/src/config/schemas/llm.ts +33 -26
  164. package/src/config/schemas/memory-retrospective.ts +19 -0
  165. package/src/config/schemas/platform.ts +8 -0
  166. package/src/config/schemas/services.ts +5 -2
  167. package/src/config/schemas/workflows.ts +42 -0
  168. package/src/config/skills.ts +3 -3
  169. package/src/context/compactor.ts +273 -39
  170. package/src/context/post-turn-tool-result-truncation.ts +23 -6
  171. package/src/context/tool-result-spool.ts +12 -17
  172. package/src/credential-execution/executable-discovery.ts +1 -1
  173. package/src/credential-execution/process-manager.ts +37 -3
  174. package/src/credential-execution/prompted-credential.ts +205 -0
  175. package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
  176. package/src/daemon/conversation-process.ts +11 -2
  177. package/src/daemon/conversation-surfaces.ts +167 -3
  178. package/src/daemon/conversation-tool-setup.ts +103 -26
  179. package/src/daemon/conversation-usage.ts +2 -0
  180. package/src/daemon/conversation.ts +115 -11
  181. package/src/daemon/handlers/shared.ts +26 -14
  182. package/src/daemon/host-cu-proxy.ts +15 -12
  183. package/src/daemon/host-file-proxy.ts +15 -12
  184. package/src/daemon/host-transfer-proxy.ts +30 -24
  185. package/src/daemon/lifecycle.ts +40 -3
  186. package/src/daemon/message-protocol.ts +3 -0
  187. package/src/daemon/message-types/messages.ts +2 -10
  188. package/src/daemon/message-types/workflows.ts +49 -0
  189. package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
  190. package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
  191. package/src/daemon/process-message.ts +6 -0
  192. package/src/daemon/tool-setup-types.ts +57 -0
  193. package/src/daemon/wake-conversation-ops.ts +18 -0
  194. package/src/heartbeat/heartbeat-run-store.ts +8 -2
  195. package/src/home/feed-types.ts +1 -1
  196. package/src/ipc/gateway-flag-listener.ts +28 -6
  197. package/src/mcp/mcp-auth-state.ts +8 -20
  198. package/src/media/__tests__/image-models.test.ts +57 -0
  199. package/src/media/image-models.ts +66 -0
  200. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
  201. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
  202. package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
  203. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
  204. package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
  205. package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
  206. package/src/memory/auto-analysis-enqueue.ts +14 -1
  207. package/src/memory/compaction-log-store-clickhouse.ts +6 -4
  208. package/src/memory/conversation-crud.ts +9 -2
  209. package/src/memory/conversation-disk-view.ts +1 -1
  210. package/src/memory/conversation-queries.ts +22 -7
  211. package/src/memory/db-init.ts +20 -0
  212. package/src/memory/db-maintenance.ts +16 -0
  213. package/src/memory/embedding-runtime-manager.ts +1 -1
  214. package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
  215. package/src/memory/llm-request-log-source-local.ts +19 -1
  216. package/src/memory/llm-request-log-source.ts +35 -6
  217. package/src/memory/llm-request-log-store.ts +90 -2
  218. package/src/memory/memory-retrospective-constants.ts +9 -0
  219. package/src/memory/memory-retrospective-enqueue.ts +3 -6
  220. package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
  221. package/src/memory/memory-retrospective-job.ts +500 -208
  222. package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
  223. package/src/memory/memory-retrospective-state.ts +85 -2
  224. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
  225. package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
  226. package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
  227. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
  228. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
  229. package/src/memory/migrations/284-workflow-runs.ts +51 -0
  230. package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
  231. package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
  232. package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
  233. package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
  234. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
  235. package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
  236. package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
  237. package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
  238. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
  239. package/src/memory/migrations/index.ts +10 -0
  240. package/src/memory/preloaded-apps.ts +116 -0
  241. package/src/memory/schema/infrastructure.ts +4 -0
  242. package/src/memory/schema/memory-core.ts +4 -0
  243. package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
  244. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
  245. package/src/memory/v2/__tests__/page-store.test.ts +13 -2
  246. package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
  247. package/src/memory/v2/frontmatter-sweep.ts +7 -6
  248. package/src/memory/v2/page-store.ts +4 -3
  249. package/src/memory/v2/qdrant.ts +42 -3
  250. package/src/memory/v2/types.ts +16 -10
  251. package/src/messaging/draft-store.ts +1 -1
  252. package/src/notifications/access-request-copy.ts +200 -113
  253. package/src/notifications/adapters/slack.ts +250 -111
  254. package/src/notifications/adapters/telegram.ts +7 -44
  255. package/src/notifications/approval-card-builder.ts +93 -0
  256. package/src/notifications/broadcaster.ts +74 -0
  257. package/src/notifications/conversation-pairing.ts +8 -6
  258. package/src/notifications/copy-composer.ts +32 -26
  259. package/src/notifications/decision-engine.ts +59 -7
  260. package/src/notifications/guardian-question-mode.ts +145 -155
  261. package/src/notifications/home-feed-side-effect.ts +28 -11
  262. package/src/notifications/notification-utils.ts +66 -0
  263. package/src/notifications/signal.ts +6 -0
  264. package/src/notifications/tool-approval-copy.ts +142 -0
  265. package/src/notifications/types.ts +19 -0
  266. package/src/permissions/threshold.ts +11 -0
  267. package/src/plugin-api/types.ts +16 -4
  268. package/src/plugins/defaults/compaction/window-manager.ts +44 -0
  269. package/src/plugins/defaults/index.ts +46 -0
  270. package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
  271. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
  272. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
  273. package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
  274. package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
  275. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
  276. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
  277. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
  278. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
  282. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
  283. package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
  284. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
  285. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
  286. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
  287. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
  288. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
  289. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
  290. package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
  291. package/src/prompts/__tests__/system-prompt.test.ts +100 -1
  292. package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
  293. package/src/prompts/normalize-onboarding.ts +2 -0
  294. package/src/prompts/persona-resolver.ts +3 -0
  295. package/src/prompts/system-prompt.ts +51 -2
  296. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
  297. package/src/prompts/templates/system-sections.ts +8 -3
  298. package/src/providers/call-site-routing.ts +6 -3
  299. package/src/providers/fireworks/client.ts +3 -0
  300. package/src/providers/inference/auth.ts +52 -46
  301. package/src/providers/model-intents.ts +2 -2
  302. package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
  303. package/src/providers/openai/chat-completions-provider.ts +47 -9
  304. package/src/providers/openai/coerce-object-args.ts +104 -0
  305. package/src/providers/retry.ts +8 -5
  306. package/src/providers/types.ts +10 -0
  307. package/src/runtime/__tests__/agent-wake.test.ts +629 -7
  308. package/src/runtime/access-request-helper.ts +16 -9
  309. package/src/runtime/agent-wake.ts +302 -51
  310. package/src/runtime/assistant-stream-state.ts +141 -8
  311. package/src/runtime/background-job-runner.ts +9 -0
  312. package/src/runtime/channel-approval-types.ts +1 -0
  313. package/src/runtime/channel-invite-transports/telegram.ts +6 -5
  314. package/src/runtime/channel-invite-transports/voice.ts +2 -2
  315. package/src/runtime/channel-invite-types.ts +4 -2
  316. package/src/runtime/channel-retry-sweep.ts +19 -41
  317. package/src/runtime/finalize-event-delivery.ts +72 -0
  318. package/src/runtime/guardian-action-message-composer.ts +0 -54
  319. package/src/runtime/http-server.ts +6 -14
  320. package/src/runtime/http-types.ts +0 -1
  321. package/src/runtime/message-composer-types.ts +0 -9
  322. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
  323. package/src/runtime/middleware/auth.ts +27 -3
  324. package/src/runtime/middleware/rate-limiter.ts +28 -1
  325. package/src/runtime/migrations/vbundle-builder.ts +6 -5
  326. package/src/runtime/pending-interactions.ts +20 -1
  327. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
  328. package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
  329. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
  330. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
  331. package/src/runtime/routes/approval-routes.ts +35 -8
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
  333. package/src/runtime/routes/btw-routes.ts +37 -1
  334. package/src/runtime/routes/channel-delivery-routes.ts +11 -7
  335. package/src/runtime/routes/channel-route-definitions.ts +3 -0
  336. package/src/runtime/routes/channel-route-shared.ts +3 -1
  337. package/src/runtime/routes/consolidation-routes.ts +17 -13
  338. package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
  339. package/src/runtime/routes/conversation-list-routes.ts +41 -4
  340. package/src/runtime/routes/conversation-query-routes.ts +196 -11
  341. package/src/runtime/routes/conversation-routes.ts +15 -1
  342. package/src/runtime/routes/credential-prompt-routes.ts +39 -17
  343. package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
  344. package/src/runtime/routes/heartbeat-routes.ts +18 -13
  345. package/src/runtime/routes/image-generation-routes.ts +20 -2
  346. package/src/runtime/routes/inbound-message-handler.ts +13 -12
  347. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
  348. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
  349. package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
  350. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
  351. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
  352. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
  353. package/src/runtime/routes/index.ts +6 -0
  354. package/src/runtime/routes/log-export/AGENTS.md +1 -1
  355. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  356. package/src/runtime/routes/migration-routes.ts +5 -9
  357. package/src/runtime/routes/plugins-routes.ts +26 -0
  358. package/src/runtime/routes/ps-routes.ts +10 -8
  359. package/src/runtime/routes/retrospective-routes.ts +235 -0
  360. package/src/runtime/routes/runs-pagination.ts +75 -0
  361. package/src/runtime/routes/schedule-routes.ts +367 -59
  362. package/src/runtime/routes/sounds-config-routes.ts +239 -0
  363. package/src/runtime/routes/surface-action-routes.ts +84 -4
  364. package/src/runtime/routes/workflow-routes.test.ts +372 -0
  365. package/src/runtime/routes/workflow-routes.ts +363 -0
  366. package/src/runtime/routes/workspace-routes.test.ts +61 -1
  367. package/src/runtime/routes/workspace-routes.ts +26 -1
  368. package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
  369. package/src/runtime/services/analyze-conversation.ts +26 -13
  370. package/src/schedule/inference-profile.ts +28 -0
  371. package/src/schedule/schedule-store.ts +152 -4
  372. package/src/schedule/scheduler-types.ts +6 -0
  373. package/src/schedule/scheduler.ts +96 -0
  374. package/src/security/secret-allowlist.ts +1 -1
  375. package/src/skills/path-classifier.ts +1 -1
  376. package/src/tools/apps/executors.ts +23 -0
  377. package/src/tools/apps/resolve-app-id.ts +42 -0
  378. package/src/tools/browser/browser-execution.ts +9 -11
  379. package/src/tools/credentials/broker.ts +4 -4
  380. package/src/tools/credentials/vault.ts +26 -137
  381. package/src/tools/executor.ts +69 -0
  382. package/src/tools/flag-gated-tools.test.ts +76 -0
  383. package/src/tools/permission-checker.ts +8 -1
  384. package/src/tools/registry.ts +51 -0
  385. package/src/tools/schedule/create.ts +77 -1
  386. package/src/tools/schedule/list.ts +1 -0
  387. package/src/tools/schedule/update.ts +73 -1
  388. package/src/tools/skills/execute.ts +56 -0
  389. package/src/tools/terminal/shell.ts +1 -1
  390. package/src/tools/ui-surface/definitions.ts +91 -2
  391. package/src/tools/workflows/manage-workflows.ts +183 -0
  392. package/src/tools/workflows/run-workflow.test.ts +442 -0
  393. package/src/tools/workflows/run-workflow.ts +88 -0
  394. package/src/types/onboarding-context.ts +2 -0
  395. package/src/usage/attribution.ts +24 -0
  396. package/src/util/canonicalize-identity.ts +12 -3
  397. package/src/util/platform.ts +17 -17
  398. package/src/watcher/__tests__/engine.test.ts +24 -0
  399. package/src/watcher/__tests__/telemetry.test.ts +135 -0
  400. package/src/watcher/engine.ts +7 -0
  401. package/src/watcher/telemetry.ts +74 -0
  402. package/src/workflows/capabilities.test.ts +365 -0
  403. package/src/workflows/capabilities.ts +359 -0
  404. package/src/workflows/deterministic-stringify.ts +27 -0
  405. package/src/workflows/engine-integration.test.ts +656 -0
  406. package/src/workflows/engine.test.ts +1144 -0
  407. package/src/workflows/engine.ts +1078 -0
  408. package/src/workflows/fanout-load.test.ts +168 -0
  409. package/src/workflows/journal-store.test.ts +369 -0
  410. package/src/workflows/journal-store.ts +470 -0
  411. package/src/workflows/leaf-runner.test.ts +704 -0
  412. package/src/workflows/leaf-runner.ts +589 -0
  413. package/src/workflows/library.test.ts +134 -0
  414. package/src/workflows/library.ts +124 -0
  415. package/src/workflows/run-manager.test.ts +711 -0
  416. package/src/workflows/run-manager.ts +593 -0
  417. package/src/workflows/sandbox-escape.test.ts +339 -0
  418. package/src/workflows/sandbox.test.ts +251 -0
  419. package/src/workflows/sandbox.ts +447 -0
  420. package/src/workspace/adaptive-thinking-repair.ts +33 -11
  421. package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
  422. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
  423. package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
  424. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
  425. package/src/workspace/migrations/031-drop-user-md.ts +1 -4
  426. package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
  427. package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
  428. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
  429. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
  430. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
  431. package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
  432. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
  433. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
  434. package/src/workspace/migrations/registry.ts +6 -0
  435. package/src/workspace/migrations/runner.ts +1 -1
  436. package/tsconfig.json +1 -1
  437. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
  438. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
  439. package/src/__tests__/guardian-action-store.test.ts +0 -106
  440. package/src/daemon/guardian-action-generators.ts +0 -71
  441. package/src/memory/guardian-action-store.ts +0 -484
  442. package/src/runtime/guardian-action-grant-minter.ts +0 -150
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.8.12",
3
+ "version": "0.9.0-staging.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -36,6 +36,7 @@
36
36
  "@agentclientprotocol/sdk": "0.25.0",
37
37
  "@anthropic-ai/sdk": "0.78.0",
38
38
  "@google/genai": "1.45.0",
39
+ "@jitl/quickjs-singlefile-mjs-release-asyncify": "0.32.0",
39
40
  "@modelcontextprotocol/sdk": "1.27.1",
40
41
  "@qdrant/js-client-rest": "1.17.0",
41
42
  "@resvg/resvg-js": "2.6.2",
@@ -60,16 +61,18 @@
60
61
  "openai": "6.29.0",
61
62
  "pino": "9.14.0",
62
63
  "pino-pretty": "13.1.3",
63
- "semver": "7.8.0",
64
64
  "playwright": "1.58.2",
65
65
  "postgres": "3.4.8",
66
+ "quickjs-emscripten": "0.32.0",
66
67
  "rrule": "2.8.1",
68
+ "semver": "7.8.0",
67
69
  "stemmer": "2.0.1",
68
70
  "tar-stream": "3.1.7",
69
71
  "tldts": "7.0.25",
70
72
  "uuid": "11.1.0",
71
73
  "yaml": "2.8.2",
72
- "zod": "4.3.6"
74
+ "zod": "4.3.6",
75
+ "zod-openapi": "5.4.6"
73
76
  },
74
77
  "engines": {
75
78
  "node": ">=20.12.0"
@@ -100,7 +103,8 @@
100
103
  "@types/node": "25.5.0",
101
104
  "@types/semver": "7.5.8",
102
105
  "@types/uuid": "10.0.0",
103
- "drizzle-kit": "0.30.6",
106
+ "ajv": "8.18.0",
107
+ "drizzle-kit": "0.31.10",
104
108
  "eslint": "10.0.3",
105
109
  "eslint-plugin-simple-import-sort": "12.1.1",
106
110
  "fast-check": "4.6.0",
@@ -19,10 +19,16 @@
19
19
  import { readFileSync } from "node:fs";
20
20
  import { readdir, readFile, writeFile } from "node:fs/promises";
21
21
  import { join, resolve } from "node:path";
22
- import { Readable } from "node:stream";
23
22
 
24
23
  import { stringify } from "yaml";
25
24
  import { z } from "zod";
25
+ import type {
26
+ oas31,
27
+ ZodOpenApiOperationObject,
28
+ ZodOpenApiPathsObject,
29
+ ZodOpenApiResponseObject,
30
+ } from "zod-openapi";
31
+ import { createDocument } from "zod-openapi";
26
32
 
27
33
  const ROOT = resolve(import.meta.dir, "..");
28
34
  const ROUTES_DIR = join(ROOT, "src/runtime/routes");
@@ -46,13 +52,13 @@ const RouteQueryParamSchema = z.object({
46
52
  * JSON-Schema-style object for backward compatibility with inline routes.
47
53
  */
48
54
  const RouteBodySchemaSchema = z.any().refine(
49
- (v) =>
55
+ (v): v is z.ZodType | oas31.SchemaObject =>
50
56
  v != null &&
51
57
  typeof v === "object" &&
52
58
  // Zod schema instance (Zod 4 uses _zod branded property)
53
59
  ("_zod" in v ||
54
60
  // Plain JSON Schema fallback
55
- typeof (v as Record<string, unknown>).type === "string"),
61
+ "type" in v),
56
62
  { message: "Expected a Zod schema or a plain JSON Schema object" },
57
63
  );
58
64
 
@@ -111,67 +117,29 @@ const RouteEntrySchema = z.object({
111
117
 
112
118
  type RouteEntry = z.infer<typeof RouteEntrySchema>;
113
119
 
114
- /** JSON Schema representation of a body (for the OpenAPI spec output). */
115
- interface JSONSchemaObject {
116
- type?: string;
117
- properties?: Record<string, unknown>;
118
- required?: string[];
119
- description?: string;
120
- additionalProperties?: boolean;
121
- [key: string]: unknown;
122
- }
120
+ type ContentSchema = z.ZodType | oas31.SchemaObject;
123
121
 
124
- /**
125
- * Recursively strip fields with a `default` from `required[]` on every
126
- * object schema in the tree. Zod 4's `toJSONSchema` (output mode) marks
127
- * defaulted fields as required because the output always carries them,
128
- * but for request bodies the server fills the default when the client
129
- * omits the field — generated clients should not be forced to send it.
130
- */
131
- function dropDefaultedFromRequired(node: unknown): void {
132
- if (node == null || typeof node !== "object") return;
133
- if (Array.isArray(node)) {
134
- for (const item of node) dropDefaultedFromRequired(item);
135
- return;
136
- }
137
- const obj = node as Record<string, unknown>;
138
- const props = obj.properties;
139
- const required = obj.required;
140
- if (Array.isArray(required) && props != null && typeof props === "object") {
141
- const propsRecord = props as Record<string, unknown>;
142
- const filtered = required.filter((name) => {
143
- if (typeof name !== "string") return true;
144
- const prop = propsRecord[name];
145
- return !(
146
- prop != null &&
147
- typeof prop === "object" &&
148
- "default" in (prop as Record<string, unknown>)
149
- );
150
- });
151
- if (filtered.length > 0) obj.required = filtered;
152
- else delete obj.required;
122
+ type HttpStatusCode = `${1 | 2 | 3 | 4 | 5}${string}`;
123
+
124
+ function toHttpStatus(status: string): HttpStatusCode {
125
+ if (!/^[1-5]\d{2}$/.test(status)) {
126
+ throw new Error(`Invalid HTTP status code: ${status}`);
153
127
  }
154
- for (const value of Object.values(obj)) dropDefaultedFromRequired(value);
128
+ return status as HttpStatusCode;
155
129
  }
156
130
 
157
- /** Convert a Zod schema or plain JSON Schema object to a JSON Schema object. */
158
- function toJSONSchemaObject(
159
- schema: unknown,
160
- options: { stripRequiredDefaults?: boolean } = {},
161
- ): JSONSchemaObject {
162
- if (schema == null || typeof schema !== "object") return {};
163
- // Zod schema: has _zod branded property
164
- if ("_zod" in (schema as Record<string, unknown>)) {
165
- const converted = z.toJSONSchema(schema as z.ZodType, {
166
- unrepresentable: "any",
167
- });
168
- // z.toJSONSchema may add $schema — strip it for inline embedding
169
- const { $schema: _, ...rest } = converted as Record<string, unknown>;
170
- if (options.stripRequiredDefaults) dropDefaultedFromRequired(rest);
171
- return rest as JSONSchemaObject;
131
+ /**
132
+ * Resolve a schema source to a value suitable for zod-openapi's
133
+ * `schema` field. If it's a Zod schema, pass it through directly (so
134
+ * createDocument can extract components and produce $ref pointers).
135
+ * For plain JSON Schema objects (backward compat), pass as-is —
136
+ * createDocument accepts SchemaObject too.
137
+ */
138
+ function resolveSchemaForDocument(schemaSource: unknown): ContentSchema {
139
+ if (schemaSource == null || typeof schemaSource !== "object") {
140
+ return { type: "object" } satisfies oas31.SchemaObject;
172
141
  }
173
- // Plain JSON Schema object (backward compat for inline/pre-auth routes)
174
- return schema as JSONSchemaObject;
142
+ return schemaSource as ContentSchema;
175
143
  }
176
144
 
177
145
  // ---------------------------------------------------------------------------
@@ -302,46 +270,26 @@ interface OpenApiParameter {
302
270
  description?: string;
303
271
  }
304
272
 
305
- interface OpenApiResponse {
306
- description: string;
307
- content?: Record<string, { schema: JSONSchemaObject }>;
308
- }
309
-
310
- interface OpenApiOperation {
311
- operationId: string;
312
- summary?: string;
313
- description?: string;
314
- tags?: string[];
315
- parameters?: OpenApiParameter[];
316
- requestBody?: {
317
- required: boolean;
318
- content: Record<string, { schema: JSONSchemaObject }>;
319
- };
320
- responses: Record<string, OpenApiResponse>;
321
- }
322
-
323
273
  /**
324
274
  * Resolve a body declaration (request or success response) into its media type
325
275
  * and the schema source to convert. A bare Zod/JSON schema is advertised as
326
276
  * `application/json`; the explicit `{ contentType, schema }` form carries its
327
277
  * own media type (e.g. `application/octet-stream` for binary bodies).
328
278
  */
279
+ function hasContentType(
280
+ body: unknown,
281
+ ): body is { contentType: string; schema: unknown } {
282
+ return typeof body === "object" && body !== null && "contentType" in body;
283
+ }
284
+
329
285
  function resolveBodyContent(body: unknown): {
330
286
  contentType: string;
331
287
  schemaSource: unknown;
332
288
  } {
333
- const hasContentType =
334
- typeof body === "object" && body !== null && "contentType" in body;
335
- return {
336
- contentType: hasContentType
337
- ? (body as { contentType: string }).contentType
338
- : "application/json",
339
- schemaSource: hasContentType ? (body as { schema: unknown }).schema : body,
340
- };
341
- }
342
-
343
- interface OpenApiPathItem {
344
- [method: string]: OpenApiOperation;
289
+ if (hasContentType(body)) {
290
+ return { contentType: body.contentType, schemaSource: body.schema };
291
+ }
292
+ return { contentType: "application/json", schemaSource: body };
345
293
  }
346
294
 
347
295
  /** Derive a tag name from a route module filename (e.g. "secret-routes.ts" → "secrets"). */
@@ -401,14 +349,22 @@ function buildSpec(
401
349
  return a.method.localeCompare(b.method);
402
350
  });
403
351
 
404
- // Build paths object
405
- const paths: Record<string, OpenApiPathItem> = {};
352
+ // Build paths object for zod-openapi's createDocument
353
+ const paths: ZodOpenApiPathsObject = {};
406
354
  for (const route of uniqueRoutes) {
407
355
  if (!paths[route.path]) {
408
356
  paths[route.path] = {};
409
357
  }
410
358
 
411
- const methodLower = route.method.toLowerCase();
359
+ const methodLower = route.method.toLowerCase() as
360
+ | "get"
361
+ | "post"
362
+ | "put"
363
+ | "patch"
364
+ | "delete"
365
+ | "options"
366
+ | "head"
367
+ | "trace";
412
368
  const operationId = route.path.startsWith("/v1/")
413
369
  ? toOperationId(route.endpoint, route.method)
414
370
  : route.path.replace(/^\//, "").replace(/[/{}\-]/g, "_") +
@@ -448,8 +404,8 @@ function buildSpec(
448
404
  // Build the operation. Default success status is 200; async endpoints
449
405
  // that enqueue a job and return immediately set responseStatus: "202"
450
406
  // so the generated spec matches the handler's actual response code.
451
- const successStatus = entry.responseStatus ?? "200";
452
- let successResponse: OpenApiResponse = {
407
+ const successStatus = toHttpStatus(entry.responseStatus ?? "200");
408
+ let successResponse: ZodOpenApiResponseObject = {
453
409
  description: "Successful response",
454
410
  };
455
411
  if (entry.responseBody) {
@@ -459,11 +415,13 @@ function buildSpec(
459
415
  successResponse = {
460
416
  description: "Successful response",
461
417
  content: {
462
- [contentType]: { schema: toJSONSchemaObject(schemaSource) },
418
+ [contentType]: {
419
+ schema: resolveSchemaForDocument(schemaSource),
420
+ },
463
421
  },
464
422
  };
465
423
  }
466
- const operation: OpenApiOperation = {
424
+ const operation: ZodOpenApiOperationObject = {
467
425
  operationId,
468
426
  ...(entry.summary ? { summary: entry.summary } : {}),
469
427
  ...(entry.description ? { description: entry.description } : {}),
@@ -477,10 +435,6 @@ function buildSpec(
477
435
  operation.parameters = parameters;
478
436
  }
479
437
 
480
- // A bare Zod/JSON schema is advertised as `application/json`; the
481
- // explicit `{ contentType, schema }` form lets a route declare a non-JSON
482
- // body (e.g. a raw `application/octet-stream` upload) so the generated SDK
483
- // describes a real body type instead of `never`.
484
438
  if (entry.requestBody) {
485
439
  const { contentType, schemaSource } = resolveBodyContent(
486
440
  entry.requestBody,
@@ -489,9 +443,7 @@ function buildSpec(
489
443
  required: true,
490
444
  content: {
491
445
  [contentType]: {
492
- schema: toJSONSchemaObject(schemaSource, {
493
- stripRequiredDefaults: true,
494
- }),
446
+ schema: resolveSchemaForDocument(schemaSource),
495
447
  },
496
448
  },
497
449
  };
@@ -500,13 +452,13 @@ function buildSpec(
500
452
  // Extra documented response variants (e.g. 502 fetch_failed).
501
453
  if (entry.additionalResponses) {
502
454
  for (const [status, resp] of Object.entries(entry.additionalResponses)) {
503
- operation.responses[status] = {
455
+ operation.responses[toHttpStatus(status)] = {
504
456
  description: resp.description,
505
457
  ...(resp.schema
506
458
  ? {
507
459
  content: {
508
460
  "application/json": {
509
- schema: toJSONSchemaObject(resp.schema),
461
+ schema: resolveSchemaForDocument(resp.schema),
510
462
  },
511
463
  },
512
464
  }
@@ -518,7 +470,7 @@ function buildSpec(
518
470
  paths[route.path][methodLower] = operation;
519
471
  }
520
472
 
521
- return {
473
+ return createDocument({
522
474
  openapi: "3.1.0",
523
475
  info: {
524
476
  title: "Vellum Assistant API",
@@ -533,7 +485,7 @@ function buildSpec(
533
485
  },
534
486
  ],
535
487
  paths,
536
- };
488
+ });
537
489
  }
538
490
 
539
491
  // ---------------------------------------------------------------------------
@@ -563,10 +515,8 @@ async function main() {
563
515
  stringify(spec, { lineWidth: 120 });
564
516
 
565
517
  // Format with prettier so the output matches what the pre-commit hook produces.
566
- // Use a Node.js Readable stream for stdin — Bun.spawn with Blob stdin produces
567
- // empty output on some platforms (Bun 1.3.x Linux sandbox).
568
518
  const prettierProc = Bun.spawn(["bunx", "prettier", "--parser", "yaml"], {
569
- stdin: Readable.from([rawYaml]) as unknown as Blob,
519
+ stdin: new Blob([rawYaml]),
570
520
  stdout: "pipe",
571
521
  stderr: "pipe",
572
522
  });
@@ -661,10 +611,12 @@ async function main() {
661
611
  await writeFile(OUTPUT_PATH, yamlOutput);
662
612
 
663
613
  // Count stats
664
- const pathCount = Object.keys(spec.paths as Record<string, unknown>).length;
665
- const operationCount = Object.values(
666
- spec.paths as Record<string, Record<string, unknown>>,
667
- ).reduce((n, methods) => n + Object.keys(methods).length, 0);
614
+ const paths = spec.paths ?? {};
615
+ const pathCount = Object.keys(paths).length;
616
+ const operationCount = Object.values(paths).reduce(
617
+ (n, methods) => n + Object.keys(methods ?? {}).length,
618
+ 0,
619
+ );
668
620
 
669
621
  console.log(`Generated ${OUTPUT_PATH}`);
670
622
  console.log(` ${pathCount} paths, ${operationCount} operations`);
@@ -0,0 +1,213 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ buildAccessRequestSeedContentBlocks,
5
+ parseAccessRequestPayload,
6
+ } from "../notifications/access-request-copy.js";
7
+
8
+ describe("buildAccessRequestSeedContentBlocks", () => {
9
+ const basePayload: Record<string, unknown> = {
10
+ requestId: "req-123",
11
+ requestCode: "ABC123",
12
+ sourceChannel: "slack",
13
+ conversationExternalId: "C01ABC",
14
+ actorExternalId: "U999",
15
+ actorDisplayName: "Alice",
16
+ actorUsername: "alice",
17
+ senderIdentifier: "U999",
18
+ messagePreview: "Hello, I need help with something",
19
+ messageTs: "1700000000.000100",
20
+ };
21
+
22
+ test("produces a ui_surface block and a text fallback block", () => {
23
+ const blocks = buildAccessRequestSeedContentBlocks(basePayload);
24
+ expect(blocks).toHaveLength(2);
25
+ expect((blocks[0] as Record<string, unknown>).type).toBe("ui_surface");
26
+ expect((blocks[1] as Record<string, unknown>).type).toBe("text");
27
+ });
28
+
29
+ test("card surface has correct surfaceType and surfaceId", () => {
30
+ const blocks = buildAccessRequestSeedContentBlocks(basePayload);
31
+ const surface = blocks[0] as Record<string, unknown>;
32
+ expect(surface.surfaceType).toBe("card");
33
+ expect(surface.surfaceId).toBe("access-request-req-123");
34
+ expect(surface.title).toBe("Access Request");
35
+ });
36
+
37
+ test("card data uses actorDisplayName as title", () => {
38
+ const blocks = buildAccessRequestSeedContentBlocks(basePayload);
39
+ const surface = blocks[0] as Record<string, unknown>;
40
+ const data = surface.data as Record<string, unknown>;
41
+ expect(data.title).toBe("Alice");
42
+ expect(data.subtitle).toBe("Requesting access to the assistant");
43
+ });
44
+
45
+ test("card data falls back to senderIdentifier when no displayName", () => {
46
+ const payload = { ...basePayload, actorDisplayName: undefined };
47
+ const blocks = buildAccessRequestSeedContentBlocks(payload);
48
+ const data = (blocks[0] as Record<string, unknown>).data as Record<
49
+ string,
50
+ unknown
51
+ >;
52
+ expect(data.title).toBe("U999");
53
+ });
54
+
55
+ test("card data falls back to 'Someone' when no identity", () => {
56
+ const payload = {
57
+ ...basePayload,
58
+ actorDisplayName: undefined,
59
+ senderIdentifier: undefined,
60
+ };
61
+ const blocks = buildAccessRequestSeedContentBlocks(payload);
62
+ const data = (blocks[0] as Record<string, unknown>).data as Record<
63
+ string,
64
+ unknown
65
+ >;
66
+ expect(data.title).toBe("Someone");
67
+ });
68
+
69
+ test("includes username and source in metadata", () => {
70
+ const blocks = buildAccessRequestSeedContentBlocks(basePayload);
71
+ const data = (blocks[0] as Record<string, unknown>).data as Record<
72
+ string,
73
+ unknown
74
+ >;
75
+ const metadata = data.metadata as Array<{ label: string; value: string }>;
76
+ expect(metadata).toContainEqual({
77
+ label: "Username",
78
+ value: "@alice",
79
+ });
80
+ expect(metadata).toContainEqual({
81
+ label: "Source",
82
+ value: "Slack — #C01ABC",
83
+ });
84
+ });
85
+
86
+ test("DM channel renders as Direct message", () => {
87
+ const payload = { ...basePayload, conversationExternalId: "D01XYZ" };
88
+ const blocks = buildAccessRequestSeedContentBlocks(payload);
89
+ const data = (blocks[0] as Record<string, unknown>).data as Record<
90
+ string,
91
+ unknown
92
+ >;
93
+ const metadata = data.metadata as Array<{ label: string; value: string }>;
94
+ expect(metadata).toContainEqual({
95
+ label: "Source",
96
+ value: "Slack — Direct message",
97
+ });
98
+ });
99
+
100
+ test("body includes message preview when present", () => {
101
+ const blocks = buildAccessRequestSeedContentBlocks(basePayload);
102
+ const data = (blocks[0] as Record<string, unknown>).data as Record<
103
+ string,
104
+ unknown
105
+ >;
106
+ expect(data.body).toContain("Hello, I need help with something");
107
+ });
108
+
109
+ test("body includes trust signal warnings", () => {
110
+ const payload = {
111
+ ...basePayload,
112
+ isStranger: true,
113
+ isRestricted: true,
114
+ previousMemberStatus: "revoked",
115
+ };
116
+ const blocks = buildAccessRequestSeedContentBlocks(payload);
117
+ const data = (blocks[0] as Record<string, unknown>).data as Record<
118
+ string,
119
+ unknown
120
+ >;
121
+ const body = data.body as string;
122
+ expect(body).toContain("External Slack user");
123
+ expect(body).toContain("Guest / restricted account");
124
+ expect(body).toContain("previously revoked");
125
+ });
126
+
127
+ test("body includes Slack message permalink", () => {
128
+ const blocks = buildAccessRequestSeedContentBlocks(basePayload);
129
+ const data = (blocks[0] as Record<string, unknown>).data as Record<
130
+ string,
131
+ unknown
132
+ >;
133
+ const body = data.body as string;
134
+ expect(body).toContain("View message");
135
+ expect(body).toContain(
136
+ "https://slack.com/archives/C01ABC/p1700000000000100",
137
+ );
138
+ });
139
+
140
+ test("text fallback block contains contract text", () => {
141
+ const blocks = buildAccessRequestSeedContentBlocks(basePayload);
142
+ const textBlock = blocks[1] as Record<string, unknown>;
143
+ expect(textBlock.type).toBe("text");
144
+ const text = textBlock.text as string;
145
+ expect(text).toContain("requesting access to the assistant");
146
+ expect(text).toContain("ABC123");
147
+ });
148
+
149
+ test("body shows fallback when no preview/warnings/permalink", () => {
150
+ const payload = {
151
+ requestId: "req-456",
152
+ requestCode: "XYZ",
153
+ senderIdentifier: "someone",
154
+ };
155
+ const blocks = buildAccessRequestSeedContentBlocks(payload);
156
+ const data = (blocks[0] as Record<string, unknown>).data as Record<
157
+ string,
158
+ unknown
159
+ >;
160
+ expect(data.body).toBe("No additional context available.");
161
+ });
162
+
163
+ test("surface block includes approve/reject actions when requestId present", () => {
164
+ const blocks = buildAccessRequestSeedContentBlocks(basePayload);
165
+ const surface = blocks[0] as Record<string, unknown>;
166
+ const actions = surface.actions as Array<{
167
+ id: string;
168
+ label: string;
169
+ style: string;
170
+ }>;
171
+ expect(actions).toHaveLength(2);
172
+ expect(actions[0]).toEqual({
173
+ id: "apr:req-123:approve_once",
174
+ label: "Approve",
175
+ style: "primary",
176
+ });
177
+ expect(actions[1]).toEqual({
178
+ id: "apr:req-123:reject",
179
+ label: "Reject",
180
+ style: "destructive",
181
+ });
182
+ });
183
+
184
+ test("surface block omits actions when requestId is missing", () => {
185
+ const payload = {
186
+ ...basePayload,
187
+ requestId: undefined,
188
+ };
189
+ const blocks = buildAccessRequestSeedContentBlocks(payload);
190
+ const surface = blocks[0] as Record<string, unknown>;
191
+ expect(surface.actions).toBeUndefined();
192
+ });
193
+
194
+ test("parseAccessRequestPayload extracts typed fields", () => {
195
+ const p = parseAccessRequestPayload(basePayload);
196
+ expect(p.requestId).toBe("req-123");
197
+ expect(p.actorDisplayName).toBe("Alice");
198
+ expect(p.actorUsername).toBe("alice");
199
+ expect(p.sourceChannel).toBe("slack");
200
+ expect(p.isStranger).toBeUndefined();
201
+ expect(p.isRestricted).toBeUndefined();
202
+ });
203
+
204
+ test("parseAccessRequestPayload handles boolean trust signals", () => {
205
+ const p = parseAccessRequestPayload({
206
+ ...basePayload,
207
+ isStranger: true,
208
+ isRestricted: true,
209
+ });
210
+ expect(p.isStranger).toBe(true);
211
+ expect(p.isRestricted).toBe(true);
212
+ });
213
+ });
@@ -11,6 +11,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
11
 
12
12
  import { repairAdaptiveThinkingOnManagedProfiles } from "../workspace/adaptive-thinking-repair.js";
13
13
  import { enableAdaptiveThinkingManagedProfilesMigration } from "../workspace/migrations/097-enable-adaptive-thinking-managed-profiles.js";
14
+ import { recheckAdaptiveThinkingModelImpliedAnthropicMigration } from "../workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.js";
14
15
 
15
16
  const ADAPTIVE = { enabled: true, streamThinking: true };
16
17
 
@@ -48,15 +49,19 @@ function profile(name: string): Record<string, unknown> {
48
49
  return profiles[name] as Record<string, unknown>;
49
50
  }
50
51
 
51
- // The startup repair and migration 097 keep frozen copies of the same logic
52
- // (migration modules must stay self-contained). Run every scenario against
53
- // both so the copies can't drift.
52
+ // The startup repair, migration 097, and migration 104 keep frozen copies of
53
+ // the same logic (migration modules must stay self-contained). Run every
54
+ // scenario against all of them so the copies can't drift.
54
55
  const implementations: Array<[string, (dir: string) => void]> = [
55
56
  ["startup repair", repairAdaptiveThinkingOnManagedProfiles],
56
57
  [
57
58
  "migration 097",
58
59
  (dir) => enableAdaptiveThinkingManagedProfilesMigration.run(dir),
59
60
  ],
61
+ [
62
+ "migration 104",
63
+ (dir) => recheckAdaptiveThinkingModelImpliedAnthropicMigration.run(dir),
64
+ ],
60
65
  ];
61
66
 
62
67
  describe.each(implementations)("%s", (_label, run) => {
@@ -131,6 +136,30 @@ describe.each(implementations)("%s", (_label, run) => {
131
136
  expect(profile("balanced").thinking).toEqual(ADAPTIVE);
132
137
  });
133
138
 
139
+ test("patches source-less profiles pinning a known Claude model under a non-Anthropic default", () => {
140
+ // A managed/source-less profile that omits `provider` but pins a known
141
+ // Claude model is Anthropic via the resolver's model-implied provider, even
142
+ // when llm.default.provider is non-Anthropic. It must still get adaptive
143
+ // thinking rather than being skipped.
144
+ writeConfig({
145
+ llm: {
146
+ default: { provider: "gemini", model: "gemini-2.5-pro" },
147
+ profiles: {
148
+ balanced: { model: "claude-sonnet-4-6" },
149
+ "quality-optimized": {
150
+ source: "managed",
151
+ model: "claude-opus-4-8",
152
+ },
153
+ },
154
+ },
155
+ });
156
+
157
+ run(workspaceDir);
158
+
159
+ expect(profile("balanced").thinking).toEqual(ADAPTIVE);
160
+ expect(profile("quality-optimized").thinking).toEqual(ADAPTIVE);
161
+ });
162
+
134
163
  test("skips profiles with an explicit non-Anthropic provider", () => {
135
164
  writeConfig({
136
165
  llm: {