@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
@@ -24,11 +24,20 @@
24
24
  // conversation might be the active one. We're conservative and only
25
25
  // sweep when no job exists at all, since the worst-case false-positive
26
26
  // is leaving a few extra orphans for the next sweep to catch.)
27
- // - AND the row is NOT the most-recent retrospective for its source
28
- // conversation. The next retrospective run reads the most-recent prior
29
- // retro via `findMostRecentRetrospectiveFor` to seed its
30
- // `<already_remembered>` dedup block; sweeping it would force the
31
- // next run to re-save facts the prior pass already captured.
27
+ // - AND the row is NOT the preserved dedup baseline for its source
28
+ // conversation. The primary dedup baseline is the persisted
29
+ // `remembered_log` on `memory_retrospective_state`, but state rows that
30
+ // predate the log column fall back to scanning the most-recent prior
31
+ // retro (via `findMostRecentRetrospectiveFor`) to seed their
32
+ // `<already_remembered>` dedup block; sweeping it would force such a
33
+ // run to re-save facts the prior pass already captured. The baseline is
34
+ // the most recent row that actually produced output (see
35
+ // `selectPreservedBaseline`) — a crash orphan with no post-fork
36
+ // assistant message would seed an EMPTY dedup block, so when an older
37
+ // row with output exists we preserve that one instead.
38
+ //
39
+ // The sweep is skipped entirely when `memory.retrospective.keepSupersededRuns`
40
+ // is true — see the comment inside the function.
32
41
 
33
42
  import {
34
43
  and,
@@ -42,16 +51,27 @@ import {
42
51
  sql,
43
52
  } from "drizzle-orm";
44
53
 
54
+ import { getConfig } from "../config/loader.js";
45
55
  import { getLogger } from "../util/logger.js";
46
56
  import { deleteConversation } from "./conversation-crud.js";
47
57
  import { getDb } from "./db-connection.js";
48
58
  import { MEMORY_RETROSPECTIVE_SOURCES } from "./memory-retrospective-constants.js";
59
+ import { loadRetrospectiveRunMessages } from "./memory-retrospective-fork-boundary.js";
49
60
  import { conversations, memoryJobs } from "./schema.js";
50
61
 
51
62
  const log = getLogger("memory-retrospective-startup-cleanup");
52
63
 
53
64
  const ORPHAN_AGE_MS = 60 * 60 * 1000;
54
65
 
66
+ /**
67
+ * How many of the newest retrospective rows per source the baseline selector
68
+ * will load messages for when looking for one with output. Bounds the
69
+ * per-startup query volume — after GC (`deleteSupersededPriorRetrospective`)
70
+ * a source normally has 1–2 retro rows, so this only matters for pathological
71
+ * crash pileups, where the older rows are orphans too.
72
+ */
73
+ const MAX_BASELINE_CANDIDATES_PER_SOURCE = 3;
74
+
55
75
  export interface CleanupResult {
56
76
  swept: number;
57
77
  }
@@ -65,6 +85,16 @@ export interface CleanupResult {
65
85
  export function sweepOrphanMemoryRetrospectiveConversations(
66
86
  now: number = Date.now(),
67
87
  ): CleanupResult {
88
+ // When the operator opted into retaining superseded retrospective runs
89
+ // (`memory.retrospective.keepSupersededRuns`), skip the sweep entirely —
90
+ // retained runs must survive restarts, and the sweep cannot distinguish a
91
+ // retained superseded run from a crash orphan. Tradeoff: under this opt-in,
92
+ // genuine crash orphans persist too. That's acceptable — the operator asked
93
+ // for full run history, and an orphan is just one more retained conversation.
94
+ if (getConfig().memory?.retrospective?.keepSupersededRuns === true) {
95
+ return { swept: 0 };
96
+ }
97
+
68
98
  const cutoff = now - ORPHAN_AGE_MS;
69
99
  const db = getDb();
70
100
 
@@ -90,13 +120,14 @@ export function sweepOrphanMemoryRetrospectiveConversations(
90
120
  .map((row) => row.conversationId)
91
121
  .filter((id): id is string => typeof id === "string" && id.length > 0);
92
122
 
93
- // Compute the most-recent retro per source so we can preserve it.
94
- // `findMostRecentRetrospectiveFor` (called by the next retrospective run)
95
- // pulls dedup context from this row; sweeping it would re-introduce the
96
- // unbounded retrospective growth this cleanup exists to prevent.
123
+ // Compute the preserved dedup baseline per source. Runs whose state row
124
+ // predates the persisted `remembered_log` pull dedup context by scanning
125
+ // the most-recent prior retro (via `findMostRecentRetrospectiveFor`);
126
+ // sweeping it would leave those runs with no baseline at all.
97
127
  const allRetros = db
98
128
  .select({
99
129
  id: conversations.id,
130
+ source: conversations.source,
100
131
  forkParentConversationId: conversations.forkParentConversationId,
101
132
  createdAt: conversations.createdAt,
102
133
  })
@@ -108,21 +139,21 @@ export function sweepOrphanMemoryRetrospectiveConversations(
108
139
  ),
109
140
  )
110
141
  .all();
111
- const mostRecentPerSource = new Map<
112
- string,
113
- { id: string; createdAt: number }
114
- >();
142
+ const retrosPerSource = new Map<string, RetroRow[]>();
115
143
  for (const row of allRetros) {
116
144
  const parent = row.forkParentConversationId;
117
145
  if (parent === null) continue;
118
- const cur = mostRecentPerSource.get(parent);
119
- if (!cur || row.createdAt > cur.createdAt) {
120
- mostRecentPerSource.set(parent, { id: row.id, createdAt: row.createdAt });
146
+ const rows = retrosPerSource.get(parent);
147
+ if (rows) {
148
+ rows.push(row);
149
+ } else {
150
+ retrosPerSource.set(parent, [row]);
121
151
  }
122
152
  }
123
- const preservedIds = new Set(
124
- Array.from(mostRecentPerSource.values(), (v) => v.id),
125
- );
153
+ const preservedIds = new Set<string>();
154
+ for (const rows of retrosPerSource.values()) {
155
+ preservedIds.add(selectPreservedBaseline(rows));
156
+ }
126
157
 
127
158
  const orphans = db
128
159
  .select({ id: conversations.id })
@@ -173,3 +204,50 @@ export function sweepOrphanMemoryRetrospectiveConversations(
173
204
  }
174
205
  return { swept };
175
206
  }
207
+
208
+ interface RetroRow {
209
+ id: string;
210
+ source: string | null;
211
+ createdAt: number;
212
+ }
213
+
214
+ /**
215
+ * Pick which retrospective row to preserve as the source's dedup baseline:
216
+ * the most recent row that actually produced output (a crash orphan with no
217
+ * post-fork assistant message would seed the next run with an EMPTY
218
+ * `<already_remembered>` block and cause re-saves). Falls back to the plain
219
+ * most-recent row when no candidate qualifies — preserves the previous
220
+ * behavior when every row is an orphan, and an orphan baseline at least
221
+ * keeps `findMostRecentRetrospectiveFor` stable until a successful run
222
+ * supersedes it.
223
+ *
224
+ * PR-E's persisted `remembered_log` reduces the impact of preserving a bad
225
+ * baseline (the next run can fall back to the persisted log), but the log
226
+ * fallback path still reads the preserved conversation, so the sweep should
227
+ * keep preferring a row that's actually useful.
228
+ *
229
+ * Only loads messages for the newest `MAX_BASELINE_CANDIDATES_PER_SOURCE`
230
+ * rows — never for every retro row.
231
+ */
232
+ function selectPreservedBaseline(rows: RetroRow[]): string {
233
+ const sorted = [...rows].sort((a, b) => b.createdAt - a.createdAt);
234
+ const withOutput = sorted
235
+ .slice(0, MAX_BASELINE_CANDIDATES_PER_SOURCE)
236
+ .find(retrospectiveHasOutput);
237
+ return (withOutput ?? sorted[0]!).id;
238
+ }
239
+
240
+ /**
241
+ * Whether the retrospective row produced any assistant output of its own.
242
+ * `loadRetrospectiveRunMessages` scopes fork-kind rows to the post-fork tail
243
+ * (the copied source prefix contains the source's own assistant turns) and
244
+ * returns `null` for rows whose output cannot be determined (load failure or
245
+ * no detectable fork boundary) — those rows contribute nothing to dedup
246
+ * (`collectPriorRetrospectiveRemembers` treats them as empty), so they don't
247
+ * qualify. Legacy-kind rows start empty, so any assistant message counts.
248
+ */
249
+ function retrospectiveHasOutput(row: RetroRow): boolean {
250
+ const runMessages = loadRetrospectiveRunMessages(row.id, row.source);
251
+ if (runMessages == null) return false;
252
+ return runMessages.some((m) => m.role === "assistant");
253
+ }
@@ -10,6 +10,13 @@
10
10
  // per-conversation cooldown gate in the trigger-check helper so failing
11
11
  // jobs can't loop in tight retries across trigger types.
12
12
  //
13
+ // A third column rides along with the success-path pointer write:
14
+ // - `rememberedLog` (JSON array of strings) — the cumulative `remember`
15
+ // contents saved across retrospective passes. The job's
16
+ // `<already_remembered>` dedup block reads from this log so dedup context
17
+ // survives GC of superseded retrospective conversations and spans more
18
+ // than the last pass. Capped — see `appendToRememberedLog`.
19
+ //
13
20
  // The schema enforces the foreign key with ON DELETE CASCADE, so deleting a
14
21
  // conversation collects its state row automatically.
15
22
 
@@ -22,6 +29,59 @@ export interface MemoryRetrospectiveState {
22
29
  conversationId: string;
23
30
  lastProcessedMessageId: string;
24
31
  lastRunAt: number;
32
+ /**
33
+ * Cumulative `remember` contents from prior retrospective passes, oldest
34
+ * first. Empty for rows that predate the `remembered_log` column or have
35
+ * no saves yet — callers fall back to scanning the prior retrospective
36
+ * conversation in that case.
37
+ */
38
+ rememberedLog: string[];
39
+ }
40
+
41
+ /**
42
+ * Cap for the persisted remembered log: keep the most recent entries up to
43
+ * 100 entries or 8 KB serialized, whichever binds first. The log is injected
44
+ * verbatim into every retrospective prompt, so the byte cap bounds prompt
45
+ * growth; the entry cap bounds list length for pathological tiny entries.
46
+ */
47
+ export const REMEMBERED_LOG_MAX_ENTRIES = 100;
48
+ export const REMEMBERED_LOG_MAX_BYTES = 8 * 1024;
49
+
50
+ /**
51
+ * Append new entries to the remembered log and apply the cap, dropping the
52
+ * OLDEST entries first. A single entry larger than the byte cap is dropped
53
+ * entirely rather than truncated mid-string.
54
+ */
55
+ export function appendToRememberedLog(
56
+ existing: string[],
57
+ newEntries: string[],
58
+ ): string[] {
59
+ const combined = [...existing, ...newEntries];
60
+ let result = combined.slice(
61
+ Math.max(0, combined.length - REMEMBERED_LOG_MAX_ENTRIES),
62
+ );
63
+ while (
64
+ result.length > 0 &&
65
+ Buffer.byteLength(JSON.stringify(result), "utf8") > REMEMBERED_LOG_MAX_BYTES
66
+ ) {
67
+ result = result.slice(1);
68
+ }
69
+ return result;
70
+ }
71
+
72
+ function parseRememberedLog(raw: string | null): string[] {
73
+ if (!raw) return [];
74
+ try {
75
+ const parsed: unknown = JSON.parse(raw);
76
+ if (!Array.isArray(parsed)) return [];
77
+ return parsed.filter((entry): entry is string => typeof entry === "string");
78
+ } catch {
79
+ return [];
80
+ }
81
+ }
82
+
83
+ function serializeRememberedLog(log: string[]): string | null {
84
+ return log.length === 0 ? null : JSON.stringify(log);
25
85
  }
26
86
 
27
87
  /**
@@ -40,25 +100,43 @@ export function getRetrospectiveState(
40
100
  conversationId: row.conversationId,
41
101
  lastProcessedMessageId: row.lastProcessedMessageId,
42
102
  lastRunAt: row.lastRunAt,
103
+ rememberedLog: parseRememberedLog(row.rememberedLog),
43
104
  };
44
105
  }
45
106
 
46
107
  /**
47
108
  * Upsert both pointers atomically. Used on successful retrospective runs.
109
+ *
110
+ * `rememberedLog`, when provided, is written in the same statement so the
111
+ * cumulative dedup log can never drift from the pointer it was computed
112
+ * against. When omitted, the stored log is left untouched (and seeded NULL on
113
+ * first insert).
48
114
  */
49
- export function upsertRetrospectiveState(args: MemoryRetrospectiveState): void {
115
+ export function upsertRetrospectiveState(
116
+ args: Omit<MemoryRetrospectiveState, "rememberedLog"> & {
117
+ rememberedLog?: string[];
118
+ },
119
+ ): void {
50
120
  const db = getDb();
121
+ const serializedLog =
122
+ args.rememberedLog === undefined
123
+ ? undefined
124
+ : serializeRememberedLog(args.rememberedLog);
51
125
  db.insert(memoryRetrospectiveState)
52
126
  .values({
53
127
  conversationId: args.conversationId,
54
128
  lastProcessedMessageId: args.lastProcessedMessageId,
55
129
  lastRunAt: args.lastRunAt,
130
+ rememberedLog: serializedLog ?? null,
56
131
  })
57
132
  .onConflictDoUpdate({
58
133
  target: memoryRetrospectiveState.conversationId,
59
134
  set: {
60
135
  lastProcessedMessageId: args.lastProcessedMessageId,
61
136
  lastRunAt: args.lastRunAt,
137
+ ...(serializedLog !== undefined
138
+ ? { rememberedLog: serializedLog }
139
+ : {}),
62
140
  },
63
141
  })
64
142
  .run();
@@ -83,6 +161,8 @@ export function upsertRetrospectiveState(args: MemoryRetrospectiveState): void {
83
161
  * wait for new post-fork messages before its first retro fires.
84
162
  *
85
163
  * `lastRunAt` is copied verbatim — the cooldown gate inherits from source.
164
+ * `rememberedLog` is copied verbatim — the parent's saves remain the child's
165
+ * dedup baseline.
86
166
  */
87
167
  export function forkRetrospectiveState(args: {
88
168
  database: DrizzleDb;
@@ -125,12 +205,14 @@ export function forkRetrospectiveState(args: {
125
205
  conversationId: forkedConversationId,
126
206
  lastProcessedMessageId: forkedPointer,
127
207
  lastRunAt: sourceRow.lastRunAt,
208
+ rememberedLog: sourceRow.rememberedLog,
128
209
  })
129
210
  .onConflictDoUpdate({
130
211
  target: memoryRetrospectiveState.conversationId,
131
212
  set: {
132
213
  lastProcessedMessageId: forkedPointer,
133
214
  lastRunAt: sourceRow.lastRunAt,
215
+ rememberedLog: sourceRow.rememberedLog,
134
216
  },
135
217
  })
136
218
  .run();
@@ -141,7 +223,8 @@ export function forkRetrospectiveState(args: {
141
223
  * applies to subsequent trigger-driven enqueues. If no row exists yet (first
142
224
  * attempt failed), seed `lastProcessedMessageId` to the empty string — a
143
225
  * sentinel meaning "nothing successfully processed yet" that subsequent
144
- * `getMessagesSince(...)` queries treat the same as a missing row.
226
+ * `getMessagesSince(...)` queries treat the same as a missing row. An
227
+ * existing row's `rememberedLog` is left untouched.
145
228
  */
146
229
  export function bumpRetrospectiveLastRunAt(
147
230
  conversationId: string,
@@ -0,0 +1,40 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+ import { withCrashRecovery } from "./validate-migration-state.js";
4
+
5
+ const CHECKPOINT_KEY = "migration_memory_retrospective_remembered_log_v1";
6
+
7
+ const TABLE = "memory_retrospective_state";
8
+ const COLUMN = "remembered_log";
9
+
10
+ /**
11
+ * Add a nullable `remembered_log TEXT` column to `memory_retrospective_state`.
12
+ *
13
+ * Stores a JSON array of strings — the cumulative `remember` contents saved
14
+ * by retrospective passes over the conversation (capped; see
15
+ * `memory-retrospective-state.ts`). The retrospective job's
16
+ * `<already_remembered>` dedup block reads from this log so it survives GC of
17
+ * superseded retrospective conversations and spans more than the last pass.
18
+ *
19
+ * `NULL` for rows persisted before this migration ran — the job falls back
20
+ * to scanning the prior retrospective conversation for those.
21
+ *
22
+ * Idempotent — the PRAGMA guard makes re-running a no-op once the column
23
+ * exists.
24
+ */
25
+ export function migrateMemoryRetrospectiveRememberedLog(
26
+ database: DrizzleDb,
27
+ ): void {
28
+ withCrashRecovery(database, CHECKPOINT_KEY, () => {
29
+ const raw = getSqliteFrom(database);
30
+
31
+ const columns = raw.query(`PRAGMA table_info(${TABLE})`).all() as Array<{
32
+ name: string;
33
+ }>;
34
+ const columnNames = new Set(columns.map((c) => c.name));
35
+
36
+ if (!columnNames.has(COLUMN)) {
37
+ raw.exec(`ALTER TABLE ${TABLE} ADD COLUMN ${COLUMN} TEXT`);
38
+ }
39
+ });
40
+ }
@@ -0,0 +1,77 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { describe, expect, test } from "bun:test";
3
+
4
+ import { drizzle } from "drizzle-orm/bun-sqlite";
5
+
6
+ import * as schema from "../schema.js";
7
+ import { migrateScheduleInferenceProfile } from "./282-schedule-inference-profile.js";
8
+
9
+ function createTestDb() {
10
+ const sqlite = new Database(":memory:");
11
+ // Pre-282 shape: no inference_profile column (trimmed to the columns the
12
+ // migration and assertions touch).
13
+ sqlite.exec(/*sql*/ `
14
+ CREATE TABLE cron_jobs (
15
+ id TEXT PRIMARY KEY,
16
+ name TEXT NOT NULL,
17
+ message TEXT NOT NULL,
18
+ next_run_at INTEGER NOT NULL,
19
+ created_by TEXT NOT NULL,
20
+ created_at INTEGER NOT NULL,
21
+ updated_at INTEGER NOT NULL
22
+ )
23
+ `);
24
+ return { sqlite, db: drizzle(sqlite, { schema }) };
25
+ }
26
+
27
+ function columnNames(sqlite: Database): string[] {
28
+ return (
29
+ sqlite.query("PRAGMA table_info(cron_jobs)").all() as Array<{
30
+ name: string;
31
+ }>
32
+ ).map((c) => c.name);
33
+ }
34
+
35
+ describe("migration 282: cron_jobs inference_profile", () => {
36
+ test("adds a nullable inference_profile column", () => {
37
+ const { sqlite, db } = createTestDb();
38
+ expect(columnNames(sqlite)).not.toContain("inference_profile");
39
+
40
+ migrateScheduleInferenceProfile(db);
41
+
42
+ const column = (
43
+ sqlite.query("PRAGMA table_info(cron_jobs)").all() as Array<{
44
+ name: string;
45
+ notnull: number;
46
+ }>
47
+ ).find((c) => c.name === "inference_profile");
48
+ expect(column).toBeDefined();
49
+ expect(column?.notnull).toBe(0);
50
+ });
51
+
52
+ test("existing rows read back with inference_profile null", () => {
53
+ const { sqlite, db } = createTestDb();
54
+ sqlite.exec(/*sql*/ `
55
+ INSERT INTO cron_jobs (id, name, message, next_run_at, created_by, created_at, updated_at)
56
+ VALUES ('job-1', 'Daily digest', 'write the digest', 1000, 'agent', 1000, 1000)
57
+ `);
58
+
59
+ migrateScheduleInferenceProfile(db);
60
+
61
+ const row = sqlite
62
+ .query("SELECT inference_profile FROM cron_jobs WHERE id = 'job-1'")
63
+ .get() as { inference_profile: string | null };
64
+ expect(row.inference_profile).toBeNull();
65
+ });
66
+
67
+ test("is idempotent — re-run is a no-op", () => {
68
+ const { sqlite, db } = createTestDb();
69
+
70
+ migrateScheduleInferenceProfile(db);
71
+ expect(() => migrateScheduleInferenceProfile(db)).not.toThrow();
72
+
73
+ expect(
74
+ columnNames(sqlite).filter((name) => name === "inference_profile"),
75
+ ).toHaveLength(1);
76
+ });
77
+ });
@@ -0,0 +1,26 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ /**
5
+ * Add a nullable `inference_profile TEXT` column to `cron_jobs`.
6
+ *
7
+ * Stores the `llm.profiles` key a schedule's LLM-executed runs should use as
8
+ * their per-turn inference-profile override. `NULL` (all pre-existing rows)
9
+ * means no override — runs resolve through the default `mainAgent` call-site
10
+ * configuration, exactly as before this migration.
11
+ *
12
+ * Idempotent — the PRAGMA guard makes re-running a no-op once the column
13
+ * exists.
14
+ */
15
+ export function migrateScheduleInferenceProfile(database: DrizzleDb): void {
16
+ const raw = getSqliteFrom(database);
17
+
18
+ const columns = raw.query(`PRAGMA table_info(cron_jobs)`).all() as Array<{
19
+ name: string;
20
+ }>;
21
+ const columnNames = new Set(columns.map((c) => c.name));
22
+
23
+ if (!columnNames.has("inference_profile")) {
24
+ raw.exec(`ALTER TABLE cron_jobs ADD COLUMN inference_profile TEXT`);
25
+ }
26
+ }
@@ -0,0 +1,102 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { describe, expect, test } from "bun:test";
3
+
4
+ import { drizzle } from "drizzle-orm/bun-sqlite";
5
+
6
+ import * as schema from "../schema.js";
7
+ import { migrateMemoryV3SelectionsMessageIdAndSections } from "./283-memory-v3-selections-message-id-and-sections.js";
8
+
9
+ function createTestDb() {
10
+ const sqlite = new Database(":memory:");
11
+ // Pre-283 shape: slug/source/pinned only, no message_id or section columns.
12
+ sqlite.exec(/*sql*/ `
13
+ CREATE TABLE memory_v3_selections (
14
+ conversation_id TEXT NOT NULL,
15
+ turn INTEGER NOT NULL,
16
+ slug TEXT NOT NULL,
17
+ source TEXT NOT NULL,
18
+ pinned INTEGER NOT NULL DEFAULT 0,
19
+ created_at INTEGER NOT NULL,
20
+ PRIMARY KEY (conversation_id, turn, slug)
21
+ )
22
+ `);
23
+ return { sqlite, db: drizzle(sqlite, { schema }) };
24
+ }
25
+
26
+ function columnNames(sqlite: Database): string[] {
27
+ return (
28
+ sqlite.query("PRAGMA table_info(memory_v3_selections)").all() as Array<{
29
+ name: string;
30
+ }>
31
+ ).map((c) => c.name);
32
+ }
33
+
34
+ function indexNames(sqlite: Database): string[] {
35
+ return (
36
+ sqlite.query("PRAGMA index_list(memory_v3_selections)").all() as Array<{
37
+ name: string;
38
+ }>
39
+ ).map((i) => i.name);
40
+ }
41
+
42
+ describe("migration 283: memory_v3_selections message_id + section columns", () => {
43
+ test("adds nullable message_id, section_ordinal, section_title + message_id index", () => {
44
+ const { sqlite, db } = createTestDb();
45
+ expect(columnNames(sqlite)).not.toContain("message_id");
46
+
47
+ migrateMemoryV3SelectionsMessageIdAndSections(db);
48
+
49
+ const cols = sqlite
50
+ .query("PRAGMA table_info(memory_v3_selections)")
51
+ .all() as Array<{ name: string; notnull: number }>;
52
+ for (const name of ["message_id", "section_ordinal", "section_title"]) {
53
+ const col = cols.find((c) => c.name === name);
54
+ expect(col).toBeDefined();
55
+ expect(col?.notnull).toBe(0); // nullable
56
+ }
57
+ expect(indexNames(sqlite)).toContain("idx_memory_v3_selections_message");
58
+ });
59
+
60
+ test("existing rows read back with the new columns null", () => {
61
+ const { sqlite, db } = createTestDb();
62
+ sqlite.exec(/*sql*/ `
63
+ INSERT INTO memory_v3_selections
64
+ (conversation_id, turn, slug, source, pinned, created_at)
65
+ VALUES ('conv-1', 0, 'a/page', 'needle', 0, 1000)
66
+ `);
67
+
68
+ migrateMemoryV3SelectionsMessageIdAndSections(db);
69
+
70
+ const row = sqlite
71
+ .query(
72
+ `SELECT message_id, section_ordinal, section_title
73
+ FROM memory_v3_selections WHERE conversation_id = 'conv-1'`,
74
+ )
75
+ .get() as {
76
+ message_id: string | null;
77
+ section_ordinal: number | null;
78
+ section_title: string | null;
79
+ };
80
+ expect(row.message_id).toBeNull();
81
+ expect(row.section_ordinal).toBeNull();
82
+ expect(row.section_title).toBeNull();
83
+ });
84
+
85
+ test("is idempotent — re-run is a no-op", () => {
86
+ const { sqlite, db } = createTestDb();
87
+
88
+ migrateMemoryV3SelectionsMessageIdAndSections(db);
89
+ expect(() =>
90
+ migrateMemoryV3SelectionsMessageIdAndSections(db),
91
+ ).not.toThrow();
92
+
93
+ expect(columnNames(sqlite).filter((n) => n === "message_id")).toHaveLength(
94
+ 1,
95
+ );
96
+ expect(
97
+ indexNames(sqlite).filter(
98
+ (n) => n === "idx_memory_v3_selections_message",
99
+ ),
100
+ ).toHaveLength(1);
101
+ });
102
+ });
@@ -0,0 +1,53 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ /**
5
+ * Add three nullable columns to `memory_v3_selections` for the inspector's
6
+ * Memory V3 panel:
7
+ *
8
+ * - `message_id TEXT` — the turn's assistant message id, backfilled at turn
9
+ * end (mirrors the v2 activation log). The inspector looks selections up by
10
+ * the inspected message's turn message ids, which is robust against the
11
+ * drift between v2's tracker turn and v3's orchestrator `turnCount` that
12
+ * previously left the panel blank. NULL for pre-existing rows and until the
13
+ * turn-end backfill stamps it.
14
+ * - `section_ordinal INTEGER` / `section_title TEXT` — the matched section a
15
+ * finder-lane selection surfaced (from `OrchestrateResult.matchedSections`).
16
+ * NULL for pre-existing rows and for core/hot/fresh/edge selections that
17
+ * carry no matched section (those render full-page, as before).
18
+ *
19
+ * All three are nullable and append-only — old rows degrade to the prior
20
+ * page-level / turn-keyed behavior. The PK (conversation_id, turn, slug) is
21
+ * unchanged; these are secondary attributes. The existing offline A/B readers
22
+ * (`summarizeSelections`, hot-set, learned-edges, prune) ignore them.
23
+ *
24
+ * Idempotent — the PRAGMA guards make re-running a no-op once the columns
25
+ * exist, and the index uses IF NOT EXISTS.
26
+ */
27
+ export function migrateMemoryV3SelectionsMessageIdAndSections(
28
+ database: DrizzleDb,
29
+ ): void {
30
+ const raw = getSqliteFrom(database);
31
+
32
+ const columns = raw
33
+ .query(`PRAGMA table_info(memory_v3_selections)`)
34
+ .all() as Array<{ name: string }>;
35
+ const columnNames = new Set(columns.map((c) => c.name));
36
+
37
+ if (!columnNames.has("message_id")) {
38
+ raw.exec(`ALTER TABLE memory_v3_selections ADD COLUMN message_id TEXT`);
39
+ }
40
+ if (!columnNames.has("section_ordinal")) {
41
+ raw.exec(
42
+ `ALTER TABLE memory_v3_selections ADD COLUMN section_ordinal INTEGER`,
43
+ );
44
+ }
45
+ if (!columnNames.has("section_title")) {
46
+ raw.exec(`ALTER TABLE memory_v3_selections ADD COLUMN section_title TEXT`);
47
+ }
48
+
49
+ raw.exec(
50
+ `CREATE INDEX IF NOT EXISTS idx_memory_v3_selections_message
51
+ ON memory_v3_selections (message_id)`,
52
+ );
53
+ }
@@ -0,0 +1,51 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+
3
+ /**
4
+ * Create the workflow-engine persistence tables.
5
+ *
6
+ * `workflow_runs` is one row per orchestration run (a sandboxed script that
7
+ * spawns parallel leaf agents). `workflow_journal` is an append-only log of
8
+ * every leaf call (agent / host function / nested workflow) keyed by
9
+ * `(run_id, seq)`, so a run can RESUME after a daemon restart by replaying
10
+ * cached results for the unchanged call prefix.
11
+ *
12
+ * Idempotent — re-running is a no-op once the tables and index exist.
13
+ */
14
+ export function migrateWorkflowRuns(database: DrizzleDb): void {
15
+ database.run(/*sql*/ `
16
+ CREATE TABLE IF NOT EXISTS workflow_runs (
17
+ id TEXT PRIMARY KEY,
18
+ name TEXT,
19
+ script_source TEXT NOT NULL,
20
+ script_hash TEXT NOT NULL,
21
+ args_json TEXT,
22
+ capabilities_json TEXT,
23
+ status TEXT NOT NULL,
24
+ conversation_id TEXT,
25
+ agents_spawned INTEGER NOT NULL DEFAULT 0,
26
+ input_tokens INTEGER NOT NULL DEFAULT 0,
27
+ output_tokens INTEGER NOT NULL DEFAULT 0,
28
+ result_json TEXT,
29
+ error TEXT,
30
+ created_at INTEGER,
31
+ updated_at INTEGER,
32
+ finished_at INTEGER
33
+ )
34
+ `);
35
+ database.run(
36
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_workflow_runs_status_created_at ON workflow_runs (status, created_at)`,
37
+ );
38
+ database.run(/*sql*/ `
39
+ CREATE TABLE IF NOT EXISTS workflow_journal (
40
+ run_id TEXT NOT NULL,
41
+ seq INTEGER NOT NULL,
42
+ call_hash TEXT NOT NULL,
43
+ kind TEXT NOT NULL,
44
+ request_json TEXT,
45
+ result_json TEXT,
46
+ status TEXT NOT NULL,
47
+ created_at INTEGER,
48
+ PRIMARY KEY (run_id, seq)
49
+ )
50
+ `);
51
+ }