@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,7 +1,11 @@
1
- import { getConfig } from "../../config/loader.js";
1
+ import {
2
+ formatSlackChannelStatus,
3
+ isSlackChannelCredential,
4
+ persistPromptedCredential,
5
+ storeSlackChannelCredential,
6
+ } from "../../credential-execution/prompted-credential.js";
2
7
  import {
3
8
  clearSlackUserToken,
4
- setSlackChannelConfig,
5
9
  type SlackChannelConfigResult,
6
10
  } from "../../daemon/handlers/config-slack-channel.js";
7
11
  import { syncManualTokenConnection } from "../../oauth/manual-token-connection.js";
@@ -22,7 +26,6 @@ import type {
22
26
  ToolDefinition,
23
27
  ToolExecutionResult,
24
28
  } from "../types.js";
25
- import { credentialBroker } from "./broker.js";
26
29
  import {
27
30
  assertMetadataWritable,
28
31
  deleteCredentialMetadata,
@@ -38,41 +41,6 @@ import { toPolicyFromInput, validatePolicyInput } from "./policy-validate.js";
38
41
 
39
42
  const log = getLogger("credential-vault");
40
43
 
41
- function isSlackChannelCredential(
42
- service: string,
43
- field: string,
44
- ): field is "bot_token" | "app_token" | "user_token" {
45
- return (
46
- service === "slack_channel" &&
47
- (field === "bot_token" || field === "app_token" || field === "user_token")
48
- );
49
- }
50
-
51
- async function storeSlackChannelCredential(
52
- field: "bot_token" | "app_token" | "user_token",
53
- value: string,
54
- ): Promise<SlackChannelConfigResult> {
55
- if (field === "bot_token") {
56
- return setSlackChannelConfig(value, undefined);
57
- }
58
- if (field === "app_token") {
59
- return setSlackChannelConfig(undefined, value);
60
- }
61
- return setSlackChannelConfig(undefined, undefined, value);
62
- }
63
-
64
- function formatSlackChannelStatus(result: SlackChannelConfigResult): string {
65
- if (result.connected) {
66
- const teamLabel = result.teamName ?? "Slack";
67
- const botLabel = result.botUsername ? ` (@${result.botUsername})` : "";
68
- return ` Slack channel connected to ${teamLabel}${botLabel}.`;
69
- }
70
- if (result.warning) {
71
- return ` ${result.warning}`;
72
- }
73
- return "";
74
- }
75
-
76
44
  export const credentialStoreTool = {
77
45
  name: "credential_store",
78
46
  description:
@@ -347,8 +315,8 @@ export const credentialStoreTool = {
347
315
  }
348
316
  try {
349
317
  upsertCredentialMetadata(service, field, {
350
- allowedTools: policy.allowedTools,
351
- allowedDomains: policy.allowedDomains,
318
+ allowedTools: policyInput.allowed_tools,
319
+ allowedDomains: policyInput.allowed_domains,
352
320
  usageDescription: policy.usageDescription,
353
321
  alias,
354
322
  injectionTemplates,
@@ -701,106 +669,27 @@ export const credentialStoreTool = {
701
669
  };
702
670
  }
703
671
 
704
- // Handle one-time send delivery: inject into context without persisting
705
- if (result.delivery === "transient_send") {
706
- if (isSlackChannelCredential(service, field)) {
707
- return {
708
- content:
709
- "Error: Slack channel credentials must be saved to secure storage. Re-run the secure prompt and choose to store the token.",
710
- isError: true,
711
- };
712
- }
713
- const config = getConfig();
714
- if (!config.secretDetection.allowOneTimeSend) {
715
- log.warn(
716
- { service, field },
717
- "One-time send requested but not enabled in config",
718
- );
719
- return {
720
- content:
721
- "Error: one-time send is not enabled. Set secretDetection.allowOneTimeSend to true in config.",
722
- isError: true,
723
- };
724
- }
725
- // Ensure metadata exists so broker policy checks work, but don't
726
- // overwrite an existing record - a stored credential's policy should
727
- // not be silently replaced by the transient prompt's policy.
728
- // Metadata must be written before injecting the transient value so
729
- // we never leave a dangling value that fails policy checks.
730
- if (!getCredentialMetadata(service, field)) {
731
- try {
732
- upsertCredentialMetadata(service, field, {
733
- allowedTools: promptPolicy.allowedTools,
734
- allowedDomains: promptPolicy.allowedDomains,
735
- usageDescription: promptPolicy.usageDescription,
736
- injectionTemplates: promptInjectionTemplates,
737
- });
738
- } catch (err) {
739
- // Without metadata the broker's policy checks will reject usage,
740
- // so the transient value would be silently unusable. Fail loudly.
741
- log.error(
742
- { service, field, err },
743
- "metadata write failed for transient credential",
744
- );
745
- return {
746
- content: `Error: failed to write credential metadata for ${service}/${field}; the one-time value was discarded.`,
747
- isError: true,
748
- };
749
- }
750
- }
751
- // Inject into broker for one-time use by the next tool call, then discard
752
- credentialBroker.injectTransient(service, field, result.value);
753
- log.info(
754
- { service, field, delivery: "transient_send" },
755
- "One-time secret delivery used",
756
- );
672
+ const persisted = await persistPromptedCredential({
673
+ service,
674
+ field,
675
+ value: result.value,
676
+ delivery: result.delivery,
677
+ policy: {
678
+ allowedTools: promptPolicyInput.allowed_tools,
679
+ allowedDomains: promptPolicyInput.allowed_domains,
680
+ usageDescription: promptPolicy.usageDescription,
681
+ injectionTemplates: promptInjectionTemplates,
682
+ },
683
+ });
684
+ if (persisted.outcome === "error") {
685
+ return { content: `Error: ${persisted.message}`, isError: true };
686
+ }
687
+ if (persisted.outcome === "transient") {
757
688
  return {
758
689
  content: `One-time credential provided for ${service}/${field}. The value was NOT saved to the vault and will be consumed by the next operation.`,
759
690
  isError: false,
760
691
  };
761
692
  }
762
-
763
- let slackChannelResult: SlackChannelConfigResult | undefined;
764
- if (isSlackChannelCredential(service, field)) {
765
- slackChannelResult = await storeSlackChannelCredential(
766
- field,
767
- result.value,
768
- );
769
- if (!slackChannelResult.success) {
770
- return {
771
- content: `Error: ${
772
- slackChannelResult.error ?? "failed to configure Slack channel"
773
- }`,
774
- isError: true,
775
- };
776
- }
777
- } else {
778
- // Default: persist to credential store
779
- const key = credentialKey(service, field);
780
- const ok = await setSecureKeyAsync(key, result.value);
781
- if (!ok) {
782
- return {
783
- content: "Error: failed to store credential",
784
- isError: true,
785
- };
786
- }
787
- }
788
- try {
789
- upsertCredentialMetadata(service, field, {
790
- allowedTools: promptPolicy.allowedTools,
791
- allowedDomains: promptPolicy.allowedDomains,
792
- usageDescription: promptPolicy.usageDescription,
793
- injectionTemplates: promptInjectionTemplates,
794
- });
795
- } catch (err) {
796
- log.warn(
797
- { service, field, err },
798
- "metadata write failed after storing credential",
799
- );
800
- }
801
- if (!isSlackChannelCredential(service, field)) {
802
- await syncManualTokenConnection(service);
803
- }
804
693
  const promptMeta = getCredentialMetadata(service, field);
805
694
  const promptCredIdSuffix = promptMeta
806
695
  ? ` (credential_id: ${promptMeta.credentialId})`
@@ -808,8 +697,8 @@ export const credentialStoreTool = {
808
697
  const promptRetrieveHint = ` Retrieve with: \`assistant credentials reveal --service ${service} --field ${field}\``;
809
698
  return {
810
699
  content: `Credential stored for ${service}/${field}.${promptCredIdSuffix}${promptRetrieveHint}${
811
- slackChannelResult
812
- ? formatSlackChannelStatus(slackChannelResult)
700
+ persisted.slackChannel
701
+ ? formatSlackChannelStatus(persisted.slackChannel)
813
702
  : ""
814
703
  }`,
815
704
  isError: false,
@@ -12,6 +12,11 @@ import { TokenExpiredError } from "../security/token-manager.js";
12
12
  import { PermissionDeniedError, ToolError } from "../util/errors.js";
13
13
  import { pathExists, safeStatSync } from "../util/fs.js";
14
14
  import { getLogger } from "../util/logger.js";
15
+ import {
16
+ callerOwnsWorkflowRun,
17
+ manifestGrantsSideEffects,
18
+ } from "../workflows/capabilities.js";
19
+ import { getWorkflowRunManager } from "../workflows/run-manager.js";
15
20
  import { resolveExecutionTarget } from "./execution-target.js";
16
21
  import { executeWithTimeout, safeTimeoutMs } from "./execution-timeout.js";
17
22
  import { PermissionChecker } from "./permission-checker.js";
@@ -138,6 +143,70 @@ export class ToolExecutor {
138
143
  context.requireFreshApproval = true;
139
144
  }
140
145
 
146
+ // A workflow run whose capability manifest grants side-effecting tools or
147
+ // host functions (beyond the read-only baseline) must prompt at LAUNCH.
148
+ // The manifest is authored and declared by the model, and the run's
149
+ // leaves execute granted tools DIRECTLY (no per-call permission check) -
150
+ // so the launch is the single point at which the user can consent to the
151
+ // grant, which would otherwise bypass the gate those tools hit when the
152
+ // main agent calls them. requireFreshApproval promotes the otherwise
153
+ // low-risk launch to an interactive prompt that cached grants/trust rules
154
+ // cannot silently bypass (run_workflow is not itself a SIDE_EFFECT tool,
155
+ // so forcePromptSideEffects would not fire — requireFreshApproval is the
156
+ // self-sufficient promotion). Read-only runs stay low-risk and silent.
157
+ if (
158
+ name === "run_workflow" &&
159
+ manifestGrantsSideEffects(input.capabilities)
160
+ ) {
161
+ context.requireFreshApproval = true;
162
+ }
163
+
164
+ // Creating a workflow-mode schedule whose capability manifest grants
165
+ // side-effecting tools or host functions persists that grant for an
166
+ // unattended future run — so the user consents to it at CREATION, the
167
+ // single interactive point in the flow (a triggered run later fires with
168
+ // no live conversation). Same rationale as run_workflow above: the
169
+ // manifest is model-declared and the eventual run's leaves execute granted
170
+ // tools directly (no per-call prompt). Read-only or absent manifests stay
171
+ // low-risk and silent. `schedule_create` is not a SIDE_EFFECT tool, so
172
+ // requireFreshApproval is the self-sufficient promotion.
173
+ if (
174
+ name === "schedule_create" &&
175
+ input.mode === "workflow" &&
176
+ manifestGrantsSideEffects(input.capabilities)
177
+ ) {
178
+ context.requireFreshApproval = true;
179
+ }
180
+
181
+ // Resuming a workflow whose STORED manifest granted side-effecting tools /
182
+ // host functions restarts unfinished leaves that perform those side
183
+ // effects. The original consent was given at LAUNCH (run_workflow above),
184
+ // but resume is reachable by any actor who can list or guess the run id —
185
+ // so re-require a fresh interactive approval when the target run's stored
186
+ // manifest grants side effects. The other manage_workflows actions
187
+ // (status/abort/list_runs) and resumes of read-only runs stay low-risk and
188
+ // silent. `manage_workflows` is not a SIDE_EFFECT tool, so (like
189
+ // run_workflow) requireFreshApproval is the self-sufficient promotion.
190
+ if (name === "manage_workflows" && input.action === "resume") {
191
+ const targetRunId =
192
+ typeof input.run_id === "string" ? input.run_id : undefined;
193
+ const targetRun = targetRunId
194
+ ? getWorkflowRunManager().status(targetRunId)
195
+ : null;
196
+ // Only promote to fresh approval for a run the caller actually OWNS. The
197
+ // tool hides others' runs as not-found, so prompting here for a
198
+ // non-owned run would both leak that the run exists and nag the guardian
199
+ // for a resume that will return not-found. Uses the same ownership scope
200
+ // the tool applies (callerOwnsWorkflowRun) so the gate and the tool agree.
201
+ if (
202
+ targetRun &&
203
+ callerOwnsWorkflowRun(targetRun, context) &&
204
+ manifestGrantsSideEffects(targetRun.capabilities)
205
+ ) {
206
+ context.requireFreshApproval = true;
207
+ }
208
+ }
209
+
141
210
  // A consumed scoped grant is a complete authorization - skip the
142
211
  // interactive permission/prompt flow so non-interactive sessions
143
212
  // don't auto-deny prompt-gated tools and burn the one-time grant.
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Tests for `syncFlagGatedTools()` — the follow-up registration that runs after
3
+ * feature-flag overrides are fetched from the gateway. `initializeTools()` runs
4
+ * during startup BEFORE that async fetch resolves, so a flag-enabled assistant
5
+ * would otherwise never expose the gated tools until a restart (which can lose
6
+ * the same race). These tests drive the gated-tool sets directly by mocking
7
+ * `tool-manifest.js`, so they verify the registry behavior in isolation without
8
+ * loading the full tool manifest.
9
+ */
10
+
11
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
12
+
13
+ mock.module("../util/logger.js", () => ({
14
+ getLogger: () =>
15
+ new Proxy({} as Record<string, unknown>, {
16
+ get: () => () => {},
17
+ }),
18
+ }));
19
+
20
+ // Controllable gated-tool set. `syncFlagGatedTools()` dynamically imports
21
+ // `./tool-manifest.js` and reads only this helper, so mocking it lets a test
22
+ // simulate "flag enabled" (non-empty) vs "flag off / not yet loaded" ([]).
23
+ let cesEnabled: Array<{ name: string }> = [];
24
+ mock.module("./tool-manifest.js", () => ({
25
+ getCesToolsIfEnabled: () => cesEnabled,
26
+ }));
27
+
28
+ import {
29
+ __clearRegistryForTesting,
30
+ getTool,
31
+ syncFlagGatedTools,
32
+ } from "./registry.js";
33
+
34
+ function fakeTool(name: string) {
35
+ return {
36
+ name,
37
+ description: `fake ${name}`,
38
+ category: "test",
39
+ defaultRiskLevel: "low" as never,
40
+ executionTarget: "sandbox" as const,
41
+ input_schema: { type: "object", properties: {}, required: [] },
42
+ execute: async () => ({ content: "ok", isError: false }),
43
+ };
44
+ }
45
+
46
+ describe("syncFlagGatedTools", () => {
47
+ beforeEach(() => {
48
+ __clearRegistryForTesting();
49
+ cesEnabled = [];
50
+ });
51
+
52
+ test("registers a CES tool that startup registration missed (the race)", async () => {
53
+ cesEnabled = [fakeTool("make_authenticated_request")];
54
+ // Simulates initializeTools() having run before the flag was known.
55
+ expect(getTool("make_authenticated_request")).toBeUndefined();
56
+
57
+ await syncFlagGatedTools();
58
+
59
+ expect(getTool("make_authenticated_request")).toBeDefined();
60
+ });
61
+
62
+ test("is idempotent — re-running does not throw or duplicate", async () => {
63
+ cesEnabled = [fakeTool("make_authenticated_request")];
64
+
65
+ await syncFlagGatedTools();
66
+ await syncFlagGatedTools();
67
+
68
+ expect(getTool("make_authenticated_request")).toBeDefined();
69
+ });
70
+
71
+ test("registers nothing when no gated flag is enabled", async () => {
72
+ await syncFlagGatedTools();
73
+
74
+ expect(getTool("make_authenticated_request")).toBeUndefined();
75
+ });
76
+ });
@@ -9,6 +9,7 @@ import {
9
9
  } from "../permissions/checker.js";
10
10
  import { getAutoApproveThreshold } from "../permissions/gateway-threshold-reader.js";
11
11
  import type { PermissionPrompter } from "../permissions/prompter.js";
12
+ import { isFullAccessThreshold } from "../permissions/threshold.js";
12
13
  import type {
13
14
  ApprovalMode,
14
15
  ApprovalReason,
@@ -183,7 +184,13 @@ export class PermissionChecker {
183
184
  // cannot bypass the interactive prompt. This is separate from the
184
185
  // forcePromptSideEffects path above to ensure requireFreshApproval
185
186
  // is self-sufficient without relying on SIDE_EFFECT_TOOLS membership.
186
- if (context.requireFreshApproval && result.decision === "allow") {
187
+ // At a full-access posture the user has opted into auto-approving even
188
+ // high-risk tools, so the promotion is skipped.
189
+ if (
190
+ context.requireFreshApproval &&
191
+ result.decision === "allow" &&
192
+ !isFullAccessThreshold(riskThreshold)
193
+ ) {
187
194
  result.decision = "prompt";
188
195
  result.reason =
189
196
  "Fresh approval required: per-invocation human review enforced";
@@ -199,6 +199,17 @@ export function getTool(name: string): Tool | undefined {
199
199
  return tools.get(name);
200
200
  }
201
201
 
202
+ /**
203
+ * True once {@link initializeTools} has populated the core registry (the core
204
+ * snapshot is captured at the end of init). Callers that must not run before the
205
+ * read-only baseline (`file_read`/`web_fetch`/etc.) exists — e.g. the scheduler
206
+ * deferring boot-time workflow triggers — gate on this. It only ever flips
207
+ * false→true, so a true reading is stable for the process lifetime.
208
+ */
209
+ export function areCoreToolsInitialized(): boolean {
210
+ return coreToolsSnapshot !== null;
211
+ }
212
+
202
213
  export function getAllTools(): Tool[] {
203
214
  return Array.from(tools.values());
204
215
  }
@@ -944,6 +955,46 @@ export async function initializeTools(): Promise<void> {
944
955
  await loadWorkspaceTools();
945
956
  }
946
957
 
958
+ /**
959
+ * Register any feature-flag-gated tools (CES `ces-tools`) that are enabled but
960
+ * not yet in the registry. Idempotent and safe to call repeatedly.
961
+ *
962
+ * `initializeTools()` registers the gated tools once at startup, but feature-flag
963
+ * overrides are fetched from the gateway ASYNCHRONOUSLY and non-blocking (see
964
+ * `lifecycle.ts` — `initFeatureFlagOverrides()` is fired with `void`), so
965
+ * `initializeTools()` can run BEFORE the flag value is known and register
966
+ * nothing. The daemon receives ALL overrides (local `protected/feature-flags.json`
967
+ * and remote/LaunchDarkly alike) through that async fetch, so this race affects
968
+ * every install — without this follow-up sync a flag-enabled assistant would
969
+ * never see the gated tools until a restart, which can lose the same race again.
970
+ * Call this once the override fetch resolves.
971
+ *
972
+ * Enable-direction only: it does not unregister tools whose flag was turned OFF
973
+ * (a live flip to OFF still requires a restart to drop them — the expected
974
+ * behavior for daemon-gating flags). Never throws.
975
+ */
976
+ export async function syncFlagGatedTools(): Promise<void> {
977
+ try {
978
+ const { getCesToolsIfEnabled } = await import("./tool-manifest.js");
979
+ const enabled = [...getCesToolsIfEnabled()];
980
+ const added: string[] = [];
981
+ for (const tool of enabled) {
982
+ if (tool.name && !tools.has(tool.name)) {
983
+ registerTool(tool);
984
+ added.push(tool.name);
985
+ }
986
+ }
987
+ if (added.length > 0) {
988
+ log.info({ tools: added }, "Registered flag-gated tools after flag load");
989
+ }
990
+ } catch (err) {
991
+ log.warn(
992
+ { err },
993
+ "syncFlagGatedTools failed; flag-gated tools may stay unregistered until restart",
994
+ );
995
+ }
996
+ }
997
+
947
998
  /**
948
999
  * Reset registry to its post-initializeTools() baseline. Exposed
949
1000
  * exclusively for test isolation - prevents cross-file contamination
@@ -1,3 +1,6 @@
1
+ import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
2
+ import { getConfig } from "../../config/loader.js";
3
+ import { validateScheduleInferenceProfile } from "../../schedule/inference-profile.js";
1
4
  import { formatIntegrationSummary } from "../../schedule/integration-status.js";
2
5
  import { validateRruleSetLines } from "../../schedule/recurrence-engine.js";
3
6
  import { normalizeScheduleSyntax } from "../../schedule/recurrence-types.js";
@@ -12,9 +15,13 @@ import {
12
15
  formatLocalDate,
13
16
  isValidCronExpression,
14
17
  } from "../../schedule/schedule-store.js";
18
+ import {
19
+ CapabilityManifestSchema,
20
+ resolveCapabilities,
21
+ } from "../../workflows/capabilities.js";
15
22
  import type { ToolContext, ToolExecutionResult } from "../types.js";
16
23
 
17
- const VALID_MODES: ScheduleMode[] = ["notify", "execute", "script"];
24
+ const VALID_MODES: ScheduleMode[] = ["notify", "execute", "script", "workflow"];
18
25
  const VALID_ROUTING_INTENTS: RoutingIntent[] = [
19
26
  "single_channel",
20
27
  "multi_channel",
@@ -49,6 +56,14 @@ export async function executeScheduleCreate(
49
56
  const maxRetries = input.max_retries as number | undefined;
50
57
  const retryBackoffMs = input.retry_backoff_ms as number | undefined;
51
58
  const timeoutMs = input.timeout_ms as number | undefined;
59
+ const workflowName =
60
+ typeof input.workflow_name === "string" ? input.workflow_name.trim() : null;
61
+ const workflowArgs = input.workflow_args;
62
+ const inferenceProfile = input.inference_profile as string | undefined;
63
+
64
+ // Validated workflow capability manifest, resolved only for workflow mode.
65
+ // Left null for non-workflow modes so `createSchedule` persists no manifest.
66
+ let capabilities: unknown = null;
52
67
 
53
68
  if (timeoutMs !== undefined) {
54
69
  const timeoutError = validateScriptTimeoutMs(timeoutMs);
@@ -57,6 +72,19 @@ export async function executeScheduleCreate(
57
72
  }
58
73
  }
59
74
 
75
+ if (inferenceProfile !== undefined) {
76
+ if (typeof inferenceProfile !== "string") {
77
+ return {
78
+ content: "Error: inference_profile must be a string",
79
+ isError: true,
80
+ };
81
+ }
82
+ const profileError = validateScheduleInferenceProfile(inferenceProfile);
83
+ if (profileError) {
84
+ return { content: `Error: ${profileError}`, isError: true };
85
+ }
86
+ }
87
+
60
88
  if (!name || typeof name !== "string") {
61
89
  return {
62
90
  content: "Error: name is required and must be a string",
@@ -92,6 +120,40 @@ export async function executeScheduleCreate(
92
120
  isError: true,
93
121
  };
94
122
  }
123
+ } else if (mode === "workflow") {
124
+ // Workflow mode is gated by the `workflows` flag (a scheduled run would
125
+ // otherwise hard-fail at trigger time) and requires a saved workflow name —
126
+ // mirrors the HTTP route's create-side validation so the assistant-facing
127
+ // path and the settings route enforce the same shape.
128
+ if (!isAssistantFeatureFlagEnabled("workflows", getConfig())) {
129
+ return { content: "Error: workflows are not enabled.", isError: true };
130
+ }
131
+ if (!workflowName) {
132
+ return {
133
+ content:
134
+ "Error: workflow_name is required for workflow mode and must be a non-empty string",
135
+ isError: true,
136
+ };
137
+ }
138
+ // A workflow schedule may carry a capability manifest — the single consent
139
+ // point for its eventual unattended run. Validate and normalize it here so a
140
+ // schedule can never persist a malformed or forbidden manifest: parse the
141
+ // declared shape, then run the same forbidden/unknown/host-tool checks
142
+ // resolveCapabilities applies at launch. A side-effecting manifest forces a
143
+ // fresh approval at CREATION (see executor.ts).
144
+ if (input.capabilities !== undefined) {
145
+ try {
146
+ const manifest = CapabilityManifestSchema.parse(input.capabilities);
147
+ resolveCapabilities(manifest);
148
+ capabilities = manifest;
149
+ } catch (err) {
150
+ const msg = err instanceof Error ? err.message : String(err);
151
+ return {
152
+ content: `Error: invalid capabilities manifest: ${msg}`,
153
+ isError: true,
154
+ };
155
+ }
156
+ }
95
157
  } else {
96
158
  if (!message || typeof message !== "string") {
97
159
  return {
@@ -157,6 +219,10 @@ export async function executeScheduleCreate(
157
219
  maxRetries,
158
220
  retryBackoffMs,
159
221
  timeoutMs,
222
+ workflowName,
223
+ workflowArgs,
224
+ capabilities,
225
+ inferenceProfile,
160
226
  createdFromConversationId: context.conversationId,
161
227
  });
162
228
 
@@ -170,6 +236,9 @@ export async function executeScheduleCreate(
170
236
  ` Description: ${job.description}`,
171
237
  ` Type: one-shot`,
172
238
  ` Mode: ${job.mode}`,
239
+ ...(job.inferenceProfile
240
+ ? [` Inference profile: ${job.inferenceProfile}`]
241
+ : []),
173
242
  ` Fire at: ${fireDate}`,
174
243
  ` Enabled: ${job.enabled}`,
175
244
  ` Status: ${job.status}`,
@@ -241,6 +310,10 @@ export async function executeScheduleCreate(
241
310
  maxRetries,
242
311
  retryBackoffMs,
243
312
  timeoutMs,
313
+ workflowName,
314
+ workflowArgs,
315
+ capabilities,
316
+ inferenceProfile,
244
317
  createdFromConversationId: context.conversationId,
245
318
  });
246
319
 
@@ -261,6 +334,9 @@ export async function executeScheduleCreate(
261
334
  ` Description: ${job.description}`,
262
335
  ` Syntax: ${job.syntax}`,
263
336
  ` Mode: ${job.mode}`,
337
+ ...(job.inferenceProfile
338
+ ? [` Inference profile: ${job.inferenceProfile}`]
339
+ : []),
264
340
  ` Schedule: ${scheduleDescription}${
265
341
  job.timezone ? ` (${job.timezone})` : ""
266
342
  }`,
@@ -71,6 +71,7 @@ export async function executeScheduleList(
71
71
  ` Enabled: ${job.enabled}`,
72
72
  ` Quiet: ${job.quiet}`,
73
73
  ` Reuse conversation: ${job.reuseConversation}`,
74
+ ` Inference profile: ${job.inferenceProfile ?? "default (mainAgent)"}`,
74
75
  ` Message: ${job.message}`,
75
76
  );
76
77