@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.5.16",
3
+ "version": "0.6.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -70,6 +70,10 @@
70
70
  "@vellumai/credential-storage",
71
71
  "@vellumai/egress-proxy"
72
72
  ],
73
+ "overrides": {
74
+ "lodash": "^4.18.0",
75
+ "path-to-regexp": "^8.4.0"
76
+ },
73
77
  "devDependencies": {
74
78
  "@types/archiver": "^7.0.0",
75
79
  "@types/bun": "^1.2.4",
@@ -75,6 +75,9 @@ const loopbackServer = mockServer("127.0.0.1");
75
75
  /** Mock non-loopback server -- returns a public IP for all requests. */
76
76
  const nonLoopbackServer = mockServer("203.0.113.50");
77
77
 
78
+ /** Mock LAN peer -- returns a private RFC 1918 IP (not loopback). */
79
+ const lanPeerServer = mockServer("192.168.1.100");
80
+
78
81
  initializeDb();
79
82
 
80
83
  beforeEach(() => {
@@ -649,6 +652,25 @@ describe("pairing credential flow", () => {
649
652
  // ---------------------------------------------------------------------------
650
653
 
651
654
  describe("bootstrap private-network guard", () => {
655
+ test("rejects bootstrap request with private X-Forwarded-For", async () => {
656
+ const { handleGuardianBootstrap } =
657
+ await import("../runtime/routes/guardian-bootstrap-routes.js");
658
+
659
+ const req = new Request("http://localhost/v1/guardian/init", {
660
+ method: "POST",
661
+ headers: {
662
+ "Content-Type": "application/json",
663
+ "X-Forwarded-For": "192.168.1.10",
664
+ },
665
+ body: JSON.stringify({ platform: "macos", deviceId: "test-device" }),
666
+ });
667
+
668
+ const res = await handleGuardianBootstrap(req, loopbackServer);
669
+ expect(res.status).toBe(403);
670
+ const body = (await res.json()) as { error: { message: string } };
671
+ expect(body.error.message).toContain("local-only");
672
+ });
673
+
652
674
  test("rejects bootstrap request with public X-Forwarded-For", async () => {
653
675
  const { handleGuardianBootstrap } =
654
676
  await import("../runtime/routes/guardian-bootstrap-routes.js");
@@ -697,4 +719,50 @@ describe("bootstrap private-network guard", () => {
697
719
  const res = await handleGuardianBootstrap(req, loopbackServer);
698
720
  expect(res.status).toBe(200);
699
721
  });
722
+
723
+ test("rejects LAN peer in non-containerized mode", async () => {
724
+ // Default IS_CONTAINERIZED is unset (non-containerized).
725
+ delete process.env.IS_CONTAINERIZED;
726
+
727
+ const { handleGuardianBootstrap } =
728
+ await import("../runtime/routes/guardian-bootstrap-routes.js");
729
+
730
+ const req = new Request("http://localhost/v1/guardian/init", {
731
+ method: "POST",
732
+ headers: { "Content-Type": "application/json" },
733
+ body: JSON.stringify({ platform: "macos", deviceId: "test-device-lan" }),
734
+ });
735
+
736
+ const res = await handleGuardianBootstrap(req, lanPeerServer);
737
+ expect(res.status).toBe(403);
738
+ const body = (await res.json()) as { error: { message: string } };
739
+ expect(body.error.message).toContain("local-only");
740
+ });
741
+
742
+ test("accepts LAN peer in containerized mode", async () => {
743
+ const prev = process.env.IS_CONTAINERIZED;
744
+ process.env.IS_CONTAINERIZED = "true";
745
+ try {
746
+ const { handleGuardianBootstrap } =
747
+ await import("../runtime/routes/guardian-bootstrap-routes.js");
748
+
749
+ const req = new Request("http://localhost/v1/guardian/init", {
750
+ method: "POST",
751
+ headers: { "Content-Type": "application/json" },
752
+ body: JSON.stringify({
753
+ platform: "macos",
754
+ deviceId: "test-device-docker",
755
+ }),
756
+ });
757
+
758
+ const res = await handleGuardianBootstrap(req, lanPeerServer);
759
+ expect(res.status).toBe(200);
760
+ } finally {
761
+ if (prev === undefined) {
762
+ delete process.env.IS_CONTAINERIZED;
763
+ } else {
764
+ process.env.IS_CONTAINERIZED = prev;
765
+ }
766
+ }
767
+ });
700
768
  });
@@ -468,38 +468,6 @@ describe("AgentLoop", () => {
468
468
  ).toBe(false);
469
469
  });
470
470
 
471
- // 8. Progress reminder injection every 5 tool-use turns
472
- test("injects progress reminder after every 5 tool-use turns", async () => {
473
- // Create 6 tool responses followed by a text response
474
- const responses: ProviderResponse[] = [];
475
- for (let i = 0; i < 6; i++) {
476
- responses.push(
477
- toolUseResponse(`t${i}`, "read_file", { path: `/file${i}.txt` }),
478
- );
479
- }
480
- responses.push(textResponse("Finally done"));
481
-
482
- const { provider, calls } = createMockProvider(responses);
483
- const toolExecutor = async () => ({ content: "data", isError: false });
484
- const loop = new AgentLoop(
485
- provider,
486
- "system",
487
- {},
488
- dummyTools,
489
- toolExecutor,
490
- );
491
-
492
- await loop.run([userMessage], () => {});
493
-
494
- // After the 5th tool-use turn, the user message should contain a progress reminder
495
- // calls[5] is the 6th provider call; its messages[-1] should have the reminder
496
- const fifthTurnResultMsg = calls[5].messages[calls[5].messages.length - 1];
497
- const reminderBlock = fifthTurnResultMsg.content.find(
498
- (b): b is Extract<ContentBlock, { type: "text" }> =>
499
- b.type === "text" && b.text.includes("making meaningful progress"),
500
- );
501
- expect(reminderBlock).toBeDefined();
502
- });
503
471
 
504
472
  // 9. Tool executor error results are forwarded correctly
505
473
  test("forwards tool error results to provider", async () => {
@@ -56,8 +56,8 @@ describe("always-loaded tool count", () => {
56
56
  "file_edit",
57
57
  "file_read",
58
58
  "file_write",
59
- "memory_manage",
60
- "memory_recall",
59
+ "recall",
60
+ "remember",
61
61
  "skill_execute",
62
62
  "skill_load",
63
63
  "web_fetch",
@@ -187,6 +187,58 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
187
187
  expect(system[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
188
188
  });
189
189
 
190
+ test("drops static system block cache_control when total would exceed 4", async () => {
191
+ const staticBlock = "You are a helpful assistant.";
192
+ const dynamicBlock = "User workspace files here.";
193
+ const prompt = staticBlock + SYSTEM_PROMPT_CACHE_BOUNDARY + dynamicBlock;
194
+
195
+ // Boundary (2 system) + tools (1) + turn-start (1) + tail (1) = 5 → must cap at 4
196
+ const messages: Message[] = [
197
+ userMsg("Do something"),
198
+ toolUseMsg("tu_1", "bash"),
199
+ toolResultMsg("tu_1", "output"),
200
+ ];
201
+ await provider.sendMessage(messages, sampleTools, prompt);
202
+
203
+ const system = lastStreamParams!.system as Array<{
204
+ type: string;
205
+ text: string;
206
+ cache_control?: { type: string; ttl?: string };
207
+ }>;
208
+ expect(system).toHaveLength(2);
209
+ // Static block's cache_control dropped (small, cheap to re-read)
210
+ expect(system[0].cache_control).toBeUndefined();
211
+ // Dynamic block keeps its cache_control
212
+ expect(system[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
213
+
214
+ // Tools breakpoint still present
215
+ const tools = lastStreamParams!.tools as Array<{
216
+ cache_control?: { type: string; ttl?: string };
217
+ }>;
218
+ expect(tools[tools.length - 1].cache_control).toEqual({
219
+ type: "ephemeral",
220
+ ttl: "1h",
221
+ });
222
+
223
+ // Turn-start + tail breakpoints still present
224
+ const sent = lastStreamParams!.messages as Array<{
225
+ role: string;
226
+ content: Array<{
227
+ type: string;
228
+ cache_control?: { type: string; ttl?: string };
229
+ }>;
230
+ }>;
231
+ const turnStart = sent[0];
232
+ expect(
233
+ turnStart.content[turnStart.content.length - 1].cache_control,
234
+ ).toEqual({ type: "ephemeral", ttl: "1h" });
235
+ const lastMsg = sent[sent.length - 1];
236
+ expect(lastMsg.content[lastMsg.content.length - 1].cache_control).toEqual({
237
+ type: "ephemeral",
238
+ ttl: "5m",
239
+ });
240
+ });
241
+
190
242
  // -----------------------------------------------------------------------
191
243
  // Tool cache control
192
244
  // -----------------------------------------------------------------------
@@ -225,33 +277,22 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
225
277
  });
226
278
 
227
279
  // -----------------------------------------------------------------------
228
- // User turn cache breakpoints — second-to-last user turn only
280
+ // Advancing tail — 5m cache on last block after turn-starting message
229
281
  // -----------------------------------------------------------------------
230
- test("single user turn does NOT get cache_control (no second-to-last)", async () => {
282
+ test("no advancing tail cache when turn-starting user message is last", async () => {
231
283
  await provider.sendMessage([userMsg("Hello")]);
232
284
 
233
- const messages = lastStreamParams!.messages as Array<{
234
- role: string;
235
- content: Array<{
236
- type: string;
237
- text: string;
238
- cache_control?: { type: string; ttl?: string };
239
- }>;
240
- }>;
241
- const lastUser = messages[messages.length - 1];
242
- expect(lastUser.role).toBe("user");
285
+ // No top-level cache_control would conflict with the 1h block breakpoint
243
286
  expect(
244
- lastUser.content[lastUser.content.length - 1].cache_control,
287
+ (lastStreamParams as Record<string, unknown>).cache_control,
245
288
  ).toBeUndefined();
246
289
  });
247
290
 
248
- test("second-to-last user turn gets cache_control, others do not", async () => {
291
+ test("advancing tail: 5m cache on last block when tool results follow turn-starting message", async () => {
249
292
  const messages: Message[] = [
250
- userMsg("Turn 1"), // user turn 0 — no cache
251
- assistantMsg("Response 1"),
252
- userMsg("Turn 2"), // user turn 1 — cache (second-to-last)
253
- assistantMsg("Response 2"),
254
- userMsg("Turn 3"), // user turn 2 — no cache (last)
293
+ userMsg("Do something"),
294
+ toolUseMsg("tu_1", "bash"),
295
+ toolResultMsg("tu_1", "output"),
255
296
  ];
256
297
  await provider.sendMessage(messages);
257
298
 
@@ -259,33 +300,30 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
259
300
  role: string;
260
301
  content: Array<{
261
302
  type: string;
262
- text: string;
263
303
  cache_control?: { type: string; ttl?: string };
264
304
  }>;
265
305
  }>;
266
306
 
267
- // Find user messages in order
268
- const userMessages = sent.filter((m) => m.role === "user");
269
- expect(userMessages).toHaveLength(3);
270
-
271
- // First user turn: no cache_control
272
- const firstUserLastBlock =
273
- userMessages[0].content[userMessages[0].content.length - 1];
274
- expect(firstUserLastBlock.cache_control).toBeUndefined();
275
-
276
- // Second user turn (second-to-last): cache_control ephemeral
277
- const secondUserLastBlock =
278
- userMessages[1].content[userMessages[1].content.length - 1];
279
- expect(secondUserLastBlock.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
280
-
281
- // Third user turn (last): no cache_control
282
- const thirdUserLastBlock =
283
- userMessages[2].content[userMessages[2].content.length - 1];
284
- expect(thirdUserLastBlock.cache_control).toBeUndefined();
307
+ // Turn-starting user message (first) keeps 1h
308
+ const turnStart = sent[0];
309
+ const turnStartLast = turnStart.content[turnStart.content.length - 1];
310
+ expect(turnStartLast.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
311
+
312
+ // Last message (tool_result) gets 5m advancing tail
313
+ const lastMessage = sent[sent.length - 1];
314
+ const lastBlock = lastMessage.content[lastMessage.content.length - 1];
315
+ expect(lastBlock.cache_control).toEqual({ type: "ephemeral", ttl: "5m" });
285
316
  });
286
317
 
287
- test("single user turn does NOT get cache_control (only one user = no second-to-last)", async () => {
288
- await provider.sendMessage([userMsg("Only turn")]);
318
+ test("turn-starting user message gets 1h cache on last block", async () => {
319
+ const messages: Message[] = [
320
+ userMsg("Turn 1"),
321
+ assistantMsg("Response 1"),
322
+ userMsg("Turn 2"),
323
+ assistantMsg("Response 2"),
324
+ userMsg("Turn 3"),
325
+ ];
326
+ await provider.sendMessage(messages);
289
327
 
290
328
  const sent = lastStreamParams!.messages as Array<{
291
329
  role: string;
@@ -295,35 +333,17 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
295
333
  cache_control?: { type: string; ttl?: string };
296
334
  }>;
297
335
  }>;
298
- const userMessages = sent.filter((m) => m.role === "user");
299
- expect(userMessages).toHaveLength(1);
300
- expect(
301
- userMessages[0].content[userMessages[0].content.length - 1].cache_control,
302
- ).toBeUndefined();
303
- });
304
-
305
- // -----------------------------------------------------------------------
306
- // User turn with tool_result — cache breakpoint on second-to-last
307
- // -----------------------------------------------------------------------
308
- test("user turn containing tool_result gets cache_control on second-to-last user turn only", async () => {
309
- const messages: Message[] = [
310
- userMsg("Read file"),
311
- toolUseMsg("tu_1", "file_read"),
312
- toolResultMsg("tu_1", "file contents here"),
313
- ];
314
- await provider.sendMessage(messages);
315
336
 
316
- const sent = lastStreamParams!.messages as Array<{
317
- role: string;
318
- content: Array<{ type: string; cache_control?: { type: string; ttl?: string } }>;
319
- }>;
320
- const userMsgs = sent.filter((m) => m.role === "user");
321
- // First user msg (second-to-last) should get cache
322
- const firstLast = userMsgs[0].content[userMsgs[0].content.length - 1];
323
- expect(firstLast.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
324
- // tool_result msg (last) should NOT get cache
325
- const secondLast = userMsgs[1].content[userMsgs[1].content.length - 1];
326
- expect(secondLast.cache_control).toBeUndefined();
337
+ const userMessages = sent.filter((m) => m.role === "user");
338
+ // Only the last user message (turn-starting) gets cache_control
339
+ for (const user of userMessages.slice(0, -1)) {
340
+ for (const block of user.content) {
341
+ expect(block.cache_control).toBeUndefined();
342
+ }
343
+ }
344
+ const lastUser = userMessages[userMessages.length - 1];
345
+ const lastBlock = lastUser.content[lastUser.content.length - 1];
346
+ expect(lastBlock.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
327
347
  });
328
348
 
329
349
  // -----------------------------------------------------------------------
@@ -358,7 +378,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
358
378
  // -----------------------------------------------------------------------
359
379
  // Multi-block user message: cache lands on LAST block
360
380
  // -----------------------------------------------------------------------
361
- test("multi-block single user message does NOT get cache (no second-to-last)", async () => {
381
+ test("multi-block single user message gets cache on last block", async () => {
362
382
  const multiBlockUser: Message = {
363
383
  role: "user",
364
384
  content: [
@@ -378,7 +398,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
378
398
  }>;
379
399
  const user = sent[0];
380
400
  expect(user.content[0].cache_control).toBeUndefined();
381
- expect(user.content[1].cache_control).toBeUndefined();
401
+ expect(user.content[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
382
402
  });
383
403
 
384
404
  // -----------------------------------------------------------------------
@@ -395,14 +415,14 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
395
415
  // -----------------------------------------------------------------------
396
416
  // Cache compatibility with workspace context injection
397
417
  // -----------------------------------------------------------------------
398
- test("workspace-prepended single user message does NOT get cache (no second-to-last)", async () => {
418
+ test("workspace-prepended single user message gets cache on last block", async () => {
399
419
  // Simulates what applyRuntimeInjections does: prepend workspace block, keep user text as trailing
400
420
  const workspaceInjectedUser: Message = {
401
421
  role: "user",
402
422
  content: [
403
423
  {
404
424
  type: "text",
405
- text: "<workspace_top_level>\nRoot: /sandbox\nDirectories: src, tests\n</workspace_top_level>",
425
+ text: "<workspace>\nRoot: /sandbox\nDirectories: src, tests\n</workspace>",
406
426
  },
407
427
  { type: "text", text: "What files are in src?" },
408
428
  ],
@@ -421,18 +441,18 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
421
441
  expect(user.content).toHaveLength(2);
422
442
  // Workspace block (first): no cache_control
423
443
  expect(user.content[0].cache_control).toBeUndefined();
424
- // User text (last): no cache_control (single user turn = no second-to-last)
425
- expect(user.content[1].cache_control).toBeUndefined();
444
+ // User text (last): cache_control with 1h TTL
445
+ expect(user.content[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
426
446
  });
427
447
 
428
- test("workspace + multi-block single user message: no cache (no second-to-last)", async () => {
448
+ test("workspace + multi-block single user message: cache on last block only", async () => {
429
449
  // Simulates workspace prepended + extra context block appended
430
450
  const injectedUser: Message = {
431
451
  role: "user",
432
452
  content: [
433
453
  {
434
454
  type: "text",
435
- text: "<workspace_top_level>\nRoot: /sandbox\nDirectories: src, tests\n</workspace_top_level>",
455
+ text: "<workspace>\nRoot: /sandbox\nDirectories: src, tests\n</workspace>",
436
456
  },
437
457
  { type: "text", text: "Help me debug this" },
438
458
  {
@@ -453,10 +473,10 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
453
473
  }>;
454
474
  const user = sent[0];
455
475
  expect(user.content).toHaveLength(3);
456
- // No blocks get cache_control (single user turn = no second-to-last)
476
+ // Only last block gets cache_control
457
477
  expect(user.content[0].cache_control).toBeUndefined();
458
478
  expect(user.content[1].cache_control).toBeUndefined();
459
- expect(user.content[2].cache_control).toBeUndefined();
479
+ expect(user.content[2].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
460
480
  });
461
481
 
462
482
  // -----------------------------------------------------------------------
@@ -550,7 +570,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
550
570
  content: [
551
571
  {
552
572
  type: "text",
553
- text: "<workspace_top_level>\nRoot: /sandbox\n</workspace_top_level>",
573
+ text: "<workspace>\nRoot: /sandbox\n</workspace>",
554
574
  },
555
575
  {
556
576
  type: "tool_result",
@@ -931,7 +951,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
931
951
 
932
952
  // Assistant message should have tool_use in paired portion, server_tool_use in carryover
933
953
  // ensureToolPairing splits: paired = [tool_use(tu_a)], carryover = [server_tool_use(srvtoolu_b)]
934
- // Result: assistant(tool_use) → user(tool_result) → assistant(server_tool_use) → user(continue)
954
+ // Result: assistant(tool_use) → user(tool_result) → assistant(server_tool_use) → user(synthetic_continuation)
935
955
  const assistantMsg = sent[1];
936
956
  expect(assistantMsg.role).toBe("assistant");
937
957
  expect(assistantMsg.content[0].type).toBe("tool_use");
@@ -1258,7 +1278,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
1258
1278
  // 2. assistant(tool_use)
1259
1279
  // 3. user(tool_result)
1260
1280
  // 4. assistant(Checking the file now.)
1261
- // 5. user((continue)) <-- synthetic user message to maintain alternation
1281
+ // 5. user(<synthetic_continuation __injected />) <-- synthetic user message to maintain alternation
1262
1282
  // 6. assistant(Next response)
1263
1283
  expect(sent).toHaveLength(6);
1264
1284
  expect(sent[0].role).toBe("user");
@@ -1271,44 +1291,95 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
1271
1291
  expect(sent[3].content[0].text).toBe("Checking the file now.");
1272
1292
  expect(sent[4].role).toBe("user");
1273
1293
  expect(sent[4].content[0].type).toBe("text");
1274
- expect(sent[4].content[0].text).toBe("(continue)");
1294
+ expect(sent[4].content[0].text).toBe(
1295
+ "<synthetic_continuation __injected />",
1296
+ );
1275
1297
  expect(sent[5].role).toBe("assistant");
1276
1298
  expect(sent[5].content[0].text).toBe("Next response");
1277
1299
  });
1278
1300
 
1279
- test("multi-turn with workspace injection: cache on second-to-last user turn only", async () => {
1301
+ test("carryover with tool_result-only user turn skips synthetic when next message is user", async () => {
1302
+ // When the user turn after the consumed pair is already a user message,
1303
+ // the synthetic continuation is unnecessary — the next user message
1304
+ // naturally maintains alternation after the carryover assistant message.
1305
+ const messages: Message[] = [
1306
+ userMsg("Read file"),
1307
+ {
1308
+ role: "assistant",
1309
+ content: [
1310
+ { type: "tool_use", id: "tu_1", name: "file_read", input: {} },
1311
+ { type: "text", text: "Checking the file now." }, // carryover content
1312
+ ],
1313
+ },
1314
+ {
1315
+ role: "user",
1316
+ content: [
1317
+ // ONLY tool_result, no other content
1318
+ {
1319
+ type: "tool_result",
1320
+ tool_use_id: "tu_1",
1321
+ content: "file contents",
1322
+ is_error: false,
1323
+ },
1324
+ ],
1325
+ },
1326
+ userMsg("Follow-up question"), // next message is user — no synthetic needed
1327
+ ];
1328
+ await provider.sendMessage(messages);
1329
+
1330
+ const sent = lastStreamParams!.messages as Array<{
1331
+ role: string;
1332
+ content: Array<{ type: string; text?: string; tool_use_id?: string }>;
1333
+ }>;
1334
+
1335
+ // Expected structure:
1336
+ // 1. user(Read file)
1337
+ // 2. assistant(tool_use)
1338
+ // 3. user(tool_result)
1339
+ // 4. assistant(Checking the file now.)
1340
+ // 5. user(Follow-up question) <-- real user message, NO synthetic continuation
1341
+ expect(sent).toHaveLength(5);
1342
+ expect(sent[0].role).toBe("user");
1343
+ expect(sent[1].role).toBe("assistant");
1344
+ expect(sent[1].content[0].type).toBe("tool_use");
1345
+ expect(sent[2].role).toBe("user");
1346
+ expect(sent[2].content[0].type).toBe("tool_result");
1347
+ expect(sent[3].role).toBe("assistant");
1348
+ expect(sent[3].content[0].text).toBe("Checking the file now.");
1349
+ expect(sent[4].role).toBe("user");
1350
+ expect(sent[4].content[0].text).toBe("Follow-up question");
1351
+ });
1352
+
1353
+ test("multi-turn with workspace injection: only last user message gets 1h cache", async () => {
1280
1354
  const messages: Message[] = [
1281
- // Turn 1: workspace + user text (no cache - 3rd-to-last)
1282
1355
  {
1283
1356
  role: "user",
1284
1357
  content: [
1285
1358
  {
1286
1359
  type: "text",
1287
- text: "<workspace_top_level>\nRoot: /sandbox\nDirectories: src\n</workspace_top_level>",
1360
+ text: "<workspace>\nRoot: /sandbox\nDirectories: src\n</workspace>",
1288
1361
  },
1289
1362
  { type: "text", text: "Turn 1" },
1290
1363
  ],
1291
1364
  },
1292
1365
  assistantMsg("Response 1"),
1293
- // Turn 2: workspace + user text (cache - second-to-last)
1294
1366
  {
1295
1367
  role: "user",
1296
1368
  content: [
1297
1369
  {
1298
1370
  type: "text",
1299
- text: "<workspace_top_level>\nRoot: /sandbox\nDirectories: src, lib\n</workspace_top_level>",
1371
+ text: "<workspace>\nRoot: /sandbox\nDirectories: src, lib\n</workspace>",
1300
1372
  },
1301
1373
  { type: "text", text: "Turn 2" },
1302
1374
  ],
1303
1375
  },
1304
1376
  assistantMsg("Response 2"),
1305
- // Turn 3: workspace + user text (no cache - last)
1306
1377
  {
1307
1378
  role: "user",
1308
1379
  content: [
1309
1380
  {
1310
1381
  type: "text",
1311
- text: "<workspace_top_level>\nRoot: /sandbox\nDirectories: src, lib, docs\n</workspace_top_level>",
1382
+ text: "<workspace>\nRoot: /sandbox\nDirectories: src, lib, docs\n</workspace>",
1312
1383
  },
1313
1384
  { type: "text", text: "Turn 3" },
1314
1385
  ],
@@ -1327,17 +1398,65 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
1327
1398
  const userMsgs = sent.filter((m) => m.role === "user");
1328
1399
  expect(userMsgs).toHaveLength(3);
1329
1400
 
1330
- // Turn 1: no cache on any block
1331
- expect(userMsgs[0].content[0].cache_control).toBeUndefined();
1332
- expect(userMsgs[0].content[1].cache_control).toBeUndefined();
1401
+ // Earlier user messages: no cache_control
1402
+ for (const user of userMsgs.slice(0, -1)) {
1403
+ for (const block of user.content) {
1404
+ expect(block.cache_control).toBeUndefined();
1405
+ }
1406
+ }
1407
+
1408
+ // Last user message (turn 3): 1h cache on last block only
1409
+ const lastUser = userMsgs[userMsgs.length - 1];
1410
+ expect(lastUser.content[0].cache_control).toBeUndefined();
1411
+ expect(lastUser.content[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
1412
+
1413
+ // No top-level cache_control — breakpoints are set directly on blocks
1414
+ expect(
1415
+ (lastStreamParams as Record<string, unknown>).cache_control,
1416
+ ).toBeUndefined();
1417
+ });
1418
+
1419
+ test("tool loop: turn-starting user message gets 1h cache, last tool_result gets 5m advancing tail", async () => {
1420
+ const messages: Message[] = [
1421
+ userMsg("Read the config file"),
1422
+ toolUseMsg("tu_1", "file_read"),
1423
+ toolResultMsg("tu_1", "config contents here"),
1424
+ toolUseMsg("tu_2", "file_read"),
1425
+ toolResultMsg("tu_2", "more contents"),
1426
+ ];
1427
+ await provider.sendMessage(messages);
1428
+
1429
+ const sent = lastStreamParams!.messages as Array<{
1430
+ role: string;
1431
+ content: Array<{
1432
+ type: string;
1433
+ text?: string;
1434
+ cache_control?: { type: string; ttl?: string };
1435
+ }>;
1436
+ }>;
1333
1437
 
1334
- // Turn 2 (second-to-last): cache on last block only
1335
- expect(userMsgs[1].content[0].cache_control).toBeUndefined();
1336
- expect(userMsgs[1].content[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
1438
+ // First message is the turn-starting user text gets 1h cache
1439
+ expect(sent[0].role).toBe("user");
1440
+ expect(sent[0].content[0].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
1441
+
1442
+ // Non-last tool result messages do NOT get cache_control
1443
+ const toolResultMsgs = sent.filter(
1444
+ (m) =>
1445
+ m.role === "user" &&
1446
+ Array.isArray(m.content) &&
1447
+ m.content.every((b) => typeof b !== "string" && b.type === "tool_result"),
1448
+ );
1449
+ expect(toolResultMsgs.length).toBeGreaterThan(0);
1450
+ for (const tr of toolResultMsgs.slice(0, -1)) {
1451
+ for (const block of tr.content) {
1452
+ expect(block.cache_control).toBeUndefined();
1453
+ }
1454
+ }
1337
1455
 
1338
- // Turn 3 (last): no cache
1339
- expect(userMsgs[2].content[0].cache_control).toBeUndefined();
1340
- expect(userMsgs[2].content[1].cache_control).toBeUndefined();
1456
+ // Last message gets 5m advancing tail cache on its last block
1457
+ const lastMsg = sent[sent.length - 1];
1458
+ const lastBlock = lastMsg.content[lastMsg.content.length - 1];
1459
+ expect(lastBlock.cache_control).toEqual({ type: "ephemeral", ttl: "5m" });
1341
1460
  });
1342
1461
 
1343
1462
  // -----------------------------------------------------------------------