@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
@@ -1,5 +1,5 @@
1
1
  import { Cron } from "croner";
2
- import { and, asc, desc, eq, isNull, lte, sql } from "drizzle-orm";
2
+ import { and, asc, desc, eq, isNull, lt, lte, sql } from "drizzle-orm";
3
3
  import { v4 as uuid } from "uuid";
4
4
 
5
5
  import { getDb } from "../memory/db-connection.js";
@@ -26,7 +26,12 @@ function notifySchedulesChanged(): void {
26
26
  );
27
27
  }
28
28
 
29
- export type ScheduleMode = "notify" | "execute" | "script" | "wake";
29
+ export type ScheduleMode =
30
+ | "notify"
31
+ | "execute"
32
+ | "script"
33
+ | "wake"
34
+ | "workflow";
30
35
  export type RoutingIntent = "single_channel" | "multi_channel" | "all_channels";
31
36
  export type ScheduleStatus = "active" | "firing" | "fired" | "cancelled";
32
37
 
@@ -42,6 +47,12 @@ export interface ScheduleJob {
42
47
  message: string;
43
48
  script: string | null;
44
49
  wakeConversationId: string | null;
50
+ /** Saved workflow to trigger; only used when mode = 'workflow'. */
51
+ workflowName: string | null;
52
+ /** Args passed verbatim to the workflow run; only used when mode = 'workflow'. */
53
+ workflowArgs: unknown;
54
+ /** Capability manifest scoping the schedule's run; null = unconstrained. */
55
+ capabilities: unknown | null;
45
56
  nextRunAt: number;
46
57
  lastRunAt: number | null;
47
58
  lastStatus: string | null;
@@ -50,6 +61,11 @@ export interface ScheduleJob {
50
61
  retryBackoffMs: number;
51
62
  /** Script-mode execution timeout override (ms); null = use the default. */
52
63
  timeoutMs: number | null;
64
+ /**
65
+ * Inference profile (`llm.profiles` key) applied to the schedule's
66
+ * LLM-executed runs; null = default main-agent model selection.
67
+ */
68
+ inferenceProfile: string | null;
53
69
  createdFromConversationId: string | null;
54
70
  createdBy: string;
55
71
  mode: ScheduleMode;
@@ -92,6 +108,9 @@ export function createSchedule(params: {
92
108
  message: string;
93
109
  script?: string | null;
94
110
  wakeConversationId?: string | null;
111
+ workflowName?: string | null;
112
+ workflowArgs?: unknown;
113
+ capabilities?: unknown;
95
114
  enabled?: boolean;
96
115
  createdBy?: string;
97
116
  syntax?: ScheduleSyntax;
@@ -105,6 +124,7 @@ export function createSchedule(params: {
105
124
  maxRetries?: number;
106
125
  retryBackoffMs?: number;
107
126
  timeoutMs?: number | null;
127
+ inferenceProfile?: string | null;
108
128
  createdFromConversationId?: string | null;
109
129
  }): ScheduleJob {
110
130
  const expression = params.expression ?? params.cronExpression ?? null;
@@ -142,6 +162,7 @@ export function createSchedule(params: {
142
162
  const maxRetries = params.maxRetries ?? 3;
143
163
  const retryBackoffMs = params.retryBackoffMs ?? 60000;
144
164
  const timeoutMs = params.timeoutMs ?? null;
165
+ const inferenceProfile = params.inferenceProfile ?? null;
145
166
  const createdFromConversationId = params.createdFromConversationId ?? null;
146
167
  const description = normalizeDescription(
147
168
  params.description,
@@ -168,6 +189,13 @@ export function createSchedule(params: {
168
189
  message: params.message,
169
190
  script: params.script ?? null,
170
191
  wakeConversationId: params.wakeConversationId ?? null,
192
+ workflowName: params.workflowName ?? null,
193
+ workflowArgsJson:
194
+ params.workflowArgs === undefined
195
+ ? null
196
+ : JSON.stringify(params.workflowArgs),
197
+ capabilitiesJson:
198
+ params.capabilities != null ? JSON.stringify(params.capabilities) : null,
171
199
  nextRunAt,
172
200
  lastRunAt: null as number | null,
173
201
  lastStatus: null as string | null,
@@ -175,6 +203,7 @@ export function createSchedule(params: {
175
203
  maxRetries,
176
204
  retryBackoffMs,
177
205
  timeoutMs,
206
+ inferenceProfile,
178
207
  createdFromConversationId,
179
208
  createdBy: params.createdBy ?? "agent",
180
209
  mode,
@@ -273,9 +302,13 @@ export function updateSchedule(
273
302
  quiet?: boolean;
274
303
  reuseConversation?: boolean;
275
304
  wakeConversationId?: string | null;
305
+ workflowName?: string | null;
306
+ workflowArgs?: unknown;
307
+ capabilities?: unknown;
276
308
  maxRetries?: number;
277
309
  retryBackoffMs?: number;
278
310
  timeoutMs?: number | null;
311
+ inferenceProfile?: string | null;
279
312
  createdFromConversationId?: string | null;
280
313
  },
281
314
  ): ScheduleJob | null {
@@ -340,10 +373,28 @@ export function updateSchedule(
340
373
  set.reuseConversation = updates.reuseConversation;
341
374
  if (updates.wakeConversationId !== undefined)
342
375
  set.wakeConversationId = updates.wakeConversationId;
376
+ if (updates.workflowName !== undefined)
377
+ set.workflowName = updates.workflowName;
378
+ // `workflowArgs` may legitimately be any JSON value (including null), so
379
+ // detect presence by key rather than `!== undefined`.
380
+ if ("workflowArgs" in updates)
381
+ set.workflowArgsJson =
382
+ updates.workflowArgs === undefined
383
+ ? null
384
+ : JSON.stringify(updates.workflowArgs);
385
+ // `capabilities` may legitimately be any JSON value (including null), so
386
+ // detect presence by key rather than `!== undefined`.
387
+ if ("capabilities" in updates)
388
+ set.capabilitiesJson =
389
+ updates.capabilities == null
390
+ ? null
391
+ : JSON.stringify(updates.capabilities);
343
392
  if (updates.maxRetries !== undefined) set.maxRetries = updates.maxRetries;
344
393
  if (updates.retryBackoffMs !== undefined)
345
394
  set.retryBackoffMs = updates.retryBackoffMs;
346
395
  if (updates.timeoutMs !== undefined) set.timeoutMs = updates.timeoutMs;
396
+ if (updates.inferenceProfile !== undefined)
397
+ set.inferenceProfile = updates.inferenceProfile;
347
398
  if (updates.createdFromConversationId !== undefined)
348
399
  set.createdFromConversationId = updates.createdFromConversationId;
349
400
 
@@ -545,6 +596,41 @@ export function failOneShot(id: string): void {
545
596
  if (rawChanges() > 0) notifySchedulesChanged();
546
597
  }
547
598
 
599
+ /**
600
+ * Re-arm a just-claimed schedule so it is due again on the very next tick,
601
+ * WITHOUT counting as a run or bumping the retry count. Sets status back to
602
+ * 'active' and pulls `nextRunAt` back to now: `claimDueSchedules` advances
603
+ * `nextRunAt` for recurring jobs (and flips one-shots to 'firing'), so without
604
+ * resetting both the claimed occurrence would be dropped until the next cron
605
+ * time. Used when a tick claims a job it cannot yet process (e.g. a workflow
606
+ * schedule that fired before the tool registry finished initializing at boot);
607
+ * unlike a failure path it does not touch `retryCount`, since the deferral is
608
+ * not the schedule's fault. Keyed by id only (no status guard) because recurring
609
+ * claims leave the row 'active' while one-shot claims leave it 'firing', and it
610
+ * runs immediately after the claim within the same tick.
611
+ *
612
+ * Also restores `enabled: true`. `claimDueSchedules` disables a row whose
613
+ * claimed occurrence was its LAST (a one-shot, or the final fire of a finite
614
+ * RRULE), but the due-claim query requires `enabled = true` — so a deferred
615
+ * final occurrence would never be re-claimed and the run would be silently lost.
616
+ * The deferred occurrence has not actually run yet, so re-enabling is correct;
617
+ * when it later fires, the claim path re-applies the right `enabled` state.
618
+ */
619
+ export function deferClaimedSchedule(id: string): void {
620
+ const db = getDb();
621
+ const now = Date.now();
622
+ db.update(scheduleJobs)
623
+ .set({
624
+ status: "active",
625
+ enabled: true,
626
+ nextRunAt: now,
627
+ updatedAt: now,
628
+ })
629
+ .where(eq(scheduleJobs.id, id))
630
+ .run();
631
+ if (rawChanges() > 0) notifySchedulesChanged();
632
+ }
633
+
548
634
  /**
549
635
  * Revert a one-shot from 'firing' back to 'active' and increment its
550
636
  * retry count. Used when a wake times out waiting for an idle conversation
@@ -718,12 +804,26 @@ export function getLastScheduleConversationId(jobId: string): string | null {
718
804
  return row?.conversationId ?? null;
719
805
  }
720
806
 
721
- export function getScheduleRuns(jobId: string, limit?: number): ScheduleRun[] {
807
+ /**
808
+ * List runs for a schedule, newest first. When `before` is set, only runs
809
+ * with `createdAt` strictly older than it are returned (cursor for
810
+ * paginating into history).
811
+ */
812
+ export function getScheduleRuns(
813
+ jobId: string,
814
+ limit?: number,
815
+ before?: number,
816
+ ): ScheduleRun[] {
722
817
  const db = getDb();
723
818
  const rows = db
724
819
  .select()
725
820
  .from(scheduleRuns)
726
- .where(eq(scheduleRuns.jobId, jobId))
821
+ .where(
822
+ and(
823
+ eq(scheduleRuns.jobId, jobId),
824
+ before != null ? lt(scheduleRuns.createdAt, before) : undefined,
825
+ ),
826
+ )
727
827
  .orderBy(desc(scheduleRuns.createdAt))
728
828
  .limit(limit ?? 10)
729
829
  .all();
@@ -886,6 +986,36 @@ export function describeCronExpression(expr: string | null): string {
886
986
  }
887
987
  }
888
988
 
989
+ // Stepped or fixed minutes constrained to a contiguous range of hours,
990
+ // every day/month (e.g. "*/30 7-23 * * *" → "Every 30 minutes, 7 AM–11 PM").
991
+ const hoursAreContiguousRange =
992
+ !allHours &&
993
+ activeHours.length > 1 &&
994
+ activeHours.every((h, i) => i === 0 || h === activeHours[i - 1] + 1);
995
+
996
+ if (hoursAreContiguousRange && anyDayAndMonth) {
997
+ const hourLabel = (h: number) => {
998
+ const period = h >= 12 ? "PM" : "AM";
999
+ return `${h % 12 || 12} ${period}`;
1000
+ };
1001
+ const rangeStr = `${hourLabel(activeHours[0])}–${hourLabel(
1002
+ activeHours[activeHours.length - 1],
1003
+ )}`;
1004
+
1005
+ if (steppedMinutes && activeMinutes[0] === 0) {
1006
+ const step = activeMinutes[1] - activeMinutes[0];
1007
+ const isRegularStep = activeMinutes.every((v, i) => v === i * step);
1008
+ if (isRegularStep && 60 % step === 0) {
1009
+ return `Every ${step} minutes, ${rangeStr}`;
1010
+ }
1011
+ }
1012
+ if (fixedMinute) {
1013
+ return activeMinutes[0] === 0
1014
+ ? `Hourly, ${rangeStr}`
1015
+ : `Hourly at minute ${activeMinutes[0]}, ${rangeStr}`;
1016
+ }
1017
+ }
1018
+
889
1019
  // Fallback: return the raw expression
890
1020
  return expr;
891
1021
  } catch {
@@ -999,6 +1129,9 @@ function parseJobRow(row: typeof scheduleJobs.$inferSelect): ScheduleJob {
999
1129
  message: row.message,
1000
1130
  script: row.script ?? null,
1001
1131
  wakeConversationId: row.wakeConversationId ?? null,
1132
+ workflowName: row.workflowName ?? null,
1133
+ workflowArgs: parseOptionalJson(row.workflowArgsJson),
1134
+ capabilities: parseOptionalJson(row.capabilitiesJson),
1002
1135
  nextRunAt: row.nextRunAt,
1003
1136
  lastRunAt: row.lastRunAt,
1004
1137
  lastStatus: row.lastStatus,
@@ -1006,6 +1139,7 @@ function parseJobRow(row: typeof scheduleJobs.$inferSelect): ScheduleJob {
1006
1139
  maxRetries: row.maxRetries ?? 3,
1007
1140
  retryBackoffMs: row.retryBackoffMs ?? 60000,
1008
1141
  timeoutMs: row.timeoutMs ?? null,
1142
+ inferenceProfile: row.inferenceProfile ?? null,
1009
1143
  createdFromConversationId: row.createdFromConversationId ?? null,
1010
1144
  createdBy: row.createdBy,
1011
1145
  mode: (row.mode ?? "execute") as ScheduleMode,
@@ -1038,6 +1172,20 @@ function safeParseJson(
1038
1172
  }
1039
1173
  }
1040
1174
 
1175
+ /**
1176
+ * Parse a nullable JSON column into an arbitrary value. Unlike
1177
+ * {@link safeParseJson}, the result is not coerced to an object — workflow
1178
+ * args may be any JSON value — and an absent/unparseable column yields null.
1179
+ */
1180
+ function parseOptionalJson(json: string | null | undefined): unknown {
1181
+ if (json == null) return null;
1182
+ try {
1183
+ return JSON.parse(json);
1184
+ } catch {
1185
+ return null;
1186
+ }
1187
+ }
1188
+
1041
1189
  function parseRunRow(row: typeof scheduleRuns.$inferSelect): ScheduleRun {
1042
1190
  return {
1043
1191
  id: row.id,
@@ -16,6 +16,12 @@ export interface ScheduleMessageOptions {
16
16
  * the default `mainAgent` route.
17
17
  */
18
18
  callSite?: LLMCallSite;
19
+ /**
20
+ * Optional ad-hoc inference-profile override (`llm.profiles` key) applied
21
+ * to every LLM call the run issues — a schedule's pinned profile. Omitted
22
+ * = the call site's default resolution (main-agent model selection).
23
+ */
24
+ overrideProfile?: string;
19
25
  }
20
26
 
21
27
  export type ScheduleMessageProcessor = (
@@ -3,6 +3,7 @@ import {
3
3
  diskPressureBackgroundSkipLogFields,
4
4
  shouldLogDiskPressureBackgroundSkip,
5
5
  } from "../daemon/disk-pressure-background-gate.js";
6
+ import { INTERNAL_GUARDIAN_TRUST_CONTEXT } from "../daemon/trust-context.js";
6
7
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
7
8
  import { getConversation } from "../memory/conversation-crud.js";
8
9
  import { invalidateAssistantInferredItemsForConversation } from "../memory/task-memory-cleanup.js";
@@ -10,8 +11,11 @@ import { emitNotificationSignal } from "../notifications/emit-signal.js";
10
11
  import { wakeAgentForOpportunity } from "../runtime/agent-wake.js";
11
12
  import { runBackgroundJob } from "../runtime/background-job-runner.js";
12
13
  import { runSequencesOnce } from "../sequence/engine.js";
14
+ import { areCoreToolsInitialized } from "../tools/registry.js";
13
15
  import { getLogger } from "../util/logger.js";
14
16
  import { runWatchersOnce, type WatcherNotifier } from "../watcher/engine.js";
17
+ import { normalizeCapabilityManifest } from "../workflows/capabilities.js";
18
+ import { getWorkflowRunManager } from "../workflows/run-manager.js";
15
19
  import { hasSetConstructs } from "./recurrence-engine.js";
16
20
  import { applyRetryDecision, decideRetry } from "./retry-policy.js";
17
21
  import { runScript, type ScriptResult } from "./run-script.js";
@@ -20,6 +24,7 @@ import {
20
24
  completeOneShot,
21
25
  completeScheduleRun,
22
26
  createScheduleRun,
27
+ deferClaimedSchedule,
23
28
  failOneShotPermanently,
24
29
  getLastScheduleConversationId,
25
30
  listSchedules,
@@ -357,6 +362,9 @@ export async function runScheduleDueWorkOnce(
357
362
  conversationId: wakeConversationId,
358
363
  hint: job.message,
359
364
  source: "defer",
365
+ ...(job.inferenceProfile
366
+ ? { forceOverrideProfile: job.inferenceProfile }
367
+ : {}),
360
368
  });
361
369
 
362
370
  if (result.reason === "timeout" && isOneShot) {
@@ -433,6 +441,85 @@ export async function runScheduleDueWorkOnce(
433
441
  continue;
434
442
  }
435
443
 
444
+ // ── Workflow mode (trigger a saved workflow by name) ────────────
445
+ if (job.mode === "workflow") {
446
+ if (!job.workflowName) {
447
+ log.warn(
448
+ { jobId: job.id, name: job.name },
449
+ "Workflow schedule has no workflowName — skipping",
450
+ );
451
+ mark("skipped");
452
+ continue;
453
+ }
454
+ // Boot race: the scheduler starts before initializeProvidersAndTools()
455
+ // registers the read-only baseline (file_read/web_fetch/etc.) that
456
+ // resolveCapabilities grants to every workflow run. Launching now would
457
+ // give the run an EMPTY baseline and degrade/fail it. Defer the claimed
458
+ // job back to due so it fires on a later tick once tools are registered
459
+ // (the window is just the few seconds of daemon boot). Non-workflow modes
460
+ // don't depend on the baseline, so only this branch gates.
461
+ if (!areCoreToolsInitialized()) {
462
+ log.info(
463
+ { jobId: job.id, name: job.name, workflowName: job.workflowName },
464
+ "Deferring workflow schedule until tools are registered",
465
+ );
466
+ deferClaimedSchedule(job.id);
467
+ mark("skipped");
468
+ continue;
469
+ }
470
+ const runId = createScheduleRun(job.id, `workflow:${job.id}`);
471
+ let failed = false;
472
+ try {
473
+ log.info(
474
+ {
475
+ jobId: job.id,
476
+ name: job.name,
477
+ workflowName: job.workflowName,
478
+ isOneShot,
479
+ },
480
+ "Triggering workflow schedule",
481
+ );
482
+ const { runId: workflowRunId } = getWorkflowRunManager().start({
483
+ name: job.workflowName,
484
+ args: job.workflowArgs ?? {},
485
+ // Where the completion summary is delivered (an agent wake). Prefer an
486
+ // explicit wake target, then fall back to the conversation that
487
+ // created the schedule — workflow schedules made via `schedule_create`
488
+ // store that as `createdFromConversationId` and leave
489
+ // `wakeConversationId` unset, so without this fallback their result
490
+ // would surface only to live SSE listeners / the DB, never delivered.
491
+ conversationId:
492
+ job.wakeConversationId ??
493
+ job.createdFromConversationId ??
494
+ undefined,
495
+ // The schedule's persisted capability manifest scopes the run; a
496
+ // legacy schedule with null `capabilities` normalizes to the read-only
497
+ // baseline.
498
+ manifest: normalizeCapabilityManifest(job.capabilities),
499
+ trustContext: INTERNAL_GUARDIAN_TRUST_CONTEXT,
500
+ });
501
+ // `start` launches the run fire-and-forget and returns synchronously;
502
+ // a successful trigger is recorded as ok. Workflow completion/failure
503
+ // is surfaced out-of-band via workflow events and the completion wake.
504
+ completeScheduleRun(runId, {
505
+ status: "ok",
506
+ output: `workflow run ${workflowRunId} started`,
507
+ });
508
+ if (isOneShot) completeOneShot(job.id);
509
+ } catch (err) {
510
+ const errorMsg = err instanceof Error ? err.message : String(err);
511
+ log.warn(
512
+ { err, jobId: job.id, name: job.name, isOneShot },
513
+ "Workflow schedule trigger failed",
514
+ );
515
+ completeScheduleRun(runId, { status: "error", error: errorMsg });
516
+ handleExecutionFailure({ job, errorMsg, isOneShot });
517
+ failed = true;
518
+ }
519
+ mark(failed ? "failed" : "completed");
520
+ continue;
521
+ }
522
+
436
523
  // ── Execute mode ────────────────────────────────────────────────
437
524
 
438
525
  // Check if message is a task invocation (run_task:<task_id>)
@@ -470,6 +557,9 @@ export async function runScheduleDueWorkOnce(
470
557
  await processMessage(conversationId, message, {
471
558
  trustClass: "guardian",
472
559
  taskRunId,
560
+ ...(job.inferenceProfile
561
+ ? { overrideProfile: job.inferenceProfile }
562
+ : {}),
473
563
  });
474
564
  },
475
565
  );
@@ -602,6 +692,9 @@ export async function runScheduleDueWorkOnce(
602
692
  try {
603
693
  await processMessage(conversationId, job.message, {
604
694
  trustClass: "guardian",
695
+ ...(job.inferenceProfile
696
+ ? { overrideProfile: job.inferenceProfile }
697
+ : {}),
605
698
  });
606
699
  ok = true;
607
700
  } catch (err) {
@@ -622,6 +715,9 @@ export async function runScheduleDueWorkOnce(
622
715
  systemHint: `Schedule: ${job.name}`,
623
716
  trustContext: { sourceChannel: "vellum", trustClass: "guardian" },
624
717
  callSite: "mainAgent",
718
+ ...(job.inferenceProfile
719
+ ? { overrideProfile: job.inferenceProfile }
720
+ : {}),
625
721
  timeoutMs: SCHEDULE_TALK_TIMEOUT_MS,
626
722
  origin: "schedule",
627
723
  groupId: "system:scheduled",
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * User-defined allowlist for suppressing secret scanner false positives.
3
3
  *
4
- * Reads `~/.vellum/workspace/data/secret-allowlist.json` (if present) and provides
4
+ * Reads `$VELLUM_WORKSPACE_DIR/data/secret-allowlist.json` (if present) and provides
5
5
  * `isAllowlisted(value)` to check whether a matched value should be
6
6
  * suppressed.
7
7
  *
@@ -6,7 +6,7 @@ import { getWorkspaceSkillsDir } from "../util/platform.js";
6
6
 
7
7
  /**
8
8
  * Returns the managed skills root directory. Managed skills are user-installed
9
- * skills that live under ~/.vellum/workspace/skills.
9
+ * skills that live under $VELLUM_WORKSPACE_DIR/skills.
10
10
  */
11
11
  export function getManagedSkillsRoot(): string {
12
12
  return normalizeDirPath(getWorkspaceSkillsDir());
@@ -12,6 +12,7 @@ import { compileApp } from "../../bundler/app-compiler.js";
12
12
  import { generateAppIcon } from "../../media/app-icon-generator.js";
13
13
  import type { AppDefinition } from "../../memory/app-store.js";
14
14
  import { getAppDirPath } from "../../memory/app-store.js";
15
+ import { getLogger } from "../../util/logger.js";
15
16
 
16
17
  // ---------------------------------------------------------------------------
17
18
  // Shared result type
@@ -55,6 +56,11 @@ export interface AppStoreWriter {
55
56
  ): AppDefinition;
56
57
  deleteApp(id: string): void;
57
58
  writeAppFile(appId: string, path: string, content: string): void;
59
+ /**
60
+ * Associate a freshly created app with the conversation that created it.
61
+ * Optional so test doubles need not implement it; the real store does.
62
+ */
63
+ addAppConversationId?(appId: string, conversationId: string): boolean;
58
64
  }
59
65
 
60
66
  export type AppStore = AppStoreReader & AppStoreWriter;
@@ -158,6 +164,7 @@ export async function executeAppCreate(
158
164
  input: AppCreateInput,
159
165
  store: AppStore,
160
166
  proxyToolResolver?: ProxyResolver,
167
+ conversationId?: string,
161
168
  ): Promise<ExecutorResult> {
162
169
  // The model sometimes omits a name; resolve a sensible one rather than
163
170
  // erroring out so the build still succeeds. Users can rename via app_update.
@@ -213,6 +220,22 @@ export async function executeAppCreate(
213
220
  formatVersion: 2,
214
221
  });
215
222
 
223
+ // Associate the app with its conversation at creation so subsequent
224
+ // `app_*` calls in the same turn can resolve it even when the model omits
225
+ // `app_id` (see resolveAppId). Without this the link is only formed at
226
+ // `app_open`, leaving the create→update→refresh gap unresolvable.
227
+ // Best-effort: a failed association must never fail the create.
228
+ if (conversationId && store.addAppConversationId) {
229
+ try {
230
+ store.addAppConversationId(app.id, conversationId);
231
+ } catch (err) {
232
+ getLogger("app-executors").debug(
233
+ { err, appId: app.id, conversationId },
234
+ "Failed to associate app with conversation at create",
235
+ );
236
+ }
237
+ }
238
+
216
239
  // Scaffold multifile app with src/ files and compile to dist/
217
240
  const htmlSafeName = name
218
241
  .replace(/&/g, "&amp;")
@@ -0,0 +1,42 @@
1
+ import { listAppsByConversation } from "../../memory/app-store.js";
2
+
3
+ /**
4
+ * Resolve the `app_id` an app-builder tool should operate on.
5
+ *
6
+ * Weaker models routinely omit `app_id` when calling `app_*` tools through
7
+ * `skill_execute`, even though the active app's id is in their context from the
8
+ * preceding `app_create`/`app_update` result. Left unhandled, the executor
9
+ * fails with a cryptic "Invalid ID: undefined", the model retries the same
10
+ * empty call, and the turn burns many steps before recovering.
11
+ *
12
+ * An explicit, non-empty `app_id` always wins. When it is missing, fall back to
13
+ * the conversation's most-recently-updated app — the one being actively built.
14
+ * Returns `null` when no app_id is supplied and the conversation has no app, so
15
+ * callers can surface an actionable error instead of a raw store throw.
16
+ *
17
+ * Not used for destructive operations (`app_delete`): deleting an inferred app
18
+ * is unsafe, so deletion requires an explicit id.
19
+ */
20
+ export function resolveAppId(
21
+ input: Record<string, unknown>,
22
+ conversationId: string,
23
+ ): string | null {
24
+ if (typeof input.app_id === "string" && input.app_id.trim().length > 0) {
25
+ return input.app_id;
26
+ }
27
+ // `listAppsByConversation` preserves `listApps`' updatedAt-descending order,
28
+ // so the first entry is the app the model is actively working on.
29
+ const apps = listAppsByConversation(conversationId);
30
+ return apps.length > 0 ? apps[0].id : null;
31
+ }
32
+
33
+ /** Error payload returned when no app_id is supplied and none can be inferred. */
34
+ export function missingAppIdError(): { content: string; isError: boolean } {
35
+ return {
36
+ content: JSON.stringify({
37
+ error:
38
+ "app_id is required and no active app exists in this conversation. Call app_create first, or pass app_id explicitly.",
39
+ }),
40
+ isError: true,
41
+ };
42
+ }
@@ -669,11 +669,10 @@ export async function executeBrowserNavigate(
669
669
  const newTab = input.new_tab === true;
670
670
  if (newTab && cdp.kind === "extension") {
671
671
  try {
672
- const result = await cdp.send<{ tabId?: number | string; clientId?: string }>(
673
- "Vellum.createTab",
674
- {},
675
- context.signal,
676
- );
672
+ const result = await cdp.send<{
673
+ tabId?: number | string;
674
+ clientId?: string;
675
+ }>("Vellum.createTab", {}, context.signal);
677
676
  const tabId =
678
677
  typeof result?.tabId === "number"
679
678
  ? String(result.tabId)
@@ -720,8 +719,7 @@ export async function executeBrowserNavigate(
720
719
  // We're early-returning before the main try/finally block below,
721
720
  // so we must dispose the cdp client manually to avoid leaking it.
722
721
  clearPinnedTab(context.conversationId);
723
- const message =
724
- err instanceof Error ? err.message : String(err);
722
+ const message = err instanceof Error ? err.message : String(err);
725
723
  log.warn(
726
724
  { conversationId: context.conversationId, err },
727
725
  "Vellum.createTab failed; aborting --new-tab navigate",
@@ -1075,7 +1073,7 @@ export async function executeBrowserNavigate(
1075
1073
  "1. Take a snapshot to find the sign-in form elements",
1076
1074
  );
1077
1075
  lines.push(
1078
- "2. Use credential fill to enter email/password from credential_store",
1076
+ "2. Use credential fill to enter email/password from the credential vault",
1079
1077
  );
1080
1078
  lines.push(
1081
1079
  "3. For email verification codes, use ui_show with a form to request the code mid-turn",
@@ -1102,7 +1100,7 @@ export async function executeBrowserNavigate(
1102
1100
  lines.push("Handle this by interacting with the login form:");
1103
1101
  lines.push("1. Take a snapshot to find the sign-in form elements");
1104
1102
  lines.push(
1105
- "2. Use credential fill to enter email/password from credential_store",
1103
+ "2. Use credential fill to enter email/password from the credential vault",
1106
1104
  );
1107
1105
  lines.push(
1108
1106
  "3. For email verification codes, use ui_show with a form to request the code mid-turn",
@@ -2175,13 +2173,13 @@ export async function executeBrowserFillCredential(
2175
2173
  reason.includes("no stored value")
2176
2174
  ) {
2177
2175
  return {
2178
- content: `No credential stored for ${service}/${field}. Use credential_store to save it first.`,
2176
+ content: `No credential stored for ${service}/${field}. Use \`assistant credentials set\` to save it first.`,
2179
2177
  isError: true,
2180
2178
  };
2181
2179
  }
2182
2180
  if (reason.includes("not allowed to use credential")) {
2183
2181
  return {
2184
- content: `Policy denied: ${reason} Update the credential's allowed_tools via credential_store if this tool should have access.`,
2182
+ content: `Policy denied: ${reason} Update the credential's allowed_tools via \`assistant credentials set\` if this tool should have access.`,
2185
2183
  isError: true,
2186
2184
  };
2187
2185
  }
@@ -76,7 +76,7 @@ export class CredentialBroker {
76
76
  reason:
77
77
  `Tool "${request.toolName}" is not allowed to use credential ${request.service}/${request.field}. ` +
78
78
  (tools.length === 0
79
- ? "No tools are currently allowed - update the credential with allowed_tools via credential_store."
79
+ ? "No tools are currently allowed - update the credential's allowed_tools via `assistant credentials set`."
80
80
  : `Allowed tools: ${tools.join(", ")}.`),
81
81
  };
82
82
  }
@@ -186,7 +186,7 @@ export class CredentialBroker {
186
186
  reason:
187
187
  `Tool "${request.toolName}" is not allowed to use credential ${request.service}/${request.field}. ` +
188
188
  (tools.length === 0
189
- ? "No tools are currently allowed - update the credential with allowed_tools via credential_store."
189
+ ? "No tools are currently allowed - update the credential's allowed_tools via `assistant credentials set`."
190
190
  : `Allowed tools: ${tools.join(", ")}.`),
191
191
  };
192
192
  }
@@ -282,7 +282,7 @@ export class CredentialBroker {
282
282
  reason:
283
283
  `Tool "${request.toolName}" is not allowed to use credential ${request.service}/${request.field}. ` +
284
284
  (tools.length === 0
285
- ? "No tools are currently allowed - update the credential with allowed_tools via credential_store."
285
+ ? "No tools are currently allowed - update the credential's allowed_tools via `assistant credentials set`."
286
286
  : `Allowed tools: ${tools.join(", ")}.`),
287
287
  };
288
288
  }
@@ -365,7 +365,7 @@ export class CredentialBroker {
365
365
  reason:
366
366
  `Tool "${request.requestingTool}" is not allowed to use credential ${metadata.service}/${metadata.field}. ` +
367
367
  (tools.length === 0
368
- ? "No tools are currently allowed - update the credential with allowed_tools via credential_store."
368
+ ? "No tools are currently allowed - update the credential's allowed_tools via `assistant credentials set`."
369
369
  : `Allowed tools: ${tools.join(", ")}.`),
370
370
  };
371
371
  }