@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
@@ -37,8 +37,9 @@ import {
37
37
  } from "../../config/loader.js";
38
38
  import { AssistantConfigSchema } from "../../config/schema.js";
39
39
  import { getSchemaAtPath } from "../../config/schema-utils.js";
40
- import { ProfileEntry } from "../../config/schemas/llm.js";
40
+ import { LLMConfigFragment, ProfileEntry } from "../../config/schemas/llm.js";
41
41
  import { VALID_MEMORY_EMBEDDING_PROVIDERS } from "../../config/schemas/memory-storage.js";
42
+ import { ServiceModeSchema } from "../../config/schemas/services.js";
42
43
  import { getConfigWatcher } from "../../daemon/config-watcher.js";
43
44
  import {
44
45
  getEmbeddingConfigInfo,
@@ -74,7 +75,7 @@ import { type LogRow } from "../../memory/llm-request-log-store.js";
74
75
  import { getMemoryRecallLogByMessageIds } from "../../memory/memory-recall-log-store.js";
75
76
  import { getMemoryV2ActivationLogByMessageIds } from "../../memory/memory-v2-activation-log-store.js";
76
77
  import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../memory/v2/constants.js";
77
- import { getMemoryV3SelectionForInspector } from "../../plugins/defaults/memory-v3-shadow/selection-log-store.js";
78
+ import { getMemoryV3SelectionForInspectorByMessageIds } from "../../plugins/defaults/memory-v3-shadow/selection-log-store.js";
78
79
  import {
79
80
  createConnection,
80
81
  listConnections,
@@ -468,6 +469,187 @@ function readPlainObject(value: unknown): Record<string, unknown> | undefined {
468
469
  return value as Record<string, unknown>;
469
470
  }
470
471
 
472
+ const WireProfileEntry = ProfileEntry.extend({
473
+ supportsVision: z.boolean().optional(),
474
+ })
475
+ .passthrough()
476
+ .meta({ id: "ProfileEntry" });
477
+
478
+ /**
479
+ * Wire shape of the `memory` section in config responses. Passthrough
480
+ * preserves fields beyond `enabled` and `v2` so the client doesn't strip
481
+ * unrecognised memory config that newer daemons may add.
482
+ */
483
+ const MemoryWireConfigSchema = z
484
+ .object({
485
+ enabled: z.boolean().optional(),
486
+ v2: z.object({ enabled: z.boolean().optional() }).passthrough().optional(),
487
+ })
488
+ .passthrough()
489
+ .meta({ id: "MemoryConfig" });
490
+
491
+ /**
492
+ * Response schema for `GET /v1/config`.
493
+ *
494
+ * Describes the wire shape of the raw `settings.json` response after
495
+ * context-default filling and vision-flag enrichment. All top-level fields
496
+ * are optional because the on-disk config may be sparse. Additional
497
+ * top-level config sections beyond what's typed here are preserved via
498
+ * passthrough — this schema types the fields that web/macOS clients consume
499
+ * without restricting the full config surface.
500
+ */
501
+ const ConfigGetResponseSchema = z
502
+ .object({
503
+ llm: z
504
+ .object({
505
+ default: LLMConfigFragment.extend({
506
+ provider_connection: z.string().optional(),
507
+ }).optional(),
508
+ profiles: z.record(z.string(), WireProfileEntry).optional(),
509
+ profileOrder: z.array(z.string()).optional(),
510
+ activeProfile: z.string().optional(),
511
+ callSites: z
512
+ .record(
513
+ z.string(),
514
+ LLMConfigFragment.extend({
515
+ profile: z.string().optional(),
516
+ }).nullable(),
517
+ )
518
+ .optional(),
519
+ profileSession: z
520
+ .object({
521
+ defaultTtlSeconds: z.number().optional(),
522
+ maxTtlSeconds: z.number().optional(),
523
+ })
524
+ .optional(),
525
+ pricingOverrides: z.array(z.unknown()).optional(),
526
+ })
527
+ .passthrough()
528
+ .optional(),
529
+ memory: MemoryWireConfigSchema.optional(),
530
+ services: z
531
+ .object({
532
+ "web-search": z
533
+ .object({
534
+ mode: ServiceModeSchema.optional(),
535
+ provider: z.string().optional(),
536
+ })
537
+ .passthrough()
538
+ .optional(),
539
+ "image-generation": z
540
+ .object({ mode: ServiceModeSchema.optional() })
541
+ .passthrough()
542
+ .optional(),
543
+ inference: z
544
+ .object({ mode: ServiceModeSchema.optional() })
545
+ .passthrough()
546
+ .optional(),
547
+ })
548
+ .passthrough()
549
+ .optional(),
550
+ })
551
+ .passthrough()
552
+ .meta({ id: "ConfigGetResponse" });
553
+
554
+ /**
555
+ * Given a `z.object(...)` schema, returns a new schema where every property
556
+ * is `.nullable().optional()`. Used to express PATCH body semantics where any
557
+ * field can be `null` (meaning "delete via deep-merge") or omitted (unchanged).
558
+ */
559
+ function nullablePartial(schema: z.ZodObject<z.ZodRawShape>) {
560
+ const shape: Record<string, z.ZodType> = {};
561
+ for (const [key, value] of Object.entries(schema.shape)) {
562
+ shape[key] = (value as z.ZodType).nullable().optional();
563
+ }
564
+ return z.object(shape);
565
+ }
566
+
567
+ /**
568
+ * A single profile entry within a PATCH body. All fields are
569
+ * `.nullable().optional()`: `null` = delete via deep-merge, omitted =
570
+ * unchanged. Named so HeyAPI generates `ProfilePatchEntry` as a top-level
571
+ * export in the SDK.
572
+ */
573
+ const ProfilePatchEntrySchema = nullablePartial(ProfileEntry)
574
+ .passthrough()
575
+ .meta({ id: "ProfilePatchEntry" });
576
+
577
+ /**
578
+ * A single call-site override within a PATCH body.
579
+ */
580
+ const CallSiteOverrideDraftSchema = nullablePartial(
581
+ LLMConfigFragment.extend({ profile: z.string().optional() }),
582
+ )
583
+ .passthrough()
584
+ .meta({ id: "CallSiteOverrideDraft" });
585
+
586
+ /**
587
+ * Request body schema for `PATCH /v1/config`.
588
+ *
589
+ * Mirrors the response shape but every field is `.nullable().optional()`:
590
+ * omitted keys are left unchanged by the daemon's deep-merge, `null` values
591
+ * delete the key. Uses the same Zod enums as `ConfigGetResponseSchema` so
592
+ * the generated SDK produces literal-union types — no hand-written patch
593
+ * types needed downstream.
594
+ */
595
+ const ConfigPatchRequestSchema = z
596
+ .object({
597
+ llm: z
598
+ .object({
599
+ default: nullablePartial(
600
+ LLMConfigFragment.extend({
601
+ provider_connection: z.string().optional(),
602
+ }),
603
+ )
604
+ .passthrough()
605
+ .nullable()
606
+ .optional(),
607
+ profiles: z
608
+ .record(z.string(), ProfilePatchEntrySchema.nullable())
609
+ .optional(),
610
+ profileOrder: z.array(z.string()).optional(),
611
+ activeProfile: z.string().nullable().optional(),
612
+ callSites: z
613
+ .record(z.string(), CallSiteOverrideDraftSchema.nullable())
614
+ .optional(),
615
+ profileSession: z
616
+ .object({
617
+ defaultTtlSeconds: z.number().optional(),
618
+ maxTtlSeconds: z.number().optional(),
619
+ })
620
+ .nullable()
621
+ .optional(),
622
+ })
623
+ .passthrough()
624
+ .optional(),
625
+ memory: MemoryWireConfigSchema.nullable().optional(),
626
+ services: z
627
+ .object({
628
+ "web-search": z
629
+ .object({
630
+ mode: ServiceModeSchema.optional(),
631
+ provider: z.string().optional(),
632
+ })
633
+ .passthrough()
634
+ .nullable()
635
+ .optional(),
636
+ "image-generation": z
637
+ .object({ mode: ServiceModeSchema.optional() })
638
+ .passthrough()
639
+ .nullable()
640
+ .optional(),
641
+ inference: z
642
+ .object({ mode: ServiceModeSchema.optional() })
643
+ .passthrough()
644
+ .nullable()
645
+ .optional(),
646
+ })
647
+ .passthrough()
648
+ .optional(),
649
+ })
650
+ .passthrough()
651
+ .meta({ id: "ConfigPatchRequest" });
652
+
471
653
  function handleGetConfig() {
472
654
  try {
473
655
  const config = applyContextDefaultsToRawConfig(loadRawConfig());
@@ -623,7 +805,10 @@ async function handlePatchConfig({ body }: RouteHandlerArgs) {
623
805
  deepMergeOverwrite(raw, patch);
624
806
 
625
807
  await commitConfigWrite(raw, "patch");
626
- return { ok: true };
808
+
809
+ const merged = applyContextDefaultsToRawConfig(loadRawConfig());
810
+ enrichProfilesWithVisionFlag(merged);
811
+ return merged;
627
812
  }
628
813
 
629
814
  /**
@@ -950,12 +1135,11 @@ async function handleGetLlmContext({
950
1135
  // turn finishes — see `assistant/src/memory/conversation-crud.ts`.
951
1136
  const conversationTotalEstimatedCostUsd =
952
1137
  conversation?.totalEstimatedCost ?? null;
953
- const memoryV3Selection = message
954
- ? await getMemoryV3SelectionForInspector(
955
- message.conversationId,
956
- memoryV2Activation?.turn ?? null,
957
- )
958
- : null;
1138
+ // v3 selections are keyed to the turn's message ids (stamped by the turn-end
1139
+ // backfill), independent of v2's tracker turn — so the panel shows whenever
1140
+ // the turn has v3 data, regardless of v2/v3 turn-counter drift.
1141
+ const memoryV3Selection =
1142
+ await getMemoryV3SelectionForInspectorByMessageIds(turnMessageIds);
959
1143
  return {
960
1144
  messageId,
961
1145
  conversationKind,
@@ -1201,6 +1385,7 @@ export const ROUTES: RouteDefinition[] = [
1201
1385
  summary: "Get full config",
1202
1386
  description: "Return the raw settings.json configuration object.",
1203
1387
  tags: ["config"],
1388
+ responseBody: ConfigGetResponseSchema,
1204
1389
  handler: handleGetConfig,
1205
1390
  },
1206
1391
  {
@@ -1215,8 +1400,8 @@ export const ROUTES: RouteDefinition[] = [
1215
1400
  description:
1216
1401
  "Deep-merge a partial JSON object into the settings.json configuration.",
1217
1402
  tags: ["config"],
1218
- requestBody: z.record(z.string(), z.unknown()),
1219
- responseBody: z.object({ ok: z.boolean() }),
1403
+ requestBody: ConfigPatchRequestSchema,
1404
+ responseBody: ConfigGetResponseSchema,
1220
1405
  handler: handlePatchConfig,
1221
1406
  },
1222
1407
  {
@@ -97,6 +97,7 @@ import {
97
97
  getOrCreateConversation,
98
98
  } from "../../memory/conversation-key-store.js";
99
99
  import { searchConversations } from "../../memory/conversation-queries.js";
100
+ import { MEMORY_RETROSPECTIVE_FORK_SOURCE } from "../../memory/memory-retrospective-constants.js";
100
101
  import { recordOnboardingEvent } from "../../memory/onboarding-events-store.js";
101
102
  import { buildSlackMessageDeepLinks } from "../../messaging/providers/slack/deep-link.js";
102
103
  import {
@@ -681,12 +682,23 @@ export function handleListMessages({
681
682
  // is left visible, the result will render as a standalone orphan because
682
683
  // `mergeToolResultsIntoAssistantMessages` has nothing to merge it into.
683
684
  //
685
+ // Exception: memory-retrospective fork conversations show their hidden rows
686
+ // (the retrospective instruction) so the run is readable as a distinct turn
687
+ // and its LLM call is inspectable. The instruction row also separates the
688
+ // copied source tail from the review turn, so `mergeConsecutiveAssistantMessages`
689
+ // no longer folds the review into the source's last assistant message. This
690
+ // is display-only and scoped to the fork source; the LLM-side `getMessages`
691
+ // loader is unfiltered regardless.
692
+ //
684
693
  // Only renderable roles reach this UI-facing transcript. `system` rows (a
685
694
  // permitted `MessageRole`, e.g. skill-authored context) are agent-context
686
695
  // scaffolding, never a displayed turn, so they are dropped here at the
687
696
  // source rather than narrowed away per-client.
697
+ const isRetrospectiveFork =
698
+ getConversation(resolvedConversationId)?.source ===
699
+ MEMORY_RETROSPECTIVE_FORK_SOURCE;
688
700
  const visibleFilter = (m: MessageRow) =>
689
- !isHiddenMessage(m.metadata) &&
701
+ (isRetrospectiveFork || !isHiddenMessage(m.metadata)) &&
690
702
  (m.role === "user" || m.role === "assistant");
691
703
 
692
704
  if (isPaginated) {
@@ -1030,6 +1042,7 @@ export function persistOnboardingArtifacts(onboarding: {
1030
1042
  tasks: string[];
1031
1043
  tone: string;
1032
1044
  userName?: string;
1045
+ occupation?: string;
1033
1046
  assistantName?: string;
1034
1047
  priorAssistants?: string[];
1035
1048
  cohort?: string;
@@ -2693,6 +2706,7 @@ export const ROUTES: RouteDefinition[] = [
2693
2706
  tasks: z.array(z.string()),
2694
2707
  tone: z.string(),
2695
2708
  userName: z.string().optional(),
2709
+ occupation: z.string().optional(),
2696
2710
  assistantName: z.string().optional(),
2697
2711
  googleConnected: z.boolean().optional(),
2698
2712
  googleScopes: z.array(z.string()).optional(),
@@ -8,14 +8,12 @@
8
8
 
9
9
  import { z } from "zod";
10
10
 
11
- import { requestSecretStandalone } from "../../daemon/handlers/shared.js";
12
- import { syncManualTokenConnection } from "../../oauth/manual-token-connection.js";
13
- import { credentialKey } from "../../security/credential-key.js";
14
- import { setSecureKeyAsync } from "../../security/secure-keys.js";
15
11
  import {
16
- assertMetadataWritable,
17
- upsertCredentialMetadata,
18
- } from "../../tools/credentials/metadata-store.js";
12
+ formatSlackChannelStatus,
13
+ persistPromptedCredential,
14
+ } from "../../credential-execution/prompted-credential.js";
15
+ import { requestSecretStandalone } from "../../daemon/handlers/shared.js";
16
+ import { assertMetadataWritable } from "../../tools/credentials/metadata-store.js";
19
17
  import { LOCAL_PRINCIPALS } from "../auth/route-policy.js";
20
18
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
21
19
 
@@ -37,9 +35,11 @@ const CredentialPromptParams = z.object({
37
35
  label: z.string().min(1),
38
36
  description: z.string().optional(),
39
37
  placeholder: z.string().optional(),
38
+ usageDescription: z.string().optional(),
40
39
  allowedDomains: z.array(z.string()).optional(),
41
40
  allowedTools: z.array(z.string()).optional(),
42
41
  injectionTemplates: z.array(InjectionTemplateSchema).optional(),
42
+ conversationId: z.string().optional(),
43
43
  });
44
44
 
45
45
  // ---------------------------------------------------------------------------
@@ -51,6 +51,7 @@ export type CredentialPromptResult = {
51
51
  error?: string;
52
52
  service?: string;
53
53
  field?: string;
54
+ message?: string;
54
55
  };
55
56
 
56
57
  // ---------------------------------------------------------------------------
@@ -68,8 +69,10 @@ async function handleCredentialPrompt({ body = {} }: RouteHandlerArgs) {
68
69
  label: validated.label,
69
70
  description: validated.description,
70
71
  placeholder: validated.placeholder,
72
+ purpose: validated.usageDescription,
71
73
  allowedTools: validated.allowedTools,
72
74
  allowedDomains: validated.allowedDomains,
75
+ conversationId: validated.conversationId,
73
76
  });
74
77
 
75
78
  if (!result.value) {
@@ -80,23 +83,41 @@ async function handleCredentialPrompt({ body = {} }: RouteHandlerArgs) {
80
83
  return { ok: false, error: reason };
81
84
  }
82
85
 
83
- const key = credentialKey(validated.service, validated.field);
84
- const stored = await setSecureKeyAsync(key, result.value);
85
- if (!stored) {
86
- return { ok: false, error: "Failed to store credential" };
86
+ const persisted = await persistPromptedCredential({
87
+ service: validated.service,
88
+ field: validated.field,
89
+ value: result.value,
90
+ delivery: result.delivery,
91
+ policy: {
92
+ allowedTools: validated.allowedTools,
93
+ allowedDomains: validated.allowedDomains,
94
+ usageDescription: validated.usageDescription,
95
+ injectionTemplates: validated.injectionTemplates,
96
+ },
97
+ });
98
+
99
+ if (persisted.outcome === "error") {
100
+ return { ok: false, error: persisted.message };
87
101
  }
88
102
 
89
- upsertCredentialMetadata(validated.service, validated.field, {
90
- allowedTools: validated.allowedTools,
91
- allowedDomains: validated.allowedDomains,
92
- injectionTemplates: validated.injectionTemplates,
93
- });
94
- await syncManualTokenConnection(validated.service);
103
+ if (persisted.outcome === "transient") {
104
+ return {
105
+ ok: true,
106
+ service: validated.service,
107
+ field: validated.field,
108
+ message: `One-time credential provided for ${validated.service}/${validated.field}. The value was not saved and will be consumed by the next operation.`,
109
+ };
110
+ }
111
+
112
+ const slackStatus = persisted.slackChannel
113
+ ? formatSlackChannelStatus(persisted.slackChannel).trim()
114
+ : "";
95
115
 
96
116
  return {
97
117
  ok: true,
98
118
  service: validated.service,
99
119
  field: validated.field,
120
+ message: slackStatus.length > 0 ? slackStatus : undefined,
100
121
  };
101
122
  }
102
123
 
@@ -124,6 +145,7 @@ export const ROUTES: RouteDefinition[] = [
124
145
  error: z.string().optional(),
125
146
  service: z.string().optional(),
126
147
  field: z.string().optional(),
148
+ message: z.string().optional(),
127
149
  }),
128
150
  },
129
151
  ];
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Caching layer for the empty-state (new-chat) greeting generated via the
3
+ * `POST /v1/btw` side-chain with `conversationKey: "greeting"`.
4
+ *
5
+ * Stores a single greeting string with a configurable TTL
6
+ * (`ui.emptyStateGreetingCacheTtlMs`, default 4h). A TTL of `0` (or less)
7
+ * disables caching entirely — reads always miss and writes are skipped — so
8
+ * the greeting regenerates on every request. This is the knob a workspace
9
+ * sets to always receive a fresh greeting.
10
+ *
11
+ * Storage uses the existing `memory_checkpoints` table (simple key-value
12
+ * store), mirroring {@link ./identity-intro-cache.ts}.
13
+ */
14
+
15
+ import { getConfig } from "../../config/loader.js";
16
+ import {
17
+ getMemoryCheckpoint,
18
+ setMemoryCheckpoint,
19
+ } from "../../memory/checkpoints.js";
20
+
21
+ const CHECKPOINT_KEY_TEXT = "empty_state:greeting:text";
22
+ const CHECKPOINT_KEY_TIMESTAMP = "empty_state:greeting:cached_at";
23
+
24
+ function cacheTtlMs(): number {
25
+ return getConfig().ui.emptyStateGreetingCacheTtlMs;
26
+ }
27
+
28
+ /**
29
+ * Return the cached greeting if present and within the configured TTL.
30
+ * Returns `null` when caching is disabled (TTL <= 0), the cache is empty,
31
+ * or the entry has expired.
32
+ */
33
+ export function getCachedEmptyStateGreeting(): string | null {
34
+ const ttl = cacheTtlMs();
35
+ if (ttl <= 0) return null; // caching disabled — always regenerate
36
+
37
+ try {
38
+ const text = getMemoryCheckpoint(CHECKPOINT_KEY_TEXT);
39
+ const timestampStr = getMemoryCheckpoint(CHECKPOINT_KEY_TIMESTAMP);
40
+ if (!text || !timestampStr) return null;
41
+
42
+ const cachedAt = Number(timestampStr);
43
+ if (Number.isNaN(cachedAt) || Date.now() - cachedAt > ttl) return null;
44
+
45
+ return text;
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Store a freshly generated greeting along with the current timestamp.
53
+ * No-ops when caching is disabled (TTL <= 0) so a zero-TTL workspace never
54
+ * writes a stale entry.
55
+ */
56
+ export function setCachedEmptyStateGreeting(text: string): void {
57
+ if (cacheTtlMs() <= 0) return; // caching disabled — skip write
58
+
59
+ try {
60
+ setMemoryCheckpoint(CHECKPOINT_KEY_TEXT, text);
61
+ setMemoryCheckpoint(CHECKPOINT_KEY_TIMESTAMP, String(Date.now()));
62
+ } catch {
63
+ // Cache write failure is non-fatal — next request will regenerate.
64
+ }
65
+ }
@@ -25,6 +25,13 @@ import { getLogger } from "../../util/logger.js";
25
25
  import { getWorkspacePromptPath } from "../../util/platform.js";
26
26
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
27
27
  import { BadRequestError, InternalError } from "./errors.js";
28
+ import {
29
+ paginateRuns,
30
+ parseRunsBeforeCursor,
31
+ parseRunsLimit,
32
+ RUNS_NEXT_CURSOR_SCHEMA,
33
+ RUNS_PAGINATION_QUERY_PARAMS,
34
+ } from "./runs-pagination.js";
28
35
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
29
36
 
30
37
  const log = getLogger("heartbeat-routes");
@@ -34,15 +41,18 @@ const log = getLogger("heartbeat-routes");
34
41
  // ---------------------------------------------------------------------------
35
42
 
36
43
  function handleListRuns(queryParams: Record<string, string>) {
37
- const rawLimit = Number(queryParams.limit ?? 20);
38
- const limit = Number.isFinite(rawLimit)
39
- ? Math.min(Math.max(Math.floor(rawLimit), 1), 100)
40
- : 20;
44
+ const limit = parseRunsLimit(queryParams, 20);
45
+ const before = parseRunsBeforeCursor(queryParams);
41
46
 
42
- const runs = listHeartbeatRuns(limit);
47
+ const { rows, nextCursor } = paginateRuns(
48
+ listHeartbeatRuns(limit + 1, before),
49
+ limit,
50
+ (r) => r.scheduledFor,
51
+ );
43
52
  const now = Date.now();
44
53
  return {
45
- runs: runs.map((r) => {
54
+ nextCursor,
55
+ runs: rows.map((r) => {
46
56
  const conversation = r.conversationId
47
57
  ? getConversation(r.conversationId)
48
58
  : null;
@@ -113,13 +123,7 @@ export const ROUTES: RouteDefinition[] = [
113
123
  summary: "List heartbeat runs",
114
124
  description: "Return recent heartbeat conversation runs.",
115
125
  tags: ["heartbeat"],
116
- queryParams: [
117
- {
118
- name: "limit",
119
- schema: { type: "integer" },
120
- description: "Max runs to return (default 20, max 100)",
121
- },
122
- ],
126
+ queryParams: RUNS_PAGINATION_QUERY_PARAMS(20),
123
127
  responseBody: z.object({
124
128
  runs: z
125
129
  .array(
@@ -140,6 +144,7 @@ export const ROUTES: RouteDefinition[] = [
140
144
  }),
141
145
  )
142
146
  .describe("Heartbeat run records"),
147
+ nextCursor: RUNS_NEXT_CURSOR_SCHEMA,
143
148
  }),
144
149
  handler: ({ queryParams }: RouteHandlerArgs) =>
145
150
  handleListRuns(queryParams ?? {}),
@@ -2,6 +2,10 @@ import { z } from "zod";
2
2
 
3
3
  import { getConfig } from "../../config/loader.js";
4
4
  import { resolveImageGenCredentials } from "../../media/image-credentials.js";
5
+ import {
6
+ describeImageModels,
7
+ resolveImageModel,
8
+ } from "../../media/image-models.js";
5
9
  import {
6
10
  generateImage,
7
11
  mapImageGenError,
@@ -77,8 +81,22 @@ async function handleImageGenerationGenerate(
77
81
  const config = getConfig();
78
82
  const svc = config.services["image-generation"];
79
83
 
84
+ // Resolve tier aliases (fast, quality, openai) to concrete model IDs via
85
+ // the registry; reject unknown values with the current catalog so the
86
+ // error is self-describing rather than a stale enum.
87
+ let resolvedModel = model as string | undefined;
88
+ if (typeof resolvedModel === "string" && resolvedModel) {
89
+ const entry = resolveImageModel(resolvedModel);
90
+ if (!entry) {
91
+ throw new BadRequestError(
92
+ `Unknown model "${resolvedModel}". Available models and aliases:\n${describeImageModels()}`,
93
+ );
94
+ }
95
+ resolvedModel = entry.id;
96
+ }
97
+
80
98
  // Derive provider from explicit model override when supplied
81
- const provider = providerForModel(model, svc.provider);
99
+ const provider = providerForModel(resolvedModel, svc.provider);
82
100
 
83
101
  // Resolve credentials
84
102
  const { credentials, errorHint } = await resolveImageGenCredentials({
@@ -103,7 +121,7 @@ async function handleImageGenerationGenerate(
103
121
  sourceImages: sourceImages as
104
122
  | Array<{ mimeType: string; dataBase64: string }>
105
123
  | undefined,
106
- model: (model as string | undefined) ?? svc.model,
124
+ model: resolvedModel ?? svc.model,
107
125
  variants: clampedVariants,
108
126
  });
109
127
 
@@ -4,6 +4,8 @@
4
4
  * verification, guardian action answers, approval interception, and
5
5
  * invite token redemption.
6
6
  */
7
+ import type { SourceMetadata } from "@vellumai/gateway-client";
8
+
7
9
  import {
8
10
  attachmentsToContentBlocks,
9
11
  type MessageAttachmentInput,
@@ -127,16 +129,16 @@ function trimMetadataString(
127
129
 
128
130
  function parseSlackActorTimezoneMetadata(
129
131
  sourceChannel: string,
130
- metadata: Record<string, unknown> | undefined,
132
+ metadata: SourceMetadata | undefined,
131
133
  ): SlackActorTimezoneMetadata | undefined {
132
134
  if (sourceChannel !== "slack") return undefined;
133
135
 
134
- const timezone = trimMetadataString(metadata, "timezone");
135
- const timezoneLabel = trimMetadataString(metadata, "timezoneLabel");
136
- const rawOffset = metadata?.timezoneOffsetSeconds;
136
+ const timezone = metadata?.timezone?.trim() || undefined;
137
+ const timezoneLabel = metadata?.timezoneLabel?.trim() || undefined;
137
138
  const timezoneOffsetSeconds =
138
- typeof rawOffset === "number" && Number.isFinite(rawOffset)
139
- ? rawOffset
139
+ metadata?.timezoneOffsetSeconds != null &&
140
+ Number.isFinite(metadata.timezoneOffsetSeconds)
141
+ ? metadata.timezoneOffsetSeconds
140
142
  : undefined;
141
143
 
142
144
  if (
@@ -193,17 +195,16 @@ function resolveSlackTranscriptTimestampTimezone(
193
195
 
194
196
  function resolveInboundClientTimezone(params: {
195
197
  bodyClientTimezone?: unknown;
196
- sourceMetadata?: Record<string, unknown>;
198
+ sourceMetadata?: SourceMetadata;
197
199
  conversationId: string;
198
200
  }): string | undefined {
199
201
  const bodyClientTimezone =
200
202
  typeof params.bodyClientTimezone === "string"
201
203
  ? canonicalizeTimeZone(params.bodyClientTimezone)
202
204
  : undefined;
203
- const metadataClientTimezone =
204
- typeof params.sourceMetadata?.clientTimezone === "string"
205
- ? canonicalizeTimeZone(params.sourceMetadata.clientTimezone)
206
- : undefined;
205
+ const metadataClientTimezone = params.sourceMetadata?.clientTimezone
206
+ ? canonicalizeTimeZone(params.sourceMetadata.clientTimezone)
207
+ : undefined;
207
208
  return (
208
209
  bodyClientTimezone ??
209
210
  metadataClientTimezone ??
@@ -248,7 +249,7 @@ export async function handleChannelInbound({
248
249
  attachmentIds?: string[];
249
250
  actorExternalId?: string;
250
251
  actorUsername?: string;
251
- sourceMetadata?: Record<string, unknown>;
252
+ sourceMetadata?: SourceMetadata;
252
253
  replyCallbackUrl?: string;
253
254
  callbackQueryId?: string;
254
255
  callbackData?: string;