@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
@@ -20,10 +20,16 @@
20
20
  * - Total size: 256 KB
21
21
  * - Age: 30 seconds
22
22
  *
23
- * The ring is in-memory and per-daemon-process. After a daemon restart
24
- * the seq resets and reconnecting clients fall through to the snapshot
25
- * path. The ring is sized generously enough that a typical refresh
26
- * round-trip (~1-3s) is well within window.
23
+ * The ring is in-memory and per-daemon-process, but the seq counter
24
+ * itself survives restarts: blocks of seq values are reserved ahead of
25
+ * use and the reserved ceiling is persisted to the workspace
26
+ * (`data/stream-seq.json`), so a restarted daemon resumes stamping
27
+ * above every seq the previous process could have emitted. Clients
28
+ * therefore never observe the counter moving backwards — a restart
29
+ * shows up as (at most) a bounded forward gap, which the normal
30
+ * gap-reconcile / snapshot-resync path already handles. The ring is
31
+ * sized generously enough that a typical refresh round-trip (~1-3s)
32
+ * is well within window.
27
33
  *
28
34
  * Persisted-seq map: alongside the live counter and ring, this module
29
35
  * tracks, per conversation, the `seq` of the last event whose content is
@@ -34,12 +40,17 @@
34
40
  * persistence flush (assistant rows persist incrementally, debounced, so
35
41
  * the snapshot can lag the live counter) -- never the live counter
36
42
  * itself, which would over-claim events that have streamed but not yet
37
- * been written. It shares the live counter's lifetime by design: both
38
- * are in-memory and reset together on restart, so a stored value can
39
- * never dangle against a fresh counter. The map is LRU-bounded; an
43
+ * been written. The map is in-memory and clears on restart; because the
44
+ * counter resumes above the persisted reservation, a value recorded by
45
+ * a previous process could only ever be lower than any seq the new
46
+ * process assigns -- never ambiguous against it. The map is LRU-bounded; an
40
47
  * evicted conversation reports no seq and the client cold-starts.
41
48
  */
42
49
 
50
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
51
+ import { dirname, join } from "node:path";
52
+
53
+ import { getWorkspaceDir } from "../util/platform.js";
43
54
  import type { AssistantEvent } from "./assistant-event.js";
44
55
 
45
56
  // ── Tunables ─────────────────────────────────────────────────────────
@@ -58,6 +69,15 @@ const RING_AGE_LIMIT_MS = 30_000;
58
69
  */
59
70
  const PERSISTED_SEQ_CONVERSATION_LIMIT = 1024;
60
71
 
72
+ /**
73
+ * How many seq values are reserved per persisted write. The counter can
74
+ * hand out seqs up to the persisted ceiling without touching disk, so
75
+ * the file is written once per block rather than once per event. A
76
+ * restart skips at most one block's worth of unused seqs — a bounded
77
+ * forward gap that clients already treat as a missed-events signal.
78
+ */
79
+ const SEQ_RESERVATION_BLOCK = 1024;
80
+
61
81
  // ── Types ────────────────────────────────────────────────────────────
62
82
 
63
83
  /**
@@ -97,6 +117,21 @@ interface RingEntry {
97
117
 
98
118
  interface AssistantStreamState {
99
119
  nextSeq: number;
120
+ /**
121
+ * Highest seq this process may assign without persisting a new
122
+ * reservation. `0` until the first stamp loads (or creates) the
123
+ * persisted reservation.
124
+ */
125
+ reservedSeqCeiling: number;
126
+ /** Whether the persisted reservation has been loaded this process. */
127
+ seqReservationLoaded: boolean;
128
+ /**
129
+ * Seq of the first event stamped by this process, or `0` before any
130
+ * stamp. Distinguishes a reservation skip (seqs below this value were
131
+ * never assigned by this process, so nothing is missing from the
132
+ * ring) from genuine ring eviction when judging replay validity.
133
+ */
134
+ firstStampedSeq: number;
100
135
  ring: RingEntry[];
101
136
  totalSizeBytes: number;
102
137
  /**
@@ -112,6 +147,9 @@ interface AssistantStreamState {
112
147
 
113
148
  const state: AssistantStreamState = {
114
149
  nextSeq: 1,
150
+ reservedSeqCeiling: 0,
151
+ seqReservationLoaded: false,
152
+ firstStampedSeq: 0,
115
153
  ring: [],
116
154
  totalSizeBytes: 0,
117
155
  persistedSeqByConversation: new Map(),
@@ -138,7 +176,9 @@ export function stampAndBuffer(
138
176
  ): void {
139
177
  if (event.conversationId == null) return;
140
178
 
179
+ reserveSeqCapacity();
141
180
  event.seq = state.nextSeq++;
181
+ if (state.firstStampedSeq === 0) state.firstStampedSeq = event.seq;
142
182
 
143
183
  // Approximate size by serialized JSON length. This is the same
144
184
  // bytes-on-wire we'll send, so it tracks ring memory pressure
@@ -187,8 +227,16 @@ export function getReplayWindow(
187
227
 
188
228
  if (state.ring.length === 0) return [];
189
229
 
230
+ // A cursor from before this process started can skip over the
231
+ // reservation gap: seqs below `firstStampedSeq` were never assigned
232
+ // by this process, so as long as the ring still holds everything
233
+ // this process stamped, no replayable event is missing. (Events from
234
+ // the previous process are unrecoverable either way — clients catch
235
+ // up on those via the snapshot path, triggered by the seq jump.)
190
236
  const oldest = state.ring[0]?.seq ?? Infinity;
191
- if (lastSeenSeq < oldest - 1) return null;
237
+ const coversRestartGap =
238
+ lastSeenSeq < state.firstStampedSeq && oldest === state.firstStampedSeq;
239
+ if (lastSeenSeq < oldest - 1 && !coversRestartGap) return null;
192
240
 
193
241
  return state.ring
194
242
  .filter(
@@ -260,6 +308,26 @@ export function getPersistedSeq(conversationId: string): number | null {
260
308
  */
261
309
  export function _resetStreamStateForTesting(): void {
262
310
  state.nextSeq = 1;
311
+ // Mark the reservation as loaded with no ceiling so the next stamp
312
+ // re-reserves from 1, ignoring any reservation file a previous test
313
+ // wrote into the (per-process temp) workspace.
314
+ state.reservedSeqCeiling = 0;
315
+ state.seqReservationLoaded = true;
316
+ state.firstStampedSeq = 0;
317
+ state.ring = [];
318
+ state.totalSizeBytes = 0;
319
+ state.persistedSeqByConversation.clear();
320
+ }
321
+
322
+ /**
323
+ * Simulate a daemon restart: clear all in-memory state and force the
324
+ * next stamp to reload the persisted seq reservation. Test-only.
325
+ */
326
+ export function _simulateRestartForTesting(): void {
327
+ state.nextSeq = 1;
328
+ state.reservedSeqCeiling = 0;
329
+ state.seqReservationLoaded = false;
330
+ state.firstStampedSeq = 0;
263
331
  state.ring = [];
264
332
  state.totalSizeBytes = 0;
265
333
  state.persistedSeqByConversation.clear();
@@ -286,6 +354,71 @@ export function _peekStreamForTesting(): {
286
354
 
287
355
  // ── Internals ────────────────────────────────────────────────────────
288
356
 
357
+ function seqReservationPath(): string {
358
+ return join(getWorkspaceDir(), "data", "stream-seq.json");
359
+ }
360
+
361
+ /**
362
+ * Ensure `state.nextSeq` is covered by the persisted reservation,
363
+ * loading the reservation file on the first stamp of the process and
364
+ * extending it by {@link SEQ_RESERVATION_BLOCK} whenever the counter
365
+ * reaches the ceiling.
366
+ *
367
+ * Persistence is best-effort: if the file cannot be read or written
368
+ * the counter falls back to in-memory-only behavior (the ceiling is
369
+ * still advanced so the write is retried at most once per block, not
370
+ * per event), matching the daemon's degraded-mode philosophy.
371
+ */
372
+ function reserveSeqCapacity(): void {
373
+ if (!state.seqReservationLoaded) {
374
+ state.seqReservationLoaded = true;
375
+ state.reservedSeqCeiling = readReservedCeiling();
376
+ if (state.reservedSeqCeiling >= state.nextSeq) {
377
+ state.nextSeq = state.reservedSeqCeiling + 1;
378
+ }
379
+ }
380
+
381
+ if (state.nextSeq <= state.reservedSeqCeiling) return;
382
+
383
+ const ceiling = state.nextSeq + SEQ_RESERVATION_BLOCK - 1;
384
+ try {
385
+ const path = seqReservationPath();
386
+ mkdirSync(dirname(path), { recursive: true });
387
+ const tmp = `${path}.tmp`;
388
+ writeFileSync(tmp, JSON.stringify({ reservedSeqCeiling: ceiling }));
389
+ renameSync(tmp, path);
390
+ } catch {
391
+ // Degraded mode: keep stamping from memory.
392
+ }
393
+ state.reservedSeqCeiling = ceiling;
394
+ }
395
+
396
+ function readReservedCeiling(): number {
397
+ try {
398
+ const parsed: unknown = JSON.parse(
399
+ readFileSync(seqReservationPath(), "utf8"),
400
+ );
401
+ if (
402
+ typeof parsed === "object" &&
403
+ parsed !== null &&
404
+ "reservedSeqCeiling" in parsed
405
+ ) {
406
+ const ceiling = (parsed as { reservedSeqCeiling: unknown })
407
+ .reservedSeqCeiling;
408
+ if (
409
+ typeof ceiling === "number" &&
410
+ Number.isFinite(ceiling) &&
411
+ ceiling > 0
412
+ ) {
413
+ return Math.floor(ceiling);
414
+ }
415
+ }
416
+ } catch {
417
+ // Missing or unreadable file: cold start from seq 1.
418
+ }
419
+ return 0;
420
+ }
421
+
289
422
  /**
290
423
  * Mirrors the delivery logic in `AssistantEventHub.publish()`. Returns
291
424
  * `true` when `subscriber` would have received the entry during live
@@ -67,6 +67,12 @@ export interface RunBackgroundJobOptions {
67
67
  trustContext: TrustContext;
68
68
  /** LLM call-site identifier — drives provider/model/effort/etc. resolution. */
69
69
  callSite: LLMCallSite;
70
+ /**
71
+ * Optional ad-hoc inference-profile override (`llm.profiles` key) applied
72
+ * to every LLM call the job's turn issues. Used by schedules with a pinned
73
+ * profile; omitted = the call site's default resolution.
74
+ */
75
+ overrideProfile?: string;
70
76
  /** Hard timeout for `processMessage` in milliseconds. */
71
77
  timeoutMs: number;
72
78
  /**
@@ -267,6 +273,9 @@ export async function runBackgroundJob(
267
273
  const work = processMessage(conversation.id, opts.prompt, {
268
274
  trustContext: opts.trustContext,
269
275
  callSite: opts.callSite,
276
+ ...(opts.overrideProfile
277
+ ? { overrideProfile: opts.overrideProfile }
278
+ : {}),
270
279
  });
271
280
  // Absorb late rejections: if the timeout wins the race, `work` keeps
272
281
  // running and may eventually reject — swallow so it doesn't surface as
@@ -92,6 +92,7 @@ export type ApprovalDecisionSource =
92
92
  | "whatsapp_button"
93
93
  | "slack_button"
94
94
  | "slack_reaction"
95
+ | "vellum_surface"
95
96
  | "plain_text";
96
97
 
97
98
  /** The structured result of a user's approval decision. */
@@ -9,6 +9,8 @@
9
9
  * verification) tokens that use the same `/start` deep-link mechanism.
10
10
  */
11
11
 
12
+ import type { CommandIntent, SourceMetadata } from "@vellumai/gateway-client";
13
+
12
14
  import type { ChannelId } from "../../channels/types.js";
13
15
  import {
14
16
  invalidateConfigCache,
@@ -130,17 +132,16 @@ export const telegramInviteAdapter: ChannelInviteAdapter = {
130
132
  },
131
133
 
132
134
  extractInboundToken(params: {
133
- commandIntent?: Record<string, unknown>;
135
+ commandIntent?: CommandIntent;
134
136
  content: string;
135
- sourceMetadata?: Record<string, unknown>;
137
+ sourceMetadata?: SourceMetadata;
136
138
  }): string | undefined {
137
139
  // Primary path: structured command intent from the gateway.
138
140
  // The gateway normalizes `/start <payload>` into
139
141
  // `{ type: 'start', payload: '<payload>' }`.
140
142
  if (
141
- params.commandIntent &&
142
- params.commandIntent.type === "start" &&
143
- typeof params.commandIntent.payload === "string"
143
+ params.commandIntent?.type === "start" &&
144
+ params.commandIntent.payload
144
145
  ) {
145
146
  const payload = params.commandIntent.payload;
146
147
  if (payload.startsWith(INVITE_TOKEN_PREFIX)) {
@@ -40,9 +40,9 @@ export const voiceInviteAdapter: ChannelInviteAdapter = {
40
40
  },
41
41
 
42
42
  extractInboundToken(_params: {
43
- commandIntent?: Record<string, unknown>;
43
+ commandIntent?: import("@vellumai/gateway-client").CommandIntent;
44
44
  content: string;
45
- sourceMetadata?: Record<string, unknown>;
45
+ sourceMetadata?: import("@vellumai/gateway-client").SourceMetadata;
46
46
  }): string | undefined {
47
47
  // Voice invite redemption bypasses generic token extraction — it uses
48
48
  // the identity-bound voice-code flow in invite-redemption-service.ts.
@@ -3,6 +3,8 @@
3
3
  * transport ↔ channel-invite-transports/* cycles (×5).
4
4
  */
5
5
 
6
+ import type { CommandIntent, SourceMetadata } from "@vellumai/gateway-client";
7
+
6
8
  import type { ChannelId } from "../channels/types.js";
7
9
 
8
10
  export interface InviteShareLink {
@@ -31,9 +33,9 @@ export interface ChannelInviteAdapter {
31
33
  * channels with link-based invites.
32
34
  */
33
35
  extractInboundToken?(params: {
34
- commandIntent?: Record<string, unknown>;
36
+ commandIntent?: CommandIntent;
35
37
  content: string;
36
- sourceMetadata?: Record<string, unknown>;
38
+ sourceMetadata?: SourceMetadata;
37
39
  }): string | undefined;
38
40
 
39
41
  /**
@@ -35,6 +35,7 @@ import {
35
35
  deliverReplyViaCallback,
36
36
  findAssistantReplyMessageIdForTurn,
37
37
  } from "./channel-reply-delivery.js";
38
+ import { finalizeEventDelivery } from "./finalize-event-delivery.js";
38
39
  import { deliverChannelReply } from "./gateway-client.js";
39
40
  import type { MessageProcessor } from "./http-types.js";
40
41
  import { createSlackDmTextDeliveryController } from "./slack-dm-text-delivery.js";
@@ -154,7 +155,7 @@ export async function sweepFailedEvents(
154
155
  parseInterfaceId(payload.sourceChannel) ??
155
156
  "web";
156
157
  const sourceMetadata = payload.sourceMetadata as
157
- | Record<string, unknown>
158
+ | import("@vellumai/gateway-client").SourceMetadata
158
159
  | undefined;
159
160
  const assistantId =
160
161
  typeof payload.assistantId === "string" ? payload.assistantId : undefined;
@@ -334,46 +335,23 @@ export async function sweepFailedEvents(
334
335
  continue;
335
336
  }
336
337
 
337
- if (replyCallbackUrl) {
338
- if (externalChatId) {
339
- try {
340
- if (slackDmTextDelivery) {
341
- await slackDmTextDelivery.waitForPendingDeliveries();
342
- }
343
- // processMessage above generated a fresh assistant response, so any
344
- // previously tracked final-delivery progress belongs to the old
345
- // response and must not carry over. Use live Slack DM progress as
346
- // the new baseline before final delivery so delivery-only retries do
347
- // not duplicate text if the callback fails before reporting progress.
348
- const liveDeliveryResumeOptions =
349
- slackDmTextDelivery?.getFinalDeliveryResumeOptions(replyMessageId);
350
- const finalDeliveryStartFromSegment =
351
- liveDeliveryResumeOptions?.startFromSegment ?? 0;
352
- updateDeliveredSegmentCount(event.id, finalDeliveryStartFromSegment);
353
- await deliverReplyViaCallback(
354
- event.conversationId,
355
- externalChatId,
356
- replyCallbackUrl,
357
- assistantId,
358
- {
359
- messageId: replyMessageId,
360
- sinceMessageId: userMessageId,
361
- startFromSegment: finalDeliveryStartFromSegment,
362
- ...(liveDeliveryResumeOptions?.messageTs
363
- ? { messageTs: liveDeliveryResumeOptions.messageTs }
364
- : {}),
365
- onSegmentDelivered: (count) =>
366
- updateDeliveredSegmentCount(event.id, count),
367
- },
368
- );
369
- markDeliveryDelivered(event.id);
370
- } catch (err) {
371
- log.error(
372
- { err, eventId: event.id },
373
- "Retry delivery failed for channel event",
374
- );
375
- recordDeliveryFailure(event.id, err);
376
- }
338
+ if (replyCallbackUrl && externalChatId) {
339
+ try {
340
+ await finalizeEventDelivery({
341
+ eventId: event.id,
342
+ conversationId: event.conversationId,
343
+ externalChatId,
344
+ replyCallbackUrl,
345
+ assistantId,
346
+ replyMessageId,
347
+ userMessageId,
348
+ slackDmTextDelivery,
349
+ });
350
+ } catch (err) {
351
+ log.error(
352
+ { err, eventId: event.id },
353
+ "Retry delivery failed for channel event",
354
+ );
377
355
  }
378
356
  }
379
357
  }
@@ -0,0 +1,72 @@
1
+ import { updateDeliveredSegmentCount } from "../memory/delivery-channels.js";
2
+ import {
3
+ markDeliveryDelivered,
4
+ recordDeliveryFailure,
5
+ } from "../memory/delivery-status.js";
6
+ import { deliverReplyViaCallback } from "./channel-reply-delivery.js";
7
+ import type { SlackDmTextDeliveryController } from "./slack-dm-text-delivery.js";
8
+
9
+ /**
10
+ * Owns the complete delivery-after-processing sequence for a channel
11
+ * inbound event: waits for in-flight live Slack DM deliveries to settle,
12
+ * persists the segment baseline so delivery-only retries resume correctly,
13
+ * delivers remaining content + attachments, and transitions the event to
14
+ * its terminal delivery state.
15
+ *
16
+ * Both the primary dispatch path and the processing-retry path call this
17
+ * function. The delivery-only retry path does NOT use this function — it
18
+ * reads the already-persisted segment count and calls
19
+ * `deliverReplyViaCallback` directly.
20
+ */
21
+ export async function finalizeEventDelivery(params: {
22
+ eventId: string;
23
+ conversationId: string;
24
+ externalChatId: string;
25
+ replyCallbackUrl: string;
26
+ assistantId: string | undefined;
27
+ replyMessageId: string | undefined;
28
+ userMessageId: string | undefined;
29
+ slackDmTextDelivery: SlackDmTextDeliveryController | undefined;
30
+ }): Promise<void> {
31
+ const {
32
+ eventId,
33
+ conversationId,
34
+ externalChatId,
35
+ replyCallbackUrl,
36
+ assistantId,
37
+ replyMessageId,
38
+ userMessageId,
39
+ slackDmTextDelivery,
40
+ } = params;
41
+
42
+ if (slackDmTextDelivery) {
43
+ await slackDmTextDelivery.waitForPendingDeliveries();
44
+ }
45
+
46
+ const resumeOptions =
47
+ slackDmTextDelivery?.getFinalDeliveryResumeOptions(replyMessageId);
48
+ const startFromSegment = resumeOptions?.startFromSegment ?? 0;
49
+ try {
50
+ updateDeliveredSegmentCount(eventId, startFromSegment);
51
+ await deliverReplyViaCallback(
52
+ conversationId,
53
+ externalChatId,
54
+ replyCallbackUrl,
55
+ assistantId,
56
+ {
57
+ messageId: replyMessageId,
58
+ sinceMessageId: userMessageId,
59
+ startFromSegment,
60
+ ...(resumeOptions?.messageTs
61
+ ? { messageTs: resumeOptions.messageTs }
62
+ : {}),
63
+ onSegmentDelivered: (count) =>
64
+ updateDeliveredSegmentCount(eventId, count),
65
+ },
66
+ );
67
+ markDeliveryDelivered(eventId);
68
+ } catch (err) {
69
+ recordDeliveryFailure(eventId, err);
70
+ throw err;
71
+ }
72
+ }
@@ -8,10 +8,8 @@
8
8
  *
9
9
  * Follows the same pattern as approval-message-composer.ts.
10
10
  */
11
- import { getLogger } from "../util/logger.js";
12
11
  import type {
13
12
  ComposeGuardianActionMessageOptions,
14
- GuardianActionCopyGenerator,
15
13
  GuardianActionMessageContext,
16
14
  } from "./message-composer-types.js";
17
15
 
@@ -21,21 +19,6 @@ export type {
21
19
  GuardianActionMessageScenario,
22
20
  } from "./message-composer-types.js";
23
21
 
24
- const log = getLogger("guardian-action-message-composer");
25
-
26
- // ---------------------------------------------------------------------------
27
- // Constants (exported for the daemon-injected generator implementation)
28
- // ---------------------------------------------------------------------------
29
-
30
- export const GUARDIAN_ACTION_COPY_TIMEOUT_MS = 4_000;
31
- export const GUARDIAN_ACTION_COPY_MAX_TOKENS = 200;
32
- export const GUARDIAN_ACTION_COPY_SYSTEM_PROMPT =
33
- "You are an assistant writing one user-facing message about a guardian action in a voice call scenario. " +
34
- "Keep it concise, natural, and conversational. Preserve factual details exactly. " +
35
- "These messages are spoken aloud, so use a warm, human tone. " +
36
- "Do not mention internal systems, scenario IDs, or technical details. " +
37
- "Return plain text only.";
38
-
39
22
  // ---------------------------------------------------------------------------
40
23
  // Public API
41
24
  // ---------------------------------------------------------------------------
@@ -51,7 +34,6 @@ export const GUARDIAN_ACTION_COPY_SYSTEM_PROMPT =
51
34
  export async function composeGuardianActionMessageGenerative(
52
35
  context: GuardianActionMessageContext,
53
36
  options: ComposeGuardianActionMessageOptions = {},
54
- generator?: GuardianActionCopyGenerator,
55
37
  ): Promise<string> {
56
38
  const fallbackText =
57
39
  options.fallbackText?.trim() || getGuardianActionFallbackMessage(context);
@@ -60,45 +42,9 @@ export async function composeGuardianActionMessageGenerative(
60
42
  return fallbackText;
61
43
  }
62
44
 
63
- if (generator) {
64
- try {
65
- const generated = await generator(context, options);
66
- if (generated) return generated;
67
- } catch (err) {
68
- log.warn(
69
- { err, scenario: context.scenario },
70
- "Failed to generate guardian action copy, using fallback",
71
- );
72
- }
73
- }
74
-
75
45
  return fallbackText;
76
46
  }
77
47
 
78
- /** @internal Exported for use by the daemon-injected generator implementation. */
79
- export function buildGuardianActionGenerationPrompt(
80
- context: GuardianActionMessageContext,
81
- fallbackText: string,
82
- requiredKeywords: string[] | undefined,
83
- ): string {
84
- const keywordClause =
85
- requiredKeywords && requiredKeywords.length > 0
86
- ? `Required words to include (as standalone words): ${requiredKeywords.join(
87
- ", ",
88
- )}.\n`
89
- : "";
90
- return [
91
- "Rewrite the following guardian action message as a natural, conversational reply.",
92
- "These messages are for voice call scenarios and may be spoken aloud.",
93
- "Keep the same concrete facts and next-step guidance.",
94
- keywordClause,
95
- `Context JSON: ${JSON.stringify(context)}`,
96
- `Fallback message: ${fallbackText}`,
97
- ]
98
- .filter(Boolean)
99
- .join("\n\n");
100
- }
101
-
102
48
  /** @internal Exported for use by the daemon-injected generator implementation. */
103
49
  export function includesRequiredKeywords(
104
50
  text: string,
@@ -7,10 +7,6 @@
7
7
 
8
8
  import type { ServerWebSocket } from "bun";
9
9
 
10
- import {
11
- startGuardianActionSweep,
12
- stopGuardianActionSweep,
13
- } from "../calls/guardian-action-sweep.js";
14
10
  import {
15
11
  activeMediaStreamSessions,
16
12
  MediaStreamCallSession,
@@ -29,7 +25,6 @@ import { isHttpAuthDisabled } from "../config/env.js";
29
25
  import { getIsPlatform } from "../config/env-registry.js";
30
26
  import { getConfig } from "../config/loader.js";
31
27
  import { createApprovalCopyGenerator } from "../daemon/approval-generators.js";
32
- import { createGuardianActionCopyGenerator } from "../daemon/guardian-action-generators.js";
33
28
  import { processMessage } from "../daemon/process-message.js";
34
29
  import { createLiveVoiceSession } from "../live-voice/live-voice-session.js";
35
30
  import { LiveVoiceSessionManager } from "../live-voice/live-voice-session-manager.js";
@@ -61,11 +56,11 @@ import {
61
56
  } from "./middleware/auth.js";
62
57
  import { withErrorHandling } from "./middleware/error-handler.js";
63
58
  import {
64
- apiRateLimiter,
65
59
  extractClientIp,
66
60
  ipRateLimiter,
67
61
  rateLimitHeaders,
68
62
  rateLimitResponse,
63
+ selectAuthenticatedRateLimiter,
69
64
  } from "./middleware/rate-limiter.js";
70
65
  import { withRequestLogging } from "./middleware/request-logger.js";
71
66
  import {
@@ -99,7 +94,6 @@ export { isPrivateAddress } from "./middleware/auth.js";
99
94
 
100
95
  import type {
101
96
  ApprovalCopyGenerator,
102
- GuardianActionCopyGenerator,
103
97
  RuntimeHttpServerOptions,
104
98
  } from "./http-types.js";
105
99
 
@@ -162,7 +156,6 @@ export class RuntimeHttpServer {
162
156
  private hostname: string;
163
157
 
164
158
  private readonly approvalCopyGenerator: ApprovalCopyGenerator;
165
- private readonly guardianActionCopyGenerator: GuardianActionCopyGenerator;
166
159
  private retrySweepTimer: ReturnType<typeof setInterval> | null = null;
167
160
  private sweepInProgress = false;
168
161
 
@@ -174,7 +167,6 @@ export class RuntimeHttpServer {
174
167
  this.hostname = options.hostname ?? DEFAULT_HOSTNAME;
175
168
 
176
169
  this.approvalCopyGenerator = createApprovalCopyGenerator();
177
- this.guardianActionCopyGenerator = createGuardianActionCopyGenerator();
178
170
  this.liveVoiceSessionManager = new LiveVoiceSessionManager({
179
171
  createSession: (context) => createLiveVoiceSession(context),
180
172
  });
@@ -486,9 +478,6 @@ export class RuntimeHttpServer {
486
478
  startGuardianExpirySweep(this.approvalCopyGenerator);
487
479
  log.info("Guardian approval expiry sweep started");
488
480
 
489
- startGuardianActionSweep(this.guardianActionCopyGenerator);
490
- log.info("Guardian action expiry sweep started");
491
-
492
481
  startCanonicalGuardianExpirySweep();
493
482
  log.info("Canonical guardian request expiry sweep started");
494
483
 
@@ -498,7 +487,6 @@ export class RuntimeHttpServer {
498
487
 
499
488
  async stop(): Promise<void> {
500
489
  stopGuardianExpirySweep();
501
- stopGuardianActionSweep();
502
490
  stopCanonicalGuardianExpirySweep();
503
491
  stopInferenceProfileSessionReaper();
504
492
  if (this.retrySweepTimer) {
@@ -702,7 +690,11 @@ export class RuntimeHttpServer {
702
690
  if (!isHttpAuthDisabled()) {
703
691
  const clientIp = extractClientIp(req, server);
704
692
  const token = extractBearerToken(req);
705
- const limiter = token ? apiRateLimiter : ipRateLimiter;
693
+ // Authenticated loopback clients (desktop app, CLI — anything on the
694
+ // daemon's own host) get a higher budget than remote clients.
695
+ const limiter = token
696
+ ? selectAuthenticatedRateLimiter(clientIp)
697
+ : ipRateLimiter;
706
698
  const limiterKind = token ? "authenticated" : "unauthenticated";
707
699
  const result = limiter.check(clientIp, path);
708
700
  if (!result.allowed) {
@@ -22,7 +22,6 @@ import type { AssistantEventHub } from "./assistant-event-hub.js";
22
22
  export type {
23
23
  ApprovalCopyGenerator,
24
24
  ComposeApprovalMessageGenerativeOptions,
25
- GuardianActionCopyGenerator,
26
25
  } from "./message-composer-types.js";
27
26
  import type { TrustContext } from "../daemon/trust-context.js";
28
27
 
@@ -123,12 +123,3 @@ export interface ComposeGuardianActionMessageOptions {
123
123
  maxTokens?: number;
124
124
  timeoutMs?: number;
125
125
  }
126
-
127
- /**
128
- * Daemon-injected function that generates guardian action copy using a provider.
129
- * Returns generated text or `null` on failure (caller falls back to deterministic text).
130
- */
131
- export type GuardianActionCopyGenerator = (
132
- context: GuardianActionMessageContext,
133
- options?: ComposeGuardianActionMessageOptions,
134
- ) => Promise<string | null>;