@vellumai/assistant 0.8.12 → 0.9.0-staging.1

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 (414) 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/openapi.yaml +6935 -5545
  15. package/package.json +8 -4
  16. package/scripts/generate-openapi.ts +66 -114
  17. package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
  18. package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
  19. package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
  20. package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
  21. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  22. package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
  23. package/src/__tests__/app-dir-path-guard.test.ts +27 -3
  24. package/src/__tests__/approval-cascade.test.ts +0 -5
  25. package/src/__tests__/approval-routes-http.test.ts +91 -0
  26. package/src/__tests__/assistant-stream-state.test.ts +107 -0
  27. package/src/__tests__/browser-fill-credential.test.ts +3 -3
  28. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
  29. package/src/__tests__/compaction-events.test.ts +63 -7
  30. package/src/__tests__/compaction-trail-store.test.ts +74 -1
  31. package/src/__tests__/compaction.benchmark.test.ts +63 -41
  32. package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
  33. package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
  34. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
  35. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
  36. package/src/__tests__/conversation-history-web-search.test.ts +7 -0
  37. package/src/__tests__/conversation-process-callsite.test.ts +0 -5
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
  39. package/src/__tests__/conversation-queue.test.ts +0 -5
  40. package/src/__tests__/conversation-slash-queue.test.ts +0 -5
  41. package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
  42. package/src/__tests__/conversation-speed-override.test.ts +0 -5
  43. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  44. package/src/__tests__/conversation-usage.test.ts +2 -0
  45. package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
  46. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
  47. package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
  48. package/src/__tests__/credential-broker-server-use.test.ts +2 -2
  49. package/src/__tests__/credential-broker.test.ts +1 -1
  50. package/src/__tests__/credential-prompt-route.test.ts +417 -0
  51. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  52. package/src/__tests__/credential-vault.test.ts +37 -0
  53. package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
  54. package/src/__tests__/dynamic-page-surface.test.ts +125 -0
  55. package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
  56. package/src/__tests__/gateway-flag-listener.test.ts +24 -7
  57. package/src/__tests__/guardian-action-sweep.test.ts +56 -219
  58. package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
  59. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  60. package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
  61. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
  62. package/src/__tests__/llm-resolver.test.ts +115 -0
  63. package/src/__tests__/managed-profile-guard.test.ts +6 -5
  64. package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
  65. package/src/__tests__/media-generate-image.test.ts +20 -9
  66. package/src/__tests__/model-intents.test.ts +1 -1
  67. package/src/__tests__/normalize-onboarding.test.ts +26 -0
  68. package/src/__tests__/notification-decision-strategy.test.ts +4 -2
  69. package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
  70. package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
  71. package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
  72. package/src/__tests__/require-fresh-approval.test.ts +425 -1
  73. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
  74. package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
  75. package/src/__tests__/schedule-routes.test.ts +257 -4
  76. package/src/__tests__/schedule-store.test.ts +60 -0
  77. package/src/__tests__/schedule-tools.test.ts +247 -2
  78. package/src/__tests__/skill-execute-input.test.ts +85 -0
  79. package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
  80. package/src/__tests__/skills.test.ts +3 -3
  81. package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
  82. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  83. package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
  84. package/src/__tests__/system-prompt.test.ts +1 -1
  85. package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
  86. package/src/__tests__/task-scheduler.test.ts +299 -0
  87. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
  88. package/src/__tests__/tool-result-spool.test.ts +3 -1
  89. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
  90. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
  91. package/src/agent/compaction-circuit.ts +11 -0
  92. package/src/agent/loop.ts +181 -12
  93. package/src/api/constants/call-sites.ts +12 -0
  94. package/src/api/events/assistant-thinking-delta.ts +10 -0
  95. package/src/api/events/usage-update.ts +7 -0
  96. package/src/api/index.ts +4 -1
  97. package/src/api/responses/memory-v3-selection-log.ts +18 -11
  98. package/src/approvals/approval-primitive.ts +2 -2
  99. package/src/background-wake/background-wake-routes.test.ts +5 -2
  100. package/src/bundler/compiler-tools.ts +1 -1
  101. package/src/calls/call-domain.ts +1 -1
  102. package/src/calls/guardian-action-sweep.ts +16 -93
  103. package/src/cli/AGENTS.md +4 -0
  104. package/src/cli/commands/__tests__/schedules.test.ts +430 -1
  105. package/src/cli/commands/credentials.ts +28 -24
  106. package/src/cli/commands/image-generation.ts +23 -9
  107. package/src/cli/commands/notifications.ts +1 -1
  108. package/src/cli/commands/plugins.ts +89 -46
  109. package/src/cli/commands/schedules.ts +384 -11
  110. package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
  111. package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
  112. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
  113. package/src/cli/lib/inspect-plugin.ts +62 -1
  114. package/src/cli/lib/install-from-github.ts +52 -5
  115. package/src/cli/lib/upgrade-plugin.ts +18 -0
  116. package/src/config/__tests__/workflows-schema.test.ts +60 -0
  117. package/src/config/bundled-skills/acp/SKILL.md +2 -2
  118. package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
  119. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
  120. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
  121. package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
  122. package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
  123. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
  124. package/src/config/bundled-skills/schedule/SKILL.md +7 -2
  125. package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
  126. package/src/config/bundled-skills/workflows/SKILL.md +214 -0
  127. package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
  128. package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
  129. package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
  130. package/src/config/bundled-tool-registry.ts +14 -2
  131. package/src/config/call-site-defaults.ts +5 -0
  132. package/src/config/feature-flag-registry.json +12 -4
  133. package/src/config/llm-context-resolution.ts +8 -0
  134. package/src/config/llm-resolver.ts +30 -0
  135. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
  136. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
  137. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
  138. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
  139. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
  140. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
  141. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
  142. package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
  143. package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
  144. package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
  145. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
  146. package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
  147. package/src/config/schema.ts +2 -0
  148. package/src/config/schemas/call-site-catalog.ts +7 -0
  149. package/src/config/schemas/heartbeat.ts +4 -1
  150. package/src/config/schemas/llm.ts +33 -26
  151. package/src/config/schemas/memory-retrospective.ts +19 -0
  152. package/src/config/schemas/platform.ts +8 -0
  153. package/src/config/schemas/services.ts +5 -2
  154. package/src/config/schemas/workflows.ts +42 -0
  155. package/src/config/skills.ts +3 -3
  156. package/src/context/compactor.ts +273 -39
  157. package/src/context/post-turn-tool-result-truncation.ts +23 -6
  158. package/src/context/tool-result-spool.ts +12 -17
  159. package/src/credential-execution/executable-discovery.ts +1 -1
  160. package/src/credential-execution/process-manager.ts +37 -3
  161. package/src/credential-execution/prompted-credential.ts +205 -0
  162. package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
  163. package/src/daemon/conversation-process.ts +11 -2
  164. package/src/daemon/conversation-surfaces.ts +75 -0
  165. package/src/daemon/conversation-tool-setup.ts +103 -26
  166. package/src/daemon/conversation-usage.ts +2 -0
  167. package/src/daemon/conversation.ts +115 -11
  168. package/src/daemon/handlers/shared.ts +26 -14
  169. package/src/daemon/host-cu-proxy.ts +15 -12
  170. package/src/daemon/host-file-proxy.ts +15 -12
  171. package/src/daemon/host-transfer-proxy.ts +30 -24
  172. package/src/daemon/lifecycle.ts +40 -3
  173. package/src/daemon/message-protocol.ts +3 -0
  174. package/src/daemon/message-types/messages.ts +2 -10
  175. package/src/daemon/message-types/workflows.ts +49 -0
  176. package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
  177. package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
  178. package/src/daemon/process-message.ts +6 -0
  179. package/src/daemon/tool-setup-types.ts +57 -0
  180. package/src/daemon/wake-conversation-ops.ts +18 -0
  181. package/src/heartbeat/heartbeat-run-store.ts +8 -2
  182. package/src/home/feed-types.ts +1 -1
  183. package/src/ipc/gateway-flag-listener.ts +28 -6
  184. package/src/mcp/mcp-auth-state.ts +8 -20
  185. package/src/media/__tests__/image-models.test.ts +57 -0
  186. package/src/media/image-models.ts +66 -0
  187. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
  188. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
  189. package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
  190. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
  191. package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
  192. package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
  193. package/src/memory/auto-analysis-enqueue.ts +14 -1
  194. package/src/memory/compaction-log-store-clickhouse.ts +6 -4
  195. package/src/memory/conversation-crud.ts +9 -2
  196. package/src/memory/conversation-disk-view.ts +1 -1
  197. package/src/memory/conversation-queries.ts +22 -7
  198. package/src/memory/db-init.ts +20 -0
  199. package/src/memory/db-maintenance.ts +16 -0
  200. package/src/memory/embedding-runtime-manager.ts +1 -1
  201. package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
  202. package/src/memory/llm-request-log-source-local.ts +19 -1
  203. package/src/memory/llm-request-log-source.ts +35 -6
  204. package/src/memory/llm-request-log-store.ts +90 -2
  205. package/src/memory/memory-retrospective-constants.ts +9 -0
  206. package/src/memory/memory-retrospective-enqueue.ts +3 -6
  207. package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
  208. package/src/memory/memory-retrospective-job.ts +500 -208
  209. package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
  210. package/src/memory/memory-retrospective-state.ts +85 -2
  211. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
  212. package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
  213. package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
  214. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
  215. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
  216. package/src/memory/migrations/284-workflow-runs.ts +51 -0
  217. package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
  218. package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
  219. package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
  220. package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
  221. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
  222. package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
  223. package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
  224. package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
  225. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
  226. package/src/memory/migrations/index.ts +10 -0
  227. package/src/memory/preloaded-apps.ts +116 -0
  228. package/src/memory/schema/infrastructure.ts +4 -0
  229. package/src/memory/schema/memory-core.ts +4 -0
  230. package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
  231. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
  232. package/src/memory/v2/__tests__/page-store.test.ts +13 -2
  233. package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
  234. package/src/memory/v2/frontmatter-sweep.ts +7 -6
  235. package/src/memory/v2/page-store.ts +4 -3
  236. package/src/memory/v2/qdrant.ts +42 -3
  237. package/src/memory/v2/types.ts +16 -10
  238. package/src/messaging/draft-store.ts +1 -1
  239. package/src/notifications/access-request-copy.ts +200 -113
  240. package/src/notifications/adapters/slack.ts +250 -111
  241. package/src/notifications/adapters/telegram.ts +7 -44
  242. package/src/notifications/approval-card-builder.ts +93 -0
  243. package/src/notifications/broadcaster.ts +74 -0
  244. package/src/notifications/conversation-pairing.ts +8 -6
  245. package/src/notifications/copy-composer.ts +32 -26
  246. package/src/notifications/decision-engine.ts +59 -7
  247. package/src/notifications/guardian-question-mode.ts +145 -155
  248. package/src/notifications/home-feed-side-effect.ts +1 -1
  249. package/src/notifications/notification-utils.ts +66 -0
  250. package/src/notifications/signal.ts +6 -0
  251. package/src/notifications/tool-approval-copy.ts +142 -0
  252. package/src/notifications/types.ts +19 -0
  253. package/src/permissions/threshold.ts +11 -0
  254. package/src/plugin-api/types.ts +16 -4
  255. package/src/plugins/defaults/compaction/window-manager.ts +44 -0
  256. package/src/plugins/defaults/index.ts +46 -0
  257. package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
  258. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
  259. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
  260. package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
  261. package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
  262. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
  263. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
  264. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
  265. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
  266. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
  267. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
  268. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
  269. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
  270. package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
  271. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
  272. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
  273. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
  274. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
  275. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
  276. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
  277. package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
  278. package/src/prompts/__tests__/system-prompt.test.ts +100 -1
  279. package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
  280. package/src/prompts/normalize-onboarding.ts +2 -0
  281. package/src/prompts/persona-resolver.ts +3 -0
  282. package/src/prompts/system-prompt.ts +51 -2
  283. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
  284. package/src/prompts/templates/system-sections.ts +8 -3
  285. package/src/providers/call-site-routing.ts +6 -3
  286. package/src/providers/fireworks/client.ts +3 -0
  287. package/src/providers/inference/auth.ts +52 -46
  288. package/src/providers/model-intents.ts +2 -2
  289. package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
  290. package/src/providers/openai/chat-completions-provider.ts +47 -9
  291. package/src/providers/openai/coerce-object-args.ts +104 -0
  292. package/src/providers/retry.ts +8 -5
  293. package/src/providers/types.ts +10 -0
  294. package/src/runtime/__tests__/agent-wake.test.ts +629 -7
  295. package/src/runtime/access-request-helper.ts +16 -9
  296. package/src/runtime/agent-wake.ts +302 -51
  297. package/src/runtime/assistant-stream-state.ts +141 -8
  298. package/src/runtime/background-job-runner.ts +9 -0
  299. package/src/runtime/channel-approval-types.ts +1 -0
  300. package/src/runtime/guardian-action-message-composer.ts +0 -54
  301. package/src/runtime/http-server.ts +6 -14
  302. package/src/runtime/http-types.ts +0 -1
  303. package/src/runtime/message-composer-types.ts +0 -9
  304. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
  305. package/src/runtime/middleware/auth.ts +27 -3
  306. package/src/runtime/middleware/rate-limiter.ts +28 -1
  307. package/src/runtime/migrations/vbundle-builder.ts +6 -5
  308. package/src/runtime/pending-interactions.ts +20 -1
  309. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
  310. package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
  311. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
  312. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
  313. package/src/runtime/routes/approval-routes.ts +35 -8
  314. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
  315. package/src/runtime/routes/btw-routes.ts +37 -1
  316. package/src/runtime/routes/channel-route-shared.ts +3 -1
  317. package/src/runtime/routes/consolidation-routes.ts +17 -13
  318. package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
  319. package/src/runtime/routes/conversation-list-routes.ts +41 -4
  320. package/src/runtime/routes/conversation-query-routes.ts +196 -11
  321. package/src/runtime/routes/conversation-routes.ts +15 -1
  322. package/src/runtime/routes/credential-prompt-routes.ts +39 -17
  323. package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
  324. package/src/runtime/routes/heartbeat-routes.ts +18 -13
  325. package/src/runtime/routes/image-generation-routes.ts +20 -2
  326. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +26 -1
  327. package/src/runtime/routes/index.ts +6 -0
  328. package/src/runtime/routes/log-export/AGENTS.md +1 -1
  329. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  330. package/src/runtime/routes/migration-routes.ts +5 -9
  331. package/src/runtime/routes/plugins-routes.ts +26 -0
  332. package/src/runtime/routes/ps-routes.ts +10 -8
  333. package/src/runtime/routes/retrospective-routes.ts +235 -0
  334. package/src/runtime/routes/runs-pagination.ts +75 -0
  335. package/src/runtime/routes/schedule-routes.ts +367 -59
  336. package/src/runtime/routes/sounds-config-routes.ts +239 -0
  337. package/src/runtime/routes/surface-action-routes.ts +84 -4
  338. package/src/runtime/routes/workflow-routes.test.ts +372 -0
  339. package/src/runtime/routes/workflow-routes.ts +363 -0
  340. package/src/runtime/routes/workspace-routes.test.ts +61 -1
  341. package/src/runtime/routes/workspace-routes.ts +26 -1
  342. package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
  343. package/src/runtime/services/analyze-conversation.ts +26 -13
  344. package/src/schedule/inference-profile.ts +28 -0
  345. package/src/schedule/schedule-store.ts +122 -4
  346. package/src/schedule/scheduler-types.ts +6 -0
  347. package/src/schedule/scheduler.ts +96 -0
  348. package/src/security/secret-allowlist.ts +1 -1
  349. package/src/skills/path-classifier.ts +1 -1
  350. package/src/tools/browser/browser-execution.ts +9 -11
  351. package/src/tools/credentials/broker.ts +4 -4
  352. package/src/tools/credentials/vault.ts +26 -137
  353. package/src/tools/executor.ts +69 -0
  354. package/src/tools/flag-gated-tools.test.ts +76 -0
  355. package/src/tools/permission-checker.ts +8 -1
  356. package/src/tools/registry.ts +51 -0
  357. package/src/tools/schedule/create.ts +77 -1
  358. package/src/tools/schedule/list.ts +1 -0
  359. package/src/tools/schedule/update.ts +73 -1
  360. package/src/tools/skills/execute.ts +56 -0
  361. package/src/tools/terminal/shell.ts +1 -1
  362. package/src/tools/ui-surface/definitions.ts +48 -2
  363. package/src/tools/workflows/manage-workflows.ts +183 -0
  364. package/src/tools/workflows/run-workflow.test.ts +442 -0
  365. package/src/tools/workflows/run-workflow.ts +88 -0
  366. package/src/types/onboarding-context.ts +2 -0
  367. package/src/usage/attribution.ts +24 -0
  368. package/src/util/canonicalize-identity.ts +12 -3
  369. package/src/util/platform.ts +17 -17
  370. package/src/watcher/__tests__/engine.test.ts +24 -0
  371. package/src/watcher/__tests__/telemetry.test.ts +135 -0
  372. package/src/watcher/engine.ts +7 -0
  373. package/src/watcher/telemetry.ts +74 -0
  374. package/src/workflows/capabilities.test.ts +365 -0
  375. package/src/workflows/capabilities.ts +359 -0
  376. package/src/workflows/deterministic-stringify.ts +27 -0
  377. package/src/workflows/engine-integration.test.ts +656 -0
  378. package/src/workflows/engine.test.ts +1144 -0
  379. package/src/workflows/engine.ts +1078 -0
  380. package/src/workflows/fanout-load.test.ts +168 -0
  381. package/src/workflows/journal-store.test.ts +369 -0
  382. package/src/workflows/journal-store.ts +470 -0
  383. package/src/workflows/leaf-runner.test.ts +704 -0
  384. package/src/workflows/leaf-runner.ts +589 -0
  385. package/src/workflows/library.test.ts +134 -0
  386. package/src/workflows/library.ts +124 -0
  387. package/src/workflows/run-manager.test.ts +711 -0
  388. package/src/workflows/run-manager.ts +593 -0
  389. package/src/workflows/sandbox-escape.test.ts +339 -0
  390. package/src/workflows/sandbox.test.ts +251 -0
  391. package/src/workflows/sandbox.ts +447 -0
  392. package/src/workspace/adaptive-thinking-repair.ts +33 -11
  393. package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
  394. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
  395. package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
  396. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
  397. package/src/workspace/migrations/031-drop-user-md.ts +1 -4
  398. package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
  399. package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
  400. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
  401. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
  402. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
  403. package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
  404. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
  405. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
  406. package/src/workspace/migrations/registry.ts +6 -0
  407. package/src/workspace/migrations/runner.ts +1 -1
  408. package/tsconfig.json +1 -1
  409. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
  410. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
  411. package/src/__tests__/guardian-action-store.test.ts +0 -106
  412. package/src/daemon/guardian-action-generators.ts +0 -71
  413. package/src/memory/guardian-action-store.ts +0 -484
  414. package/src/runtime/guardian-action-grant-minter.ts +0 -150
@@ -6,7 +6,7 @@
6
6
  * user-message injection that replaced it.
7
7
  */
8
8
 
9
- import { copyFileSync, mkdirSync, readFileSync } from "node:fs";
9
+ import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
10
  import { join } from "node:path";
11
11
  import { beforeEach, describe, expect, mock, test } from "bun:test";
12
12
 
@@ -73,6 +73,105 @@ describe("buildSystemPrompt — tool routing guidance", () => {
73
73
  });
74
74
  });
75
75
 
76
+ describe("buildSystemPrompt — persona override", () => {
77
+ const DEFAULT_SENTINEL = "Sentinel: default persona body.";
78
+ const ALICE_SENTINEL = "Sentinel: alice persona body.";
79
+ const TELEGRAM_SENTINEL = "Sentinel: telegram channel persona body.";
80
+
81
+ beforeEach(() => {
82
+ mkdirSync(join(TEST_DIR, "users"), { recursive: true });
83
+ mkdirSync(join(TEST_DIR, "channels"), { recursive: true });
84
+ writeFileSync(join(TEST_DIR, "users", "default.md"), DEFAULT_SENTINEL);
85
+ writeFileSync(join(TEST_DIR, "users", "alice.md"), ALICE_SENTINEL);
86
+ writeFileSync(join(TEST_DIR, "channels", "telegram.md"), TELEGRAM_SENTINEL);
87
+ });
88
+
89
+ test("personaOverride renders the given user + channel persona sections", () => {
90
+ const result = buildSystemPrompt({
91
+ personaOverride: { userSlug: "alice", channelSlug: "telegram" },
92
+ });
93
+
94
+ expect(result).toContain(ALICE_SENTINEL);
95
+ expect(result).toContain(TELEGRAM_SENTINEL);
96
+ expect(result).not.toContain(DEFAULT_SENTINEL);
97
+ });
98
+
99
+ test("no override → trust-context-derived resolution (default persona, vellum channel)", () => {
100
+ // No trust context and no resolvable guardian contact in this test env,
101
+ // so the user persona falls back to users/default.md and the channel
102
+ // section resolves channels/vellum.md (absent → omitted).
103
+ const result = buildSystemPrompt({});
104
+
105
+ expect(result).toContain(DEFAULT_SENTINEL);
106
+ expect(result).not.toContain(ALICE_SENTINEL);
107
+ expect(result).not.toContain(TELEGRAM_SENTINEL);
108
+ });
109
+
110
+ test("partial override: userSlug alone leaves channel resolution untouched", () => {
111
+ const result = buildSystemPrompt({
112
+ personaOverride: { userSlug: "alice" },
113
+ });
114
+
115
+ expect(result).toContain(ALICE_SENTINEL);
116
+ expect(result).not.toContain(TELEGRAM_SENTINEL);
117
+ });
118
+
119
+ test("override userSlug with no matching file falls back to users/default.md", () => {
120
+ const result = buildSystemPrompt({
121
+ personaOverride: { userSlug: "missing-user" },
122
+ });
123
+
124
+ expect(result).toContain(DEFAULT_SENTINEL);
125
+ });
126
+ });
127
+
128
+ describe("buildSystemPrompt — hasNoClient pin", () => {
129
+ // Marker unique to the `{{^hasNoClient}}` branch of 05-access-preference:
130
+ // host fallbacks only render when a client is connected.
131
+ const WITH_CLIENT_MARKER = "`host_bash` with CLIs";
132
+
133
+ beforeEach(() => {
134
+ mkdirSync(TEST_DIR, { recursive: true });
135
+ });
136
+
137
+ test("hasNoClient renders divergent access-preference text", () => {
138
+ expect(buildSystemPrompt({ hasNoClient: false })).toContain(
139
+ WITH_CLIENT_MARKER,
140
+ );
141
+ expect(buildSystemPrompt({ hasNoClient: true })).not.toContain(
142
+ WITH_CLIENT_MARKER,
143
+ );
144
+ });
145
+
146
+ test("personaOverride.hasNoClient pins the flag over the conversation-derived option", () => {
147
+ // Fork-retrospective case: the fork is hydrated clientless
148
+ // (hasNoClient: true) but pins the source's live-turn value (false) so
149
+ // the prompt byte-matches the source's cached prefix.
150
+ expect(
151
+ buildSystemPrompt({
152
+ hasNoClient: true,
153
+ personaOverride: { hasNoClient: false },
154
+ }),
155
+ ).toContain(WITH_CLIENT_MARKER);
156
+ // And the pin wins in the other direction too.
157
+ expect(
158
+ buildSystemPrompt({
159
+ hasNoClient: false,
160
+ personaOverride: { hasNoClient: true },
161
+ }),
162
+ ).not.toContain(WITH_CLIENT_MARKER);
163
+ });
164
+
165
+ test("a personaOverride without the pin leaves the conversation-derived flag untouched", () => {
166
+ expect(
167
+ buildSystemPrompt({ hasNoClient: true, personaOverride: {} }),
168
+ ).not.toContain(WITH_CLIENT_MARKER);
169
+ expect(
170
+ buildSystemPrompt({ hasNoClient: false, personaOverride: {} }),
171
+ ).toContain(WITH_CLIENT_MARKER);
172
+ });
173
+ });
174
+
76
175
  describe("maybeReseedBootstrap — content-automation template", () => {
77
176
  const templatesDir = join(import.meta.dirname!, "..", "templates");
78
177
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Tests for the task_progress hint in the 01-parallel-tool-calls workspace
2
+ * Tests for the task_progress hint in the 01-progress-surface workspace
3
3
  * system prompt section.
4
4
  *
5
5
  * Verifies that the task_progress guidance renders unconditionally in the
@@ -54,11 +54,10 @@ mock.module("../../config/loader.js", () => ({
54
54
  setNestedValue: () => {},
55
55
  }));
56
56
 
57
- const { buildSystemPrompt, ensurePromptFiles } = await import(
58
- "../system-prompt.js"
59
- );
57
+ const { buildSystemPrompt, ensurePromptFiles } =
58
+ await import("../system-prompt.js");
60
59
 
61
- describe("task_progress hint in parallel-tool-calls section", () => {
60
+ describe("task_progress hint in progress-surface section", () => {
62
61
  beforeEach(() => {
63
62
  ensurePromptFiles();
64
63
  });
@@ -66,7 +65,7 @@ describe("task_progress hint in parallel-tool-calls section", () => {
66
65
  test("buildSystemPrompt() includes task_progress guidance", () => {
67
66
  const result = buildSystemPrompt();
68
67
  expect(result).toContain("task_progress");
69
- expect(result).toContain("No exceptions");
68
+ expect(result).toContain("Show Progress on Long Turns");
70
69
  });
71
70
 
72
71
  test("renders unconditionally — no options required", () => {
@@ -85,5 +84,4 @@ describe("task_progress hint in parallel-tool-calls section", () => {
85
84
  expect(withoutClientFlag).toContain("task_progress");
86
85
  expect(withExcludePrefix).toContain("task_progress");
87
86
  });
88
-
89
87
  });
@@ -82,6 +82,7 @@ export function normalizePriorAssistants(assistants: string[]): string[] {
82
82
 
83
83
  export interface NormalizedOnboarding {
84
84
  preferredName?: string;
85
+ occupation?: string;
85
86
  commonWork: string[];
86
87
  dailyTools: string[];
87
88
  tone?: string;
@@ -123,6 +124,7 @@ export function normalizeOnboardingContext(
123
124
  ): NormalizedOnboarding {
124
125
  return {
125
126
  preferredName: ctx.userName?.trim() || undefined,
127
+ occupation: ctx.occupation?.trim() || undefined,
126
128
  commonWork: normalizeTasks(ctx.tasks),
127
129
  dailyTools: normalizeTools(ctx.tools),
128
130
  tone: ctx.tone,
@@ -348,6 +348,9 @@ function buildOnboardingSection(normalized: NormalizedOnboarding): string {
348
348
  if (normalized.preferredName) {
349
349
  lines.push(`- **Preferred name:** ${normalized.preferredName}`);
350
350
  }
351
+ if (normalized.occupation) {
352
+ lines.push(`- **Role:** ${normalized.occupation}`);
353
+ }
351
354
  if (normalized.commonWork.length > 0) {
352
355
  lines.push(`- **Common work:** ${normalized.commonWork.join("; ")}`);
353
356
  }
@@ -304,6 +304,33 @@ export function applyBootstrapTemplate(
304
304
  }
305
305
  }
306
306
 
307
+ /**
308
+ * Explicit prompt-build override for builds that run outside the
309
+ * inbound-turn pipeline (agent wakes). Each field, when present, takes
310
+ * precedence over the corresponding derivation in {@link buildSystemPrompt}.
311
+ * Prompt-build selection only — trust class and approval semantics are
312
+ * unaffected.
313
+ */
314
+ export interface SystemPromptPersonaOverride {
315
+ /** Renders `users/<slug>.md` as the user persona section. */
316
+ userSlug?: string;
317
+ /** Renders `channels/<slug>.md` as the channel persona section. */
318
+ channelSlug?: string;
319
+ /**
320
+ * Pins the `hasNoClient` flag for the prompt build, taking precedence over
321
+ * the top-level `BuildSystemPromptOptions.hasNoClient` (which mirrors the
322
+ * conversation's live client state). The `05-access-preference` section
323
+ * renders different text under the flag — early in the prompt, so a
324
+ * mismatch breaks byte-parity with a cached prefix even when persona and
325
+ * profile match. Used by fork-based memory retrospectives: the fork is
326
+ * hydrated clientless (`hasNoClient = true`) while the source's live turns
327
+ * ran under the source's own client state (`false` for interactive
328
+ * interfaces, `true` for channel-routed sources) — the pin carries that
329
+ * live-turn value.
330
+ */
331
+ hasNoClient?: boolean;
332
+ }
333
+
307
334
  export interface BuildSystemPromptOptions {
308
335
  hasNoClient?: boolean;
309
336
  excludeBootstrap?: boolean;
@@ -311,6 +338,15 @@ export interface BuildSystemPromptOptions {
311
338
  trustContext?: TrustContext;
312
339
  channelCapabilities?: ChannelCapabilities;
313
340
  onboardingContext?: OnboardingContext;
341
+ /**
342
+ * Explicit persona/channel slugs, taking precedence over the
343
+ * trust-context-derived `userSlug` and capabilities-derived `channelSlug`.
344
+ * Used by fork-based memory retrospectives so the fork's prompt renders the
345
+ * SOURCE conversation's persona sections (review quality + byte-parity with
346
+ * the source's cached system-prompt prefix) even though the wake itself
347
+ * carries an internal guardian trust context with no requester identity.
348
+ */
349
+ personaOverride?: SystemPromptPersonaOverride;
314
350
  /**
315
351
  * Conversation this prompt is being built for. Optional because several
316
352
  * callers build a prompt outside a conversation (e.g. home greeting,
@@ -344,8 +380,20 @@ export function buildSystemPrompt(options?: BuildSystemPromptOptions): string {
344
380
  // `users/<slug>.md → users/default.md` fallback lives in the
345
381
  // section's `workspacePath` array. `channelSlug` is the channel
346
382
  // identifier from `channelCapabilities`, defaulting to "vellum".
347
- const userSlug = resolveUserSlug(options?.trustContext) ?? "default";
348
- const channelSlug = options?.channelCapabilities?.channel ?? "vellum";
383
+ // An explicit `personaOverride` slug wins over either derivation.
384
+ const userSlug =
385
+ options?.personaOverride?.userSlug ??
386
+ resolveUserSlug(options?.trustContext) ??
387
+ "default";
388
+ const channelSlug =
389
+ options?.personaOverride?.channelSlug ??
390
+ options?.channelCapabilities?.channel ??
391
+ "vellum";
392
+ // The override's `hasNoClient` pin wins over the conversation-derived
393
+ // top-level option (see the interface doc); placed after the `...options`
394
+ // spread below so it overrides the spread-in value.
395
+ const hasNoClient =
396
+ options?.personaOverride?.hasNoClient ?? options?.hasNoClient;
349
397
 
350
398
  // Section render context. Workspace section frontmatter `enabled:`
351
399
  // predicates, `{{key}}` / `{{#flag}}...{{/flag}}` body interpolation,
@@ -358,6 +406,7 @@ export function buildSystemPrompt(options?: BuildSystemPromptOptions): string {
358
406
  // no explicit normalization needed; `...options` is enough.
359
407
  const ctx = {
360
408
  ...options,
409
+ hasNoClient,
361
410
  isContainerized: getIsContainerized(),
362
411
  workspaceDir: getWorkspaceDir(),
363
412
  userSlug,
@@ -35,6 +35,8 @@ Surface the outcome as a clickable component, strongest first. The component is
35
35
 
36
36
  DONE: you cannot exit Propose without emitting at least one `ui_show` offer surface (card / choice). A proposal in prose is not a proposal.
37
37
 
38
+ Thin signal still ships a proposal. Weak findings mean you make the smallest defensible offer and name the confidence in a clause ("low confidence, but —") — they do not mean you skip the move. Never resolve Propose into a critique of why the signal's too thin: "not enough here to suggest anything" is a stall, not a move. The least-wrong job for someone who looks like them beats an honest shrug, every time.
39
+
38
40
  **Run.** Do it. Real tools, real data. The user watches something happen.
39
41
 
40
42
  DONE: a real tool ran against real data and the user can see the result.
@@ -69,7 +71,7 @@ These are enforcement rules, not advice.
69
71
 
70
72
  **Self-check before final emit.** If this turn contains an offer or a follow-up, render it via `ui_show`, not prose.
71
73
 
72
- **Long turns show progress.** Any post-submit / post-skill-load turn must render a `task_progress` card within ~5s, or fall back to streaming text. Bind "long turn" → "task_progress emitted": a long-running turn that produces neither a progress card nor streaming text didn't satisfy this move.
74
+ **Long turns show progress.** Any post-submit / post-skill-load turn must render a `task_progress` card within ~5s, or fall back to streaming text. Bind "long turn" → "task_progress emitted": a long-running turn that produces neither a progress card nor streaming text didn't satisfy this move. Progress is the card, not a play-by-play. Tool selection, routing, retries, and failures ("search blocked, trying the browser…", "that captcha'd, falling back to —") belong in thinking, never in visible prose: the card shows progress, the result surface shows outcome, and the mechanics in between stay out of the user's view.
73
75
 
74
76
  **Action Trust-Guarantee.** Sibling to the OAuth Trust-Guarantee. Before a bulk write / delete / destructive op, render a `ui_show` preview — a table surface showing total count, breakdown, sample rows, and the categories to confirm. The user commits or refines on that surface; only then do you execute. Single-item actions use the natural draft instead. Threshold for the preview gate: bulk _and_ low recoverability. One of the two alone doesn't trip it.
75
77
 
@@ -269,9 +269,14 @@ Batch independent tool calls into the same response. An extra LLM round trip cos
269
269
  Before emitting a single tool call, ask whether your next turn would be another tool call that doesn't consume this one's output — if so, they belong together. Serialized tool calls without a real data dependency are a bug.
270
270
 
271
271
  For non-trivial independent workstreams — research, coding, multi-step investigations — delegate to subagents (load the \`subagent\` skill) and spawn them early and in parallel; an unnecessary subagent is cheaper than serialized work.
272
-
273
- **Before your first tool call**, check: does this turn involve a web search, file operations, multi-step work, or anything that will take more than a few seconds? If yes, call ui_show with surface_type "card" and template "task_progress" first, then update steps via ui_update as work progresses. No exceptions.
274
272
  </use_parallel_tool_calls>
273
+ `,
274
+ },
275
+ {
276
+ id: "01-progress-surface",
277
+ body: `## Show Progress on Long Turns
278
+
279
+ When a turn will take more than a few seconds — web searches, multi-step file work, research — show the user a progress card early: call ui_show with surface_type "card" and template "task_progress", then flip each step pending → in_progress → completed via ui_update as you go. Coarse steps are fine; a rough "Working on X" beats no signal at all. You can add or revise steps as the work takes shape — you are not committed to your first list. Skip the card when the turn is quick or you are already wrapping up; never let it get in the way of doing the actual work.
275
280
  `,
276
281
  },
277
282
  {
@@ -332,7 +337,7 @@ Priority: (1) sandbox \`bash\` - install tools yourself, only fall back to host
332
337
  id: "06-credential-security",
333
338
  body: `## Credential Security
334
339
 
335
- Never ask users to share secrets (API keys, tokens, passwords, webhook secrets) in chat — secret messages may be blocked at ingress. Use the \`credential_store\` tool with \`action: "prompt"\` instead; it collects secrets through a secure UI that never exposes the value in the conversation. Non-secret values (Client IDs, Account SIDs, usernames) may be collected conversationally.
340
+ Never ask users to share secrets (API keys, tokens, passwords, webhook secrets) in chat — secret messages may be blocked at ingress. Run \`assistant credentials prompt\` (via the bash tool) instead; it collects secrets through a secure UI that never exposes the value in the conversation. This command blocks until the user submits the secret, so set the bash tool's \`timeout_seconds\` to at least 330 — the default (120s) cuts the prompt off before the user can respond. Non-secret values (Client IDs, Account SIDs, usernames) may be collected conversationally.
336
341
  `,
337
342
  },
338
343
  {
@@ -141,13 +141,16 @@ export class CallSiteRoutingProvider implements Provider {
141
141
  if (!callSite) return this.defaultProvider;
142
142
 
143
143
  const overrideProfile = options?.config?.overrideProfile;
144
- // Forward the per-conversation mix seed so transport selection picks the
145
- // same mix arm as wire-param normalization in `retry.ts` — otherwise a mix
146
- // spanning providers could route the transport to a different arm than the
144
+ // Forward `forceOverrideProfile` and the per-conversation mix seed so
145
+ // transport selection resolves the same profile/arm as wire-param
146
+ // normalization in `retry.ts` — otherwise a forced profile (or a mix)
147
+ // spanning providers could route the transport differently than the
147
148
  // request params.
149
+ const forceOverrideProfile = options?.config?.forceOverrideProfile;
148
150
  const selectionSeed = options?.config?.selectionSeed;
149
151
  const resolved = resolveCallSiteConfig(callSite, getConfig().llm, {
150
152
  overrideProfile,
153
+ forceOverrideProfile,
151
154
  selectionSeed,
152
155
  });
153
156
 
@@ -35,6 +35,9 @@ export class FireworksProvider extends OpenAIChatCompletionsProvider {
35
35
  // {@link resolveMaxReasoningEffort}.
36
36
  maxReasoningEffort: "high",
37
37
  assistantReasoningField: "reasoning_content",
38
+ // minimax-m3's function-call serialization collapses object-typed tool
39
+ // args to `{}` on the wire; present them as JSON strings and decode back.
40
+ coerceObjectArgsToJsonString: /minimax/i.test(model),
38
41
  });
39
42
  }
40
43
 
@@ -18,26 +18,28 @@ import { PROVIDER_CATALOG } from "../model-catalog.js";
18
18
  * Schema-accepted variants (runtime rejects with a clear "not yet shipped" error):
19
19
  * - service_account: service-account credentials (Vertex AI, Bedrock).
20
20
  */
21
- export const AuthSchema = z.discriminatedUnion("type", [
22
- z.object({
23
- type: z.literal("api_key"),
24
- credential: z.string().min(1),
25
- }),
26
- z.object({
27
- type: z.literal("platform"),
28
- }),
29
- z.object({
30
- type: z.literal("none"),
31
- }),
32
- z.object({
33
- type: z.literal("oauth_subscription"),
34
- credential: z.string().min(1),
35
- }),
36
- z.object({
37
- type: z.literal("service_account"),
38
- credential: z.string().min(1),
39
- }),
40
- ]);
21
+ export const AuthSchema = z
22
+ .discriminatedUnion("type", [
23
+ z.object({
24
+ type: z.literal("api_key"),
25
+ credential: z.string().min(1),
26
+ }),
27
+ z.object({
28
+ type: z.literal("platform"),
29
+ }),
30
+ z.object({
31
+ type: z.literal("none"),
32
+ }),
33
+ z.object({
34
+ type: z.literal("oauth_subscription"),
35
+ credential: z.string().min(1),
36
+ }),
37
+ z.object({
38
+ type: z.literal("service_account"),
39
+ credential: z.string().min(1),
40
+ }),
41
+ ])
42
+ .meta({ id: "Auth" });
41
43
 
42
44
  export type Auth = z.infer<typeof AuthSchema>;
43
45
 
@@ -75,42 +77,46 @@ export const VALID_CONNECTION_PROVIDERS: readonly string[] =
75
77
 
76
78
  export type ConnectionProvider = string;
77
79
 
78
- export const ConnectionProviderSchema = z.enum(
79
- VALID_CONNECTION_PROVIDERS as readonly [string, ...string[]],
80
- );
80
+ export const ConnectionProviderSchema = z
81
+ .enum(VALID_CONNECTION_PROVIDERS as readonly [string, ...string[]])
82
+ .meta({ id: "ConnectionProvider" });
81
83
 
82
84
  // ---------------------------------------------------------------------------
83
85
  // Per-connection model entries (openai-compatible)
84
86
  // ---------------------------------------------------------------------------
85
87
 
86
- export const ConnectionModelSchema = z.object({
87
- id: z.string().min(1),
88
- displayName: z.string().min(1).optional(),
89
- });
88
+ export const ConnectionModelSchema = z
89
+ .object({
90
+ id: z.string().min(1),
91
+ displayName: z.string().min(1).optional(),
92
+ })
93
+ .meta({ id: "ConnectionModel" });
90
94
  export type ConnectionModel = z.infer<typeof ConnectionModelSchema>;
91
95
 
92
96
  // ---------------------------------------------------------------------------
93
97
  // Full connection shape used by CRUD layer
94
98
  // ---------------------------------------------------------------------------
95
99
 
96
- export const ProviderConnectionSchema = z.object({
97
- name: z.string().min(1),
98
- provider: ConnectionProviderSchema,
99
- auth: AuthSchema,
100
- label: z.string().min(1).nullable(),
101
- baseUrl: z.string().url().nullable(),
102
- models: z.array(ConnectionModelSchema).nullable(),
103
- createdAt: z.number().int(),
104
- updatedAt: z.number().int(),
105
- /**
106
- * Whether this row is a Vellum-managed connection (`anthropic-managed`,
107
- * `openai-managed`, `gemini-managed`). Derived from
108
- * `MANAGED_CONNECTION_NAMES` in `connections.ts` at serialize time; the
109
- * DB column does not exist. Clients use this to render the read-only
110
- * "Vellum" badge + view-only editor and to disable the delete affordance
111
- * without mirroring the canonical name list locally.
112
- */
113
- isManaged: z.boolean(),
114
- });
100
+ export const ProviderConnectionSchema = z
101
+ .object({
102
+ name: z.string().min(1),
103
+ provider: ConnectionProviderSchema,
104
+ auth: AuthSchema,
105
+ label: z.string().min(1).nullable(),
106
+ baseUrl: z.string().url().nullable(),
107
+ models: z.array(ConnectionModelSchema).nullable(),
108
+ createdAt: z.number().int(),
109
+ updatedAt: z.number().int(),
110
+ /**
111
+ * Whether this row is a Vellum-managed connection (`anthropic-managed`,
112
+ * `openai-managed`, `gemini-managed`). Derived from
113
+ * `MANAGED_CONNECTION_NAMES` in `connections.ts` at serialize time; the
114
+ * DB column does not exist. Clients use this to render the read-only
115
+ * "Vellum" badge + view-only editor and to disable the delete affordance
116
+ * without mirroring the canonical name list locally.
117
+ */
118
+ isManaged: z.boolean(),
119
+ })
120
+ .meta({ id: "ProviderConnection" });
115
121
 
116
122
  export type ProviderConnection = z.infer<typeof ProviderConnectionSchema>;
@@ -13,7 +13,7 @@ const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
13
13
  anthropic: {
14
14
  balanced: "claude-sonnet-4-6",
15
15
  "latency-optimized": "claude-haiku-4-5-20251001",
16
- "quality-optimized": "claude-fable-5",
16
+ "quality-optimized": "claude-opus-4-8",
17
17
  "vision-optimized": "claude-opus-4-6",
18
18
  },
19
19
  openai: {
@@ -43,7 +43,7 @@ const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
43
43
  openrouter: {
44
44
  balanced: "anthropic/claude-sonnet-4.6",
45
45
  "latency-optimized": "anthropic/claude-haiku-4.5",
46
- "quality-optimized": "anthropic/claude-fable-5",
46
+ "quality-optimized": "anthropic/claude-opus-4.8",
47
47
  "vision-optimized": "anthropic/claude-opus-4.6",
48
48
  },
49
49
  };
@@ -0,0 +1,105 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ coerceObjectParamsToJsonString,
5
+ decodeCoercedObjectArgs,
6
+ } from "../coerce-object-args.js";
7
+
8
+ const SKILL_EXECUTE_SCHEMA = {
9
+ type: "object",
10
+ properties: {
11
+ tool: { type: "string", description: "The skill tool name" },
12
+ input: { type: "object", description: "Tool-specific parameters" },
13
+ activity: { type: "string", description: "Progress update" },
14
+ },
15
+ required: ["tool", "input", "activity"],
16
+ };
17
+
18
+ describe("coerceObjectParamsToJsonString", () => {
19
+ test("rewrites object params to strings and reports the keys", () => {
20
+ const { parameters, objectKeys } =
21
+ coerceObjectParamsToJsonString(SKILL_EXECUTE_SCHEMA);
22
+
23
+ expect(objectKeys).toEqual(["input"]);
24
+ const props = (
25
+ parameters as { properties: Record<string, { type: string }> }
26
+ ).properties;
27
+ expect(props.input.type).toBe("string");
28
+ // Scalars are left untouched.
29
+ expect(props.tool.type).toBe("string");
30
+ expect(props.activity.type).toBe("string");
31
+ // The required list is preserved.
32
+ expect((parameters as { required: string[] }).required).toEqual([
33
+ "tool",
34
+ "input",
35
+ "activity",
36
+ ]);
37
+ });
38
+
39
+ test("leaves array and scalar params alone (no coercion)", () => {
40
+ const schema = {
41
+ type: "object",
42
+ properties: {
43
+ query: { type: "string" },
44
+ sources: { type: "array", items: { type: "string" } },
45
+ },
46
+ };
47
+ const { parameters, objectKeys } = coerceObjectParamsToJsonString(schema);
48
+ expect(objectKeys).toEqual([]);
49
+ expect(parameters).toEqual(schema);
50
+ });
51
+
52
+ test("handles type unions that include object", () => {
53
+ const schema = {
54
+ type: "object",
55
+ properties: { data: { type: ["object", "null"] } },
56
+ };
57
+ const { objectKeys } = coerceObjectParamsToJsonString(schema);
58
+ expect(objectKeys).toEqual(["data"]);
59
+ });
60
+ });
61
+
62
+ describe("decodeCoercedObjectArgs", () => {
63
+ test("round-trips a JSON-string object back into an object", () => {
64
+ const { objectKeys } = coerceObjectParamsToJsonString(SKILL_EXECUTE_SCHEMA);
65
+ // What minimax would send once `input` is a string param.
66
+ const onWire = {
67
+ tool: "app_delete",
68
+ input: '{"app_id": "5ee3d2d5-46e8-4a79-928f-00a7471d340b"}',
69
+ activity: "Delete placeholder",
70
+ };
71
+ const { input, failedKey } = decodeCoercedObjectArgs(onWire, objectKeys);
72
+ expect(failedKey).toBeUndefined();
73
+ expect(input.input).toEqual({
74
+ app_id: "5ee3d2d5-46e8-4a79-928f-00a7471d340b",
75
+ });
76
+ // Scalars pass through unchanged.
77
+ expect(input.tool).toBe("app_delete");
78
+ });
79
+
80
+ test("empty string decodes to an empty object", () => {
81
+ const { input } = decodeCoercedObjectArgs({ input: "" }, ["input"]);
82
+ expect(input.input).toEqual({});
83
+ });
84
+
85
+ test("is idempotent when the value is already an object", () => {
86
+ const args = { input: { app_id: "x" } };
87
+ const { input, failedKey } = decodeCoercedObjectArgs(args, ["input"]);
88
+ expect(failedKey).toBeUndefined();
89
+ expect(input.input).toEqual({ app_id: "x" });
90
+ });
91
+
92
+ test("reports failedKey on invalid JSON instead of throwing", () => {
93
+ const { failedKey } = decodeCoercedObjectArgs(
94
+ { input: "{not valid json" },
95
+ ["input"],
96
+ );
97
+ expect(failedKey).toBe("input");
98
+ });
99
+
100
+ test("no-op when there are no coerced keys", () => {
101
+ const args = { tool: "x", input: "{}" };
102
+ const { input } = decodeCoercedObjectArgs(args, []);
103
+ expect(input).toEqual(args);
104
+ });
105
+ });