@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
@@ -663,19 +663,22 @@ export class HostTransferProxy {
663
663
  const transferId = interaction.metadata?.transferId as string | undefined;
664
664
  if (transferId) this.transfers.delete(transferId);
665
665
  pendingInteractions.resolve(requestId, "cancelled");
666
+ const { conversationId } = interaction;
666
667
  try {
667
- broadcastMessage(
668
- {
669
- type: "host_transfer_cancel",
670
- requestId,
671
- conversationId: interaction.conversationId,
672
- ...(interaction.targetClientId != null
673
- ? { targetClientId: interaction.targetClientId }
674
- : {}),
675
- },
676
- interaction.conversationId,
677
- { targetClientId: interaction.targetClientId },
678
- );
668
+ if (conversationId !== undefined) {
669
+ broadcastMessage(
670
+ {
671
+ type: "host_transfer_cancel",
672
+ requestId,
673
+ conversationId,
674
+ ...(interaction.targetClientId != null
675
+ ? { targetClientId: interaction.targetClientId }
676
+ : {}),
677
+ },
678
+ conversationId,
679
+ { targetClientId: interaction.targetClientId },
680
+ );
681
+ }
679
682
  } catch {
680
683
  // Best-effort cancel notification
681
684
  }
@@ -719,19 +722,22 @@ export class HostTransferProxy {
719
722
  const transferId = entry.metadata?.transferId as string | undefined;
720
723
  if (transferId) this.transfers.delete(transferId);
721
724
  pendingInteractions.resolve(entry.requestId, "cancelled");
725
+ const { conversationId } = entry;
722
726
  try {
723
- broadcastMessage(
724
- {
725
- type: "host_transfer_cancel",
726
- requestId: entry.requestId,
727
- conversationId: entry.conversationId,
728
- ...(entry.targetClientId != null
729
- ? { targetClientId: entry.targetClientId }
730
- : {}),
731
- },
732
- entry.conversationId,
733
- { targetClientId: entry.targetClientId },
734
- );
727
+ if (conversationId !== undefined) {
728
+ broadcastMessage(
729
+ {
730
+ type: "host_transfer_cancel",
731
+ requestId: entry.requestId,
732
+ conversationId,
733
+ ...(entry.targetClientId != null
734
+ ? { targetClientId: entry.targetClientId }
735
+ : {}),
736
+ },
737
+ conversationId,
738
+ { targetClientId: entry.targetClientId },
739
+ );
740
+ }
735
741
  } catch {
736
742
  // Best-effort cancel notification
737
743
  }
@@ -84,6 +84,7 @@ import {
84
84
  setUsageTelemetryReporter,
85
85
  UsageTelemetryReporter,
86
86
  } from "../telemetry/usage-telemetry-reporter.js";
87
+ import { syncFlagGatedTools } from "../tools/registry.js";
87
88
  import { registerBuiltinTtsProviders } from "../tts/providers/register-builtins.js";
88
89
  import { getDeviceId } from "../util/device-id.js";
89
90
  import { getLogger, initLogger } from "../util/logger.js";
@@ -97,6 +98,7 @@ import {
97
98
  listWorkItems,
98
99
  updateWorkItem,
99
100
  } from "../work-items/work-item-store.js";
101
+ import { getWorkflowRunManager } from "../workflows/run-manager.js";
100
102
  import { repairAdaptiveThinkingOnManagedProfiles } from "../workspace/adaptive-thinking-repair.js";
101
103
  import { WorkspaceHeartbeatService } from "../workspace/heartbeat-service.js";
102
104
  import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
@@ -358,9 +360,14 @@ export async function runDaemon(): Promise<void> {
358
360
  // so a slow or unreachable gateway doesn't delay daemon startup (the
359
361
  // IPC call has a 3s connect + 5s call timeout that would otherwise
360
362
  // stall the critical path).
361
- void initFeatureFlagOverrides().catch((err) =>
362
- log.warn({ err }, "Background feature flag init failed"),
363
- );
363
+ // After the async fetch resolves, (re)register any flag-gated tools
364
+ // (`workflows`, `ces-tools`): `initializeTools()` runs during startup before
365
+ // this fetch completes, so without this follow-up sync a flag-enabled
366
+ // assistant would not expose the gated tools until a restart (which can lose
367
+ // the same race). Enable-direction only; chained so it sees the fresh cache.
368
+ void initFeatureFlagOverrides()
369
+ .then(() => syncFlagGatedTools())
370
+ .catch((err) => log.warn({ err }, "Background feature flag init failed"));
364
371
 
365
372
  startGatewayFlagListener();
366
373
 
@@ -951,6 +958,26 @@ export async function runDaemon(): Promise<void> {
951
958
  log.error({ err }, "Schedule recovery failed — continuing startup");
952
959
  }
953
960
 
961
+ // Reconcile workflow runs orphaned by a crash: any row still `running` was
962
+ // in flight when the process died (the engine always finishes its row on
963
+ // exit), so flip it to `interrupted` to make it eligible for an explicit
964
+ // resume. Status only — accounting counters are preserved. Never blocks
965
+ // startup on failure.
966
+ try {
967
+ const reconciled = getWorkflowRunManager().reconcileOrphanedRuns();
968
+ if (reconciled > 0) {
969
+ log.info(
970
+ { reconciled },
971
+ "Reconciled orphaned workflow runs to interrupted",
972
+ );
973
+ }
974
+ } catch (err) {
975
+ log.error(
976
+ { err },
977
+ "Workflow run reconciliation failed — continuing startup",
978
+ );
979
+ }
980
+
954
981
  const scheduler = startScheduler(
955
982
  async (conversationId, message, options) => {
956
983
  await processMessage(
@@ -967,6 +994,9 @@ export async function runDaemon(): Promise<void> {
967
994
  }
968
995
  : {}),
969
996
  ...(options.taskRunId ? { taskRunId: options.taskRunId } : {}),
997
+ ...(options.overrideProfile
998
+ ? { overrideProfile: options.overrideProfile }
999
+ : {}),
970
1000
  }
971
1001
  : undefined,
972
1002
  );
@@ -1251,6 +1281,13 @@ export async function runDaemon(): Promise<void> {
1251
1281
  log.warn({ err }, "Assistant symlink installation failed — continuing");
1252
1282
  }
1253
1283
 
1284
+ // Seed preloaded apps (personal landing page) in background (non-blocking).
1285
+ void import("../memory/preloaded-apps.js")
1286
+ .then(({ seedPreloadedApps }) => seedPreloadedApps(config))
1287
+ .catch((err) =>
1288
+ log.warn({ err }, "Preloaded app seeding failed — continuing"),
1289
+ );
1290
+
1254
1291
  // Download embedding runtime in background (non-blocking).
1255
1292
  // If download fails, local embeddings gracefully fall back to cloud backends.
1256
1293
  void (async () => {
@@ -46,6 +46,7 @@ export * from "./message-types/sync.js";
46
46
  export * from "./message-types/upgrades.js";
47
47
  export * from "./message-types/web-activity.js";
48
48
  export * from "./message-types/work-items.js";
49
+ export * from "./message-types/workflows.js";
49
50
  export * from "./message-types/workspace.js";
50
51
 
51
52
  // Import domain-level union aliases for composition
@@ -135,6 +136,7 @@ import type {
135
136
  _WorkItemsClientMessages,
136
137
  _WorkItemsServerMessages,
137
138
  } from "./message-types/work-items.js";
139
+ import type { _WorkflowsServerMessages } from "./message-types/workflows.js";
138
140
  import type {
139
141
  _WorkspaceClientMessages,
140
142
  _WorkspaceServerMessages,
@@ -208,6 +210,7 @@ export type ServerMessage =
208
210
  | _UpgradesServerMessages
209
211
  | _AcpServerMessages
210
212
  | _BookmarksServerMessages
213
+ | _WorkflowsServerMessages
211
214
  | DiskPressureStatusChangedEvent
212
215
  | SubagentEvent;
213
216
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import type { AssistantActivityStateEvent } from "../../api/events/assistant-activity-state.js";
4
4
  import type { AssistantTextDeltaEvent } from "../../api/events/assistant-text-delta.js";
5
+ import type { AssistantThinkingDeltaEvent } from "../../api/events/assistant-thinking-delta.js";
5
6
  import type { AssistantTurnStartEvent } from "../../api/events/assistant-turn-start.js";
6
7
  import type { ConfirmationRequestEvent } from "../../api/events/confirmation-request.js";
7
8
  import type { ErrorEvent } from "../../api/events/error.js";
@@ -76,15 +77,6 @@ export interface SuggestionRequest {
76
77
 
77
78
  // === Server → Client ===
78
79
 
79
- export interface AssistantThinkingDelta {
80
- type: "assistant_thinking_delta";
81
- thinking: string;
82
- conversationId?: string;
83
- /** Database ID of the assistant message this thinking delta belongs to.
84
- * Same semantics as `AssistantTextDeltaEvent.messageId`. */
85
- messageId?: string;
86
- }
87
-
88
80
  export interface ToolInputDelta {
89
81
  type: "tool_input_delta";
90
82
  toolName: string;
@@ -160,7 +152,7 @@ export type _MessagesServerMessages =
160
152
  | UserMessageEchoEvent
161
153
  | AssistantTurnStartEvent
162
154
  | AssistantTextDeltaEvent
163
- | AssistantThinkingDelta
155
+ | AssistantThinkingDeltaEvent
164
156
  | ToolUseStartEvent
165
157
  | ToolUsePreviewStartEvent
166
158
  | ToolOutputChunkEvent
@@ -0,0 +1,49 @@
1
+ // Workflow orchestration run lifecycle events.
2
+ //
3
+ // Emitted by the `WorkflowRunManager` (run-manager.ts) as a workflow run
4
+ // progresses and completes. These are server → client push events only — the
5
+ // run is launched via the workflow tool / scheduler / routes (later PRs), not
6
+ // via a client message in this union.
7
+
8
+ import type { WorkflowRunStatus } from "../../workflows/journal-store.js";
9
+
10
+ // === Server → Client ===
11
+
12
+ /**
13
+ * Progress push for an in-flight workflow run. Maps the engine's
14
+ * `onProgress` (`phase`/`log`) callback plus the current usage snapshot into a
15
+ * single wire event. `phase` carries the latest `phase(title)`; `message`
16
+ * carries the latest `log(msg)`; only one is set per emission.
17
+ */
18
+ export interface WorkflowProgress {
19
+ type: "workflow_progress";
20
+ runId: string;
21
+ /** Latest phase title, when this emission came from a `phase(...)` call. */
22
+ phase?: string;
23
+ /** Run label (the workflow's `meta.name`), for client display. */
24
+ label?: string;
25
+ /** Live agent count at emission time. */
26
+ agentsSpawned: number;
27
+ /** Latest log line, when this emission came from a `log(...)` call. */
28
+ message?: string;
29
+ }
30
+
31
+ /**
32
+ * Terminal push for a workflow run. Carries the final status, counts, token
33
+ * usage, and a human-readable result/error summary the originating
34
+ * conversation also receives via an agent wake.
35
+ */
36
+ export interface WorkflowCompleted {
37
+ type: "workflow_completed";
38
+ runId: string;
39
+ status: WorkflowRunStatus;
40
+ agentsSpawned: number;
41
+ inputTokens: number;
42
+ outputTokens: number;
43
+ /** Human-readable result-or-error summary. */
44
+ summary?: string;
45
+ }
46
+
47
+ // --- Domain-level union aliases (consumed by the barrel file) ---
48
+
49
+ export type _WorkflowsServerMessages = WorkflowProgress | WorkflowCompleted;
@@ -1,7 +1,10 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import { ContextOverflowError } from "../providers/types.js";
4
- import { parseActualTokensFromError } from "./parse-actual-tokens-from-error.js";
4
+ import {
5
+ looksLikeContextOverflowError,
6
+ parseActualTokensFromError,
7
+ } from "./parse-actual-tokens-from-error.js";
5
8
 
6
9
  describe("parseActualTokensFromError", () => {
7
10
  test("returns null for null input", () => {
@@ -125,3 +128,61 @@ describe("parseActualTokensFromError", () => {
125
128
  expect(parseActualTokensFromError(err)).toBe(242201);
126
129
  });
127
130
  });
131
+
132
+ describe("looksLikeContextOverflowError", () => {
133
+ test("matches the typed ContextOverflowError even without a parseable message", () => {
134
+ const err = new ContextOverflowError("context window exceeded", "openai");
135
+ expect(looksLikeContextOverflowError(err)).toBe(true);
136
+ });
137
+
138
+ test("matches a REWRAPPED plain Error carrying an Anthropic-style message", () => {
139
+ // Managed-proxy/adapter paths rewrap overflow rejections into untyped
140
+ // errors — the heuristic must still recognize them.
141
+ const err = new Error(
142
+ "Provider API error (400): prompt is too long: 250000 tokens > 200000 maximum",
143
+ );
144
+ expect(looksLikeContextOverflowError(err)).toBe(true);
145
+ });
146
+
147
+ test("matches a rewrapped OpenAI-style message", () => {
148
+ expect(
149
+ looksLikeContextOverflowError(
150
+ new Error("too many input tokens: 150000 > 128000"),
151
+ ),
152
+ ).toBe(true);
153
+ });
154
+
155
+ test("matches context_length_exceeded / maximum-context-length phrasings", () => {
156
+ expect(
157
+ looksLikeContextOverflowError(new Error("context_length_exceeded")),
158
+ ).toBe(true);
159
+ expect(
160
+ looksLikeContextOverflowError(
161
+ new Error("This model's maximum context length is 128000 tokens"),
162
+ ),
163
+ ).toBe(true);
164
+ });
165
+
166
+ test("accepts a raw string message", () => {
167
+ expect(
168
+ looksLikeContextOverflowError(
169
+ "prompt is too long: 242201 tokens > 200000 maximum",
170
+ ),
171
+ ).toBe(true);
172
+ });
173
+
174
+ test("rejects unrelated errors, strings, and nullish input", () => {
175
+ expect(looksLikeContextOverflowError(new Error("socket hang up"))).toBe(
176
+ false,
177
+ );
178
+ expect(looksLikeContextOverflowError("something went wrong")).toBe(false);
179
+ expect(looksLikeContextOverflowError(null)).toBe(false);
180
+ expect(looksLikeContextOverflowError(undefined)).toBe(false);
181
+ });
182
+
183
+ test("does not misread arbitrary numeric comparisons as overflow", () => {
184
+ expect(
185
+ looksLikeContextOverflowError(new Error("retry budget: 5 > 3 attempts")),
186
+ ).toBe(false);
187
+ });
188
+ });
@@ -30,13 +30,52 @@ export function parseActualTokensFromError(
30
30
  }
31
31
 
32
32
  // Untyped path — accept either an Error or a string.
33
+ return parseFromMessage(extractMessage(errorOrMessage));
34
+ }
35
+
36
+ /**
37
+ * Message patterns that identify a provider context-overflow rejection.
38
+ * Covers the typed wrapper's source patterns plus common provider phrasings
39
+ * that adapter paths (e.g. managed-proxy rewrappers) surface as untyped
40
+ * errors:
41
+ * "prompt is too long: 242201 tokens > 200000 maximum" (Anthropic)
42
+ * "too many input tokens: 242201 > 200000" (OpenAI)
43
+ * "context_length_exceeded" / "maximum context length" (OpenAI-compat)
44
+ */
45
+ const OVERFLOW_MESSAGE_PATTERNS: readonly RegExp[] = [
46
+ /prompt is too long/i,
47
+ /too many (?:input )?tokens/i,
48
+ /context[_\s-]?length[_\s-]?exceeded/i,
49
+ /maximum context length/i,
50
+ /\d[\d,]*\s*tokens?\s*[>≥]\s*\d/i,
51
+ ];
52
+
53
+ /**
54
+ * Heuristic context-overflow check that also catches REWRAPPED provider
55
+ * errors. `isContextOverflowError` only matches the typed
56
+ * `ContextOverflowError` wrapper; adapter layers (managed-proxy rewrappers,
57
+ * retry shims) can re-throw the same rejection as a plain `Error`, hiding the
58
+ * typed signal. Callers that must not misread an overflow as a clean
59
+ * no-output stop (e.g. the suppressed-compaction agent-wake path) should use
60
+ * this instead of the bare typed check.
61
+ *
62
+ * Accepts the same inputs as {@link parseActualTokensFromError}: a typed
63
+ * error, a plain `Error`/object with a `message`, or a raw string.
64
+ */
65
+ export function looksLikeContextOverflowError(err: unknown): boolean {
66
+ if (isContextOverflowError(err)) return true;
67
+ const message = extractMessage(err);
68
+ if (message === null) return false;
69
+ return OVERFLOW_MESSAGE_PATTERNS.some((pattern) => pattern.test(message));
70
+ }
71
+
72
+ /** Pull a message string out of an untyped error-ish value, if possible. */
73
+ function extractMessage(errorOrMessage: unknown): string | null {
33
74
  if (errorOrMessage == null) return null;
34
- if (typeof errorOrMessage === "string") {
35
- return parseFromMessage(errorOrMessage);
36
- }
75
+ if (typeof errorOrMessage === "string") return errorOrMessage;
37
76
  if (typeof errorOrMessage === "object" && "message" in errorOrMessage) {
38
77
  const msg = (errorOrMessage as { message?: unknown }).message;
39
- if (typeof msg === "string") return parseFromMessage(msg);
78
+ if (typeof msg === "string") return msg;
40
79
  }
41
80
  return null;
42
81
  }
@@ -534,6 +534,9 @@ export async function processMessage(
534
534
  isInteractive: options?.isInteractive ?? false,
535
535
  isUserMessage: true,
536
536
  ...(options?.callSite ? { callSite: options.callSite } : {}),
537
+ ...(options?.overrideProfile
538
+ ? { overrideProfile: options.overrideProfile }
539
+ : {}),
537
540
  });
538
541
  } finally {
539
542
  if (
@@ -591,6 +594,9 @@ export async function processMessageInBackground(
591
594
  isInteractive: options?.isInteractive ?? false,
592
595
  isUserMessage: true,
593
596
  ...(options?.callSite ? { callSite: options.callSite } : {}),
597
+ ...(options?.overrideProfile
598
+ ? { overrideProfile: options.overrideProfile }
599
+ : {}),
594
600
  })
595
601
  .finally(() => {
596
602
  if (
@@ -8,6 +8,54 @@ import type { LLMCallSite } from "../config/schemas/llm.js";
8
8
  import type { SurfaceConversationContext } from "./conversation-surfaces.js";
9
9
  import type { TrustContext } from "./trust-context.js";
10
10
 
11
+ /**
12
+ * How a subagent/wake tool allowlist is enforced.
13
+ *
14
+ * - `"wire"` (default): filter the tool definitions sent to the provider so
15
+ * the model never sees non-allowlisted tools. Smaller request, but the
16
+ * tool array no longer byte-matches the conversation's normal turns, so
17
+ * the provider prompt-cache prefix (`tools → system → messages`) cannot
18
+ * be reused.
19
+ * - `"execution"`: keep the conversation's full tool surface on the wire
20
+ * (preserving cache parity with the source conversation's turns) and
21
+ * enforce the allowlist at execution time — a call to a non-allowlisted
22
+ * tool returns an error tool_result without ever invoking the tool's
23
+ * executor.
24
+ */
25
+ export type SubagentToolGateMode = "wire" | "execution";
26
+
27
+ /**
28
+ * Client-context inputs frozen for tool-DEFINITION resolution during a wake
29
+ * that runs with `subagentToolGateMode: "execution"`.
30
+ *
31
+ * Execution gate mode exists to keep the wire tool array byte-identical to
32
+ * the source conversation's live turns (see {@link SubagentToolGateMode}),
33
+ * but the definitions themselves are resolved from the live context: a
34
+ * fork-retrospective wake hydrates clientless (`hasNoClient = true`, no
35
+ * transport interface, no channel capabilities), which drops client-gated
36
+ * tools (`host_*`, `ui_*`, `app_open`, `request_system_permission`) from
37
+ * the wire definitions and breaks the cache prefix anyway. When this pin is
38
+ * set on the conversation, `isToolActiveForContext` reads `hasNoClient` and
39
+ * `transportInterface` exclusively from the pin and treats channel
40
+ * capabilities as unset — an absent optional field pins the value to
41
+ * `undefined`; there is no fall-through to the live conversation state.
42
+ * (Interactive-interface turns never set channel capabilities, so unset IS
43
+ * parity for desktop/web sources; channel-routed sources resolve every tool
44
+ * gate identically under `hasNoClient: true` with or without them.)
45
+ *
46
+ * Tool-definition resolution ONLY. The executor callback and host-proxy
47
+ * attachment paths never read the pin, so it cannot make a host tool
48
+ * runnable: in execution gate mode every non-allowlisted call is rejected
49
+ * before its executor runs, so a pinned-in tool can appear on the wire but
50
+ * can never execute.
51
+ */
52
+ export interface WakeToolContextPin {
53
+ /** The source conversation's live-turn `hasNoClient` value. */
54
+ hasNoClient: boolean;
55
+ /** The interface the source's live turns ran on (e.g. `"macos"`). */
56
+ transportInterface?: InterfaceId;
57
+ }
58
+
11
59
  /**
12
60
  * Subset of Conversation state that the tool executor callback reads at
13
61
  * call time (not construction time). These are captured by the
@@ -21,6 +69,15 @@ export interface ToolSetupContext extends SurfaceConversationContext {
21
69
  abortController: AbortController | null;
22
70
  /** When set, only tools in this set may execute during the current turn. */
23
71
  allowedToolNames?: Set<string>;
72
+ /** When set, the subagent/wake tool allowlist (see {@link subagentToolGateMode}). */
73
+ subagentAllowedTools?: Set<string>;
74
+ /**
75
+ * How {@link subagentAllowedTools} is enforced. Absent or `"wire"` keeps
76
+ * the historical behavior (definitions filtered before the provider
77
+ * request); `"execution"` keeps the full tool surface on the wire and
78
+ * rejects non-allowlisted calls in the executor callback instead.
79
+ */
80
+ subagentToolGateMode?: SubagentToolGateMode;
24
81
  /** Turn-scoped disk-pressure cleanup mode flag. */
25
82
  diskPressureCleanupModeActive?: boolean;
26
83
  /** True when the conversation has no connected client (HTTP-only path). */
@@ -25,6 +25,10 @@ import { broadcastMessage } from "../runtime/assistant-event-hub.js";
25
25
  import { getLogger } from "../util/logger.js";
26
26
  import type { Conversation } from "./conversation.js";
27
27
  import type { ServerMessage } from "./message-protocol.js";
28
+ import type {
29
+ SubagentToolGateMode,
30
+ WakeToolContextPin,
31
+ } from "./tool-setup-types.js";
28
32
 
29
33
  const log = getLogger("wake-conversation-ops");
30
34
 
@@ -256,14 +260,28 @@ export async function persistWakeTailMessage(
256
260
  * reusing the conversation's subagent allowlist slot. Returns a restore
257
261
  * callback that reinstates the previous allowlist so the wake can release
258
262
  * the scope before any queued user turn is drained.
263
+ *
264
+ * `gateMode` controls how the allowlist is enforced — see
265
+ * {@link SubagentToolGateMode}. `toolContextPin`, when provided
266
+ * (execution-gate-mode cache-parity wakes), freezes the client-context
267
+ * inputs for tool-definition resolution — see {@link WakeToolContextPin}.
268
+ * Both are set and restored alongside the allowlist.
259
269
  */
260
270
  export function scopeWakeAllowedTools(
261
271
  conversation: Conversation,
262
272
  tools: ReadonlySet<string>,
273
+ gateMode: SubagentToolGateMode = "wire",
274
+ toolContextPin?: WakeToolContextPin,
263
275
  ): () => void {
264
276
  const previous = conversation.subagentAllowedTools;
277
+ const previousGateMode = conversation.subagentToolGateMode;
278
+ const previousToolContextPin = conversation.toolContextPin;
265
279
  conversation.setSubagentAllowedTools(new Set(tools));
280
+ conversation.subagentToolGateMode = gateMode;
281
+ conversation.toolContextPin = toolContextPin;
266
282
  return () => {
267
283
  conversation.setSubagentAllowedTools(previous);
284
+ conversation.subagentToolGateMode = previousGateMode;
285
+ conversation.toolContextPin = previousToolContextPin;
268
286
  };
269
287
  }
@@ -1,4 +1,4 @@
1
- import { desc, eq, sql } from "drizzle-orm";
1
+ import { desc, eq, lt, sql } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
4
  import { getDb } from "../memory/db-connection.js";
@@ -272,12 +272,18 @@ export function countRecentConsecutiveRuns(maxToCheck: number): number {
272
272
 
273
273
  /**
274
274
  * List heartbeat runs ordered by `scheduledFor` descending.
275
+ * When `before` is set, only runs with `scheduledFor` strictly older than it
276
+ * are returned (cursor for paginating into history).
275
277
  */
276
- export function listHeartbeatRuns(limit = 20): HeartbeatRunRecord[] {
278
+ export function listHeartbeatRuns(
279
+ limit = 20,
280
+ before?: number,
281
+ ): HeartbeatRunRecord[] {
277
282
  const db = getDb();
278
283
  const rows = db
279
284
  .select()
280
285
  .from(heartbeatRuns)
286
+ .where(before != null ? lt(heartbeatRuns.scheduledFor, before) : undefined)
281
287
  .orderBy(desc(heartbeatRuns.scheduledFor))
282
288
  .limit(limit)
283
289
  .all();
@@ -43,7 +43,7 @@ export {
43
43
  } from "../api/responses/home.js";
44
44
 
45
45
  /**
46
- * On-disk file format for `~/.vellum/workspace/data/home-feed.json`.
46
+ * On-disk file format for `$VELLUM_WORKSPACE_DIR/data/home-feed.json`.
47
47
  *
48
48
  * Written by the feed writer, read by the HTTP route and `parseFeedFile`
49
49
  * below. `version` is pinned to `2` (collapsed schema); pre-v2 files are
@@ -3,11 +3,35 @@ import { connect, type Socket } from "node:net";
3
3
  import { refreshOverridesFromGateway } from "../config/assistant-feature-flags.js";
4
4
  import { SYNC_TAGS } from "../daemon/message-types/sync.js";
5
5
  import { publishSyncInvalidation } from "../runtime/sync/sync-publisher.js";
6
+ import { syncFlagGatedTools } from "../tools/registry.js";
6
7
  import { getLogger } from "../util/logger.js";
7
8
  import { resolveIpcSocketPath } from "./socket-path.js";
8
9
 
9
10
  const log = getLogger("gateway-flag-listener");
10
11
 
12
+ /**
13
+ * Refresh the flag-overrides cache from the gateway, then register any
14
+ * registry-gated tools whose flag gate has since opened. Both the
15
+ * `feature_flags_changed` event and a reconnect only refresh the cache;
16
+ * `syncFlagGatedTools` registers the CES tools whose flag is enabled so that
17
+ * enabling a CES-gating flag at runtime surfaces them without a daemon restart.
18
+ * Workflow tools (`run_workflow` / `manage_workflows`) are not registry-gated:
19
+ * they live in a flag-gated bundled skill surfaced via `skill_load` against the
20
+ * live flag cache, so they need no registry sync here. `syncFlagGatedTools` is
21
+ * idempotent and enable-only, so re-running it on every refresh is safe; it also
22
+ * never throws (it logs internally).
23
+ */
24
+ function refreshFlagsAndSyncTools(context: string): void {
25
+ refreshOverridesFromGateway()
26
+ .then(() => syncFlagGatedTools())
27
+ .catch((err) => {
28
+ log.warn(
29
+ { err },
30
+ `Failed to refresh feature flag overrides (${context})`,
31
+ );
32
+ });
33
+ }
34
+
11
35
  const MAX_BACKOFF_MS = 30_000;
12
36
  const INITIAL_BACKOFF_MS = 1_000;
13
37
 
@@ -29,9 +53,7 @@ function handleData(chunk: Buffer): void {
29
53
  const msg = JSON.parse(trimmed) as { event?: string };
30
54
  if (msg.event === "feature_flags_changed") {
31
55
  log.info("Received feature_flags_changed event — refreshing overrides");
32
- refreshOverridesFromGateway().catch((err) => {
33
- log.warn({ err }, "Failed to refresh feature flag overrides");
34
- });
56
+ refreshFlagsAndSyncTools("flags-changed event");
35
57
  // Fan out to every connected web client so React Query caches
36
58
  // for `/v1/feature-flags/client-flag-values/` and
37
59
  // `/v1/assistants/:id/feature-flags` invalidate immediately
@@ -72,9 +94,9 @@ function connectToGateway(): void {
72
94
  log.info("Connected to gateway IPC for flag events");
73
95
  currentBackoffMs = INITIAL_BACKOFF_MS;
74
96
  socket = conn;
75
- refreshOverridesFromGateway().catch((err) => {
76
- log.warn({ err }, "Failed to refresh feature flag overrides on reconnect");
77
- });
97
+ // A reconnect may have missed a flag flip while disconnected, so sync the
98
+ // gated tools too not just the cache.
99
+ refreshFlagsAndSyncTools("reconnect");
78
100
  });
79
101
 
80
102
  let buffer = "";
@@ -96,38 +96,26 @@ export function setMcpAuthError(
96
96
  }
97
97
 
98
98
  /**
99
- * Get the current state of an OAuth flow, or null if none exists.
99
+ * Get the current state of an OAuth flow, or null if none exists. Sweeps
100
+ * expired entries on every read so a long-polling CLI can never observe
101
+ * a stale flow past its TTL.
100
102
  */
101
103
  export function getMcpAuthState(serverId: string): McpAuthState | null {
102
- clearExpiredMcpAuthStates();
103
- return activeMcpAuthFlows.get(serverId) ?? null;
104
- }
105
-
106
- /**
107
- * Remove expired entries from the auth flow map.
108
- */
109
- export function clearExpiredMcpAuthStates(): void {
110
104
  const now = Date.now();
111
- for (const [serverId, state] of activeMcpAuthFlows) {
105
+ for (const [id, state] of activeMcpAuthFlows) {
112
106
  if (state.status === "pending" && now > state.expiresAt) {
113
- activeMcpAuthFlows.delete(serverId);
107
+ activeMcpAuthFlows.delete(id);
114
108
  } else if (
115
109
  state.status === "complete" &&
116
110
  now > state.completedAt + COMPLETION_GRACE_MS
117
111
  ) {
118
- activeMcpAuthFlows.delete(serverId);
112
+ activeMcpAuthFlows.delete(id);
119
113
  } else if (
120
114
  state.status === "error" &&
121
115
  now > state.failedAt + COMPLETION_GRACE_MS
122
116
  ) {
123
- activeMcpAuthFlows.delete(serverId);
117
+ activeMcpAuthFlows.delete(id);
124
118
  }
125
119
  }
126
- }
127
-
128
- /**
129
- * Test-only helper — clears all auth flow state for test isolation.
130
- */
131
- export function _clearAllMcpAuthStates(): void {
132
- activeMcpAuthFlows.clear();
120
+ return activeMcpAuthFlows.get(serverId) ?? null;
133
121
  }