@vellumai/assistant 0.4.56 → 0.4.57

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 (450) hide show
  1. package/ARCHITECTURE.md +10 -10
  2. package/Dockerfile +3 -0
  3. package/README.md +11 -11
  4. package/docs/architecture/integrations.md +2 -2
  5. package/docs/architecture/memory.md +3 -4
  6. package/docs/credential-execution-service.md +13 -20
  7. package/node_modules/@vellumai/ces-contracts/src/error.ts +5 -4
  8. package/package.json +1 -1
  9. package/src/__tests__/actor-token-service.test.ts +7 -7
  10. package/src/__tests__/anthropic-provider.test.ts +172 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +15 -1
  12. package/src/__tests__/approval-cascade.test.ts +2 -2
  13. package/src/__tests__/approval-routes-http.test.ts +3 -4
  14. package/src/__tests__/asset-materialize-tool.test.ts +5 -5
  15. package/src/__tests__/asset-search-tool.test.ts +1 -1
  16. package/src/__tests__/assistant-attachments.test.ts +5 -5
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +50 -38
  19. package/src/__tests__/attachments-store.test.ts +2 -2
  20. package/src/__tests__/avatar-e2e.test.ts +5 -3
  21. package/src/__tests__/browser-skill-endstate.test.ts +0 -1
  22. package/src/__tests__/call-routes-http.test.ts +2 -2
  23. package/src/__tests__/callback-handoff-copy.test.ts +1 -1
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +158 -0
  25. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  26. package/src/__tests__/channel-readiness-service.test.ts +0 -1
  27. package/src/__tests__/checker.test.ts +31 -32
  28. package/src/__tests__/chrome-cdp.test.ts +47 -18
  29. package/src/__tests__/claude-code-skill-regression.test.ts +2 -2
  30. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  31. package/src/__tests__/config-schema.test.ts +9 -18
  32. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -4
  34. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -2
  35. package/src/__tests__/conversation-agent-loop.test.ts +11 -4
  36. package/src/__tests__/conversation-attachments.test.ts +1 -1
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -2
  38. package/src/__tests__/conversation-error.test.ts +33 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +0 -1
  40. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  41. package/src/__tests__/conversation-pairing.test.ts +1 -1
  42. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -4
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +4 -4
  44. package/src/__tests__/conversation-queue.test.ts +23 -14
  45. package/src/__tests__/conversation-routes-slash-commands.test.ts +3 -3
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +185 -173
  47. package/src/__tests__/conversation-seed-composer.test.ts +1 -1
  48. package/src/__tests__/conversation-slash-queue.test.ts +4 -4
  49. package/src/__tests__/conversation-slash-unknown.test.ts +4 -4
  50. package/src/__tests__/conversation-starter-routes.test.ts +291 -0
  51. package/src/__tests__/conversation-wipe.test.ts +438 -0
  52. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -3
  53. package/src/__tests__/conversation-workspace-injection.test.ts +4 -5
  54. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -5
  55. package/src/__tests__/credential-security-e2e.test.ts +20 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault-unit.test.ts +227 -0
  58. package/src/__tests__/credentials-cli.test.ts +3 -0
  59. package/src/__tests__/date-context.test.ts +59 -377
  60. package/src/__tests__/drop-capability-card-state-migration.test.ts +169 -0
  61. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +11 -45
  62. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -3
  63. package/src/__tests__/encrypted-store.test.ts +237 -15
  64. package/src/__tests__/ephemeral-permissions.test.ts +4 -5
  65. package/src/__tests__/event-bus.test.ts +3 -3
  66. package/src/__tests__/gateway-only-enforcement.test.ts +2 -2
  67. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  68. package/src/__tests__/gemini-image-service.test.ts +4 -4
  69. package/src/__tests__/gemini-provider.test.ts +6 -9
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +128 -0
  71. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  72. package/src/__tests__/host-shell-tool.test.ts +6 -6
  73. package/src/__tests__/http-user-message-parity.test.ts +2 -2
  74. package/src/__tests__/intent-routing.test.ts +51 -99
  75. package/src/__tests__/invite-routes-http.test.ts +5 -0
  76. package/src/__tests__/list-messages-attachments.test.ts +1 -1
  77. package/src/__tests__/managed-proxy-context.test.ts +2 -5
  78. package/src/__tests__/managed-skill-lifecycle.test.ts +8 -8
  79. package/src/__tests__/media-generate-image.test.ts +32 -15
  80. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  81. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +1 -1
  82. package/src/__tests__/memory-lifecycle-e2e.test.ts +24 -18
  83. package/src/__tests__/memory-recall-quality.test.ts +4 -3
  84. package/src/__tests__/memory-regressions.test.ts +86 -90
  85. package/src/__tests__/migration-cross-version-compatibility.test.ts +32 -32
  86. package/src/__tests__/migration-export-http.test.ts +26 -27
  87. package/src/__tests__/migration-import-commit-http.test.ts +165 -37
  88. package/src/__tests__/migration-import-preflight-http.test.ts +81 -20
  89. package/src/__tests__/migration-validate-http.test.ts +16 -16
  90. package/src/__tests__/model-intents.test.ts +1 -1
  91. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +1 -1
  92. package/src/__tests__/notification-broadcaster.test.ts +1 -1
  93. package/src/__tests__/notification-decision-fallback.test.ts +2 -2
  94. package/src/__tests__/notification-decision-identity.test.ts +8 -9
  95. package/src/__tests__/notification-decision-strategy.test.ts +1 -1
  96. package/src/__tests__/notification-deep-link.test.ts +1 -1
  97. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  98. package/src/__tests__/notification-schedule-dedup.test.ts +7 -7
  99. package/src/__tests__/oauth-store.test.ts +1 -3
  100. package/src/__tests__/oauth2-gateway-transport.test.ts +6 -1
  101. package/src/__tests__/onboarding-template-contract.test.ts +23 -59
  102. package/src/__tests__/provider-error-scenarios.test.ts +154 -0
  103. package/src/__tests__/provider-fail-open-selection.test.ts +2 -2
  104. package/src/__tests__/provider-managed-proxy-integration.test.ts +8 -9
  105. package/src/__tests__/provider-registry-ollama.test.ts +5 -2
  106. package/src/__tests__/qdrant-manager.test.ts +7 -7
  107. package/src/__tests__/ratelimit.test.ts +0 -74
  108. package/src/__tests__/recording-handler.test.ts +0 -1
  109. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  110. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  111. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
  112. package/src/__tests__/runtime-events-sse.test.ts +1 -1
  113. package/src/__tests__/scheduler-recurrence.test.ts +46 -2
  114. package/src/__tests__/schema-transforms.test.ts +114 -54
  115. package/src/__tests__/secret-onetime-send.test.ts +20 -0
  116. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -2
  117. package/src/__tests__/secret-scanner-executor.test.ts +1 -2
  118. package/src/__tests__/send-endpoint-busy.test.ts +63 -4
  119. package/src/__tests__/send-notification-tool.test.ts +2 -2
  120. package/src/__tests__/shell-credential-ref.test.ts +0 -1
  121. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -2
  122. package/src/__tests__/skill-memory.test.ts +547 -0
  123. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -2
  124. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -0
  125. package/src/__tests__/slack-channel-config.test.ts +109 -94
  126. package/src/__tests__/swarm-conversation-integration.test.ts +2 -2
  127. package/src/__tests__/swarm-recursion.test.ts +2 -2
  128. package/src/__tests__/swarm-tool.test.ts +2 -2
  129. package/src/__tests__/system-prompt.test.ts +19 -66
  130. package/src/__tests__/telegram-config.test.ts +121 -0
  131. package/src/__tests__/terminal-tools.test.ts +1 -1
  132. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -2
  133. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  134. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
  135. package/src/__tests__/tool-executor.test.ts +1 -1
  136. package/src/__tests__/trace-emitter.test.ts +8 -1
  137. package/src/__tests__/trust-store.test.ts +7 -8
  138. package/src/__tests__/twilio-routes.test.ts +1 -18
  139. package/src/__tests__/user-reference.test.ts +82 -2
  140. package/src/__tests__/vbundle-pax-and-symlink.test.ts +196 -0
  141. package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
  142. package/src/approvals/guardian-request-resolvers.ts +3 -3
  143. package/src/avatar/ascii-renderer.ts +2 -2
  144. package/src/avatar/png-renderer.ts +2 -2
  145. package/src/avatar/resvg-lazy.ts +21 -0
  146. package/src/calls/guardian-dispatch.ts +1 -1
  147. package/src/calls/relay-access-wait.ts +2 -2
  148. package/src/calls/twilio-rest.ts +0 -248
  149. package/src/cli/AGENTS.md +5 -8
  150. package/src/cli/__tests__/notifications.test.ts +5 -5
  151. package/src/cli/commands/avatar.ts +64 -2
  152. package/src/cli/commands/conversations.ts +131 -1
  153. package/src/cli/commands/credentials.ts +2 -0
  154. package/src/cli/commands/notifications.ts +3 -3
  155. package/src/cli.ts +10 -0
  156. package/src/config/bundled-skills/acp/SKILL.md +5 -5
  157. package/src/config/bundled-skills/acp/TOOLS.json +6 -6
  158. package/src/config/bundled-skills/app-builder/SKILL.md +42 -42
  159. package/src/config/bundled-skills/app-builder/TOOLS.json +10 -10
  160. package/src/config/bundled-skills/browser/SKILL.md +15 -15
  161. package/src/config/bundled-skills/browser/TOOLS.json +14 -14
  162. package/src/config/bundled-skills/chatgpt-import/SKILL.md +2 -2
  163. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +1 -1
  164. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  165. package/src/config/bundled-skills/claude-code/SKILL.md +5 -5
  166. package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
  167. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -15
  168. package/src/config/bundled-skills/contacts/SKILL.md +3 -3
  169. package/src/config/bundled-skills/contacts/TOOLS.json +4 -4
  170. package/src/config/bundled-skills/document/SKILL.md +4 -4
  171. package/src/config/bundled-skills/document/TOOLS.json +2 -2
  172. package/src/config/bundled-skills/followups/TOOLS.json +3 -3
  173. package/src/config/bundled-skills/gmail/SKILL.md +32 -32
  174. package/src/config/bundled-skills/gmail/TOOLS.json +16 -16
  175. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +1 -1
  176. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  177. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
  178. package/src/config/bundled-skills/google-calendar/TOOLS.json +5 -5
  179. package/src/config/bundled-skills/google-calendar/types.ts +1 -1
  180. package/src/config/bundled-skills/heartbeat/SKILL.md +43 -0
  181. package/src/config/bundled-skills/image-studio/SKILL.md +3 -3
  182. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -3
  183. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +16 -12
  184. package/src/config/bundled-skills/media-processing/SKILL.md +40 -40
  185. package/src/config/bundled-skills/media-processing/TOOLS.json +8 -8
  186. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +2 -2
  187. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +1 -1
  188. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +5 -5
  189. package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -2
  190. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -2
  191. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -2
  192. package/src/config/bundled-skills/media-processing/services/reduce.ts +3 -3
  193. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  194. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +1 -1
  195. package/src/config/bundled-skills/messaging/SKILL.md +29 -25
  196. package/src/config/bundled-skills/messaging/TOOLS.json +11 -11
  197. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  198. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  199. package/src/config/bundled-skills/notifications/SKILL.md +3 -3
  200. package/src/config/bundled-skills/notifications/TOOLS.json +2 -2
  201. package/src/config/bundled-skills/notifications/tools/send-notification.ts +3 -3
  202. package/src/config/bundled-skills/orchestration/SKILL.md +1 -1
  203. package/src/config/bundled-skills/orchestration/TOOLS.json +1 -1
  204. package/src/config/bundled-skills/phone-calls/SKILL.md +18 -14
  205. package/src/config/bundled-skills/phone-calls/TOOLS.json +3 -3
  206. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +2 -2
  207. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +2 -2
  208. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +1 -1
  209. package/src/config/bundled-skills/playbooks/TOOLS.json +4 -4
  210. package/src/config/bundled-skills/schedule/SKILL.md +26 -26
  211. package/src/config/bundled-skills/schedule/TOOLS.json +5 -5
  212. package/src/config/bundled-skills/screen-watch/SKILL.md +3 -3
  213. package/src/config/bundled-skills/screen-watch/TOOLS.json +1 -1
  214. package/src/config/bundled-skills/sequences/SKILL.md +2 -2
  215. package/src/config/bundled-skills/sequences/TOOLS.json +10 -10
  216. package/src/config/bundled-skills/sequences/tools/sequence-analytics.ts +2 -2
  217. package/src/config/bundled-skills/sequences/tools/sequence-enroll.ts +2 -2
  218. package/src/config/bundled-skills/sequences/tools/sequence-enrollment-list.ts +1 -1
  219. package/src/config/bundled-skills/sequences/tools/sequence-get.ts +1 -1
  220. package/src/config/bundled-skills/sequences/tools/sequence-import.ts +3 -3
  221. package/src/config/bundled-skills/sequences/tools/sequence-list.ts +1 -1
  222. package/src/config/bundled-skills/sequences/tools/sequence-update.ts +1 -1
  223. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  224. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
  225. package/src/config/bundled-skills/skill-management/TOOLS.json +5 -5
  226. package/src/config/bundled-skills/skills-catalog/SKILL.md +84 -0
  227. package/src/config/bundled-skills/slack/SKILL.md +2 -2
  228. package/src/config/bundled-skills/slack/TOOLS.json +8 -8
  229. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +3 -3
  230. package/src/config/bundled-skills/subagent/TOOLS.json +5 -5
  231. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  232. package/src/config/bundled-skills/tasks/TOOLS.json +9 -9
  233. package/src/config/bundled-skills/transcribe/SKILL.md +5 -5
  234. package/src/config/bundled-skills/transcribe/TOOLS.json +1 -1
  235. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +10 -10
  236. package/src/config/bundled-skills/watcher/SKILL.md +4 -4
  237. package/src/config/bundled-skills/watcher/TOOLS.json +5 -5
  238. package/src/config/feature-flag-registry.json +33 -17
  239. package/src/config/schemas/sandbox.ts +1 -1
  240. package/src/config/schemas/services.ts +13 -3
  241. package/src/config/schemas/timeouts.ts +0 -10
  242. package/src/contacts/contact-store.ts +63 -0
  243. package/src/contacts/contacts-write.ts +1 -1
  244. package/src/daemon/assistant-attachments.ts +2 -2
  245. package/src/daemon/conversation-agent-loop-handlers.ts +2 -2
  246. package/src/daemon/conversation-agent-loop.ts +7 -30
  247. package/src/daemon/conversation-error.ts +24 -0
  248. package/src/daemon/conversation-memory.ts +8 -7
  249. package/src/daemon/conversation-runtime-assembly.ts +139 -274
  250. package/src/daemon/conversation-slash.ts +7 -26
  251. package/src/daemon/conversation-surfaces.ts +14 -0
  252. package/src/daemon/conversation-tool-setup.ts +9 -8
  253. package/src/daemon/conversation.ts +2 -0
  254. package/src/daemon/daemon-control.ts +1 -1
  255. package/src/daemon/date-context.ts +10 -83
  256. package/src/daemon/handlers/config-channels.ts +12 -2
  257. package/src/daemon/handlers/config-slack-channel.ts +7 -1
  258. package/src/daemon/handlers/config-telegram.ts +6 -1
  259. package/src/daemon/handlers/conversations.ts +2 -2
  260. package/src/daemon/handlers/skills.ts +4 -0
  261. package/src/daemon/lifecycle.ts +28 -4
  262. package/src/daemon/providers-setup.ts +1 -1
  263. package/src/daemon/server.ts +1 -5
  264. package/src/daemon/shutdown-handlers.ts +9 -3
  265. package/src/daemon/tool-side-effects.ts +40 -0
  266. package/src/daemon/trace-emitter.ts +25 -2
  267. package/src/events/domain-events.ts +1 -1
  268. package/src/events/tool-permission-telemetry-listener.ts +46 -0
  269. package/src/inbound/platform-callback-registration.ts +0 -18
  270. package/src/media/app-icon-generator.ts +15 -8
  271. package/src/media/avatar-router.ts +15 -8
  272. package/src/media/gemini-image-service.ts +125 -21
  273. package/src/memory/attachments-store.ts +3 -3
  274. package/src/memory/channel-verification-sessions.ts +6 -6
  275. package/src/memory/conversation-crud.ts +196 -1
  276. package/src/memory/{thread-starters-cadence.ts → conversation-starters-cadence.ts} +9 -42
  277. package/src/memory/conversation-title-service.ts +2 -3
  278. package/src/memory/db-init.ts +25 -1
  279. package/src/memory/invite-store.ts +4 -4
  280. package/src/memory/items-extractor.ts +4 -4
  281. package/src/memory/job-handlers/{thread-starters.ts → conversation-starters.ts} +123 -38
  282. package/src/memory/jobs-store.ts +3 -2
  283. package/src/memory/jobs-worker.ts +7 -5
  284. package/src/memory/lifecycle-events-store.ts +63 -0
  285. package/src/memory/migrations/172-rename-created-by-session-id.ts +27 -0
  286. package/src/memory/migrations/173-rename-source-session-id.ts +16 -0
  287. package/src/memory/migrations/174-rename-thread-starters-table.ts +52 -0
  288. package/src/memory/migrations/175-create-lifecycle-events.ts +15 -0
  289. package/src/memory/migrations/176-drop-capability-card-state.ts +36 -0
  290. package/src/memory/migrations/177-create-trace-events-table.ts +40 -0
  291. package/src/memory/migrations/index.ts +6 -0
  292. package/src/memory/migrations/registry.ts +13 -0
  293. package/src/memory/retriever.test.ts +223 -96
  294. package/src/memory/retriever.ts +115 -138
  295. package/src/memory/schema/calls.ts +1 -1
  296. package/src/memory/schema/contacts.ts +1 -1
  297. package/src/memory/schema/infrastructure.ts +29 -0
  298. package/src/memory/schema/memory-core.ts +7 -17
  299. package/src/memory/schema/notifications.ts +1 -1
  300. package/src/memory/search/formatting.ts +23 -6
  301. package/src/memory/search/lexical.ts +2 -0
  302. package/src/memory/search/semantic.ts +2 -0
  303. package/src/memory/search/staleness.ts +1 -0
  304. package/src/memory/search/types.ts +4 -0
  305. package/src/memory/task-memory-cleanup.ts +96 -6
  306. package/src/memory/trace-event-store.ts +148 -0
  307. package/src/notifications/README.md +1 -1
  308. package/src/notifications/decision-engine.ts +2 -2
  309. package/src/notifications/emit-signal.ts +4 -4
  310. package/src/notifications/events-store.ts +4 -4
  311. package/src/notifications/signal.ts +1 -1
  312. package/src/oauth/manual-token-connection.ts +49 -25
  313. package/src/permissions/checker.ts +6 -5
  314. package/src/permissions/defaults.ts +4 -4
  315. package/src/prompts/__tests__/build-cli-reference-section.test.ts +9 -90
  316. package/src/prompts/cache-boundary.ts +8 -0
  317. package/src/prompts/system-prompt.ts +105 -634
  318. package/src/prompts/templates/BOOTSTRAP.md +166 -33
  319. package/src/prompts/templates/IDENTITY.md +8 -23
  320. package/src/prompts/templates/SOUL.md +20 -41
  321. package/src/prompts/templates/USER.md +3 -19
  322. package/src/prompts/user-reference.ts +14 -16
  323. package/src/providers/anthropic/client.ts +46 -2
  324. package/src/providers/gemini/client.ts +6 -9
  325. package/src/providers/managed-proxy/constants.ts +1 -7
  326. package/src/providers/managed-proxy/context.ts +0 -1
  327. package/src/providers/model-intents.ts +5 -5
  328. package/src/providers/openai/client.ts +10 -1
  329. package/src/providers/openrouter/client.ts +1 -0
  330. package/src/providers/ratelimit.ts +0 -35
  331. package/src/providers/registry.ts +3 -5
  332. package/src/providers/retry.ts +18 -1
  333. package/src/runtime/access-request-helper.ts +1 -1
  334. package/src/runtime/auth/route-policy.ts +7 -0
  335. package/src/runtime/channel-verification-service.ts +1 -1
  336. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  337. package/src/runtime/guardian-vellum-migration.ts +63 -1
  338. package/src/runtime/http-server.ts +8 -4
  339. package/src/runtime/migrations/vbundle-builder.ts +212 -32
  340. package/src/runtime/migrations/vbundle-import-analyzer.ts +74 -8
  341. package/src/runtime/migrations/vbundle-importer.ts +66 -1
  342. package/src/runtime/migrations/vbundle-validator.ts +17 -3
  343. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +4 -4
  344. package/src/runtime/routes/attachment-routes.ts +2 -2
  345. package/src/runtime/routes/btw-routes.ts +9 -0
  346. package/src/runtime/routes/channel-verification-routes.ts +19 -2
  347. package/src/runtime/routes/conversation-management-routes.ts +55 -1
  348. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  349. package/src/runtime/routes/conversation-routes.ts +49 -5
  350. package/src/runtime/routes/conversation-starter-routes.ts +207 -0
  351. package/src/runtime/routes/guardian-bootstrap-routes.ts +13 -9
  352. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -1
  353. package/src/runtime/routes/inbound-stages/verification-intercept.ts +1 -1
  354. package/src/runtime/routes/migration-routes.ts +25 -13
  355. package/src/runtime/routes/secret-routes.ts +18 -0
  356. package/src/runtime/routes/settings-routes.ts +8 -8
  357. package/src/runtime/routes/telemetry-routes.ts +53 -0
  358. package/src/runtime/routes/trace-event-routes.ts +62 -0
  359. package/src/runtime/tool-grant-request-helper.ts +1 -1
  360. package/src/runtime/verification-outbound-actions.ts +47 -31
  361. package/src/security/encrypted-store.ts +263 -78
  362. package/src/skills/catalog-install.ts +10 -0
  363. package/src/skills/managed-store.ts +2 -0
  364. package/src/skills/skill-memory.ts +220 -0
  365. package/src/subagent/manager.ts +1 -4
  366. package/src/telemetry/types.ts +10 -1
  367. package/src/telemetry/usage-telemetry-reporter.test.ts +1 -1
  368. package/src/telemetry/usage-telemetry-reporter.ts +51 -4
  369. package/src/tools/AGENTS.md +11 -11
  370. package/src/tools/acp/spawn.ts +1 -1
  371. package/src/tools/apps/executors.ts +8 -8
  372. package/src/tools/apps/registry.ts +1 -1
  373. package/src/tools/assets/materialize.ts +6 -6
  374. package/src/tools/assets/search.ts +10 -10
  375. package/src/tools/browser/__tests__/auth-cache.test.ts +2 -2
  376. package/src/tools/browser/__tests__/auth-detector.test.ts +4 -4
  377. package/src/tools/browser/auth-detector.ts +6 -6
  378. package/src/tools/browser/browser-execution.ts +13 -13
  379. package/src/tools/browser/browser-manager.ts +3 -3
  380. package/src/tools/browser/chrome-cdp.ts +5 -5
  381. package/src/tools/browser/jit-auth.ts +2 -2
  382. package/src/tools/browser/network-recorder.test.ts +2 -2
  383. package/src/tools/browser/network-recorder.ts +3 -3
  384. package/src/tools/browser/runtime-check.ts +3 -3
  385. package/src/tools/claude-code/claude-code.ts +2 -2
  386. package/src/tools/computer-use/definitions.ts +18 -18
  387. package/src/tools/credential-execution/make-authenticated-request.ts +4 -4
  388. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -3
  389. package/src/tools/credential-execution/run-authenticated-command.ts +4 -4
  390. package/src/tools/credentials/broker-types.ts +5 -5
  391. package/src/tools/credentials/broker.ts +15 -15
  392. package/src/tools/credentials/metadata-store.ts +2 -2
  393. package/src/tools/credentials/resolve.ts +1 -1
  394. package/src/tools/credentials/selection.ts +1 -1
  395. package/src/tools/credentials/tool-policy.ts +1 -1
  396. package/src/tools/credentials/vault.ts +115 -25
  397. package/src/tools/execution-target.ts +2 -2
  398. package/src/tools/executor.ts +7 -7
  399. package/src/tools/filesystem/edit.ts +2 -2
  400. package/src/tools/filesystem/read.ts +1 -1
  401. package/src/tools/filesystem/write.ts +1 -1
  402. package/src/tools/host-filesystem/edit.ts +2 -1
  403. package/src/tools/host-filesystem/read.ts +2 -1
  404. package/src/tools/host-filesystem/write.ts +1 -1
  405. package/src/tools/host-terminal/host-shell.ts +9 -8
  406. package/src/tools/mcp/mcp-tool-factory.ts +7 -6
  407. package/src/tools/memory/definitions.ts +6 -5
  408. package/src/tools/memory/handlers.test.ts +1 -1
  409. package/src/tools/network/__tests__/web-search.test.ts +3 -3
  410. package/src/tools/network/domain-normalize.ts +2 -2
  411. package/src/tools/network/script-proxy/session-manager.ts +10 -10
  412. package/src/tools/network/web-fetch.ts +1 -1
  413. package/src/tools/network/web-search.ts +3 -3
  414. package/src/tools/permission-checker.ts +8 -8
  415. package/src/tools/registry.ts +7 -7
  416. package/src/tools/schedule/list.ts +2 -2
  417. package/src/tools/schema-transforms.ts +31 -21
  418. package/src/tools/secret-detection-handler.ts +1 -1
  419. package/src/tools/sensitive-output-placeholders.ts +1 -1
  420. package/src/tools/shared/filesystem/edit-engine.ts +1 -1
  421. package/src/tools/shared/filesystem/file-ops-service.ts +3 -3
  422. package/src/tools/shared/filesystem/image-read.ts +25 -5
  423. package/src/tools/shared/filesystem/path-policy.ts +2 -2
  424. package/src/tools/shared/shell-output.ts +1 -1
  425. package/src/tools/side-effects.ts +1 -1
  426. package/src/tools/skills/execute.ts +1 -1
  427. package/src/tools/skills/load.ts +3 -3
  428. package/src/tools/skills/sandbox-runner.ts +3 -3
  429. package/src/tools/subagent/read.ts +1 -1
  430. package/src/tools/subagent/spawn.ts +2 -2
  431. package/src/tools/swarm/delegate.ts +3 -3
  432. package/src/tools/system/request-permission.ts +5 -4
  433. package/src/tools/terminal/backends/native.ts +4 -4
  434. package/src/tools/terminal/parser.ts +6 -6
  435. package/src/tools/terminal/sandbox-diagnostics.ts +1 -1
  436. package/src/tools/terminal/shell.ts +16 -16
  437. package/src/tools/tool-approval-handler.ts +21 -12
  438. package/src/tools/tool-manifest.ts +4 -4
  439. package/src/tools/types.ts +3 -3
  440. package/src/tools/ui-surface/definitions.ts +9 -37
  441. package/src/tools/watcher/list.ts +1 -1
  442. package/src/util/logger.ts +7 -2
  443. package/src/util/retry.ts +29 -1
  444. package/src/workspace/migrations/007-web-search-provider-rename.ts +37 -0
  445. package/src/workspace/migrations/registry.ts +2 -0
  446. package/src/__tests__/cli-help-reference-sync.test.ts +0 -26
  447. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -190
  448. package/src/cli/reference.ts +0 -38
  449. package/src/memory/job-handlers/capability-cards.ts +0 -420
  450. package/src/runtime/routes/thread-starter-routes.ts +0 -294
@@ -11,10 +11,11 @@ import type { ToolContext } from "../tools/types.js";
11
11
  // ---------------------------------------------------------------------------
12
12
 
13
13
  let mockApiKey: string | undefined = "test-gemini-key";
14
+ let mockImageGenMode: "your-own" | "managed" = "your-own";
14
15
  let mockGenerateResult = {
15
16
  images: [{ mimeType: "image/png", dataBase64: "generated-data" }],
16
17
  text: "A beautiful image",
17
- resolvedModel: "gemini-2.5-flash-image",
18
+ resolvedModel: "gemini-3.1-flash-image-preview",
18
19
  };
19
20
  let mockGenerateError: Error | null = null;
20
21
  let lastGenerateCredentials: unknown = null;
@@ -29,11 +30,11 @@ mock.module("../config/loader.js", () => ({
29
30
  model: "claude-opus-4-6",
30
31
  },
31
32
  "image-generation": {
32
- mode: "your-own",
33
+ mode: mockImageGenMode,
33
34
  provider: "gemini",
34
- model: "gemini-2.5-flash-image",
35
+ model: "gemini-3.1-flash-image-preview",
35
36
  },
36
- "web-search": { mode: "your-own", provider: "anthropic-native" },
37
+ "web-search": { mode: "your-own", provider: "inference-provider-native" },
37
38
  },
38
39
  }),
39
40
  }));
@@ -43,6 +44,10 @@ mock.module("../security/secure-keys.js", () => ({
43
44
  if (account === "gemini") return mockApiKey;
44
45
  return undefined;
45
46
  },
47
+ getProviderKeyAsync: async (provider: string) => {
48
+ if (provider === "gemini") return mockApiKey;
49
+ return undefined;
50
+ },
46
51
  }));
47
52
 
48
53
  mock.module("../media/gemini-image-service.js", () => ({
@@ -171,10 +176,11 @@ const CONFIG_DIR = join(
171
176
 
172
177
  beforeEach(() => {
173
178
  mockApiKey = "test-gemini-key";
179
+ mockImageGenMode = "your-own";
174
180
  mockGenerateResult = {
175
181
  images: [{ mimeType: "image/png", dataBase64: "generated-data" }],
176
182
  text: "A beautiful image",
177
- resolvedModel: "gemini-2.5-flash-image",
183
+ resolvedModel: "gemini-3.1-flash-image-preview",
178
184
  };
179
185
  mockGenerateError = null;
180
186
  mockAttachments = [];
@@ -208,9 +214,9 @@ describe("image-studio skill script wrapper", () => {
208
214
  expect(result.content).toContain("No Gemini API key");
209
215
  });
210
216
 
211
- test("falls back to managed proxy when no API key is configured", async () => {
212
- mockApiKey = undefined;
213
- mockManagedBaseUrl = "https://platform.example.com/v1/runtime-proxy/vertex";
217
+ test("managed mode uses managed proxy credentials", async () => {
218
+ mockImageGenMode = "managed";
219
+ mockManagedBaseUrl = "https://platform.example.com/v1/runtime-proxy/gemini";
214
220
  mockManagedProxyContext = {
215
221
  enabled: true,
216
222
  platformBaseUrl: "https://platform.example.com",
@@ -224,13 +230,25 @@ describe("image-studio skill script wrapper", () => {
224
230
  expect(lastGenerateCredentials).toEqual({
225
231
  type: "managed-proxy",
226
232
  assistantApiKey: "managed-key-123",
227
- baseUrl: "https://platform.example.com/v1/runtime-proxy/vertex",
233
+ baseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
228
234
  });
229
235
  });
230
236
 
231
- test("prefers direct API key over managed proxy", async () => {
237
+ test("managed mode returns error when managed proxy is unavailable", async () => {
238
+ mockImageGenMode = "managed";
239
+ mockApiKey = "direct-key"; // should be ignored in managed mode
240
+ mockManagedBaseUrl = undefined;
241
+
242
+ const result = await run({ prompt: "a cat" }, fakeContext);
243
+
244
+ expect(result.isError).toBe(true);
245
+ expect(result.content).toContain("Managed proxy is not available");
246
+ });
247
+
248
+ test("your-own mode uses direct API key", async () => {
249
+ mockImageGenMode = "your-own";
232
250
  mockApiKey = "direct-key";
233
- mockManagedBaseUrl = "https://platform.example.com/v1/runtime-proxy/vertex";
251
+ mockManagedBaseUrl = "https://platform.example.com/v1/runtime-proxy/gemini";
234
252
  mockManagedProxyContext = {
235
253
  enabled: true,
236
254
  platformBaseUrl: "https://platform.example.com",
@@ -250,7 +268,7 @@ describe("image-studio skill script wrapper", () => {
250
268
 
251
269
  expect(result.isError).toBe(false);
252
270
  expect(result.content).toContain("Generated 1 image");
253
- expect(result.content).toContain("gemini-2.5-flash-image");
271
+ expect(result.content).toContain("gemini-3.1-flash-image-preview");
254
272
  expect(result.content).toContain("A beautiful image");
255
273
  expect(result.contentBlocks).toHaveLength(1);
256
274
  expect(result.contentBlocks![0]).toEqual({
@@ -270,7 +288,7 @@ describe("image-studio skill script wrapper", () => {
270
288
  { mimeType: "image/png", dataBase64: "img2" },
271
289
  ],
272
290
  text: undefined as unknown as string,
273
- resolvedModel: "gemini-2.5-flash-image",
291
+ resolvedModel: "gemini-3.1-flash-image-preview",
274
292
  };
275
293
 
276
294
  const result = await run({ prompt: "test", variants: 2 }, fakeContext);
@@ -357,8 +375,7 @@ describe("image-studio TOOLS.json manifest", () => {
357
375
  expect(props.mode.enum).toEqual(["generate", "edit"]);
358
376
  expect(props.attachment_ids.type).toBe("array");
359
377
  expect(props.model.enum).toEqual([
360
- "gemini-2.5-flash-image",
361
- "gemini-3-pro-image",
378
+ "gemini-3.1-flash-image-preview",
362
379
  "gemini-3-pro-image-preview",
363
380
  ]);
364
381
  expect(props.variants.type).toBe("number");
@@ -53,7 +53,7 @@ mock.module("../config/loader.js", () => ({
53
53
  model: "test",
54
54
  provider: "test",
55
55
  memory: { enabled: false },
56
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
56
+ rateLimit: { maxRequestsPerMinute: 0 },
57
57
  timeouts: { shellDefaultTimeoutSec: 30, shellMaxTimeoutSec: 60 },
58
58
  sandbox: { enabled: false, backend: "native" },
59
59
  }),
@@ -248,7 +248,7 @@ describe("Memory context benchmark", () => {
248
248
  ...DEFAULT_CONFIG.memory,
249
249
  embeddings: {
250
250
  ...DEFAULT_CONFIG.memory.embeddings,
251
- provider: "openai" as const,
251
+ provider: "local" as const,
252
252
  required: false,
253
253
  },
254
254
  retrieval: {
@@ -82,6 +82,7 @@ mock.module("../config/loader.js", () => ({
82
82
  invalidateConfigCache: () => {},
83
83
  }));
84
84
 
85
+ import { stripUserTextBlocksByPrefix } from "../daemon/conversation-runtime-assembly.js";
85
86
  import { getDb, initializeDb, resetDb } from "../memory/db.js";
86
87
  import {
87
88
  resetCleanupScheduleThrottle,
@@ -89,8 +90,7 @@ import {
89
90
  } from "../memory/jobs-worker.js";
90
91
  import {
91
92
  buildMemoryRecall,
92
- injectMemoryRecallAsSeparateMessage,
93
- stripMemoryRecallMessages,
93
+ injectMemoryRecallAsUserBlock,
94
94
  } from "../memory/retriever.js";
95
95
  import {
96
96
  conversations,
@@ -98,6 +98,7 @@ import {
98
98
  memoryItemSources,
99
99
  messages,
100
100
  } from "../memory/schema.js";
101
+ import type { Message } from "../providers/types.js";
101
102
 
102
103
  describe("Memory lifecycle E2E regression", () => {
103
104
  beforeAll(() => {
@@ -322,7 +323,7 @@ describe("Memory lifecycle E2E regression", () => {
322
323
  totalOutputTokens: 0,
323
324
  totalEstimatedCost: 0,
324
325
  contextSummary: null,
325
- contextCompactedMessageCount: 0,
326
+ contextCompactedMessageCount: 1,
326
327
  contextCompactedAt: null,
327
328
  })
328
329
  .run();
@@ -365,34 +366,39 @@ describe("Memory lifecycle E2E regression", () => {
365
366
  expect(recall.enabled).toBe(true);
366
367
  expect(recall.injectedText.length).toBeGreaterThan(0);
367
368
  expect(recall.injectedTokens).toBeGreaterThan(0);
368
- expect(recall.injectedText).toContain("<memory_context>");
369
+ expect(recall.injectedText).toContain("<memory_context __injected>");
369
370
  expect(recall.injectedText).toContain("</memory_context>");
370
371
  });
371
372
 
372
- test("stripping removes <memory_context> tags from injected recall", () => {
373
+ test("stripping removes <memory_context> block from injected recall", () => {
373
374
  const memoryRecallText =
374
- "<memory_context>\n\n<relevant_context>\nuser prefers concise answers\n</relevant_context>\n\n</memory_context>";
375
- const originalMessages = [
375
+ "<memory_context __injected>\n\n<relevant_context>\nuser prefers concise answers\n</relevant_context>\n\n</memory_context>";
376
+ const originalMessages: Message[] = [
376
377
  {
377
- role: "user" as const,
378
- content: [{ type: "text", text: "Actual user request" }],
378
+ role: "user",
379
+ content: [{ type: "text" as const, text: "Actual user request" }],
379
380
  },
380
381
  ];
381
- const injected = injectMemoryRecallAsSeparateMessage(
382
+ const injected = injectMemoryRecallAsUserBlock(
382
383
  originalMessages,
383
384
  memoryRecallText,
384
385
  );
385
386
 
386
- expect(injected).toHaveLength(3);
387
+ // Memory context prepended to the last user message as a content block
388
+ expect(injected).toHaveLength(1);
387
389
  expect(injected[0].role).toBe("user");
388
- expect(injected[0].content[0].text).toBe(memoryRecallText);
389
- expect(injected[1].role as string).toBe("assistant");
390
- expect(injected[2].role).toBe("user");
391
- expect(injected[2].content[0].text).toBe("Actual user request");
392
-
393
- const cleaned = stripMemoryRecallMessages(injected, memoryRecallText);
390
+ expect(injected[0].content).toHaveLength(2);
391
+ const b0 = injected[0].content[0];
392
+ const b1 = injected[0].content[1];
393
+ expect(b0.type === "text" && b0.text).toBe(memoryRecallText);
394
+ expect(b1.type === "text" && b1.text).toBe("Actual user request");
395
+
396
+ // Stripped by prefix-based stripping (same mechanism as workspace/temporal)
397
+ const cleaned = stripUserTextBlocksByPrefix(injected, ["<memory_context __injected>"]);
394
398
  expect(cleaned).toHaveLength(1);
395
- expect(cleaned[0].content[0].text).toBe("Actual user request");
399
+ expect(cleaned[0].content).toHaveLength(1);
400
+ const cb0 = cleaned[0].content[0];
401
+ expect(cb0.type === "text" && cb0.text).toBe("Actual user request");
396
402
  });
397
403
 
398
404
  test("empty retrieval returns no injection", async () => {
@@ -112,6 +112,7 @@ function insertConversation(
112
112
  db: ReturnType<typeof getDb>,
113
113
  id: string,
114
114
  createdAt: number,
115
+ contextCompactedMessageCount = 0,
115
116
  ) {
116
117
  db.insert(conversations)
117
118
  .values({
@@ -123,7 +124,7 @@ function insertConversation(
123
124
  totalOutputTokens: 0,
124
125
  totalEstimatedCost: 0,
125
126
  contextSummary: null,
126
- contextCompactedMessageCount: 0,
127
+ contextCompactedMessageCount,
127
128
  contextCompactedAt: null,
128
129
  })
129
130
  .run();
@@ -629,7 +630,7 @@ describe("Memory Recall Quality", () => {
629
630
  test("invalidated items are excluded from recall", async () => {
630
631
  const db = getDb();
631
632
  const now = 1_700_000_275_000;
632
- insertConversation(db, "conv-invalid-status", now);
633
+ insertConversation(db, "conv-invalid-status", now, 1);
633
634
  insertMessage(
634
635
  db,
635
636
  "msg-invalid-status",
@@ -989,7 +990,7 @@ describe("Memory Recall Quality", () => {
989
990
  test("precision@k guard verifies pipeline completes with seeded segments", async () => {
990
991
  const db = getDb();
991
992
  const now = 1_700_000_700_000;
992
- insertConversation(db, "conv-pk", now);
993
+ insertConversation(db, "conv-pk", now, 3);
993
994
 
994
995
  const prefs = [
995
996
  {
@@ -67,8 +67,8 @@ import { and, eq } from "drizzle-orm";
67
67
  import { DEFAULT_CONFIG } from "../config/defaults.js";
68
68
  import { vectorToBlob } from "../memory/job-utils.js";
69
69
 
70
- // Disable LLM extraction in tests to avoid real API calls and ensure
71
- // deterministic pattern-based extraction.
70
+ // Disable LLM extraction and summarization in tests to avoid real API calls
71
+ // and ensure deterministic pattern-based extraction / fallback summaries.
72
72
  const TEST_CONFIG = {
73
73
  ...DEFAULT_CONFIG,
74
74
  memory: {
@@ -77,6 +77,10 @@ const TEST_CONFIG = {
77
77
  ...DEFAULT_CONFIG.memory.extraction,
78
78
  useLLM: false,
79
79
  },
80
+ summarization: {
81
+ ...DEFAULT_CONFIG.memory.summarization,
82
+ useLLM: false,
83
+ },
80
84
  },
81
85
  };
82
86
 
@@ -86,6 +90,7 @@ mock.module("../config/loader.js", () => ({
86
90
  invalidateConfigCache: () => {},
87
91
  }));
88
92
  import { estimateTextTokens } from "../context/token-estimator.js";
93
+ import { stripUserTextBlocksByPrefix } from "../daemon/conversation-runtime-assembly.js";
89
94
  import {
90
95
  getMemorySystemStatus,
91
96
  requestMemoryBackfill,
@@ -120,8 +125,7 @@ import {
120
125
  escapeXmlTags,
121
126
  formatAbsoluteTime,
122
127
  formatRelativeTime,
123
- injectMemoryRecallAsSeparateMessage,
124
- stripMemoryRecallMessages,
128
+ injectMemoryRecallAsUserBlock,
125
129
  } from "../memory/retriever.js";
126
130
  import {
127
131
  conversations,
@@ -133,6 +137,7 @@ import {
133
137
  messages,
134
138
  } from "../memory/schema.js";
135
139
  import { buildCoreIdentityContext } from "../prompts/system-prompt.js";
140
+ import type { Message } from "../providers/types.js";
136
141
 
137
142
  describe("Memory regressions", () => {
138
143
  beforeAll(() => {
@@ -308,133 +313,124 @@ describe("Memory regressions", () => {
308
313
  expect(recall.enabled).toBe(true);
309
314
  });
310
315
 
311
- test("memory recall injection via separate message and stripped from runtime history", () => {
316
+ test("memory recall injection as user block and stripped from runtime history", () => {
312
317
  const memoryRecallText =
313
- "<memory_context>\n\n<relevant_context>\nuser prefers concise answers\n</relevant_context>\n\n</memory_context>";
314
- const originalMessages = [
318
+ "<memory_context __injected>\n\n<relevant_context>\nuser prefers concise answers\n</relevant_context>\n\n</memory_context>";
319
+ const originalMessages: Message[] = [
315
320
  {
316
- role: "user" as const,
317
- content: [{ type: "text", text: "Actual user request" }],
321
+ role: "user",
322
+ content: [{ type: "text" as const, text: "Actual user request" }],
318
323
  },
319
324
  ];
320
- const injected = injectMemoryRecallAsSeparateMessage(
325
+ const injected = injectMemoryRecallAsUserBlock(
321
326
  originalMessages,
322
327
  memoryRecallText,
323
328
  );
324
329
 
325
- expect(injected).toHaveLength(3);
330
+ // Memory context prepended to last user message as content block
331
+ expect(injected).toHaveLength(1);
326
332
  expect(injected[0].role).toBe("user");
327
- expect(injected[0].content[0].text).toBe(memoryRecallText);
328
- expect(injected[1].role as string).toBe("assistant");
329
- expect(injected[2].role).toBe("user");
330
- expect(injected[2].content[0].text).toBe("Actual user request");
331
-
332
- const cleaned = stripMemoryRecallMessages(injected, memoryRecallText);
333
+ expect(injected[0].content).toHaveLength(2);
334
+ const b0 = injected[0].content[0];
335
+ const b1 = injected[0].content[1];
336
+ expect(b0.type === "text" && b0.text).toBe(memoryRecallText);
337
+ expect(b1.type === "text" && b1.text).toBe("Actual user request");
338
+
339
+ // Stripped by prefix-based stripping
340
+ const cleaned = stripUserTextBlocksByPrefix(injected, [
341
+ "<memory_context __injected>",
342
+ ]);
333
343
  expect(cleaned).toHaveLength(1);
334
- expect(cleaned[0].content[0].text).toBe("Actual user request");
344
+ expect(cleaned[0].content).toHaveLength(1);
345
+ const cb0 = cleaned[0].content[0];
346
+ expect(cb0.type === "text" && cb0.text).toBe("Actual user request");
335
347
  });
336
348
 
337
- test("recall stripping removes last matching block in merged content after deep-repair", () => {
349
+ test("prefix-based stripping removes all <memory_context> blocks from merged content", () => {
338
350
  const memoryRecallText =
339
- "[Memory Recall v1]\n- [item:abc] user prefers concise answers";
340
- // Simulate deep-repair merging two consecutive user messages where both
341
- // contain the recall text. The injected (active) recall block is the last one.
342
- const mergedUserMessage = {
343
- role: "user" as const,
351
+ "<memory_context __injected>\n\n<relevant_context>\nuser prefers concise answers\n</relevant_context>\n\n</memory_context>";
352
+ // Simulate deep-repair merging where multiple memory context blocks exist.
353
+ // Prefix-based stripping removes all blocks starting with <memory_context __injected>.
354
+ const mergedUserMessage: Message = {
355
+ role: "user",
344
356
  content: [
345
- { type: "text", text: memoryRecallText },
346
- { type: "text", text: "Earlier user request" },
347
- { type: "text", text: memoryRecallText },
348
- { type: "text", text: "Latest user request" },
357
+ { type: "text" as const, text: memoryRecallText },
358
+ { type: "text" as const, text: "Earlier user request" },
359
+ { type: "text" as const, text: memoryRecallText },
360
+ { type: "text" as const, text: "Latest user request" },
349
361
  ],
350
362
  };
351
363
 
352
- const cleaned = stripMemoryRecallMessages(
364
+ const cleaned = stripUserTextBlocksByPrefix(
353
365
  [mergedUserMessage],
354
- memoryRecallText,
366
+ ["<memory_context __injected>"],
355
367
  );
356
368
  expect(cleaned).toHaveLength(1);
357
- // The last (active) recall block should be stripped, the first (leaked) one preserved
358
369
  expect(cleaned[0].content).toEqual([
359
- { type: "text", text: memoryRecallText },
360
370
  { type: "text", text: "Earlier user request" },
361
371
  { type: "text", text: "Latest user request" },
362
372
  ]);
363
373
  });
364
374
 
365
- test("separate_context_message injects memory as user+assistant pair before last user message", () => {
366
- const history = [
367
- { role: "user" as const, content: [{ type: "text", text: "Hello" }] },
368
- { role: "assistant" as const, content: [{ type: "text", text: "Hi!" }] },
375
+ test("injectMemoryRecallAsUserBlock prepends memory to last user message", () => {
376
+ const history: Message[] = [
377
+ { role: "user", content: [{ type: "text" as const, text: "Hello" }] },
378
+ { role: "assistant", content: [{ type: "text" as const, text: "Hi!" }] },
369
379
  {
370
- role: "user" as const,
371
- content: [{ type: "text", text: "Tell me about X" }],
380
+ role: "user",
381
+ content: [{ type: "text" as const, text: "Tell me about X" }],
372
382
  },
373
383
  ];
374
- const recallText = "<memory>Some recalled fact</memory>";
375
- const result = injectMemoryRecallAsSeparateMessage(history, recallText);
376
- // Should have 5 messages: original 2 + injected user + injected assistant ack + original last user
377
- expect(result).toHaveLength(5);
384
+ const recallText =
385
+ "<memory_context __injected>\n\n<relevant_context>\nSome recalled fact\n</relevant_context>\n\n</memory_context>";
386
+ const result = injectMemoryRecallAsUserBlock(history, recallText);
387
+ // Same number of messages — no synthetic pair
388
+ expect(result).toHaveLength(3);
378
389
  expect(result[0]).toBe(history[0]);
379
390
  expect(result[1]).toBe(history[1]);
380
- // Injected context message
381
- expect(result[2].role).toBe("user");
382
- expect(result[2].content).toEqual([{ type: "text", text: recallText }]);
383
- // Assistant acknowledgment
384
- expect(result[3].role).toBe("assistant");
385
- expect(result[3].content).toEqual([
386
- { type: "text", text: "[Memory context loaded.]" },
387
- ]);
388
- // Original user message preserved unchanged
389
- expect(result[4]).toBe(history[2]);
391
+ // Last user message has memory prepended
392
+ const r0 = result[2].content[0];
393
+ const r1 = result[2].content[1];
394
+ expect(r0.type === "text" && r0.text).toBe(recallText);
395
+ expect(r1.type === "text" && r1.text).toBe("Tell me about X");
390
396
  });
391
397
 
392
- test("separate_context_message with empty text is a no-op", () => {
393
- const history = [
394
- { role: "user" as const, content: [{ type: "text", text: "Hello" }] },
398
+ test("injectMemoryRecallAsUserBlock with empty text is a no-op", () => {
399
+ const history: Message[] = [
400
+ { role: "user", content: [{ type: "text" as const, text: "Hello" }] },
395
401
  ];
396
- const result = injectMemoryRecallAsSeparateMessage(history, " ");
402
+ const result = injectMemoryRecallAsUserBlock(history, " ");
397
403
  expect(result).toBe(history);
398
404
  });
399
405
 
400
- test("stripMemoryRecallMessages removes separate_context_message pair", () => {
401
- const recallText = "<memory>Some recalled fact</memory>";
402
- const messages = [
403
- { role: "user" as const, content: [{ type: "text", text: "Hello" }] },
404
- { role: "assistant" as const, content: [{ type: "text", text: "Hi!" }] },
405
- // Injected context message pair
406
- { role: "user" as const, content: [{ type: "text", text: recallText }] },
406
+ test("stripUserTextBlocksByPrefix removes memory_context block from user message", () => {
407
+ const recallText =
408
+ "<memory_context __injected>\n\n<relevant_context>\nSome recalled fact\n</relevant_context>\n\n</memory_context>";
409
+ const msgs: Message[] = [
410
+ { role: "user", content: [{ type: "text" as const, text: "Hello" }] },
407
411
  {
408
- role: "assistant" as const,
409
- content: [{ type: "text", text: "[Memory context loaded.]" }],
410
- },
411
- // Real user message
412
- {
413
- role: "user" as const,
414
- content: [{ type: "text", text: "Tell me about X" }],
412
+ role: "assistant",
413
+ content: [{ type: "text" as const, text: "Hi!" }],
415
414
  },
416
- ];
417
- const cleaned = stripMemoryRecallMessages(messages, recallText);
418
- expect(cleaned).toHaveLength(3);
419
- expect(cleaned[0].content[0].text).toBe("Hello");
420
- expect(cleaned[1].content[0].text).toBe("Hi!");
421
- expect(cleaned[2].content[0].text).toBe("Tell me about X");
422
- });
423
-
424
- test("stripMemoryRecallMessages falls back to prepend_user_block when no separate pair found", () => {
425
- const recallText = "<memory>Fact</memory>";
426
- const messages = [
427
415
  {
428
- role: "user" as const,
416
+ role: "user",
429
417
  content: [
430
- { type: "text", text: recallText },
431
- { type: "text", text: "User query" },
418
+ { type: "text" as const, text: recallText },
419
+ { type: "text" as const, text: "Tell me about X" },
432
420
  ],
433
421
  },
434
422
  ];
435
- const cleaned = stripMemoryRecallMessages(messages, recallText);
436
- expect(cleaned).toHaveLength(1);
437
- expect(cleaned[0].content).toEqual([{ type: "text", text: "User query" }]);
423
+ const cleaned = stripUserTextBlocksByPrefix(msgs, [
424
+ "<memory_context __injected>",
425
+ ]);
426
+ expect(cleaned).toHaveLength(3);
427
+ const c0 = cleaned[0].content[0];
428
+ const c1 = cleaned[1].content[0];
429
+ const c2 = cleaned[2].content[0];
430
+ expect(c0.type === "text" && c0.text).toBe("Hello");
431
+ expect(c1.type === "text" && c1.text).toBe("Hi!");
432
+ expect(cleaned[2].content).toHaveLength(1);
433
+ expect(c2.type === "text" && c2.text).toBe("Tell me about X");
438
434
  });
439
435
 
440
436
  test("aborting memory recall embedding returns a non-degraded aborted recall result", async () => {
@@ -1819,7 +1815,7 @@ describe("Memory regressions", () => {
1819
1815
  totalOutputTokens: 0,
1820
1816
  totalEstimatedCost: 0,
1821
1817
  contextSummary: null,
1822
- contextCompactedMessageCount: 0,
1818
+ contextCompactedMessageCount: 1,
1823
1819
  contextCompactedAt: null,
1824
1820
  })
1825
1821
  .run();
@@ -2064,7 +2060,7 @@ describe("Memory regressions", () => {
2064
2060
  totalOutputTokens: 0,
2065
2061
  totalEstimatedCost: 0,
2066
2062
  contextSummary: null,
2067
- contextCompactedMessageCount: 0,
2063
+ contextCompactedMessageCount: 1,
2068
2064
  contextCompactedAt: null,
2069
2065
  })
2070
2066
  .run();
@@ -2138,7 +2134,7 @@ describe("Memory regressions", () => {
2138
2134
  totalOutputTokens: 0,
2139
2135
  totalEstimatedCost: 0,
2140
2136
  contextSummary: null,
2141
- contextCompactedMessageCount: 0,
2137
+ contextCompactedMessageCount: 1,
2142
2138
  contextCompactedAt: null,
2143
2139
  })
2144
2140
  .run();