@vellumai/assistant 0.5.16 → 0.6.0

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 (407) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +0 -3
  3. package/knip.json +2 -1
  4. package/openapi.yaml +660 -80
  5. package/package.json +1 -1
  6. package/src/__tests__/actor-token-service.test.ts +68 -0
  7. package/src/__tests__/agent-loop.test.ts +0 -32
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  9. package/src/__tests__/anthropic-provider.test.ts +57 -3
  10. package/src/__tests__/app-compiler.test.ts +120 -0
  11. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  12. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  13. package/src/__tests__/call-domain.test.ts +2 -6
  14. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  15. package/src/__tests__/call-recovery.test.ts +2 -6
  16. package/src/__tests__/call-routes-http.test.ts +2 -6
  17. package/src/__tests__/call-store.test.ts +2 -6
  18. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  19. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  20. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  21. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  22. package/src/__tests__/checker.test.ts +25 -3
  23. package/src/__tests__/clawhub.test.ts +54 -24
  24. package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
  25. package/src/__tests__/cli-memory.test.ts +74 -69
  26. package/src/__tests__/config-schema.test.ts +1 -1
  27. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  28. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  29. package/src/__tests__/contacts-tools.test.ts +31 -0
  30. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  31. package/src/__tests__/context-token-estimator.test.ts +175 -10
  32. package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
  33. package/src/__tests__/conversation-agent-loop.test.ts +9 -0
  34. package/src/__tests__/conversation-attachments.test.ts +2 -6
  35. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  36. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  37. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  38. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  39. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  40. package/src/__tests__/conversation-error.test.ts +33 -2
  41. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  42. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  43. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  44. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  45. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  46. package/src/__tests__/conversation-store.test.ts +2 -6
  47. package/src/__tests__/conversation-usage.test.ts +2 -6
  48. package/src/__tests__/conversation-wipe.test.ts +11 -408
  49. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  50. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  51. package/src/__tests__/credential-security-e2e.test.ts +2 -0
  52. package/src/__tests__/followup-tools.test.ts +2 -6
  53. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  54. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  55. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  56. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  57. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  58. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  59. package/src/__tests__/guardian-action-store.test.ts +2 -6
  60. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  61. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  62. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  63. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  64. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  65. package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
  66. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  67. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  68. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  69. package/src/__tests__/injection-block.test.ts +154 -0
  70. package/src/__tests__/install-meta.test.ts +506 -0
  71. package/src/__tests__/install-skill-routing.test.ts +292 -0
  72. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  73. package/src/__tests__/invite-routes-http.test.ts +2 -6
  74. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
  75. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  76. package/src/__tests__/llm-context-route-provider.test.ts +2 -6
  77. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
  78. package/src/__tests__/llm-usage-store.test.ts +2 -6
  79. package/src/__tests__/log-export-workspace.test.ts +2 -6
  80. package/src/__tests__/managed-store.test.ts +38 -11
  81. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  82. package/src/__tests__/memory-recall-log-store.test.ts +2 -6
  83. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  84. package/src/__tests__/non-member-access-request.test.ts +2 -6
  85. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  86. package/src/__tests__/oauth-cli.test.ts +364 -2
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  88. package/src/__tests__/outlook-attachments.test.ts +301 -0
  89. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  90. package/src/__tests__/outlook-categories.test.ts +212 -0
  91. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  92. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  93. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  94. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  95. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  96. package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
  97. package/src/__tests__/outlook-trash.test.ts +77 -0
  98. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  99. package/src/__tests__/platform-callback-registration.test.ts +4 -4
  100. package/src/__tests__/playbook-execution.test.ts +76 -80
  101. package/src/__tests__/playbook-tools.test.ts +5 -7
  102. package/src/__tests__/provider-error-scenarios.test.ts +21 -0
  103. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  104. package/src/__tests__/registry.test.ts +2 -2
  105. package/src/__tests__/require-fresh-approval.test.ts +64 -2
  106. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  107. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  108. package/src/__tests__/schedule-store.test.ts +2 -6
  109. package/src/__tests__/schedule-tools.test.ts +2 -6
  110. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  111. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  112. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  113. package/src/__tests__/search-skills-unified.test.ts +421 -0
  114. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  115. package/src/__tests__/send-endpoint-busy.test.ts +2 -6
  116. package/src/__tests__/sequence-store.test.ts +2 -6
  117. package/src/__tests__/server-history-render.test.ts +2 -6
  118. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  119. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  120. package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
  121. package/src/__tests__/skill-memory.test.ts +140 -98
  122. package/src/__tests__/skills-uninstall.test.ts +2 -2
  123. package/src/__tests__/skills.test.ts +1 -1
  124. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  125. package/src/__tests__/task-compiler.test.ts +2 -6
  126. package/src/__tests__/task-management-tools.test.ts +2 -6
  127. package/src/__tests__/task-memory-cleanup.test.ts +173 -229
  128. package/src/__tests__/task-runner.test.ts +2 -6
  129. package/src/__tests__/task-scheduler.test.ts +2 -6
  130. package/src/__tests__/test-preload.ts +3 -0
  131. package/src/__tests__/tool-approval-handler.test.ts +2 -6
  132. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
  133. package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
  134. package/src/__tests__/trust-store.test.ts +1 -1
  135. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
  136. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
  137. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  138. package/src/__tests__/trusted-contact-verification.test.ts +2 -6
  139. package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
  140. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
  141. package/src/__tests__/usage-routes.test.ts +2 -6
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  143. package/src/__tests__/voice-invite-redemption.test.ts +2 -6
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
  145. package/src/__tests__/voice-session-bridge.test.ts +2 -6
  146. package/src/__tests__/volume-security-guard.test.ts +2 -0
  147. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  148. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
  149. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
  150. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  151. package/src/__tests__/workspace-policy.test.ts +1 -1
  152. package/src/agent/attachments.ts +7 -2
  153. package/src/agent/image-optimize.ts +165 -0
  154. package/src/agent/loop.ts +1 -15
  155. package/src/bundler/app-compiler.ts +179 -2
  156. package/src/bundler/package-resolver.ts +3 -5
  157. package/src/cli/__tests__/notifications.test.ts +1 -2
  158. package/src/cli/cli-memory.ts +67 -64
  159. package/src/cli/commands/avatar.ts +3 -3
  160. package/src/cli/commands/config.ts +26 -13
  161. package/src/cli/commands/doctor.ts +2 -2
  162. package/src/cli/commands/memory.ts +41 -55
  163. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  164. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  165. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  166. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  167. package/src/cli/commands/oauth/connect.ts +11 -6
  168. package/src/cli/commands/oauth/mode.ts +7 -0
  169. package/src/cli/commands/oauth/shared.ts +39 -3
  170. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  171. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  172. package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
  173. package/src/cli/commands/platform/index.ts +16 -16
  174. package/src/cli/commands/skills.ts +88 -16
  175. package/src/cli/commands/trust.ts +2 -2
  176. package/src/cli/lib/daemon-credential-client.ts +2 -3
  177. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  178. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  179. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  180. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  181. package/src/config/bundled-skills/gmail/SKILL.md +2 -10
  182. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  183. package/src/config/bundled-skills/messaging/SKILL.md +10 -18
  184. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  185. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  186. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  187. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  188. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  189. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  190. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  191. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  192. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  193. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  194. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  195. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  196. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  197. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  198. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  199. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  200. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  201. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  202. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  203. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  204. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  205. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  206. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  207. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  208. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  209. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  210. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  211. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  212. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  213. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  214. package/src/config/bundled-skills/slack/SKILL.md +1 -7
  215. package/src/config/bundled-tool-registry.ts +56 -4
  216. package/src/config/env-registry.ts +15 -8
  217. package/src/config/feature-flag-registry.json +21 -124
  218. package/src/config/schemas/platform.ts +8 -0
  219. package/src/config/schemas/timeouts.ts +1 -1
  220. package/src/config/skills.ts +18 -7
  221. package/src/context/token-estimator.ts +25 -18
  222. package/src/context/window-manager.ts +6 -2
  223. package/src/credential-execution/process-manager.ts +3 -1
  224. package/src/daemon/context-overflow-reducer.ts +46 -2
  225. package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
  226. package/src/daemon/conversation-agent-loop.ts +96 -61
  227. package/src/daemon/conversation-error.ts +31 -8
  228. package/src/daemon/conversation-lifecycle.ts +33 -0
  229. package/src/daemon/conversation-media-retry.ts +85 -7
  230. package/src/daemon/conversation-notifiers.ts +4 -1
  231. package/src/daemon/conversation-runtime-assembly.ts +5 -0
  232. package/src/daemon/conversation.ts +41 -2
  233. package/src/daemon/daemon-control.ts +8 -2
  234. package/src/daemon/handlers/shared.ts +22 -12
  235. package/src/daemon/handlers/skills.ts +416 -202
  236. package/src/daemon/lifecycle.ts +40 -1
  237. package/src/daemon/main.ts +5 -1
  238. package/src/daemon/message-types/conversations.ts +4 -1
  239. package/src/daemon/message-types/messages.ts +3 -1
  240. package/src/daemon/message-types/skills.ts +97 -36
  241. package/src/daemon/providers-setup.ts +5 -0
  242. package/src/daemon/server.ts +11 -2
  243. package/src/daemon/tool-side-effects.ts +27 -5
  244. package/src/heartbeat/heartbeat-service.ts +1 -0
  245. package/src/hooks/cli.ts +2 -2
  246. package/src/hooks/runner.ts +15 -38
  247. package/src/inbound/platform-callback-registration.ts +14 -14
  248. package/src/memory/admin.ts +11 -45
  249. package/src/memory/conversation-bootstrap.ts +2 -0
  250. package/src/memory/conversation-crud.ts +242 -348
  251. package/src/memory/conversation-group-migration.ts +157 -0
  252. package/src/memory/conversation-queries.ts +4 -2
  253. package/src/memory/db-init.ts +30 -3
  254. package/src/memory/embed.ts +73 -0
  255. package/src/memory/embedding-backend.ts +8 -14
  256. package/src/memory/embedding-runtime-manager.ts +12 -114
  257. package/src/memory/fingerprint.ts +2 -2
  258. package/src/memory/graph/bootstrap.ts +512 -0
  259. package/src/memory/graph/capability-seed.ts +297 -0
  260. package/src/memory/graph/consolidation.ts +691 -0
  261. package/src/memory/graph/conversation-graph-memory.ts +630 -0
  262. package/src/memory/graph/decay.test.ts +208 -0
  263. package/src/memory/graph/decay.ts +195 -0
  264. package/src/memory/graph/extraction-job.ts +69 -0
  265. package/src/memory/graph/extraction.test.ts +936 -0
  266. package/src/memory/graph/extraction.ts +1254 -0
  267. package/src/memory/graph/graph-search.ts +266 -0
  268. package/src/memory/graph/image-ref-utils.ts +29 -0
  269. package/src/memory/graph/injection.test.ts +513 -0
  270. package/src/memory/graph/injection.ts +439 -0
  271. package/src/memory/graph/inspect.ts +534 -0
  272. package/src/memory/graph/narrative.ts +267 -0
  273. package/src/memory/graph/pattern-scan.ts +269 -0
  274. package/src/memory/graph/retriever.ts +1008 -0
  275. package/src/memory/graph/scoring.test.ts +548 -0
  276. package/src/memory/graph/scoring.ts +232 -0
  277. package/src/memory/graph/serendipity.ts +65 -0
  278. package/src/memory/graph/store.test.ts +1050 -0
  279. package/src/memory/graph/store.ts +699 -0
  280. package/src/memory/graph/tool-handlers.ts +426 -0
  281. package/src/memory/graph/tools.ts +141 -0
  282. package/src/memory/graph/triggers.test.ts +487 -0
  283. package/src/memory/graph/triggers.ts +223 -0
  284. package/src/memory/graph/types.ts +271 -0
  285. package/src/memory/group-crud.ts +191 -0
  286. package/src/memory/indexer.ts +37 -19
  287. package/src/memory/job-handlers/cleanup.ts +0 -53
  288. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  289. package/src/memory/job-handlers/embedding.ts +5 -31
  290. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  291. package/src/memory/job-handlers/summarization.ts +32 -17
  292. package/src/memory/job-utils.ts +1 -1
  293. package/src/memory/jobs-store.ts +50 -70
  294. package/src/memory/jobs-worker.ts +147 -112
  295. package/src/memory/message-content.ts +1 -0
  296. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  297. package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
  298. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  299. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  300. package/src/memory/migrations/index.ts +4 -0
  301. package/src/memory/migrations/registry.ts +8 -0
  302. package/src/memory/qdrant-client.ts +44 -17
  303. package/src/memory/schema/index.ts +1 -0
  304. package/src/memory/schema/memory-graph.ts +139 -0
  305. package/src/memory/search/semantic.ts +47 -91
  306. package/src/memory/task-memory-cleanup.ts +28 -50
  307. package/src/messaging/providers/outlook/adapter.ts +8 -1
  308. package/src/messaging/providers/outlook/client.ts +299 -0
  309. package/src/messaging/providers/outlook/types.ts +118 -0
  310. package/src/notifications/adapters/macos.ts +1 -0
  311. package/src/notifications/copy-composer.ts +9 -0
  312. package/src/notifications/signal.ts +16 -0
  313. package/src/oauth/seed-providers.ts +2 -1
  314. package/src/permissions/checker.ts +24 -3
  315. package/src/permissions/defaults.ts +4 -4
  316. package/src/permissions/workspace-policy.ts +1 -1
  317. package/src/playbooks/playbook-compiler.ts +19 -18
  318. package/src/playbooks/types.ts +4 -3
  319. package/src/prompts/system-prompt.ts +3 -29
  320. package/src/providers/anthropic/client.ts +47 -19
  321. package/src/providers/gemini/client.ts +1 -1
  322. package/src/providers/openai/client.ts +1 -1
  323. package/src/providers/registry.ts +1 -1
  324. package/src/providers/retry.ts +19 -3
  325. package/src/runtime/actor-trust-resolver.ts +5 -1
  326. package/src/runtime/auth/route-policy.ts +7 -0
  327. package/src/runtime/guardian-reply-router.ts +5 -1
  328. package/src/runtime/http-server.ts +23 -3
  329. package/src/runtime/middleware/auth.ts +20 -0
  330. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  331. package/src/runtime/routes/attachment-routes.ts +106 -16
  332. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  333. package/src/runtime/routes/btw-routes.ts +8 -0
  334. package/src/runtime/routes/conversation-management-routes.ts +2 -0
  335. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  336. package/src/runtime/routes/debug-routes.ts +1 -1
  337. package/src/runtime/routes/global-search-routes.ts +21 -19
  338. package/src/runtime/routes/group-routes.ts +207 -0
  339. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  340. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  341. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  342. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  343. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +2 -14
  345. package/src/runtime/routes/memory-item-routes.ts +341 -388
  346. package/src/runtime/routes/schedule-routes.ts +2 -0
  347. package/src/runtime/routes/skills-routes.ts +103 -37
  348. package/src/runtime/routes/work-items-routes.test.ts +2 -6
  349. package/src/schedule/scheduler.ts +8 -1
  350. package/src/security/oauth2.ts +1 -1
  351. package/src/security/secure-keys.ts +4 -8
  352. package/src/shared/provider-env-vars.ts +19 -0
  353. package/src/skills/catalog-cache.ts +5 -0
  354. package/src/skills/catalog-install.ts +15 -14
  355. package/src/skills/clawhub.ts +134 -154
  356. package/src/skills/install-meta.ts +208 -0
  357. package/src/skills/managed-store.ts +27 -16
  358. package/src/skills/skill-memory.ts +152 -77
  359. package/src/skills/skillssh-registry.ts +19 -17
  360. package/src/tasks/task-runner.ts +3 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  362. package/src/tools/browser/runtime-check.ts +3 -1
  363. package/src/tools/memory/register.ts +63 -46
  364. package/src/tools/permission-checker.ts +7 -1
  365. package/src/tools/shared/filesystem/image-read.ts +22 -85
  366. package/src/tools/terminal/safe-env.ts +1 -0
  367. package/src/tools/tool-manifest.ts +3 -3
  368. package/src/util/browser.ts +25 -10
  369. package/src/util/bun-runtime.ts +172 -0
  370. package/src/watcher/providers/outlook-calendar.ts +343 -0
  371. package/src/watcher/providers/outlook.ts +198 -0
  372. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  373. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  374. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  375. package/src/workspace/migrations/registry.ts +6 -0
  376. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  377. package/src/__tests__/journal-context.test.ts +0 -268
  378. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  379. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  380. package/src/__tests__/memory-query-builder.test.ts +0 -59
  381. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  382. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  383. package/src/__tests__/memory-regressions.test.ts +0 -3696
  384. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  385. package/src/daemon/conversation-memory.ts +0 -207
  386. package/src/memory/conversation-starters-cadence.ts +0 -74
  387. package/src/memory/items-extractor.ts +0 -860
  388. package/src/memory/job-handlers/batch-extraction.ts +0 -753
  389. package/src/memory/job-handlers/extraction.ts +0 -40
  390. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
  391. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  392. package/src/memory/journal-memory.ts +0 -224
  393. package/src/memory/query-builder.ts +0 -47
  394. package/src/memory/query-expansion.ts +0 -83
  395. package/src/memory/retriever.test.ts +0 -1592
  396. package/src/memory/retriever.ts +0 -1331
  397. package/src/memory/search/formatting.test.ts +0 -140
  398. package/src/memory/search/formatting.ts +0 -262
  399. package/src/memory/search/mmr.ts +0 -139
  400. package/src/memory/search/ranking.ts +0 -15
  401. package/src/memory/search/staleness.ts +0 -40
  402. package/src/memory/search/tier-classifier.ts +0 -18
  403. package/src/memory/search/types.ts +0 -121
  404. package/src/prompts/journal-context.ts +0 -154
  405. package/src/tools/memory/definitions.ts +0 -69
  406. package/src/tools/memory/handlers.test.ts +0 -562
  407. package/src/tools/memory/handlers.ts +0 -434
@@ -56,7 +56,7 @@ import * as contactMerge from "./bundled-skills/contacts/tools/contact-merge.js"
56
56
  import * as contactSearch from "./bundled-skills/contacts/tools/contact-search.js";
57
57
  import * as contactUpsert from "./bundled-skills/contacts/tools/contact-upsert.js";
58
58
  import * as googleContacts from "./bundled-skills/contacts/tools/google-contacts.js";
59
- // ── conversations ─────────────────────────────────────────────────────────────
59
+ // ── conversations ──────────────────────────────────────────────────────────────
60
60
  import * as renameConversation from "./bundled-skills/conversations/tools/rename-conversation.js";
61
61
  // ── document ───────────────────────────────────────────────────────────────────
62
62
  import * as documentCreate from "./bundled-skills/document/tools/document-create.js";
@@ -107,6 +107,25 @@ import * as messagingSend from "./bundled-skills/messaging/tools/messaging-send.
107
107
  import * as messagingSenderDigest from "./bundled-skills/messaging/tools/messaging-sender-digest.js";
108
108
  // ── notifications ──────────────────────────────────────────────────────────────
109
109
  import * as sendNotification from "./bundled-skills/notifications/tools/send-notification.js";
110
+ // ── outlook ────────────────────────────────────────────────────────────────────
111
+ import * as outlookAttachments from "./bundled-skills/outlook/tools/outlook-attachments.js";
112
+ import * as outlookCategories from "./bundled-skills/outlook/tools/outlook-categories.js";
113
+ import * as outlookDraft from "./bundled-skills/outlook/tools/outlook-draft.js";
114
+ import * as outlookFollowUp from "./bundled-skills/outlook/tools/outlook-follow-up.js";
115
+ import * as outlookForward from "./bundled-skills/outlook/tools/outlook-forward.js";
116
+ import * as outlookOutreachScan from "./bundled-skills/outlook/tools/outlook-outreach-scan.js";
117
+ import * as outlookRules from "./bundled-skills/outlook/tools/outlook-rules.js";
118
+ import * as outlookSendDraft from "./bundled-skills/outlook/tools/outlook-send-draft.js";
119
+ import * as outlookSenderDigest from "./bundled-skills/outlook/tools/outlook-sender-digest.js";
120
+ import * as outlookTrash from "./bundled-skills/outlook/tools/outlook-trash.js";
121
+ import * as outlookUnsubscribe from "./bundled-skills/outlook/tools/outlook-unsubscribe.js";
122
+ import * as outlookVacation from "./bundled-skills/outlook/tools/outlook-vacation.js";
123
+ // ── outlook-calendar ───────────────────────────────────────────────────────────
124
+ import * as outlookCalendarCheckAvailability from "./bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.js";
125
+ import * as outlookCalendarCreateEvent from "./bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.js";
126
+ import * as outlookCalendarGetEvent from "./bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.js";
127
+ import * as outlookCalendarListEvents from "./bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.js";
128
+ import * as outlookCalendarRsvp from "./bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.js";
110
129
  // ── phone-calls ────────────────────────────────────────────────────────────────
111
130
  import * as callEnd from "./bundled-skills/phone-calls/tools/call-end.js";
112
131
  import * as callStart from "./bundled-skills/phone-calls/tools/call-start.js";
@@ -290,6 +309,39 @@ export const bundledToolRegistry = new Map<string, SkillToolScript>([
290
309
  // notifications
291
310
  ["notifications:tools/send-notification.ts", sendNotification],
292
311
 
312
+ // outlook
313
+ ["outlook:tools/outlook-rules.ts", outlookRules],
314
+ ["outlook:tools/outlook-vacation.ts", outlookVacation],
315
+ ["outlook:tools/outlook-sender-digest.ts", outlookSenderDigest],
316
+ ["outlook:tools/outlook-outreach-scan.ts", outlookOutreachScan],
317
+ ["outlook:tools/outlook-draft.ts", outlookDraft],
318
+ ["outlook:tools/outlook-send-draft.ts", outlookSendDraft],
319
+ ["outlook:tools/outlook-forward.ts", outlookForward],
320
+ ["outlook:tools/outlook-trash.ts", outlookTrash],
321
+ ["outlook:tools/outlook-categories.ts", outlookCategories],
322
+ ["outlook:tools/outlook-follow-up.ts", outlookFollowUp],
323
+ ["outlook:tools/outlook-unsubscribe.ts", outlookUnsubscribe],
324
+ ["outlook:tools/outlook-attachments.ts", outlookAttachments],
325
+
326
+ // outlook-calendar
327
+ [
328
+ "outlook-calendar:tools/outlook-calendar-list-events.ts",
329
+ outlookCalendarListEvents,
330
+ ],
331
+ [
332
+ "outlook-calendar:tools/outlook-calendar-get-event.ts",
333
+ outlookCalendarGetEvent,
334
+ ],
335
+ [
336
+ "outlook-calendar:tools/outlook-calendar-create-event.ts",
337
+ outlookCalendarCreateEvent,
338
+ ],
339
+ [
340
+ "outlook-calendar:tools/outlook-calendar-check-availability.ts",
341
+ outlookCalendarCheckAvailability,
342
+ ],
343
+ ["outlook-calendar:tools/outlook-calendar-rsvp.ts", outlookCalendarRsvp],
344
+
293
345
  // phone-calls
294
346
  ["phone-calls:tools/call-start.ts", callStart],
295
347
  ["phone-calls:tools/call-status.ts", callStatus],
@@ -322,12 +374,12 @@ export const bundledToolRegistry = new Map<string, SkillToolScript>([
322
374
  ["sequences:tools/sequence-analytics.ts", sequenceAnalytics],
323
375
 
324
376
  // settings
325
- ["settings:tools/avatar-get.ts", avatarGet],
326
- ["settings:tools/avatar-update.ts", avatarUpdate],
327
- ["settings:tools/avatar-remove.ts", avatarRemove],
328
377
  ["settings:tools/voice-config-update.ts", voiceConfigUpdate],
329
378
  ["settings:tools/open-system-settings.ts", openSystemSettings],
330
379
  ["settings:tools/navigate-settings-tab.ts", navigateSettingsTab],
380
+ ["settings:tools/avatar-update.ts", avatarUpdate],
381
+ ["settings:tools/avatar-remove.ts", avatarRemove],
382
+ ["settings:tools/avatar-get.ts", avatarGet],
331
383
 
332
384
  // skill-management
333
385
  ["skill-management:tools/scaffold-managed.ts", scaffoldManaged],
@@ -45,17 +45,24 @@ export function getIsContainerized(): boolean {
45
45
  }
46
46
 
47
47
  /**
48
- * Whether this assistant is running as a platform-managed remote instance.
48
+ * IS_PLATFORM boolean, default: false
49
+ * When true, indicates the assistant is running as a platform-managed
50
+ * remote instance. Controls platform-specific behaviors like webhook
51
+ * callback registration and blocking `platform disconnect`.
49
52
  *
50
- * Currently this is determined solely by the IS_CONTAINERIZED env var, which
51
- * the platform sets when provisioning assistant containers. This is not ideal
52
- * because any Docker environment could set it. We plan to replace this with a
53
- * less spoof-able mechanism in the future — e.g. a signed platform token
54
- * verified via asymmetric cryptography or an authenticated attestation
55
- * endpoint on the platform.
53
+ * Separate from IS_CONTAINERIZED because local Docker assistants are
54
+ * containerized (need CES sidecar, gateway trust store, etc.) but are
55
+ * not platform-managed.
56
+ */
57
+ export function getIsPlatform(): boolean {
58
+ return flag("IS_PLATFORM");
59
+ }
60
+
61
+ /**
62
+ * Whether this assistant is running as a platform-managed remote instance.
56
63
  */
57
64
  export function isPlatformRemote(): boolean {
58
- return getIsContainerized();
65
+ return getIsPlatform();
59
66
  }
60
67
 
61
68
  /**
@@ -17,14 +17,6 @@
17
17
  "description": "Enable user-hosted onboarding flow",
18
18
  "defaultEnabled": false
19
19
  },
20
- {
21
- "id": "platform-hosted-enabled",
22
- "scope": "macos",
23
- "key": "platform-hosted-enabled",
24
- "label": "Platform Hosted Assistants",
25
- "description": "Enable the Vellum Cloud hosting option on the Hosting screen",
26
- "defaultEnabled": false
27
- },
28
20
  {
29
21
  "id": "local-docker-enabled",
30
22
  "scope": "macos",
@@ -33,14 +25,6 @@
33
25
  "description": "When enabled, the Local hosting option uses Docker under the hood for sandboxed execution, hiding the separate Docker card",
34
26
  "defaultEnabled": false
35
27
  },
36
- {
37
- "id": "contacts",
38
- "scope": "assistant",
39
- "key": "contacts",
40
- "label": "Contacts",
41
- "description": "Show the Contacts tab in Settings for viewing and managing contacts",
42
- "defaultEnabled": true
43
- },
44
28
  {
45
29
  "id": "email-channel",
46
30
  "scope": "assistant",
@@ -145,102 +129,6 @@
145
129
  "description": "Enable managed (organization-hosted) sign-in flow in the onboarding experience",
146
130
  "defaultEnabled": true
147
131
  },
148
- {
149
- "id": "integration-twitter",
150
- "scope": "assistant",
151
- "key": "integration-twitter",
152
- "label": "Twitter / X Integration",
153
- "description": "Enable the Twitter / X OAuth setup skill for connecting to the Twitter API",
154
- "defaultEnabled": false
155
- },
156
- {
157
- "id": "integration-github",
158
- "scope": "assistant",
159
- "key": "integration-github",
160
- "label": "GitHub Integration",
161
- "description": "Enable the GitHub OAuth setup skill for connecting to GitHub",
162
- "defaultEnabled": false
163
- },
164
- {
165
- "id": "integration-linear",
166
- "scope": "assistant",
167
- "key": "integration-linear",
168
- "label": "Linear Integration",
169
- "description": "Enable the Linear OAuth setup skill for connecting to Linear",
170
- "defaultEnabled": false
171
- },
172
- {
173
- "id": "integration-spotify",
174
- "scope": "assistant",
175
- "key": "integration-spotify",
176
- "label": "Spotify Integration",
177
- "description": "Enable the Spotify OAuth setup skill for connecting to the Spotify API",
178
- "defaultEnabled": false
179
- },
180
- {
181
- "id": "integration-todoist",
182
- "scope": "assistant",
183
- "key": "integration-todoist",
184
- "label": "Todoist Integration",
185
- "description": "Enable the Todoist OAuth setup skill for connecting to Todoist",
186
- "defaultEnabled": false
187
- },
188
- {
189
- "id": "integration-discord",
190
- "scope": "assistant",
191
- "key": "integration-discord",
192
- "label": "Discord Integration",
193
- "description": "Enable the Discord OAuth setup skill for connecting to Discord",
194
- "defaultEnabled": false
195
- },
196
- {
197
- "id": "integration-dropbox",
198
- "scope": "assistant",
199
- "key": "integration-dropbox",
200
- "label": "Dropbox Integration",
201
- "description": "Enable the Dropbox OAuth setup skill for connecting to Dropbox",
202
- "defaultEnabled": false
203
- },
204
- {
205
- "id": "integration-asana",
206
- "scope": "assistant",
207
- "key": "integration-asana",
208
- "label": "Asana Integration",
209
- "description": "Enable the Asana OAuth setup skill for connecting to Asana",
210
- "defaultEnabled": false
211
- },
212
- {
213
- "id": "integration-airtable",
214
- "scope": "assistant",
215
- "key": "integration-airtable",
216
- "label": "Airtable Integration",
217
- "description": "Enable the Airtable OAuth setup skill for connecting to Airtable",
218
- "defaultEnabled": false
219
- },
220
- {
221
- "id": "integration-hubspot",
222
- "scope": "assistant",
223
- "key": "integration-hubspot",
224
- "label": "HubSpot Integration",
225
- "description": "Enable the HubSpot OAuth setup skill for connecting to HubSpot CRM",
226
- "defaultEnabled": false
227
- },
228
- {
229
- "id": "integration-figma",
230
- "scope": "assistant",
231
- "key": "integration-figma",
232
- "label": "Figma Integration",
233
- "description": "Enable the Figma OAuth setup skill for connecting to Figma",
234
- "defaultEnabled": false
235
- },
236
- {
237
- "id": "integration-notion",
238
- "scope": "assistant",
239
- "key": "integration-notion",
240
- "label": "Notion Integration",
241
- "description": "Enable the Notion setup skill for connecting to Notion",
242
- "defaultEnabled": false
243
- },
244
132
  {
245
133
  "id": "conversation-starters",
246
134
  "scope": "assistant",
@@ -281,6 +169,14 @@
281
169
  "description": "Show the Schedules tab in Settings for viewing and managing schedules",
282
170
  "defaultEnabled": false
283
171
  },
172
+ {
173
+ "id": "settings-integrations-grid",
174
+ "scope": "assistant",
175
+ "key": "settings-integrations-grid",
176
+ "label": "Integrations Grid",
177
+ "description": "Show the Integrations grid in Models & Services settings, replacing individual OAuth provider cards",
178
+ "defaultEnabled": false
179
+ },
284
180
  {
285
181
  "id": "quick-input",
286
182
  "scope": "macos",
@@ -345,14 +241,6 @@
345
241
  "description": "Show older versions in the version picker, allowing rollback to previous releases",
346
242
  "defaultEnabled": true
347
243
  },
348
- {
349
- "id": "show-background-conversations",
350
- "scope": "assistant",
351
- "key": "show-background-conversations",
352
- "label": "Show Background Conversations",
353
- "description": "Include background conversations (heartbeat, tasks) in the sidebar conversation list",
354
- "defaultEnabled": false
355
- },
356
244
  {
357
245
  "id": "voice-mode",
358
246
  "scope": "assistant",
@@ -370,12 +258,21 @@
370
258
  "defaultEnabled": false
371
259
  },
372
260
  {
373
- "id": "outlook-oauth-integration",
261
+ "id": "conversation-groups-ui",
374
262
  "scope": "assistant",
375
- "key": "outlook-oauth-integration",
376
- "label": "Outlook / Microsoft Integration",
377
- "description": "Enable the Outlook / Microsoft OAuth provider for connecting to Microsoft Graph APIs (email, calendar)",
263
+ "key": "conversation-groups-ui",
264
+ "label": "Conversation Groups",
265
+ "description": "Enable custom conversation group creation, move-to-group, and group management in the sidebar",
266
+ "defaultEnabled": false
267
+ },
268
+ {
269
+ "id": "teleport",
270
+ "scope": "macos",
271
+ "key": "teleport",
272
+ "label": "Teleport",
273
+ "description": "Enable teleport UI in General settings for moving assistants between hosting environments",
378
274
  "defaultEnabled": false
379
275
  }
380
276
  ]
381
277
  }
278
+
@@ -56,6 +56,14 @@ export const UiConfigSchema = z
56
56
  .describe(
57
57
  "IANA timezone identifier for displaying dates and times (e.g. 'America/New_York')",
58
58
  ),
59
+ greetingModelIntent: z
60
+ .enum(["latency-optimized", "quality-optimized"], {
61
+ error: "ui.greetingModelIntent must be 'latency-optimized' or 'quality-optimized'",
62
+ })
63
+ .default("latency-optimized")
64
+ .describe(
65
+ "Model intent for empty-state greeting generation (latency-optimized = fast/small model, quality-optimized = primary model)",
66
+ ),
59
67
  })
60
68
  .describe("User interface display settings");
61
69
 
@@ -36,7 +36,7 @@ export const TimeoutConfigSchema = z
36
36
  .number({ error: "timeouts.providerStreamTimeoutSec must be a number" })
37
37
  .finite("timeouts.providerStreamTimeoutSec must be finite")
38
38
  .positive("timeouts.providerStreamTimeoutSec must be a positive number")
39
- .default(300)
39
+ .default(1800)
40
40
  .describe(
41
41
  "Timeout for waiting on the LLM provider's streaming response (seconds)",
42
42
  ),
@@ -73,7 +73,6 @@ export interface SkillSummary {
73
73
  bundled?: boolean;
74
74
  icon?: string;
75
75
  emoji?: string;
76
- homepage?: string;
77
76
  source: SkillSource;
78
77
  /** Parsed tool manifest metadata, if the skill has a valid TOOLS.json. */
79
78
  toolManifest?: SkillToolManifestMeta;
@@ -962,8 +961,9 @@ function isEscapingSymlink(filePath: string, rootDir: string): boolean {
962
961
  /**
963
962
  * Check for a `references/` subdirectory within a skill directory and return
964
963
  * a formatted listing of available `.md` reference files with full absolute
965
- * paths. Returns `null` if no references exist. Files are listed in
966
- * alphabetical order. Non-`.md` files are ignored. Symlinks that resolve
964
+ * paths. Returns `null` if no references exist. Files are discovered
965
+ * recursively through subdirectories and listed alphabetically within each
966
+ * directory level. Non-`.md` files are ignored. Symlinks that resolve
967
967
  * outside the skill directory are skipped.
968
968
  */
969
969
  export function listReferenceFiles(directoryPath: string): string | null {
@@ -977,10 +977,21 @@ export function listReferenceFiles(directoryPath: string): string | null {
977
977
  return null;
978
978
  }
979
979
 
980
- const entries = readdirSync(refsDir);
980
+ const entries = readdirSync(refsDir, { recursive: true }) as string[];
981
981
  const mdFiles = entries
982
982
  .filter((f) => f.toLowerCase().endsWith(".md"))
983
- .filter((f) => !isEscapingSymlink(join(refsDir, f), directoryPath))
983
+ .filter((f) => {
984
+ // Check the file itself
985
+ if (isEscapingSymlink(join(refsDir, f), directoryPath)) return false;
986
+ // Check all intermediate directory components (e.g. for "sub/dir/file.md"
987
+ // check "sub" and "sub/dir") to prevent traversal through symlinked dirs.
988
+ const parts = f.split("/");
989
+ for (let i = 1; i < parts.length; i++) {
990
+ const ancestor = join(refsDir, ...parts.slice(0, i));
991
+ if (isEscapingSymlink(ancestor, directoryPath)) return false;
992
+ }
993
+ return true;
994
+ })
984
995
  .sort((a, b) => a.localeCompare(b));
985
996
 
986
997
  if (mdFiles.length === 0) return null;
@@ -991,8 +1002,8 @@ export function listReferenceFiles(directoryPath: string): string | null {
991
1002
  "The following reference files are available in this skill's directory. Use `file_read` to load any that are relevant to the current task:",
992
1003
  "",
993
1004
  ];
994
- for (const filename of mdFiles) {
995
- lines.push(`- \`${join(refsDir, filename)}\``);
1005
+ for (const filepath of mdFiles) {
1006
+ lines.push(`- \`${join(refsDir, filepath)}\` (references/${filepath})`);
996
1007
  }
997
1008
 
998
1009
  return lines.join("\n");
@@ -9,7 +9,6 @@ const CHARS_PER_TOKEN = 4;
9
9
  const MESSAGE_OVERHEAD_TOKENS = 4;
10
10
  const TEXT_BLOCK_OVERHEAD_TOKENS = 2;
11
11
  const TOOL_BLOCK_OVERHEAD_TOKENS = 16;
12
- const IMAGE_BLOCK_TOKENS = 1024;
13
12
  const IMAGE_BLOCK_OVERHEAD_TOKENS = 16;
14
13
  const FILE_BLOCK_OVERHEAD_TOKENS = 48;
15
14
  const WEB_SEARCH_RESULT_TOKENS = 800;
@@ -20,12 +19,15 @@ const GEMINI_INLINE_FILE_MIME_TYPES = new Set(["application/pdf"]);
20
19
  // Anthropic scales images to fit within 1568x1568 maintaining aspect ratio,
21
20
  // then charges ~(width * height) / 750 tokens.
22
21
  const ANTHROPIC_IMAGE_MAX_DIMENSION = 1568;
22
+ // Anthropic caps images at ~1.2 megapixels in addition to the 1568px dimension limit.
23
+ // Images exceeding this are further scaled down. The docs state images above ~1,600 tokens
24
+ // are resized. 1,200,000 / 750 = 1,600 tokens, matching the documented threshold.
25
+ // Reference table (max sizes that won't be resized):
26
+ // 1:1 → 1092x1092 (~1,590 tokens) 1:2 → 784x1568 (~1,639 tokens)
27
+ // See: https://platform.claude.com/docs/en/build-with-claude/vision#evaluate-image-size
28
+ const ANTHROPIC_IMAGE_MAX_PIXELS = 1_200_000;
23
29
  const ANTHROPIC_IMAGE_TOKENS_PER_PIXEL = 1 / 750;
24
- const ANTHROPIC_IMAGE_MAX_TOKENS = Math.ceil(
25
- ANTHROPIC_IMAGE_MAX_DIMENSION *
26
- ANTHROPIC_IMAGE_MAX_DIMENSION *
27
- ANTHROPIC_IMAGE_TOKENS_PER_PIXEL,
28
- ); // ~3,277 tokens
30
+ const ANTHROPIC_IMAGE_MAX_TOKENS = 1_600;
29
31
 
30
32
  // Anthropic renders each PDF page as an image (~1,568 tokens at standard
31
33
  // resolution) plus any extracted text. Typical PDF pages are 50-150 KB.
@@ -85,17 +87,23 @@ function estimateFileDataTokens(
85
87
  }
86
88
 
87
89
  function estimateAnthropicImageTokens(width: number, height: number): number {
88
- // Scale down to fit within 1568x1568 bounding box, maintaining aspect ratio
89
- const scale = Math.min(
90
+ // Step 1: Scale to fit within 1568px bounding box
91
+ const dimScale = Math.min(
90
92
  1,
91
93
  ANTHROPIC_IMAGE_MAX_DIMENSION / Math.max(width, height),
92
94
  );
93
- const scaledWidth = Math.round(width * scale);
94
- const scaledHeight = Math.round(height * scale);
95
- return Math.max(
96
- IMAGE_BLOCK_TOKENS, // minimum 1024
97
- Math.ceil(scaledWidth * scaledHeight * ANTHROPIC_IMAGE_TOKENS_PER_PIXEL),
98
- );
95
+ let scaledWidth = Math.round(width * dimScale);
96
+ let scaledHeight = Math.round(height * dimScale);
97
+
98
+ // Step 2: Scale further if exceeds megapixel budget
99
+ const pixels = scaledWidth * scaledHeight;
100
+ if (pixels > ANTHROPIC_IMAGE_MAX_PIXELS) {
101
+ const mpScale = Math.sqrt(ANTHROPIC_IMAGE_MAX_PIXELS / pixels);
102
+ scaledWidth = Math.round(scaledWidth * mpScale);
103
+ scaledHeight = Math.round(scaledHeight * mpScale);
104
+ }
105
+
106
+ return Math.ceil(scaledWidth * scaledHeight * ANTHROPIC_IMAGE_TOKENS_PER_PIXEL);
99
107
  }
100
108
 
101
109
  function estimateImageTokens(
@@ -143,11 +151,10 @@ export function estimateContentBlockTokens(
143
151
  return tokens;
144
152
  }
145
153
  case "image":
146
- return Math.max(
147
- IMAGE_BLOCK_TOKENS,
154
+ return (
148
155
  IMAGE_BLOCK_OVERHEAD_TOKENS +
149
- estimateTextTokens(block.source.media_type) +
150
- estimateImageTokens(block, options),
156
+ estimateTextTokens(block.source.media_type) +
157
+ estimateImageTokens(block, options)
151
158
  );
152
159
  case "file":
153
160
  return (
@@ -628,14 +628,18 @@ export class ContextWindowManager {
628
628
  const excess = totalTokens - maxTranscriptTokens;
629
629
  if (blockTokens > excess && result[i].type === "text") {
630
630
  // Truncate this block to shed exactly the excess tokens.
631
- const keepTokens = blockTokens - excess;
631
+ // Subtract the cost of the "[...truncated] " prefix so the final
632
+ // block (prefix + kept text) stays within budget.
633
+ const truncationPrefix = "[...truncated] ";
634
+ const prefixTokens = estimateTextTokens(truncationPrefix);
635
+ const keepTokens = Math.max(1, blockTokens - excess - prefixTokens);
632
636
  const text = (result[i] as { type: "text"; text: string }).text;
633
637
  // Approximate: 1 token ≈ 4 characters for truncation purposes.
634
638
  const keepChars = Math.max(1, Math.floor(keepTokens * 4));
635
639
  const truncatedText = text.slice(-keepChars);
636
640
  const truncatedBlock: ContentBlock = {
637
641
  type: "text",
638
- text: `[...truncated] ${truncatedText}`,
642
+ text: `${truncationPrefix}${truncatedText}`,
639
643
  };
640
644
  const newBlockTokens = estimateBlockTokens(truncatedBlock);
641
645
  droppedTokens += blockTokens - newBlockTokens;
@@ -31,6 +31,7 @@ import { StringDecoder } from "node:string_decoder";
31
31
  import type { Subprocess } from "bun";
32
32
 
33
33
  import type { AssistantConfig } from "../config/schema.js";
34
+ import { ensureBun } from "../util/bun-runtime.js";
34
35
  import { getLogger } from "../util/logger.js";
35
36
  import type { CesTransport } from "./client.js";
36
37
  import {
@@ -266,8 +267,9 @@ export function createCesProcessManager(
266
267
  "Spawning CES child process from source",
267
268
  );
268
269
 
270
+ const bunPath = await ensureBun();
269
271
  const proc = Bun.spawn({
270
- cmd: ["bun", "run", discovery.sourcePath],
272
+ cmd: [bunPath, "run", discovery.sourcePath],
271
273
  stdin: "pipe",
272
274
  stdout: "pipe",
273
275
  stderr: "ignore",
@@ -17,7 +17,10 @@
17
17
  */
18
18
 
19
19
  import type { ContextWindowConfig } from "../config/types.js";
20
- import { estimatePromptTokens } from "../context/token-estimator.js";
20
+ import {
21
+ estimateContentBlockTokens,
22
+ estimatePromptTokens,
23
+ } from "../context/token-estimator.js";
21
24
  import { truncateToolResultsAcrossHistory } from "../context/tool-result-truncation.js";
22
25
  import type {
23
26
  ContextWindowCompactOptions,
@@ -26,6 +29,7 @@ import type {
26
29
  import type { Message } from "../providers/types.js";
27
30
  import {
28
31
  countMediaBlocks,
32
+ estimateUnconditionalStubTokens,
29
33
  stripMediaPayloadsForRetry,
30
34
  } from "./conversation-media-retry.js";
31
35
  import type { InjectionMode } from "./conversation-runtime-assembly.js";
@@ -240,7 +244,47 @@ function applyMediaStubbing(
240
244
  let nextMessages = messages;
241
245
 
242
246
  if (mediaCount > 0) {
243
- const stripped = stripMediaPayloadsForRetry(messages);
247
+ // Compute the token budget available for media content.
248
+ const totalTokens = estimatePromptTokens(messages, config.systemPrompt, {
249
+ providerName: config.providerName,
250
+ toolTokenBudget: config.toolTokenBudget,
251
+ });
252
+
253
+ // Sum tokens for all image and file blocks (top-level and nested in tool_result).
254
+ let mediaTokens = 0;
255
+ for (const msg of messages) {
256
+ for (const block of msg.content) {
257
+ if (block.type === "image" || block.type === "file") {
258
+ mediaTokens += estimateContentBlockTokens(block, {
259
+ providerName: config.providerName,
260
+ });
261
+ } else if (block.type === "tool_result" && block.contentBlocks) {
262
+ for (const cb of block.contentBlocks) {
263
+ if (cb.type === "image" || cb.type === "file") {
264
+ mediaTokens += estimateContentBlockTokens(cb, {
265
+ providerName: config.providerName,
266
+ });
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ const nonMediaTokens = totalTokens - mediaTokens;
274
+
275
+ // Account for the token cost of text stubs that replace unconditionally
276
+ // stubbed media (non-latest-user images/files, tool_result-nested media).
277
+ // Without this adjustment the budget is systematically over-allocated.
278
+ const estimatedStubTokens = estimateUnconditionalStubTokens(messages, {
279
+ providerName: config.providerName,
280
+ });
281
+ const adjustedNonMediaTokens = nonMediaTokens + estimatedStubTokens;
282
+ const mediaTokenBudget = Math.max(0, config.targetTokens - adjustedNonMediaTokens);
283
+
284
+ const stripped = stripMediaPayloadsForRetry(messages, {
285
+ mediaTokenBudget,
286
+ providerName: config.providerName,
287
+ });
244
288
  if (stripped.modified) {
245
289
  nextMessages = stripped.messages;
246
290
  }