@vellumai/assistant 0.5.16 → 0.6.1

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 (592) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +69 -16
  3. package/Dockerfile +2 -5
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +32 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/knip.json +2 -1
  9. package/openapi.yaml +1198 -83
  10. package/package.json +5 -1
  11. package/src/__tests__/actor-token-service.test.ts +68 -0
  12. package/src/__tests__/agent-loop.test.ts +0 -32
  13. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  14. package/src/__tests__/anthropic-provider.test.ts +217 -98
  15. package/src/__tests__/app-compiler.test.ts +120 -0
  16. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  17. package/src/__tests__/app-executors.test.ts +47 -1
  18. package/src/__tests__/app-source-watcher.test.ts +159 -0
  19. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  20. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  21. package/src/__tests__/call-domain.test.ts +2 -6
  22. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  23. package/src/__tests__/call-recovery.test.ts +2 -6
  24. package/src/__tests__/call-routes-http.test.ts +2 -6
  25. package/src/__tests__/call-store.test.ts +2 -6
  26. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  27. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  28. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  29. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  30. package/src/__tests__/checker.test.ts +63 -9
  31. package/src/__tests__/clawhub.test.ts +54 -24
  32. package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
  33. package/src/__tests__/config-schema.test.ts +6 -1
  34. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  35. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  36. package/src/__tests__/contacts-tools.test.ts +31 -0
  37. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  38. package/src/__tests__/context-token-estimator.test.ts +175 -10
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +13 -6
  40. package/src/__tests__/conversation-agent-loop.test.ts +13 -51
  41. package/src/__tests__/conversation-attachments.test.ts +2 -6
  42. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  43. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  44. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  45. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  46. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  47. package/src/__tests__/conversation-error.test.ts +33 -2
  48. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  49. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  50. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  53. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  54. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  55. package/src/__tests__/conversation-store.test.ts +2 -6
  56. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  57. package/src/__tests__/conversation-usage.test.ts +2 -6
  58. package/src/__tests__/conversation-wipe.test.ts +13 -414
  59. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  60. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  61. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  62. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  63. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  64. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  65. package/src/__tests__/credential-security-e2e.test.ts +2 -0
  66. package/src/__tests__/date-context.test.ts +76 -210
  67. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  68. package/src/__tests__/file-list-tool.test.ts +219 -0
  69. package/src/__tests__/first-greeting.test.ts +1 -1
  70. package/src/__tests__/followup-tools.test.ts +2 -6
  71. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  72. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  73. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  74. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  75. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  76. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  77. package/src/__tests__/guardian-action-store.test.ts +2 -6
  78. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  79. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  80. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  81. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  82. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  83. package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
  84. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  85. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  86. package/src/__tests__/heartbeat-service.test.ts +180 -3
  87. package/src/__tests__/identity-routes.test.ts +328 -0
  88. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  89. package/src/__tests__/injection-block.test.ts +178 -0
  90. package/src/__tests__/install-meta.test.ts +506 -0
  91. package/src/__tests__/install-skill-routing.test.ts +293 -0
  92. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  93. package/src/__tests__/invite-routes-http.test.ts +2 -6
  94. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +17 -28
  95. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  96. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  97. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  98. package/src/__tests__/llm-context-route-provider.test.ts +103 -6
  99. package/src/__tests__/llm-request-log-turn-query.test.ts +164 -6
  100. package/src/__tests__/llm-usage-store.test.ts +2 -6
  101. package/src/__tests__/log-export-workspace.test.ts +74 -111
  102. package/src/__tests__/managed-store.test.ts +38 -11
  103. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  104. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  105. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  106. package/src/__tests__/memory-recall-log-store.test.ts +134 -6
  107. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  108. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  109. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  110. package/src/__tests__/mock-fetch.ts +87 -0
  111. package/src/__tests__/non-member-access-request.test.ts +2 -6
  112. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  113. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  114. package/src/__tests__/oauth-cli.test.ts +364 -2
  115. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  116. package/src/__tests__/onboarding-template-contract.test.ts +62 -14
  117. package/src/__tests__/outlook-attachments.test.ts +301 -0
  118. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  119. package/src/__tests__/outlook-categories.test.ts +212 -0
  120. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  121. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  122. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  123. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  124. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  125. package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
  126. package/src/__tests__/outlook-trash.test.ts +77 -0
  127. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  128. package/src/__tests__/parser.test.ts +32 -0
  129. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  130. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  131. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  132. package/src/__tests__/permission-mode-store.test.ts +277 -0
  133. package/src/__tests__/permission-mode.test.ts +101 -0
  134. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  135. package/src/__tests__/platform-callback-registration.test.ts +4 -4
  136. package/src/__tests__/playbook-execution.test.ts +76 -80
  137. package/src/__tests__/playbook-tools.test.ts +5 -7
  138. package/src/__tests__/profiler-routes.test.ts +502 -0
  139. package/src/__tests__/profiler-run-store.test.ts +441 -0
  140. package/src/__tests__/provider-error-scenarios.test.ts +21 -0
  141. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  142. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  143. package/src/__tests__/registry.test.ts +3 -3
  144. package/src/__tests__/require-fresh-approval.test.ts +64 -2
  145. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  146. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  147. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  148. package/src/__tests__/schedule-store.test.ts +2 -6
  149. package/src/__tests__/schedule-tools.test.ts +2 -6
  150. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  151. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  152. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  153. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  154. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  155. package/src/__tests__/search-skills-unified.test.ts +422 -0
  156. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  157. package/src/__tests__/send-endpoint-busy.test.ts +44 -9
  158. package/src/__tests__/sequence-store.test.ts +2 -6
  159. package/src/__tests__/server-history-render.test.ts +2 -6
  160. package/src/__tests__/set-permission-mode.test.ts +274 -0
  161. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  162. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  163. package/src/__tests__/skill-load-feature-flag.test.ts +23 -11
  164. package/src/__tests__/skill-memory.test.ts +2 -741
  165. package/src/__tests__/skills-uninstall.test.ts +2 -2
  166. package/src/__tests__/skills.test.ts +1 -1
  167. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  168. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  169. package/src/__tests__/subagent-detail.test.ts +84 -0
  170. package/src/__tests__/subagent-disposal.test.ts +308 -0
  171. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  172. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  173. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  174. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  175. package/src/__tests__/subagent-tools.test.ts +464 -4
  176. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  177. package/src/__tests__/task-compiler.test.ts +2 -6
  178. package/src/__tests__/task-management-tools.test.ts +2 -6
  179. package/src/__tests__/task-memory-cleanup.test.ts +185 -241
  180. package/src/__tests__/task-runner.test.ts +2 -6
  181. package/src/__tests__/task-scheduler.test.ts +2 -6
  182. package/src/__tests__/terminal-tools.test.ts +17 -27
  183. package/src/__tests__/test-preload.ts +7 -0
  184. package/src/__tests__/tool-approval-handler.test.ts +2 -6
  185. package/src/__tests__/tool-executor.test.ts +4 -26
  186. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
  187. package/src/__tests__/tool-side-effects-slack-dm.test.ts +277 -0
  188. package/src/__tests__/top-level-renderer.test.ts +10 -13
  189. package/src/__tests__/trust-store.test.ts +1 -1
  190. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
  191. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +118 -8
  192. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  193. package/src/__tests__/trusted-contact-verification.test.ts +2 -6
  194. package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
  195. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
  196. package/src/__tests__/usage-routes.test.ts +2 -6
  197. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  198. package/src/__tests__/voice-invite-redemption.test.ts +2 -6
  199. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
  200. package/src/__tests__/voice-session-bridge.test.ts +2 -6
  201. package/src/__tests__/volume-security-guard.test.ts +2 -0
  202. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  203. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
  204. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
  205. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  206. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  207. package/src/__tests__/workspace-policy.test.ts +1 -1
  208. package/src/agent/attachments.ts +7 -2
  209. package/src/agent/image-optimize.ts +165 -0
  210. package/src/agent/loop.ts +7 -15
  211. package/src/approvals/guardian-request-resolvers.ts +24 -0
  212. package/src/avatar/traits-png-sync.ts +3 -3
  213. package/src/bundler/app-compiler.ts +179 -2
  214. package/src/bundler/package-resolver.ts +3 -5
  215. package/src/cli/__tests__/notifications.test.ts +1 -2
  216. package/src/cli/__tests__/run-assistant-command.ts +29 -0
  217. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  218. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  219. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  220. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  221. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  222. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  223. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  224. package/src/cli/commands/avatar.ts +3 -3
  225. package/src/cli/commands/config.ts +26 -13
  226. package/src/cli/commands/conversations.ts +1 -8
  227. package/src/cli/commands/doctor.ts +2 -2
  228. package/src/cli/commands/email.ts +584 -835
  229. package/src/cli/commands/memory.ts +37 -84
  230. package/src/cli/commands/notifications.ts +7 -2
  231. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  232. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  233. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  234. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  235. package/src/cli/commands/oauth/connect.ts +25 -11
  236. package/src/cli/commands/oauth/mode.ts +7 -0
  237. package/src/cli/commands/oauth/shared.ts +39 -3
  238. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  239. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  240. package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
  241. package/src/cli/commands/platform/index.ts +16 -16
  242. package/src/cli/commands/routes.ts +396 -0
  243. package/src/cli/commands/skills.ts +218 -36
  244. package/src/cli/commands/trust.ts +2 -2
  245. package/src/cli/lib/daemon-credential-client.ts +2 -3
  246. package/src/cli/program.ts +2 -0
  247. package/src/cli.ts +1 -120
  248. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  249. package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
  250. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  251. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  252. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  253. package/src/config/bundled-skills/gmail/SKILL.md +4 -12
  254. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  255. package/src/config/bundled-skills/messaging/SKILL.md +17 -18
  256. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  257. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  258. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  259. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  260. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  261. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  262. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  263. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  264. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  265. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  266. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  267. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  268. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  269. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  270. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  271. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  272. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  273. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  274. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  275. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  276. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  277. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  278. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  279. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  280. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  281. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  282. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  283. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  284. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  285. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  286. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  287. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  288. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  289. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  290. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  291. package/src/config/bundled-skills/slack/SKILL.md +3 -7
  292. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  293. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  294. package/src/config/bundled-tool-registry.ts +56 -4
  295. package/src/config/env-registry.ts +78 -8
  296. package/src/config/feature-flag-registry.json +38 -125
  297. package/src/config/schema.ts +8 -0
  298. package/src/config/schemas/filing.ts +51 -0
  299. package/src/config/schemas/heartbeat.ts +15 -12
  300. package/src/config/schemas/memory-lifecycle.ts +12 -0
  301. package/src/config/schemas/platform.ts +8 -0
  302. package/src/config/schemas/security.ts +14 -0
  303. package/src/config/schemas/timeouts.ts +1 -1
  304. package/src/config/skills.ts +18 -7
  305. package/src/context/token-estimator.ts +25 -18
  306. package/src/context/window-manager.ts +6 -2
  307. package/src/credential-execution/process-manager.ts +3 -1
  308. package/src/daemon/app-source-watcher.ts +93 -0
  309. package/src/daemon/config-watcher.ts +79 -1
  310. package/src/daemon/context-overflow-reducer.ts +46 -2
  311. package/src/daemon/conversation-agent-loop-handlers.ts +143 -82
  312. package/src/daemon/conversation-agent-loop.ts +236 -108
  313. package/src/daemon/conversation-error.ts +31 -8
  314. package/src/daemon/conversation-history.ts +4 -19
  315. package/src/daemon/conversation-lifecycle.ts +36 -9
  316. package/src/daemon/conversation-media-retry.ts +85 -7
  317. package/src/daemon/conversation-notifiers.ts +4 -1
  318. package/src/daemon/conversation-process.ts +13 -7
  319. package/src/daemon/conversation-runtime-assembly.ts +305 -306
  320. package/src/daemon/conversation-tool-setup.ts +44 -14
  321. package/src/daemon/conversation-workspace.ts +1 -2
  322. package/src/daemon/conversation.ts +59 -2
  323. package/src/daemon/daemon-control.ts +8 -2
  324. package/src/daemon/date-context.ts +26 -53
  325. package/src/daemon/first-greeting.ts +1 -1
  326. package/src/daemon/handlers/conversations.ts +4 -7
  327. package/src/daemon/handlers/shared.test.ts +143 -0
  328. package/src/daemon/handlers/shared.ts +85 -17
  329. package/src/daemon/handlers/skills.ts +416 -209
  330. package/src/daemon/lifecycle.ts +212 -131
  331. package/src/daemon/main.ts +5 -1
  332. package/src/daemon/message-types/conversations.ts +29 -7
  333. package/src/daemon/message-types/messages.ts +12 -2
  334. package/src/daemon/message-types/schedules.ts +1 -0
  335. package/src/daemon/message-types/settings.ts +6 -0
  336. package/src/daemon/message-types/skills.ts +97 -36
  337. package/src/daemon/profiler-run-store.ts +557 -0
  338. package/src/daemon/providers-setup.ts +5 -0
  339. package/src/daemon/server.ts +100 -11
  340. package/src/daemon/shutdown-handlers.ts +5 -0
  341. package/src/daemon/tool-side-effects.ts +50 -8
  342. package/src/export/transcript-formatter.ts +148 -0
  343. package/src/filing/filing-service.ts +228 -0
  344. package/src/heartbeat/heartbeat-service.ts +97 -7
  345. package/src/hooks/cli.ts +2 -2
  346. package/src/hooks/runner.ts +15 -38
  347. package/src/inbound/platform-callback-registration.ts +14 -14
  348. package/src/mcp/client.ts +6 -0
  349. package/src/mcp/mcp-oauth-provider.ts +149 -27
  350. package/src/memory/admin.ts +42 -75
  351. package/src/memory/app-store.ts +69 -0
  352. package/src/memory/conversation-bootstrap.ts +3 -1
  353. package/src/memory/conversation-crud.ts +211 -288
  354. package/src/memory/conversation-group-migration.ts +157 -0
  355. package/src/memory/conversation-queries.ts +61 -13
  356. package/src/memory/conversation-title-service.ts +1 -0
  357. package/src/memory/db-init.ts +194 -361
  358. package/src/memory/embed.ts +73 -0
  359. package/src/memory/embedding-backend.ts +8 -14
  360. package/src/memory/embedding-runtime-manager.ts +12 -114
  361. package/src/memory/fingerprint.ts +2 -2
  362. package/src/memory/graph/bootstrap.ts +521 -0
  363. package/src/memory/graph/capability-seed.ts +449 -0
  364. package/src/memory/graph/consolidation.ts +725 -0
  365. package/src/memory/graph/conversation-graph-memory.ts +659 -0
  366. package/src/memory/graph/decay.test.ts +208 -0
  367. package/src/memory/graph/decay.ts +195 -0
  368. package/src/memory/graph/extraction-job.ts +74 -0
  369. package/src/memory/graph/extraction.test.ts +936 -0
  370. package/src/memory/graph/extraction.ts +1297 -0
  371. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  372. package/src/memory/graph/graph-search.ts +280 -0
  373. package/src/memory/graph/image-ref-utils.ts +29 -0
  374. package/src/memory/graph/injection.test.ts +513 -0
  375. package/src/memory/graph/injection.ts +469 -0
  376. package/src/memory/graph/inspect.ts +543 -0
  377. package/src/memory/graph/narrative.ts +267 -0
  378. package/src/memory/graph/pattern-scan.ts +269 -0
  379. package/src/memory/graph/retriever.ts +1111 -0
  380. package/src/memory/graph/scoring.test.ts +548 -0
  381. package/src/memory/graph/scoring.ts +232 -0
  382. package/src/memory/graph/serendipity.ts +65 -0
  383. package/src/memory/graph/store.test.ts +1098 -0
  384. package/src/memory/graph/store.ts +838 -0
  385. package/src/memory/graph/tool-handlers.ts +301 -0
  386. package/src/memory/graph/tools.ts +97 -0
  387. package/src/memory/graph/triggers.test.ts +487 -0
  388. package/src/memory/graph/triggers.ts +223 -0
  389. package/src/memory/graph/types.ts +295 -0
  390. package/src/memory/group-crud.ts +191 -0
  391. package/src/memory/indexer.ts +37 -19
  392. package/src/memory/job-handlers/cleanup.ts +32 -42
  393. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  394. package/src/memory/job-handlers/embedding.ts +5 -31
  395. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  396. package/src/memory/job-handlers/summarization.ts +32 -17
  397. package/src/memory/job-utils.ts +1 -1
  398. package/src/memory/jobs-store.ts +21 -31
  399. package/src/memory/jobs-worker.ts +180 -129
  400. package/src/memory/llm-request-log-store.ts +96 -12
  401. package/src/memory/memory-recall-log-store.ts +49 -5
  402. package/src/memory/message-content.ts +1 -0
  403. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  404. package/src/memory/migrations/203-drop-memory-items-tables.ts +55 -0
  405. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  406. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  407. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  408. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  409. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  410. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  411. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  412. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  413. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  414. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  415. package/src/memory/migrations/index.ts +12 -0
  416. package/src/memory/migrations/registry.ts +16 -0
  417. package/src/memory/qdrant-client.ts +44 -17
  418. package/src/memory/schema/conversations.ts +14 -0
  419. package/src/memory/schema/index.ts +1 -0
  420. package/src/memory/schema/infrastructure.ts +8 -1
  421. package/src/memory/schema/memory-core.ts +0 -51
  422. package/src/memory/schema/memory-graph.ts +154 -0
  423. package/src/memory/search/semantic.ts +47 -91
  424. package/src/memory/task-memory-cleanup.ts +58 -61
  425. package/src/messaging/providers/outlook/adapter.ts +8 -1
  426. package/src/messaging/providers/outlook/client.ts +299 -0
  427. package/src/messaging/providers/outlook/types.ts +118 -0
  428. package/src/notifications/adapters/macos.ts +1 -0
  429. package/src/notifications/copy-composer.ts +95 -0
  430. package/src/notifications/decision-engine.ts +35 -0
  431. package/src/notifications/signal.ts +16 -0
  432. package/src/oauth/seed-providers.ts +2 -1
  433. package/src/permissions/checker.ts +36 -4
  434. package/src/permissions/defaults.ts +4 -4
  435. package/src/permissions/permission-mode-store.ts +180 -0
  436. package/src/permissions/permission-mode.ts +31 -0
  437. package/src/permissions/workspace-policy.ts +10 -1
  438. package/src/playbooks/playbook-compiler.ts +19 -18
  439. package/src/playbooks/types.ts +4 -3
  440. package/src/prompts/system-prompt.ts +62 -36
  441. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  442. package/src/prompts/templates/BOOTSTRAP.md +70 -165
  443. package/src/prompts/templates/HEARTBEAT.md +3 -1
  444. package/src/prompts/templates/SOUL.md +25 -4
  445. package/src/prompts/templates/UPDATES.md +8 -0
  446. package/src/providers/anthropic/client.ts +136 -220
  447. package/src/providers/gemini/client.ts +1 -1
  448. package/src/providers/openai/client.ts +1 -1
  449. package/src/providers/registry.ts +1 -1
  450. package/src/providers/retry.ts +19 -3
  451. package/src/runtime/actor-trust-resolver.ts +5 -1
  452. package/src/runtime/auth/route-policy.ts +30 -0
  453. package/src/runtime/guardian-reply-router.ts +5 -1
  454. package/src/runtime/http-server.ts +55 -5
  455. package/src/runtime/http-types.ts +12 -1
  456. package/src/runtime/middleware/auth.ts +20 -0
  457. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  458. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  459. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  460. package/src/runtime/routes/app-management-routes.ts +1 -11
  461. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  462. package/src/runtime/routes/archive-utils.ts +29 -0
  463. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  464. package/src/runtime/routes/attachment-routes.ts +106 -16
  465. package/src/runtime/routes/avatar-routes.ts +2 -9
  466. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  467. package/src/runtime/routes/btw-routes.ts +22 -1
  468. package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
  469. package/src/runtime/routes/conversation-management-routes.ts +3 -14
  470. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  471. package/src/runtime/routes/conversation-routes.ts +264 -44
  472. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  473. package/src/runtime/routes/debug-routes.ts +1 -1
  474. package/src/runtime/routes/global-search-routes.ts +21 -19
  475. package/src/runtime/routes/group-routes.ts +207 -0
  476. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  477. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  478. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  479. package/src/runtime/routes/identity-routes.ts +53 -18
  480. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  481. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  482. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  483. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  484. package/src/runtime/routes/log-export-routes.ts +23 -275
  485. package/src/runtime/routes/memory-item-routes.test.ts +170 -247
  486. package/src/runtime/routes/memory-item-routes.ts +341 -388
  487. package/src/runtime/routes/migration-routes.ts +18 -7
  488. package/src/runtime/routes/profiler-routes.ts +350 -0
  489. package/src/runtime/routes/schedule-routes.ts +28 -11
  490. package/src/runtime/routes/settings-routes.ts +95 -8
  491. package/src/runtime/routes/skills-routes.ts +103 -37
  492. package/src/runtime/routes/subagents-routes.ts +28 -7
  493. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  494. package/src/runtime/routes/user-routes.ts +41 -0
  495. package/src/runtime/routes/work-items-routes.test.ts +2 -6
  496. package/src/runtime/routes/workspace-routes.ts +0 -1
  497. package/src/schedule/schedule-store.ts +30 -0
  498. package/src/schedule/scheduler.ts +52 -18
  499. package/src/security/oauth2.ts +1 -1
  500. package/src/security/secure-keys.ts +4 -8
  501. package/src/shared/provider-env-vars.ts +19 -0
  502. package/src/skills/catalog-cache.ts +5 -0
  503. package/src/skills/catalog-install.ts +25 -16
  504. package/src/skills/clawhub.ts +134 -154
  505. package/src/skills/install-meta.ts +208 -0
  506. package/src/skills/managed-store.ts +29 -18
  507. package/src/skills/skill-memory.ts +12 -229
  508. package/src/skills/skillssh-registry.ts +19 -17
  509. package/src/subagent/index.ts +13 -3
  510. package/src/subagent/manager.ts +308 -29
  511. package/src/subagent/types.ts +68 -0
  512. package/src/tasks/task-runner.ts +7 -5
  513. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  514. package/src/tools/apps/executors.ts +29 -4
  515. package/src/tools/browser/runtime-check.ts +3 -1
  516. package/src/tools/filesystem/list.ts +93 -0
  517. package/src/tools/memory/register.ts +63 -46
  518. package/src/tools/permission-checker.ts +85 -1
  519. package/src/tools/registry.ts +4 -0
  520. package/src/tools/schedule/create.ts +3 -0
  521. package/src/tools/schedule/list.ts +1 -0
  522. package/src/tools/schedule/update.ts +6 -0
  523. package/src/tools/shared/filesystem/errors.ts +5 -0
  524. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  525. package/src/tools/shared/filesystem/image-read.ts +22 -85
  526. package/src/tools/shared/filesystem/types.ts +17 -0
  527. package/src/tools/shared/shell-output.ts +31 -2
  528. package/src/tools/subagent/abort.ts +12 -2
  529. package/src/tools/subagent/message.ts +9 -2
  530. package/src/tools/subagent/notify-parent.ts +79 -0
  531. package/src/tools/subagent/read.ts +29 -8
  532. package/src/tools/subagent/resolve.ts +21 -0
  533. package/src/tools/subagent/spawn.ts +2 -0
  534. package/src/tools/subagent/status.ts +11 -1
  535. package/src/tools/system/avatar-generator.ts +3 -3
  536. package/src/tools/system/register.ts +23 -0
  537. package/src/tools/system/set-permission-mode.ts +103 -0
  538. package/src/tools/terminal/parser.ts +30 -5
  539. package/src/tools/terminal/safe-env.ts +17 -1
  540. package/src/tools/tool-manifest.ts +9 -3
  541. package/src/tools/types.ts +2 -0
  542. package/src/util/browser.ts +25 -10
  543. package/src/util/bun-runtime.ts +172 -0
  544. package/src/util/logger.ts +1 -1
  545. package/src/util/platform.ts +50 -17
  546. package/src/watcher/providers/outlook-calendar.ts +343 -0
  547. package/src/watcher/providers/outlook.ts +198 -0
  548. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  549. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  550. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  551. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  552. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  553. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  554. package/src/workspace/migrations/029-seed-pkb.ts +84 -0
  555. package/src/workspace/migrations/registry.ts +10 -0
  556. package/src/workspace/top-level-renderer.ts +5 -9
  557. package/src/__tests__/cli-memory.test.ts +0 -372
  558. package/src/__tests__/clipboard.test.ts +0 -88
  559. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  560. package/src/__tests__/journal-context.test.ts +0 -268
  561. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  562. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  563. package/src/__tests__/memory-query-builder.test.ts +0 -59
  564. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  565. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  566. package/src/__tests__/memory-regressions.test.ts +0 -3696
  567. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  568. package/src/cli/cli-memory.ts +0 -176
  569. package/src/daemon/conversation-memory.ts +0 -207
  570. package/src/memory/conversation-starters-cadence.ts +0 -74
  571. package/src/memory/items-extractor.ts +0 -860
  572. package/src/memory/job-handlers/batch-extraction.ts +0 -753
  573. package/src/memory/job-handlers/extraction.ts +0 -40
  574. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
  575. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  576. package/src/memory/journal-memory.ts +0 -224
  577. package/src/memory/query-builder.ts +0 -47
  578. package/src/memory/query-expansion.ts +0 -83
  579. package/src/memory/retriever.test.ts +0 -1592
  580. package/src/memory/retriever.ts +0 -1331
  581. package/src/memory/search/formatting.test.ts +0 -140
  582. package/src/memory/search/formatting.ts +0 -262
  583. package/src/memory/search/mmr.ts +0 -139
  584. package/src/memory/search/ranking.ts +0 -15
  585. package/src/memory/search/staleness.ts +0 -40
  586. package/src/memory/search/tier-classifier.ts +0 -18
  587. package/src/memory/search/types.ts +0 -121
  588. package/src/prompts/journal-context.ts +0 -154
  589. package/src/tools/memory/definitions.ts +0 -69
  590. package/src/tools/memory/handlers.test.ts +0 -562
  591. package/src/tools/memory/handlers.ts +0 -434
  592. package/src/util/clipboard.ts +0 -34
@@ -0,0 +1,387 @@
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Mocks — must come before any imports that depend on them
7
+ // ---------------------------------------------------------------------------
8
+
9
+ const testDir = process.env.VELLUM_WORKSPACE_DIR!;
10
+ const workspaceDir = testDir;
11
+ const conversationsDir = join(workspaceDir, "conversations");
12
+ mkdirSync(conversationsDir, { recursive: true });
13
+
14
+ mock.module("../util/logger.js", () => ({
15
+ getLogger: () =>
16
+ new Proxy({} as Record<string, unknown>, {
17
+ get: () => () => {},
18
+ }),
19
+ }));
20
+
21
+ mock.module("../config/loader.js", () => ({
22
+ getConfig: () => ({
23
+ ui: {},
24
+ model: "test",
25
+ provider: "test",
26
+ memory: { enabled: false },
27
+ rateLimit: { maxRequestsPerMinute: 0 },
28
+ }),
29
+ }));
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Imports — after mocks
33
+ // ---------------------------------------------------------------------------
34
+
35
+ import { getDb, initializeDb } from "../memory/db.js";
36
+ import { conversations, messages } from "../memory/schema.js";
37
+ import { recoverConversationsFromDiskViewMigration } from "../workspace/migrations/028-recover-conversations-from-disk-view.js";
38
+
39
+ initializeDb();
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Helpers
43
+ // ---------------------------------------------------------------------------
44
+
45
+ function resetTables() {
46
+ const db = getDb();
47
+ db.run("DELETE FROM messages");
48
+ db.run("DELETE FROM conversations");
49
+ }
50
+
51
+ function resetConversationsDir() {
52
+ rmSync(conversationsDir, { recursive: true, force: true });
53
+ mkdirSync(conversationsDir, { recursive: true });
54
+ }
55
+
56
+ function createDiskViewDir(
57
+ id: string,
58
+ meta: Record<string, unknown>,
59
+ messagesJsonl?: string,
60
+ ): string {
61
+ const createdAt =
62
+ typeof meta.createdAt === "string" ? meta.createdAt : new Date().toISOString();
63
+ const timestamp = createdAt.replace(/:/g, "-");
64
+ const dirName = `${timestamp}_${id}`;
65
+ const dirPath = join(conversationsDir, dirName);
66
+ mkdirSync(dirPath, { recursive: true });
67
+ writeFileSync(join(dirPath, "meta.json"), JSON.stringify(meta, null, 2) + "\n");
68
+ if (messagesJsonl !== undefined) {
69
+ writeFileSync(join(dirPath, "messages.jsonl"), messagesJsonl);
70
+ }
71
+ return dirPath;
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Tests
76
+ // ---------------------------------------------------------------------------
77
+
78
+ describe("028-recover-conversations-from-disk-view migration", () => {
79
+ beforeEach(() => {
80
+ resetTables();
81
+ resetConversationsDir();
82
+ });
83
+
84
+ test("recovers conversation with messages", () => {
85
+ const id = "conv-028-basic";
86
+ const createdAt = "2026-03-18T14:23:00.000Z";
87
+ const updatedAt = "2026-03-18T14:25:00.000Z";
88
+
89
+ const userLine = JSON.stringify({
90
+ role: "user",
91
+ ts: "2026-03-18T14:23:30.000Z",
92
+ content: "Hello, world",
93
+ });
94
+ const assistantLine = JSON.stringify({
95
+ role: "assistant",
96
+ ts: "2026-03-18T14:24:00.000Z",
97
+ content: "Hi there!",
98
+ });
99
+
100
+ createDiskViewDir(
101
+ id,
102
+ { id, title: "Basic Recovery", type: "standard", channel: "desktop", createdAt, updatedAt },
103
+ userLine + "\n" + assistantLine + "\n",
104
+ );
105
+
106
+ recoverConversationsFromDiskViewMigration.run(workspaceDir);
107
+
108
+ const db = getDb();
109
+ const convRows = db.select().from(conversations).all();
110
+ expect(convRows).toHaveLength(1);
111
+ expect(convRows[0].id).toBe(id);
112
+ expect(convRows[0].title).toBe("Basic Recovery");
113
+ expect(convRows[0].conversationType).toBe("standard");
114
+ expect(convRows[0].createdAt).toBe(Date.parse(createdAt));
115
+ expect(convRows[0].updatedAt).toBe(Date.parse(updatedAt));
116
+
117
+ const msgRows = db.select().from(messages).all();
118
+ expect(msgRows).toHaveLength(2);
119
+
120
+ const userMsg = msgRows.find((m) => m.role === "user")!;
121
+ expect(userMsg).toBeDefined();
122
+ const userContent = JSON.parse(userMsg.content);
123
+ expect(userContent).toEqual([{ type: "text", text: "Hello, world" }]);
124
+ expect(userMsg.createdAt).toBe(Date.parse("2026-03-18T14:23:30.000Z"));
125
+
126
+ const assistantMsg = msgRows.find((m) => m.role === "assistant")!;
127
+ expect(assistantMsg).toBeDefined();
128
+ const assistantContent = JSON.parse(assistantMsg.content);
129
+ expect(assistantContent).toEqual([{ type: "text", text: "Hi there!" }]);
130
+ expect(assistantMsg.createdAt).toBe(Date.parse("2026-03-18T14:24:00.000Z"));
131
+ });
132
+
133
+ test("handles toolCalls and toolResults", () => {
134
+ const id = "conv-028-tools";
135
+ const createdAt = "2026-03-18T15:00:00.000Z";
136
+
137
+ const toolCallLine = JSON.stringify({
138
+ role: "assistant",
139
+ ts: "2026-03-18T15:00:10.000Z",
140
+ toolCalls: [{ name: "bash", input: { command: "ls" } }],
141
+ });
142
+ const toolResultLine = JSON.stringify({
143
+ role: "user",
144
+ ts: "2026-03-18T15:00:20.000Z",
145
+ toolResults: [{ content: "file.txt" }],
146
+ });
147
+
148
+ createDiskViewDir(
149
+ id,
150
+ { id, title: "Tool Test", type: "standard", createdAt, updatedAt: createdAt },
151
+ toolCallLine + "\n" + toolResultLine + "\n",
152
+ );
153
+
154
+ recoverConversationsFromDiskViewMigration.run(workspaceDir);
155
+
156
+ const db = getDb();
157
+ const msgRows = db.select().from(messages).all();
158
+ expect(msgRows).toHaveLength(2);
159
+
160
+ const assistantMsg = msgRows.find((m) => m.role === "assistant")!;
161
+ const assistantContent = JSON.parse(assistantMsg.content);
162
+ expect(assistantContent).toHaveLength(1);
163
+ expect(assistantContent[0].type).toBe("tool_use");
164
+ expect(assistantContent[0].name).toBe("bash");
165
+ expect(assistantContent[0].input).toEqual({ command: "ls" });
166
+ // tool_use blocks get a random UUID id — just check it's a string
167
+ expect(typeof assistantContent[0].id).toBe("string");
168
+
169
+ const userMsg = msgRows.find((m) => m.role === "user")!;
170
+ const userContent = JSON.parse(userMsg.content);
171
+ expect(userContent).toHaveLength(1);
172
+ expect(userContent[0].type).toBe("tool_result");
173
+ expect(userContent[0].content).toBe("file.txt");
174
+ expect(userContent[0].tool_use_id).toBe("");
175
+ });
176
+
177
+ test("handles mixed content + toolCalls on the same message", () => {
178
+ const id = "conv-028-mixed";
179
+ const createdAt = "2026-03-18T15:30:00.000Z";
180
+
181
+ const mixedLine = JSON.stringify({
182
+ role: "assistant",
183
+ ts: "2026-03-18T15:30:10.000Z",
184
+ content: "Let me check that",
185
+ toolCalls: [{ name: "bash", input: { command: "ls" } }],
186
+ });
187
+
188
+ createDiskViewDir(
189
+ id,
190
+ { id, title: "Mixed Test", type: "standard", createdAt, updatedAt: createdAt },
191
+ mixedLine + "\n",
192
+ );
193
+
194
+ recoverConversationsFromDiskViewMigration.run(workspaceDir);
195
+
196
+ const db = getDb();
197
+ const msgRows = db.select().from(messages).all();
198
+ expect(msgRows).toHaveLength(1);
199
+
200
+ const assistantMsg = msgRows[0];
201
+ expect(assistantMsg.role).toBe("assistant");
202
+
203
+ const contentBlocks = JSON.parse(assistantMsg.content);
204
+ expect(contentBlocks).toHaveLength(2);
205
+
206
+ expect(contentBlocks[0].type).toBe("text");
207
+ expect(contentBlocks[0].text).toBe("Let me check that");
208
+
209
+ expect(contentBlocks[1].type).toBe("tool_use");
210
+ expect(contentBlocks[1].name).toBe("bash");
211
+ expect(contentBlocks[1].input).toEqual({ command: "ls" });
212
+ expect(typeof contentBlocks[1].id).toBe("string");
213
+ });
214
+
215
+ test("skips existing conversations", () => {
216
+ const id = "conv-028-existing";
217
+ const createdAt = "2026-03-18T16:00:00.000Z";
218
+ const createdAtMs = Date.parse(createdAt);
219
+
220
+ // Pre-insert the conversation in the DB
221
+ const db = getDb();
222
+ db.insert(conversations)
223
+ .values({
224
+ id,
225
+ title: "Already Here",
226
+ createdAt: createdAtMs,
227
+ updatedAt: createdAtMs,
228
+ conversationType: "standard",
229
+ source: "user",
230
+ memoryScopeId: "default",
231
+ })
232
+ .run();
233
+
234
+ // Create matching disk-view dir with a message
235
+ createDiskViewDir(
236
+ id,
237
+ { id, title: "Already Here", type: "standard", createdAt, updatedAt: createdAt },
238
+ JSON.stringify({ role: "user", ts: createdAt, content: "Should not be imported" }) + "\n",
239
+ );
240
+
241
+ recoverConversationsFromDiskViewMigration.run(workspaceDir);
242
+
243
+ // Verify no duplication: still 1 conversation, 0 messages (the disk-view message was not imported)
244
+ const convRows = db.select().from(conversations).all();
245
+ expect(convRows).toHaveLength(1);
246
+ expect(convRows[0].title).toBe("Already Here");
247
+
248
+ const msgRows = db.select().from(messages).all();
249
+ expect(msgRows).toHaveLength(0);
250
+ });
251
+
252
+ test("idempotent — running twice produces same result", () => {
253
+ const id = "conv-028-idem";
254
+ const createdAt = "2026-03-18T17:00:00.000Z";
255
+
256
+ createDiskViewDir(
257
+ id,
258
+ { id, title: "Idempotency Test", type: "standard", createdAt, updatedAt: createdAt },
259
+ JSON.stringify({ role: "user", ts: createdAt, content: "First message" }) + "\n" +
260
+ JSON.stringify({ role: "assistant", ts: "2026-03-18T17:01:00.000Z", content: "Reply" }) + "\n",
261
+ );
262
+
263
+ recoverConversationsFromDiskViewMigration.run(workspaceDir);
264
+
265
+ const db = getDb();
266
+ const convCountAfterFirst = db.select().from(conversations).all().length;
267
+ const msgCountAfterFirst = db.select().from(messages).all().length;
268
+ expect(convCountAfterFirst).toBe(1);
269
+ expect(msgCountAfterFirst).toBe(2);
270
+
271
+ // Run again
272
+ recoverConversationsFromDiskViewMigration.run(workspaceDir);
273
+
274
+ const convCountAfterSecond = db.select().from(conversations).all().length;
275
+ const msgCountAfterSecond = db.select().from(messages).all().length;
276
+ expect(convCountAfterSecond).toBe(convCountAfterFirst);
277
+ expect(msgCountAfterSecond).toBe(msgCountAfterFirst);
278
+ });
279
+
280
+ test("handles missing messages.jsonl", () => {
281
+ const id = "conv-028-no-messages";
282
+ const createdAt = "2026-03-18T18:00:00.000Z";
283
+
284
+ // Create dir with only meta.json — no messages.jsonl
285
+ createDiskViewDir(
286
+ id,
287
+ { id, title: "No Messages", type: "standard", createdAt, updatedAt: createdAt },
288
+ );
289
+
290
+ recoverConversationsFromDiskViewMigration.run(workspaceDir);
291
+
292
+ const db = getDb();
293
+ const convRows = db.select().from(conversations).all();
294
+ expect(convRows).toHaveLength(1);
295
+ expect(convRows[0].id).toBe(id);
296
+ expect(convRows[0].title).toBe("No Messages");
297
+
298
+ const msgRows = db.select().from(messages).all();
299
+ expect(msgRows).toHaveLength(0);
300
+ });
301
+
302
+ test("handles malformed JSONL lines", () => {
303
+ const id = "conv-028-malformed-jsonl";
304
+ const createdAt = "2026-03-18T19:00:00.000Z";
305
+
306
+ const validLine = JSON.stringify({ role: "user", ts: createdAt, content: "Valid" });
307
+ const invalidLine = "{ this is not valid json }}}";
308
+
309
+ createDiskViewDir(
310
+ id,
311
+ { id, title: "Malformed JSONL", type: "standard", createdAt, updatedAt: createdAt },
312
+ validLine + "\n" + invalidLine + "\n",
313
+ );
314
+
315
+ recoverConversationsFromDiskViewMigration.run(workspaceDir);
316
+
317
+ const db = getDb();
318
+ const convRows = db.select().from(conversations).all();
319
+ expect(convRows).toHaveLength(1);
320
+
321
+ // Only the valid line should produce a message row
322
+ const msgRows = db.select().from(messages).all();
323
+ expect(msgRows).toHaveLength(1);
324
+ expect(msgRows[0].role).toBe("user");
325
+ const content = JSON.parse(msgRows[0].content);
326
+ expect(content).toEqual([{ type: "text", text: "Valid" }]);
327
+ });
328
+
329
+ test("handles malformed meta.json", () => {
330
+ const id = "conv-028-malformed-meta";
331
+ const createdAt = "2026-03-18T20:00:00.000Z";
332
+ const timestamp = createdAt.replace(/:/g, "-");
333
+ const dirName = `${timestamp}_${id}`;
334
+ const dirPath = join(conversationsDir, dirName);
335
+ mkdirSync(dirPath, { recursive: true });
336
+
337
+ // Write broken JSON directly
338
+ writeFileSync(join(dirPath, "meta.json"), "{ broken json");
339
+
340
+ // Migration should complete without error
341
+ recoverConversationsFromDiskViewMigration.run(workspaceDir);
342
+
343
+ const db = getDb();
344
+ const convRows = db.select().from(conversations).all();
345
+ expect(convRows).toHaveLength(0);
346
+ });
347
+
348
+ test("no-op when conversations dir missing", () => {
349
+ // Remove the conversations dir entirely
350
+ rmSync(conversationsDir, { recursive: true, force: true });
351
+ expect(existsSync(conversationsDir)).toBe(false);
352
+
353
+ // Migration should complete without error
354
+ recoverConversationsFromDiskViewMigration.run(workspaceDir);
355
+
356
+ // No conversations should exist since we can't access the DB rows through a missing dir
357
+ const db = getDb();
358
+ const convRows = db.select().from(conversations).all();
359
+ expect(convRows).toHaveLength(0);
360
+ });
361
+
362
+ test("processes multiple directories", () => {
363
+ const ids = ["conv-028-multi-a", "conv-028-multi-b", "conv-028-multi-c"];
364
+ const baseTime = Date.parse("2026-03-18T21:00:00.000Z");
365
+
366
+ for (let i = 0; i < ids.length; i++) {
367
+ const ts = new Date(baseTime + i * 60_000).toISOString();
368
+ createDiskViewDir(
369
+ ids[i],
370
+ { id: ids[i], title: `Multi ${i + 1}`, type: "standard", createdAt: ts, updatedAt: ts },
371
+ JSON.stringify({ role: "user", ts, content: `Message ${i + 1}` }) + "\n",
372
+ );
373
+ }
374
+
375
+ recoverConversationsFromDiskViewMigration.run(workspaceDir);
376
+
377
+ const db = getDb();
378
+ const convRows = db.select().from(conversations).all();
379
+ expect(convRows).toHaveLength(3);
380
+
381
+ const recoveredIds = convRows.map((c) => c.id).sort();
382
+ expect(recoveredIds).toEqual([...ids].sort());
383
+
384
+ const msgRows = db.select().from(messages).all();
385
+ expect(msgRows).toHaveLength(3);
386
+ });
387
+ });
@@ -257,7 +257,7 @@ describe("isWorkspaceScopedInvocation", () => {
257
257
  describe("always-scoped tools", () => {
258
258
  const safeTools = [
259
259
  "skill_load",
260
- "memory_recall",
260
+ "recall",
261
261
  "ui_update",
262
262
  "ui_dismiss",
263
263
  ];
@@ -1,4 +1,5 @@
1
1
  import type { ContentBlock, Message } from "../providers/types.js";
2
+ import { optimizeImageForTransport } from "./image-optimize.js";
2
3
 
3
4
  export interface MessageAttachmentInput {
4
5
  id?: string;
@@ -14,12 +15,16 @@ export function attachmentsToContentBlocks(
14
15
  ): ContentBlock[] {
15
16
  return attachments.map((attachment) => {
16
17
  if (attachment.mimeType.toLowerCase().startsWith("image/")) {
18
+ const { data, mediaType } = optimizeImageForTransport(
19
+ attachment.data,
20
+ attachment.mimeType,
21
+ );
17
22
  return {
18
23
  type: "image",
19
24
  source: {
20
25
  type: "base64",
21
- media_type: attachment.mimeType,
22
- data: attachment.data,
26
+ media_type: mediaType,
27
+ data,
23
28
  },
24
29
  } as ContentBlock;
25
30
  }
@@ -0,0 +1,165 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ readFileSync,
8
+ statSync,
9
+ unlinkSync,
10
+ writeFileSync,
11
+ } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+
15
+ import { parseImageDimensions } from "../context/image-dimensions.js";
16
+
17
+ // Anthropic's documented max dimension — images larger than this are scaled
18
+ // down server-side anyway, so pre-scaling is zero quality loss.
19
+ const MAX_DIMENSION = 1568;
20
+
21
+ // Threshold below which we skip optimization — small images don't need it.
22
+ const OPTIMIZE_THRESHOLD_BYTES = 300 * 1024; // 300 KB
23
+
24
+ const JPEG_QUALITY = 80;
25
+
26
+ // Content-addressed disk cache to avoid re-running sips on the same image.
27
+ const CACHE_MAX_ENTRIES = 500;
28
+
29
+ function getCacheDir(): string {
30
+ return join(tmpdir(), "vellum-optimized-images");
31
+ }
32
+
33
+ function readFromCache(
34
+ key: string,
35
+ ): { data: string; mediaType: string } | null {
36
+ try {
37
+ const cachePath = join(getCacheDir(), `${key}.jpg`);
38
+ if (!existsSync(cachePath)) return null;
39
+ const buf = readFileSync(cachePath) as Buffer;
40
+ return { data: buf.toString("base64"), mediaType: "image/jpeg" };
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function writeToCache(key: string, optimizedBytes: Buffer): void {
47
+ try {
48
+ const dir = getCacheDir();
49
+ mkdirSync(dir, { recursive: true });
50
+ writeFileSync(join(dir, `${key}.jpg`), optimizedBytes);
51
+ evictIfNeeded(dir);
52
+ } catch {
53
+ // Cache write failure is non-fatal.
54
+ }
55
+ }
56
+
57
+ function evictIfNeeded(dir: string): void {
58
+ try {
59
+ const entries = readdirSync(dir)
60
+ .filter((f) => f.endsWith(".jpg"))
61
+ .map((f) => {
62
+ const full = join(dir, f);
63
+ return { path: full, mtimeMs: statSync(full).mtimeMs };
64
+ })
65
+ .sort((a, b) => a.mtimeMs - b.mtimeMs);
66
+ const excess = entries.length - CACHE_MAX_ENTRIES;
67
+ for (let i = 0; i < excess; i++) {
68
+ try {
69
+ unlinkSync(entries[i]!.path);
70
+ } catch {
71
+ /* ignore */
72
+ }
73
+ }
74
+ } catch {
75
+ /* ignore */
76
+ }
77
+ }
78
+
79
+ function runSips(inputBytes: Buffer): Buffer | null {
80
+ const srcPath = join(tmpdir(), `vellum-img-opt-${Date.now()}-src`);
81
+ const outPath = join(tmpdir(), `vellum-img-opt-${Date.now()}-out.jpg`);
82
+ try {
83
+ writeFileSync(srcPath, inputBytes);
84
+ execFileSync(
85
+ "sips",
86
+ [
87
+ "--resampleHeightWidthMax",
88
+ String(MAX_DIMENSION),
89
+ "-s",
90
+ "format",
91
+ "jpeg",
92
+ "-s",
93
+ "formatOptions",
94
+ String(JPEG_QUALITY),
95
+ srcPath,
96
+ "--out",
97
+ outPath,
98
+ ],
99
+ { stdio: "pipe", timeout: 15_000 },
100
+ );
101
+ return readFileSync(outPath) as Buffer;
102
+ } catch {
103
+ return null;
104
+ } finally {
105
+ try {
106
+ unlinkSync(srcPath);
107
+ } catch {
108
+ /* ignore */
109
+ }
110
+ try {
111
+ unlinkSync(outPath);
112
+ } catch {
113
+ /* ignore */
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Downscale a base64 image to fit within Anthropic's recommended dimensions
120
+ * (1568px max side). Returns the original data unchanged if the image is
121
+ * already small enough or if optimization fails.
122
+ *
123
+ * Anthropic applies the same scaling server-side, so this is zero quality
124
+ * loss — we just do it pre-flight to keep request payloads small and avoid
125
+ * 413 "request too large" errors when many images accumulate in context.
126
+ *
127
+ * Results are cached on disk by content hash so repeated sends of the same
128
+ * image (or daemon restarts) skip the sips call entirely.
129
+ */
130
+ export function optimizeImageForTransport(
131
+ base64Data: string,
132
+ mediaType: string,
133
+ ): { data: string; mediaType: string } {
134
+ const rawBytes = Buffer.from(base64Data, "base64");
135
+
136
+ // Small images don't need optimization.
137
+ if (rawBytes.length <= OPTIMIZE_THRESHOLD_BYTES) {
138
+ return { data: base64Data, mediaType };
139
+ }
140
+
141
+ // If dimensions are already within limits, skip.
142
+ const dims = parseImageDimensions(base64Data, mediaType);
143
+ if (
144
+ dims &&
145
+ dims.width <= MAX_DIMENSION &&
146
+ dims.height <= MAX_DIMENSION
147
+ ) {
148
+ return { data: base64Data, mediaType };
149
+ }
150
+
151
+ // Content-addressed cache lookup.
152
+ const hash = createHash("sha256").update(rawBytes).digest("hex");
153
+ const cacheKey = hash.slice(0, 16);
154
+ const cached = readFromCache(cacheKey);
155
+ if (cached) return cached;
156
+
157
+ // Run sips (macOS). On other platforms this gracefully returns null.
158
+ const optimized = runSips(rawBytes);
159
+ if (!optimized) {
160
+ return { data: base64Data, mediaType };
161
+ }
162
+
163
+ writeToCache(cacheKey, optimized);
164
+ return { data: optimized.toString("base64"), mediaType: "image/jpeg" };
165
+ }
package/src/agent/loop.ts CHANGED
@@ -31,6 +31,8 @@ export interface AgentLoopConfig {
31
31
  | { type: "tool"; name: string };
32
32
  /** Minimum interval (ms) between consecutive LLM calls to prevent spin when tools return instantly */
33
33
  minTurnIntervalMs?: number;
34
+ /** Override the default prompt cache TTL sent to the provider (e.g. "5m" for short-lived subagents). */
35
+ cacheTtl?: "5m" | "1h";
34
36
  }
35
37
 
36
38
  export interface CheckpointInfo {
@@ -101,10 +103,7 @@ const DEFAULT_CONFIG: AgentLoopConfig = {
101
103
  minTurnIntervalMs: 150,
102
104
  };
103
105
 
104
- const PROGRESS_CHECK_INTERVAL = 5;
105
106
  const MAX_CONSECUTIVE_ERROR_NUDGES = 3;
106
- const PROGRESS_CHECK_REMINDER =
107
- "You have been using tools for several turns. Check whether you are making meaningful progress toward the user's goal. If you are stuck in a loop or not making progress, summarize what you have tried and ask the user for guidance instead of continuing.";
108
107
 
109
108
  export interface ResolvedSystemPrompt {
110
109
  systemPrompt: string;
@@ -255,6 +254,10 @@ export class AgentLoop {
255
254
  providerConfig.tool_choice = this.config.toolChoice;
256
255
  }
257
256
 
257
+ if (this.config.cacheTtl) {
258
+ providerConfig.cacheTtl = this.config.cacheTtl;
259
+ }
260
+
258
261
  const preLlmResult = await getHookManager().trigger("pre-llm-call", {
259
262
  systemPrompt: turnSystemPrompt,
260
263
  messages: history,
@@ -573,21 +576,11 @@ export class AgentLoop {
573
576
  break;
574
577
  }
575
578
 
576
- // Track tool-use turns and inject progress reminder every N turns
577
579
  toolUseTurns++;
578
- const isProgressCheckTurn =
579
- toolUseTurns % PROGRESS_CHECK_INTERVAL === 0;
580
- if (isProgressCheckTurn) {
581
- resultBlocks.push({
582
- type: "text",
583
- text: `<system_notice>${PROGRESS_CHECK_REMINDER}</system_notice>`,
584
- });
585
- }
586
580
 
587
581
  // When any tool returned an error, nudge the LLM to retry with
588
582
  // corrected parameters instead of ending its turn. Skip the nudge
589
- // when the progress check fires (to avoid contradictory instructions)
590
- // and after MAX_CONSECUTIVE_ERROR_NUDGES consecutive error turns
583
+ // after MAX_CONSECUTIVE_ERROR_NUDGES consecutive error turns
591
584
  // (the error is likely unrecoverable at that point).
592
585
  const hasToolError = toolResults.some(({ result }) => result.isError);
593
586
  if (hasToolError) {
@@ -597,7 +590,6 @@ export class AgentLoop {
597
590
  }
598
591
  if (
599
592
  hasToolError &&
600
- !isProgressCheckTurn &&
601
593
  consecutiveErrorTurns <= MAX_CONSECUTIVE_ERROR_NUDGES
602
594
  ) {
603
595
  resultBlocks.push({
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { answerCall } from "../calls/call-domain.js";
15
15
  import { getGatewayInternalBaseUrl } from "../config/env.js";
16
+ import { findContactChannel } from "../contacts/contact-store.js";
16
17
  import { upsertContactChannel } from "../contacts/contacts-write.js";
17
18
  import {
18
19
  type CanonicalGuardianRequest,
@@ -396,6 +397,25 @@ const accessRequestResolver: GuardianRequestResolver = {
396
397
  const desktopDeliverUrl = resolveDeliverCallbackUrlForChannel(channel);
397
398
  const desktopBearerToken = mintDaemonDeliveryToken();
398
399
 
400
+ // Resolve display names from the contacts database for enriched payloads
401
+ const requesterContactResult = requesterExternalUserId
402
+ ? findContactChannel({
403
+ channelType: channel,
404
+ externalUserId: requesterExternalUserId,
405
+ })
406
+ : null;
407
+ const requesterDisplayName =
408
+ requesterContactResult?.contact.displayName ?? null;
409
+
410
+ const decidedByContactResult = decidedByExternalUserId
411
+ ? findContactChannel({
412
+ channelType: channel,
413
+ externalUserId: decidedByExternalUserId,
414
+ })
415
+ : null;
416
+ const decidedByDisplayName =
417
+ decidedByContactResult?.contact.displayName ?? null;
418
+
399
419
  if (decision.action === "reject") {
400
420
  log.info(
401
421
  { event: "resolver_access_request_denied", requestId: request.id },
@@ -435,6 +455,8 @@ const accessRequestResolver: GuardianRequestResolver = {
435
455
  requesterExternalUserId,
436
456
  requesterChatId,
437
457
  decidedByExternalUserId,
458
+ requesterDisplayName,
459
+ decidedByDisplayName,
438
460
  decision: "denied" as const,
439
461
  };
440
462
 
@@ -726,6 +748,8 @@ const accessRequestResolver: GuardianRequestResolver = {
726
748
  sourceChannel: channel,
727
749
  requesterExternalUserId,
728
750
  requesterChatId,
751
+ requesterDisplayName,
752
+ decidedByDisplayName,
729
753
  verificationSessionId: session.sessionId,
730
754
  },
731
755
  dedupeKey: `trusted-contact:verification-sent:${session.sessionId}`,