@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,805 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import type {
4
+ Message,
5
+ ToolResultContent,
6
+ ToolUseContent,
7
+ } from "../../providers/types.js";
8
+ import { microcompact } from "../microcompact.js";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+
14
+ function userText(text: string): Message {
15
+ return { role: "user", content: [{ type: "text", text }] };
16
+ }
17
+
18
+ function assistantToolUse(
19
+ id: string,
20
+ name: string,
21
+ input: Record<string, unknown> = {},
22
+ ): Message {
23
+ const block: ToolUseContent = { type: "tool_use", id, name, input };
24
+ return { role: "assistant", content: [block] };
25
+ }
26
+
27
+ function toolResult(
28
+ tool_use_id: string,
29
+ content: string,
30
+ extras: Partial<
31
+ Omit<ToolResultContent, "type" | "tool_use_id" | "content">
32
+ > = {},
33
+ ): Message {
34
+ const block: ToolResultContent = {
35
+ type: "tool_result",
36
+ tool_use_id,
37
+ content,
38
+ ...extras,
39
+ };
40
+ return { role: "user", content: [block] };
41
+ }
42
+
43
+ /**
44
+ * Build a realistic multi-turn conversation where every "exchange" is a
45
+ * user text message followed by a tool_use + tool_result pair.
46
+ *
47
+ * turns[i].tool = the tool name used on turn i.
48
+ * turns[i].resultBody = the text of the tool_result for that turn.
49
+ */
50
+ function buildConversation(
51
+ turns: Array<{ tool: string; resultBody: string; userText?: string }>,
52
+ ): Message[] {
53
+ const out: Message[] = [];
54
+ for (let i = 0; i < turns.length; i++) {
55
+ const t = turns[i];
56
+ const id = `tu-${i}`;
57
+ out.push(userText(t.userText ?? `user says ${i}`));
58
+ out.push(assistantToolUse(id, t.tool));
59
+ out.push(toolResult(id, t.resultBody));
60
+ }
61
+ return out;
62
+ }
63
+
64
+ /** Large payload sized to dominate the token budget. */
65
+ function big(label: string, size = 10_000): string {
66
+ return `${label}:${"x".repeat(size)}`;
67
+ }
68
+
69
+ /** Count tool_result blocks whose content equals the cleared placeholder. */
70
+ function countClearedToolResults(messages: Message[]): number {
71
+ let n = 0;
72
+ for (const m of messages) {
73
+ for (const b of m.content) {
74
+ if (
75
+ b.type === "tool_result" &&
76
+ b.content === "[Old tool result content cleared]"
77
+ ) {
78
+ n += 1;
79
+ }
80
+ }
81
+ }
82
+ return n;
83
+ }
84
+
85
+ function findToolResults(messages: Message[]): ToolResultContent[] {
86
+ const out: ToolResultContent[] = [];
87
+ for (const m of messages) {
88
+ for (const b of m.content) {
89
+ if (b.type === "tool_result") out.push(b);
90
+ }
91
+ }
92
+ return out;
93
+ }
94
+
95
+ function findToolUses(messages: Message[]): ToolUseContent[] {
96
+ const out: ToolUseContent[] = [];
97
+ for (const m of messages) {
98
+ for (const b of m.content) {
99
+ if (b.type === "tool_use") out.push(b);
100
+ }
101
+ }
102
+ return out;
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // (a) last-N-turns-protected invariant
107
+ // ---------------------------------------------------------------------------
108
+
109
+ describe("microcompact — protectRecentTurns invariant", () => {
110
+ test("leaves the last N user turns untouched", () => {
111
+ const turns = Array.from({ length: 8 }, () => ({
112
+ tool: "Bash",
113
+ resultBody: big("stale"),
114
+ }));
115
+ const messages = buildConversation(turns);
116
+
117
+ // Cleared count *in the stripped region* should be exactly 8 - 4 = 4
118
+ // when protectRecentTurns = 4 (default).
119
+ const result = microcompact(messages);
120
+
121
+ expect(result.clearedToolResults).toBe(4);
122
+ expect(result.reclaimedTokens).toBeGreaterThan(0);
123
+
124
+ // The last 4 user turns occupy indices 12..23 of a 24-message array
125
+ // (3 messages per turn × 8 turns). Every tool_result at index >= 12
126
+ // must still contain the original body.
127
+ const trs = findToolResults(result.messages);
128
+ // tool_results are at message indices 2,5,8,...23 — the 4 newest are
129
+ // at positions 4..7 in this list (i.e. turns 4..7).
130
+ for (let i = 0; i < 4; i++) {
131
+ expect(trs[i].content).toBe("[Old tool result content cleared]");
132
+ }
133
+ for (let i = 4; i < 8; i++) {
134
+ expect(trs[i].content.startsWith("stale:")).toBe(true);
135
+ }
136
+ });
137
+
138
+ test("when history has fewer turns than protectRecentTurns, nothing is cleared", () => {
139
+ const messages = buildConversation([
140
+ { tool: "Bash", resultBody: big("stale") },
141
+ { tool: "Bash", resultBody: big("stale") },
142
+ ]);
143
+ const result = microcompact(messages, { protectRecentTurns: 4 });
144
+ expect(result.clearedToolResults).toBe(0);
145
+ expect(result.reclaimedTokens).toBe(0);
146
+ expect(result.messages).toBe(messages);
147
+ });
148
+
149
+ test("tool_result-only user messages do not count as a user turn", () => {
150
+ // A single exchange where the user sends text, then the model makes 3
151
+ // separate tool calls. Providers emit one user message per tool_result,
152
+ // all of which are tool_result-only. Those should not each count as a
153
+ // user turn — otherwise protectRecentTurns=1 would protect everything.
154
+ const msgs: Message[] = [
155
+ userText("first real user turn"),
156
+ assistantToolUse("tu-1", "Bash"),
157
+ toolResult("tu-1", big("body-1")),
158
+ assistantToolUse("tu-2", "Bash"),
159
+ toolResult("tu-2", big("body-2")),
160
+ assistantToolUse("tu-3", "Bash"),
161
+ toolResult("tu-3", big("body-3")),
162
+ userText("second real user turn"),
163
+ assistantToolUse("tu-4", "Bash"),
164
+ toolResult("tu-4", big("body-4")),
165
+ ];
166
+ // protectRecentTurns=1 should protect ONLY the "second real user turn"
167
+ // and its tool_result (tu-4). tu-1..tu-3 should be cleared.
168
+ const result = microcompact(msgs, { protectRecentTurns: 1 });
169
+ expect(result.clearedToolResults).toBe(3);
170
+
171
+ const trs = findToolResults(result.messages);
172
+ expect(trs[0].content).toBe("[Old tool result content cleared]");
173
+ expect(trs[1].content).toBe("[Old tool result content cleared]");
174
+ expect(trs[2].content).toBe("[Old tool result content cleared]");
175
+ expect(trs[3].content.startsWith("body-4:")).toBe(true);
176
+ });
177
+ });
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // (b) protected tools never cleared
181
+ // ---------------------------------------------------------------------------
182
+
183
+ describe("microcompact — protected tools", () => {
184
+ test("never replaces the body of a tool_result from a protected tool", () => {
185
+ // 6 turns: 1 Task, 1 subagent, 1 skill, 3 Bash — plus 4 protected turns
186
+ // at the tail to push the first 6 into the stripped region.
187
+ const turns = [
188
+ { tool: "Task", resultBody: big("task-result") },
189
+ { tool: "subagent", resultBody: big("subagent-result") },
190
+ { tool: "skill", resultBody: big("skill-result") },
191
+ { tool: "Bash", resultBody: big("bash-1") },
192
+ { tool: "Bash", resultBody: big("bash-2") },
193
+ { tool: "Bash", resultBody: big("bash-3") },
194
+ // Protected tail — 4 recent turns.
195
+ { tool: "Bash", resultBody: "recent-1" },
196
+ { tool: "Bash", resultBody: "recent-2" },
197
+ { tool: "Bash", resultBody: "recent-3" },
198
+ { tool: "Bash", resultBody: "recent-4" },
199
+ ];
200
+ const messages = buildConversation(turns);
201
+ const result = microcompact(messages);
202
+
203
+ // Only the 3 Bash calls in the stripped region should have been cleared.
204
+ expect(result.clearedToolResults).toBe(3);
205
+
206
+ const trs = findToolResults(result.messages);
207
+ // Turn 0 (Task) — body preserved
208
+ expect(trs[0].content.startsWith("task-result:")).toBe(true);
209
+ // Turn 1 (subagent) — body preserved
210
+ expect(trs[1].content.startsWith("subagent-result:")).toBe(true);
211
+ // Turn 2 (skill) — body preserved
212
+ expect(trs[2].content.startsWith("skill-result:")).toBe(true);
213
+ // Turns 3,4,5 — cleared
214
+ expect(trs[3].content).toBe("[Old tool result content cleared]");
215
+ expect(trs[4].content).toBe("[Old tool result content cleared]");
216
+ expect(trs[5].content).toBe("[Old tool result content cleared]");
217
+ });
218
+
219
+ test("custom protectedTools override the default list", () => {
220
+ const turns = [
221
+ { tool: "Task", resultBody: big("task-body") },
222
+ { tool: "MyTool", resultBody: big("mytool-body") },
223
+ // Protected tail
224
+ { tool: "Bash", resultBody: "recent-1" },
225
+ { tool: "Bash", resultBody: "recent-2" },
226
+ { tool: "Bash", resultBody: "recent-3" },
227
+ { tool: "Bash", resultBody: "recent-4" },
228
+ ];
229
+ const messages = buildConversation(turns);
230
+ const result = microcompact(messages, { protectedTools: ["MyTool"] });
231
+
232
+ const trs = findToolResults(result.messages);
233
+ // Task is NO LONGER protected (not in the custom list) — should be cleared.
234
+ expect(trs[0].content).toBe("[Old tool result content cleared]");
235
+ // MyTool is protected — body preserved.
236
+ expect(trs[1].content.startsWith("mytool-body:")).toBe(true);
237
+ });
238
+ });
239
+
240
+ // ---------------------------------------------------------------------------
241
+ // (c) idempotency
242
+ // ---------------------------------------------------------------------------
243
+
244
+ describe("microcompact — idempotency", () => {
245
+ test("running twice produces zero incremental reclaim", () => {
246
+ const turns = Array.from({ length: 8 }, () => ({
247
+ tool: "Bash",
248
+ resultBody: big("stale"),
249
+ }));
250
+ const messages = buildConversation(turns);
251
+
252
+ const first = microcompact(messages);
253
+ expect(first.reclaimedTokens).toBeGreaterThan(0);
254
+ expect(first.clearedToolResults).toBeGreaterThan(0);
255
+
256
+ const second = microcompact(first.messages);
257
+ expect(second.reclaimedTokens).toBe(0);
258
+ expect(second.clearedToolResults).toBe(0);
259
+ // Second pass returns the same reference when there's nothing to do.
260
+ expect(second.messages).toBe(first.messages);
261
+ });
262
+ });
263
+
264
+ // ---------------------------------------------------------------------------
265
+ // (d) minGainTokens no-op
266
+ // ---------------------------------------------------------------------------
267
+
268
+ describe("microcompact — minGainTokens", () => {
269
+ test("returns original messages when reclaim is below the floor", () => {
270
+ // Tiny bodies — stale region exists but compaction saves very little.
271
+ const turns = Array.from({ length: 8 }, (_, i) => ({
272
+ tool: "Bash",
273
+ resultBody: `tiny-${i}`,
274
+ }));
275
+ const messages = buildConversation(turns);
276
+
277
+ const result = microcompact(messages, { minGainTokens: 10_000 });
278
+ expect(result.reclaimedTokens).toBe(0);
279
+ expect(result.clearedToolResults).toBe(0);
280
+ // Original reference returned verbatim.
281
+ expect(result.messages).toBe(messages);
282
+ });
283
+
284
+ test("returns compacted messages when reclaim meets the floor", () => {
285
+ const turns = Array.from({ length: 8 }, () => ({
286
+ tool: "Bash",
287
+ resultBody: big("stale"),
288
+ }));
289
+ const messages = buildConversation(turns);
290
+
291
+ const result = microcompact(messages, { minGainTokens: 100 });
292
+ expect(result.reclaimedTokens).toBeGreaterThanOrEqual(100);
293
+ expect(result.messages).not.toBe(messages);
294
+ });
295
+ });
296
+
297
+ // ---------------------------------------------------------------------------
298
+ // (e) <ax-tree> block stripping
299
+ // ---------------------------------------------------------------------------
300
+
301
+ describe("microcompact — ax-tree stripping", () => {
302
+ test("strips ax-tree blocks from protected tool_results in the stripped region", () => {
303
+ // Place a protected (Task) tool_result in the stripped region and verify
304
+ // that its ax-tree is collapsed even though the rest of its body is
305
+ // preserved.
306
+ const axTreeBody = `Task output before.\n<ax-tree>\n${"<node/>".repeat(
307
+ 1000,
308
+ )}\n</ax-tree>\nTask output after.`;
309
+
310
+ const turns = [
311
+ { tool: "Task", resultBody: axTreeBody },
312
+ // Protected tail
313
+ { tool: "Bash", resultBody: "recent-1" },
314
+ { tool: "Bash", resultBody: "recent-2" },
315
+ { tool: "Bash", resultBody: "recent-3" },
316
+ { tool: "Bash", resultBody: "recent-4" },
317
+ ];
318
+ const messages = buildConversation(turns);
319
+ const result = microcompact(messages, { minGainTokens: 100 });
320
+
321
+ const trs = findToolResults(result.messages);
322
+ // Task body preserved (it's protected), but ax-tree is collapsed.
323
+ expect(trs[0].content).not.toContain("<ax-tree>");
324
+ expect(trs[0].content).not.toContain("</ax-tree>");
325
+ expect(trs[0].content).toContain("<ax_tree_omitted />");
326
+ expect(trs[0].content).toContain("Task output before.");
327
+ expect(trs[0].content).toContain("Task output after.");
328
+ });
329
+
330
+ test("leaves ax-tree blocks in the protected tail untouched", () => {
331
+ const axTreeBody = `recent output.\n<ax-tree>\n${"<node/>".repeat(
332
+ 500,
333
+ )}\n</ax-tree>`;
334
+
335
+ // Only 2 turns — protectRecentTurns defaults to 4, so everything is
336
+ // protected and ax-trees must not be touched.
337
+ const messages = buildConversation([
338
+ { tool: "Task", resultBody: axTreeBody },
339
+ { tool: "Task", resultBody: axTreeBody },
340
+ ]);
341
+ const result = microcompact(messages);
342
+
343
+ const trs = findToolResults(result.messages);
344
+ expect(trs[0].content).toContain("<ax-tree>");
345
+ expect(trs[1].content).toContain("<ax-tree>");
346
+ });
347
+ });
348
+
349
+ // ---------------------------------------------------------------------------
350
+ // (f) image / file block stubbing
351
+ // ---------------------------------------------------------------------------
352
+
353
+ describe("microcompact — image/file stubbing", () => {
354
+ test("replaces image and file blocks in the stripped region with text stubs", () => {
355
+ const bigBase64 = "A".repeat(20_000);
356
+
357
+ const msgs: Message[] = [
358
+ {
359
+ role: "user",
360
+ content: [
361
+ { type: "text", text: "look at these" },
362
+ {
363
+ type: "image",
364
+ source: {
365
+ type: "base64",
366
+ media_type: "image/png",
367
+ data: bigBase64,
368
+ },
369
+ },
370
+ {
371
+ type: "file",
372
+ source: {
373
+ type: "base64",
374
+ media_type: "application/pdf",
375
+ data: bigBase64,
376
+ filename: "report.pdf",
377
+ },
378
+ },
379
+ ],
380
+ },
381
+ { role: "assistant", content: [{ type: "text", text: "ok" }] },
382
+ // Push 4 follow-up user turns so the image-bearing message is in the
383
+ // stripped region.
384
+ userText("t1"),
385
+ { role: "assistant", content: [{ type: "text", text: "r1" }] },
386
+ userText("t2"),
387
+ { role: "assistant", content: [{ type: "text", text: "r2" }] },
388
+ userText("t3"),
389
+ { role: "assistant", content: [{ type: "text", text: "r3" }] },
390
+ userText("t4"),
391
+ { role: "assistant", content: [{ type: "text", text: "r4" }] },
392
+ ];
393
+
394
+ const result = microcompact(msgs);
395
+
396
+ expect(result.clearedMedia).toBe(2);
397
+ expect(result.reclaimedTokens).toBeGreaterThan(0);
398
+
399
+ // The image and file blocks should be replaced with text stubs.
400
+ const first = result.messages[0];
401
+ expect(first.content[0]).toEqual({ type: "text", text: "look at these" });
402
+ expect(first.content[1]).toEqual({ type: "text", text: "[image omitted]" });
403
+ expect(first.content[2]).toEqual({ type: "text", text: "[file omitted]" });
404
+
405
+ // No image or file blocks remain anywhere in the output.
406
+ const hasImageOrFile = result.messages.some((m) =>
407
+ m.content.some((b) => b.type === "image" || b.type === "file"),
408
+ );
409
+ expect(hasImageOrFile).toBe(false);
410
+ });
411
+
412
+ test("leaves image blocks in the protected tail untouched", () => {
413
+ const bigBase64 = "A".repeat(5_000);
414
+
415
+ const msgs: Message[] = [
416
+ {
417
+ role: "user",
418
+ content: [
419
+ { type: "text", text: "recent user turn" },
420
+ {
421
+ type: "image",
422
+ source: {
423
+ type: "base64",
424
+ media_type: "image/png",
425
+ data: bigBase64,
426
+ },
427
+ },
428
+ ],
429
+ },
430
+ { role: "assistant", content: [{ type: "text", text: "ok" }] },
431
+ ];
432
+
433
+ const result = microcompact(msgs);
434
+ expect(result.clearedMedia).toBe(0);
435
+ expect(result.messages).toBe(msgs);
436
+
437
+ const firstBlocks = result.messages[0].content;
438
+ expect(firstBlocks[1].type).toBe("image");
439
+ });
440
+ });
441
+
442
+ // ---------------------------------------------------------------------------
443
+ // (g) tool_use / tool_result pairing invariant
444
+ // ---------------------------------------------------------------------------
445
+
446
+ describe("microcompact — tool_use/tool_result pairing", () => {
447
+ test("every tool_use retains a matching tool_result after compaction", () => {
448
+ const turns = Array.from({ length: 8 }, () => ({
449
+ tool: "Bash",
450
+ resultBody: big("stale"),
451
+ }));
452
+ const messages = buildConversation(turns);
453
+ const result = microcompact(messages);
454
+
455
+ const tuIds = new Set(findToolUses(result.messages).map((b) => b.id));
456
+ const trIds = new Set(
457
+ findToolResults(result.messages).map((b) => b.tool_use_id),
458
+ );
459
+
460
+ expect(tuIds.size).toBe(8);
461
+ expect(trIds.size).toBe(8);
462
+ for (const id of tuIds) {
463
+ expect(trIds.has(id)).toBe(true);
464
+ }
465
+ });
466
+
467
+ test("message roles and block types are preserved — only bodies mutate", () => {
468
+ const turns = Array.from({ length: 6 }, () => ({
469
+ tool: "Bash",
470
+ resultBody: big("stale"),
471
+ }));
472
+ const messages = buildConversation(turns);
473
+ const result = microcompact(messages);
474
+
475
+ expect(result.messages.length).toBe(messages.length);
476
+ for (let i = 0; i < messages.length; i++) {
477
+ expect(result.messages[i].role).toBe(messages[i].role);
478
+ expect(result.messages[i].content.length).toBe(
479
+ messages[i].content.length,
480
+ );
481
+ for (let j = 0; j < messages[i].content.length; j++) {
482
+ expect(result.messages[i].content[j].type).toBe(
483
+ messages[i].content[j].type,
484
+ );
485
+ }
486
+ }
487
+
488
+ // And every cleared tool_result retains its tool_use_id.
489
+ const resultTrs = findToolResults(result.messages);
490
+ const originalTrs = findToolResults(messages);
491
+ for (let i = 0; i < resultTrs.length; i++) {
492
+ expect(resultTrs[i].tool_use_id).toBe(originalTrs[i].tool_use_id);
493
+ }
494
+ });
495
+
496
+ test("preserves is_error flag when clearing", () => {
497
+ const messages: Message[] = [
498
+ userText("initial"),
499
+ assistantToolUse("tu-err", "Bash"),
500
+ {
501
+ role: "user",
502
+ content: [
503
+ {
504
+ type: "tool_result",
505
+ tool_use_id: "tu-err",
506
+ content: big("error-body"),
507
+ is_error: true,
508
+ },
509
+ ],
510
+ },
511
+ // Push 4 turns to strip the first.
512
+ userText("t1"),
513
+ { role: "assistant", content: [{ type: "text", text: "r1" }] },
514
+ userText("t2"),
515
+ { role: "assistant", content: [{ type: "text", text: "r2" }] },
516
+ userText("t3"),
517
+ { role: "assistant", content: [{ type: "text", text: "r3" }] },
518
+ userText("t4"),
519
+ { role: "assistant", content: [{ type: "text", text: "r4" }] },
520
+ ];
521
+
522
+ const result = microcompact(messages);
523
+ const errBlock = result.messages[2].content[0] as ToolResultContent;
524
+ expect(errBlock.type).toBe("tool_result");
525
+ expect(errBlock.content).toBe("[Old tool result content cleared]");
526
+ expect(errBlock.is_error).toBe(true);
527
+ });
528
+
529
+ test("strips contentBlocks (rich content) from cleared tool_results", () => {
530
+ const messages: Message[] = [
531
+ userText("initial"),
532
+ assistantToolUse("tu-rich", "Bash"),
533
+ {
534
+ role: "user",
535
+ content: [
536
+ {
537
+ type: "tool_result",
538
+ tool_use_id: "tu-rich",
539
+ content: big("body"),
540
+ contentBlocks: [
541
+ {
542
+ type: "image",
543
+ source: {
544
+ type: "base64",
545
+ media_type: "image/png",
546
+ data: "X".repeat(10_000),
547
+ },
548
+ },
549
+ ],
550
+ } as ToolResultContent,
551
+ ],
552
+ },
553
+ // Push 4 turns to strip the first.
554
+ userText("t1"),
555
+ { role: "assistant", content: [{ type: "text", text: "r1" }] },
556
+ userText("t2"),
557
+ { role: "assistant", content: [{ type: "text", text: "r2" }] },
558
+ userText("t3"),
559
+ { role: "assistant", content: [{ type: "text", text: "r3" }] },
560
+ userText("t4"),
561
+ { role: "assistant", content: [{ type: "text", text: "r4" }] },
562
+ ];
563
+
564
+ const result = microcompact(messages);
565
+ const cleared = result.messages[2].content[0] as ToolResultContent;
566
+ expect(cleared.contentBlocks).toBeUndefined();
567
+ expect(cleared.content).toBe("[Old tool result content cleared]");
568
+ });
569
+
570
+ test("preserves text entries in contentBlocks on protected tool_results, drops media", () => {
571
+ // A protected (Task) tool_result in the stripped region carrying both a
572
+ // text entry AND an image entry in its `contentBlocks`. Before the P2
573
+ // fix, BOTH were dropped; text entries can be meaningful (protected-tool
574
+ // narration) and must survive.
575
+ const messages: Message[] = [
576
+ userText("initial"),
577
+ assistantToolUse("tu-prot", "Task"),
578
+ {
579
+ role: "user",
580
+ content: [
581
+ {
582
+ type: "tool_result",
583
+ tool_use_id: "tu-prot",
584
+ content: big("task-body"),
585
+ contentBlocks: [
586
+ { type: "text", text: "important narration from subagent" },
587
+ {
588
+ type: "image",
589
+ source: {
590
+ type: "base64",
591
+ media_type: "image/png",
592
+ data: "X".repeat(10_000),
593
+ },
594
+ },
595
+ ],
596
+ } as ToolResultContent,
597
+ ],
598
+ },
599
+ // Push 4 turns to strip the first.
600
+ userText("t1"),
601
+ { role: "assistant", content: [{ type: "text", text: "r1" }] },
602
+ userText("t2"),
603
+ { role: "assistant", content: [{ type: "text", text: "r2" }] },
604
+ userText("t3"),
605
+ { role: "assistant", content: [{ type: "text", text: "r3" }] },
606
+ userText("t4"),
607
+ { role: "assistant", content: [{ type: "text", text: "r4" }] },
608
+ ];
609
+
610
+ const result = microcompact(messages);
611
+ const preserved = result.messages[2].content[0] as ToolResultContent;
612
+
613
+ // Body is preserved (protected tool — ax-tree stripping only, body kept).
614
+ expect(preserved.type).toBe("tool_result");
615
+ expect(preserved.content.startsWith("task-body:")).toBe(true);
616
+
617
+ // Text contentBlock entry survives.
618
+ expect(preserved.contentBlocks).toBeDefined();
619
+ expect(preserved.contentBlocks!.length).toBe(1);
620
+ expect(preserved.contentBlocks![0]).toEqual({
621
+ type: "text",
622
+ text: "important narration from subagent",
623
+ });
624
+
625
+ // Media entry was dropped, which saved tokens.
626
+ expect(result.reclaimedTokens).toBeGreaterThan(0);
627
+ });
628
+
629
+ test("protected tool_result with only text contentBlocks is a no-op (body unchanged, text kept)", () => {
630
+ // No media to strip and no ax-tree in body — nothing should change.
631
+ const messages: Message[] = [
632
+ userText("initial"),
633
+ assistantToolUse("tu-prot-txt", "Task"),
634
+ {
635
+ role: "user",
636
+ content: [
637
+ {
638
+ type: "tool_result",
639
+ tool_use_id: "tu-prot-txt",
640
+ content: "task body without ax-tree",
641
+ contentBlocks: [
642
+ { type: "text", text: "only a text attachment here" },
643
+ ],
644
+ } as ToolResultContent,
645
+ ],
646
+ },
647
+ userText("t1"),
648
+ { role: "assistant", content: [{ type: "text", text: "r1" }] },
649
+ userText("t2"),
650
+ { role: "assistant", content: [{ type: "text", text: "r2" }] },
651
+ userText("t3"),
652
+ { role: "assistant", content: [{ type: "text", text: "r3" }] },
653
+ userText("t4"),
654
+ { role: "assistant", content: [{ type: "text", text: "r4" }] },
655
+ ];
656
+
657
+ const result = microcompact(messages, { minGainTokens: 0 });
658
+ // No reclaim for the protected tool_result, so it keeps its original text
659
+ // entries and body.
660
+ const kept = result.messages[2].content[0] as ToolResultContent;
661
+ expect(kept.content).toBe("task body without ax-tree");
662
+ expect(kept.contentBlocks).toBeDefined();
663
+ expect(kept.contentBlocks!.length).toBe(1);
664
+ expect(kept.contentBlocks![0]).toEqual({
665
+ type: "text",
666
+ text: "only a text attachment here",
667
+ });
668
+ });
669
+ });
670
+
671
+ // ---------------------------------------------------------------------------
672
+ // (h) web_search_tool_result & system_notice classifier
673
+ // ---------------------------------------------------------------------------
674
+
675
+ describe("microcompact — tool-response-only user message classifier", () => {
676
+ test("web_search_tool_result-only user messages do not count as a user turn", () => {
677
+ // Single real user turn followed by an assistant server_tool_use and a
678
+ // web_search_tool_result-only user message. protectRecentTurns=1 should
679
+ // still protect the single real user turn plus its trailing assistant
680
+ // response — NOT wastefully also protect the web_search_tool_result
681
+ // message (which would push the real tool_result on the prior turn out of
682
+ // reach).
683
+ const msgs: Message[] = [
684
+ userText("first real user turn"),
685
+ assistantToolUse("tu-old", "Bash"),
686
+ toolResult("tu-old", big("old-body")),
687
+ userText("second real user turn"),
688
+ {
689
+ role: "assistant",
690
+ content: [
691
+ {
692
+ type: "server_tool_use",
693
+ id: "srv-1",
694
+ name: "web_search",
695
+ input: { query: "x" },
696
+ },
697
+ ],
698
+ },
699
+ {
700
+ role: "user",
701
+ content: [
702
+ {
703
+ type: "web_search_tool_result",
704
+ tool_use_id: "srv-1",
705
+ content: [],
706
+ },
707
+ ],
708
+ },
709
+ { role: "assistant", content: [{ type: "text", text: "done" }] },
710
+ ];
711
+
712
+ // protectRecentTurns=1 should protect ONLY "second real user turn" and
713
+ // everything after. tu-old (attached to "first real user turn") should be
714
+ // cleared.
715
+ const result = microcompact(msgs, { protectRecentTurns: 1 });
716
+ expect(result.clearedToolResults).toBe(1);
717
+
718
+ const trs = findToolResults(result.messages);
719
+ expect(trs[0].content).toBe("[Old tool result content cleared]");
720
+ });
721
+
722
+ test("system_notice-only text user messages do not count as a user turn", () => {
723
+ // System notices (retry nudges / progress checks) are injected as
724
+ // user-role text blocks wrapped in <system_notice>...</system_notice>.
725
+ // They must not be treated as real user turns.
726
+ const msgs: Message[] = [
727
+ userText("first real user turn"),
728
+ assistantToolUse("tu-old", "Bash"),
729
+ toolResult("tu-old", big("old-body")),
730
+ userText("second real user turn"),
731
+ {
732
+ role: "assistant",
733
+ content: [{ type: "text", text: "some response" }],
734
+ },
735
+ {
736
+ role: "user",
737
+ content: [
738
+ {
739
+ type: "text",
740
+ text: "<system_notice>please continue</system_notice>",
741
+ },
742
+ ],
743
+ },
744
+ ];
745
+
746
+ const result = microcompact(msgs, { protectRecentTurns: 1 });
747
+ expect(result.clearedToolResults).toBe(1);
748
+
749
+ const trs = findToolResults(result.messages);
750
+ expect(trs[0].content).toBe("[Old tool result content cleared]");
751
+ });
752
+
753
+ test("mixed user message (real text + tool_result) still counts as a user turn", () => {
754
+ // A user message containing both real text AND tool-response blocks is
755
+ // still a real user turn — don't misclassify.
756
+ const msgs: Message[] = [
757
+ userText("older turn"),
758
+ assistantToolUse("tu-old", "Bash"),
759
+ toolResult("tu-old", big("old-body")),
760
+ {
761
+ role: "user",
762
+ content: [
763
+ { type: "text", text: "new user message with context" },
764
+ {
765
+ type: "tool_result",
766
+ tool_use_id: "tu-old",
767
+ content: "inline result",
768
+ } as ToolResultContent,
769
+ ],
770
+ },
771
+ { role: "assistant", content: [{ type: "text", text: "response" }] },
772
+ ];
773
+
774
+ // protectRecentTurns=1 should protect the mixed message + trailing
775
+ // assistant response; the older turn's tool_result should be cleared.
776
+ const result = microcompact(msgs, { protectRecentTurns: 1 });
777
+ expect(result.clearedToolResults).toBe(1);
778
+ });
779
+ });
780
+
781
+ // ---------------------------------------------------------------------------
782
+ // Summary counters sanity
783
+ // ---------------------------------------------------------------------------
784
+
785
+ describe("microcompact — counter sanity", () => {
786
+ test("counts match what was actually cleared", () => {
787
+ const messages = buildConversation(
788
+ Array.from({ length: 8 }, () => ({
789
+ tool: "Bash",
790
+ resultBody: big("stale"),
791
+ })),
792
+ );
793
+ const result = microcompact(messages);
794
+ expect(countClearedToolResults(result.messages)).toBe(
795
+ result.clearedToolResults,
796
+ );
797
+ });
798
+
799
+ test("empty input returns zero counters and original reference", () => {
800
+ const result = microcompact([]);
801
+ expect(result.reclaimedTokens).toBe(0);
802
+ expect(result.clearedToolResults).toBe(0);
803
+ expect(result.clearedMedia).toBe(0);
804
+ });
805
+ });