@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
@@ -7,11 +7,14 @@
7
7
 
8
8
  import { z } from "zod";
9
9
 
10
+ import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
11
+ import { getConfig } from "../../config/loader.js";
10
12
  import { getOrCreateConversation } from "../../daemon/conversation-store.js";
11
13
  import { INTERNAL_GUARDIAN_TRUST_CONTEXT } from "../../daemon/trust-context.js";
12
14
  import { bootstrapConversation } from "../../memory/conversation-bootstrap.js";
13
15
  import { getConversation } from "../../memory/conversation-crud.js";
14
16
  import { getUsageCostForConversationWindow } from "../../memory/llm-usage-store.js";
17
+ import { validateScheduleInferenceProfile } from "../../schedule/inference-profile.js";
15
18
  import {
16
19
  describeRRuleExpression,
17
20
  isSingleFireRRule,
@@ -37,10 +40,25 @@ import {
37
40
  updateSchedule,
38
41
  } from "../../schedule/schedule-store.js";
39
42
  import { getScheduleUsageSummaries } from "../../schedule/schedule-usage-store.js";
43
+ import { areCoreToolsInitialized } from "../../tools/registry.js";
40
44
  import { getLogger } from "../../util/logger.js";
45
+ import { normalizeCapabilityManifest } from "../../workflows/capabilities.js";
46
+ import { getWorkflowRunManager } from "../../workflows/run-manager.js";
41
47
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
42
48
  import { parseEpochMillisRange } from "./epoch-millis-range.js";
43
- import { BadRequestError, InternalError, NotFoundError } from "./errors.js";
49
+ import {
50
+ BadRequestError,
51
+ InternalError,
52
+ NotFoundError,
53
+ ServiceUnavailableError,
54
+ } from "./errors.js";
55
+ import {
56
+ paginateRuns,
57
+ parseRunsBeforeCursor,
58
+ parseRunsLimit,
59
+ RUNS_NEXT_CURSOR_SCHEMA,
60
+ RUNS_PAGINATION_QUERY_PARAMS,
61
+ } from "./runs-pagination.js";
44
62
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
45
63
 
46
64
  const log = getLogger("schedule-routes");
@@ -66,16 +84,18 @@ const scheduleSchema = z.object({
66
84
  maxRetries: z.number(),
67
85
  retryBackoffMs: z.number(),
68
86
  timeoutMs: z.number().nullable(),
87
+ inferenceProfile: z.string().nullable(),
69
88
  createdFromConversationId: z.string().nullable(),
70
89
  createdFromConversationExists: z.boolean(),
71
90
  createdFromConversationArchivedAt: z.number().nullable(),
72
91
  description: z.string(),
73
92
  cadenceDescription: z.string(),
74
- mode: z.enum(["notify", "execute", "script", "wake"]),
93
+ mode: z.enum(["notify", "execute", "script", "wake", "workflow"]),
75
94
  status: z.enum(["active", "firing", "fired", "cancelled"]),
76
95
  routingIntent: z.enum(["single_channel", "multi_channel", "all_channels"]),
77
96
  reuseConversation: z.boolean(),
78
97
  wakeConversationId: z.string().nullable(),
98
+ workflowName: z.string().nullable(),
79
99
  isOneShot: z.boolean(),
80
100
  });
81
101
 
@@ -156,6 +176,47 @@ function isOneShotForDisplay(
156
176
  return job.syntax === "rrule" && isSingleFireRRule(job.cronExpression);
157
177
  }
158
178
 
179
+ function serializeSchedule(
180
+ j: ScheduleJob,
181
+ sourceConversationCache: Map<string, CreatedFromConversationState>,
182
+ ) {
183
+ const sourceConversation = getCreatedFromConversationState(
184
+ j.createdFromConversationId,
185
+ sourceConversationCache,
186
+ );
187
+ return {
188
+ id: j.id,
189
+ name: j.name,
190
+ enabled: j.enabled,
191
+ syntax: j.syntax,
192
+ expression: j.expression,
193
+ cronExpression: j.cronExpression,
194
+ timezone: j.timezone,
195
+ message: j.message,
196
+ script: j.script,
197
+ nextRunAt: j.nextRunAt,
198
+ lastRunAt: j.lastRunAt,
199
+ lastStatus: j.lastStatus,
200
+ retryCount: j.retryCount,
201
+ maxRetries: j.maxRetries,
202
+ retryBackoffMs: j.retryBackoffMs,
203
+ timeoutMs: j.timeoutMs,
204
+ inferenceProfile: j.inferenceProfile,
205
+ createdFromConversationId: j.createdFromConversationId,
206
+ createdFromConversationExists: sourceConversation.exists,
207
+ createdFromConversationArchivedAt: sourceConversation.archivedAt,
208
+ description: j.description,
209
+ cadenceDescription: getCadenceDescription(j),
210
+ mode: j.mode,
211
+ status: j.status,
212
+ routingIntent: j.routingIntent,
213
+ reuseConversation: j.reuseConversation,
214
+ wakeConversationId: j.wakeConversationId,
215
+ workflowName: j.workflowName,
216
+ isOneShot: isOneShotForDisplay(j),
217
+ };
218
+ }
219
+
159
220
  function handleListSchedules(queryParams: Record<string, string>) {
160
221
  const includeAll = queryParams.include_all === "true";
161
222
  const jobs = listSchedules();
@@ -167,44 +228,20 @@ function handleListSchedules(queryParams: Record<string, string>) {
167
228
  CreatedFromConversationState
168
229
  >();
169
230
  return {
170
- schedules: filtered.map((j) => {
171
- const sourceConversation = getCreatedFromConversationState(
172
- j.createdFromConversationId,
173
- sourceConversationCache,
174
- );
175
- return {
176
- id: j.id,
177
- name: j.name,
178
- enabled: j.enabled,
179
- syntax: j.syntax,
180
- expression: j.expression,
181
- cronExpression: j.cronExpression,
182
- timezone: j.timezone,
183
- message: j.message,
184
- script: j.script,
185
- nextRunAt: j.nextRunAt,
186
- lastRunAt: j.lastRunAt,
187
- lastStatus: j.lastStatus,
188
- retryCount: j.retryCount,
189
- maxRetries: j.maxRetries,
190
- retryBackoffMs: j.retryBackoffMs,
191
- timeoutMs: j.timeoutMs,
192
- createdFromConversationId: j.createdFromConversationId,
193
- createdFromConversationExists: sourceConversation.exists,
194
- createdFromConversationArchivedAt: sourceConversation.archivedAt,
195
- description: j.description,
196
- cadenceDescription: getCadenceDescription(j),
197
- mode: j.mode,
198
- status: j.status,
199
- routingIntent: j.routingIntent,
200
- reuseConversation: j.reuseConversation,
201
- wakeConversationId: j.wakeConversationId,
202
- isOneShot: isOneShotForDisplay(j),
203
- };
204
- }),
231
+ schedules: filtered.map((j) =>
232
+ serializeSchedule(j, sourceConversationCache),
233
+ ),
205
234
  };
206
235
  }
207
236
 
237
+ function handleGetSchedule(id: string) {
238
+ const job = getSchedule(id);
239
+ if (!job) {
240
+ throw new NotFoundError("Schedule not found");
241
+ }
242
+ return { schedule: serializeSchedule(job, new Map()) };
243
+ }
244
+
208
245
  function handleCreateSchedule(body: Record<string, unknown>) {
209
246
  const name = typeof body.name === "string" ? body.name.trim() : "";
210
247
  const expression =
@@ -221,19 +258,36 @@ function handleCreateSchedule(body: Record<string, unknown>) {
221
258
  const timezone = timezoneRaw === "" ? null : timezoneRaw;
222
259
  const enabled = body.enabled !== false;
223
260
  const mode = (body.mode as string | undefined) ?? "execute";
261
+ const inferenceProfile =
262
+ body.inferenceProfile == null ? null : body.inferenceProfile;
263
+
264
+ if (inferenceProfile !== null) {
265
+ if (typeof inferenceProfile !== "string") {
266
+ throw new BadRequestError("inferenceProfile must be a string or null");
267
+ }
268
+ const profileError = validateScheduleInferenceProfile(inferenceProfile);
269
+ if (profileError) throw new BadRequestError(profileError);
270
+ }
224
271
 
225
272
  if (!name) throw new BadRequestError("name is required");
226
273
  if (!expression) throw new BadRequestError("expression is required");
227
- if (!message) throw new BadRequestError("message is required");
274
+ // Workflow-mode runs trigger a saved workflow by name and ignore `job.message`
275
+ // entirely (see the workflow branch below), so only require a message for the
276
+ // execute path. Requiring it for workflow mode would force API/UI callers to
277
+ // pass an unused dummy string.
278
+ if (mode !== "workflow" && !message) {
279
+ throw new BadRequestError("message is required");
280
+ }
228
281
  if (description === "") {
229
282
  throw new BadRequestError("description is required");
230
283
  }
231
284
 
232
- // The settings UI only exposes execute mode for now. Other modes
233
- // remain reachable via the schedule_create LLM tool.
234
- if (mode !== "execute") {
285
+ // The settings UI only exposes execute mode; `workflow` mode is reachable
286
+ // here (flag-gated) and via the schedule_create LLM tool. All other modes
287
+ // remain tool-only.
288
+ if (mode !== "execute" && mode !== "workflow") {
235
289
  throw new BadRequestError(
236
- "Only 'execute' mode is supported by this endpoint",
290
+ "Only 'execute' and 'workflow' modes are supported by this endpoint",
237
291
  );
238
292
  }
239
293
 
@@ -244,6 +298,39 @@ function handleCreateSchedule(body: Record<string, unknown>) {
244
298
  );
245
299
  }
246
300
 
301
+ if (mode === "workflow") {
302
+ assertWorkflowsEnabled();
303
+ const workflowName =
304
+ typeof body.workflowName === "string" ? body.workflowName.trim() : "";
305
+ if (!workflowName) {
306
+ throw new BadRequestError(
307
+ "workflowName is required for workflow-mode schedules",
308
+ );
309
+ }
310
+ try {
311
+ const job = createSchedule({
312
+ name,
313
+ description,
314
+ message,
315
+ mode: "workflow",
316
+ workflowName,
317
+ workflowArgs: body.workflowArgs,
318
+ enabled,
319
+ timezone,
320
+ expression: normalized.expression,
321
+ syntax: normalized.syntax,
322
+ });
323
+ log.info(
324
+ { id: job.id, name: job.name, workflowName },
325
+ "Workflow schedule created",
326
+ );
327
+ } catch (err) {
328
+ if (err instanceof Error) throw new BadRequestError(err.message);
329
+ throw err;
330
+ }
331
+ return handleListSchedules({});
332
+ }
333
+
247
334
  try {
248
335
  const job = createSchedule({
249
336
  name,
@@ -254,6 +341,7 @@ function handleCreateSchedule(body: Record<string, unknown>) {
254
341
  timezone,
255
342
  expression: normalized.expression,
256
343
  syntax: normalized.syntax,
344
+ inferenceProfile,
257
345
  });
258
346
  log.info({ id: job.id, name: job.name }, "Schedule created");
259
347
  } catch (err) {
@@ -263,6 +351,17 @@ function handleCreateSchedule(body: Record<string, unknown>) {
263
351
  return handleListSchedules({});
264
352
  }
265
353
 
354
+ /**
355
+ * Reject workflow-mode schedule operations when the `workflows` feature flag
356
+ * is off. Scheduled workflow runs would fail at trigger time (the run manager
357
+ * hard-fails with WorkflowsDisabledError), so we fail fast at the route.
358
+ */
359
+ function assertWorkflowsEnabled(): void {
360
+ if (!isAssistantFeatureFlagEnabled("workflows", getConfig())) {
361
+ throw new BadRequestError("Workflows are not enabled.");
362
+ }
363
+ }
364
+
266
365
  function handleToggleSchedule(id: string, body: Record<string, unknown>) {
267
366
  const enabled = body.enabled;
268
367
  if (typeof enabled !== "boolean") {
@@ -295,7 +394,13 @@ function handleCancelSchedule(id: string) {
295
394
  return handleListSchedules({});
296
395
  }
297
396
 
298
- const VALID_MODES = ["notify", "execute", "script", "wake"] as const;
397
+ const VALID_MODES = [
398
+ "notify",
399
+ "execute",
400
+ "script",
401
+ "wake",
402
+ "workflow",
403
+ ] as const;
299
404
  const VALID_ROUTING_INTENTS = [
300
405
  "single_channel",
301
406
  "multi_channel",
@@ -322,6 +427,39 @@ function handleUpdateSchedule(id: string, body: Record<string, unknown>) {
322
427
  );
323
428
  }
324
429
 
430
+ // Switching a schedule into workflow mode requires the workflows flag.
431
+ if (body.mode === "workflow") {
432
+ assertWorkflowsEnabled();
433
+ }
434
+
435
+ // Mirror the create-side validation: a schedule whose RESULTING mode is
436
+ // `workflow` must carry a non-empty `workflowName`. Without this, a PATCH can
437
+ // leave a workflow-mode schedule nameless — the scheduler then hits the
438
+ // `!job.workflowName` skip branch and a one-shot job claimed as `firing`
439
+ // never calls completeOneShot/retry, wedging it `firing` forever. We compute
440
+ // the post-update state (the body's value if present, else the persisted one)
441
+ // so both "switch to workflow without a name" and "clear the name on an
442
+ // already-workflow schedule" are rejected. Skip when the schedule does not
443
+ // exist — the updateSchedule call below surfaces that as NotFound.
444
+ const existing = getSchedule(id);
445
+ if (existing) {
446
+ const resultingMode =
447
+ "mode" in body ? (body.mode as string) : existing.mode;
448
+ if (resultingMode === "workflow") {
449
+ const resultingWorkflowName =
450
+ "workflowName" in body
451
+ ? typeof body.workflowName === "string"
452
+ ? body.workflowName.trim()
453
+ : ""
454
+ : (existing.workflowName ?? "");
455
+ if (!resultingWorkflowName) {
456
+ throw new BadRequestError(
457
+ "workflowName is required for workflow-mode schedules",
458
+ );
459
+ }
460
+ }
461
+ }
462
+
325
463
  const updates: Record<string, unknown> = {};
326
464
  for (const key of [
327
465
  "name",
@@ -334,6 +472,8 @@ function handleUpdateSchedule(id: string, body: Record<string, unknown>) {
334
472
  "quiet",
335
473
  "reuseConversation",
336
474
  "wakeConversationId",
475
+ "workflowName",
476
+ "workflowArgs",
337
477
  "maxRetries",
338
478
  "retryBackoffMs",
339
479
  "timeoutMs",
@@ -343,6 +483,21 @@ function handleUpdateSchedule(id: string, body: Record<string, unknown>) {
343
483
  }
344
484
  }
345
485
 
486
+ // Re-derive syntax whenever the expression changes, mirroring the create
487
+ // handler. Without this, switching an expression between cron and rrule
488
+ // would validate the new expression against the schedule's old syntax.
489
+ if (typeof updates.expression === "string") {
490
+ const normalized = normalizeScheduleSyntax({
491
+ expression: updates.expression,
492
+ });
493
+ if (!normalized) {
494
+ throw new BadRequestError(
495
+ "expression could not be parsed as cron or rrule",
496
+ );
497
+ }
498
+ updates.syntax = normalized.syntax;
499
+ }
500
+
346
501
  if ("description" in body) {
347
502
  const description =
348
503
  typeof body.description === "string" ? body.description.trim() : "";
@@ -360,6 +515,20 @@ function handleUpdateSchedule(id: string, body: Record<string, unknown>) {
360
515
  if (timeoutError) throw new BadRequestError(timeoutError);
361
516
  }
362
517
 
518
+ // Inference profile: null clears the override (back to the default
519
+ // main-agent model selection); a string must name a configured profile.
520
+ if ("inferenceProfile" in body) {
521
+ const inferenceProfile = body.inferenceProfile;
522
+ if (inferenceProfile !== null && typeof inferenceProfile !== "string") {
523
+ throw new BadRequestError("inferenceProfile must be a string or null");
524
+ }
525
+ if (typeof inferenceProfile === "string") {
526
+ const profileError = validateScheduleInferenceProfile(inferenceProfile);
527
+ if (profileError) throw new BadRequestError(profileError);
528
+ }
529
+ updates.inferenceProfile = inferenceProfile;
530
+ }
531
+
363
532
  try {
364
533
  const updated = updateSchedule(id, updates);
365
534
  if (!updated) {
@@ -389,14 +558,17 @@ function handleListScheduleRuns(
389
558
  if (!schedule) {
390
559
  throw new NotFoundError("Schedule not found");
391
560
  }
392
- const rawLimit = Number(queryParams.limit ?? 10);
393
- const limit = Number.isFinite(rawLimit)
394
- ? Math.min(Math.max(Math.floor(rawLimit), 1), 100)
395
- : 10;
396
- const runs = getScheduleRuns(id, limit);
561
+ const limit = parseRunsLimit(queryParams, 10);
562
+ const before = parseRunsBeforeCursor(queryParams);
563
+ const { rows, nextCursor } = paginateRuns(
564
+ getScheduleRuns(id, limit + 1, before),
565
+ limit,
566
+ (r) => r.createdAt,
567
+ );
397
568
  const now = Date.now();
398
569
  return {
399
- runs: runs.map((r) => {
570
+ nextCursor,
571
+ runs: rows.map((r) => {
400
572
  const conversation = r.conversationId
401
573
  ? getConversation(r.conversationId)
402
574
  : null;
@@ -494,6 +666,25 @@ export const ROUTES: RouteDefinition[] = [
494
666
  handler: ({ queryParams }: RouteHandlerArgs) =>
495
667
  handleScheduleUsageSummary(queryParams ?? {}),
496
668
  },
669
+ // Must stay after literal `schedules/*` GET siblings (e.g. usage-summary):
670
+ // the router matches in declaration order and `:id` would shadow them.
671
+ {
672
+ operationId: "getSchedule",
673
+ endpoint: "schedules/:id",
674
+ method: "GET",
675
+ policy: {
676
+ requiredScopes: ["settings.read"],
677
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
678
+ },
679
+ summary: "Get schedule",
680
+ description: "Return a single schedule by ID.",
681
+ tags: ["schedules"],
682
+ responseBody: z.object({
683
+ schedule: scheduleSchema.describe("Schedule object"),
684
+ }),
685
+ handler: ({ pathParams }: RouteHandlerArgs) =>
686
+ handleGetSchedule(pathParams!.id),
687
+ },
497
688
  {
498
689
  operationId: "createSchedule",
499
690
  endpoint: "schedules",
@@ -515,7 +706,12 @@ export const ROUTES: RouteDefinition[] = [
515
706
  )
516
707
  .optional(),
517
708
  expression: z.string().describe("Cron or RRULE expression"),
518
- message: z.string().describe("Message body to execute on each fire"),
709
+ message: z
710
+ .string()
711
+ .describe(
712
+ "Message body to execute on each fire. Required for execute mode; ignored for workflow mode (which triggers workflowName/workflowArgs).",
713
+ )
714
+ .optional(),
519
715
  timezone: z
520
716
  .string()
521
717
  .nullable()
@@ -525,7 +721,25 @@ export const ROUTES: RouteDefinition[] = [
525
721
  .boolean()
526
722
  .describe("Whether the schedule starts active (default true)")
527
723
  .optional(),
528
- mode: z.string().describe("Currently must be 'execute'").optional(),
724
+ mode: z
725
+ .string()
726
+ .describe("'execute' (default) or 'workflow' (flag-gated)")
727
+ .optional(),
728
+ workflowName: z
729
+ .string()
730
+ .describe("Saved workflow to trigger (required for workflow mode)")
731
+ .optional(),
732
+ workflowArgs: z
733
+ .unknown()
734
+ .describe("Args passed to the workflow run (workflow mode)")
735
+ .optional(),
736
+ inferenceProfile: z
737
+ .string()
738
+ .nullable()
739
+ .describe(
740
+ "Inference profile (llm.profiles key) the schedule's runs use. Omitted or null = default, i.e. the mainAgent call-site model selection.",
741
+ )
742
+ .optional(),
529
743
  }),
530
744
  responseBody: z.object({
531
745
  schedules: z.array(scheduleSchema).describe("Updated schedule list"),
@@ -543,15 +757,10 @@ export const ROUTES: RouteDefinition[] = [
543
757
  summary: "List schedule runs",
544
758
  description: "Return recent invocation history for a schedule.",
545
759
  tags: ["schedules"],
546
- queryParams: [
547
- {
548
- name: "limit",
549
- schema: { type: "integer" },
550
- description: "Max runs to return (default 10, max 100)",
551
- },
552
- ],
760
+ queryParams: RUNS_PAGINATION_QUERY_PARAMS(10),
553
761
  responseBody: z.object({
554
762
  runs: z.array(scheduleRunSchema).describe("Schedule run objects"),
763
+ nextCursor: RUNS_NEXT_CURSOR_SCHEMA,
555
764
  }),
556
765
  handler: ({ pathParams, queryParams }: RouteHandlerArgs) =>
557
766
  handleListScheduleRuns(pathParams!.id, queryParams ?? {}),
@@ -615,7 +824,19 @@ export const ROUTES: RouteDefinition[] = [
615
824
  .nullable()
616
825
  .describe("Shell command for script mode")
617
826
  .optional(),
618
- mode: z.string().describe("notify, execute, or script").optional(),
827
+ mode: z
828
+ .string()
829
+ .describe("notify, execute, script, wake, or workflow")
830
+ .optional(),
831
+ workflowName: z
832
+ .string()
833
+ .nullable()
834
+ .describe("Saved workflow to trigger (workflow mode)")
835
+ .optional(),
836
+ workflowArgs: z
837
+ .unknown()
838
+ .describe("Args passed to the workflow run (workflow mode)")
839
+ .optional(),
619
840
  routingIntent: z
620
841
  .string()
621
842
  .describe("single_channel, multi_channel, or all_channels")
@@ -632,6 +853,13 @@ export const ROUTES: RouteDefinition[] = [
632
853
  .nullable()
633
854
  .describe("Script-mode execution timeout in ms; null = use default")
634
855
  .optional(),
856
+ inferenceProfile: z
857
+ .string()
858
+ .nullable()
859
+ .describe(
860
+ "Inference profile (llm.profiles key) the schedule's runs use; null clears it back to the default mainAgent call-site model selection",
861
+ )
862
+ .optional(),
635
863
  }),
636
864
  responseBody: z.object({
637
865
  schedules: z.array(scheduleSchema).describe("Updated schedule list"),
@@ -711,6 +939,77 @@ async function handleRunScheduleNow(id: string) {
711
939
  return handleListSchedules({});
712
940
  }
713
941
 
942
+ // ── Workflow mode (trigger a saved workflow by name) ──────────────
943
+ // Mirrors the scheduler's automatic workflow-firing branch. Without this, a
944
+ // workflow-mode schedule manually triggered via `POST /schedules/:id/run`
945
+ // would fall through to the regular message path below and process
946
+ // `schedule.message` (usually empty — workflow-mode create no longer requires
947
+ // a message), running a no-op normal turn instead of the workflow.
948
+ if (schedule.mode === "workflow") {
949
+ // Fail fast at the route when the flag is off (the run manager would also
950
+ // hard-fail with WorkflowsDisabledError); matches create/update handling.
951
+ assertWorkflowsEnabled();
952
+ if (!schedule.workflowName) {
953
+ throw new BadRequestError("Workflow schedule has no workflowName");
954
+ }
955
+ // Boot race: resolveCapabilities grants the read-only baseline (file_read,
956
+ // web_search, …) from the tool registry, which initializeProvidersAndTools()
957
+ // populates during startup. The scheduler's automatic firing path DEFERS a
958
+ // workflow trigger to a later tick while `!areCoreToolsInitialized()` so a
959
+ // run never launches with an empty baseline. A manual "run now" has no later
960
+ // tick to defer to, so fail fast with a retryable 503 rather than launch a
961
+ // degraded run; the window is just the few seconds of assistant boot.
962
+ if (!areCoreToolsInitialized()) {
963
+ throw new ServiceUnavailableError(
964
+ "The assistant is still starting up and its tools are not ready yet. " +
965
+ "Try running this workflow again in a moment.",
966
+ );
967
+ }
968
+ const runId = createScheduleRun(schedule.id, `workflow:${schedule.id}`);
969
+ try {
970
+ log.info(
971
+ {
972
+ jobId: schedule.id,
973
+ name: schedule.name,
974
+ workflowName: schedule.workflowName,
975
+ },
976
+ "Triggering workflow schedule manually (run now)",
977
+ );
978
+ const { runId: workflowRunId } = getWorkflowRunManager().start({
979
+ name: schedule.workflowName,
980
+ args: schedule.workflowArgs ?? {},
981
+ // Deliver the completion summary to the schedule's wake target, falling
982
+ // back to the conversation that created it (workflow schedules made via
983
+ // `schedule_create` store `createdFromConversationId` and leave
984
+ // `wakeConversationId` unset) — mirrors the scheduler's firing path.
985
+ conversationId:
986
+ schedule.wakeConversationId ??
987
+ schedule.createdFromConversationId ??
988
+ undefined,
989
+ // The schedule's persisted capability manifest scopes the run, mirroring
990
+ // the scheduler's firing path; a legacy schedule with null
991
+ // `capabilities` normalizes to the read-only baseline.
992
+ manifest: normalizeCapabilityManifest(schedule.capabilities),
993
+ trustContext: INTERNAL_GUARDIAN_TRUST_CONTEXT,
994
+ });
995
+ // `start` launches the run fire-and-forget and returns synchronously;
996
+ // a successful trigger is recorded as ok. Completion/failure is surfaced
997
+ // out-of-band via workflow events and the completion wake.
998
+ completeScheduleRun(runId, {
999
+ status: "ok",
1000
+ output: `workflow run ${workflowRunId} started`,
1001
+ });
1002
+ } catch (err) {
1003
+ const errorMsg = err instanceof Error ? err.message : String(err);
1004
+ log.warn(
1005
+ { err, jobId: schedule.id, name: schedule.name },
1006
+ "Manual workflow schedule execution failed",
1007
+ );
1008
+ completeScheduleRun(runId, { status: "error", error: errorMsg });
1009
+ }
1010
+ return handleListSchedules({});
1011
+ }
1012
+
714
1013
  // Check if message is a task invocation (run_task:<task_id>)
715
1014
  const taskMatch = schedule.message.match(/^run_task:(\S+)$/);
716
1015
  if (taskMatch) {
@@ -735,6 +1034,9 @@ async function handleRunScheduleNow(id: string) {
735
1034
  attachments: [],
736
1035
  onEvent: () => {},
737
1036
  isInteractive: false,
1037
+ ...(schedule.inferenceProfile
1038
+ ? { overrideProfile: schedule.inferenceProfile }
1039
+ : {}),
738
1040
  });
739
1041
  } finally {
740
1042
  conversation.taskRunId = undefined;
@@ -781,6 +1083,9 @@ async function handleRunScheduleNow(id: string) {
781
1083
  conversationId: schedule.wakeConversationId,
782
1084
  hint: schedule.message,
783
1085
  source: "defer",
1086
+ ...(schedule.inferenceProfile
1087
+ ? { forceOverrideProfile: schedule.inferenceProfile }
1088
+ : {}),
784
1089
  });
785
1090
  } catch (err) {
786
1091
  const message = err instanceof Error ? err.message : String(err);
@@ -828,6 +1133,9 @@ async function handleRunScheduleNow(id: string) {
828
1133
  attachments: [],
829
1134
  onEvent: () => {},
830
1135
  isInteractive: false,
1136
+ ...(schedule.inferenceProfile
1137
+ ? { overrideProfile: schedule.inferenceProfile }
1138
+ : {}),
831
1139
  });
832
1140
  completeScheduleRun(runId, { status: "ok" });
833
1141
  } catch (err) {