@vellumai/assistant 0.8.12 → 0.9.0-staging.1

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 (414) 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/openapi.yaml +6935 -5545
  15. package/package.json +8 -4
  16. package/scripts/generate-openapi.ts +66 -114
  17. package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
  18. package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
  19. package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
  20. package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
  21. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  22. package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
  23. package/src/__tests__/app-dir-path-guard.test.ts +27 -3
  24. package/src/__tests__/approval-cascade.test.ts +0 -5
  25. package/src/__tests__/approval-routes-http.test.ts +91 -0
  26. package/src/__tests__/assistant-stream-state.test.ts +107 -0
  27. package/src/__tests__/browser-fill-credential.test.ts +3 -3
  28. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
  29. package/src/__tests__/compaction-events.test.ts +63 -7
  30. package/src/__tests__/compaction-trail-store.test.ts +74 -1
  31. package/src/__tests__/compaction.benchmark.test.ts +63 -41
  32. package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
  33. package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
  34. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
  35. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
  36. package/src/__tests__/conversation-history-web-search.test.ts +7 -0
  37. package/src/__tests__/conversation-process-callsite.test.ts +0 -5
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
  39. package/src/__tests__/conversation-queue.test.ts +0 -5
  40. package/src/__tests__/conversation-slash-queue.test.ts +0 -5
  41. package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
  42. package/src/__tests__/conversation-speed-override.test.ts +0 -5
  43. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  44. package/src/__tests__/conversation-usage.test.ts +2 -0
  45. package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
  46. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
  47. package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
  48. package/src/__tests__/credential-broker-server-use.test.ts +2 -2
  49. package/src/__tests__/credential-broker.test.ts +1 -1
  50. package/src/__tests__/credential-prompt-route.test.ts +417 -0
  51. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  52. package/src/__tests__/credential-vault.test.ts +37 -0
  53. package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
  54. package/src/__tests__/dynamic-page-surface.test.ts +125 -0
  55. package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
  56. package/src/__tests__/gateway-flag-listener.test.ts +24 -7
  57. package/src/__tests__/guardian-action-sweep.test.ts +56 -219
  58. package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
  59. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  60. package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
  61. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
  62. package/src/__tests__/llm-resolver.test.ts +115 -0
  63. package/src/__tests__/managed-profile-guard.test.ts +6 -5
  64. package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
  65. package/src/__tests__/media-generate-image.test.ts +20 -9
  66. package/src/__tests__/model-intents.test.ts +1 -1
  67. package/src/__tests__/normalize-onboarding.test.ts +26 -0
  68. package/src/__tests__/notification-decision-strategy.test.ts +4 -2
  69. package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
  70. package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
  71. package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
  72. package/src/__tests__/require-fresh-approval.test.ts +425 -1
  73. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
  74. package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
  75. package/src/__tests__/schedule-routes.test.ts +257 -4
  76. package/src/__tests__/schedule-store.test.ts +60 -0
  77. package/src/__tests__/schedule-tools.test.ts +247 -2
  78. package/src/__tests__/skill-execute-input.test.ts +85 -0
  79. package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
  80. package/src/__tests__/skills.test.ts +3 -3
  81. package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
  82. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  83. package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
  84. package/src/__tests__/system-prompt.test.ts +1 -1
  85. package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
  86. package/src/__tests__/task-scheduler.test.ts +299 -0
  87. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
  88. package/src/__tests__/tool-result-spool.test.ts +3 -1
  89. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
  90. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
  91. package/src/agent/compaction-circuit.ts +11 -0
  92. package/src/agent/loop.ts +181 -12
  93. package/src/api/constants/call-sites.ts +12 -0
  94. package/src/api/events/assistant-thinking-delta.ts +10 -0
  95. package/src/api/events/usage-update.ts +7 -0
  96. package/src/api/index.ts +4 -1
  97. package/src/api/responses/memory-v3-selection-log.ts +18 -11
  98. package/src/approvals/approval-primitive.ts +2 -2
  99. package/src/background-wake/background-wake-routes.test.ts +5 -2
  100. package/src/bundler/compiler-tools.ts +1 -1
  101. package/src/calls/call-domain.ts +1 -1
  102. package/src/calls/guardian-action-sweep.ts +16 -93
  103. package/src/cli/AGENTS.md +4 -0
  104. package/src/cli/commands/__tests__/schedules.test.ts +430 -1
  105. package/src/cli/commands/credentials.ts +28 -24
  106. package/src/cli/commands/image-generation.ts +23 -9
  107. package/src/cli/commands/notifications.ts +1 -1
  108. package/src/cli/commands/plugins.ts +89 -46
  109. package/src/cli/commands/schedules.ts +384 -11
  110. package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
  111. package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
  112. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
  113. package/src/cli/lib/inspect-plugin.ts +62 -1
  114. package/src/cli/lib/install-from-github.ts +52 -5
  115. package/src/cli/lib/upgrade-plugin.ts +18 -0
  116. package/src/config/__tests__/workflows-schema.test.ts +60 -0
  117. package/src/config/bundled-skills/acp/SKILL.md +2 -2
  118. package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
  119. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
  120. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
  121. package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
  122. package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
  123. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
  124. package/src/config/bundled-skills/schedule/SKILL.md +7 -2
  125. package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
  126. package/src/config/bundled-skills/workflows/SKILL.md +214 -0
  127. package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
  128. package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
  129. package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
  130. package/src/config/bundled-tool-registry.ts +14 -2
  131. package/src/config/call-site-defaults.ts +5 -0
  132. package/src/config/feature-flag-registry.json +12 -4
  133. package/src/config/llm-context-resolution.ts +8 -0
  134. package/src/config/llm-resolver.ts +30 -0
  135. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
  136. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
  137. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
  138. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
  139. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
  140. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
  141. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
  142. package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
  143. package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
  144. package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
  145. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
  146. package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
  147. package/src/config/schema.ts +2 -0
  148. package/src/config/schemas/call-site-catalog.ts +7 -0
  149. package/src/config/schemas/heartbeat.ts +4 -1
  150. package/src/config/schemas/llm.ts +33 -26
  151. package/src/config/schemas/memory-retrospective.ts +19 -0
  152. package/src/config/schemas/platform.ts +8 -0
  153. package/src/config/schemas/services.ts +5 -2
  154. package/src/config/schemas/workflows.ts +42 -0
  155. package/src/config/skills.ts +3 -3
  156. package/src/context/compactor.ts +273 -39
  157. package/src/context/post-turn-tool-result-truncation.ts +23 -6
  158. package/src/context/tool-result-spool.ts +12 -17
  159. package/src/credential-execution/executable-discovery.ts +1 -1
  160. package/src/credential-execution/process-manager.ts +37 -3
  161. package/src/credential-execution/prompted-credential.ts +205 -0
  162. package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
  163. package/src/daemon/conversation-process.ts +11 -2
  164. package/src/daemon/conversation-surfaces.ts +75 -0
  165. package/src/daemon/conversation-tool-setup.ts +103 -26
  166. package/src/daemon/conversation-usage.ts +2 -0
  167. package/src/daemon/conversation.ts +115 -11
  168. package/src/daemon/handlers/shared.ts +26 -14
  169. package/src/daemon/host-cu-proxy.ts +15 -12
  170. package/src/daemon/host-file-proxy.ts +15 -12
  171. package/src/daemon/host-transfer-proxy.ts +30 -24
  172. package/src/daemon/lifecycle.ts +40 -3
  173. package/src/daemon/message-protocol.ts +3 -0
  174. package/src/daemon/message-types/messages.ts +2 -10
  175. package/src/daemon/message-types/workflows.ts +49 -0
  176. package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
  177. package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
  178. package/src/daemon/process-message.ts +6 -0
  179. package/src/daemon/tool-setup-types.ts +57 -0
  180. package/src/daemon/wake-conversation-ops.ts +18 -0
  181. package/src/heartbeat/heartbeat-run-store.ts +8 -2
  182. package/src/home/feed-types.ts +1 -1
  183. package/src/ipc/gateway-flag-listener.ts +28 -6
  184. package/src/mcp/mcp-auth-state.ts +8 -20
  185. package/src/media/__tests__/image-models.test.ts +57 -0
  186. package/src/media/image-models.ts +66 -0
  187. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
  188. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
  189. package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
  190. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
  191. package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
  192. package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
  193. package/src/memory/auto-analysis-enqueue.ts +14 -1
  194. package/src/memory/compaction-log-store-clickhouse.ts +6 -4
  195. package/src/memory/conversation-crud.ts +9 -2
  196. package/src/memory/conversation-disk-view.ts +1 -1
  197. package/src/memory/conversation-queries.ts +22 -7
  198. package/src/memory/db-init.ts +20 -0
  199. package/src/memory/db-maintenance.ts +16 -0
  200. package/src/memory/embedding-runtime-manager.ts +1 -1
  201. package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
  202. package/src/memory/llm-request-log-source-local.ts +19 -1
  203. package/src/memory/llm-request-log-source.ts +35 -6
  204. package/src/memory/llm-request-log-store.ts +90 -2
  205. package/src/memory/memory-retrospective-constants.ts +9 -0
  206. package/src/memory/memory-retrospective-enqueue.ts +3 -6
  207. package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
  208. package/src/memory/memory-retrospective-job.ts +500 -208
  209. package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
  210. package/src/memory/memory-retrospective-state.ts +85 -2
  211. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
  212. package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
  213. package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
  214. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
  215. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
  216. package/src/memory/migrations/284-workflow-runs.ts +51 -0
  217. package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
  218. package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
  219. package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
  220. package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
  221. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
  222. package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
  223. package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
  224. package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
  225. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
  226. package/src/memory/migrations/index.ts +10 -0
  227. package/src/memory/preloaded-apps.ts +116 -0
  228. package/src/memory/schema/infrastructure.ts +4 -0
  229. package/src/memory/schema/memory-core.ts +4 -0
  230. package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
  231. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
  232. package/src/memory/v2/__tests__/page-store.test.ts +13 -2
  233. package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
  234. package/src/memory/v2/frontmatter-sweep.ts +7 -6
  235. package/src/memory/v2/page-store.ts +4 -3
  236. package/src/memory/v2/qdrant.ts +42 -3
  237. package/src/memory/v2/types.ts +16 -10
  238. package/src/messaging/draft-store.ts +1 -1
  239. package/src/notifications/access-request-copy.ts +200 -113
  240. package/src/notifications/adapters/slack.ts +250 -111
  241. package/src/notifications/adapters/telegram.ts +7 -44
  242. package/src/notifications/approval-card-builder.ts +93 -0
  243. package/src/notifications/broadcaster.ts +74 -0
  244. package/src/notifications/conversation-pairing.ts +8 -6
  245. package/src/notifications/copy-composer.ts +32 -26
  246. package/src/notifications/decision-engine.ts +59 -7
  247. package/src/notifications/guardian-question-mode.ts +145 -155
  248. package/src/notifications/home-feed-side-effect.ts +1 -1
  249. package/src/notifications/notification-utils.ts +66 -0
  250. package/src/notifications/signal.ts +6 -0
  251. package/src/notifications/tool-approval-copy.ts +142 -0
  252. package/src/notifications/types.ts +19 -0
  253. package/src/permissions/threshold.ts +11 -0
  254. package/src/plugin-api/types.ts +16 -4
  255. package/src/plugins/defaults/compaction/window-manager.ts +44 -0
  256. package/src/plugins/defaults/index.ts +46 -0
  257. package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
  258. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
  259. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
  260. package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
  261. package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
  262. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
  263. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
  264. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
  265. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
  266. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
  267. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
  268. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
  269. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
  270. package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
  271. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
  272. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
  273. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
  274. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
  275. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
  276. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
  277. package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
  278. package/src/prompts/__tests__/system-prompt.test.ts +100 -1
  279. package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
  280. package/src/prompts/normalize-onboarding.ts +2 -0
  281. package/src/prompts/persona-resolver.ts +3 -0
  282. package/src/prompts/system-prompt.ts +51 -2
  283. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
  284. package/src/prompts/templates/system-sections.ts +8 -3
  285. package/src/providers/call-site-routing.ts +6 -3
  286. package/src/providers/fireworks/client.ts +3 -0
  287. package/src/providers/inference/auth.ts +52 -46
  288. package/src/providers/model-intents.ts +2 -2
  289. package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
  290. package/src/providers/openai/chat-completions-provider.ts +47 -9
  291. package/src/providers/openai/coerce-object-args.ts +104 -0
  292. package/src/providers/retry.ts +8 -5
  293. package/src/providers/types.ts +10 -0
  294. package/src/runtime/__tests__/agent-wake.test.ts +629 -7
  295. package/src/runtime/access-request-helper.ts +16 -9
  296. package/src/runtime/agent-wake.ts +302 -51
  297. package/src/runtime/assistant-stream-state.ts +141 -8
  298. package/src/runtime/background-job-runner.ts +9 -0
  299. package/src/runtime/channel-approval-types.ts +1 -0
  300. package/src/runtime/guardian-action-message-composer.ts +0 -54
  301. package/src/runtime/http-server.ts +6 -14
  302. package/src/runtime/http-types.ts +0 -1
  303. package/src/runtime/message-composer-types.ts +0 -9
  304. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
  305. package/src/runtime/middleware/auth.ts +27 -3
  306. package/src/runtime/middleware/rate-limiter.ts +28 -1
  307. package/src/runtime/migrations/vbundle-builder.ts +6 -5
  308. package/src/runtime/pending-interactions.ts +20 -1
  309. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
  310. package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
  311. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
  312. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
  313. package/src/runtime/routes/approval-routes.ts +35 -8
  314. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
  315. package/src/runtime/routes/btw-routes.ts +37 -1
  316. package/src/runtime/routes/channel-route-shared.ts +3 -1
  317. package/src/runtime/routes/consolidation-routes.ts +17 -13
  318. package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
  319. package/src/runtime/routes/conversation-list-routes.ts +41 -4
  320. package/src/runtime/routes/conversation-query-routes.ts +196 -11
  321. package/src/runtime/routes/conversation-routes.ts +15 -1
  322. package/src/runtime/routes/credential-prompt-routes.ts +39 -17
  323. package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
  324. package/src/runtime/routes/heartbeat-routes.ts +18 -13
  325. package/src/runtime/routes/image-generation-routes.ts +20 -2
  326. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +26 -1
  327. package/src/runtime/routes/index.ts +6 -0
  328. package/src/runtime/routes/log-export/AGENTS.md +1 -1
  329. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  330. package/src/runtime/routes/migration-routes.ts +5 -9
  331. package/src/runtime/routes/plugins-routes.ts +26 -0
  332. package/src/runtime/routes/ps-routes.ts +10 -8
  333. package/src/runtime/routes/retrospective-routes.ts +235 -0
  334. package/src/runtime/routes/runs-pagination.ts +75 -0
  335. package/src/runtime/routes/schedule-routes.ts +367 -59
  336. package/src/runtime/routes/sounds-config-routes.ts +239 -0
  337. package/src/runtime/routes/surface-action-routes.ts +84 -4
  338. package/src/runtime/routes/workflow-routes.test.ts +372 -0
  339. package/src/runtime/routes/workflow-routes.ts +363 -0
  340. package/src/runtime/routes/workspace-routes.test.ts +61 -1
  341. package/src/runtime/routes/workspace-routes.ts +26 -1
  342. package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
  343. package/src/runtime/services/analyze-conversation.ts +26 -13
  344. package/src/schedule/inference-profile.ts +28 -0
  345. package/src/schedule/schedule-store.ts +122 -4
  346. package/src/schedule/scheduler-types.ts +6 -0
  347. package/src/schedule/scheduler.ts +96 -0
  348. package/src/security/secret-allowlist.ts +1 -1
  349. package/src/skills/path-classifier.ts +1 -1
  350. package/src/tools/browser/browser-execution.ts +9 -11
  351. package/src/tools/credentials/broker.ts +4 -4
  352. package/src/tools/credentials/vault.ts +26 -137
  353. package/src/tools/executor.ts +69 -0
  354. package/src/tools/flag-gated-tools.test.ts +76 -0
  355. package/src/tools/permission-checker.ts +8 -1
  356. package/src/tools/registry.ts +51 -0
  357. package/src/tools/schedule/create.ts +77 -1
  358. package/src/tools/schedule/list.ts +1 -0
  359. package/src/tools/schedule/update.ts +73 -1
  360. package/src/tools/skills/execute.ts +56 -0
  361. package/src/tools/terminal/shell.ts +1 -1
  362. package/src/tools/ui-surface/definitions.ts +48 -2
  363. package/src/tools/workflows/manage-workflows.ts +183 -0
  364. package/src/tools/workflows/run-workflow.test.ts +442 -0
  365. package/src/tools/workflows/run-workflow.ts +88 -0
  366. package/src/types/onboarding-context.ts +2 -0
  367. package/src/usage/attribution.ts +24 -0
  368. package/src/util/canonicalize-identity.ts +12 -3
  369. package/src/util/platform.ts +17 -17
  370. package/src/watcher/__tests__/engine.test.ts +24 -0
  371. package/src/watcher/__tests__/telemetry.test.ts +135 -0
  372. package/src/watcher/engine.ts +7 -0
  373. package/src/watcher/telemetry.ts +74 -0
  374. package/src/workflows/capabilities.test.ts +365 -0
  375. package/src/workflows/capabilities.ts +359 -0
  376. package/src/workflows/deterministic-stringify.ts +27 -0
  377. package/src/workflows/engine-integration.test.ts +656 -0
  378. package/src/workflows/engine.test.ts +1144 -0
  379. package/src/workflows/engine.ts +1078 -0
  380. package/src/workflows/fanout-load.test.ts +168 -0
  381. package/src/workflows/journal-store.test.ts +369 -0
  382. package/src/workflows/journal-store.ts +470 -0
  383. package/src/workflows/leaf-runner.test.ts +704 -0
  384. package/src/workflows/leaf-runner.ts +589 -0
  385. package/src/workflows/library.test.ts +134 -0
  386. package/src/workflows/library.ts +124 -0
  387. package/src/workflows/run-manager.test.ts +711 -0
  388. package/src/workflows/run-manager.ts +593 -0
  389. package/src/workflows/sandbox-escape.test.ts +339 -0
  390. package/src/workflows/sandbox.test.ts +251 -0
  391. package/src/workflows/sandbox.ts +447 -0
  392. package/src/workspace/adaptive-thinking-repair.ts +33 -11
  393. package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
  394. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
  395. package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
  396. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
  397. package/src/workspace/migrations/031-drop-user-md.ts +1 -4
  398. package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
  399. package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
  400. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
  401. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
  402. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
  403. package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
  404. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
  405. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
  406. package/src/workspace/migrations/registry.ts +6 -0
  407. package/src/workspace/migrations/runner.ts +1 -1
  408. package/tsconfig.json +1 -1
  409. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
  410. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
  411. package/src/__tests__/guardian-action-store.test.ts +0 -106
  412. package/src/daemon/guardian-action-generators.ts +0 -71
  413. package/src/memory/guardian-action-store.ts +0 -484
  414. 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
 
@@ -23,7 +23,7 @@ import {
23
23
  findByInviteCodeHash,
24
24
  findByInviteCodeHashAnyChannel,
25
25
  } from "../../../memory/invite-store.js";
26
- import { MESSAGE_PREVIEW_MAX_LENGTH } from "../../../notifications/access-request-copy.js";
26
+ import { MESSAGE_PREVIEW_MAX_LENGTH } from "../../../notifications/notification-utils.js";
27
27
  import { resolveGuardianName } from "../../../prompts/user-reference.js";
28
28
  import { getLogger } from "../../../util/logger.js";
29
29
  import { truncate } from "../../../util/truncate.js";
@@ -137,6 +137,16 @@ export async function enforceIngressAcl(
137
137
  externalMessageId,
138
138
  } = params;
139
139
 
140
+ // Trust signals from Slack users.info, forwarded via sourceMetadata.
141
+ const isStranger = sourceMetadata?.isStranger === true ? true : undefined;
142
+ const isRestricted = sourceMetadata?.isRestricted === true ? true : undefined;
143
+
144
+ // Slack message timestamp for permalink construction.
145
+ const messageTs =
146
+ typeof sourceMetadata?.messageId === "string"
147
+ ? sourceMetadata.messageId
148
+ : undefined;
149
+
140
150
  let resolvedMember: ResolvedMember | null = null;
141
151
 
142
152
  // /start gv_<token> bootstrap commands must also bypass ACL — the user
@@ -293,6 +303,9 @@ export async function enforceIngressAcl(
293
303
  trimmedContent,
294
304
  MESSAGE_PREVIEW_MAX_LENGTH,
295
305
  ),
306
+ isStranger,
307
+ isRestricted,
308
+ messageTs,
296
309
  });
297
310
  } catch (err) {
298
311
  log.error(
@@ -365,6 +378,9 @@ export async function enforceIngressAcl(
365
378
  trimmedContent,
366
379
  MESSAGE_PREVIEW_MAX_LENGTH,
367
380
  ),
381
+ isStranger,
382
+ isRestricted,
383
+ messageTs,
368
384
  });
369
385
  } catch (err) {
370
386
  log.error(
@@ -401,6 +417,9 @@ export async function enforceIngressAcl(
401
417
  trimmedContent,
402
418
  MESSAGE_PREVIEW_MAX_LENGTH,
403
419
  ),
420
+ isStranger,
421
+ isRestricted,
422
+ messageTs,
404
423
  });
405
424
  guardianNotified = accessResult.notified;
406
425
  } catch (err) {
@@ -575,6 +594,9 @@ export async function enforceIngressAcl(
575
594
  trimmedContent,
576
595
  MESSAGE_PREVIEW_MAX_LENGTH,
577
596
  ),
597
+ isStranger,
598
+ isRestricted,
599
+ messageTs,
578
600
  });
579
601
  } catch (err) {
580
602
  log.error(
@@ -640,6 +662,9 @@ export async function enforceIngressAcl(
640
662
  trimmedContent,
641
663
  MESSAGE_PREVIEW_MAX_LENGTH,
642
664
  ),
665
+ isStranger,
666
+ isRestricted,
667
+ messageTs,
643
668
  });
644
669
  guardianNotified = accessResult.notified;
645
670
  } catch (err) {