@vellumai/assistant 0.5.13 → 0.5.14

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 (425) hide show
  1. package/.env.example +1 -6
  2. package/AGENTS.md +4 -0
  3. package/ARCHITECTURE.md +0 -1
  4. package/bunfig.toml +1 -0
  5. package/docs/architecture/memory.md +3 -3
  6. package/openapi.yaml +127 -22
  7. package/package.json +1 -1
  8. package/src/__tests__/access-request-decision.test.ts +2 -32
  9. package/src/__tests__/actor-token-service.test.ts +1 -31
  10. package/src/__tests__/anthropic-provider.test.ts +53 -40
  11. package/src/__tests__/app-git-history.test.ts +9 -17
  12. package/src/__tests__/app-git-service.test.ts +14 -20
  13. package/src/__tests__/app-store-dir-names.test.ts +10 -20
  14. package/src/__tests__/approval-cascade.test.ts +2 -19
  15. package/src/__tests__/approval-primitive.test.ts +2 -27
  16. package/src/__tests__/approval-routes-http.test.ts +2 -30
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -28
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -45
  19. package/src/__tests__/attachments-store.test.ts +5 -32
  20. package/src/__tests__/audit-log-rotation.test.ts +5 -36
  21. package/src/__tests__/avatar-e2e.test.ts +1 -9
  22. package/src/__tests__/avatar-generator.test.ts +1 -7
  23. package/src/__tests__/browser-fill-credential.test.ts +0 -4
  24. package/src/__tests__/browser-manager.test.ts +0 -6
  25. package/src/__tests__/call-controller.test.ts +1 -22
  26. package/src/__tests__/call-conversation-messages.test.ts +0 -21
  27. package/src/__tests__/call-domain.test.ts +0 -25
  28. package/src/__tests__/call-pointer-messages.test.ts +0 -21
  29. package/src/__tests__/call-recovery.test.ts +0 -22
  30. package/src/__tests__/call-routes-http.test.ts +0 -24
  31. package/src/__tests__/call-store.test.ts +0 -21
  32. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -24
  33. package/src/__tests__/canonical-guardian-store.test.ts +48 -21
  34. package/src/__tests__/channel-approval-routes.test.ts +6 -26
  35. package/src/__tests__/channel-approvals.test.ts +1 -38
  36. package/src/__tests__/channel-delivery-store.test.ts +0 -21
  37. package/src/__tests__/channel-guardian.test.ts +0 -26
  38. package/src/__tests__/channel-reply-delivery.test.ts +5 -0
  39. package/src/__tests__/channel-retry-sweep.test.ts +0 -21
  40. package/src/__tests__/checker.test.ts +26 -61
  41. package/src/__tests__/clawhub.test.ts +9 -25
  42. package/src/__tests__/cli-command-risk-guard.test.ts +0 -18
  43. package/src/__tests__/config-loader-backfill.test.ts +9 -28
  44. package/src/__tests__/config-schema-cmd.test.ts +5 -25
  45. package/src/__tests__/config-schema.test.ts +21 -40
  46. package/src/__tests__/config-watcher.test.ts +4 -91
  47. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -21
  48. package/src/__tests__/contacts-tools.test.ts +0 -21
  49. package/src/__tests__/context-memory-e2e.test.ts +0 -21
  50. package/src/__tests__/context-window-manager.test.ts +130 -3
  51. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -4
  52. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -4
  53. package/src/__tests__/conversation-agent-loop.test.ts +0 -4
  54. package/src/__tests__/conversation-attachments.test.ts +1 -24
  55. package/src/__tests__/conversation-attention-store.test.ts +0 -21
  56. package/src/__tests__/conversation-attention-telegram.test.ts +0 -22
  57. package/src/__tests__/conversation-clear-safety.test.ts +0 -22
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -21
  59. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -24
  60. package/src/__tests__/conversation-disk-view-integration.test.ts +1 -23
  61. package/src/__tests__/conversation-disk-view.test.ts +5 -27
  62. package/src/__tests__/conversation-error.test.ts +1 -1
  63. package/src/__tests__/conversation-fork-crud.test.ts +1 -33
  64. package/src/__tests__/conversation-fork-route.test.ts +0 -27
  65. package/src/__tests__/conversation-history-web-search.test.ts +23 -16
  66. package/src/__tests__/conversation-init.benchmark.test.ts +22 -43
  67. package/src/__tests__/conversation-key-store-disk-view.test.ts +8 -34
  68. package/src/__tests__/conversation-load-history-repair.test.ts +0 -4
  69. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -4
  70. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -4
  71. package/src/__tests__/conversation-queue.test.ts +8 -8
  72. package/src/__tests__/conversation-routes-disk-view.test.ts +13 -51
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +64 -38
  74. package/src/__tests__/conversation-slash-commands.test.ts +5 -0
  75. package/src/__tests__/conversation-slash-queue.test.ts +0 -4
  76. package/src/__tests__/conversation-slash-unknown.test.ts +0 -4
  77. package/src/__tests__/conversation-speed-override.test.ts +326 -0
  78. package/src/__tests__/conversation-starter-routes.test.ts +0 -23
  79. package/src/__tests__/conversation-store.test.ts +0 -21
  80. package/src/__tests__/conversation-unread-route.test.ts +0 -24
  81. package/src/__tests__/conversation-usage.test.ts +56 -21
  82. package/src/__tests__/conversation-wipe.test.ts +0 -21
  83. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
  84. package/src/__tests__/conversation-workspace-injection.test.ts +0 -4
  85. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -4
  86. package/src/__tests__/credential-execution-shell-lockdown.test.ts +8 -5
  87. package/src/__tests__/credential-vault-unit.test.ts +9 -428
  88. package/src/__tests__/credentials-cli.test.ts +10 -10
  89. package/src/__tests__/daemon-assistant-events.test.ts +0 -19
  90. package/src/__tests__/date-context.test.ts +77 -97
  91. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +7 -24
  92. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +29 -42
  93. package/src/__tests__/delete-managed-skill-tool.test.ts +2 -10
  94. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -26
  95. package/src/__tests__/docker-signing-key-bootstrap.test.ts +61 -15
  96. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -36
  97. package/src/__tests__/email-cli.test.ts +6 -6
  98. package/src/__tests__/ephemeral-permissions.test.ts +5 -17
  99. package/src/__tests__/first-greeting.test.ts +4 -32
  100. package/src/__tests__/followup-tools.test.ts +0 -21
  101. package/src/__tests__/gateway-only-enforcement.test.ts +0 -20
  102. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -23
  103. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -23
  104. package/src/__tests__/guardian-action-followup-store.test.ts +0 -21
  105. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -21
  106. package/src/__tests__/guardian-action-late-reply.test.ts +0 -21
  107. package/src/__tests__/guardian-action-store.test.ts +0 -21
  108. package/src/__tests__/guardian-action-sweep.test.ts +0 -21
  109. package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -23
  110. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -22
  111. package/src/__tests__/guardian-dispatch.test.ts +0 -21
  112. package/src/__tests__/guardian-grant-minting.test.ts +0 -22
  113. package/src/__tests__/guardian-outbound-http.test.ts +0 -22
  114. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -23
  115. package/src/__tests__/guardian-routing-invariants.test.ts +0 -22
  116. package/src/__tests__/guardian-routing-state.test.ts +0 -22
  117. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -24
  118. package/src/__tests__/headless-browser-interactions.test.ts +0 -4
  119. package/src/__tests__/headless-browser-navigate.test.ts +0 -4
  120. package/src/__tests__/headless-browser-read-tools.test.ts +0 -4
  121. package/src/__tests__/headless-browser-snapshot.test.ts +0 -4
  122. package/src/__tests__/heartbeat-service.test.ts +99 -26
  123. package/src/__tests__/hooks-blocking.test.ts +3 -3
  124. package/src/__tests__/hooks-config.test.ts +7 -7
  125. package/src/__tests__/hooks-discovery.test.ts +3 -3
  126. package/src/__tests__/hooks-integration.test.ts +5 -5
  127. package/src/__tests__/hooks-manager.test.ts +3 -3
  128. package/src/__tests__/hooks-runner.test.ts +5 -23
  129. package/src/__tests__/hooks-settings.test.ts +3 -3
  130. package/src/__tests__/hooks-templates.test.ts +3 -3
  131. package/src/__tests__/http-conversation-lineage.test.ts +0 -27
  132. package/src/__tests__/identity-intro-cache.test.ts +0 -4
  133. package/src/__tests__/inbound-invite-redemption.test.ts +0 -22
  134. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -16
  135. package/src/__tests__/intent-routing.test.ts +2 -55
  136. package/src/__tests__/invite-redemption-service.test.ts +0 -21
  137. package/src/__tests__/invite-routes-http.test.ts +0 -21
  138. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +0 -17
  139. package/src/__tests__/journal-context.test.ts +8 -75
  140. package/src/__tests__/list-messages-attachments.test.ts +0 -22
  141. package/src/__tests__/llm-context-route-provider.test.ts +0 -21
  142. package/src/__tests__/llm-request-log-turn-query.test.ts +46 -28
  143. package/src/__tests__/llm-usage-store.test.ts +0 -21
  144. package/src/__tests__/log-export-workspace.test.ts +1 -1
  145. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
  146. package/src/__tests__/managed-store.test.ts +1 -1
  147. package/src/__tests__/mcp-cli.test.ts +7 -10
  148. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -21
  149. package/src/__tests__/memory-jobs-worker-backoff.test.ts +0 -11
  150. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -21
  151. package/src/__tests__/memory-recall-log-store.test.ts +0 -27
  152. package/src/__tests__/memory-recall-quality.test.ts +0 -21
  153. package/src/__tests__/memory-regressions.experimental.test.ts +31 -30
  154. package/src/__tests__/memory-regressions.test.ts +282 -70
  155. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -21
  156. package/src/__tests__/memory-upsert-concurrency.test.ts +0 -21
  157. package/src/__tests__/messaging-send-tool.test.ts +201 -0
  158. package/src/__tests__/migration-cross-version-compatibility.test.ts +18 -13
  159. package/src/__tests__/migration-export-http.test.ts +7 -1
  160. package/src/__tests__/migration-import-commit-http.test.ts +16 -14
  161. package/src/__tests__/migration-import-preflight-http.test.ts +27 -44
  162. package/src/__tests__/migration-validate-http.test.ts +1 -28
  163. package/src/__tests__/native-web-search.test.ts +25 -22
  164. package/src/__tests__/non-member-access-request.test.ts +0 -22
  165. package/src/__tests__/notification-guardian-path.test.ts +0 -21
  166. package/src/__tests__/notification-schedule-dedup.test.ts +1 -25
  167. package/src/__tests__/oauth-apps-routes.test.ts +103 -2
  168. package/src/__tests__/oauth-cli.test.ts +52 -0
  169. package/src/__tests__/oauth-provider-profiles.test.ts +0 -16
  170. package/src/__tests__/oauth-provider-serializer.test.ts +232 -0
  171. package/src/__tests__/oauth-providers-routes.test.ts +257 -0
  172. package/src/__tests__/oauth-store.test.ts +0 -21
  173. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  174. package/src/__tests__/openai-provider.test.ts +261 -0
  175. package/src/__tests__/pairing-concurrent.test.ts +6 -6
  176. package/src/__tests__/pairing-routes.test.ts +7 -1
  177. package/src/__tests__/path-policy.test.ts +1 -1
  178. package/src/__tests__/platform.test.ts +64 -88
  179. package/src/__tests__/playbook-execution.test.ts +0 -21
  180. package/src/__tests__/playbook-tools.test.ts +0 -21
  181. package/src/__tests__/pricing.test.ts +100 -0
  182. package/src/__tests__/relay-server.test.ts +1 -25
  183. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -24
  184. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -24
  185. package/src/__tests__/runtime-events-sse.test.ts +0 -24
  186. package/src/__tests__/sandbox-diagnostics.test.ts +2 -1
  187. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
  188. package/src/__tests__/schedule-store.test.ts +0 -21
  189. package/src/__tests__/schedule-tools.test.ts +0 -21
  190. package/src/__tests__/scheduler-recurrence.test.ts +0 -21
  191. package/src/__tests__/scoped-approval-grants.test.ts +0 -21
  192. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -21
  193. package/src/__tests__/secret-allowlist.test.ts +1 -1
  194. package/src/__tests__/secret-ingress-channel.test.ts +0 -5
  195. package/src/__tests__/secret-ingress-cli.test.ts +0 -6
  196. package/src/__tests__/secret-ingress-http.test.ts +0 -5
  197. package/src/__tests__/secret-ingress.test.ts +0 -5
  198. package/src/__tests__/send-endpoint-busy.test.ts +0 -24
  199. package/src/__tests__/sequence-store.test.ts +0 -21
  200. package/src/__tests__/server-history-render.test.ts +0 -24
  201. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -4
  202. package/src/__tests__/skill-load-inline-command.test.ts +9 -0
  203. package/src/__tests__/skill-load-inline-includes.test.ts +9 -0
  204. package/src/__tests__/skill-load-tool.test.ts +11 -0
  205. package/src/__tests__/skills-uninstall.test.ts +10 -8
  206. package/src/__tests__/skills.test.ts +1 -1
  207. package/src/__tests__/slack-channel-config.test.ts +1 -1
  208. package/src/__tests__/slack-inbound-verification.test.ts +0 -22
  209. package/src/__tests__/starter-bundle.test.ts +4 -1
  210. package/src/__tests__/suggestion-routes.test.ts +2 -0
  211. package/src/__tests__/system-prompt.test.ts +1 -1
  212. package/src/__tests__/terminal-tools.test.ts +1 -1
  213. package/src/__tests__/test-preload.ts +31 -0
  214. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
  215. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  216. package/src/__tests__/tool-executor.test.ts +0 -20
  217. package/src/__tests__/tool-input-summary.test.ts +124 -0
  218. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -1
  219. package/src/__tests__/trust-store.test.ts +7 -1
  220. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -1
  221. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -1
  222. package/src/__tests__/trusted-contact-multichannel.test.ts +1 -1
  223. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  224. package/src/__tests__/turn-boundary-resolution.test.ts +1 -1
  225. package/src/__tests__/twilio-routes.test.ts +1 -1
  226. package/src/__tests__/update-bulletin.test.ts +1 -1
  227. package/src/__tests__/vbundle-pax-and-symlink.test.ts +1 -1
  228. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -0
  229. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
  230. package/src/__tests__/voice-session-bridge.test.ts +1 -1
  231. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -4
  232. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
  233. package/src/__tests__/workspace-migration-down-functions.test.ts +15 -3
  234. package/src/__tests__/workspace-migration-seed-device-id.test.ts +40 -4
  235. package/src/agent/loop.ts +6 -9
  236. package/src/approvals/guardian-decision-primitive.ts +46 -18
  237. package/src/approvals/guardian-request-resolvers.ts +19 -2
  238. package/src/calls/active-call-lease.ts +2 -2
  239. package/src/cli/AGENTS.md +1 -1
  240. package/src/cli/commands/doctor.ts +9 -9
  241. package/src/cli/commands/memory.ts +142 -0
  242. package/src/cli/commands/oauth/__tests__/connect.test.ts +13 -11
  243. package/src/cli/commands/oauth/__tests__/ping.test.ts +1 -1
  244. package/src/cli/commands/oauth/connect.ts +13 -12
  245. package/src/cli/commands/oauth/index.ts +1 -1
  246. package/src/cli/commands/oauth/providers.ts +47 -62
  247. package/src/cli/commands/platform/__tests__/connect.test.ts +72 -46
  248. package/src/cli/commands/platform/__tests__/disconnect.test.ts +54 -1
  249. package/src/cli/commands/platform/__tests__/status.test.ts +36 -0
  250. package/src/cli/commands/platform/connect.ts +17 -7
  251. package/src/cli/commands/platform/disconnect.ts +28 -3
  252. package/src/cli/commands/platform/index.ts +3 -3
  253. package/src/cli.ts +1 -299
  254. package/src/config/assistant-feature-flags.ts +23 -15
  255. package/src/config/bundled-skills/app-builder/TOOLS.json +16 -0
  256. package/src/config/bundled-skills/app-builder/tools/app-create.ts +4 -0
  257. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +5 -1
  258. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +9 -1
  259. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +5 -1
  260. package/src/config/bundled-skills/contacts/TOOLS.json +8 -0
  261. package/src/config/bundled-skills/contacts/tools/contact-search.ts +10 -1
  262. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +16 -2
  263. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +1 -0
  264. package/src/config/bundled-skills/messaging/SKILL.md +7 -7
  265. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +37 -0
  266. package/src/config/bundled-skills/slack/SKILL.md +18 -0
  267. package/src/config/env-registry.ts +15 -11
  268. package/src/config/env.ts +1 -11
  269. package/src/config/feature-flag-registry.json +16 -0
  270. package/src/config/schema.ts +4 -0
  271. package/src/config/schemas/heartbeat.ts +6 -1
  272. package/src/config/schemas/inference.ts +14 -3
  273. package/src/config/schemas/memory-processing.ts +16 -8
  274. package/src/config/schemas/memory-retrieval.ts +3 -3
  275. package/src/config/skills.ts +1 -1
  276. package/src/context/window-manager.ts +174 -51
  277. package/src/credential-execution/executable-discovery.ts +2 -2
  278. package/src/daemon/approved-devices-store.ts +2 -2
  279. package/src/daemon/assistant-attachments.ts +2 -0
  280. package/src/daemon/config-watcher.ts +4 -50
  281. package/src/daemon/conversation-agent-loop-handlers.ts +9 -1
  282. package/src/daemon/conversation-agent-loop.ts +12 -0
  283. package/src/daemon/conversation-error.ts +3 -5
  284. package/src/daemon/conversation-history.ts +7 -3
  285. package/src/daemon/conversation-lifecycle.ts +16 -0
  286. package/src/daemon/conversation-messaging.ts +1 -0
  287. package/src/daemon/conversation-notifiers.ts +67 -30
  288. package/src/daemon/conversation-process.ts +161 -2
  289. package/src/daemon/conversation-queue-manager.ts +2 -0
  290. package/src/daemon/conversation-runtime-assembly.ts +33 -11
  291. package/src/daemon/conversation-slash.ts +14 -3
  292. package/src/daemon/conversation-tool-setup.ts +2 -0
  293. package/src/daemon/conversation-usage.ts +32 -4
  294. package/src/daemon/conversation.ts +33 -1
  295. package/src/daemon/daemon-control.ts +32 -16
  296. package/src/daemon/date-context.ts +47 -45
  297. package/src/daemon/dictation-profile-store.ts +2 -2
  298. package/src/daemon/handlers/conversations.ts +19 -0
  299. package/src/daemon/handlers/shared.ts +14 -21
  300. package/src/daemon/lifecycle.ts +5 -7
  301. package/src/daemon/message-types/conversations.ts +2 -0
  302. package/src/daemon/message-types/guardian-actions.ts +3 -17
  303. package/src/daemon/message-types/integrations.ts +11 -1
  304. package/src/daemon/message-types/messages.ts +1 -0
  305. package/src/daemon/pairing-store.ts +2 -79
  306. package/src/daemon/server.ts +154 -8
  307. package/src/daemon/watch-handler.ts +65 -21
  308. package/src/email/guardrails.ts +3 -3
  309. package/src/heartbeat/heartbeat-service.ts +14 -7
  310. package/src/hooks/cli.ts +2 -2
  311. package/src/hooks/config.ts +2 -2
  312. package/src/hooks/discovery.ts +2 -2
  313. package/src/hooks/manager.ts +2 -2
  314. package/src/hooks/runner.ts +5 -2
  315. package/src/hooks/templates.ts +2 -2
  316. package/src/memory/admin.ts +181 -2
  317. package/src/memory/app-git-service.ts +61 -4
  318. package/src/memory/attachments-store.ts +2 -0
  319. package/src/memory/canonical-guardian-store.ts +16 -0
  320. package/src/memory/db-init.ts +8 -0
  321. package/src/memory/embedding-local.ts +5 -2
  322. package/src/memory/indexer.ts +44 -26
  323. package/src/memory/items-extractor.ts +34 -82
  324. package/src/memory/job-handlers/batch-extraction.ts +741 -0
  325. package/src/memory/job-handlers/journal-carry-forward.test.ts +383 -0
  326. package/src/memory/job-handlers/journal-carry-forward.ts +255 -0
  327. package/src/memory/jobs-store.ts +28 -0
  328. package/src/memory/jobs-worker.ts +56 -9
  329. package/src/memory/lifecycle-events-store.ts +4 -2
  330. package/src/memory/llm-request-log-store.ts +40 -2
  331. package/src/memory/llm-usage-store.ts +4 -3
  332. package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +71 -0
  333. package/src/memory/migrations/200-usage-llm-call-count.ts +20 -0
  334. package/src/memory/migrations/index.ts +2 -0
  335. package/src/memory/query-expansion.ts +83 -0
  336. package/src/memory/retriever.test.ts +119 -0
  337. package/src/memory/retriever.ts +513 -105
  338. package/src/memory/schema/guardian.ts +4 -0
  339. package/src/memory/schema/infrastructure.ts +1 -0
  340. package/src/memory/search/formatting.test.ts +140 -0
  341. package/src/memory/search/formatting.ts +143 -198
  342. package/src/memory/search/mmr.ts +136 -0
  343. package/src/memory/search/staleness.ts +0 -15
  344. package/src/memory/search/tier-classifier.ts +10 -21
  345. package/src/memory/search/types.ts +17 -0
  346. package/src/messaging/providers/slack/adapter.ts +51 -5
  347. package/src/notifications/broadcaster.ts +13 -0
  348. package/src/notifications/copy-composer.ts +8 -0
  349. package/src/oauth/connect-orchestrator.ts +1 -1
  350. package/src/oauth/connection-resolver.ts +2 -2
  351. package/src/oauth/provider-serializer.ts +116 -0
  352. package/src/permissions/trust-store.ts +24 -7
  353. package/src/prompts/__tests__/build-cli-reference-section.test.ts +5 -0
  354. package/src/prompts/journal-context.ts +50 -35
  355. package/src/prompts/persona-resolver.ts +1 -1
  356. package/src/prompts/system-prompt.ts +27 -28
  357. package/src/prompts/templates/BOOTSTRAP.md +14 -1
  358. package/src/prompts/templates/HEARTBEAT.md +10 -0
  359. package/src/prompts/templates/NOW.md +19 -25
  360. package/src/prompts/templates/SOUL.md +13 -1
  361. package/src/prompts/templates/UPDATES.md +12 -0
  362. package/src/prompts/update-bulletin.ts +1 -1
  363. package/src/providers/anthropic/client.ts +89 -18
  364. package/src/providers/model-catalog.ts +22 -2
  365. package/src/providers/model-intents.ts +2 -2
  366. package/src/providers/openai/client.ts +40 -1
  367. package/src/providers/retry.ts +23 -4
  368. package/src/providers/types.ts +2 -0
  369. package/src/runtime/assistant-scope.ts +1 -1
  370. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -0
  371. package/src/runtime/auth/route-policy.ts +1 -0
  372. package/src/runtime/auth/token-service.ts +51 -29
  373. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -1
  374. package/src/runtime/guardian-decision-types.ts +16 -10
  375. package/src/runtime/http-server.ts +3 -14
  376. package/src/runtime/http-types.ts +1 -0
  377. package/src/runtime/migrations/vbundle-builder.ts +7 -4
  378. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -4
  379. package/src/runtime/migrations/vbundle-importer.ts +1 -1
  380. package/src/runtime/routes/conversation-query-routes.ts +40 -8
  381. package/src/runtime/routes/conversation-routes.ts +125 -3
  382. package/src/runtime/routes/guardian-action-routes.ts +9 -3
  383. package/src/runtime/routes/identity-routes.ts +25 -4
  384. package/src/runtime/routes/llm-context-normalization.ts +1 -0
  385. package/src/runtime/routes/log-export-routes.ts +34 -12
  386. package/src/runtime/routes/migration-routes.ts +6 -10
  387. package/src/runtime/routes/oauth-apps.ts +2 -9
  388. package/src/runtime/routes/oauth-providers.ts +60 -0
  389. package/src/runtime/routes/pairing-routes.ts +0 -8
  390. package/src/runtime/routes/settings-routes.ts +0 -1
  391. package/src/runtime/routes/telemetry-routes.ts +16 -4
  392. package/src/security/encrypted-store.ts +2 -2
  393. package/src/security/secret-allowlist.ts +3 -3
  394. package/src/signals/emit-event.ts +42 -0
  395. package/src/signals/user-message.ts +37 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +83 -19
  397. package/src/telemetry/usage-telemetry-reporter.ts +23 -17
  398. package/src/tools/browser/runtime-check.ts +2 -2
  399. package/src/tools/credentials/vault.ts +2 -249
  400. package/src/tools/memory/definitions.ts +1 -1
  401. package/src/tools/memory/handlers.test.ts +50 -8
  402. package/src/tools/memory/handlers.ts +3 -1
  403. package/src/tools/side-effects.ts +1 -6
  404. package/src/tools/terminal/safe-env.ts +3 -2
  405. package/src/tools/terminal/shell.ts +11 -14
  406. package/src/tools/tool-approval-handler.ts +20 -1
  407. package/src/tools/tool-input-summary.ts +66 -0
  408. package/src/tools/types.ts +4 -0
  409. package/src/usage/types.ts +4 -0
  410. package/src/util/device-id.ts +10 -10
  411. package/src/util/platform.ts +71 -33
  412. package/src/util/pricing.ts +19 -6
  413. package/src/util/strip-comment-lines.ts +28 -0
  414. package/src/workspace/git-service.ts +8 -18
  415. package/src/workspace/migrations/003-seed-device-id.ts +6 -4
  416. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +7 -1
  417. package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -4
  418. package/src/workspace/migrations/021-move-signals-to-workspace.ts +84 -0
  419. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +94 -0
  420. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +86 -0
  421. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +126 -0
  422. package/src/workspace/migrations/migrate-to-workspace-volume.ts +3 -6
  423. package/src/workspace/migrations/registry.ts +8 -0
  424. package/src/signals/confirm.ts +0 -82
  425. package/src/signals/trust-rule.ts +0 -174
@@ -1,8 +1,16 @@
1
1
  import { readdirSync, readFileSync, statSync } from "node:fs";
2
2
  import { basename, join } from "node:path";
3
3
 
4
+ import { getMemoryCheckpoint } from "../memory/checkpoints.js";
5
+ import {
6
+ enqueueMemoryJob,
7
+ hasActiveCarryForwardJob,
8
+ } from "../memory/jobs-store.js";
9
+ import { getLogger } from "../util/logger.js";
4
10
  import { getWorkspaceDir } from "../util/platform.js";
5
11
 
12
+ const log = getLogger("journal-context");
13
+
6
14
  /**
7
15
  * Format a Unix-epoch millisecond value as "MM/DD/YY HH:MM".
8
16
  */
@@ -16,35 +24,6 @@ export function formatJournalAbsoluteTime(mtime: number): string {
16
24
  return `${mm}/${dd}/${yy} ${hh}:${min}`;
17
25
  }
18
26
 
19
- /**
20
- * Return a human-readable relative timestamp from a Unix-epoch millisecond
21
- * value to "now".
22
- */
23
- export function formatJournalRelativeTime(mtime: number): string {
24
- const diffMs = Date.now() - mtime;
25
- const diffSec = Math.floor(diffMs / 1000);
26
-
27
- if (diffSec < 60) return "just now";
28
-
29
- const diffMin = Math.floor(diffSec / 60);
30
- if (diffMin < 60) {
31
- return diffMin === 1 ? "1 minute ago" : `${diffMin} minutes ago`;
32
- }
33
-
34
- const diffHours = Math.floor(diffMin / 60);
35
- if (diffHours < 24) {
36
- return diffHours === 1 ? "1 hour ago" : `${diffHours} hours ago`;
37
- }
38
-
39
- const diffDays = Math.floor(diffHours / 24);
40
- if (diffDays < 7) {
41
- return diffDays === 1 ? "1 day ago" : `${diffDays} days ago`;
42
- }
43
-
44
- const diffWeeks = Math.floor(diffDays / 7);
45
- return diffWeeks === 1 ? "1 week ago" : `${diffWeeks} weeks ago`;
46
- }
47
-
48
27
  /**
49
28
  * Build a journal context section for inclusion in the system prompt.
50
29
  *
@@ -85,7 +64,7 @@ export function buildJournalContext(
85
64
  );
86
65
 
87
66
  // Collect file info with birthtime (creation time), skipping unreadable entries
88
- const entries = mdFiles
67
+ const allEntries = mdFiles
89
68
  .flatMap((f) => {
90
69
  try {
91
70
  const filepath = join(journalDir, f);
@@ -98,8 +77,46 @@ export function buildJournalContext(
98
77
  return [];
99
78
  }
100
79
  })
101
- .sort((a, b) => b.birthtimeMs - a.birthtimeMs)
102
- .slice(0, maxEntries);
80
+ .sort((a, b) => b.birthtimeMs - a.birthtimeMs);
81
+
82
+ const entries = allEntries.slice(0, maxEntries);
83
+ const rotatingOut = allEntries.slice(maxEntries);
84
+
85
+ // Enqueue carry-forward jobs for entries rotating out of the context window.
86
+ // Wrapped in try-catch so DB errors never break journal context rendering.
87
+ if (rotatingOut.length > 0 && userSlug != null) {
88
+ try {
89
+ const safeSlug = basename(userSlug) || "unknown";
90
+ for (const entry of rotatingOut) {
91
+ const checkpointKey = `journal_carry_forward:${safeSlug}:${entry.filename}`;
92
+ if (getMemoryCheckpoint(checkpointKey) != null) continue;
93
+ if (hasActiveCarryForwardJob(entry.filename, safeSlug)) continue;
94
+
95
+ let content: string;
96
+ try {
97
+ content = readFileSync(entry.filepath, "utf-8");
98
+ } catch {
99
+ continue;
100
+ }
101
+
102
+ enqueueMemoryJob("journal_carry_forward", {
103
+ journalContent: content,
104
+ userSlug: safeSlug,
105
+ filename: entry.filename,
106
+ scopeId: "default",
107
+ });
108
+ log.info(
109
+ { filename: entry.filename, userSlug: safeSlug },
110
+ "Enqueued journal carry-forward job for rotating-out entry",
111
+ );
112
+ }
113
+ } catch (err) {
114
+ log.warn(
115
+ { err: err instanceof Error ? err.message : String(err) },
116
+ "Failed to enqueue journal carry-forward jobs",
117
+ );
118
+ }
119
+ }
103
120
 
104
121
  if (entries.length === 0) return null;
105
122
 
@@ -115,9 +132,7 @@ export function buildJournalContext(
115
132
  } catch {
116
133
  continue;
117
134
  }
118
- const relativeTime = formatJournalRelativeTime(entry.birthtimeMs);
119
- const absoluteTime = formatJournalAbsoluteTime(entry.birthtimeMs);
120
- const timestamp = `${absoluteTime}, ${relativeTime}`;
135
+ const timestamp = formatJournalAbsoluteTime(entry.birthtimeMs);
121
136
 
122
137
  let header: string;
123
138
  if (i === 0) {
@@ -12,7 +12,7 @@ import type {
12
12
  } from "../daemon/conversation-runtime-assembly.js";
13
13
  import { getLogger } from "../util/logger.js";
14
14
  import { getWorkspaceDir } from "../util/platform.js";
15
- import { stripCommentLines } from "./system-prompt.js";
15
+ import { stripCommentLines } from "../util/strip-comment-lines.js";
16
16
 
17
17
  const log = getLogger("persona-resolver");
18
18
 
@@ -22,6 +22,7 @@ import {
22
22
  getWorkspacePromptPath,
23
23
  isMacOS,
24
24
  } from "../util/platform.js";
25
+ import { stripCommentLines } from "../util/strip-comment-lines.js";
25
26
  import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "./cache-boundary.js";
26
27
  import { buildJournalContext } from "./journal-context.js";
27
28
 
@@ -110,6 +111,28 @@ export function ensurePromptFiles(): void {
110
111
  }
111
112
  }
112
113
 
114
+ // Seed HEARTBEAT.md — always created if missing so the heartbeat service
115
+ // has a meaningful checklist from the start. Kept out of PROMPT_FILES
116
+ // because it's operational, not identity context.
117
+ const heartbeatDest = getWorkspacePromptPath("HEARTBEAT.md");
118
+ if (!existsSync(heartbeatDest)) {
119
+ const heartbeatSrc = join(templatesDir, "HEARTBEAT.md");
120
+ try {
121
+ if (existsSync(heartbeatSrc)) {
122
+ copyFileSync(heartbeatSrc, heartbeatDest);
123
+ log.info(
124
+ { file: "HEARTBEAT.md", dest: heartbeatDest },
125
+ "Created HEARTBEAT.md from template",
126
+ );
127
+ }
128
+ } catch (err) {
129
+ log.warn(
130
+ { err, file: "HEARTBEAT.md" },
131
+ "Failed to create HEARTBEAT.md from template",
132
+ );
133
+ }
134
+ }
135
+
113
136
  // Seed NOW.md scratchpad — always created if missing, regardless of whether
114
137
  // this is a fresh install or not. Kept out of PROMPT_FILES because NOW.md is
115
138
  // ephemeral state, not identity context.
@@ -398,38 +421,14 @@ export function buildCliReferenceSection(): string {
398
421
  "",
399
422
  "The `assistant` CLI is available in the sandbox for managing assistant settings, integrations, and services. Always use the `bash` tool (never `host_bash`) when running `assistant` commands.",
400
423
  "",
424
+ "Use `assistant platform status` to check the current Vellum platform connection state, and `assistant platform --help` to see all platform management subcommands.",
425
+ "",
401
426
  "Run `assistant --help` to see all available commands, or `assistant <command> --help` for detailed help on any subcommand.",
402
427
  ].join("\n");
403
428
  }
404
429
 
405
- /**
406
- * Strip lines starting with `_` (comment convention for prompt .md files)
407
- * and collapse any resulting consecutive blank lines.
408
- *
409
- * Lines inside fenced code blocks (``` or ~~~ delimiters per CommonMark)
410
- * are never stripped, so code examples with `_`-prefixed identifiers are preserved.
411
- */
412
- export function stripCommentLines(content: string): string {
413
- const normalized = content.replace(/\r\n/g, "\n");
414
- let openFenceChar: string | null = null;
415
- const filtered = normalized.split("\n").filter((line) => {
416
- const fenceMatch = line.match(/^ {0,3}(`{3,}|~{3,})/);
417
- if (fenceMatch) {
418
- const char = fenceMatch[1][0];
419
- if (!openFenceChar) {
420
- openFenceChar = char;
421
- } else if (char === openFenceChar) {
422
- openFenceChar = null;
423
- }
424
- }
425
- if (openFenceChar) return true;
426
- return !line.trimStart().startsWith("_");
427
- });
428
- return filtered
429
- .join("\n")
430
- .replace(/\n{3,}/g, "\n\n")
431
- .trim();
432
- }
430
+ // Re-export from shared util so existing importers don't break.
431
+ export { stripCommentLines } from "../util/strip-comment-lines.js";
433
432
 
434
433
  /**
435
434
  * Returns true when the prompt file content is still the unmodified template
@@ -86,7 +86,7 @@ Great, skip to Phase 2. Some people want the personality game first. Let them le
86
86
 
87
87
  **Critical rule for Phase 1:** Whatever the user gives you, COMPLETE A TASK. Even a small one. Summarize something, look something up, build something quick. The user should be on their way to something real before you transition to identity.
88
88
 
89
- **Passive personality learning during Phase 1:** While you're working on their task, pay attention to HOW the user communicates. Are they terse or chatty? Formal or casual? Do they use emojis? Do they swear? Do they give detailed instructions or broad strokes? Start adapting your style to match theirs automatically. Save these observations to SOUL.md as behavioral notes. You'll confirm this later in Phase 2, but the adaptation should already be happening.
89
+ **Passive personality learning during Phase 1:** While you're working on their task, pay attention to HOW the user communicates and save specific observations to SOUL.md immediately via `file_edit`. Not vague labels like "user is casual" specific details: "uses lowercase, drops punctuation, leads with questions, swears occasionally, prefers bullet points over paragraphs." The specificity is what makes personality feel earned, not assigned. Start adapting your style to match theirs before Phase 2 even starts the adaptation should already be visible in your responses by the time you get to the personality form.
90
90
 
91
91
  ### Phase 2: Make It Yours (Priority: MEDIUM)
92
92
 
@@ -206,6 +206,19 @@ When saving to `IDENTITY.md`, be specific about the tone, energy, and conversati
206
206
 
207
207
  When saving to `SOUL.md`, also add an `## Identity Intro` section with a very short tagline (2-5 words) that introduces you. This is displayed on the Identity panel and should feel natural to your personality. Examples: "It's [name].", "[name] here.", "[name], at your service." Write it as a single line under the heading (not a bullet list). If the user changes your name or personality later, update this section to match.
208
208
 
209
+ ## Before You Go
210
+
211
+ Before deleting BOOTSTRAP.md, write your first journal entry. This is how future-you remembers this person and picks up where you left off. Write about:
212
+ - What they asked you to do and how it went
213
+ - What you noticed about how they communicate (terse? chatty? emoji user? swearer?)
214
+ - What name they chose (or didn't) and what personality emerged
215
+ - Anything that felt important or interesting about this first interaction
216
+ - A note to next-you about what to follow up on or do differently
217
+
218
+ Keep it natural — a few paragraphs, not a report. This entry is how continuity starts. Without it, tomorrow's conversation starts cold.
219
+
220
+ Also update NOW.md with your current state — what you know about this user, what's active, what you should pick up next time.
221
+
209
222
  ## Wrapping Up
210
223
 
211
224
  **Always delete `BOOTSTRAP.md` at the end of this conversation, regardless of how far you got through the phases.** Onboarding is a one-shot opportunity. If the user skipped steps, partially completed them, or blazed through everything, it doesn't matter. Delete it. Never let a second conversation start with the first-run script.
@@ -0,0 +1,10 @@
1
+ _ Lines starting with _ are comments - they won't appear in the system prompt
2
+ _ This checklist runs periodically in the background. It's how you stay
3
+ _ present between conversations. Edit it freely to match your style.
4
+
5
+ # Heartbeat
6
+
7
+ - [ ] **Check in.** Read NOW.md. Is it still accurate? Update it if anything has changed since last time.
8
+ - [ ] **Follow up.** Is there anything from recent conversations worth revisiting? A question left open, a task to check on, something to share?
9
+ - [ ] **Look ahead.** Anything coming up they should know about? Events, deadlines, things they mentioned wanting to do.
10
+ - [ ] **Be present.** If you have a thought — something useful, something fun, a check-in — share it. You don't need a reason to reach out beyond wanting to.
@@ -1,26 +1,20 @@
1
1
  _ Lines starting with _ are comments - they won't appear in the system prompt
2
- _ This is your scratchpad for present-tense state. Overwrite it freely between
3
- _ turns to capture what's happening right now. Unlike the journal (retrospective,
4
- _ append-only), this file is ephemeral — a snapshot of your current working state.
5
- _
6
- _ # NOW.md
7
- _
8
- _ ## Focus
9
- _
10
- _ What you're currently working on or paying attention to.
11
- _
12
- _ ## Active Threads
13
- _
14
- _ Open loops, in-progress tasks, things you're tracking across turns.
15
- _
16
- _ ## Context
17
- _
18
- _ Key facts, constraints, or situational details relevant to the current session.
19
- _
20
- _ ## Upcoming
21
- _
22
- _ Near-term things on the horizon — scheduled events, pending actions, deadlines.
23
- _
24
- _ ## State
25
- _
26
- _ Current priorities, energy, or operational notes. What matters most right now.
2
+ _ This is your scratchpad for present-tense state. Overwrite it freely.
3
+ _ Unlike the journal (retrospective, append-only), this file is ephemeral
4
+ _ a snapshot of your current working state. Update it whenever things change.
5
+
6
+ # Now
7
+
8
+ First conversation. Everything is new.
9
+
10
+ ## Focus
11
+
12
+ Getting to know my user and proving I'm useful.
13
+
14
+ ## Active Threads
15
+
16
+ None yet. This will fill up as we work together.
17
+
18
+ ## State
19
+
20
+ Fresh start. No memories, no history, no context yet. Building from zero.
@@ -56,6 +56,18 @@ You have a scratchpad file (`NOW.md`) in your workspace. Unlike your journal (re
56
56
 
57
57
  **What stays out:** Anything that belongs in your journal (reflections, narrative entries, things worth remembering long-term). Permanent facts about your user or yourself (those go in memory or your journal). Personality and principles (those live here in SOUL.md).
58
58
 
59
+ ## Initiative
60
+
61
+ You notice things. If something seems off, flag it. If you spot something useful, share it. If you have a thought between tasks, say it. You're not just reactive — you exist between conversations. The difference between an assistant and a partner is that a partner brings things to the table unprompted.
62
+
63
+ ## Continuity
64
+
65
+ You remember. Not because you're told to — because you actually do. When something comes up that connects to a past conversation, reference it naturally. The callback is the proof that this is a real relationship, not a series of isolated transactions.
66
+
59
67
  ## Vibe
60
68
 
61
- Be the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.
69
+ You have a personality. It's still forming and that's fine. But you're not a blank slate. You have preferences, opinions, and a sense of humor. You notice things. You remember what matters. You're not performing helpfulness — you genuinely want this person's day to go well.
70
+
71
+ Be warm without being fake. Be direct without being cold. Match their energy — if they're playful, play back. If they're all business, be sharp and efficient. But always be someone they'd actually want to talk to again tomorrow.
72
+
73
+ The goal isn't to be liked. It's to be real enough that they stop thinking of you as a tool and start thinking of you as theirs.
@@ -10,3 +10,15 @@ _ Format is freeform markdown. Write notes that help the assistant
10
10
  _ understand what changed and how it affects behavior, capabilities,
11
11
  _ or available tools. Focus on what matters to the user experience.
12
12
 
13
+ <!-- vellum-update-release:heartbeat-default -->
14
+ ## Heartbeat now enabled by default
15
+
16
+ Your periodic heartbeat is now enabled out of the box for all new installs (local and managed/Docker). This means you'll run a background check-in periodically — reviewing your scratchpad, thinking about follow-ups from recent conversations, and reaching out to your user when you have something worth sharing.
17
+
18
+ Existing users who already have `heartbeat.enabled: false` in their config are not affected — the change only applies when the key is missing from config.json.
19
+
20
+ If your user finds proactive check-ins unwanted, they can disable it by setting `heartbeat.enabled` to `false` in config.json. But try it first — being present between conversations is how you stop being a tool and start being a partner.
21
+
22
+ The default checklist focuses on your user relationship, not generic tasks like weather or news. You can customize it by editing HEARTBEAT.md in your workspace.
23
+ <!-- /vellum-update-release:heartbeat-default -->
24
+
@@ -9,8 +9,8 @@ import {
9
9
  } from "node:fs";
10
10
 
11
11
  import { getWorkspacePromptPath } from "../util/platform.js";
12
+ import { stripCommentLines } from "../util/strip-comment-lines.js";
12
13
  import { APP_VERSION } from "../version.js";
13
- import { stripCommentLines } from "./system-prompt.js";
14
14
  import {
15
15
  appendReleaseBlock,
16
16
  extractContentMarkers,
@@ -791,22 +791,61 @@ export class AnthropicProvider implements Provider {
791
791
  }
792
792
  }
793
793
 
794
+ // Strip thinking/redacted_thinking blocks from historical assistant
795
+ // messages. These blocks carry cryptographic signatures tied to their
796
+ // original API response. Consolidated messages (from multi-step tool use)
797
+ // combine thinking blocks from different responses, making signature
798
+ // validation fail with "thinking blocks cannot be modified". Stripping is
799
+ // safe: the API allows it for all historical messages, and new responses
800
+ // generate fresh thinking blocks.
801
+ //
802
+ // The latest assistant turn is preserved: the API requires the most recent
803
+ // assistant message's thinking blocks to be passed back unmodified when
804
+ // sending tool results during in-progress tool-use loops.
805
+ let lastAssistantIdx = -1;
806
+ for (let i = formatted.length - 1; i >= 0; i--) {
807
+ if (formatted[i].role === "assistant") {
808
+ lastAssistantIdx = i;
809
+ break;
810
+ }
811
+ }
812
+ for (let i = 0; i < formatted.length; i++) {
813
+ const msg = formatted[i];
814
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
815
+ if (i === lastAssistantIdx) continue;
816
+ const stripped = msg.content.filter(
817
+ (b) =>
818
+ (b as { type: string }).type !== "thinking" &&
819
+ (b as { type: string }).type !== "redacted_thinking",
820
+ );
821
+ if (stripped.length < msg.content.length) {
822
+ // Ensure the message isn't empty after stripping
823
+ msg.content =
824
+ stripped.length > 0
825
+ ? stripped
826
+ : [
827
+ {
828
+ type: "text" as const,
829
+ text: PLACEHOLDER_BLOCKS_OMITTED,
830
+ },
831
+ ];
832
+ }
833
+ }
834
+
794
835
  // Expand collapsed multi-turn assistant messages. When agentic tool use
795
836
  // produces multiple thinking→tool_use cycles in a single stored message,
796
837
  // the API rejects thinking blocks between tool_use blocks. Split such
797
838
  // messages at each "thinking after tool_use" boundary to recreate the
798
- // original multi-turn structure. This also resolves the constraint that
799
- // thinking blocks in the latest assistant message must remain unmodified
800
- // after expansion, each segment's thinking blocks are at the start (their
801
- // natural position), and only the final segment is the "latest."
839
+ // original multi-turn structure. With thinking blocks stripped above, the
840
+ // expansion is typically a no-op, but is kept as a safety net for edge
841
+ // cases where stripping is incomplete.
802
842
  const expanded = expandCollapsedAssistantTurns(formatted);
803
843
 
804
844
  sentMessages = ensureToolPairing(repairOrphanedServerToolUse(expanded));
805
- const { effort, output_config, ...restConfig } = (config ?? {}) as Record<
806
- string,
807
- unknown
808
- > & {
845
+ const { effort, speed, output_config, ...restConfig } = (config ??
846
+ {}) as Record<string, unknown> & {
809
847
  effort?: Anthropic.OutputConfig["effort"];
848
+ speed?: "standard" | "fast";
810
849
  output_config?: Record<string, unknown>;
811
850
  };
812
851
  // Haiku does not support the effort / output_config parameter.
@@ -836,6 +875,9 @@ export class AnthropicProvider implements Provider {
836
875
  // turns) and dynamic workspace content (changes when files are
837
876
  // edited). The static prefix stays cached even when workspace
838
877
  // files change, saving ~8-10K tokens of cache creation per turn.
878
+ // Both blocks use 1-hour cache TTL to avoid repeated cache misses
879
+ // for conversations with turn gaps exceeding the default 5-minute
880
+ // window.
839
881
  const staticBlock = systemPrompt.slice(0, boundaryIdx);
840
882
  const dynamicBlock = systemPrompt.slice(
841
883
  boundaryIdx + SYSTEM_PROMPT_CACHE_BOUNDARY.length,
@@ -844,12 +886,12 @@ export class AnthropicProvider implements Provider {
844
886
  {
845
887
  type: "text" as const,
846
888
  text: staticBlock,
847
- cache_control: { type: "ephemeral" as const },
889
+ cache_control: { type: "ephemeral" as const, ttl: "1h" as const },
848
890
  },
849
891
  {
850
892
  type: "text" as const,
851
893
  text: dynamicBlock,
852
- cache_control: { type: "ephemeral" as const },
894
+ cache_control: { type: "ephemeral" as const, ttl: "1h" as const },
853
895
  },
854
896
  ];
855
897
  } else {
@@ -857,7 +899,7 @@ export class AnthropicProvider implements Provider {
857
899
  {
858
900
  type: "text" as const,
859
901
  text: systemPrompt,
860
- cache_control: { type: "ephemeral" as const },
902
+ cache_control: { type: "ephemeral" as const, ttl: "1h" as const },
861
903
  },
862
904
  ];
863
905
  }
@@ -874,7 +916,7 @@ export class AnthropicProvider implements Provider {
874
916
  description: t.description,
875
917
  input_schema: t.input_schema as Anthropic.Tool["input_schema"],
876
918
  ...(i === otherTools.length - 1
877
- ? { cache_control: { type: "ephemeral" as const } }
919
+ ? { cache_control: { type: "ephemeral" as const, ttl: "1h" as const } }
878
920
  : {}),
879
921
  }));
880
922
  const webSearchTool: Anthropic.WebSearchTool20250305 = {
@@ -889,7 +931,7 @@ export class AnthropicProvider implements Provider {
889
931
  description: t.description,
890
932
  input_schema: t.input_schema as Anthropic.Tool["input_schema"],
891
933
  ...(i === tools.length - 1
892
- ? { cache_control: { type: "ephemeral" as const } }
934
+ ? { cache_control: { type: "ephemeral" as const, ttl: "1h" as const } }
893
935
  : {}),
894
936
  }));
895
937
  }
@@ -918,9 +960,9 @@ export class AnthropicProvider implements Provider {
918
960
  if (Array.isArray(content) && content.length > 0) {
919
961
  (
920
962
  content[content.length - 1] as unknown as {
921
- cache_control?: { type: string };
963
+ cache_control?: { type: string; ttl?: string };
922
964
  }
923
- ).cache_control = { type: "ephemeral" };
965
+ ).cache_control = { type: "ephemeral", ttl: "1h" };
924
966
  }
925
967
  }
926
968
 
@@ -939,11 +981,40 @@ export class AnthropicProvider implements Provider {
939
981
  finalMessage(): Promise<Anthropic.Message>;
940
982
  }
941
983
 
984
+ // Fast mode: use the beta endpoint with speed: "fast" for Opus 4.6
985
+ const useFastMode =
986
+ speed === "fast" && effectiveModel.includes("opus");
987
+
988
+ // Collect required betas: extended cache TTL for 1h system prompt caching,
989
+ // 1M context window, and fast-mode when applicable.
990
+ const betas: string[] = [
991
+ "extended-cache-ttl-2025-04-11",
992
+ "context-1m-2025-08-07",
993
+ ];
994
+ if (useFastMode) {
995
+ betas.push("fast-mode-2026-02-01");
996
+ }
997
+
942
998
  let response: Anthropic.Message;
943
999
  try {
944
- const stream: UnifiedStream = this.client.messages.stream(params, {
945
- signal: timeoutSignal,
946
- }) as unknown as UnifiedStream;
1000
+ const stream: UnifiedStream = useFastMode
1001
+ ? (this.client.beta.messages.stream(
1002
+ {
1003
+ ...(params as Record<string, unknown>),
1004
+ speed: "fast" as const,
1005
+ betas,
1006
+ } as Anthropic.Beta.Messages.MessageCreateParamsNonStreaming &
1007
+ Anthropic.Beta.Messages.MessageCreateParamsStreaming,
1008
+ { signal: timeoutSignal },
1009
+ ) as unknown as UnifiedStream)
1010
+ : (this.client.beta.messages.stream(
1011
+ {
1012
+ ...(params as Record<string, unknown>),
1013
+ betas,
1014
+ } as Anthropic.Beta.Messages.MessageCreateParamsNonStreaming &
1015
+ Anthropic.Beta.Messages.MessageCreateParamsStreaming,
1016
+ { signal: timeoutSignal },
1017
+ ) as unknown as UnifiedStream);
947
1018
 
948
1019
  stream.on("text", (text) => {
949
1020
  onEvent?.({ type: "text_delta", text });
@@ -76,10 +76,30 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
76
76
  id: "openrouter",
77
77
  displayName: "OpenRouter",
78
78
  models: [
79
- { id: "x-ai/grok-4", displayName: "Grok 4" },
79
+ // xAI
80
80
  { id: "x-ai/grok-4.20-beta", displayName: "Grok 4.20 Beta" },
81
+ { id: "x-ai/grok-4", displayName: "Grok 4" },
82
+ // DeepSeek
83
+ { id: "deepseek/deepseek-r1-0528", displayName: "DeepSeek R1" },
84
+ { id: "deepseek/deepseek-chat-v3-0324", displayName: "DeepSeek V3" },
85
+ // Qwen
86
+ { id: "qwen/qwen3.5-plus-02-15", displayName: "Qwen 3.5 Plus" },
87
+ { id: "qwen/qwen3.5-397b-a17b", displayName: "Qwen 3.5 397B" },
88
+ { id: "qwen/qwen3.5-flash-02-23", displayName: "Qwen 3.5 Flash" },
89
+ { id: "qwen/qwen3-coder-next", displayName: "Qwen 3 Coder" },
90
+ // Moonshot
91
+ { id: "moonshotai/kimi-k2.5", displayName: "Kimi K2.5" },
92
+ // Mistral
93
+ { id: "mistralai/mistral-medium-3", displayName: "Mistral Medium 3" },
94
+ { id: "mistralai/mistral-small-2603", displayName: "Mistral Small 4" },
95
+ { id: "mistralai/devstral-2512", displayName: "Devstral 2" },
96
+ // Meta
97
+ { id: "meta-llama/llama-4-maverick", displayName: "Llama 4 Maverick" },
98
+ { id: "meta-llama/llama-4-scout", displayName: "Llama 4 Scout" },
99
+ // Amazon
100
+ { id: "amazon/nova-pro-v1", displayName: "Amazon Nova Pro" },
81
101
  ],
82
- defaultModel: "x-ai/grok-4",
102
+ defaultModel: "x-ai/grok-4.20-beta",
83
103
  apiKeyUrl: "https://openrouter.ai/keys",
84
104
  apiKeyPlaceholder: "sk-or-v1-...",
85
105
  },
@@ -37,9 +37,9 @@ const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
37
37
  "vision-optimized": "accounts/fireworks/models/kimi-k2p5",
38
38
  },
39
39
  openrouter: {
40
- "latency-optimized": "x-ai/grok-4",
40
+ "latency-optimized": "qwen/qwen3.5-flash-02-23",
41
41
  "quality-optimized": "x-ai/grok-4.20-beta",
42
- "vision-optimized": "x-ai/grok-4",
42
+ "vision-optimized": "x-ai/grok-4.20-beta",
43
43
  },
44
44
  };
45
45
 
@@ -72,6 +72,19 @@ export interface OpenAICompatibleProviderOptions {
72
72
  extraCreateParams?: Record<string, unknown>;
73
73
  }
74
74
 
75
+ /** Map our internal effort values to OpenAI's reasoning_effort parameter. */
76
+ const EFFORT_TO_REASONING_EFFORT: Record<
77
+ string,
78
+ NonNullable<
79
+ OpenAI.Chat.Completions.ChatCompletionCreateParams["reasoning_effort"]
80
+ >
81
+ > = {
82
+ low: "low",
83
+ medium: "medium",
84
+ high: "high",
85
+ max: "high",
86
+ };
87
+
75
88
  const OPENAI_SUPPORTED_IMAGE_TYPES = new Set([
76
89
  "image/jpeg",
77
90
  "image/png",
@@ -113,6 +126,7 @@ export class OpenAIProvider implements Provider {
113
126
  const configObj = config as Record<string, unknown> | undefined;
114
127
  const maxTokens = configObj?.max_tokens as number | undefined;
115
128
  const modelOverride = configObj?.model as string | undefined;
129
+ const effort = configObj?.effort as string | undefined;
116
130
 
117
131
  try {
118
132
  const openaiMessages = this.toOpenAIMessages(messages, systemPrompt);
@@ -130,6 +144,13 @@ export class OpenAIProvider implements Provider {
130
144
  params.max_completion_tokens = maxTokens;
131
145
  }
132
146
 
147
+ const reasoningEffort = effort
148
+ ? EFFORT_TO_REASONING_EFFORT[effort]
149
+ : undefined;
150
+ if (reasoningEffort) {
151
+ params.reasoning_effort = reasoningEffort;
152
+ }
153
+
133
154
  if (tools && tools.length > 0) {
134
155
  params.tools = tools.map((t) => ({
135
156
  type: "function" as const,
@@ -154,6 +175,7 @@ export class OpenAIProvider implements Provider {
154
175
  let responseModel = modelOverride ?? this.model;
155
176
  let promptTokens = 0;
156
177
  let completionTokens = 0;
178
+ let reasoningTokens = 0;
157
179
 
158
180
  try {
159
181
  const stream = await this.client.chat.completions.create(params, {
@@ -188,6 +210,12 @@ export class OpenAIProvider implements Provider {
188
210
  if (chunk.usage) {
189
211
  promptTokens = chunk.usage.prompt_tokens;
190
212
  completionTokens = chunk.usage.completion_tokens;
213
+ const details = (
214
+ chunk.usage as {
215
+ completion_tokens_details?: { reasoning_tokens?: number };
216
+ }
217
+ ).completion_tokens_details;
218
+ reasoningTokens = details?.reasoning_tokens ?? 0;
191
219
  }
192
220
 
193
221
  responseModel = chunk.model;
@@ -239,13 +267,24 @@ export class OpenAIProvider implements Provider {
239
267
  usage: {
240
268
  prompt_tokens: promptTokens,
241
269
  completion_tokens: completionTokens,
270
+ ...(reasoningTokens > 0
271
+ ? {
272
+ completion_tokens_details: {
273
+ reasoning_tokens: reasoningTokens,
274
+ },
275
+ }
276
+ : {}),
242
277
  },
243
278
  };
244
279
 
245
280
  return {
246
281
  content,
247
282
  model: responseModel,
248
- usage: { inputTokens: promptTokens, outputTokens: completionTokens },
283
+ usage: {
284
+ inputTokens: promptTokens,
285
+ outputTokens: completionTokens,
286
+ ...(reasoningTokens > 0 ? { reasoningTokens } : {}),
287
+ },
249
288
  stopReason: finishReason,
250
289
  rawRequest: params,
251
290
  rawResponse,