@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
@@ -37,15 +37,15 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
37
37
 
38
38
  test("claims embed jobs when circuit breaker is closed (healthy)", () => {
39
39
  enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
40
- enqueueMemoryJob("embed_item", { itemId: "item-1" });
41
- enqueueMemoryJob("extract_items", { conversationId: "conv-1" });
40
+ enqueueMemoryJob("embed_graph_node", { nodeId: "node-1" });
41
+ enqueueMemoryJob("graph_extract", { conversationId: "conv-1" });
42
42
 
43
43
  const claimed = claimMemoryJobs(10);
44
44
  const types = claimed.map((j) => j.type);
45
45
 
46
46
  expect(types).toContain("embed_segment");
47
- expect(types).toContain("embed_item");
48
- expect(types).toContain("extract_items");
47
+ expect(types).toContain("embed_graph_node");
48
+ expect(types).toContain("graph_extract");
49
49
  expect(claimed).toHaveLength(3);
50
50
  });
51
51
 
@@ -62,9 +62,9 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
62
62
  }
63
63
 
64
64
  enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
65
- enqueueMemoryJob("embed_item", { itemId: "item-1" });
65
+ enqueueMemoryJob("embed_graph_node", { nodeId: "node-1" });
66
66
  enqueueMemoryJob("embed_summary", { summaryId: "sum-1" });
67
- enqueueMemoryJob("extract_items", { conversationId: "conv-1" });
67
+ enqueueMemoryJob("graph_extract", { conversationId: "conv-1" });
68
68
  enqueueMemoryJob("build_conversation_summary", {
69
69
  conversationId: "conv-1",
70
70
  });
@@ -73,10 +73,10 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
73
73
  const types = claimed.map((j) => j.type);
74
74
 
75
75
  // Only non-embed jobs should be claimed
76
- expect(types).toContain("extract_items");
76
+ expect(types).toContain("graph_extract");
77
77
  expect(types).toContain("build_conversation_summary");
78
78
  expect(types).not.toContain("embed_segment");
79
- expect(types).not.toContain("embed_item");
79
+ expect(types).not.toContain("embed_graph_node");
80
80
  expect(types).not.toContain("embed_summary");
81
81
  expect(claimed).toHaveLength(2);
82
82
  });
@@ -95,7 +95,7 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
95
95
 
96
96
  // Verify embed jobs are skipped while open
97
97
  enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
98
- enqueueMemoryJob("extract_items", { conversationId: "conv-1" });
98
+ enqueueMemoryJob("graph_extract", { conversationId: "conv-1" });
99
99
 
100
100
  const claimedWhileOpen = claimMemoryJobs(10);
101
101
  expect(claimedWhileOpen.map((j) => j.type)).not.toContain("embed_segment");
@@ -104,21 +104,22 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
104
104
  _resetQdrantBreaker();
105
105
 
106
106
  // Re-enqueue an embed job (the previous one is now "running")
107
- enqueueMemoryJob("embed_item", { itemId: "item-2" });
107
+ enqueueMemoryJob("embed_graph_node", { nodeId: "node-2" });
108
108
 
109
109
  const claimedAfterClose = claimMemoryJobs(10);
110
110
  const types = claimedAfterClose.map((j) => j.type);
111
111
 
112
- expect(types).toContain("embed_item");
112
+ expect(types).toContain("embed_graph_node");
113
113
  });
114
114
 
115
115
  test("all embed job types are skipped when breaker is open", async () => {
116
116
  const embedTypes: MemoryJobType[] = [
117
117
  "embed_segment",
118
- "embed_item",
119
118
  "embed_summary",
120
119
  "embed_media",
121
120
  "embed_attachment",
121
+ "embed_graph_node",
122
+ "graph_trigger_embed",
122
123
  ];
123
124
 
124
125
  // Trip the circuit breaker
@@ -137,13 +138,13 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
137
138
  enqueueMemoryJob(type, { id: `test-${type}` });
138
139
  }
139
140
  // Also enqueue a non-embed job
140
- enqueueMemoryJob("extract_entities", { conversationId: "conv-1" });
141
+ enqueueMemoryJob("graph_consolidate", { conversationId: "conv-1" });
141
142
 
142
143
  const claimed = claimMemoryJobs(20);
143
144
  const types = claimed.map((j) => j.type);
144
145
 
145
146
  // Only the non-embed job should be claimed
146
147
  expect(claimed).toHaveLength(1);
147
- expect(types).toEqual(["extract_entities"]);
148
+ expect(types).toEqual(["graph_consolidate"]);
148
149
  });
149
150
  });
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Tests for handleListMessages tool_result merging.
3
+ *
4
+ * Verifies that tool_result blocks from user messages are merged into the
5
+ * preceding assistant message so they render with proper tool names instead
6
+ * of "Unknown" after a conversation reload.
7
+ */
8
+
9
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
10
+
11
+ mock.module("../util/logger.js", () => ({
12
+ getLogger: () =>
13
+ new Proxy({} as Record<string, unknown>, {
14
+ get: () => () => {},
15
+ }),
16
+ }));
17
+
18
+ mock.module("../config/loader.js", () => ({
19
+ getConfig: () => ({
20
+ ui: {},
21
+ model: "test",
22
+ provider: "test",
23
+ memory: { enabled: false },
24
+ rateLimit: { maxRequestsPerMinute: 0 },
25
+ }),
26
+ }));
27
+
28
+ import { addMessage, createConversation } from "../memory/conversation-crud.js";
29
+ import { getDb, initializeDb } from "../memory/db.js";
30
+ import { handleListMessages } from "../runtime/routes/conversation-routes.js";
31
+
32
+ initializeDb();
33
+
34
+ function resetTables() {
35
+ const db = getDb();
36
+ db.run("DELETE FROM message_attachments");
37
+ db.run("DELETE FROM attachments");
38
+ db.run("DELETE FROM messages");
39
+ db.run("DELETE FROM conversations");
40
+ }
41
+
42
+ function createTestUrl(conversationId: string): URL {
43
+ return new URL(
44
+ `http://localhost/v1/messages?conversationId=${conversationId}`,
45
+ );
46
+ }
47
+
48
+ interface ToolCallPayload {
49
+ name: string;
50
+ input: Record<string, unknown>;
51
+ result?: string;
52
+ isError?: boolean;
53
+ }
54
+
55
+ interface MessagePayload {
56
+ role: string;
57
+ content: string;
58
+ toolCalls?: ToolCallPayload[];
59
+ textSegments?: string[];
60
+ }
61
+
62
+ describe("handleListMessages tool_result merging", () => {
63
+ beforeEach(resetTables);
64
+
65
+ test("merges tool_result from user message into preceding assistant", async () => {
66
+ const conv = createConversation();
67
+ // User prompt
68
+ await addMessage(
69
+ conv.id,
70
+ "user",
71
+ JSON.stringify([{ type: "text", text: "run ls" }]),
72
+ );
73
+ // Assistant with tool_use
74
+ await addMessage(
75
+ conv.id,
76
+ "assistant",
77
+ JSON.stringify([
78
+ { type: "text", text: "Running command." },
79
+ { type: "tool_use", id: "tu1", name: "bash", input: { command: "ls" } },
80
+ ]),
81
+ );
82
+ // Tool result (separate user message)
83
+ await addMessage(
84
+ conv.id,
85
+ "user",
86
+ JSON.stringify([
87
+ { type: "tool_result", tool_use_id: "tu1", content: "file1.txt\nfile2.txt" },
88
+ ]),
89
+ );
90
+
91
+ const response = handleListMessages(createTestUrl(conv.id), null);
92
+ const body = (await response.json()) as { messages: MessagePayload[] };
93
+
94
+ // Should be 2 messages: user prompt + assistant (tool_result user msg suppressed)
95
+ expect(body.messages).toHaveLength(2);
96
+ expect(body.messages[0].role).toBe("user");
97
+ expect(body.messages[1].role).toBe("assistant");
98
+
99
+ // Assistant tool call should have proper name AND result
100
+ const toolCalls = body.messages[1].toolCalls;
101
+ expect(toolCalls).toBeDefined();
102
+ expect(toolCalls).toHaveLength(1);
103
+ expect(toolCalls![0].name).toBe("bash");
104
+ expect(toolCalls![0].result).toBe("file1.txt\nfile2.txt");
105
+ });
106
+
107
+ test("merges multiple tool_results into matching tool_uses", async () => {
108
+ const conv = createConversation();
109
+ await addMessage(
110
+ conv.id,
111
+ "user",
112
+ JSON.stringify([{ type: "text", text: "do stuff" }]),
113
+ );
114
+ await addMessage(
115
+ conv.id,
116
+ "assistant",
117
+ JSON.stringify([
118
+ { type: "tool_use", id: "tu1", name: "bash", input: { command: "ls" } },
119
+ { type: "text", text: "and also" },
120
+ { type: "tool_use", id: "tu2", name: "file_read", input: { path: "/tmp/a" } },
121
+ ]),
122
+ );
123
+ await addMessage(
124
+ conv.id,
125
+ "user",
126
+ JSON.stringify([
127
+ { type: "tool_result", tool_use_id: "tu1", content: "dir listing" },
128
+ { type: "tool_result", tool_use_id: "tu2", content: "file contents" },
129
+ ]),
130
+ );
131
+
132
+ const response = handleListMessages(createTestUrl(conv.id), null);
133
+ const body = (await response.json()) as { messages: MessagePayload[] };
134
+
135
+ expect(body.messages).toHaveLength(2);
136
+ const toolCalls = body.messages[1].toolCalls!;
137
+ expect(toolCalls).toHaveLength(2);
138
+ expect(toolCalls[0].name).toBe("bash");
139
+ expect(toolCalls[0].result).toBe("dir listing");
140
+ expect(toolCalls[1].name).toBe("file_read");
141
+ expect(toolCalls[1].result).toBe("file contents");
142
+ });
143
+
144
+ test("plain user message passes through unchanged", async () => {
145
+ const conv = createConversation();
146
+ await addMessage(
147
+ conv.id,
148
+ "user",
149
+ JSON.stringify([{ type: "text", text: "hello" }]),
150
+ );
151
+ await addMessage(
152
+ conv.id,
153
+ "assistant",
154
+ JSON.stringify([{ type: "text", text: "hi there" }]),
155
+ );
156
+ await addMessage(
157
+ conv.id,
158
+ "user",
159
+ JSON.stringify([{ type: "text", text: "how are you?" }]),
160
+ );
161
+
162
+ const response = handleListMessages(createTestUrl(conv.id), null);
163
+ const body = (await response.json()) as { messages: MessagePayload[] };
164
+
165
+ expect(body.messages).toHaveLength(3);
166
+ expect(body.messages[2].role).toBe("user");
167
+ expect(body.messages[2].content).toBe("how are you?");
168
+ });
169
+
170
+ test("tool_result at start of array (no preceding assistant) is preserved", async () => {
171
+ const conv = createConversation();
172
+ // Orphan tool_result with no preceding assistant (pagination boundary).
173
+ // The preceding assistant tool_use lives in the previous page — dropping
174
+ // the result would be unrecoverable, so it is kept as-is.
175
+ await addMessage(
176
+ conv.id,
177
+ "user",
178
+ JSON.stringify([
179
+ { type: "tool_result", tool_use_id: "tu_orphan", content: "stale result" },
180
+ ]),
181
+ );
182
+ await addMessage(
183
+ conv.id,
184
+ "assistant",
185
+ JSON.stringify([{ type: "text", text: "response" }]),
186
+ );
187
+
188
+ const response = handleListMessages(createTestUrl(conv.id), null);
189
+ const body = (await response.json()) as { messages: MessagePayload[] };
190
+
191
+ // Orphan tool_result is preserved (not suppressed) to avoid data loss
192
+ expect(body.messages).toHaveLength(2);
193
+ expect(body.messages[0].role).toBe("user");
194
+ // The preserved message must retain the actual tool_result payload
195
+ const orphanToolCalls = body.messages[0].toolCalls;
196
+ expect(orphanToolCalls).toBeDefined();
197
+ expect(orphanToolCalls).toHaveLength(1);
198
+ expect(orphanToolCalls![0].result).toBe("stale result");
199
+ expect(body.messages[1].role).toBe("assistant");
200
+ expect(body.messages[1].content).toBe("response");
201
+ });
202
+
203
+ test("multi-turn: each tool_result merges into correct assistant", async () => {
204
+ const conv = createConversation();
205
+ // Turn 1
206
+ await addMessage(
207
+ conv.id,
208
+ "user",
209
+ JSON.stringify([{ type: "text", text: "list files" }]),
210
+ );
211
+ await addMessage(
212
+ conv.id,
213
+ "assistant",
214
+ JSON.stringify([
215
+ { type: "tool_use", id: "tu1", name: "bash", input: { command: "ls" } },
216
+ ]),
217
+ );
218
+ await addMessage(
219
+ conv.id,
220
+ "user",
221
+ JSON.stringify([
222
+ { type: "tool_result", tool_use_id: "tu1", content: "files" },
223
+ ]),
224
+ );
225
+ // Turn 2
226
+ await addMessage(
227
+ conv.id,
228
+ "assistant",
229
+ JSON.stringify([
230
+ { type: "text", text: "Now reading:" },
231
+ { type: "tool_use", id: "tu2", name: "file_read", input: { path: "/x" } },
232
+ ]),
233
+ );
234
+ await addMessage(
235
+ conv.id,
236
+ "user",
237
+ JSON.stringify([
238
+ { type: "tool_result", tool_use_id: "tu2", content: "file data" },
239
+ ]),
240
+ );
241
+ // Turn 3: real user message
242
+ await addMessage(
243
+ conv.id,
244
+ "user",
245
+ JSON.stringify([{ type: "text", text: "thanks" }]),
246
+ );
247
+
248
+ const response = handleListMessages(createTestUrl(conv.id), null);
249
+ const body = (await response.json()) as { messages: MessagePayload[] };
250
+
251
+ // user("list files"), assistant(bash), assistant(file_read), user("thanks")
252
+ expect(body.messages).toHaveLength(4);
253
+ expect(body.messages[0].role).toBe("user");
254
+ expect(body.messages[1].role).toBe("assistant");
255
+ expect(body.messages[1].toolCalls![0].name).toBe("bash");
256
+ expect(body.messages[1].toolCalls![0].result).toBe("files");
257
+ expect(body.messages[2].role).toBe("assistant");
258
+ expect(body.messages[2].toolCalls![0].name).toBe("file_read");
259
+ expect(body.messages[2].toolCalls![0].result).toBe("file data");
260
+ expect(body.messages[3].role).toBe("user");
261
+ expect(body.messages[3].content).toBe("thanks");
262
+ });
263
+
264
+ test("tool_result with is_error propagates error status", async () => {
265
+ const conv = createConversation();
266
+ await addMessage(
267
+ conv.id,
268
+ "user",
269
+ JSON.stringify([{ type: "text", text: "do it" }]),
270
+ );
271
+ await addMessage(
272
+ conv.id,
273
+ "assistant",
274
+ JSON.stringify([
275
+ { type: "tool_use", id: "tu1", name: "bash", input: { command: "fail" } },
276
+ ]),
277
+ );
278
+ await addMessage(
279
+ conv.id,
280
+ "user",
281
+ JSON.stringify([
282
+ {
283
+ type: "tool_result",
284
+ tool_use_id: "tu1",
285
+ content: "command not found",
286
+ is_error: true,
287
+ },
288
+ ]),
289
+ );
290
+
291
+ const response = handleListMessages(createTestUrl(conv.id), null);
292
+ const body = (await response.json()) as { messages: MessagePayload[] };
293
+
294
+ expect(body.messages).toHaveLength(2);
295
+ const tc = body.messages[1].toolCalls![0];
296
+ expect(tc.name).toBe("bash");
297
+ expect(tc.result).toBe("command not found");
298
+ expect(tc.isError).toBe(true);
299
+ });
300
+ });
@@ -278,12 +278,6 @@ describe("normalizeLlmContextPayloads", () => {
278
278
  role: "user",
279
279
  text: "Find the latest changelog.",
280
280
  },
281
- {
282
- kind: "message",
283
- label: "Assistant message 2",
284
- role: "assistant",
285
- text: "Checking sources.",
286
- },
287
281
  {
288
282
  kind: "reasoning",
289
283
  label: "Assistant message 2 reasoning",
@@ -296,6 +290,12 @@ describe("normalizeLlmContextPayloads", () => {
296
290
  role: "assistant",
297
291
  text: "[redacted thinking]",
298
292
  },
293
+ {
294
+ kind: "message",
295
+ label: "Assistant message 2",
296
+ role: "assistant",
297
+ text: "Checking sources.",
298
+ },
299
299
  {
300
300
  kind: "tool_use",
301
301
  label: "Assistant message 2 tool use",
@@ -331,12 +331,6 @@ describe("normalizeLlmContextPayloads", () => {
331
331
  },
332
332
  ]);
333
333
  expect(normalized.responseSections).toEqual([
334
- {
335
- kind: "message",
336
- label: "Assistant response",
337
- role: "assistant",
338
- text: "I found the changelog.",
339
- },
340
334
  {
341
335
  kind: "reasoning",
342
336
  label: "Assistant response reasoning",
@@ -349,6 +343,12 @@ describe("normalizeLlmContextPayloads", () => {
349
343
  role: "assistant",
350
344
  text: "[redacted thinking]",
351
345
  },
346
+ {
347
+ kind: "message",
348
+ label: "Assistant response",
349
+ role: "assistant",
350
+ text: "I found the changelog.",
351
+ },
352
352
  {
353
353
  kind: "tool_use",
354
354
  label: "Assistant response tool use",
@@ -412,12 +412,6 @@ describe("normalizeLlmContextPayloads", () => {
412
412
  toolCallNames: undefined,
413
413
  });
414
414
  expect(normalized.responseSections).toEqual([
415
- {
416
- kind: "message",
417
- label: "Assistant response",
418
- role: "assistant",
419
- text: "The answer is 42.",
420
- },
421
415
  {
422
416
  kind: "reasoning",
423
417
  label: "Assistant response reasoning",
@@ -430,6 +424,12 @@ describe("normalizeLlmContextPayloads", () => {
430
424
  role: "assistant",
431
425
  text: "[redacted thinking]",
432
426
  },
427
+ {
428
+ kind: "message",
429
+ label: "Assistant response",
430
+ role: "assistant",
431
+ text: "The answer is 42.",
432
+ },
433
433
  ]);
434
434
  });
435
435
 
@@ -33,6 +33,27 @@ function dispatchLlmContext(messageId: string): Promise<Response> | Response {
33
33
  });
34
34
  }
35
35
 
36
+ function dispatchLogPayload(logId: string): Promise<Response> | Response {
37
+ const url = new URL(
38
+ `http://localhost/v1/llm-request-logs/${logId}/payload`,
39
+ );
40
+ const route = routes.find(
41
+ (r) =>
42
+ r.method === "GET" && r.endpoint === "llm-request-logs/:id/payload",
43
+ );
44
+ if (!route) {
45
+ throw new Error("No llm-request-logs payload route found");
46
+ }
47
+
48
+ return route.handler({
49
+ req: new Request(url.toString(), { method: "GET" }),
50
+ url,
51
+ server: null as never,
52
+ authContext: {} as never,
53
+ params: { id: logId },
54
+ });
55
+ }
56
+
36
57
  function clearRequestLogs(): void {
37
58
  getDb().delete(llmRequestLogs).run();
38
59
  }
@@ -189,4 +210,84 @@ describe("GET /v1/messages/:id/llm-context provider preference", () => {
189
210
  expect(body.logs).toHaveLength(1);
190
211
  expect(body.logs[0]?.summary).toEqual({ provider: "ollama" });
191
212
  });
213
+
214
+ test("returns null payloads to keep the initial response lightweight", async () => {
215
+ seedRequestLog({
216
+ id: "log-null-payload",
217
+ messageId: "msg-null-payload",
218
+ provider: "openrouter",
219
+ requestPayload: openAiRequestPayload,
220
+ responsePayload: openAiResponsePayload,
221
+ });
222
+
223
+ const response = await dispatchLlmContext("msg-null-payload");
224
+ expect(response.status).toBe(200);
225
+
226
+ const body = (await response.json()) as {
227
+ logs: Array<{
228
+ requestPayload: unknown;
229
+ responsePayload: unknown;
230
+ }>;
231
+ };
232
+
233
+ expect(body.logs).toHaveLength(1);
234
+ expect(body.logs[0]?.requestPayload).toBeNull();
235
+ expect(body.logs[0]?.responsePayload).toBeNull();
236
+ });
237
+ });
238
+
239
+ describe("GET /v1/llm-request-logs/:id/payload", () => {
240
+ test("returns parsed payloads for a valid log", async () => {
241
+ const reqPayload = JSON.stringify({ model: "gpt-4.1", messages: [] });
242
+ const resPayload = JSON.stringify({
243
+ choices: [{ message: { content: "hi" } }],
244
+ });
245
+ seedRequestLog({
246
+ id: "log-payload-ok",
247
+ messageId: "msg-payload-ok",
248
+ provider: "openai",
249
+ requestPayload: reqPayload,
250
+ responsePayload: resPayload,
251
+ });
252
+
253
+ const response = await dispatchLogPayload("log-payload-ok");
254
+ expect(response.status).toBe(200);
255
+
256
+ const body = (await response.json()) as {
257
+ id: string;
258
+ requestPayload: unknown;
259
+ responsePayload: unknown;
260
+ };
261
+
262
+ expect(body.id).toBe("log-payload-ok");
263
+ expect(body.requestPayload).toEqual(JSON.parse(reqPayload));
264
+ expect(body.responsePayload).toEqual(JSON.parse(resPayload));
265
+ });
266
+
267
+ test("returns 404 for a nonexistent log", async () => {
268
+ const response = await dispatchLogPayload("does-not-exist");
269
+ expect(response.status).toBe(404);
270
+ });
271
+
272
+ test("falls back to string values for non-JSON payloads", async () => {
273
+ seedRequestLog({
274
+ id: "log-raw-strings",
275
+ messageId: "msg-raw-strings",
276
+ provider: null,
277
+ requestPayload: "raw-request-text",
278
+ responsePayload: "raw-response-text",
279
+ });
280
+
281
+ const response = await dispatchLogPayload("log-raw-strings");
282
+ expect(response.status).toBe(200);
283
+
284
+ const body = (await response.json()) as {
285
+ id: string;
286
+ requestPayload: unknown;
287
+ responsePayload: unknown;
288
+ };
289
+
290
+ expect(body.requestPayload).toBe("raw-request-text");
291
+ expect(body.responsePayload).toBe("raw-response-text");
292
+ });
192
293
  });