@vellumai/assistant 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +68 -15
  3. package/Dockerfile +2 -2
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +42 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  9. package/openapi.yaml +539 -4
  10. package/package.json +5 -1
  11. package/src/__tests__/anthropic-provider.test.ts +160 -95
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  13. package/src/__tests__/app-executors.test.ts +47 -1
  14. package/src/__tests__/app-source-watcher.test.ts +159 -0
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/checker.test.ts +138 -172
  17. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  18. package/src/__tests__/config-schema.test.ts +5 -0
  19. package/src/__tests__/context-overflow-approval.test.ts +5 -5
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  22. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  23. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  24. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  27. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  28. package/src/__tests__/conversation-wipe.test.ts +2 -6
  29. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  30. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  31. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  32. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  33. package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
  34. package/src/__tests__/date-context.test.ts +76 -210
  35. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  36. package/src/__tests__/file-list-tool.test.ts +219 -0
  37. package/src/__tests__/first-greeting.test.ts +1 -1
  38. package/src/__tests__/heartbeat-service.test.ts +180 -3
  39. package/src/__tests__/identity-routes.test.ts +328 -0
  40. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  41. package/src/__tests__/injection-block.test.ts +24 -0
  42. package/src/__tests__/inline-command-runner.test.ts +7 -5
  43. package/src/__tests__/install-skill-routing.test.ts +7 -6
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  45. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  46. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  47. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  48. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  49. package/src/__tests__/log-export-workspace.test.ts +257 -100
  50. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  51. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  53. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  54. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  55. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  56. package/src/__tests__/mock-fetch.ts +87 -0
  57. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  58. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  59. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  60. package/src/__tests__/onboarding-template-contract.test.ts +63 -14
  61. package/src/__tests__/parser.test.ts +32 -0
  62. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  63. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  64. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  65. package/src/__tests__/permission-mode-store.test.ts +277 -0
  66. package/src/__tests__/permission-mode.test.ts +101 -0
  67. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  68. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  69. package/src/__tests__/profiler-routes.test.ts +502 -0
  70. package/src/__tests__/profiler-run-store.test.ts +441 -0
  71. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  72. package/src/__tests__/registry.test.ts +1 -1
  73. package/src/__tests__/require-fresh-approval.test.ts +0 -2
  74. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  75. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  76. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  77. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  78. package/src/__tests__/search-skills-unified.test.ts +4 -3
  79. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  80. package/src/__tests__/set-permission-mode.test.ts +274 -0
  81. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  82. package/src/__tests__/skill-memory.test.ts +2 -783
  83. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  84. package/src/__tests__/subagent-detail.test.ts +84 -0
  85. package/src/__tests__/subagent-disposal.test.ts +308 -0
  86. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  87. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  88. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  89. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  90. package/src/__tests__/subagent-tools.test.ts +464 -4
  91. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  92. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  93. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  94. package/src/__tests__/terminal-tools.test.ts +16 -29
  95. package/src/__tests__/test-preload.ts +18 -0
  96. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  98. package/src/__tests__/tool-executor.test.ts +4 -27
  99. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  100. package/src/__tests__/top-level-renderer.test.ts +10 -13
  101. package/src/__tests__/transport-hints-queue.test.ts +77 -0
  102. package/src/__tests__/trust-store.test.ts +4 -4
  103. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  104. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  105. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  106. package/src/__tests__/workspace-policy.test.ts +2 -7
  107. package/src/agent/loop.ts +6 -29
  108. package/src/approvals/guardian-request-resolvers.ts +24 -0
  109. package/src/avatar/traits-png-sync.ts +3 -3
  110. package/src/channels/types.ts +5 -0
  111. package/src/cli/__tests__/run-assistant-command.ts +56 -0
  112. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  113. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  114. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  115. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  116. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  117. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  118. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  119. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  120. package/src/cli/commands/conversations.ts +1 -8
  121. package/src/cli/commands/default-action.ts +68 -1
  122. package/src/cli/commands/email.ts +584 -835
  123. package/src/cli/commands/memory.ts +1 -34
  124. package/src/cli/commands/notifications.ts +7 -2
  125. package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
  126. package/src/cli/commands/oauth/connect.ts +25 -5
  127. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  128. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  129. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  130. package/src/cli/commands/routes.ts +396 -0
  131. package/src/cli/commands/skills.ts +130 -20
  132. package/src/cli/program.ts +11 -2
  133. package/src/cli.ts +1 -120
  134. package/src/config/assistant-feature-flags.ts +59 -55
  135. package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
  136. package/src/config/bundled-skills/gmail/SKILL.md +13 -8
  137. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  139. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  141. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  142. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  143. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  144. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  145. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  146. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  147. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  148. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  149. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  150. package/src/config/env-registry.ts +63 -0
  151. package/src/config/feature-flag-registry.json +17 -1
  152. package/src/config/schema.ts +8 -0
  153. package/src/config/schemas/filing.ts +51 -0
  154. package/src/config/schemas/heartbeat.ts +15 -12
  155. package/src/config/schemas/memory-lifecycle.ts +12 -0
  156. package/src/config/schemas/security.ts +14 -0
  157. package/src/config/schemas/services.ts +8 -0
  158. package/src/credential-execution/approval-bridge.ts +0 -1
  159. package/src/credential-execution/managed-catalog.ts +3 -7
  160. package/src/daemon/app-source-watcher.ts +93 -0
  161. package/src/daemon/config-watcher.ts +85 -3
  162. package/src/daemon/context-overflow-approval.ts +0 -1
  163. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  164. package/src/daemon/conversation-agent-loop.ts +179 -65
  165. package/src/daemon/conversation-attachments.ts +0 -1
  166. package/src/daemon/conversation-history.ts +4 -19
  167. package/src/daemon/conversation-lifecycle.ts +8 -14
  168. package/src/daemon/conversation-messaging.ts +3 -0
  169. package/src/daemon/conversation-process.ts +30 -8
  170. package/src/daemon/conversation-queue-manager.ts +8 -0
  171. package/src/daemon/conversation-runtime-assembly.ts +359 -308
  172. package/src/daemon/conversation-surfaces.ts +65 -0
  173. package/src/daemon/conversation-tool-setup.ts +44 -17
  174. package/src/daemon/conversation-workspace.ts +1 -2
  175. package/src/daemon/conversation.ts +19 -3
  176. package/src/daemon/date-context.ts +26 -53
  177. package/src/daemon/first-greeting.ts +1 -1
  178. package/src/daemon/handlers/conversations.ts +5 -7
  179. package/src/daemon/handlers/shared.test.ts +143 -0
  180. package/src/daemon/handlers/shared.ts +70 -5
  181. package/src/daemon/handlers/skills.ts +11 -18
  182. package/src/daemon/lifecycle.ts +220 -158
  183. package/src/daemon/message-types/conversations.ts +29 -6
  184. package/src/daemon/message-types/messages.ts +9 -2
  185. package/src/daemon/message-types/notifications.ts +12 -0
  186. package/src/daemon/message-types/schedules.ts +1 -0
  187. package/src/daemon/message-types/settings.ts +18 -0
  188. package/src/daemon/profiler-run-store.ts +557 -0
  189. package/src/daemon/server.ts +87 -10
  190. package/src/daemon/shutdown-handlers.ts +5 -0
  191. package/src/daemon/tool-side-effects.ts +23 -3
  192. package/src/daemon/transport-hints.ts +33 -0
  193. package/src/export/transcript-formatter.ts +148 -0
  194. package/src/filing/filing-service.ts +228 -0
  195. package/src/heartbeat/heartbeat-service.ts +96 -7
  196. package/src/index.ts +1 -1
  197. package/src/mcp/client.ts +6 -0
  198. package/src/mcp/mcp-oauth-provider.ts +149 -27
  199. package/src/memory/admin.ts +33 -32
  200. package/src/memory/app-store.ts +69 -0
  201. package/src/memory/conversation-bootstrap.ts +1 -1
  202. package/src/memory/conversation-crud.ts +151 -117
  203. package/src/memory/conversation-directories.ts +39 -0
  204. package/src/memory/conversation-group-migration.ts +66 -6
  205. package/src/memory/conversation-queries.ts +58 -12
  206. package/src/memory/conversation-title-service.ts +1 -0
  207. package/src/memory/db-init.ts +182 -376
  208. package/src/memory/embedding-local.ts +1 -1
  209. package/src/memory/graph/bootstrap.ts +75 -66
  210. package/src/memory/graph/capability-seed.ts +167 -17
  211. package/src/memory/graph/consolidation.ts +38 -4
  212. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  213. package/src/memory/graph/extraction-job.ts +9 -4
  214. package/src/memory/graph/extraction.ts +66 -23
  215. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  216. package/src/memory/graph/graph-search.ts +29 -15
  217. package/src/memory/graph/injection.ts +38 -8
  218. package/src/memory/graph/inspect.ts +12 -3
  219. package/src/memory/graph/retriever.ts +365 -262
  220. package/src/memory/graph/store.test.ts +48 -0
  221. package/src/memory/graph/store.ts +150 -11
  222. package/src/memory/graph/tool-handlers.ts +84 -209
  223. package/src/memory/graph/tools.ts +8 -52
  224. package/src/memory/graph/types.ts +24 -0
  225. package/src/memory/group-crud.ts +25 -9
  226. package/src/memory/job-handlers/cleanup.ts +44 -1
  227. package/src/memory/jobs-store.ts +70 -60
  228. package/src/memory/jobs-worker.ts +44 -28
  229. package/src/memory/llm-request-log-store.ts +96 -12
  230. package/src/memory/memory-recall-log-store.ts +49 -5
  231. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  232. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  233. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  234. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  235. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  236. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  237. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  238. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  239. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  240. package/src/memory/migrations/index.ts +8 -0
  241. package/src/memory/migrations/registry.ts +8 -0
  242. package/src/memory/schema/conversations.ts +14 -0
  243. package/src/memory/schema/infrastructure.ts +8 -1
  244. package/src/memory/schema/memory-core.ts +0 -51
  245. package/src/memory/schema/memory-graph.ts +15 -0
  246. package/src/memory/task-memory-cleanup.ts +30 -11
  247. package/src/messaging/provider.ts +1 -1
  248. package/src/notifications/broadcaster.ts +6 -0
  249. package/src/notifications/conversation-pairing.ts +12 -4
  250. package/src/notifications/copy-composer.ts +86 -0
  251. package/src/notifications/decision-engine.ts +35 -0
  252. package/src/notifications/emit-signal.ts +14 -0
  253. package/src/notifications/signal.ts +11 -0
  254. package/src/oauth/platform-connection.test.ts +2 -2
  255. package/src/oauth/seed-providers.ts +1 -0
  256. package/src/permissions/checker.ts +15 -4
  257. package/src/permissions/defaults.ts +7 -8
  258. package/src/permissions/permission-mode-store.ts +180 -0
  259. package/src/permissions/permission-mode.ts +31 -0
  260. package/src/permissions/prompter.ts +0 -2
  261. package/src/permissions/workspace-policy.ts +9 -0
  262. package/src/platform/client.ts +1 -1
  263. package/src/prompts/system-prompt.ts +59 -7
  264. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  265. package/src/prompts/templates/BOOTSTRAP.md +76 -162
  266. package/src/prompts/templates/HEARTBEAT.md +3 -1
  267. package/src/prompts/templates/SOUL.md +30 -9
  268. package/src/prompts/templates/UPDATES.md +8 -0
  269. package/src/providers/anthropic/client.ts +107 -219
  270. package/src/runtime/assistant-event-hub.ts +22 -0
  271. package/src/runtime/auth/route-policy.ts +23 -0
  272. package/src/runtime/auth/token-service.ts +8 -0
  273. package/src/runtime/http-server.ts +32 -2
  274. package/src/runtime/http-types.ts +12 -1
  275. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  276. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  277. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  278. package/src/runtime/routes/app-management-routes.ts +1 -11
  279. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  280. package/src/runtime/routes/archive-utils.ts +29 -0
  281. package/src/runtime/routes/avatar-routes.ts +2 -9
  282. package/src/runtime/routes/btw-routes.ts +14 -1
  283. package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
  284. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  285. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  286. package/src/runtime/routes/conversation-routes.ts +270 -44
  287. package/src/runtime/routes/group-routes.ts +22 -8
  288. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  289. package/src/runtime/routes/identity-routes.ts +53 -18
  290. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  291. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  292. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  293. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  294. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  295. package/src/runtime/routes/log-export-routes.ts +41 -278
  296. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  297. package/src/runtime/routes/migration-routes.ts +18 -7
  298. package/src/runtime/routes/profiler-routes.ts +350 -0
  299. package/src/runtime/routes/schedule-routes.ts +27 -12
  300. package/src/runtime/routes/settings-routes.ts +95 -8
  301. package/src/runtime/routes/subagents-routes.ts +28 -7
  302. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  303. package/src/runtime/routes/user-routes.ts +41 -0
  304. package/src/runtime/routes/workspace-routes.ts +0 -1
  305. package/src/schedule/schedule-store.ts +30 -0
  306. package/src/schedule/scheduler.ts +45 -18
  307. package/src/skills/catalog-install.ts +10 -2
  308. package/src/skills/inline-command-runner.ts +12 -14
  309. package/src/skills/managed-store.ts +2 -2
  310. package/src/skills/skill-memory.ts +1 -293
  311. package/src/subagent/index.ts +13 -3
  312. package/src/subagent/manager.ts +308 -29
  313. package/src/subagent/types.ts +68 -0
  314. package/src/tasks/task-runner.ts +4 -4
  315. package/src/tools/apps/executors.ts +29 -4
  316. package/src/tools/filesystem/list.ts +93 -0
  317. package/src/tools/permission-checker.ts +78 -18
  318. package/src/tools/registry.ts +4 -0
  319. package/src/tools/schedule/create.ts +3 -0
  320. package/src/tools/schedule/list.ts +1 -0
  321. package/src/tools/schedule/update.ts +6 -0
  322. package/src/tools/secret-detection-handler.ts +0 -1
  323. package/src/tools/shared/filesystem/errors.ts +5 -0
  324. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  325. package/src/tools/shared/filesystem/types.ts +17 -0
  326. package/src/tools/shared/shell-output.ts +31 -2
  327. package/src/tools/skills/sandbox-runner.ts +3 -6
  328. package/src/tools/subagent/abort.ts +12 -2
  329. package/src/tools/subagent/message.ts +9 -2
  330. package/src/tools/subagent/notify-parent.ts +79 -0
  331. package/src/tools/subagent/read.ts +29 -8
  332. package/src/tools/subagent/resolve.ts +21 -0
  333. package/src/tools/subagent/spawn.ts +2 -0
  334. package/src/tools/subagent/status.ts +11 -1
  335. package/src/tools/system/avatar-generator.ts +3 -3
  336. package/src/tools/system/register.ts +23 -0
  337. package/src/tools/system/set-permission-mode.ts +103 -0
  338. package/src/tools/terminal/parser.ts +30 -5
  339. package/src/tools/terminal/safe-env.ts +16 -1
  340. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  341. package/src/tools/terminal/sandbox.ts +4 -1
  342. package/src/tools/terminal/shell.ts +3 -5
  343. package/src/tools/tool-manifest.ts +6 -0
  344. package/src/tools/types.ts +2 -3
  345. package/src/util/logger.ts +1 -1
  346. package/src/util/platform.ts +50 -17
  347. package/src/watcher/provider-types.ts +1 -1
  348. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  349. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  350. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  351. package/src/workspace/migrations/029-seed-pkb.ts +85 -0
  352. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  353. package/src/workspace/migrations/registry.ts +6 -0
  354. package/src/workspace/top-level-renderer.ts +5 -9
  355. package/src/__tests__/cli-memory.test.ts +0 -377
  356. package/src/__tests__/clipboard.test.ts +0 -88
  357. package/src/cli/cli-memory.ts +0 -179
  358. package/src/util/clipboard.ts +0 -34
@@ -44,10 +44,7 @@ import {
44
44
  updateConversationTitle,
45
45
  updateMessageMetadata,
46
46
  } from "../memory/conversation-crud.js";
47
- import {
48
- rebuildConversationDiskViewFromDbState,
49
- syncMessageToDisk,
50
- } from "../memory/conversation-disk-view.js";
47
+ import { syncMessageToDisk } from "../memory/conversation-disk-view.js";
51
48
  import {
52
49
  isReplaceableTitle,
53
50
  queueGenerateConversationTitle,
@@ -91,30 +88,32 @@ import {
91
88
  classifyConversationError,
92
89
  isUserCancellation,
93
90
  } from "./conversation-error.js";
94
- import { consolidateAssistantMessages } from "./conversation-history.js";
95
91
  import { raceWithTimeout } from "./conversation-media-retry.js";
96
92
  import type { MessageQueue } from "./conversation-queue-manager.js";
97
93
  import type { QueueDrainReason } from "./conversation-queue-manager.js";
98
94
  import type {
99
95
  ActiveSurfaceContext,
100
96
  ChannelCapabilities,
101
- ChannelTurnContextParams,
102
97
  InboundActorContext,
103
98
  InjectionMode,
104
- InterfaceTurnContextParams,
105
99
  TrustContext,
106
100
  } from "./conversation-runtime-assembly.js";
107
101
  import {
108
102
  applyRuntimeInjections,
103
+ buildUnifiedTurnContextBlock,
104
+ findLastInjectedNowContent,
105
+ findLastInjectedPkbContent,
109
106
  inboundActorContextFromTrust,
110
107
  inboundActorContextFromTrustContext,
111
108
  readNowScratchpad,
112
- stripInjectedContext,
109
+ readPkbContext,
110
+ stripInjectionsForCompaction,
113
111
  } from "./conversation-runtime-assembly.js";
114
112
  import type { SkillProjectionCache } from "./conversation-skill-tools.js";
113
+ import { markSurfaceCompleted } from "./conversation-surfaces.js";
115
114
  import { resolveTrustClass } from "./conversation-tool-setup.js";
116
115
  import { recordUsage } from "./conversation-usage.js";
117
- import { buildTemporalContext } from "./date-context.js";
116
+ import { formatTurnTimestamp } from "./date-context.js";
118
117
  import { deepRepairHistory, repairHistory } from "./history-repair.js";
119
118
  import type {
120
119
  DynamicPageSurfaceData,
@@ -123,6 +122,7 @@ import type {
123
122
  SurfaceType,
124
123
  UsageStats,
125
124
  } from "./message-protocol.js";
125
+ import type { MemoryRecalled } from "./message-types/memory.js";
126
126
  import type { TraceEmitter } from "./trace-emitter.js";
127
127
 
128
128
  const log = getLogger("conversation-agent-loop");
@@ -440,6 +440,7 @@ export async function runAgentLoopImpl(
440
440
  surfaceId,
441
441
  summary: "Dismissed",
442
442
  });
443
+ markSurfaceCompleted(ctx, surfaceId, "Dismissed");
443
444
  ctx.pendingSurfaceActions.delete(surfaceId);
444
445
  }
445
446
  }
@@ -502,6 +503,7 @@ export async function runAgentLoopImpl(
502
503
  }
503
504
 
504
505
  const isFirstMessage = ctx.messages.length === 1;
506
+ let shouldInjectWorkspace = isFirstMessage;
505
507
 
506
508
  const compactCheck = ctx.contextWindowManager.shouldCompact(ctx.messages);
507
509
  if (compactCheck.needed) {
@@ -556,6 +558,7 @@ export async function runAgentLoopImpl(
556
558
  compacted.summaryCacheReadInputTokens ?? 0,
557
559
  collapseRawResponses(compacted.summaryRawResponses),
558
560
  );
561
+ shouldInjectWorkspace = true;
559
562
  }
560
563
 
561
564
  const state = createEventHandlerState();
@@ -599,8 +602,8 @@ export async function runAgentLoopImpl(
599
602
 
600
603
  let runMessages = ctx.messages;
601
604
 
602
- // Memory graph retrieval — dispatches to context-load / per-turn / refresh
603
- // based on conversation state.
605
+ // Memory graph retrieval — dispatches to context-load / per-turn based on
606
+ // conversation state.
604
607
  const isTrustedActor = resolveTrustClass(ctx.trustContext) === "guardian";
605
608
  if (isTrustedActor) {
606
609
  const graphResult = await ctx.graphMemory.prepareMemory(
@@ -627,26 +630,65 @@ export async function runAgentLoopImpl(
627
630
  }
628
631
  }
629
632
 
633
+ const m = graphResult.metrics;
634
+
630
635
  try {
631
636
  recordMemoryRecallLog({
632
637
  conversationId: ctx.conversationId,
633
638
  enabled: true,
634
639
  degraded: false,
635
- semanticHits: 0,
636
- mergedCount: 0,
637
- selectedCount: 0,
638
- tier1Count: 0,
639
- tier2Count: 0,
640
- hybridSearchLatencyMs: 0,
641
- sparseVectorUsed: false,
640
+ provider: m?.embeddingProvider ?? undefined,
641
+ model: m?.embeddingModel ?? undefined,
642
+ semanticHits: m?.semanticHits ?? 0,
643
+ mergedCount: m?.mergedCount ?? 0,
644
+ selectedCount: m?.selectedCount ?? 0,
645
+ tier1Count: m?.tier1Count ?? 0,
646
+ tier2Count: m?.tier2Count ?? 0,
647
+ hybridSearchLatencyMs: m?.hybridSearchLatencyMs ?? 0,
648
+ sparseVectorUsed: m?.sparseVectorUsed ?? false,
642
649
  injectedTokens: graphResult.injectedTokens,
643
650
  latencyMs: graphResult.latencyMs,
644
- topCandidatesJson: [],
651
+ topCandidatesJson: (m?.topCandidates ?? []).map((c) => ({
652
+ key: c.nodeId,
653
+ type: c.type,
654
+ kind: "graph",
655
+ finalScore: c.score,
656
+ semantic: c.semanticSimilarity,
657
+ recency: c.recencyBoost,
658
+ })),
659
+ injectedText: graphResult.injectedBlockText ?? undefined,
645
660
  reason: `graph:${graphResult.mode}`,
661
+ queryContext: m?.queryContext ?? undefined,
646
662
  });
647
663
  } catch (err) {
648
664
  log.warn({ err }, "Failed to persist memory recall log (non-fatal)");
649
665
  }
666
+
667
+ if (m) {
668
+ const memoryRecalledEvent: MemoryRecalled = {
669
+ type: "memory_recalled",
670
+ provider: m.embeddingProvider ?? "unknown",
671
+ model: m.embeddingModel ?? "unknown",
672
+ semanticHits: m.semanticHits,
673
+ mergedCount: m.mergedCount,
674
+ selectedCount: m.selectedCount,
675
+ tier1Count: m.tier1Count,
676
+ tier2Count: m.tier2Count,
677
+ hybridSearchLatencyMs: m.hybridSearchLatencyMs,
678
+ sparseVectorUsed: m.sparseVectorUsed,
679
+ injectedTokens: graphResult.injectedTokens,
680
+ latencyMs: graphResult.latencyMs,
681
+ topCandidates: m.topCandidates.map((c) => ({
682
+ key: c.nodeId,
683
+ type: c.type,
684
+ kind: "graph",
685
+ finalScore: c.score,
686
+ semantic: c.semanticSimilarity,
687
+ recency: c.recencyBoost,
688
+ })),
689
+ };
690
+ onEvent(memoryRecalledEvent);
691
+ }
650
692
  }
651
693
 
652
694
  // Build active surface context
@@ -678,37 +720,20 @@ export async function runAgentLoopImpl(
678
720
 
679
721
  ctx.refreshWorkspaceTopLevelContextIfNeeded();
680
722
 
681
- // Compute fresh temporal context each turn for date grounding.
723
+ // Compute fresh turn timestamp for date grounding.
682
724
  // Absolute "now" is always anchored to assistant host clock, while local
683
725
  // date semantics prefer configured user timezone, then recalled memory.
684
726
  const hostTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
685
727
  const configuredUserTimeZone = getConfig().ui.userTimezone ?? null;
686
728
  const recalledUserTimeZone = null;
687
- const temporalContext = buildTemporalContext({
729
+ const timestamp = formatTurnTimestamp({
688
730
  hostTimeZone,
689
731
  configuredUserTimeZone,
690
732
  userTimeZone: recalledUserTimeZone,
691
733
  });
692
734
 
693
- // Use the channel/interface context captured at the top of this function
694
- // so it reflects the channel/interface that originally sent *this* turn's
695
- // message, even if a newer message from a different channel arrived since.
696
- const channelTurnContext: ChannelTurnContextParams = {
697
- turnContext: capturedTurnChannelContext,
698
- conversationOriginChannel: getConversationOriginChannel(
699
- ctx.conversationId,
700
- ),
701
- };
702
-
703
- const interfaceTurnContext: InterfaceTurnContextParams = {
704
- turnContext: capturedTurnInterfaceContext,
705
- conversationOriginInterface: getConversationOriginInterface(
706
- ctx.conversationId,
707
- ),
708
- };
709
-
710
- // Resolve the inbound actor context for the model's <inbound_actor_context>
711
- // block. When the conversation carries enough identity info, use the unified
735
+ // Resolve the inbound actor context for the unified <turn_context> block.
736
+ // When the conversation carries enough identity info, use the unified
712
737
  // actor trust resolver so member status/policy and guardian binding details
713
738
  // are fresh for this turn. The conversation runtime context remains the source
714
739
  // for policy gating; this block is model-facing grounding metadata.
@@ -729,22 +754,61 @@ export async function runAgentLoopImpl(
729
754
  }
730
755
  }
731
756
 
757
+ // Build unified turn context block that replaces the separate temporal,
758
+ // channel, interface, and actor context blocks.
759
+ const interfaceName =
760
+ capturedTurnInterfaceContext.userMessageInterface ?? undefined;
761
+ const channelName =
762
+ capturedTurnChannelContext?.userMessageChannel ?? undefined;
763
+ const isGuardian =
764
+ resolvedInboundActorContext?.trustClass === "guardian" ||
765
+ !resolvedInboundActorContext;
766
+ const unifiedTurnContextStr = buildUnifiedTurnContextBlock(
767
+ isGuardian
768
+ ? { timestamp, interfaceName, channelName }
769
+ : {
770
+ timestamp,
771
+ interfaceName,
772
+ channelName,
773
+ actorContext: resolvedInboundActorContext,
774
+ },
775
+ );
776
+
732
777
  // The `remember` tool handles scratchpad-style memory writes directly to the graph.
733
778
 
734
779
  const isInteractiveResolved =
735
780
  options?.isInteractive ?? (!ctx.hasNoClient && !ctx.headlessLock);
736
781
 
782
+ // Only inject NOW.md if it changed since the last injection in the
783
+ // conversation. Keeping the previous injection in place avoids mutating
784
+ // historical user messages and preserves the cached prefix.
785
+ const currentNowContent = readNowScratchpad();
786
+ const lastInjectedNow = findLastInjectedNowContent(ctx.messages);
787
+ const nowScratchpad =
788
+ currentNowContent !== lastInjectedNow ? currentNowContent : null;
789
+
790
+ // Only inject PKB if it changed since the last injection in the
791
+ // conversation. Keeping the previous injection in place avoids mutating
792
+ // historical user messages and preserves the cached prefix.
793
+ // Note: injectPkbContext escapes </pkb> sequences before writing to history,
794
+ // so we must apply the same escaping before comparing to avoid false mismatches.
795
+ const currentPkbContent = readPkbContext();
796
+ const lastInjectedPkb = findLastInjectedPkbContent(ctx.messages);
797
+ const escapedCurrentPkb = currentPkbContent?.replace(/<\/pkb\s*>/gi, "&lt;/pkb&gt;") ?? null;
798
+ const pkbContext =
799
+ escapedCurrentPkb !== lastInjectedPkb ? currentPkbContent : null;
800
+
737
801
  // Shared injection options — reused whenever we need to re-inject after reduction.
738
802
  const injectionOpts = {
739
803
  activeSurface,
740
- workspaceTopLevelContext: ctx.workspaceTopLevelContext,
804
+ workspaceTopLevelContext: shouldInjectWorkspace
805
+ ? ctx.workspaceTopLevelContext
806
+ : null,
741
807
  channelCapabilities: ctx.channelCapabilities ?? null,
742
808
  channelCommandContext: ctx.commandIntent ?? null,
743
- channelTurnContext,
744
- interfaceTurnContext,
745
- inboundActorContext: resolvedInboundActorContext,
746
- temporalContext,
747
- nowScratchpad: readNowScratchpad(),
809
+ unifiedTurnContext: unifiedTurnContextStr,
810
+ pkbContext,
811
+ nowScratchpad,
748
812
  voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
749
813
  transportHints: ctx.transportHints ?? null,
750
814
  isNonInteractive: !isInteractiveResolved,
@@ -860,11 +924,20 @@ export async function runAgentLoopImpl(
860
924
  ctx.graphMemory.onCompacted(
861
925
  step.compactionResult.compactedPersistedMessages,
862
926
  );
927
+ shouldInjectWorkspace = true;
863
928
  }
864
929
 
865
- // Re-inject with potentially downgraded injection mode
930
+ // Re-inject with potentially downgraded injection mode.
931
+ // When compaction ran it strips existing NOW.md / PKB blocks, so we
932
+ // must re-inject the current content. Otherwise rely on the deduplicated
933
+ // value from injectionOpts to avoid duplicate injection.
866
934
  runMessages = applyRuntimeInjections(ctx.messages, {
867
935
  ...injectionOpts,
936
+ ...(step.compactionResult?.compacted && { pkbContext: currentPkbContent }),
937
+ ...(step.compactionResult?.compacted && { nowScratchpad: currentNowContent }),
938
+ workspaceTopLevelContext: shouldInjectWorkspace
939
+ ? ctx.workspaceTopLevelContext
940
+ : null,
868
941
  mode: currentInjectionMode,
869
942
  });
870
943
  if (isTrustedActor && currentInjectionMode !== "minimal") {
@@ -994,7 +1067,7 @@ export async function runAgentLoopImpl(
994
1067
 
995
1068
  // Strip injected context from updated history before compacting,
996
1069
  // so we compact the "raw" persistent messages.
997
- const rawHistory = stripInjectedContext(updatedHistory);
1070
+ const rawHistory = stripInjectionsForCompaction(updatedHistory);
998
1071
  ctx.messages = rawHistory;
999
1072
 
1000
1073
  ctx.emitActivityState(
@@ -1048,14 +1121,21 @@ export async function runAgentLoopImpl(
1048
1121
  midLoopCompact.summaryCacheReadInputTokens ?? 0,
1049
1122
  collapseRawResponses(midLoopCompact.summaryRawResponses),
1050
1123
  );
1051
- ctx.graphMemory.onCompacted(
1052
- midLoopCompact.compactedPersistedMessages,
1053
- );
1124
+ ctx.graphMemory.onCompacted(midLoopCompact.compactedPersistedMessages);
1125
+ shouldInjectWorkspace = true;
1054
1126
  }
1055
1127
 
1056
- // Re-inject runtime context and re-enter the agent loop
1128
+ // Re-inject runtime context and re-enter the agent loop.
1129
+ // stripInjectionsForCompaction() unconditionally removed the existing
1130
+ // NOW.md block from ctx.messages above, so we must always re-inject
1131
+ // the current content regardless of whether compaction actually ran.
1057
1132
  runMessages = applyRuntimeInjections(ctx.messages, {
1058
1133
  ...injectionOpts,
1134
+ pkbContext: currentPkbContent,
1135
+ nowScratchpad: currentNowContent,
1136
+ workspaceTopLevelContext: shouldInjectWorkspace
1137
+ ? ctx.workspaceTopLevelContext
1138
+ : null,
1059
1139
  mode: currentInjectionMode,
1060
1140
  });
1061
1141
  if (isTrustedActor && currentInjectionMode !== "minimal") {
@@ -1133,8 +1213,16 @@ export async function runAgentLoopImpl(
1133
1213
  // limit), incorporate those new messages into ctx.messages so the
1134
1214
  // convergence loop operates on the full (larger) history.
1135
1215
  if (state.contextTooLargeDetected) {
1216
+ // Track whether ctx.messages was actually stripped so we know if
1217
+ // NOW.md (and other injections) need to be re-injected. When the
1218
+ // provider rejects before adding any messages, the strip is skipped
1219
+ // and ctx.messages still contains the previous injection — blindly
1220
+ // re-injecting would duplicate the NOW.md block.
1221
+ let convergenceStripped = false;
1222
+
1136
1223
  if (updatedHistory.length > preRunHistoryLength) {
1137
- ctx.messages = stripInjectedContext(updatedHistory);
1224
+ ctx.messages = stripInjectionsForCompaction(updatedHistory);
1225
+ convergenceStripped = true;
1138
1226
  preRepairMessages = updatedHistory;
1139
1227
  preRunHistoryLength = updatedHistory.length;
1140
1228
  }
@@ -1254,10 +1342,19 @@ export async function runAgentLoopImpl(
1254
1342
  ctx.graphMemory.onCompacted(
1255
1343
  step.compactionResult.compactedPersistedMessages,
1256
1344
  );
1345
+ shouldInjectWorkspace = true;
1257
1346
  }
1258
1347
 
1348
+ // Only re-inject NOW.md when ctx.messages was actually stripped;
1349
+ // otherwise the existing NOW.md block is still present and
1350
+ // re-injecting would duplicate it.
1259
1351
  runMessages = applyRuntimeInjections(ctx.messages, {
1260
1352
  ...injectionOpts,
1353
+ pkbContext: currentPkbContent,
1354
+ nowScratchpad: convergenceStripped ? currentNowContent : null,
1355
+ workspaceTopLevelContext: shouldInjectWorkspace
1356
+ ? ctx.workspaceTopLevelContext
1357
+ : null,
1261
1358
  mode: currentInjectionMode,
1262
1359
  });
1263
1360
  if (isTrustedActor && currentInjectionMode !== "minimal") {
@@ -1295,7 +1392,8 @@ export async function runAgentLoopImpl(
1295
1392
  // tier operates on up-to-date history instead of stale
1296
1393
  // pre-rerun messages.
1297
1394
  if (updatedHistory.length > preRunHistoryLength) {
1298
- ctx.messages = stripInjectedContext(updatedHistory);
1395
+ ctx.messages = stripInjectionsForCompaction(updatedHistory);
1396
+ convergenceStripped = true;
1299
1397
  preRepairMessages = updatedHistory;
1300
1398
  preRunHistoryLength = updatedHistory.length;
1301
1399
  }
@@ -1368,14 +1466,23 @@ export async function runAgentLoopImpl(
1368
1466
  ctx.graphMemory.onCompacted(
1369
1467
  emergencyCompact.compactedPersistedMessages,
1370
1468
  );
1469
+ shouldInjectWorkspace = true;
1371
1470
  }
1372
1471
 
1472
+ // Only re-inject NOW.md when ctx.messages was actually stripped;
1473
+ // otherwise the existing block is still present.
1373
1474
  runMessages = applyRuntimeInjections(ctx.messages, {
1374
1475
  ...injectionOpts,
1476
+ pkbContext: currentPkbContent,
1477
+ nowScratchpad: convergenceStripped ? currentNowContent : null,
1478
+ workspaceTopLevelContext: shouldInjectWorkspace
1479
+ ? ctx.workspaceTopLevelContext
1480
+ : null,
1375
1481
  mode: currentInjectionMode,
1376
1482
  });
1377
1483
  if (isTrustedActor && currentInjectionMode !== "minimal") {
1378
- const memResult = ctx.graphMemory.reinjectCachedMemory(runMessages);
1484
+ const memResult =
1485
+ ctx.graphMemory.reinjectCachedMemory(runMessages);
1379
1486
  runMessages = memResult.runMessages;
1380
1487
  }
1381
1488
  preRepairMessages = runMessages;
@@ -1479,10 +1586,18 @@ export async function runAgentLoopImpl(
1479
1586
  ctx.graphMemory.onCompacted(
1480
1587
  emergencyCompact.compactedPersistedMessages,
1481
1588
  );
1589
+ shouldInjectWorkspace = true;
1482
1590
  }
1483
1591
 
1592
+ // Only re-inject NOW.md when ctx.messages was actually stripped;
1593
+ // otherwise the existing block is still present.
1484
1594
  runMessages = applyRuntimeInjections(ctx.messages, {
1485
1595
  ...injectionOpts,
1596
+ pkbContext: currentPkbContent,
1597
+ nowScratchpad: convergenceStripped ? currentNowContent : null,
1598
+ workspaceTopLevelContext: shouldInjectWorkspace
1599
+ ? ctx.workspaceTopLevelContext
1600
+ : null,
1486
1601
  mode: currentInjectionMode,
1487
1602
  });
1488
1603
  if (isTrustedActor && currentInjectionMode !== "minimal") {
@@ -1626,7 +1741,11 @@ export async function runAgentLoopImpl(
1626
1741
  { providerName: ctx.provider.name, toolTokenBudget },
1627
1742
  );
1628
1743
 
1629
- ctx.messages = stripInjectedContext(restoredHistory);
1744
+ // Persist injections in history: runtime-injected context stays on
1745
+ // historical user messages so the conversation prefix is stable for
1746
+ // Anthropic's prefix caching. Stripping only happens during
1747
+ // compaction/overflow recovery (where a cache miss is expected).
1748
+ ctx.messages = restoredHistory;
1630
1749
 
1631
1750
  emitUsage(
1632
1751
  ctx,
@@ -1877,15 +1996,10 @@ export async function runAgentLoopImpl(
1877
1996
  // Clear at turn end so they never leak into subsequent unrelated messages.
1878
1997
  ctx.commandIntent = undefined;
1879
1998
 
1880
- if (userMessageId) {
1881
- const didMutateHistory = consolidateAssistantMessages(
1882
- ctx.conversationId,
1883
- userMessageId,
1884
- );
1885
- if (didMutateHistory) {
1886
- rebuildConversationDiskViewFromDbState(ctx.conversationId);
1887
- }
1888
- }
1999
+ // Consolidation deferred to compaction: keeping assistant + tool_result
2000
+ // messages unconsolidated preserves the exact message structure sent to
2001
+ // the API, enabling stable prefix caching across turns. Compaction
2002
+ // consolidates when it summarizes old messages (cache miss is expected).
1889
2003
 
1890
2004
  ctx.drainQueue(yieldedForHandoff ? "checkpoint_handoff" : "loop_complete");
1891
2005
 
@@ -71,7 +71,6 @@ export async function approveHostAttachmentRead(
71
71
  await generateAllowlistOptions(toolName, input),
72
72
  generateScopeOptions(workingDir, toolName),
73
73
  undefined,
74
- undefined,
75
74
  conversationId,
76
75
  "host",
77
76
  );
@@ -68,14 +68,13 @@ export function findLastUndoableUserMessageIndex(messages: Message[]): number {
68
68
  // ── Qdrant Vector Cleanup ────────────────────────────────────────────
69
69
 
70
70
  /**
71
- * Delete Qdrant vector entries for the given segment and item IDs.
71
+ * Delete Qdrant vector entries for the given segment IDs.
72
72
  * Individual deletion failures are logged and enqueued as retry jobs
73
73
  * to prevent silently orphaned vectors.
74
74
  */
75
75
  export async function cleanupQdrantVectors(
76
76
  conversationId: string,
77
77
  segmentIds: string[],
78
- orphanedItemIds: string[],
79
78
  ): Promise<void> {
80
79
  let qdrant: ReturnType<typeof getQdrantClient>;
81
80
  try {
@@ -84,15 +83,12 @@ export async function cleanupQdrantVectors(
84
83
  return; // Qdrant not initialized — nothing to clean up.
85
84
  }
86
85
 
87
- if (segmentIds.length === 0 && orphanedItemIds.length === 0) return;
86
+ if (segmentIds.length === 0) return;
88
87
 
89
88
  const targets: Array<{ targetType: string; targetId: string }> = [];
90
89
  for (const segId of segmentIds) {
91
90
  targets.push({ targetType: "segment", targetId: segId });
92
91
  }
93
- for (const itemId of orphanedItemIds) {
94
- targets.push({ targetType: "item", targetId: itemId });
95
- }
96
92
 
97
93
  const results = await Promise.allSettled(
98
94
  targets.map((t) =>
@@ -124,7 +120,6 @@ export async function cleanupQdrantVectors(
124
120
  succeeded,
125
121
  failed,
126
122
  segments: segmentIds.length,
127
- items: orphanedItemIds.length,
128
123
  },
129
124
  "Cleaned up Qdrant vectors after regenerate",
130
125
  );
@@ -187,20 +182,17 @@ export function consolidateAssistantMessages(
187
182
  // Still delete internal tool_result messages even if only one assistant message,
188
183
  // and collect IDs for vector cleanup
189
184
  const allSegmentIds: string[] = [];
190
- const allOrphanedItemIds: string[] = [];
191
185
  for (const id of messagesToDelete) {
192
186
  const deleted = deleteMessageById(id);
193
187
  didMutate = true;
194
188
  allSegmentIds.push(...deleted.segmentIds);
195
- allOrphanedItemIds.push(...deleted.orphanedItemIds);
196
189
  }
197
190
 
198
191
  // Clean up Qdrant vectors (fire-and-forget)
199
- if (allSegmentIds.length > 0 || allOrphanedItemIds.length > 0) {
192
+ if (allSegmentIds.length > 0) {
200
193
  cleanupQdrantVectors(
201
194
  conversationId,
202
195
  allSegmentIds,
203
- allOrphanedItemIds,
204
196
  ).catch((err) => {
205
197
  log.warn(
206
198
  { err, conversationId },
@@ -322,24 +314,20 @@ export function consolidateAssistantMessages(
322
314
  // Delete the other assistant messages and internal tool_result messages,
323
315
  // and collect IDs for vector cleanup
324
316
  const allSegmentIds: string[] = [];
325
- const allOrphanedItemIds: string[] = [];
326
317
  for (let i = 1; i < messagesToConsolidate.length; i++) {
327
318
  const deleted = deleteMessageById(messagesToConsolidate[i].id);
328
319
  allSegmentIds.push(...deleted.segmentIds);
329
- allOrphanedItemIds.push(...deleted.orphanedItemIds);
330
320
  }
331
321
  for (const id of messagesToDelete) {
332
322
  const deleted = deleteMessageById(id);
333
323
  allSegmentIds.push(...deleted.segmentIds);
334
- allOrphanedItemIds.push(...deleted.orphanedItemIds);
335
324
  }
336
325
 
337
326
  // Clean up Qdrant vectors (fire-and-forget)
338
- if (allSegmentIds.length > 0 || allOrphanedItemIds.length > 0) {
327
+ if (allSegmentIds.length > 0) {
339
328
  cleanupQdrantVectors(
340
329
  conversationId,
341
330
  allSegmentIds,
342
- allOrphanedItemIds,
343
331
  ).catch((err) => {
344
332
  log.warn(
345
333
  { err, conversationId },
@@ -539,18 +527,15 @@ export async function regenerate(
539
527
 
540
528
  // Delete each message via deleteMessageById and collect IDs for Qdrant cleanup.
541
529
  const allSegmentIds: string[] = [];
542
- const allOrphanedItemIds: string[] = [];
543
530
  for (const msg of messagesToDelete) {
544
531
  const deleted = deleteMessageById(msg.id);
545
532
  allSegmentIds.push(...deleted.segmentIds);
546
- allOrphanedItemIds.push(...deleted.orphanedItemIds);
547
533
  }
548
534
 
549
535
  // Clean up Qdrant vectors (fire-and-forget).
550
536
  cleanupQdrantVectors(
551
537
  conversation.conversationId,
552
538
  allSegmentIds,
553
- allOrphanedItemIds,
554
539
  ).catch((err) => {
555
540
  log.warn(
556
541
  { err, conversationId: conversation.conversationId },
@@ -9,7 +9,6 @@ import type { EventBus } from "../events/bus.js";
9
9
  import type { AssistantDomainEvents } from "../events/domain-events.js";
10
10
  import type { ToolProfiler } from "../events/tool-profiling-listener.js";
11
11
  import { getHookManager } from "../hooks/manager.js";
12
- import { getMemoryCheckpoint } from "../memory/checkpoints.js";
13
12
  import {
14
13
  getConversation,
15
14
  getMessages,
@@ -151,6 +150,10 @@ export interface DisposeContext extends AbortContext {
151
150
  lastSurfaceAction: Map<string, unknown>;
152
151
  workspaceTopLevelContext: string | null;
153
152
  trustContext?: { trustClass: TrustClass };
153
+ /** Active memory node IDs snapshotted from the conversation's InContextTracker before disposal. */
154
+ activeContextNodeIds?: string[];
155
+ /** Memory scope for extraction — defaults to "default" if omitted. */
156
+ memoryScopeId?: string;
154
157
  abort(): void;
155
158
  }
156
159
 
@@ -289,19 +292,6 @@ export function disposeConversation(ctx: DisposeContext): void {
289
292
  conversationId: ctx.conversationId,
290
293
  });
291
294
 
292
- // Trigger batch extraction for any remaining unextracted messages
293
- try {
294
- const pendingKey = `batch_extract:${ctx.conversationId}:pending_count`;
295
- const pending = getMemoryCheckpoint(pendingKey);
296
- if (pending && parseInt(pending, 10) > 0) {
297
- enqueueMemoryJob("batch_extract", {
298
- conversationId: ctx.conversationId,
299
- });
300
- }
301
- } catch {
302
- // Best-effort — don't block conversation disposal
303
- }
304
-
305
295
  // Trigger graph extraction for end-of-conversation sweep.
306
296
  // Only extract from guardian conversations to preserve the memory trust
307
297
  // boundary — untrusted content must not influence future memory retrieval.
@@ -309,6 +299,10 @@ export function disposeConversation(ctx: DisposeContext): void {
309
299
  try {
310
300
  enqueueMemoryJob("graph_extract", {
311
301
  conversationId: ctx.conversationId,
302
+ scopeId: ctx.memoryScopeId ?? "default",
303
+ ...(ctx.activeContextNodeIds?.length
304
+ ? { activeContextNodeIds: ctx.activeContextNodeIds }
305
+ : {}),
312
306
  });
313
307
  } catch {
314
308
  // Best-effort — don't block conversation disposal
@@ -41,6 +41,7 @@ import type {
41
41
  ServerMessage,
42
42
  UserMessageAttachment,
43
43
  } from "./message-protocol.js";
44
+ import type { ConversationTransportMetadata } from "./message-types/conversations.js";
44
45
 
45
46
  const log = getLogger("conversation-messaging");
46
47
 
@@ -222,6 +223,7 @@ export function enqueueMessage(
222
223
  metadata?: Record<string, unknown>,
223
224
  options?: { isInteractive?: boolean },
224
225
  displayContent?: string,
226
+ transport?: ConversationTransportMetadata,
225
227
  ): { queued: boolean; requestId: string; rejected?: boolean } {
226
228
  if (!ctx.processing) {
227
229
  return { queued: false, requestId };
@@ -246,6 +248,7 @@ export function enqueueMessage(
246
248
  turnChannelContext,
247
249
  turnInterfaceContext,
248
250
  isInteractive: options?.isInteractive,
251
+ transport,
249
252
  displayContent,
250
253
  sentAt: Date.now(),
251
254
  });