@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
@@ -493,8 +493,11 @@ export type PostModelCallDecision = "continue" | "stop";
493
493
  * the hook's result as the persisted and streamed message), and
494
494
  * {@link stopReason} carries the provider's stop reason. Fires once per model
495
495
  * call, including tool-bearing turns (a reply can carry both text and
496
- * `tool_use`), so a hook should transform only the blocks it owns and leave
497
- * others — notably `tool_use` — intact.
496
+ * `tool_use`). A hook should leave blocks it does not own untouched, but it
497
+ * may **append a `tool_use` block** to invoke a tool as if the model had
498
+ * called it — the loop executes whatever the finalized content carries (see
499
+ * {@link content}). This is the supported way for a plugin to drive a tool
500
+ * (e.g. render a surface via `ui_show`) deterministically after a turn.
498
501
  * - **Provider rejection.** The call threw before any reply existed.
499
502
  * {@link error} holds the rejection, {@link content} is empty, and
500
503
  * {@link stopReason} is `null`. A hook that recognizes the rejection may
@@ -518,8 +521,17 @@ export interface PostModelCallContext {
518
521
  /** The call site this message serves — `"mainAgent"` for the user-facing reply; `null` when untagged. */
519
522
  readonly callSite: LLMCallSite | null;
520
523
  /**
521
- * The finalized message content. Mutable — transform the text blocks and leave
522
- * `tool_use` (and other non-text blocks) intact. Empty on a provider rejection.
524
+ * The finalized message content. Mutable, and the source of truth for both
525
+ * persistence and execution: the loop derives the turn's executable tool
526
+ * calls from this array *after* the hook chain runs. A hook may transform the
527
+ * text blocks, **append a `tool_use` block** to invoke a tool as if the model
528
+ * had called it (executed through the normal tool path — trust rules apply,
529
+ * and its result/surface is appended after any already-streamed text without
530
+ * discarding it), or drop a `tool_use` block to suppress a call. The host
531
+ * assigns an id to any appended `tool_use` block whose `id` is empty or
532
+ * collides. Empty on a provider rejection. Appended `tool_use` blocks are
533
+ * dropped on a truncated (max-tokens) turn, which short-circuits before the
534
+ * executor runs and so cannot pair a tool call with a result.
523
535
  */
524
536
  content: ContentBlock[];
525
537
  /**
@@ -115,6 +115,13 @@ export interface ContextWindowResult {
115
115
  * `context_too_large`. Omitted on the ordinary compaction path.
116
116
  */
117
117
  autoCompressApplied?: boolean;
118
+ /**
119
+ * Propagated from the compactor: the deterministic forward-cut hit the tail
120
+ * floor while still over the low-watermark budget. {@link _maybeCompact} reads
121
+ * it to stop retrying — a second pass lands on the same floor and frees
122
+ * nothing. See {@link CompactionRunResult.tailFloorReached}.
123
+ */
124
+ tailFloorReached?: boolean;
118
125
  }
119
126
 
120
127
  export interface ShouldCompactResult {
@@ -627,6 +634,28 @@ export class ContextWindowManager {
627
634
  return Math.floor(this.config.maxInputTokens * (1 - safetyMargin));
628
635
  }
629
636
 
637
+ /**
638
+ * Low-watermark token budget a compaction pass aims to land the rebuilt
639
+ * history at or below. Derived from `contextWindow.targetBudgetRatio` (the
640
+ * fraction of the window to retain after compaction) minus the summary's own
641
+ * reserve (`summaryBudgetRatio`), so the post-compaction total — summary plus
642
+ * verbatim tail — fits the target. Clamped meaningfully below the
643
+ * auto-threshold success gate: a target at or above the gate would defeat the
644
+ * purpose (a pass could "succeed" while landing a hair under the trigger and
645
+ * thrash on the next tick), so it is pulled down to at most 80% of the gate.
646
+ */
647
+ private resolveCompactionTargetTokens(thresholdTokens: number): number {
648
+ const { maxInputTokens, targetBudgetRatio, summaryBudgetRatio } =
649
+ this.config;
650
+ const verbatimRatio = Math.max(
651
+ 0,
652
+ targetBudgetRatio - (summaryBudgetRatio ?? 0),
653
+ );
654
+ const raw = Math.floor(maxInputTokens * verbatimRatio);
655
+ const ceiling = Math.floor(thresholdTokens * 0.8);
656
+ return Math.max(1, Math.min(raw, ceiling));
657
+ }
658
+
630
659
  private async _maybeCompact(
631
660
  messages: Message[],
632
661
  signal?: AbortSignal,
@@ -642,6 +671,7 @@ export class ContextWindowManager {
642
671
  const thresholdTokens = Math.floor(
643
672
  this.config.maxInputTokens * compaction.autoThreshold,
644
673
  );
674
+ const targetTokens = this.resolveCompactionTargetTokens(thresholdTokens);
645
675
 
646
676
  if (!compaction.enabled) {
647
677
  return noopResult(messages, previousEstimatedInputTokens, {
@@ -681,6 +711,7 @@ export class ContextWindowManager {
681
711
  tools: this.resolveTools?.(),
682
712
  compaction,
683
713
  maxInputTokens: this.config.maxInputTokens,
714
+ targetTokens,
684
715
  previousEstimatedInputTokens,
685
716
  force: options?.force,
686
717
  signal,
@@ -713,6 +744,16 @@ export class ContextWindowManager {
713
744
  return { ...result, estimatedInputTokens };
714
745
  }
715
746
 
747
+ // The deterministic forward-cut already advanced to the tail floor (the
748
+ // most recent complete exchange) and still couldn't fit the budget — the
749
+ // verbatim tail alone is over budget (a tool-heavy in-flight turn). A
750
+ // second full-context pass would re-derive the same floor and free
751
+ // nothing, just paying another full cache write. Stop now and surface
752
+ // `exhausted` so reducers escalate instead of thrashing the compactor.
753
+ if (result.tailFloorReached) {
754
+ return { ...result, estimatedInputTokens, exhausted: true };
755
+ }
756
+
716
757
  // Still above the threshold after one pass — retry on the compacted
717
758
  // history, up to the remaining budget. Each retry runs against the
718
759
  // PREVIOUS attempt's output, building a tighter summary each time.
@@ -737,6 +778,9 @@ export class ContextWindowManager {
737
778
  if (estimatedInputTokens < thresholdTokens) {
738
779
  return { ...result, estimatedInputTokens };
739
780
  }
781
+ // Forward-cut hit the floor and still over budget — same stop condition
782
+ // as the first pass: another retry lands on the same floor.
783
+ if (nextResult.tailFloorReached) break;
740
784
  // Non-productive (compacted but didn't shrink) — stuck compactor.
741
785
  if (estimatedInputTokens >= previousEstimate) break;
742
786
  previousEstimate = estimatedInputTokens;
@@ -44,12 +44,20 @@ import imageRecoveryPostModelCall from "./image-recovery/hooks/post-model-call.j
44
44
  import imageRecoveryStop from "./image-recovery/hooks/stop.js";
45
45
  import { resetImageRecoveryStoreForTests } from "./image-recovery/image-recovery-state-store.js";
46
46
  import imageRecoveryPkg from "./image-recovery/package.json" with { type: "json" };
47
+ import { resetMaxTokensContinueStoreForTests } from "./max-tokens-continue/continue-state-store.js";
48
+ import maxTokensContinuePostModelCall from "./max-tokens-continue/hooks/post-model-call.js";
49
+ import maxTokensContinueStop from "./max-tokens-continue/hooks/stop.js";
50
+ import maxTokensContinuePkg from "./max-tokens-continue/package.json" with { type: "json" };
47
51
  import memoryRetrievalPostCompact from "./memory-retrieval/hooks/post-compact.js";
48
52
  import memoryRetrievalUserPromptSubmit from "./memory-retrieval/hooks/user-prompt-submit.js";
49
53
  import memoryRetrievalPkg from "./memory-retrieval/package.json" with { type: "json" };
50
54
  import memoryV3PostCompact from "./memory-v3-shadow/hooks/post-compact.js";
51
55
  import memoryV3UserPromptSubmit from "./memory-v3-shadow/hooks/user-prompt-submit.js";
52
56
  import memoryV3Pkg from "./memory-v3-shadow/package.json" with { type: "json" };
57
+ import taskProgressNudgePostToolUse, {
58
+ resetTaskProgressNudgeStateForTests,
59
+ } from "./task-progress-nudge/hooks/post-tool-use.js";
60
+ import taskProgressNudgePkg from "./task-progress-nudge/package.json" with { type: "json" };
53
61
  import titleGenerateStop from "./title-generate/hooks/stop.js";
54
62
  import titleGenerateUserPromptSubmit from "./title-generate/hooks/user-prompt-submit.js";
55
63
  import titleGeneratePkg from "./title-generate/package.json" with { type: "json" };
@@ -150,6 +158,24 @@ export const defaultImageRecoveryPlugin: Plugin = {
150
158
  },
151
159
  };
152
160
 
161
+ /**
162
+ * `max-tokens-continue` — a `post-model-call` hook that auto-resumes a
163
+ * user-facing turn the provider truncated at its output token limit, keeping
164
+ * the partial output and re-querying with a continuation nudge so long
165
+ * generations can finish without the user clicking the continuation card.
166
+ * Bounded per run; the `stop` hook clears the budget on a terminal stop.
167
+ */
168
+ export const defaultMaxTokensContinuePlugin: Plugin = {
169
+ manifest: {
170
+ name: maxTokensContinuePkg.name,
171
+ version: maxTokensContinuePkg.version,
172
+ },
173
+ hooks: {
174
+ "post-model-call": maxTokensContinuePostModelCall,
175
+ stop: maxTokensContinueStop,
176
+ },
177
+ };
178
+
153
179
  /**
154
180
  * `memory-v3-shadow` — houses the memory-v3 shadow/live orchestration engine
155
181
  * (`memory-v3-shadow/`) and its injector. The `user-prompt-submit` /
@@ -217,6 +243,22 @@ export const defaultExplorationDriftPlugin: Plugin = {
217
243
  },
218
244
  };
219
245
 
246
+ /**
247
+ * `task-progress-nudge` — a `post-tool-use` hook that nudges the model to show
248
+ * a `task_progress` card once an interactive turn has accumulated several
249
+ * tool-call rounds without one. Best-effort and once-per-turn; capable models
250
+ * that already show a card are never nudged.
251
+ */
252
+ export const defaultTaskProgressNudgePlugin: Plugin = {
253
+ manifest: {
254
+ name: taskProgressNudgePkg.name,
255
+ version: taskProgressNudgePkg.version,
256
+ },
257
+ hooks: {
258
+ "post-tool-use": taskProgressNudgePostToolUse,
259
+ },
260
+ };
261
+
220
262
  /**
221
263
  * `tool-result-truncate` — a `post-tool-use` hook that tail-drops an oversized
222
264
  * tool result down to a character budget derived from the model's context
@@ -243,8 +285,10 @@ function getAllDefaultPlugins(): readonly Plugin[] {
243
285
  defaultMemoryRetrievalPlugin,
244
286
  defaultToolResultTruncatePlugin,
245
287
  defaultEmptyResponsePlugin,
288
+ defaultMaxTokensContinuePlugin,
246
289
  defaultToolErrorPlugin,
247
290
  defaultExplorationDriftPlugin,
291
+ defaultTaskProgressNudgePlugin,
248
292
  defaultHistoryRepairPlugin,
249
293
  defaultImageRecoveryPlugin,
250
294
  defaultCompactionPlugin,
@@ -290,8 +334,10 @@ export function registerDefaultPlugins(): void {
290
334
  export function resetPluginRegistryAndRegisterDefaults(): void {
291
335
  resetPluginRegistryForTests();
292
336
  resetEmptyResponseNudgeStoreForTests();
337
+ resetMaxTokensContinueStoreForTests();
293
338
  resetRepairStateStoreForTests();
294
339
  resetImageRecoveryStoreForTests();
295
340
  resetExplorationDriftStateForTests();
341
+ resetTaskProgressNudgeStateForTests();
296
342
  registerDefaultPlugins();
297
343
  }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Per-conversation auto-continue budget for the default max-tokens-continue
3
+ * module.
4
+ *
5
+ * The `post-model-call` hook resumes a turn the provider truncated at its
6
+ * output token limit by appending a continuation nudge and asking the loop to
7
+ * re-query. The recovery is bounded per run: a long output legitimately spans
8
+ * a few continuations (each model call gets a fresh output budget), but a turn
9
+ * that keeps hitting the limit after several resumes is burning tokens without
10
+ * converging, so the hook lets it end and the continuation card surfaces for
11
+ * the user to drive.
12
+ *
13
+ * The two hooks split this state's lifecycle: `post-model-call` consumes a
14
+ * unit of budget each time it issues a continue, and the sibling `stop` hook
15
+ * clears the counter when the turn terminates. A conversation therefore only
16
+ * holds an entry while a run is in flight, and the next run starts with a
17
+ * full budget.
18
+ */
19
+
20
+ /** Maximum automatic continuations per run before the turn ends terminally. */
21
+ export const MAX_TOKENS_AUTO_CONTINUES = 3;
22
+
23
+ /** Continues consumed this run, keyed by conversation. */
24
+ const continuesUsed = new Map<string, number>();
25
+
26
+ /** Whether the conversation still has auto-continue budget this run. */
27
+ export function hasMaxTokensContinueBudget(conversationId: string): boolean {
28
+ return (continuesUsed.get(conversationId) ?? 0) < MAX_TOKENS_AUTO_CONTINUES;
29
+ }
30
+
31
+ /** Consume one unit of the conversation's auto-continue budget. */
32
+ export function consumeMaxTokensContinueBudget(conversationId: string): void {
33
+ continuesUsed.set(
34
+ conversationId,
35
+ (continuesUsed.get(conversationId) ?? 0) + 1,
36
+ );
37
+ }
38
+
39
+ /**
40
+ * Clear the conversation's budget counter so the next run starts afresh. The
41
+ * sibling `stop` hook calls this when the turn terminates.
42
+ */
43
+ export function clearMaxTokensContinueBudget(conversationId: string): void {
44
+ continuesUsed.delete(conversationId);
45
+ }
46
+
47
+ /**
48
+ * Test-only: drop every conversation's budget state so a suite that drives
49
+ * the hook directly starts each case from an empty store.
50
+ */
51
+ export function resetMaxTokensContinueStoreForTests(): void {
52
+ continuesUsed.clear();
53
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Default `post-model-call` hook: when the provider truncates a user-facing
3
+ * turn at its output token limit, resume it automatically instead of ending
4
+ * the turn.
5
+ *
6
+ * A `max_tokens` stop means the model had more to say — the reply (often a
7
+ * long generation such as a skill building an entire app) simply exceeded the
8
+ * profile's output budget. Without intervention the loop ends the turn and
9
+ * surfaces a continuation card the user must click, and a single click often
10
+ * just truncates again — the turn can never finish. This hook keeps the
11
+ * truncated turn in history, appends a continuation nudge, and sets
12
+ * `decision: "continue"` so the next model call resumes with a fresh output
13
+ * budget.
14
+ *
15
+ * Scope and bounds:
16
+ *
17
+ * - **Main-agent turns only.** Background, subagent, and compaction calls
18
+ * self-manage their budgets; per the post-model-call contract the hook
19
+ * gates on {@link PostModelCallContext.callSite}.
20
+ * - **Bounded per run.** A long output legitimately spans a few
21
+ * continuations, but a turn that keeps truncating after
22
+ * `MAX_TOKENS_AUTO_CONTINUES` resumes is not converging; the hook lets it
23
+ * end so the continuation card surfaces for the user to drive. The sibling
24
+ * `stop` hook (see `./stop.ts`) clears the budget when the turn
25
+ * terminates.
26
+ * - **Truncation-safe content only.** The agent loop strips unsafe blocks
27
+ * (truncated `tool_use` and friends) before running this hook; an entirely
28
+ * empty remainder leaves nothing to resume from, so the hook lets the turn
29
+ * end rather than pushing an empty assistant message the provider would
30
+ * reject.
31
+ */
32
+
33
+ import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
34
+
35
+ import { isMaxTokensStopReason } from "../../../../agent/loop.js";
36
+ import {
37
+ consumeMaxTokensContinueBudget,
38
+ hasMaxTokensContinueBudget,
39
+ } from "../continue-state-store.js";
40
+
41
+ /**
42
+ * Continuation nudge appended after the truncated assistant turn. Shown to
43
+ * the LLM, not the user — edits here affect model behavior, not end-user UX.
44
+ */
45
+ export const MAX_TOKENS_CONTINUE_NUDGE_TEXT =
46
+ "<system_notice>Your previous response was cut off because it reached the maximum output length. Continue exactly where you stopped — do not repeat content you already sent and do not start over.</system_notice>";
47
+
48
+ const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
49
+ if (ctx.error) return;
50
+ if (!isMaxTokensStopReason(ctx.stopReason)) return;
51
+ if (ctx.callSite !== "mainAgent") return;
52
+ // Nothing survived truncation-block stripping — there is no partial output
53
+ // to resume from, so let the turn end terminally.
54
+ if (ctx.content.length === 0) return;
55
+
56
+ if (!hasMaxTokensContinueBudget(ctx.conversationId)) {
57
+ ctx.logger.warn(
58
+ { plugin: "max-tokens-continue", conversationId: ctx.conversationId },
59
+ "Turn kept hitting the output token limit — auto-continue budget exhausted, ending the turn",
60
+ );
61
+ return;
62
+ }
63
+
64
+ consumeMaxTokensContinueBudget(ctx.conversationId);
65
+ ctx.messages.push({
66
+ role: "assistant",
67
+ content: structuredClone(ctx.content),
68
+ });
69
+ ctx.messages.push({
70
+ role: "user",
71
+ content: [{ type: "text", text: MAX_TOKENS_CONTINUE_NUDGE_TEXT }],
72
+ });
73
+ ctx.decision = "continue";
74
+ ctx.logger.warn(
75
+ { plugin: "max-tokens-continue", conversationId: ctx.conversationId },
76
+ "Turn truncated at the output token limit — auto-continuing",
77
+ );
78
+ };
79
+
80
+ export default postModelCall;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Default `stop` hook: clears the per-conversation max-tokens auto-continue
3
+ * budget when a turn terminates.
4
+ *
5
+ * The `post-model-call` hook (see `./post-model-call.ts`) consumes budget each
6
+ * time it resumes a truncated turn. `stop` is the definitive terminal hook —
7
+ * it fires exactly once when the turn is truly ending — so clearing the
8
+ * counter here unconditionally guarantees the next run starts with a full
9
+ * budget, no matter how the turn ended.
10
+ */
11
+
12
+ import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
13
+
14
+ import { clearMaxTokensContinueBudget } from "../continue-state-store.js";
15
+
16
+ const stop: PluginHookFn<StopContext> = async (ctx) => {
17
+ clearMaxTokensContinueBudget(ctx.conversationId);
18
+ };
19
+
20
+ export default stop;
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "default-max-tokens-continue",
3
+ "version": "1.0.0",
4
+ "description": "First-party default plugin that auto-resumes a user-facing turn truncated by the provider's output token limit.",
5
+ "private": true,
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=20.12.0"
10
+ },
11
+ "peerDependencies": {
12
+ "@vellumai/plugin-api": ">=0.8.0"
13
+ }
14
+ }
@@ -0,0 +1,37 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { shouldRunV2Retrieval } from "../user-prompt-submit.js";
4
+
5
+ /**
6
+ * v2 graph-memory retrieval is the deprecated path. It is gated off under
7
+ * `memory-v3-live` — v3 owns the injected-memory layer and runtime assembly
8
+ * strips any v2 `<memory>` block, so running v2's per-turn retrieval (embedding
9
+ * + hybrid search + the `memoryRetrieval` LLM router) would only have its
10
+ * result discarded. It is also skipped for untrusted actors. The
11
+ * conversation/abort-signal presence checks stay inline at the call site (for
12
+ * type narrowing) and are deliberately NOT part of this policy decision.
13
+ */
14
+ describe("shouldRunV2Retrieval", () => {
15
+ test("runs for a trusted actor when memory-v3-live is off (v2/shadow path)", () => {
16
+ expect(
17
+ shouldRunV2Retrieval({ isTrustedActor: true, memoryV3Live: false }),
18
+ ).toBe(true);
19
+ });
20
+
21
+ test("is skipped under memory-v3-live even for a trusted actor", () => {
22
+ // The cutover: v3 owns memory, so v2 retrieval (and its LLM router) must
23
+ // not fire — this is the per-turn cost the gate removes.
24
+ expect(
25
+ shouldRunV2Retrieval({ isTrustedActor: true, memoryV3Live: true }),
26
+ ).toBe(false);
27
+ });
28
+
29
+ test("is skipped for an untrusted actor regardless of the flag", () => {
30
+ expect(
31
+ shouldRunV2Retrieval({ isTrustedActor: false, memoryV3Live: false }),
32
+ ).toBe(false);
33
+ expect(
34
+ shouldRunV2Retrieval({ isTrustedActor: false, memoryV3Live: true }),
35
+ ).toBe(false);
36
+ });
37
+ });
@@ -32,6 +32,7 @@ import type {
32
32
  UserPromptSubmitContext,
33
33
  } from "@vellumai/plugin-api";
34
34
 
35
+ import { isAssistantFeatureFlagEnabled } from "../../../../config/assistant-feature-flags.js";
35
36
  import { getConfig } from "../../../../config/loader.js";
36
37
  import { findConversationOrSubagent } from "../../../../daemon/conversation-registry.js";
37
38
  import {
@@ -48,6 +49,22 @@ import { broadcastMessage } from "../../../../runtime/assistant-event-hub.js";
48
49
  import type { GraphMemoryResult } from "../../../types.js";
49
50
  import { MEMORY_V3_INJECTED_BLOCK_METADATA_KEY } from "../../memory-v3-shadow/ever-injected-store.js";
50
51
 
52
+ /**
53
+ * Whether to run v2 graph-memory retrieval this turn. v2 retrieval is the
54
+ * deprecated path: under `memory-v3-live`, v3 is the injected-memory source and
55
+ * runtime assembly strips any v2 `<memory>` block, so running v2's retrieval
56
+ * (embedding + hybrid search + the `memoryRetrieval` LLM router) only to
57
+ * discard the result is pure per-turn waste. Untrusted actors never run it
58
+ * either. The caller additionally requires the live conversation and its abort
59
+ * signal to be present (kept inline at the call site for type narrowing).
60
+ */
61
+ export function shouldRunV2Retrieval(params: {
62
+ isTrustedActor: boolean;
63
+ memoryV3Live: boolean;
64
+ }): boolean {
65
+ return params.isTrustedActor && !params.memoryV3Live;
66
+ }
67
+
51
68
  /**
52
69
  * Persist and broadcast the retrieval's side effects: the injected block on
53
70
  * the user message's metadata (so it survives reloads), a recall-log row, and
@@ -250,8 +267,19 @@ const userPromptSubmitMemoryRetrieval: PluginHookFn<
250
267
  conversation?.assistantId,
251
268
  );
252
269
 
270
+ // v2 graph retrieval is the deprecated path: `shouldRunV2Retrieval` skips it
271
+ // under memory-v3-live (v3 owns the `<memory>` layer and assembly strips any
272
+ // v2 block) and for untrusted actors. The `conversation && abortSignal`
273
+ // presence checks stay inline so the block below narrows. NOTE: this removes
274
+ // the v2 fallback — under v3-live, a v3 empty/failed selection yields no NEW
275
+ // injected memory that turn (prior turns' frozen v3 cards still ride history).
276
+ const memoryV3Live = isAssistantFeatureFlagEnabled("memory-v3-live", config);
253
277
  let v2BlockPersisted = false;
254
- if (conversation && isTrustedActor && abortSignal) {
278
+ if (
279
+ shouldRunV2Retrieval({ isTrustedActor, memoryV3Live }) &&
280
+ conversation &&
281
+ abortSignal
282
+ ) {
255
283
  // Retrieval progress (`memory_status`) and the `memory_recalled` summary
256
284
  // publish to the shared `broadcastMessage` hub — the sink every turn
257
285
  // publisher converges to — rather than a threaded event callback. This
@@ -57,6 +57,7 @@ import {
57
57
  } from "../../../../memory/memory-marker.js";
58
58
  import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
59
59
  import { migrateAddMemoryV3EverInjected } from "../../../../memory/migrations/277-add-memory-v3-ever-injected.js";
60
+ import { migrateMemoryV3SelectionsMessageIdAndSections } from "../../../../memory/migrations/283-memory-v3-selections-message-id-and-sections.js";
60
61
  import * as schema from "../../../../memory/schema.js";
61
62
  import type { PageIndexEntry } from "../../../../memory/v2/page-index.js";
62
63
  import type {
@@ -131,6 +132,7 @@ function makeDb() {
131
132
  const db = drizzle(testSqlite, { schema });
132
133
  migrateAddMemoryV3EverInjected(db);
133
134
  migrateAddMemoryV3Selections(db);
135
+ migrateMemoryV3SelectionsMessageIdAndSections(db);
134
136
  // Minimal `messages` shape — metadata persistence, the prune valve's
135
137
  // v3-ownership scan, and the restart rehydration read only these columns.
136
138
  testSqlite.run(/*sql*/ `
@@ -631,7 +633,10 @@ async function runTurn(
631
633
  }
632
634
 
633
635
  // Spotlight: scoped strip of the stale block (real assembly helper), then
634
- // append the fresh one at the current-message tail.
636
+ // re-attach the fresh one. Real assembly splices it after the memory cards
637
+ // (after-memory-prefix); this sim appends to the tail because only the
638
+ // block's presence, content, placement value, and strip-and-replace are
639
+ // asserted here — not its exact position within the message.
635
640
  const spotlight = await memoryV3SpotlightInjector.produce(ctx);
636
641
  const stripped = stripSpotlightInjections(history);
637
642
  history.splice(0, history.length, ...stripped);
@@ -976,13 +981,13 @@ describe("memory-v3 carry integration — cache contract", () => {
976
981
  });
977
982
 
978
983
  describe("memory-v3 carry integration — spotlight contract", () => {
979
- test("spotlight is present every turn, at the user tail, bounded by n × (window + 1)", () => {
984
+ test("spotlight is present every turn, after the memory cards, bounded by n × (window + 1)", () => {
980
985
  for (const record of records) {
981
986
  expect(record.spotlightText.startsWith("<memory_spotlight>\n")).toBe(
982
987
  true,
983
988
  );
984
989
  expect(record.spotlightText.endsWith("\n</memory_spotlight>")).toBe(true);
985
- expect(record.spotlightPlacement).toBe("append-user-tail");
990
+ expect(record.spotlightPlacement).toBe("after-memory-prefix");
986
991
  expect(record.spotlightEntries).toBeGreaterThanOrEqual(1);
987
992
  expect(record.spotlightEntries).toBeLessThanOrEqual(
988
993
  SPOTLIGHT_N * (SPOTLIGHT_WINDOW_TURNS + 1),
@@ -33,6 +33,7 @@ import { drizzle } from "drizzle-orm/bun-sqlite";
33
33
  import { unwrapMemoryBlock } from "../../../../memory/memory-marker.js";
34
34
  import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
35
35
  import { migrateAddMemoryV3EverInjected } from "../../../../memory/migrations/277-add-memory-v3-ever-injected.js";
36
+ import { migrateMemoryV3SelectionsMessageIdAndSections } from "../../../../memory/migrations/283-memory-v3-selections-message-id-and-sections.js";
36
37
  import * as schema from "../../../../memory/schema.js";
37
38
  import type { InjectionBlock } from "../../../types.js";
38
39
  import type { OrchestrateResult } from "../orchestrate.js";
@@ -93,6 +94,7 @@ function makeDb() {
93
94
  migrateAddMemoryV3EverInjected(db);
94
95
  // The prune valve's recency ranking reads `memory_v3_selections`.
95
96
  migrateAddMemoryV3Selections(db);
97
+ migrateMemoryV3SelectionsMessageIdAndSections(db);
96
98
  // The prune valve plans only against slugs whose card sections are
97
99
  // locatable in persisted `memoryV3InjectedBlock` rows
98
100
  // (`collectPersistedV3Cards`) — minimal `messages` shape it reads.
@@ -573,13 +575,13 @@ describe("memoryV3SpotlightInjector — ephemeral section spotlight", () => {
573
575
  const sectionB = section("page-b", "Beta", "beta section text");
574
576
  const sectionC = section("page-c", "Gamma", "gamma section text");
575
577
 
576
- test("renders selected finder hits' matched sections at the user tail", async () => {
578
+ test("renders selected finder hits' matched sections right after the memory cards", async () => {
577
579
  liveEnabled = true;
578
580
  turnResults.set(0, result(["page-a", "page-b"], [["page-a", sectionA]]));
579
581
 
580
582
  const block = await produceSpotlight("conv-1", 0);
581
583
  expect(block).not.toBeNull();
582
- expect(block!.placement).toBe("append-user-tail");
584
+ expect(block!.placement).toBe("after-memory-prefix");
583
585
  expect(block!.text.startsWith("<memory_spotlight>\n")).toBe(true);
584
586
  expect(block!.text.endsWith("\n</memory_spotlight>")).toBe(true);
585
587
  expect(block!.text).toContain(
@@ -13,10 +13,12 @@
13
13
  * by `pinned_ids`; out-of-range IDs dropped; selections deduped by slug
14
14
  * (a page can appear as both a card and a finder line; pinned flags OR).
15
15
  * - Omitted `ids` → keep ALL candidates (recall-safe, slug-deduped).
16
- * - Explicit `ids: []` → keep none (deliberate abstention).
16
+ * - Explicit `ids: []` → keep none (deliberate abstention) — a normal result.
17
+ * - Empty candidate pool → keep none (nothing to select).
17
18
  * - Finder snippets are whitespace-collapsed and truncated (~300 chars).
18
- * - No provider / missing tool_use / schema mismatch / throw → keep none
19
- * (degrade to deterministic lanes), the last three after a re-prompt retry.
19
+ * - No provider / missing tool_use / schema mismatch / provider throw → throw
20
+ * MemoryV3RetrievalUnavailableError (an INFRA failure, deliberately DISTINCT
21
+ * from a deliberate empty selection), the last three after a re-prompt retry.
20
22
  * - One forced-tool `select_pages` call on the v3 L2 call site with
21
23
  * `disableTurnStartCache` (the tail varies per turn — the provider's
22
24
  * auto-anchor would never hit).
@@ -61,7 +63,8 @@ mock.module("../../../../util/logger.js", () => ({
61
63
  }),
62
64
  }));
63
65
 
64
- const { selectPool } = await import("../pool-select.js");
66
+ const { selectPool, MemoryV3RetrievalUnavailableError } =
67
+ await import("../pool-select.js");
65
68
  type SelectorPool = Parameters<typeof selectPool>[0];
66
69
 
67
70
  // ---------------------------------------------------------------------------
@@ -226,35 +229,42 @@ describe("selectPool — id mapping", () => {
226
229
  });
227
230
 
228
231
  // ---------------------------------------------------------------------------
229
- // selectPool — recall-safe fallbacks.
232
+ // selectPool — infrastructure failures THROW (no silent degradation). A
233
+ // deliberate empty selection and an empty pool (covered above) still return
234
+ // normally; only a genuine infra failure throws so the LIVE injector can
235
+ // hard-fail the turn instead of shipping it with no memory.
230
236
  // ---------------------------------------------------------------------------
231
237
 
232
- describe("selectPool — degradation on failure", () => {
233
- test("no provider → no pages, without calling the provider", async () => {
238
+ describe("selectPool — infrastructure failures throw", () => {
239
+ test("no provider → throws without calling the provider", async () => {
234
240
  providerStub = null;
235
- const result = await selectPool(makePool(), makeTurn("x"));
236
- expect(result).toEqual([]);
241
+ await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
242
+ MemoryV3RetrievalUnavailableError,
243
+ );
237
244
  expect(providerCalls).toHaveLength(0);
238
245
  });
239
246
 
240
- test("missing tool_use → no pages after retrying", async () => {
247
+ test("missing tool_use → throws after retrying", async () => {
241
248
  providerStub = makeProvider(noToolResponse());
242
- const result = await selectPool(makePool(), makeTurn("x"));
243
- expect(result).toEqual([]);
249
+ await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
250
+ MemoryV3RetrievalUnavailableError,
251
+ );
244
252
  expect(providerCalls).toHaveLength(3);
245
253
  });
246
254
 
247
- test("schema mismatch → no pages after retrying", async () => {
255
+ test("schema mismatch → throws after retrying", async () => {
248
256
  providerStub = makeProvider(toolUseResponse({ ids: "not-an-array" }));
249
- const result = await selectPool(makePool(), makeTurn("x"));
250
- expect(result).toEqual([]);
257
+ await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
258
+ MemoryV3RetrievalUnavailableError,
259
+ );
251
260
  expect(providerCalls).toHaveLength(3);
252
261
  });
253
262
 
254
- test("provider throw → no pages after retrying", async () => {
263
+ test("provider throw → throws after retrying", async () => {
255
264
  providerStub = makeThrowingProvider();
256
- const result = await selectPool(makePool(), makeTurn("x"));
257
- expect(result).toEqual([]);
265
+ await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
266
+ MemoryV3RetrievalUnavailableError,
267
+ );
258
268
  expect(providerCalls).toHaveLength(3);
259
269
  });
260
270