@vellumai/assistant 0.5.1 → 0.5.3

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 (405) hide show
  1. package/ARCHITECTURE.md +163 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/docs/skills.md +100 -0
  5. package/package.json +1 -1
  6. package/src/__tests__/agent-loop.test.ts +111 -0
  7. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  8. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  9. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  10. package/src/__tests__/app-executors.test.ts +1 -291
  11. package/src/__tests__/app-git-history.test.ts +4 -4
  12. package/src/__tests__/app-routes-csp.test.ts +1 -0
  13. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  14. package/src/__tests__/attachments-store.test.ts +169 -21
  15. package/src/__tests__/attachments.test.ts +115 -1
  16. package/src/__tests__/btw-routes.test.ts +1 -0
  17. package/src/__tests__/canonical-guardian-store.test.ts +38 -0
  18. package/src/__tests__/channel-reply-delivery.test.ts +55 -0
  19. package/src/__tests__/checker.test.ts +54 -0
  20. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  21. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  22. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  23. package/src/__tests__/config-schema-cmd.test.ts +68 -21
  24. package/src/__tests__/config-schema.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +156 -5
  26. package/src/__tests__/conversation-agent-loop.test.ts +297 -2
  27. package/src/__tests__/conversation-attachments.test.ts +17 -19
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
  29. package/src/__tests__/conversation-disk-view.test.ts +810 -0
  30. package/src/__tests__/conversation-error.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +551 -0
  32. package/src/__tests__/conversation-fork-route.test.ts +386 -0
  33. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  34. package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
  35. package/src/__tests__/conversation-media-retry.test.ts +8 -2
  36. package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
  37. package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
  38. package/src/__tests__/conversation-queue.test.ts +36 -1
  39. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  40. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  42. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  43. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  44. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  45. package/src/__tests__/conversation-store.test.ts +24 -21
  46. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  47. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  48. package/src/__tests__/conversation-title-service.test.ts +137 -0
  49. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  50. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  51. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  52. package/src/__tests__/conversation-wipe.test.ts +226 -0
  53. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  54. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  55. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  56. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  57. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  58. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  59. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  60. package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
  61. package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
  62. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
  63. package/src/__tests__/diagnostics-export.test.ts +70 -1
  64. package/src/__tests__/first-greeting.test.ts +80 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  66. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  67. package/src/__tests__/history-repair.test.ts +32 -10
  68. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  69. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  70. package/src/__tests__/inline-command-runner.test.ts +311 -0
  71. package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
  72. package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
  73. package/src/__tests__/list-messages-attachments.test.ts +96 -0
  74. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  75. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  76. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  77. package/src/__tests__/media-generate-image.test.ts +47 -94
  78. package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
  79. package/src/__tests__/memory-brief-time.test.ts +285 -0
  80. package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
  81. package/src/__tests__/memory-chunk-archive.test.ts +400 -0
  82. package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
  83. package/src/__tests__/memory-episode-archive.test.ts +370 -0
  84. package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  86. package/src/__tests__/memory-observation-archive.test.ts +375 -0
  87. package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
  88. package/src/__tests__/memory-recall-quality.test.ts +7 -7
  89. package/src/__tests__/memory-reducer-store.test.ts +728 -0
  90. package/src/__tests__/memory-reducer-types.test.ts +699 -0
  91. package/src/__tests__/memory-reducer.test.ts +698 -0
  92. package/src/__tests__/memory-regressions.test.ts +6 -4
  93. package/src/__tests__/memory-simplified-config.test.ts +281 -0
  94. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  95. package/src/__tests__/migration-export-http.test.ts +3 -1
  96. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  97. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  98. package/src/__tests__/mime-builder.test.ts +3 -2
  99. package/src/__tests__/non-member-access-request.test.ts +12 -1
  100. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  101. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  102. package/src/__tests__/oauth-store.test.ts +115 -0
  103. package/src/__tests__/parse-identity-fields.test.ts +129 -0
  104. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  105. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  106. package/src/__tests__/recording-handler.test.ts +17 -0
  107. package/src/__tests__/registry.test.ts +3 -8
  108. package/src/__tests__/relay-server.test.ts +1 -1
  109. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  110. package/src/__tests__/schema-transforms.test.ts +165 -5
  111. package/src/__tests__/server-history-render.test.ts +2 -2
  112. package/src/__tests__/skill-load-inline-command.test.ts +598 -0
  113. package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
  114. package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
  115. package/src/__tests__/skills-transitive-hash.test.ts +333 -0
  116. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  117. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  118. package/src/__tests__/starter-task-flow.test.ts +1 -0
  119. package/src/__tests__/suggestion-routes.test.ts +443 -0
  120. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  121. package/src/__tests__/swarm-recursion.test.ts +1 -0
  122. package/src/__tests__/swarm-tool.test.ts +1 -0
  123. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  124. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  125. package/src/__tests__/top-level-renderer.test.ts +22 -0
  126. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  127. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
  128. package/src/__tests__/web-fetch.test.ts +6 -2
  129. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  130. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  131. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  132. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  133. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  134. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  135. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  136. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  137. package/src/agent/attachments.ts +27 -1
  138. package/src/agent/loop.ts +29 -1
  139. package/src/avatar/traits-png-sync.ts +80 -25
  140. package/src/bundler/app-bundler.ts +4 -4
  141. package/src/calls/call-domain.ts +1 -0
  142. package/src/calls/voice-session-bridge.ts +1 -0
  143. package/src/cli/commands/auth.ts +92 -0
  144. package/src/cli/commands/avatar.ts +7 -6
  145. package/src/cli/commands/config.ts +2 -0
  146. package/src/cli/commands/oauth/providers.ts +29 -0
  147. package/src/cli/program.ts +12 -0
  148. package/src/cli.ts +15 -48
  149. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  150. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  151. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  152. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  153. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  154. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  155. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  156. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  157. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  158. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  159. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  160. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  161. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  162. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  163. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  164. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  165. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  166. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  170. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  171. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  172. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  173. package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
  174. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  175. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  176. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  177. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  178. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  179. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  180. package/src/config/bundled-tool-registry.ts +2 -14
  181. package/src/config/feature-flag-registry.json +24 -0
  182. package/src/config/loader.ts +65 -0
  183. package/src/config/raw-config-utils.ts +58 -0
  184. package/src/config/schema-utils.ts +28 -7
  185. package/src/config/schema.ts +20 -0
  186. package/src/config/schemas/elevenlabs.ts +18 -0
  187. package/src/config/schemas/memory-lifecycle.ts +4 -2
  188. package/src/config/schemas/memory-simplified.ts +101 -0
  189. package/src/config/schemas/memory-storage.ts +1 -1
  190. package/src/config/schemas/memory.ts +4 -0
  191. package/src/config/schemas/services.ts +8 -6
  192. package/src/config/skills.ts +50 -4
  193. package/src/contacts/contact-store.ts +13 -6
  194. package/src/contacts/contacts-write.ts +0 -1
  195. package/src/context/window-manager.ts +13 -2
  196. package/src/daemon/conversation-agent-loop-handlers.ts +54 -8
  197. package/src/daemon/conversation-agent-loop.ts +127 -20
  198. package/src/daemon/conversation-attachments.ts +18 -36
  199. package/src/daemon/conversation-error.ts +2 -1
  200. package/src/daemon/conversation-history.ts +18 -4
  201. package/src/daemon/conversation-lifecycle.ts +50 -16
  202. package/src/daemon/conversation-messaging.ts +70 -26
  203. package/src/daemon/conversation-process.ts +58 -34
  204. package/src/daemon/conversation-runtime-assembly.ts +22 -38
  205. package/src/daemon/conversation-slash.ts +121 -256
  206. package/src/daemon/conversation-surfaces.ts +170 -24
  207. package/src/daemon/conversation-tool-setup.ts +0 -6
  208. package/src/daemon/conversation-workspace.ts +21 -1
  209. package/src/daemon/conversation.ts +69 -30
  210. package/src/daemon/first-greeting.ts +35 -0
  211. package/src/daemon/handlers/config-embeddings.ts +156 -0
  212. package/src/daemon/handlers/config-model.ts +62 -26
  213. package/src/daemon/handlers/conversations.ts +0 -23
  214. package/src/daemon/handlers/identity.ts +12 -1
  215. package/src/daemon/handlers/recording.ts +26 -21
  216. package/src/daemon/host-cu-proxy.ts +2 -2
  217. package/src/daemon/lifecycle.ts +115 -65
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/conversations.ts +18 -0
  220. package/src/daemon/message-types/messages.ts +1 -0
  221. package/src/daemon/message-types/shared.ts +2 -0
  222. package/src/daemon/message-types/surfaces.ts +2 -0
  223. package/src/daemon/message-types/upgrades.ts +23 -0
  224. package/src/daemon/server.ts +83 -12
  225. package/src/daemon/shutdown-handlers.ts +8 -5
  226. package/src/daemon/startup-error.ts +9 -0
  227. package/src/daemon/tool-side-effects.ts +11 -28
  228. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  229. package/src/followups/followup-store.ts +47 -1
  230. package/src/instrument.ts +0 -4
  231. package/src/media/app-icon-generator.ts +2 -2
  232. package/src/memory/app-git-service.ts +28 -16
  233. package/src/memory/app-store.ts +230 -41
  234. package/src/memory/archive-store.ts +400 -0
  235. package/src/memory/attachments-store.ts +558 -130
  236. package/src/memory/brief-formatting.ts +33 -0
  237. package/src/memory/brief-open-loops.ts +266 -0
  238. package/src/memory/brief-time.ts +161 -0
  239. package/src/memory/brief.ts +75 -0
  240. package/src/memory/conversation-attention-store.ts +70 -0
  241. package/src/memory/conversation-crud.ts +591 -8
  242. package/src/memory/conversation-directories.ts +125 -0
  243. package/src/memory/conversation-disk-view.ts +390 -0
  244. package/src/memory/conversation-key-store.ts +17 -5
  245. package/src/memory/conversation-queries.ts +5 -1
  246. package/src/memory/conversation-title-service.ts +21 -49
  247. package/src/memory/db-init.ts +40 -0
  248. package/src/memory/embedding-backend.ts +42 -53
  249. package/src/memory/embedding-gemini.test.ts +4 -4
  250. package/src/memory/embedding-local.ts +1 -3
  251. package/src/memory/embedding-ollama.ts +1 -3
  252. package/src/memory/embedding-openai.ts +1 -3
  253. package/src/memory/indexer.ts +114 -21
  254. package/src/memory/items-extractor.ts +42 -13
  255. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  256. package/src/memory/job-handlers/embedding.test.ts +2 -4
  257. package/src/memory/job-handlers/embedding.ts +83 -0
  258. package/src/memory/job-utils.ts +1 -1
  259. package/src/memory/jobs-store.ts +6 -0
  260. package/src/memory/jobs-worker.ts +12 -0
  261. package/src/memory/llm-request-log-store.ts +100 -1
  262. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  263. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  264. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  265. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  266. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  267. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  268. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  269. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  270. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  271. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  272. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  273. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  274. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  275. package/src/memory/migrations/185-memory-brief-state.ts +52 -0
  276. package/src/memory/migrations/186-memory-archive.ts +109 -0
  277. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
  278. package/src/memory/migrations/index.ts +10 -0
  279. package/src/memory/migrations/registry.ts +13 -0
  280. package/src/memory/qdrant-client.ts +23 -4
  281. package/src/memory/reducer-store.ts +271 -0
  282. package/src/memory/reducer-types.ts +99 -0
  283. package/src/memory/reducer.ts +453 -0
  284. package/src/memory/retriever.test.ts +601 -2
  285. package/src/memory/retriever.ts +85 -9
  286. package/src/memory/schema/conversations.ts +9 -0
  287. package/src/memory/schema/index.ts +2 -0
  288. package/src/memory/schema/infrastructure.ts +13 -7
  289. package/src/memory/schema/memory-archive.ts +121 -0
  290. package/src/memory/schema/memory-brief.ts +55 -0
  291. package/src/memory/schema/oauth.ts +6 -0
  292. package/src/memory/search/semantic.ts +17 -4
  293. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  294. package/src/notifications/copy-composer.ts +26 -0
  295. package/src/notifications/decision-engine.ts +14 -1
  296. package/src/notifications/emit-signal.ts +1 -1
  297. package/src/notifications/signal.ts +36 -0
  298. package/src/oauth/byo-connection.test.ts +1 -45
  299. package/src/oauth/byo-connection.ts +2 -8
  300. package/src/oauth/connect-orchestrator.ts +15 -11
  301. package/src/oauth/connection-resolver.test.ts +191 -0
  302. package/src/oauth/connection-resolver.ts +66 -38
  303. package/src/oauth/connection.ts +0 -1
  304. package/src/oauth/oauth-store.ts +99 -47
  305. package/src/oauth/platform-connection.test.ts +0 -1
  306. package/src/oauth/platform-connection.ts +11 -3
  307. package/src/oauth/seed-providers.ts +78 -3
  308. package/src/oauth/token-persistence.ts +16 -10
  309. package/src/permissions/checker.ts +160 -14
  310. package/src/permissions/defaults.ts +14 -0
  311. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  312. package/src/providers/anthropic/client.ts +8 -1
  313. package/src/providers/failover.ts +4 -1
  314. package/src/providers/gemini/client.ts +50 -0
  315. package/src/providers/model-catalog.ts +92 -0
  316. package/src/providers/model-intents.ts +29 -20
  317. package/src/providers/openai/client.ts +49 -0
  318. package/src/providers/types.ts +2 -0
  319. package/src/runtime/access-request-helper.ts +16 -7
  320. package/src/runtime/auth/credential-service.ts +3 -1
  321. package/src/runtime/auth/route-policy.ts +14 -1
  322. package/src/runtime/btw-sidechain.ts +101 -0
  323. package/src/runtime/channel-reply-delivery.ts +17 -1
  324. package/src/runtime/http-router.ts +3 -1
  325. package/src/runtime/http-server.ts +196 -141
  326. package/src/runtime/http-types.ts +1 -0
  327. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  328. package/src/runtime/routes/access-request-decision.ts +41 -0
  329. package/src/runtime/routes/app-management-routes.ts +6 -3
  330. package/src/runtime/routes/app-routes.ts +7 -3
  331. package/src/runtime/routes/approval-routes.ts +1 -0
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  333. package/src/runtime/routes/attachment-routes.ts +45 -15
  334. package/src/runtime/routes/btw-routes.ts +21 -61
  335. package/src/runtime/routes/conversation-management-routes.ts +74 -0
  336. package/src/runtime/routes/conversation-query-routes.ts +187 -10
  337. package/src/runtime/routes/conversation-routes.ts +269 -28
  338. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  339. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  340. package/src/runtime/routes/identity-routes.ts +2 -35
  341. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  342. package/src/runtime/routes/llm-context-normalization.ts +1212 -0
  343. package/src/runtime/routes/log-export-routes.ts +3 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  345. package/src/runtime/routes/memory-item-routes.ts +94 -5
  346. package/src/runtime/routes/migration-routes.ts +4 -1
  347. package/src/runtime/routes/oauth-apps.ts +291 -0
  348. package/src/runtime/routes/secret-routes.ts +30 -1
  349. package/src/runtime/routes/settings-routes.ts +14 -0
  350. package/src/runtime/routes/surface-action-routes.ts +68 -1
  351. package/src/runtime/routes/trace-event-routes.ts +4 -1
  352. package/src/schedule/schedule-store.ts +30 -21
  353. package/src/security/secure-keys.ts +21 -0
  354. package/src/signals/bash.ts +1 -1
  355. package/src/skills/inline-command-expansions.ts +204 -0
  356. package/src/skills/inline-command-render.ts +127 -0
  357. package/src/skills/inline-command-runner.ts +242 -0
  358. package/src/skills/transitive-version-hash.ts +88 -0
  359. package/src/swarm/backend-claude-code.ts +3 -6
  360. package/src/tasks/task-store.ts +43 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  362. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  363. package/src/tools/AGENTS.md +6 -10
  364. package/src/tools/apps/executors.ts +17 -232
  365. package/src/tools/claude-code/claude-code.ts +2 -3
  366. package/src/tools/credentials/vault.ts +7 -12
  367. package/src/tools/host-filesystem/read.ts +13 -10
  368. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  369. package/src/tools/permission-checker.ts +8 -1
  370. package/src/tools/schedule/list.ts +2 -7
  371. package/src/tools/schema-transforms.ts +5 -0
  372. package/src/tools/shared/filesystem/format-diff.ts +2 -7
  373. package/src/tools/skills/execute.ts +1 -1
  374. package/src/tools/skills/load.ts +140 -6
  375. package/src/tools/tool-manifest.ts +0 -6
  376. package/src/tools/ui-surface/definitions.ts +2 -2
  377. package/src/util/device-id.ts +28 -5
  378. package/src/util/platform.ts +24 -0
  379. package/src/util/pricing.ts +1 -0
  380. package/src/util/retry.ts +1 -3
  381. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  382. package/src/workspace/migrations/006-services-config.ts +5 -0
  383. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  384. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  385. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  386. package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
  387. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  388. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  389. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  390. package/src/workspace/migrations/registry.ts +11 -1
  391. package/src/workspace/top-level-renderer.ts +12 -0
  392. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  393. package/src/__tests__/asset-search-tool.test.ts +0 -536
  394. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  395. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  396. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  397. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  398. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  399. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  400. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  401. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  402. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  403. package/src/daemon/media-visibility-policy.ts +0 -59
  404. package/src/tools/assets/materialize.ts +0 -248
  405. package/src/tools/assets/search.ts +0 -400
@@ -0,0 +1,626 @@
1
+ /**
2
+ * Tests for dual-writing archive episodes from compaction summaries.
3
+ *
4
+ * Verifies:
5
+ * - Normal compaction triggers an episode insertion
6
+ * - Overflow (preflight) compaction triggers an episode insertion
7
+ * - No episode is created when compaction does not produce a new summary
8
+ */
9
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
10
+
11
+ import type {
12
+ AgentEvent,
13
+ CheckpointDecision,
14
+ CheckpointInfo,
15
+ } from "../agent/loop.js";
16
+ import type { ContextWindowResult } from "../context/window-manager.js";
17
+ import type { ServerMessage } from "../daemon/message-protocol.js";
18
+ import type { Message } from "../providers/types.js";
19
+
20
+ // ── Module mocks (must precede imports of the module under test) ─────
21
+
22
+ mock.module("../util/logger.js", () => ({
23
+ getLogger: () =>
24
+ new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
25
+ }));
26
+
27
+ mock.module("../util/platform.js", () => ({
28
+ getDataDir: () => "/tmp",
29
+ }));
30
+
31
+ mock.module("../config/loader.js", () => ({
32
+ getConfig: () => ({
33
+ provider: "mock-provider",
34
+ maxTokens: 4096,
35
+ thinking: false,
36
+ contextWindow: {
37
+ maxInputTokens: 100000,
38
+ thresholdTokens: 80000,
39
+ preserveRecentMessages: 6,
40
+ summaryModel: "mock-model",
41
+ maxSummaryTokens: 512,
42
+ overflowRecovery: {
43
+ enabled: true,
44
+ safetyMarginRatio: 0.05,
45
+ maxAttempts: 3,
46
+ interactiveLatestTurnCompression: "summarize",
47
+ nonInteractiveLatestTurnCompression: "truncate",
48
+ },
49
+ },
50
+ rateLimit: { maxRequestsPerMinute: 0 },
51
+ workspaceGit: { turnCommitMaxWaitMs: 10 },
52
+ ui: {},
53
+ }),
54
+ loadRawConfig: () => ({}),
55
+ saveRawConfig: () => {},
56
+ invalidateConfigCache: () => {},
57
+ }));
58
+
59
+ // Token estimator — small by default to avoid preflight trigger
60
+ let mockEstimateTokens = 1000;
61
+ mock.module("../context/token-estimator.js", () => ({
62
+ estimatePromptTokens: () => mockEstimateTokens,
63
+ }));
64
+
65
+ // Reducer
66
+ let mockReducerStepFn:
67
+ | ((msgs: Message[], cfg: unknown, state: unknown) => unknown)
68
+ | null = null;
69
+ mock.module("../daemon/context-overflow-reducer.js", () => ({
70
+ createInitialReducerState: () => ({
71
+ appliedTiers: [],
72
+ injectionMode: "full" as const,
73
+ exhausted: false,
74
+ }),
75
+ reduceContextOverflow: async (
76
+ msgs: Message[],
77
+ cfg: unknown,
78
+ state: unknown,
79
+ ) => {
80
+ if (mockReducerStepFn) return mockReducerStepFn(msgs, cfg, state);
81
+ return {
82
+ messages: msgs,
83
+ tier: "forced_compaction",
84
+ state: {
85
+ appliedTiers: [
86
+ "forced_compaction",
87
+ "tool_result_truncation",
88
+ "media_stubbing",
89
+ "injection_downgrade",
90
+ ],
91
+ injectionMode: "full",
92
+ exhausted: true,
93
+ },
94
+ estimatedTokens: 1000,
95
+ };
96
+ },
97
+ }));
98
+
99
+ mock.module("../daemon/context-overflow-policy.js", () => ({
100
+ resolveOverflowAction: () => "fail_gracefully",
101
+ }));
102
+
103
+ mock.module("../daemon/context-overflow-approval.js", () => ({
104
+ requestCompressionApproval: async () => ({ approved: false }),
105
+ CONTEXT_OVERFLOW_TOOL_NAME: "context_overflow_compression",
106
+ }));
107
+
108
+ mock.module("../hooks/manager.js", () => ({
109
+ getHookManager: () => ({
110
+ trigger: async () => ({ blocked: false }),
111
+ }),
112
+ }));
113
+
114
+ mock.module("../memory/conversation-crud.js", () => ({
115
+ getConversationType: () => "default",
116
+ setConversationOriginChannelIfUnset: () => {},
117
+ updateConversationUsage: () => {},
118
+ getMessages: () => [],
119
+ getConversation: () => ({
120
+ id: "conv-1",
121
+ contextSummary: null,
122
+ contextCompactedMessageCount: 0,
123
+ totalInputTokens: 0,
124
+ totalOutputTokens: 0,
125
+ totalEstimatedCost: 0,
126
+ title: null,
127
+ }),
128
+ provenanceFromTrustContext: () => ({
129
+ source: "user",
130
+ trustContext: undefined,
131
+ }),
132
+ getConversationOriginInterface: () => null,
133
+ addMessage: () => ({ id: "mock-msg-id" }),
134
+ deleteMessageById: () => {},
135
+ updateConversationContextWindow: () => {},
136
+ updateConversationTitle: () => {},
137
+ getConversationOriginChannel: () => null,
138
+ }));
139
+
140
+ mock.module("../memory/conversation-disk-view.js", () => ({
141
+ syncMessageToDisk: () => {},
142
+ rebuildConversationDiskViewFromDbState: () => {},
143
+ }));
144
+
145
+ mock.module("../memory/retriever.js", () => ({
146
+ buildMemoryRecall: async () => ({
147
+ enabled: false,
148
+ degraded: false,
149
+ injectedText: "",
150
+ semanticHits: 0,
151
+ recencyHits: 0,
152
+ injectedTokens: 0,
153
+ latencyMs: 0,
154
+ }),
155
+ injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
156
+ }));
157
+
158
+ mock.module("../memory/app-store.js", () => ({
159
+ getApp: () => null,
160
+ listAppFiles: () => [],
161
+ getAppsDir: () => "/tmp/apps",
162
+ }));
163
+
164
+ mock.module("../memory/app-git-service.js", () => ({
165
+ commitAppTurnChanges: () => Promise.resolve(),
166
+ }));
167
+
168
+ mock.module("../daemon/conversation-memory.js", () => ({
169
+ prepareMemoryContext: async (
170
+ _ctx: unknown,
171
+ _content: string,
172
+ _id: string,
173
+ _signal: AbortSignal,
174
+ ) => ({
175
+ runMessages: [],
176
+ recall: {
177
+ enabled: false,
178
+ degraded: false,
179
+ injectedText: "",
180
+ semanticHits: 0,
181
+ recencyHits: 0,
182
+ injectedTokens: 0,
183
+ latencyMs: 0,
184
+ tier1Count: 0,
185
+ tier2Count: 0,
186
+ hybridSearchMs: 0,
187
+ },
188
+ }),
189
+ }));
190
+
191
+ mock.module("../daemon/conversation-runtime-assembly.js", () => ({
192
+ applyRuntimeInjections: (msgs: Message[]) => msgs,
193
+ stripInjectedContext: (msgs: Message[]) => msgs,
194
+ }));
195
+
196
+ mock.module("../daemon/date-context.js", () => ({
197
+ buildTemporalContext: () => null,
198
+ }));
199
+
200
+ mock.module("../daemon/history-repair.js", () => ({
201
+ repairHistory: (msgs: Message[]) => ({
202
+ messages: msgs,
203
+ stats: {
204
+ assistantToolResultsMigrated: 0,
205
+ missingToolResultsInserted: 0,
206
+ orphanToolResultsDowngraded: 0,
207
+ consecutiveSameRoleMerged: 0,
208
+ },
209
+ }),
210
+ deepRepairHistory: (msgs: Message[]) => ({ messages: msgs, stats: {} }),
211
+ }));
212
+
213
+ mock.module("../daemon/conversation-history.js", () => ({
214
+ consolidateAssistantMessages: () => false,
215
+ }));
216
+
217
+ mock.module("../daemon/conversation-usage.js", () => ({
218
+ recordUsage: () => {},
219
+ }));
220
+
221
+ mock.module("../daemon/conversation-attachments.js", () => ({
222
+ resolveAssistantAttachments: async () => ({
223
+ assistantAttachments: [],
224
+ emittedAttachments: [],
225
+ directiveWarnings: [],
226
+ }),
227
+ approveHostAttachmentRead: async () => true,
228
+ formatAttachmentWarnings: () => "",
229
+ }));
230
+
231
+ mock.module("../daemon/assistant-attachments.js", () => ({
232
+ cleanAssistantContent: (content: unknown[]) => ({
233
+ cleanedContent: content,
234
+ directives: [],
235
+ warnings: [],
236
+ }),
237
+ drainDirectiveDisplayBuffer: (buffer: string) => ({
238
+ emitText: buffer,
239
+ bufferedRemainder: "",
240
+ }),
241
+ }));
242
+
243
+ mock.module("../daemon/conversation-media-retry.js", () => ({
244
+ stripMediaPayloadsForRetry: (msgs: Message[]) => ({
245
+ messages: msgs,
246
+ modified: false,
247
+ replacedBlocks: 0,
248
+ latestUserIndex: null,
249
+ }),
250
+ raceWithTimeout: async () => "completed" as const,
251
+ }));
252
+
253
+ mock.module("../workspace/turn-commit.js", () => ({
254
+ commitTurnChanges: async () => {},
255
+ }));
256
+
257
+ mock.module("../workspace/git-service.js", () => ({
258
+ getWorkspaceGitService: () => ({
259
+ ensureInitialized: async () => {},
260
+ }),
261
+ }));
262
+
263
+ mock.module("../daemon/conversation-error.js", () => ({
264
+ classifyConversationError: (_err: unknown, _ctx: unknown) => ({
265
+ code: "CONVERSATION_PROCESSING_FAILED",
266
+ userMessage: "Something went wrong processing your message.",
267
+ retryable: false,
268
+ errorCategory: "processing_failed",
269
+ }),
270
+ isUserCancellation: (err: unknown, ctx: { aborted?: boolean }) => {
271
+ if (!ctx.aborted) return false;
272
+ if (err instanceof DOMException && err.name === "AbortError") return true;
273
+ if (err instanceof Error && err.name === "AbortError") return true;
274
+ return false;
275
+ },
276
+ buildConversationErrorMessage: (
277
+ conversationId: string,
278
+ classified: Record<string, unknown>,
279
+ ) => ({
280
+ type: "conversation_error",
281
+ conversationId,
282
+ ...classified,
283
+ }),
284
+ isContextTooLarge: (msg: string) => /context.?length.?exceeded/i.test(msg),
285
+ }));
286
+
287
+ mock.module("../daemon/conversation-slash.js", () => ({
288
+ isProviderOrderingError: (msg: string) =>
289
+ /ordering|before.*after|messages.*order/i.test(msg),
290
+ }));
291
+
292
+ mock.module("../util/truncate.js", () => ({
293
+ truncate: (s: string, maxLen: number) =>
294
+ s.length <= maxLen ? s : s.slice(0, maxLen),
295
+ }));
296
+
297
+ mock.module("../agent/message-types.js", () => ({
298
+ createAssistantMessage: (text: string) => ({
299
+ role: "assistant" as const,
300
+ content: [{ type: "text", text }],
301
+ }),
302
+ }));
303
+
304
+ mock.module("../memory/llm-request-log-store.js", () => ({
305
+ recordRequestLog: () => {},
306
+ backfillMessageIdOnLogs: () => {},
307
+ }));
308
+
309
+ // ── Archive store mock — tracks insertCompactionEpisode calls ────────
310
+
311
+ const insertCompactionEpisodeCalls: Array<{
312
+ conversationId: string;
313
+ scopeId?: string;
314
+ title: string;
315
+ summary: string;
316
+ tokenEstimate: number;
317
+ }> = [];
318
+
319
+ mock.module("../memory/archive-store.js", () => ({
320
+ insertCompactionEpisode: (params: {
321
+ conversationId: string;
322
+ scopeId?: string;
323
+ title: string;
324
+ summary: string;
325
+ tokenEstimate: number;
326
+ startAt: number;
327
+ endAt: number;
328
+ }) => {
329
+ insertCompactionEpisodeCalls.push({
330
+ conversationId: params.conversationId,
331
+ scopeId: params.scopeId,
332
+ title: params.title,
333
+ summary: params.summary,
334
+ tokenEstimate: params.tokenEstimate,
335
+ });
336
+ return { episodeId: "mock-episode-id", jobId: "mock-job-id" };
337
+ },
338
+ }));
339
+
340
+ // ── Imports (after mocks) ────────────────────────────────────────────
341
+
342
+ import {
343
+ type AgentLoopConversationContext,
344
+ runAgentLoopImpl,
345
+ } from "../daemon/conversation-agent-loop.js";
346
+
347
+ // ── Test helpers ─────────────────────────────────────────────────────
348
+
349
+ type AgentLoopRun = (
350
+ messages: Message[],
351
+ onEvent: (event: AgentEvent) => void,
352
+ signal?: AbortSignal,
353
+ requestId?: string,
354
+ onCheckpoint?: (checkpoint: CheckpointInfo) => CheckpointDecision,
355
+ ) => Promise<Message[]>;
356
+
357
+ function makeCompactResult(
358
+ summaryText: string,
359
+ overrides?: Partial<ContextWindowResult>,
360
+ ): ContextWindowResult {
361
+ return {
362
+ messages: [
363
+ {
364
+ role: "user",
365
+ content: [{ type: "text", text: `[Summary] ${summaryText}` }],
366
+ },
367
+ ] as Message[],
368
+ compacted: true,
369
+ previousEstimatedInputTokens: 80000,
370
+ estimatedInputTokens: 30000,
371
+ maxInputTokens: 100000,
372
+ thresholdTokens: 80000,
373
+ compactedMessages: 10,
374
+ compactedPersistedMessages: 8,
375
+ summaryCalls: 1,
376
+ summaryInputTokens: 500,
377
+ summaryOutputTokens: 150,
378
+ summaryModel: "mock-model",
379
+ summaryText,
380
+ ...overrides,
381
+ };
382
+ }
383
+
384
+ function makeCtx(
385
+ overrides?: Partial<AgentLoopConversationContext> & {
386
+ agentLoopRun?: AgentLoopRun;
387
+ },
388
+ ): AgentLoopConversationContext {
389
+ const agentLoopRun =
390
+ overrides?.agentLoopRun ??
391
+ (async (messages: Message[]) => [
392
+ ...messages,
393
+ {
394
+ role: "assistant" as const,
395
+ content: [{ type: "text" as const, text: "response" }],
396
+ },
397
+ ]);
398
+
399
+ return {
400
+ conversationId: "test-conv",
401
+ messages: [
402
+ { role: "user", content: [{ type: "text", text: "Hello" }] },
403
+ ] as Message[],
404
+ processing: true,
405
+ abortController: new AbortController(),
406
+ currentRequestId: "test-req",
407
+
408
+ agentLoop: {
409
+ run: agentLoopRun,
410
+ getToolTokenBudget: () => 0,
411
+ } as unknown as AgentLoopConversationContext["agentLoop"],
412
+ provider: {
413
+ name: "mock-provider",
414
+ sendMessage: async () => ({
415
+ content: [{ type: "text", text: "title" }],
416
+ model: "mock",
417
+ usage: { inputTokens: 0, outputTokens: 0 },
418
+ stopReason: "end_turn",
419
+ }),
420
+ } as unknown as AgentLoopConversationContext["provider"],
421
+ systemPrompt: "system prompt",
422
+
423
+ contextWindowManager: {
424
+ shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
425
+ maybeCompact: async () => ({ compacted: false }),
426
+ } as unknown as AgentLoopConversationContext["contextWindowManager"],
427
+ contextCompactedMessageCount: 0,
428
+ contextCompactedAt: null,
429
+
430
+ memoryPolicy: { scopeId: "default", includeDefaultFallback: true },
431
+
432
+ currentActiveSurfaceId: undefined,
433
+ currentPage: undefined,
434
+ surfaceState: new Map(),
435
+ pendingSurfaceActions: new Map(),
436
+ surfaceActionRequestIds: new Set<string>(),
437
+ currentTurnSurfaces: [],
438
+
439
+ workingDir: "/tmp",
440
+ workspaceTopLevelContext: null,
441
+ workspaceTopLevelDirty: false,
442
+ channelCapabilities: undefined,
443
+ commandIntent: undefined,
444
+ trustContext: undefined,
445
+
446
+ coreToolNames: new Set(),
447
+ allowedToolNames: undefined,
448
+ preactivatedSkillIds: undefined,
449
+ skillProjectionState: new Map(),
450
+ skillProjectionCache:
451
+ new Map() as unknown as AgentLoopConversationContext["skillProjectionCache"],
452
+
453
+ traceEmitter: {
454
+ emit: () => {},
455
+ } as unknown as AgentLoopConversationContext["traceEmitter"],
456
+ profiler: {
457
+ startRequest: () => {},
458
+ emitSummary: () => {},
459
+ } as unknown as AgentLoopConversationContext["profiler"],
460
+ usageStats: {
461
+ totalInputTokens: 0,
462
+ totalOutputTokens: 0,
463
+ totalEstimatedCost: 0,
464
+ model: "",
465
+ },
466
+ turnCount: 0,
467
+
468
+ lastAssistantAttachments: [],
469
+ lastAttachmentWarnings: [],
470
+
471
+ hasNoClient: false,
472
+ streamThinking: false,
473
+ prompter: {} as unknown as AgentLoopConversationContext["prompter"],
474
+ queue: {} as unknown as AgentLoopConversationContext["queue"],
475
+
476
+ getWorkspaceGitService: () => ({ ensureInitialized: async () => {} }),
477
+ commitTurnChanges: async () => {},
478
+
479
+ refreshWorkspaceTopLevelContextIfNeeded: () => {},
480
+ markWorkspaceTopLevelDirty: () => {},
481
+ emitActivityState: () => {},
482
+ emitConfirmationStateChanged: () => {},
483
+ getQueueDepth: () => 0,
484
+ hasQueuedMessages: () => false,
485
+ canHandoffAtCheckpoint: () => false,
486
+ drainQueue: () => {},
487
+ getTurnInterfaceContext: () => null,
488
+ getTurnChannelContext: () => ({
489
+ userMessageChannel: "vellum" as const,
490
+ assistantMessageChannel: "vellum" as const,
491
+ }),
492
+
493
+ toolsDisabledDepth: 0,
494
+
495
+ ...overrides,
496
+ } as AgentLoopConversationContext;
497
+ }
498
+
499
+ // ── Tests ────────────────────────────────────────────────────────────
500
+
501
+ beforeEach(() => {
502
+ insertCompactionEpisodeCalls.length = 0;
503
+ mockEstimateTokens = 1000;
504
+ mockReducerStepFn = null;
505
+ });
506
+
507
+ describe("memory episode dual-write from compaction", () => {
508
+ test("normal compaction creates a compaction episode", async () => {
509
+ const summaryText =
510
+ "User discussed project blockers and asked about deployment timeline.";
511
+ const compactResult = makeCompactResult(summaryText);
512
+
513
+ const ctx = makeCtx({
514
+ contextWindowManager: {
515
+ shouldCompact: () => ({ needed: true, estimatedTokens: 85000 }),
516
+ maybeCompact: async () => compactResult,
517
+ } as unknown as AgentLoopConversationContext["contextWindowManager"],
518
+ });
519
+
520
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
521
+
522
+ expect(insertCompactionEpisodeCalls.length).toBe(1);
523
+ expect(insertCompactionEpisodeCalls[0]!.conversationId).toBe("test-conv");
524
+ expect(insertCompactionEpisodeCalls[0]!.scopeId).toBe("default");
525
+ expect(insertCompactionEpisodeCalls[0]!.summary).toBe(summaryText);
526
+ expect(insertCompactionEpisodeCalls[0]!.tokenEstimate).toBe(150);
527
+ });
528
+
529
+ test("overflow (preflight) compaction creates a compaction episode", async () => {
530
+ // Make the preflight budget check trigger by returning a high token count
531
+ mockEstimateTokens = 200000;
532
+
533
+ const summaryText = "Overflow compaction summary of earlier conversation.";
534
+ const compactResult = makeCompactResult(summaryText, {
535
+ summaryOutputTokens: 200,
536
+ });
537
+
538
+ // The reducer step must trigger compaction via its compactionResult
539
+ mockReducerStepFn = (_msgs: Message[]) => ({
540
+ messages: compactResult.messages,
541
+ tier: "forced_compaction",
542
+ state: {
543
+ appliedTiers: [
544
+ "forced_compaction",
545
+ "tool_result_truncation",
546
+ "media_stubbing",
547
+ "injection_downgrade",
548
+ ],
549
+ injectionMode: "full",
550
+ exhausted: true,
551
+ },
552
+ estimatedTokens: 30000,
553
+ compactionResult: compactResult,
554
+ });
555
+
556
+ const ctx = makeCtx({
557
+ contextWindowManager: {
558
+ shouldCompact: () => ({ needed: false, estimatedTokens: 200000 }),
559
+ maybeCompact: async () => ({ compacted: false }),
560
+ } as unknown as AgentLoopConversationContext["contextWindowManager"],
561
+ });
562
+
563
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
564
+
565
+ expect(insertCompactionEpisodeCalls.length).toBe(1);
566
+ expect(insertCompactionEpisodeCalls[0]!.conversationId).toBe("test-conv");
567
+ expect(insertCompactionEpisodeCalls[0]!.summary).toBe(summaryText);
568
+ expect(insertCompactionEpisodeCalls[0]!.tokenEstimate).toBe(200);
569
+ });
570
+
571
+ test("no episode created when compaction does not produce a new summary", async () => {
572
+ // Compaction returns compacted: false — no new summary was produced
573
+ const ctx = makeCtx({
574
+ contextWindowManager: {
575
+ shouldCompact: () => ({ needed: false, estimatedTokens: 5000 }),
576
+ maybeCompact: async () => ({ compacted: false }),
577
+ } as unknown as AgentLoopConversationContext["contextWindowManager"],
578
+ });
579
+
580
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
581
+
582
+ expect(insertCompactionEpisodeCalls.length).toBe(0);
583
+ });
584
+
585
+ test("episode uses the conversation's memory scope", async () => {
586
+ const summaryText = "Scoped compaction summary.";
587
+ const compactResult = makeCompactResult(summaryText);
588
+
589
+ const ctx = makeCtx({
590
+ memoryPolicy: { scopeId: "project-alpha", includeDefaultFallback: false },
591
+ contextWindowManager: {
592
+ shouldCompact: () => ({ needed: true, estimatedTokens: 85000 }),
593
+ maybeCompact: async () => compactResult,
594
+ } as unknown as AgentLoopConversationContext["contextWindowManager"],
595
+ });
596
+
597
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
598
+
599
+ expect(insertCompactionEpisodeCalls.length).toBe(1);
600
+ expect(insertCompactionEpisodeCalls[0]!.scopeId).toBe("project-alpha");
601
+ });
602
+
603
+ test("existing contextSummary persistence is unchanged alongside episode write", async () => {
604
+ const events: ServerMessage[] = [];
605
+ const summaryText =
606
+ "Compaction summary that should be persisted in both places.";
607
+ const compactResult = makeCompactResult(summaryText);
608
+
609
+ const ctx = makeCtx({
610
+ contextWindowManager: {
611
+ shouldCompact: () => ({ needed: true, estimatedTokens: 85000 }),
612
+ maybeCompact: async () => compactResult,
613
+ } as unknown as AgentLoopConversationContext["contextWindowManager"],
614
+ });
615
+
616
+ await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
617
+
618
+ // The context_compacted event should still be emitted (existing behavior)
619
+ const compactEvent = events.find((e) => e.type === "context_compacted");
620
+ expect(compactEvent).toBeDefined();
621
+
622
+ // And the episode should also be created (new dual-write behavior)
623
+ expect(insertCompactionEpisodeCalls.length).toBe(1);
624
+ expect(insertCompactionEpisodeCalls[0]!.summary).toBe(summaryText);
625
+ });
626
+ });
@@ -394,7 +394,9 @@ describe("Memory lifecycle E2E regression", () => {
394
394
  expect(b1.type === "text" && b1.text).toBe("Actual user request");
395
395
 
396
396
  // Stripped by prefix-based stripping (same mechanism as workspace/temporal)
397
- const cleaned = stripUserTextBlocksByPrefix(injected, ["<memory_context __injected>"]);
397
+ const cleaned = stripUserTextBlocksByPrefix(injected, [
398
+ "<memory_context __injected>",
399
+ ]);
398
400
  expect(cleaned).toHaveLength(1);
399
401
  expect(cleaned[0].content).toHaveLength(1);
400
402
  const cb0 = cleaned[0].content[0];