@vellumai/assistant 0.6.4 → 0.6.5

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 (717) hide show
  1. package/.prettierignore +5 -0
  2. package/ARCHITECTURE.md +32 -36
  3. package/Dockerfile +12 -0
  4. package/README.md +3 -4
  5. package/bun.lock +8 -3
  6. package/docs/architecture/integrations.md +1 -20
  7. package/docs/architecture/security.md +16 -16
  8. package/docs/error-handling.md +111 -0
  9. package/docs/skills.md +10 -10
  10. package/docs/stt-provider-onboarding.md +2 -1
  11. package/knip.json +9 -2
  12. package/node_modules/@vellumai/ces-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  14. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  15. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  16. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  17. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  18. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  19. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  20. package/openapi.yaml +123 -11
  21. package/package.json +6 -3
  22. package/scripts/generate-openapi.ts +50 -11
  23. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  24. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  25. package/src/__tests__/agent-loop.test.ts +112 -1
  26. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  27. package/src/__tests__/anthropic-provider.test.ts +171 -2
  28. package/src/__tests__/approval-cascade.test.ts +31 -10
  29. package/src/__tests__/approval-routes-http.test.ts +134 -10
  30. package/src/__tests__/assistant-attachments.test.ts +44 -0
  31. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  32. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  33. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  34. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  35. package/src/__tests__/browser-skill-endstate.test.ts +51 -182
  36. package/src/__tests__/btw-routes.test.ts +47 -1
  37. package/src/__tests__/call-controller.test.ts +1 -2
  38. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  39. package/src/__tests__/catalog-cache.test.ts +27 -4
  40. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  41. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  42. package/src/__tests__/checker.test.ts +428 -501
  43. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  44. package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
  45. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  46. package/src/__tests__/config-analysis.test.ts +11 -28
  47. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  48. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  49. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  50. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  51. package/src/__tests__/config-schema.test.ts +427 -114
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/contact-store-user-file.test.ts +72 -73
  54. package/src/__tests__/contacts-write.test.ts +4 -4
  55. package/src/__tests__/context-token-estimator.test.ts +191 -1
  56. package/src/__tests__/context-window-manager.test.ts +530 -2
  57. package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
  58. package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
  59. package/src/__tests__/conversation-agent-loop.test.ts +412 -82
  60. package/src/__tests__/conversation-attachments.test.ts +1 -1
  61. package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
  62. package/src/__tests__/conversation-error.test.ts +37 -6
  63. package/src/__tests__/conversation-history-web-search.test.ts +6 -0
  64. package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
  65. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  66. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  67. package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
  68. package/src/__tests__/conversation-process-callsite.test.ts +306 -0
  69. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
  70. package/src/__tests__/conversation-queue.test.ts +41 -26
  71. package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
  75. package/src/__tests__/conversation-skill-tools.test.ts +12 -146
  76. package/src/__tests__/conversation-slash-queue.test.ts +34 -19
  77. package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
  78. package/src/__tests__/conversation-speed-override.test.ts +30 -11
  79. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  80. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  81. package/src/__tests__/conversation-title-service.test.ts +2 -2
  82. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
  83. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  84. package/src/__tests__/conversation-usage.test.ts +3 -1
  85. package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
  86. package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
  87. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
  88. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  89. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  90. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  91. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  92. package/src/__tests__/credential-vault-unit.test.ts +135 -19
  93. package/src/__tests__/credentials-cli.test.ts +1 -9
  94. package/src/__tests__/cross-provider-web-search.test.ts +84 -0
  95. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  96. package/src/__tests__/delete-propagation.test.ts +437 -0
  97. package/src/__tests__/dm-backfill.test.ts +417 -0
  98. package/src/__tests__/dm-persistence.test.ts +227 -0
  99. package/src/__tests__/edit-propagation.test.ts +280 -0
  100. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  101. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  102. package/src/__tests__/estimator-calibration.test.ts +213 -0
  103. package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
  104. package/src/__tests__/file-write-tool.test.ts +151 -1
  105. package/src/__tests__/filing-service.test.ts +255 -0
  106. package/src/__tests__/gemini-provider.test.ts +0 -3
  107. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  108. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  109. package/src/__tests__/heartbeat-service.test.ts +96 -15
  110. package/src/__tests__/host-shell-tool.test.ts +124 -18
  111. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  112. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  113. package/src/__tests__/intent-routing.test.ts +1 -40
  114. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  115. package/src/__tests__/llm-context-normalization.test.ts +121 -0
  116. package/src/__tests__/llm-resolver.test.ts +214 -0
  117. package/src/__tests__/llm-schema.test.ts +223 -0
  118. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  119. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  120. package/src/__tests__/migration-import-from-url.test.ts +684 -0
  121. package/src/__tests__/model-intents.test.ts +9 -83
  122. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  123. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  124. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  125. package/src/__tests__/oauth-store.test.ts +10 -7
  126. package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
  127. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  128. package/src/__tests__/openai-provider.test.ts +7 -0
  129. package/src/__tests__/openai-responses-provider.test.ts +396 -0
  130. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  131. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  132. package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
  133. package/src/__tests__/permission-mode.test.ts +16 -0
  134. package/src/__tests__/permission-types.test.ts +0 -1
  135. package/src/__tests__/persona-resolver.test.ts +13 -13
  136. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  137. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
  138. package/src/__tests__/pricing.test.ts +50 -3
  139. package/src/__tests__/profiler-routes.test.ts +1 -1
  140. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  141. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  143. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  144. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  145. package/src/__tests__/proxy-approval-callback.test.ts +0 -1
  146. package/src/__tests__/reaction-persistence.test.ts +560 -0
  147. package/src/__tests__/relay-server.test.ts +1 -1
  148. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  149. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  150. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  151. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  152. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  153. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  154. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  155. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  156. package/src/__tests__/secret-scanner-executor.test.ts +1 -1
  157. package/src/__tests__/send-endpoint-busy.test.ts +29 -1
  158. package/src/__tests__/server-history-render.test.ts +31 -0
  159. package/src/__tests__/shell-parser-property.test.ts +13 -13
  160. package/src/__tests__/skill-cache-store.test.ts +182 -0
  161. package/src/__tests__/skills.test.ts +19 -33
  162. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  163. package/src/__tests__/slack-skill.test.ts +3 -8
  164. package/src/__tests__/starter-bundle.test.ts +35 -0
  165. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  166. package/src/__tests__/suggestion-routes.test.ts +160 -3
  167. package/src/__tests__/system-prompt.test.ts +22 -35
  168. package/src/__tests__/task-runner.test.ts +3 -1
  169. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  170. package/src/__tests__/terminal-tools.test.ts +8 -0
  171. package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
  172. package/src/__tests__/thread-backfill.test.ts +941 -0
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  175. package/src/__tests__/tool-executor.test.ts +60 -94
  176. package/src/__tests__/trust-store.test.ts +442 -109
  177. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  178. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  179. package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
  180. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  181. package/src/__tests__/volume-security-guard.test.ts +3 -2
  182. package/src/__tests__/web-search-history.test.ts +337 -0
  183. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  184. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  185. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  186. package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
  187. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  188. package/src/__tests__/workspace-policy.test.ts +1 -13
  189. package/src/acp/client-handler.ts +1 -2
  190. package/src/agent/loop.ts +209 -17
  191. package/src/avatar/resvg-lazy.test.ts +136 -0
  192. package/src/avatar/resvg-lazy.ts +82 -9
  193. package/src/avatar/traits-png-sync.ts +21 -1
  194. package/src/browser/__tests__/operations.test.ts +163 -0
  195. package/src/browser/identifiers.ts +51 -0
  196. package/src/browser/operations.ts +660 -0
  197. package/src/browser/types.ts +81 -0
  198. package/src/calls/guardian-question-copy.ts +2 -2
  199. package/src/calls/telephony-stt-routing.ts +1 -1
  200. package/src/calls/voice-session-bridge.ts +1 -0
  201. package/src/cli/AGENTS.md +1 -1
  202. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  203. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  204. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  205. package/src/cli/commands/__tests__/email-list.test.ts +6 -0
  206. package/src/cli/commands/__tests__/email-send.test.ts +93 -1
  207. package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
  208. package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
  209. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  210. package/src/cli/commands/__tests__/task.test.ts +913 -0
  211. package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
  212. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  213. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  214. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  215. package/src/cli/commands/attachment.ts +182 -0
  216. package/src/cli/commands/browser.ts +350 -0
  217. package/src/cli/commands/cache.ts +341 -0
  218. package/src/cli/commands/completions.ts +0 -3
  219. package/src/cli/commands/config.ts +6 -6
  220. package/src/cli/commands/conversations-import.ts +347 -0
  221. package/src/cli/commands/conversations.ts +14 -1
  222. package/src/cli/commands/email.ts +234 -194
  223. package/src/cli/commands/image-generation.ts +300 -0
  224. package/src/cli/commands/inference.ts +200 -0
  225. package/src/cli/commands/memory.ts +127 -17
  226. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  227. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
  228. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
  229. package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
  230. package/src/cli/commands/stt.ts +339 -0
  231. package/src/cli/commands/task.ts +795 -0
  232. package/src/cli/commands/trust.ts +50 -19
  233. package/src/cli/commands/tts.ts +273 -0
  234. package/src/cli/commands/ui.ts +670 -0
  235. package/src/cli/commands/watchers.ts +509 -0
  236. package/src/cli/lib/daemon-credential-client.ts +0 -19
  237. package/src/cli/program.ts +23 -4
  238. package/src/cli.ts +0 -37
  239. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
  240. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  241. package/src/config/bundled-skills/messaging/SKILL.md +2 -2
  242. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  243. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
  244. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  245. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  246. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
  247. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
  248. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  249. package/src/config/bundled-tool-registry.ts +0 -175
  250. package/src/config/env.ts +7 -2
  251. package/src/config/feature-flag-registry.json +25 -9
  252. package/src/config/llm-resolver.ts +128 -0
  253. package/src/config/loader.ts +194 -10
  254. package/src/config/raw-config-utils.ts +30 -2
  255. package/src/config/sanitize-for-transfer.ts +35 -0
  256. package/src/config/schema.ts +30 -41
  257. package/src/config/schemas/analysis.ts +3 -22
  258. package/src/config/schemas/calls.ts +0 -4
  259. package/src/config/schemas/filing.ts +2 -7
  260. package/src/config/schemas/heartbeat.ts +0 -5
  261. package/src/config/schemas/inference.ts +3 -23
  262. package/src/config/schemas/llm.ts +318 -0
  263. package/src/config/schemas/memory-processing.ts +1 -9
  264. package/src/config/schemas/notifications.ts +4 -11
  265. package/src/config/schemas/platform.ts +3 -9
  266. package/src/config/schemas/security.ts +33 -0
  267. package/src/config/schemas/services.ts +9 -4
  268. package/src/config/schemas/stt.ts +1 -0
  269. package/src/config/schemas/tts.ts +53 -0
  270. package/src/config/schemas/updates.ts +1 -1
  271. package/src/config/schemas/workspace-git.ts +3 -40
  272. package/src/config/skills.ts +2 -2
  273. package/src/context/__tests__/compact-prompt.test.ts +45 -0
  274. package/src/context/__tests__/microcompact.test.ts +805 -0
  275. package/src/context/estimator-calibration.ts +136 -0
  276. package/src/context/microcompact.ts +443 -0
  277. package/src/context/prompts/compact.md +12 -0
  278. package/src/context/token-estimator.ts +61 -3
  279. package/src/context/window-manager.ts +229 -25
  280. package/src/credential-execution/approval-bridge.ts +0 -1
  281. package/src/credential-execution/executable-discovery.ts +19 -8
  282. package/src/credential-execution/process-manager.test.ts +109 -0
  283. package/src/credential-execution/process-manager.ts +65 -2
  284. package/src/daemon/approval-generators.ts +29 -4
  285. package/src/daemon/assistant-attachments.ts +24 -13
  286. package/src/daemon/classifier.ts +2 -2
  287. package/src/daemon/config-watcher.ts +0 -1
  288. package/src/daemon/context-overflow-reducer.ts +4 -1
  289. package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
  290. package/src/daemon/conversation-agent-loop.ts +462 -80
  291. package/src/daemon/conversation-attachments.ts +2 -6
  292. package/src/daemon/conversation-error.ts +36 -1
  293. package/src/daemon/conversation-lifecycle.ts +30 -6
  294. package/src/daemon/conversation-messaging.ts +73 -4
  295. package/src/daemon/conversation-process.ts +10 -4
  296. package/src/daemon/conversation-queue-manager.ts +3 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +760 -29
  298. package/src/daemon/conversation-slash.ts +2 -2
  299. package/src/daemon/conversation-surfaces.ts +389 -1
  300. package/src/daemon/conversation-tool-setup.ts +10 -5
  301. package/src/daemon/conversation-usage.ts +1 -1
  302. package/src/daemon/conversation.ts +118 -30
  303. package/src/daemon/external-skills-bootstrap.ts +41 -0
  304. package/src/daemon/guardian-action-generators.ts +34 -14
  305. package/src/daemon/handlers/config-model.test.ts +86 -0
  306. package/src/daemon/handlers/config-model.ts +54 -12
  307. package/src/daemon/handlers/conversations.ts +9 -2
  308. package/src/daemon/handlers/shared.ts +39 -11
  309. package/src/daemon/handlers/skills.ts +2 -2
  310. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  311. package/src/daemon/lifecycle.ts +76 -14
  312. package/src/daemon/message-types/conversations.ts +14 -0
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/trust.ts +0 -2
  315. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  316. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  317. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  318. package/src/daemon/pkb-context-tracker.ts +125 -0
  319. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  320. package/src/daemon/pkb-reminder-builder.ts +31 -0
  321. package/src/daemon/providers-setup.ts +6 -0
  322. package/src/daemon/server.ts +117 -9
  323. package/src/daemon/tool-side-effects.ts +0 -9
  324. package/src/daemon/watch-handler.ts +4 -4
  325. package/src/daemon/web-search-history.ts +126 -0
  326. package/src/events/domain-events.ts +0 -1
  327. package/src/filing/filing-service.ts +9 -10
  328. package/src/heartbeat/heartbeat-service.ts +76 -28
  329. package/src/home/__tests__/feed-scheduler.test.ts +39 -11
  330. package/src/home/__tests__/rollup-producer.test.ts +44 -0
  331. package/src/home/assistant-feed-authoring.ts +4 -0
  332. package/src/home/emit-feed-event.ts +4 -0
  333. package/src/home/feed-scheduler.ts +20 -4
  334. package/src/home/feed-types.ts +56 -2
  335. package/src/home/relationship-state-writer.ts +2 -2
  336. package/src/home/rollup-producer.ts +34 -5
  337. package/src/home/suggested-prompts.ts +101 -0
  338. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  339. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  340. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  341. package/src/ipc/__tests__/socket-path.test.ts +73 -0
  342. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  343. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  344. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  345. package/src/ipc/cli-client.ts +2 -1
  346. package/src/ipc/cli-server.ts +26 -8
  347. package/src/ipc/gateway-client.ts +4 -4
  348. package/src/ipc/routes/attachment.ts +114 -0
  349. package/src/ipc/routes/browser-context.ts +61 -0
  350. package/src/ipc/routes/browser.ts +96 -0
  351. package/src/ipc/routes/cache.ts +96 -0
  352. package/src/ipc/routes/index.ts +17 -1
  353. package/src/ipc/routes/task-queue.ts +226 -0
  354. package/src/ipc/routes/task.ts +173 -0
  355. package/src/ipc/routes/ui-request.ts +50 -0
  356. package/src/ipc/routes/watcher.ts +203 -0
  357. package/src/ipc/socket-path.ts +100 -0
  358. package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
  359. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  360. package/src/memory/admin.ts +18 -0
  361. package/src/memory/conversation-analyze-job.ts +14 -13
  362. package/src/memory/conversation-attention-store.ts +13 -6
  363. package/src/memory/conversation-crud.ts +103 -3
  364. package/src/memory/conversation-group-migration.ts +38 -6
  365. package/src/memory/conversation-title-service.ts +7 -4
  366. package/src/memory/db-init.ts +2 -0
  367. package/src/memory/embedding-backend.ts +1 -1
  368. package/src/memory/graph/compaction.ts +299 -0
  369. package/src/memory/graph/consolidation.ts +4 -4
  370. package/src/memory/graph/conversation-graph-memory.ts +89 -29
  371. package/src/memory/graph/extraction.test.ts +272 -2
  372. package/src/memory/graph/extraction.ts +173 -51
  373. package/src/memory/graph/graph-search.test.ts +92 -0
  374. package/src/memory/graph/graph-search.ts +4 -1
  375. package/src/memory/graph/narrative.ts +2 -2
  376. package/src/memory/graph/pattern-scan.ts +2 -2
  377. package/src/memory/graph/retriever.test.ts +459 -0
  378. package/src/memory/graph/retriever.ts +230 -48
  379. package/src/memory/graph/store.ts +41 -0
  380. package/src/memory/graph/tool-handlers.ts +27 -0
  381. package/src/memory/graph/tools.ts +6 -1
  382. package/src/memory/indexer.ts +5 -5
  383. package/src/memory/job-handlers/conversation-starters.ts +23 -20
  384. package/src/memory/job-handlers/summarization.ts +2 -2
  385. package/src/memory/job-utils.ts +7 -1
  386. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  387. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  388. package/src/memory/jobs-store.ts +44 -3
  389. package/src/memory/jobs-worker.ts +4 -0
  390. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  391. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
  392. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  393. package/src/memory/migrations/index.ts +1 -0
  394. package/src/memory/pkb/pkb-index.test.ts +368 -0
  395. package/src/memory/pkb/pkb-index.ts +255 -0
  396. package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
  397. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  398. package/src/memory/pkb/pkb-search.test.ts +438 -0
  399. package/src/memory/pkb/pkb-search.ts +137 -0
  400. package/src/memory/pkb/types.ts +53 -0
  401. package/src/memory/qdrant-client.ts +122 -1
  402. package/src/memory/slack-thread-store.ts +37 -0
  403. package/src/messaging/providers/gmail/adapter.ts +6 -16
  404. package/src/messaging/providers/gmail/client.ts +22 -0
  405. package/src/messaging/providers/gmail/types.ts +7 -0
  406. package/src/messaging/providers/slack/adapter.ts +14 -2
  407. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  408. package/src/messaging/providers/slack/backfill.ts +101 -0
  409. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  410. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  411. package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
  412. package/src/messaging/providers/slack/render-transcript.ts +443 -0
  413. package/src/messaging/style-analyzer.ts +5 -2
  414. package/src/notifications/README.md +9 -5
  415. package/src/notifications/decision-engine.ts +3 -9
  416. package/src/notifications/preference-extractor.ts +2 -6
  417. package/src/oauth/oauth-store.ts +1 -0
  418. package/src/oauth/platform-connection.test.ts +47 -0
  419. package/src/oauth/platform-connection.ts +15 -5
  420. package/src/oauth/seed-providers.ts +4 -2
  421. package/src/permissions/approval-policy.test.ts +948 -0
  422. package/src/permissions/approval-policy.ts +257 -0
  423. package/src/permissions/bash-risk-classifier.test.ts +1208 -0
  424. package/src/permissions/bash-risk-classifier.ts +707 -0
  425. package/src/permissions/checker.ts +217 -708
  426. package/src/permissions/command-registry.test.ts +535 -0
  427. package/src/permissions/command-registry.ts +825 -0
  428. package/src/permissions/defaults.ts +26 -78
  429. package/src/permissions/file-risk-classifier.test.ts +535 -0
  430. package/src/permissions/file-risk-classifier.ts +274 -0
  431. package/src/permissions/risk-types.ts +205 -0
  432. package/src/permissions/secret-prompter.ts +53 -2
  433. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  434. package/src/permissions/skill-risk-classifier.ts +214 -0
  435. package/src/permissions/trust-client.ts +52 -25
  436. package/src/permissions/trust-store-interface.ts +1 -6
  437. package/src/permissions/trust-store.ts +161 -62
  438. package/src/permissions/types.ts +23 -14
  439. package/src/permissions/web-risk-classifier.test.ts +170 -0
  440. package/src/permissions/web-risk-classifier.ts +89 -0
  441. package/src/permissions/workspace-policy.ts +1 -16
  442. package/src/platform/client.ts +19 -1
  443. package/src/prompts/persona-resolver.ts +3 -3
  444. package/src/prompts/system-prompt.ts +19 -20
  445. package/src/prompts/templates/SOUL.md +2 -2
  446. package/src/prompts/update-bulletin-job.ts +190 -0
  447. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  448. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  449. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  450. package/src/providers/anthropic/client.ts +183 -14
  451. package/src/providers/call-site-routing.ts +71 -0
  452. package/src/providers/gemini/client.ts +65 -2
  453. package/src/providers/managed-proxy/constants.ts +2 -1
  454. package/src/providers/model-catalog.ts +501 -33
  455. package/src/providers/model-intents.ts +4 -4
  456. package/src/providers/openai/chat-completions-provider.ts +57 -1
  457. package/src/providers/openai/responses-provider.ts +86 -9
  458. package/src/providers/openrouter/client.ts +76 -9
  459. package/src/providers/provider-env-vars.ts +56 -0
  460. package/src/providers/provider-send-message.ts +22 -5
  461. package/src/providers/ratelimit.ts +4 -0
  462. package/src/providers/registry.ts +19 -8
  463. package/src/providers/retry.ts +174 -39
  464. package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
  465. package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
  466. package/src/providers/speech-to-text/provider-catalog.ts +17 -0
  467. package/src/providers/speech-to-text/resolve.ts +7 -0
  468. package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
  469. package/src/providers/speech-to-text/xai-realtime.ts +796 -0
  470. package/src/providers/speech-to-text/xai.test.ts +155 -0
  471. package/src/providers/speech-to-text/xai.ts +97 -0
  472. package/src/providers/types.ts +93 -3
  473. package/src/runtime/AGENTS.md +2 -2
  474. package/src/runtime/__tests__/agent-wake.test.ts +43 -2
  475. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  476. package/src/runtime/agent-wake.ts +63 -22
  477. package/src/runtime/auth/route-policy.ts +4 -0
  478. package/src/runtime/btw-sidechain.ts +13 -3
  479. package/src/runtime/channel-reply-delivery.ts +106 -2
  480. package/src/runtime/decision-token.ts +116 -0
  481. package/src/runtime/gateway-client.ts +2 -2
  482. package/src/runtime/http-router.ts +32 -0
  483. package/src/runtime/http-server.ts +52 -1
  484. package/src/runtime/http-types.ts +23 -1
  485. package/src/runtime/interactive-ui.ts +362 -0
  486. package/src/runtime/invite-instruction-generator.ts +2 -2
  487. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  488. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  489. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  490. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  491. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  492. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  493. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  494. package/src/runtime/migrations/vbundle-importer.ts +154 -9
  495. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  496. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  497. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  498. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  499. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  500. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
  501. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
  502. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  503. package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
  504. package/src/runtime/routes/approval-routes.ts +12 -17
  505. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  506. package/src/runtime/routes/avatar-routes.ts +20 -4
  507. package/src/runtime/routes/btw-routes.ts +1 -4
  508. package/src/runtime/routes/conversation-management-routes.ts +20 -2
  509. package/src/runtime/routes/conversation-routes.ts +133 -27
  510. package/src/runtime/routes/debug-routes.ts +1 -1
  511. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  512. package/src/runtime/routes/events-routes.ts +16 -0
  513. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  514. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  515. package/src/runtime/routes/home-feed-routes.ts +120 -2
  516. package/src/runtime/routes/inbound-message-handler.ts +912 -2
  517. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  518. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  519. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  520. package/src/runtime/routes/integrations/slack/channel.ts +25 -3
  521. package/src/runtime/routes/llm-context-normalization.ts +23 -1
  522. package/src/runtime/routes/migration-routes.ts +720 -124
  523. package/src/runtime/routes/settings-routes.ts +4 -2
  524. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  525. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  526. package/src/runtime/routes/work-items-routes.ts +3 -2
  527. package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
  528. package/src/runtime/services/analyze-conversation.ts +12 -16
  529. package/src/runtime/skill-route-registry.ts +28 -6
  530. package/src/schedule/scheduler.ts +8 -0
  531. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  532. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  533. package/src/security/oauth2.ts +98 -35
  534. package/src/security/secure-keys.ts +7 -8
  535. package/src/security/token-manager.ts +27 -13
  536. package/src/security/untrusted-content.ts +102 -0
  537. package/src/skills/catalog-cache.ts +26 -7
  538. package/src/skills/catalog-install.ts +31 -3
  539. package/src/skills/skill-cache-store.ts +97 -0
  540. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
  541. package/src/stt/daemon-batch-transcriber.ts +33 -0
  542. package/src/stt/stt-stream-session.ts +8 -1
  543. package/src/stt/types.ts +5 -1
  544. package/src/subagent/manager.ts +41 -13
  545. package/src/tasks/ephemeral-permissions.ts +9 -4
  546. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  547. package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
  548. package/src/tools/browser/browser-execution.ts +65 -38
  549. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
  550. package/src/tools/credentials/tool-policy.ts +39 -5
  551. package/src/tools/credentials/vault.ts +9 -4
  552. package/src/tools/executor.ts +4 -0
  553. package/src/tools/filesystem/write.ts +52 -0
  554. package/src/tools/host-terminal/host-shell.ts +45 -5
  555. package/src/tools/memory/register.test.ts +185 -0
  556. package/src/tools/memory/register.ts +3 -1
  557. package/src/tools/network/web-fetch.ts +20 -10
  558. package/src/tools/network/web-search.ts +19 -4
  559. package/src/tools/permission-checker.ts +36 -15
  560. package/src/tools/policy-context.ts +25 -8
  561. package/src/tools/registry.ts +55 -3
  562. package/src/tools/side-effects.ts +0 -11
  563. package/src/tools/skills/execute.ts +2 -2
  564. package/src/tools/skills/sandbox-runner.ts +5 -2
  565. package/src/tools/terminal/backends/native.ts +51 -2
  566. package/src/tools/terminal/safe-env.ts +3 -2
  567. package/src/tools/terminal/shell.ts +1 -0
  568. package/src/tools/tool-manifest.ts +6 -21
  569. package/src/tools/types.ts +12 -3
  570. package/src/tools/verification-control-plane-policy.ts +1 -1
  571. package/src/tts/__tests__/provider-adapters.test.ts +240 -13
  572. package/src/tts/provider-catalog.ts +18 -0
  573. package/src/tts/providers/index.ts +2 -0
  574. package/src/tts/providers/xai-provider.ts +224 -0
  575. package/src/tts/types.ts +46 -0
  576. package/src/types/tar-stream.d.ts +66 -0
  577. package/src/util/json.ts +17 -0
  578. package/src/util/platform.ts +2 -2
  579. package/src/util/pricing.ts +15 -5
  580. package/src/watcher/engine.ts +1 -1
  581. package/src/watcher/providers/google-calendar.ts +134 -8
  582. package/src/watcher/providers/outlook-calendar.ts +42 -2
  583. package/src/workspace/git-service.ts +23 -4
  584. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  585. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  586. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  587. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
  588. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  589. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  590. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  591. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  592. package/src/workspace/migrations/AGENTS.md +1 -1
  593. package/src/workspace/migrations/registry.ts +16 -0
  594. package/src/workspace/provider-commit-message-generator.ts +19 -38
  595. package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
  596. package/src/__tests__/gmail-archive-gate.test.ts +0 -246
  597. package/src/__tests__/gmail-preferences.test.ts +0 -117
  598. package/src/__tests__/outlook-attachments.test.ts +0 -301
  599. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  600. package/src/__tests__/outlook-categories.test.ts +0 -212
  601. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  602. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  603. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  604. package/src/__tests__/outlook-trash.test.ts +0 -77
  605. package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
  606. package/src/__tests__/update-bulletin-format.test.ts +0 -181
  607. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  608. package/src/__tests__/update-bulletin.test.ts +0 -478
  609. package/src/__tests__/update-template-contract.test.ts +0 -29
  610. package/src/cli/commands/doctor.ts +0 -341
  611. package/src/config/bundled-skills/browser/SKILL.md +0 -88
  612. package/src/config/bundled-skills/browser/TOOLS.json +0 -516
  613. package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
  614. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  615. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  616. package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
  617. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  618. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  619. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  620. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  621. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  622. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  623. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  624. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  625. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  626. package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
  627. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  628. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
  629. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  630. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  631. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  632. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  633. package/src/config/bundled-skills/gmail/SKILL.md +0 -221
  634. package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
  635. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
  636. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  637. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  638. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  639. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  640. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  641. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  642. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
  643. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
  644. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
  645. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  646. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
  647. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  648. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  649. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  650. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  651. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  652. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  653. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  654. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  655. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  656. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  657. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  658. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  659. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  660. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  661. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  662. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  663. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  664. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  665. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  666. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  667. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  668. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  669. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  670. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  671. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  672. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  673. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  674. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  675. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  676. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  677. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  678. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  679. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  680. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  681. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  682. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  683. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  684. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  685. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  686. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  687. package/src/config/bundled-skills/slack/SKILL.md +0 -108
  688. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  689. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  690. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  691. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  692. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  693. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  694. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  695. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  696. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  697. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  698. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  699. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  700. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  701. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  702. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  703. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  704. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  705. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  706. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  707. package/src/prompts/templates/UPDATES.md +0 -50
  708. package/src/prompts/update-bulletin-format.ts +0 -85
  709. package/src/prompts/update-bulletin-state.ts +0 -58
  710. package/src/prompts/update-bulletin-template-path.ts +0 -13
  711. package/src/prompts/update-bulletin.ts +0 -139
  712. package/src/shared/provider-env-vars.ts +0 -19
  713. package/src/tools/watcher/create.ts +0 -86
  714. package/src/tools/watcher/delete.ts +0 -36
  715. package/src/tools/watcher/digest.ts +0 -54
  716. package/src/tools/watcher/list.ts +0 -83
  717. package/src/tools/watcher/update.ts +0 -71
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Edit-propagation tests for Slack `message.changed` events.
3
+ *
4
+ * Validates that the edit intercept stage:
5
+ * - Updates `messages.content` and stamps `slackMeta.editedAt` when the
6
+ * original message can be located.
7
+ * - Is idempotent across successive edits (subsequent edits keep updating).
8
+ * - Treats missing-target edits as a silent no-op (no throw, no row change).
9
+ */
10
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
11
+
12
+ import { eq } from "drizzle-orm";
13
+
14
+ mock.module("../util/logger.js", () => ({
15
+ getLogger: () =>
16
+ new Proxy({} as Record<string, unknown>, {
17
+ get: () => () => {},
18
+ }),
19
+ }));
20
+
21
+ import { addMessage } from "../memory/conversation-crud.js";
22
+ import { getDb, initializeDb } from "../memory/db.js";
23
+ import { linkMessage, recordInbound } from "../memory/delivery-crud.js";
24
+ import { messages } from "../memory/schema.js";
25
+ import { readSlackMetadata } from "../messaging/providers/slack/message-metadata.js";
26
+ import { handleEditIntercept } from "../runtime/routes/inbound-stages/edit-intercept.js";
27
+
28
+ initializeDb();
29
+
30
+ function resetTables(): void {
31
+ const db = getDb();
32
+ db.run("DELETE FROM channel_inbound_events");
33
+ db.run("DELETE FROM messages");
34
+ db.run("DELETE FROM conversation_keys");
35
+ db.run("DELETE FROM conversations");
36
+ }
37
+
38
+ interface SeededFixture {
39
+ conversationId: string;
40
+ messageId: string;
41
+ channelTs: string;
42
+ conversationExternalId: string;
43
+ }
44
+
45
+ /**
46
+ * Seed a Slack message in a fresh conversation. Mirrors what the new-message
47
+ * pipeline does at runtime: `recordInbound` writes the channel_inbound_events
48
+ * row (storing `sourceMessageId = ts`), `addMessage` writes the user message,
49
+ * and `linkMessage` connects them so edit lookups succeed.
50
+ *
51
+ * Note: the gateway sets `externalMessageId = client_msg_id ?? ts` for new
52
+ * Slack messages, so this fixture mirrors a message where `client_msg_id`
53
+ * equals the `ts` (i.e. the simplest case). The lookup mechanism keys on
54
+ * `sourceMessageId`, which always carries the `ts`, so the test exercises
55
+ * the same path that production hits regardless of `client_msg_id` presence.
56
+ */
57
+ async function seedSlackMessage(opts: {
58
+ conversationExternalId: string;
59
+ channelTs: string;
60
+ initialContent: string;
61
+ }): Promise<SeededFixture> {
62
+ const { conversationExternalId, channelTs, initialContent } = opts;
63
+
64
+ const inboundResult = recordInbound(
65
+ "slack",
66
+ conversationExternalId,
67
+ channelTs,
68
+ {
69
+ sourceMessageId: channelTs,
70
+ },
71
+ );
72
+
73
+ const inserted = await addMessage(
74
+ inboundResult.conversationId,
75
+ "user",
76
+ initialContent,
77
+ { userMessageChannel: "slack" },
78
+ { skipIndexing: true },
79
+ );
80
+
81
+ linkMessage(inboundResult.eventId, inserted.id);
82
+
83
+ return {
84
+ conversationId: inboundResult.conversationId,
85
+ messageId: inserted.id,
86
+ channelTs,
87
+ conversationExternalId,
88
+ };
89
+ }
90
+
91
+ function readMessageRow(messageId: string): {
92
+ content: string;
93
+ metadata: string | null;
94
+ } {
95
+ const db = getDb();
96
+ const row = db
97
+ .select({ content: messages.content, metadata: messages.metadata })
98
+ .from(messages)
99
+ .where(eq(messages.id, messageId))
100
+ .get();
101
+ if (!row) {
102
+ throw new Error(`message ${messageId} not found`);
103
+ }
104
+ return { content: row.content, metadata: row.metadata };
105
+ }
106
+
107
+ let editEventCounter = 0;
108
+ function nextEditEventId(): string {
109
+ editEventCounter += 1;
110
+ return `edit-event-${Date.now()}-${editEventCounter}`;
111
+ }
112
+
113
+ describe("Slack edit propagation", () => {
114
+ beforeEach(() => {
115
+ resetTables();
116
+ editEventCounter = 0;
117
+ });
118
+
119
+ test("updates content and stamps slackMeta.editedAt when original is found", async () => {
120
+ const seeded = await seedSlackMessage({
121
+ conversationExternalId: "C0123CHANNEL",
122
+ channelTs: "1234.5678",
123
+ initialContent: "original text",
124
+ });
125
+
126
+ const before = readMessageRow(seeded.messageId);
127
+ expect(before.content).toBe("original text");
128
+
129
+ const t0 = Date.now();
130
+ const resp = await handleEditIntercept({
131
+ sourceChannel: "slack",
132
+ conversationExternalId: seeded.conversationExternalId,
133
+ externalMessageId: nextEditEventId(),
134
+ sourceMessageId: seeded.channelTs,
135
+ canonicalAssistantId: "self",
136
+ assistantId: "self",
137
+ content: "new text",
138
+ });
139
+
140
+ expect(resp.status).toBe(200);
141
+ const respJson = (await resp.json()) as Record<string, unknown>;
142
+ expect(respJson.accepted).toBe(true);
143
+ expect(respJson.duplicate).toBe(false);
144
+
145
+ const after = readMessageRow(seeded.messageId);
146
+ expect(after.content).toBe("new text");
147
+
148
+ expect(after.metadata).not.toBeNull();
149
+ const outer = JSON.parse(after.metadata!);
150
+ expect(outer.userMessageChannel).toBe("slack");
151
+ expect(typeof outer.slackMeta).toBe("string");
152
+
153
+ const slackMeta = readSlackMetadata(outer.slackMeta);
154
+ expect(slackMeta).not.toBeNull();
155
+ expect(slackMeta!.source).toBe("slack");
156
+ expect(slackMeta!.channelId).toBe(seeded.conversationExternalId);
157
+ expect(slackMeta!.channelTs).toBe(seeded.channelTs);
158
+ expect(slackMeta!.eventKind).toBe("message");
159
+ expect(typeof slackMeta!.editedAt).toBe("number");
160
+ expect(slackMeta!.editedAt!).toBeGreaterThanOrEqual(t0);
161
+ });
162
+
163
+ test("is idempotent across successive edits", async () => {
164
+ const seeded = await seedSlackMessage({
165
+ conversationExternalId: "C0123CHANNEL",
166
+ channelTs: "1234.5678",
167
+ initialContent: "original text",
168
+ });
169
+
170
+ await handleEditIntercept({
171
+ sourceChannel: "slack",
172
+ conversationExternalId: seeded.conversationExternalId,
173
+ externalMessageId: nextEditEventId(),
174
+ sourceMessageId: seeded.channelTs,
175
+ canonicalAssistantId: "self",
176
+ assistantId: "self",
177
+ content: "first edit",
178
+ });
179
+
180
+ const afterFirst = readMessageRow(seeded.messageId);
181
+ expect(afterFirst.content).toBe("first edit");
182
+ const firstSlackMeta = readSlackMetadata(
183
+ (JSON.parse(afterFirst.metadata!) as Record<string, unknown>)
184
+ .slackMeta as string | null,
185
+ );
186
+ expect(firstSlackMeta).not.toBeNull();
187
+ const firstEditedAt = firstSlackMeta!.editedAt!;
188
+
189
+ // Ensure the second edit's timestamp is observably after the first so the
190
+ // assertion below proves the field was re-stamped, not stale.
191
+ await Bun.sleep(2);
192
+
193
+ await handleEditIntercept({
194
+ sourceChannel: "slack",
195
+ conversationExternalId: seeded.conversationExternalId,
196
+ externalMessageId: nextEditEventId(),
197
+ sourceMessageId: seeded.channelTs,
198
+ canonicalAssistantId: "self",
199
+ assistantId: "self",
200
+ content: "second edit",
201
+ });
202
+
203
+ const afterSecond = readMessageRow(seeded.messageId);
204
+ expect(afterSecond.content).toBe("second edit");
205
+ const secondSlackMeta = readSlackMetadata(
206
+ (JSON.parse(afterSecond.metadata!) as Record<string, unknown>)
207
+ .slackMeta as string | null,
208
+ );
209
+ expect(secondSlackMeta).not.toBeNull();
210
+ expect(secondSlackMeta!.editedAt!).toBeGreaterThan(firstEditedAt);
211
+ // Other fields stay stable across edits.
212
+ expect(secondSlackMeta!.channelId).toBe(seeded.conversationExternalId);
213
+ expect(secondSlackMeta!.channelTs).toBe(seeded.channelTs);
214
+ expect(secondSlackMeta!.eventKind).toBe("message");
215
+ });
216
+
217
+ test("no-op edit (identical text, e.g. unfurl) skips DB write", async () => {
218
+ const seeded = await seedSlackMessage({
219
+ conversationExternalId: "C0123CHANNEL",
220
+ channelTs: "1234.5678",
221
+ initialContent: "original text",
222
+ });
223
+
224
+ const before = readMessageRow(seeded.messageId);
225
+
226
+ const resp = await handleEditIntercept({
227
+ sourceChannel: "slack",
228
+ conversationExternalId: seeded.conversationExternalId,
229
+ externalMessageId: nextEditEventId(),
230
+ sourceMessageId: seeded.channelTs,
231
+ canonicalAssistantId: "self",
232
+ assistantId: "self",
233
+ // Same text as stored -- simulates a Slack unfurl `message_changed`
234
+ // where only attachments changed.
235
+ content: "original text",
236
+ });
237
+
238
+ expect(resp.status).toBe(200);
239
+ const respJson = (await resp.json()) as Record<string, unknown>;
240
+ expect(respJson.accepted).toBe(true);
241
+ expect(respJson.duplicate).toBe(false);
242
+ expect(respJson.noop).toBe(true);
243
+
244
+ const after = readMessageRow(seeded.messageId);
245
+ expect(after.content).toBe(before.content);
246
+ // No metadata mutation either -- the write is fully skipped.
247
+ expect(after.metadata).toBe(before.metadata);
248
+ });
249
+
250
+ // The lookup retries 5 times with 2s backoff (~10s total) before giving up,
251
+ // so this test legitimately needs to outrun the default 5s per-test timeout.
252
+ test("missing-target edit is a no-op (no throw, no row changed)", async () => {
253
+ const seeded = await seedSlackMessage({
254
+ conversationExternalId: "C0123CHANNEL",
255
+ channelTs: "1234.5678",
256
+ initialContent: "original text",
257
+ });
258
+ const beforeUnknown = readMessageRow(seeded.messageId);
259
+
260
+ const resp = await handleEditIntercept({
261
+ sourceChannel: "slack",
262
+ conversationExternalId: seeded.conversationExternalId,
263
+ // sourceMessageId points at a ts that was never stored.
264
+ externalMessageId: nextEditEventId(),
265
+ sourceMessageId: "9999.0000",
266
+ canonicalAssistantId: "self",
267
+ assistantId: "self",
268
+ content: "new text",
269
+ });
270
+
271
+ expect(resp.status).toBe(200);
272
+ const respJson = (await resp.json()) as Record<string, unknown>;
273
+ expect(respJson.accepted).toBe(true);
274
+ expect(respJson.duplicate).toBe(false);
275
+
276
+ const afterUnknown = readMessageRow(seeded.messageId);
277
+ expect(afterUnknown.content).toBe(beforeUnknown.content);
278
+ expect(afterUnknown.metadata).toBe(beforeUnknown.metadata);
279
+ }, 30_000);
280
+ });
@@ -83,7 +83,7 @@ describe("ephemeral-permissions", () => {
83
83
  expect(fileReadRule.createdAt).toBeGreaterThan(0);
84
84
 
85
85
  // Ephemeral task rules should not auto-allow high-risk tools
86
- expect(fileReadRule.allowHighRisk).toBeUndefined();
86
+ // allowHighRisk is no longer a field on rules
87
87
 
88
88
  // Check other rules have correct tool names
89
89
  expect(rules[1].tool).toBe("bash");
@@ -276,7 +276,7 @@ describe("ephemeral-permissions", () => {
276
276
  expect(result.decision).toBe("allow");
277
277
  });
278
278
 
279
- test("high-risk tool still prompts even with ephemeral allow rule (no allowHighRisk)", async () => {
279
+ test("high-risk tool still prompts even with ephemeral allow rule", async () => {
280
280
  const ephemeralRules: TrustRule[] = [
281
281
  {
282
282
  id: "ephemeral:run-1:bash",
@@ -286,7 +286,7 @@ describe("ephemeral-permissions", () => {
286
286
  decision: "allow",
287
287
  priority: 50,
288
288
  createdAt: Date.now(),
289
- // Note: allowHighRisk is NOT set
289
+ // allowHighRisk is no longer a field
290
290
  },
291
291
  ];
292
292
 
@@ -306,6 +306,96 @@ describe("ephemeral-permissions", () => {
306
306
  });
307
307
  });
308
308
 
309
+ // ── Canonical shape and scope fallback semantics ──────────────────
310
+ //
311
+ // Validates that ephemeral rules follow canonical persisted shapes
312
+ // and that optional scope behaves correctly as a fallback.
313
+
314
+ describe("canonical shape and scope fallback semantics", () => {
315
+ beforeEach(() => {
316
+ clearCache();
317
+ });
318
+
319
+ test("ephemeral rule with scope 'everywhere' matches from any working directory", () => {
320
+ // Use a tool (host_file_read) that has no default allow rule and a
321
+ // high priority so the ephemeral rule wins over the default ask rule.
322
+ const ephemeralRules: TrustRule[] = [
323
+ {
324
+ id: "ephemeral:run-scope:host_file_read",
325
+ tool: "host_file_read",
326
+ pattern: "**",
327
+ scope: "everywhere",
328
+ decision: "allow",
329
+ priority: 2000,
330
+ createdAt: Date.now(),
331
+ },
332
+ ];
333
+
334
+ const ctx: PolicyContext = { ephemeralRules };
335
+
336
+ // Should match from /tmp
337
+ const r1 = findHighestPriorityRule(
338
+ "host_file_read",
339
+ ["host_file_read:/tmp/foo.txt"],
340
+ "/tmp",
341
+ ctx,
342
+ );
343
+ expect(r1).not.toBeNull();
344
+ expect(r1!.id).toBe("ephemeral:run-scope:host_file_read");
345
+
346
+ // Should match from /home/user/project
347
+ const r2 = findHighestPriorityRule(
348
+ "host_file_read",
349
+ ["host_file_read:/home/user/project/bar.ts"],
350
+ "/home/user/project",
351
+ ctx,
352
+ );
353
+ expect(r2).not.toBeNull();
354
+ expect(r2!.id).toBe("ephemeral:run-scope:host_file_read");
355
+ });
356
+
357
+ test("ephemeral rule does not carry stray metadata fields for non-scoped tools", () => {
358
+ // buildTaskRules generates canonical ephemeral rules.
359
+ // For a tool like file_read (scoped family), the generated rule
360
+ // should have no executionTarget.
361
+ const rules = buildTaskRules("run-canon", ["file_read", "bash"], "/tmp");
362
+
363
+ for (const rule of rules) {
364
+ expect(rule.scope).toBe("everywhere");
365
+ expect(rule.executionTarget).toBeUndefined();
366
+ }
367
+ });
368
+
369
+ test("ephemeral rule with project scope does not match sibling project", () => {
370
+ const ephemeralRules: TrustRule[] = [
371
+ {
372
+ id: "ephemeral:run-proj:file_write",
373
+ tool: "file_write",
374
+ pattern: "**",
375
+ scope: "/home/user/project-a",
376
+ decision: "allow",
377
+ priority: 75,
378
+ createdAt: Date.now(),
379
+ },
380
+ ];
381
+
382
+ const ctx: PolicyContext = { ephemeralRules };
383
+
384
+ // Should NOT match from a sibling directory
385
+ const result = findHighestPriorityRule(
386
+ "file_write",
387
+ ["file_write:/home/user/project-b/file.ts"],
388
+ "/home/user/project-b",
389
+ ctx,
390
+ );
391
+
392
+ // The ephemeral rule should not be the match (scope mismatch)
393
+ if (result) {
394
+ expect(result.id).not.toBe("ephemeral:run-proj:file_write");
395
+ }
396
+ });
397
+ });
398
+
309
399
  describe("workspace mode interactions", () => {
310
400
  beforeEach(() => {
311
401
  clearCache();
@@ -0,0 +1,208 @@
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ getCalibrationSnapshot,
5
+ getCorrection,
6
+ recordEstimate,
7
+ resetCalibrations,
8
+ } from "../context/estimator-calibration.js";
9
+ import {
10
+ estimatePromptTokens,
11
+ getCalibrationProviderKey,
12
+ } from "../context/token-estimator.js";
13
+ import type { Message, Provider } from "../providers/types.js";
14
+
15
+ /**
16
+ * Integration-style tests that exercise the full self-calibration loop end
17
+ * to end:
18
+ * 1. Estimate is recorded for a `(provider, model)` pair via
19
+ * `handleUsage` (the record side still threads the provider-echoed
20
+ * model through `recordEstimate`).
21
+ * 2. A subsequent `estimatePromptTokens` lookup picks up the learned
22
+ * correction via the per-provider aggregate key `(provider, "")`.
23
+ * Lookup always uses the aggregate — model-specific keys are only
24
+ * read as a fallback inside `getCorrection`.
25
+ *
26
+ * Since the `modelId` lookup option has been removed from the public
27
+ * token-estimator API, the lookup side always converges to the aggregate.
28
+ * `recordEstimate` still updates both the specific `(provider, model)`
29
+ * key AND the `(provider, "")` aggregate on every sample, so the
30
+ * aggregate stays accurate even as per-model data accumulates.
31
+ */
32
+ describe("estimator calibration — end-to-end recording → lookup", () => {
33
+ beforeEach(() => {
34
+ resetCalibrations();
35
+ });
36
+
37
+ /**
38
+ * Build a representative message history with enough content to clear the
39
+ * MIN_SAMPLE_MAGNITUDE floor (500 tokens). Each message repeats a block of
40
+ * text large enough to make the heuristic estimator produce a substantial
41
+ * token count so the calibration machinery actually runs.
42
+ */
43
+ function largeHistory(): Message[] {
44
+ const body = "lorem ipsum dolor sit amet ".repeat(500);
45
+ return [
46
+ { role: "user", content: [{ type: "text", text: body }] },
47
+ { role: "assistant", content: [{ type: "text", text: body }] },
48
+ { role: "user", content: [{ type: "text", text: body }] },
49
+ ];
50
+ }
51
+
52
+ test("subsequent estimate picks up the aggregate-key correction", () => {
53
+ const provider: Provider = {
54
+ name: "anthropic",
55
+ async sendMessage() {
56
+ throw new Error("not used in this test");
57
+ },
58
+ };
59
+ const model = "claude-sonnet-4-5";
60
+ const history = largeHistory();
61
+
62
+ // 1. Raw estimate (what agent/loop.ts computes pre-send).
63
+ const preSend = estimatePromptTokens(history, "system", {
64
+ providerName: getCalibrationProviderKey(provider),
65
+ });
66
+ expect(preSend).toBeGreaterThan(0);
67
+
68
+ // Baseline: no correction recorded yet.
69
+ expect(getCorrection("anthropic", "")).toBe(1.0);
70
+
71
+ // 2. Provider returns ground truth (simulating `handleUsage`, which
72
+ // still records under (provider, event.model) and folds into the
73
+ // aggregate). Simulate a systematic 30% underestimate.
74
+ const groundTruth = Math.ceil(preSend * 1.3);
75
+ recordEstimate(
76
+ getCalibrationProviderKey(provider),
77
+ model,
78
+ preSend,
79
+ groundTruth,
80
+ );
81
+
82
+ // 3. Lookup under the aggregate key now returns the learned ratio.
83
+ expect(getCorrection("anthropic", "")).toBeCloseTo(1.3, 3);
84
+
85
+ // And the corrected estimate moves toward the ground truth.
86
+ const corrected = estimatePromptTokens(history, "system", {
87
+ providerName: getCalibrationProviderKey(provider),
88
+ });
89
+ // With correction factor ≈1.3, corrected estimate is within 1 token of
90
+ // the ground truth (Math.ceil rounding).
91
+ expect(corrected).toBeGreaterThan(preSend);
92
+ expect(Math.abs(corrected - groundTruth)).toBeLessThanOrEqual(1);
93
+ });
94
+
95
+ test("record with model writes both the specific and aggregate keys", () => {
96
+ // Simulate a preflight site that records against (anthropic, sonnet).
97
+ // `recordEstimate` also folds the sample into the `(anthropic, "")`
98
+ // aggregate so aggregate-key callers see the correction.
99
+ const provider: Provider = {
100
+ name: "anthropic",
101
+ async sendMessage() {
102
+ throw new Error("not used");
103
+ },
104
+ };
105
+ const history = largeHistory();
106
+
107
+ const preSend = estimatePromptTokens(history, "system", {
108
+ providerName: getCalibrationProviderKey(provider),
109
+ });
110
+ const groundTruth = Math.ceil(preSend * 1.25);
111
+
112
+ recordEstimate(
113
+ getCalibrationProviderKey(provider),
114
+ "claude-sonnet-4-5",
115
+ preSend,
116
+ groundTruth,
117
+ );
118
+
119
+ // A subsequent lookup via the token-estimator uses the per-provider
120
+ // aggregate (the only key the public API reads).
121
+ const correctedAggregate = estimatePromptTokens(history, "system", {
122
+ providerName: getCalibrationProviderKey(provider),
123
+ });
124
+ // Aggregate ratio ≈ 1.25 (first sample snaps to exact ratio).
125
+ expect(correctedAggregate).toBe(Math.ceil(preSend * 1.25));
126
+ });
127
+
128
+ test("wrapper provider (OpenRouter → Anthropic) uses the canonical key on both sides", () => {
129
+ // This is the Devin scenario: OpenRouter wraps Anthropic. If the record
130
+ // site used `name` ("openrouter") and the lookup site used
131
+ // `tokenEstimationProvider` ("anthropic"), the data would be scattered
132
+ // across mismatched keys and calibration would silently fail.
133
+ // `getCalibrationProviderKey` gives us one source of truth.
134
+ const openrouter: Provider = {
135
+ name: "openrouter",
136
+ tokenEstimationProvider: "anthropic",
137
+ async sendMessage() {
138
+ throw new Error("not used");
139
+ },
140
+ };
141
+ const model = "anthropic/claude-sonnet-4-5";
142
+ const history = largeHistory();
143
+
144
+ // Pre-send estimate via the canonical key.
145
+ const preSend = estimatePromptTokens(history, "system", {
146
+ providerName: getCalibrationProviderKey(openrouter),
147
+ });
148
+ expect(preSend).toBeGreaterThan(0);
149
+
150
+ // Provider returns ground truth. `handleUsage` uses the same helper
151
+ // to pick the calibration key, so the record and lookup sides agree.
152
+ const groundTruth = Math.ceil(preSend * 1.2);
153
+ recordEstimate(
154
+ getCalibrationProviderKey(openrouter),
155
+ model,
156
+ preSend,
157
+ groundTruth,
158
+ );
159
+
160
+ // Lookup under "anthropic" — the canonical upstream key — returns the
161
+ // ratio. See note above about precision=3.
162
+ expect(getCorrection("anthropic", model)).toBeCloseTo(1.2, 3);
163
+ // Aggregate under the canonical upstream key is also populated.
164
+ expect(getCorrection("anthropic", "")).toBeCloseTo(1.2, 3);
165
+ // And under the bare wrapper name stays at the default, because NOTHING
166
+ // was recorded under "openrouter".
167
+ expect(getCorrection("openrouter", "")).toBe(1.0);
168
+
169
+ // The snapshot reflects a single (provider, model) key + aggregate under
170
+ // the canonical upstream key — never under the wrapper name.
171
+ const keys = getCalibrationSnapshot().map(
172
+ (e) => `${e.provider}::${e.model}`,
173
+ );
174
+ expect(keys).toContain(`anthropic::${model}`);
175
+ expect(keys).toContain("anthropic::");
176
+ expect(keys).not.toContain(`openrouter::${model}`);
177
+ });
178
+
179
+ test("a run of consistent samples pulls the estimate toward ground truth", () => {
180
+ // The EWMA should converge quickly. After five consistent 1.3 samples
181
+ // the correction should be within 1% of 1.3, and the corrected estimate
182
+ // should be within 1% of the ground truth.
183
+ const model = "claude-sonnet-4-5";
184
+ const history = largeHistory();
185
+
186
+ const preSend = estimatePromptTokens(history, "system", {
187
+ providerName: "anthropic",
188
+ });
189
+ const groundTruth = Math.ceil(preSend * 1.3);
190
+
191
+ for (let i = 0; i < 5; i++) {
192
+ recordEstimate("anthropic", model, preSend, groundTruth);
193
+ }
194
+
195
+ const finalCorrection = getCorrection("anthropic", "");
196
+ // EWMA with alpha=0.2 on constant 1.3 stays at 1.3 from the first sample
197
+ // onward (all deltas are 0 after the initial snap). `precision=3` gives
198
+ // us ~0.0005 tolerance which covers the Math.ceil rounding noise.
199
+ expect(finalCorrection).toBeCloseTo(1.3, 3);
200
+
201
+ const corrected = estimatePromptTokens(history, "system", {
202
+ providerName: "anthropic",
203
+ });
204
+ // Corrected should be very close to the ground truth (within 1 token
205
+ // because of the Math.ceil rounding at the end of estimatePromptTokens).
206
+ expect(Math.abs(corrected - groundTruth)).toBeLessThanOrEqual(1);
207
+ });
208
+ });