@vellumai/assistant 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (666) hide show
  1. package/ARCHITECTURE.md +6 -7
  2. package/Dockerfile +1 -0
  3. package/README.md +2 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +79 -139
  5. package/bun.lock +3 -0
  6. package/docs/architecture/security.md +18 -16
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +1 -5
  9. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -5
  10. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -16
  11. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +1 -9
  12. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +12 -12
  13. package/node_modules/@vellumai/slack-text/bun.lock +24 -0
  14. package/node_modules/@vellumai/slack-text/package.json +18 -0
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +153 -0
  16. package/node_modules/@vellumai/slack-text/src/index.ts +235 -0
  17. package/node_modules/@vellumai/slack-text/tsconfig.json +20 -0
  18. package/openapi.yaml +294 -107
  19. package/package.json +4 -2
  20. package/scripts/generate-openapi.ts +16 -111
  21. package/src/__tests__/agent-wake-override-profile.test.ts +23 -1
  22. package/src/__tests__/anthropic-provider.test.ts +56 -13
  23. package/src/__tests__/app-conversation-ids-backfill.test.ts +278 -0
  24. package/src/__tests__/app-conversation-ids.test.ts +151 -0
  25. package/src/__tests__/approval-cascade.test.ts +0 -15
  26. package/src/__tests__/approval-routes-http.test.ts +6 -17
  27. package/src/__tests__/assistant-event-hub.test.ts +126 -77
  28. package/src/__tests__/assistant-event.test.ts +0 -5
  29. package/src/__tests__/assistant-events-sse-hardening.test.ts +37 -15
  30. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -29
  31. package/src/__tests__/background-shell-host-bash.test.ts +34 -43
  32. package/src/__tests__/call-controller.test.ts +1 -1
  33. package/src/__tests__/call-site-routing-provider.test.ts +193 -0
  34. package/src/__tests__/channel-approval-routes.test.ts +10 -296
  35. package/src/__tests__/channel-approvals.test.ts +25 -17
  36. package/src/__tests__/channel-guardian.test.ts +100 -146
  37. package/src/__tests__/checker.test.ts +20 -34
  38. package/src/__tests__/compact-event-conversation-id-guard.test.ts +50 -0
  39. package/src/__tests__/compaction-events.test.ts +2 -0
  40. package/src/__tests__/config-schema.test.ts +6 -48
  41. package/src/__tests__/config-watcher.test.ts +12 -0
  42. package/src/__tests__/connection-policy.test.ts +1 -52
  43. package/src/__tests__/contacts-write.test.ts +2 -64
  44. package/src/__tests__/context-image-dimensions.test.ts +1 -1
  45. package/src/__tests__/context-search-memory-source.test.ts +120 -1
  46. package/src/__tests__/context-search-memory-v2-source.test.ts +383 -0
  47. package/src/__tests__/context-search-pkb-source.test.ts +49 -0
  48. package/src/__tests__/context-search-workspace-source.test.ts +9 -22
  49. package/src/__tests__/context-window-manager.test.ts +46 -0
  50. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  51. package/src/__tests__/conversation-agent-loop-overflow.test.ts +102 -29
  52. package/src/__tests__/conversation-agent-loop.test.ts +980 -13
  53. package/src/__tests__/conversation-analysis-routes.test.ts +12 -10
  54. package/src/__tests__/conversation-attention-telegram.test.ts +11 -3
  55. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -291
  56. package/src/__tests__/conversation-history-web-search.test.ts +4 -3
  57. package/src/__tests__/conversation-inference-profile-route.test.ts +12 -23
  58. package/src/__tests__/conversation-lifecycle.test.ts +4 -4
  59. package/src/__tests__/conversation-process-callsite.test.ts +79 -2
  60. package/src/__tests__/conversation-queue.test.ts +3 -8
  61. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -161
  62. package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -32
  63. package/src/__tests__/conversation-routes-slash-commands.test.ts +75 -66
  64. package/src/__tests__/conversation-runtime-assembly.test.ts +257 -3
  65. package/src/__tests__/conversation-slash-commands.test.ts +24 -4
  66. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  67. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  68. package/src/__tests__/conversation-starter-routes.test.ts +79 -2
  69. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -5
  70. package/src/__tests__/conversation-surfaces-standalone.test.ts +18 -14
  71. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -2
  72. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +8 -46
  73. package/src/__tests__/conversation-usage.test.ts +253 -3
  74. package/src/__tests__/credential-execution-shell-lockdown.test.ts +0 -39
  75. package/src/__tests__/credential-health-service.test.ts +68 -0
  76. package/src/__tests__/credential-security-e2e.test.ts +4 -3
  77. package/src/__tests__/credential-security-invariants.test.ts +1 -5
  78. package/src/__tests__/credential-token-resolver.test.ts +180 -0
  79. package/src/__tests__/cu-unified-flow.test.ts +33 -16
  80. package/src/__tests__/daemon-assistant-events.test.ts +34 -21
  81. package/src/__tests__/daemon-credential-client.test.ts +4 -1
  82. package/src/__tests__/db-connection-isolation.test.ts +125 -0
  83. package/src/__tests__/db-migration-rollback.test.ts +101 -0
  84. package/src/__tests__/db-slack-compaction-watermark-migration.test.ts +169 -0
  85. package/src/__tests__/deterministic-verification-control-plane.test.ts +7 -80
  86. package/src/__tests__/document-conversations.test.ts +332 -0
  87. package/src/__tests__/embedding-managed-proxy-selection.test.ts +2 -2
  88. package/src/__tests__/emit-event-signal.test.ts +4 -6
  89. package/src/__tests__/events-client-registration.test.ts +193 -49
  90. package/src/__tests__/filing-service.test.ts +58 -7
  91. package/src/__tests__/first-greeting.test.ts +156 -150
  92. package/src/__tests__/fixtures/mock-chrome-extension.ts +108 -66
  93. package/src/__tests__/get-skill-detail-audit.test.ts +3 -8
  94. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  95. package/src/__tests__/guardian-dispatch.test.ts +1 -1
  96. package/src/__tests__/guardian-grant-minting.test.ts +7 -2
  97. package/src/__tests__/guardian-routing-invariants.test.ts +7 -2
  98. package/src/__tests__/guardian-routing-state.test.ts +1 -1
  99. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +32 -11
  100. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -83
  101. package/src/__tests__/headless-browser-mode.test.ts +4 -9
  102. package/src/__tests__/headless-browser-navigate.test.ts +21 -20
  103. package/src/__tests__/heartbeat-service.test.ts +289 -7
  104. package/src/__tests__/helpers/channel-test-adapter.ts +2 -2
  105. package/src/__tests__/helpers/create-guardian-binding.ts +91 -0
  106. package/src/__tests__/host-bash-proxy.test.ts +46 -122
  107. package/src/__tests__/host-browser-e2e-cloud.test.ts +36 -497
  108. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +26 -96
  109. package/src/__tests__/host-browser-proxy.test.ts +111 -185
  110. package/src/__tests__/host-browser-routes.test.ts +45 -75
  111. package/src/__tests__/host-browser-ws-events-e2e.test.ts +26 -30
  112. package/src/__tests__/host-cu-proxy.test.ts +56 -111
  113. package/src/__tests__/host-file-proxy.test.ts +44 -98
  114. package/src/__tests__/host-file-read-tool.test.ts +42 -21
  115. package/src/__tests__/host-shell-tool.test.ts +33 -68
  116. package/src/__tests__/host-transfer-pending-interactions.test.ts +2 -18
  117. package/src/__tests__/host-transfer-proxy.test.ts +43 -53
  118. package/src/__tests__/http-user-message-parity.test.ts +0 -6
  119. package/src/__tests__/inbound-slack-persistence.test.ts +31 -0
  120. package/src/__tests__/injector-chain.test.ts +10 -5
  121. package/src/__tests__/injector-pkb-v2-silenced.test.ts +124 -0
  122. package/src/__tests__/inline-command-runner.test.ts +0 -66
  123. package/src/__tests__/inline-skill-load-permissions.test.ts +0 -2
  124. package/src/__tests__/install-skill-routing.test.ts +1 -13
  125. package/src/__tests__/llm-callsite-catalog.test.ts +34 -0
  126. package/src/__tests__/llm-catalog-parity.test.ts +90 -0
  127. package/src/__tests__/llm-context-resolution.test.ts +180 -0
  128. package/src/__tests__/llm-resolver.test.ts +80 -12
  129. package/src/__tests__/llm-usage-store.test.ts +269 -4
  130. package/src/__tests__/log-export-routes.test.ts +89 -0
  131. package/src/__tests__/managed-profile-guard.test.ts +225 -0
  132. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -10
  133. package/src/__tests__/manual-token-reconciliation.test.ts +334 -0
  134. package/src/__tests__/memory-v2-static-injector.test.ts +95 -0
  135. package/src/__tests__/migration-cross-version-compatibility.test.ts +197 -291
  136. package/src/__tests__/migration-export-http.test.ts +33 -26
  137. package/src/__tests__/migration-export-streaming.test.ts +18 -10
  138. package/src/__tests__/migration-export-to-gcs.test.ts +49 -9
  139. package/src/__tests__/migration-import-commit-http.test.ts +66 -21
  140. package/src/__tests__/migration-import-from-gcs.test.ts +50 -9
  141. package/src/__tests__/migration-import-from-url.test.ts +20 -6
  142. package/src/__tests__/migration-import-preflight-http.test.ts +95 -95
  143. package/src/__tests__/migration-parity-persistence.test.ts +62 -25
  144. package/src/__tests__/migration-transport.test.ts +115 -23
  145. package/src/__tests__/migration-validate-http.test.ts +105 -80
  146. package/src/__tests__/migration-wizard.test.ts +133 -27
  147. package/src/__tests__/non-member-access-request.test.ts +1 -1
  148. package/src/__tests__/notification-guardian-path.test.ts +1 -1
  149. package/src/__tests__/oauth-store.test.ts +19 -0
  150. package/src/__tests__/platform-bash-auto-approve.test.ts +21 -12
  151. package/src/__tests__/prechat-onboarding-contract.test.ts +31 -7
  152. package/src/__tests__/pricing.test.ts +68 -4
  153. package/src/__tests__/process-message-background-slack.test.ts +331 -0
  154. package/src/__tests__/provider-managed-proxy-integration.test.ts +153 -17
  155. package/src/__tests__/provider-send-message-override-profile.test.ts +50 -0
  156. package/src/__tests__/provider-usage-tracking.test.ts +208 -0
  157. package/src/__tests__/reaction-persistence.test.ts +9 -6
  158. package/src/__tests__/rebind-secrets-screen.test.ts +53 -16
  159. package/src/__tests__/recording-handler.test.ts +64 -81
  160. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +4 -3
  161. package/src/__tests__/relay-server.test.ts +18 -13
  162. package/src/__tests__/require-fresh-approval.test.ts +13 -22
  163. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  164. package/src/__tests__/runtime-events-sse-parity.test.ts +3 -4
  165. package/src/__tests__/runtime-events-sse.test.ts +3 -12
  166. package/src/__tests__/search-skills-unified.test.ts +9 -15
  167. package/src/__tests__/secret-ingress-cli.test.ts +2 -5
  168. package/src/__tests__/secret-ingress-http.test.ts +0 -4
  169. package/src/__tests__/secret-onetime-send.test.ts +4 -2
  170. package/src/__tests__/secret-prompt-log-hygiene.test.ts +24 -7
  171. package/src/__tests__/secret-prompter-channel-fallback.test.ts +42 -47
  172. package/src/__tests__/secret-response-routing.test.ts +29 -15
  173. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -1
  174. package/src/__tests__/secret-scanner.test.ts +2 -545
  175. package/src/__tests__/send-endpoint-busy.test.ts +9 -24
  176. package/src/__tests__/settings-routes.test.ts +1 -1
  177. package/src/__tests__/shell-credential-ref.test.ts +0 -8
  178. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -56
  179. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -11
  180. package/src/__tests__/skill-tool-factory.test.ts +97 -0
  181. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -30
  182. package/src/__tests__/skills-files-catalog-fallback.test.ts +11 -17
  183. package/src/__tests__/slack-inbound-verification.test.ts +1 -62
  184. package/src/__tests__/subagent-fork-notifications.test.ts +57 -47
  185. package/src/__tests__/subagent-manager-notify.test.ts +70 -70
  186. package/src/__tests__/subagent-notify-parent.test.ts +80 -83
  187. package/src/__tests__/system-prompt.test.ts +115 -13
  188. package/src/__tests__/terminal-tools.test.ts +0 -89
  189. package/src/__tests__/thread-backfill.test.ts +945 -31
  190. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -36
  191. package/src/__tests__/tool-execute-pipeline.test.ts +0 -6
  192. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -16
  193. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +9 -19
  194. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -7
  195. package/src/__tests__/tool-executor.test.ts +12 -19
  196. package/src/__tests__/tool-metrics-listener.test.ts +0 -35
  197. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  198. package/src/__tests__/tool-trace-listener.test.ts +0 -17
  199. package/src/__tests__/transfer-progress-screen.test.ts +63 -26
  200. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -149
  201. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -4
  202. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  203. package/src/__tests__/tts-catalog-parity.test.ts +16 -5
  204. package/src/__tests__/usage-attribution.test.ts +247 -0
  205. package/src/__tests__/usage-cli.test.ts +143 -0
  206. package/src/__tests__/usage-grouped-buckets.test.ts +155 -0
  207. package/src/__tests__/usage-routes.test.ts +150 -0
  208. package/src/__tests__/validation-results-screen.test.ts +39 -16
  209. package/src/__tests__/vbundle-pax-and-symlink.test.ts +12 -3
  210. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +49 -137
  211. package/src/__tests__/verification-control-plane-policy.test.ts +4 -7
  212. package/src/__tests__/voice-session-bridge.test.ts +5 -5
  213. package/src/__tests__/workspace-migration-062-drop-memory-v2-edges-json.test.ts +103 -0
  214. package/src/__tests__/workspace-migration-063-release-notes-dynamic-model-context.test.ts +77 -0
  215. package/src/__tests__/workspace-migration-064-unwind-main-agent-opus-seed.test.ts +225 -0
  216. package/src/__tests__/workspace-migration-memory-v2-init.test.ts +8 -30
  217. package/src/acp/index.ts +0 -15
  218. package/src/acp/session-manager.ts +37 -34
  219. package/src/agent/loop.ts +16 -1
  220. package/src/approvals/AGENTS.md +4 -0
  221. package/src/approvals/__tests__/guardian-feed-event.test.ts +10 -3
  222. package/src/approvals/guardian-request-resolvers.ts +10 -2
  223. package/src/backup/__tests__/backup-worker.test.ts +36 -8
  224. package/src/backup/__tests__/paths.test.ts +2 -2
  225. package/src/backup/__tests__/restore.test.ts +45 -28
  226. package/src/backup/backup-worker.ts +36 -2
  227. package/src/backup/paths.ts +9 -6
  228. package/src/browser-session/events.ts +0 -9
  229. package/src/calls/call-store.ts +1 -34
  230. package/src/calls/guardian-question-copy.ts +0 -108
  231. package/src/calls/relay-server.ts +0 -24
  232. package/src/calls/twilio-rest.ts +0 -38
  233. package/src/calls/twilio-routes.ts +1 -1
  234. package/src/calls/voice-session-bridge.ts +7 -38
  235. package/src/channels/types.ts +1 -36
  236. package/src/cli/commands/__tests__/cache.test.ts +152 -5
  237. package/src/cli/commands/__tests__/memory-v2.test.ts +14 -28
  238. package/src/cli/commands/__tests__/trust.test.ts +21 -387
  239. package/src/cli/commands/backup.ts +4 -4
  240. package/src/cli/commands/cache-fs.ts +8 -0
  241. package/src/cli/commands/cache.ts +153 -82
  242. package/src/cli/commands/clients.ts +63 -5
  243. package/src/cli/commands/completions.ts +3 -3
  244. package/src/cli/commands/contacts.ts +231 -76
  245. package/src/cli/commands/keys.ts +4 -1
  246. package/src/cli/commands/memory-v2.ts +24 -52
  247. package/src/cli/commands/oauth/shared.ts +2 -29
  248. package/src/cli/commands/pending.ts +102 -0
  249. package/src/cli/commands/skills.ts +77 -35
  250. package/src/cli/commands/trust.ts +70 -430
  251. package/src/cli/commands/usage.ts +25 -16
  252. package/src/cli/lib/daemon-credential-client.ts +14 -0
  253. package/src/cli/program.ts +2 -0
  254. package/src/cli.ts +0 -21
  255. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  256. package/src/config/bundled-skills/messaging/TOOLS.json +14 -4
  257. package/src/config/env-registry.ts +12 -2
  258. package/src/config/env.ts +3 -14
  259. package/src/config/feature-flag-registry.json +30 -30
  260. package/src/config/llm-callsite-catalog.ts +12 -0
  261. package/src/config/llm-context-resolution.ts +80 -0
  262. package/src/config/llm-resolver.ts +58 -22
  263. package/src/config/loader.ts +3 -3
  264. package/src/config/schema.ts +2 -158
  265. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  266. package/src/config/schemas/call-site-catalog.ts +271 -0
  267. package/src/config/schemas/calls.ts +5 -5
  268. package/src/config/schemas/inference.ts +1 -1
  269. package/src/config/schemas/ingress.ts +1 -1
  270. package/src/config/schemas/llm.ts +31 -3
  271. package/src/config/schemas/memory-retrieval.ts +2 -2
  272. package/src/config/schemas/memory-v2.ts +9 -0
  273. package/src/config/schemas/security.ts +1 -42
  274. package/src/config/schemas/services.ts +6 -6
  275. package/src/config/schemas/skills.ts +5 -5
  276. package/src/config/schemas/tts.ts +1 -1
  277. package/src/config/seed-inference-profiles.ts +117 -0
  278. package/src/config/skills.ts +0 -90
  279. package/src/config/types.ts +3 -6
  280. package/src/contacts/contact-store.ts +0 -17
  281. package/src/contacts/contacts-write.ts +1 -105
  282. package/src/context/window-manager.ts +44 -5
  283. package/src/credential-execution/process-manager.ts +34 -10
  284. package/src/credential-health/credential-health-service.ts +21 -16
  285. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +75 -82
  286. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -9
  287. package/src/daemon/connection-policy.ts +1 -26
  288. package/src/daemon/conversation-agent-loop-handlers.ts +53 -4
  289. package/src/daemon/conversation-agent-loop.ts +277 -36
  290. package/src/daemon/conversation-history.ts +8 -8
  291. package/src/daemon/conversation-launch.ts +20 -135
  292. package/src/daemon/conversation-lifecycle.ts +1 -1
  293. package/src/daemon/conversation-messaging.ts +1 -0
  294. package/src/daemon/conversation-process.ts +83 -163
  295. package/src/daemon/conversation-runtime-assembly.ts +219 -76
  296. package/src/daemon/conversation-slash.ts +47 -5
  297. package/src/daemon/conversation-store.ts +7 -31
  298. package/src/daemon/conversation-surfaces.ts +22 -28
  299. package/src/daemon/conversation-tool-setup.ts +3 -33
  300. package/src/daemon/conversation-usage.ts +36 -0
  301. package/src/daemon/conversation.ts +117 -233
  302. package/src/daemon/daemon-control.ts +3 -71
  303. package/src/daemon/daemon-skill-host.ts +8 -11
  304. package/src/daemon/dictation-profile-store.ts +2 -26
  305. package/src/daemon/first-greeting.ts +44 -156
  306. package/src/daemon/handlers/config-channels.ts +12 -12
  307. package/src/daemon/handlers/config-ingress.ts +4 -165
  308. package/src/daemon/handlers/config-model.ts +1 -1
  309. package/src/daemon/handlers/config-voice.ts +0 -42
  310. package/src/daemon/handlers/conversations.ts +11 -190
  311. package/src/daemon/handlers/recording.ts +26 -158
  312. package/src/daemon/handlers/shared.ts +23 -71
  313. package/src/daemon/handlers/skills.ts +42 -93
  314. package/src/daemon/host-bash-proxy.ts +67 -45
  315. package/src/daemon/host-browser-proxy.ts +65 -27
  316. package/src/daemon/host-cu-proxy.ts +40 -39
  317. package/src/daemon/host-file-proxy.ts +58 -37
  318. package/src/daemon/host-transfer-proxy.ts +84 -46
  319. package/src/daemon/lifecycle.ts +49 -15
  320. package/src/daemon/message-types/conversations.ts +7 -0
  321. package/src/daemon/message-types/host-bash.ts +1 -0
  322. package/src/daemon/message-types/host-cu.ts +1 -0
  323. package/src/daemon/message-types/host-file.ts +1 -0
  324. package/src/daemon/message-types/host-transfer.ts +1 -0
  325. package/src/daemon/message-types/messages.ts +10 -9
  326. package/src/daemon/message-types/workspace.ts +1 -1
  327. package/src/daemon/process-message.ts +102 -239
  328. package/src/daemon/server.ts +13 -462
  329. package/src/daemon/shutdown-handlers.ts +2 -2
  330. package/src/daemon/tool-side-effects.ts +125 -107
  331. package/src/daemon/trust-context.ts +13 -0
  332. package/src/daemon/wake-target-adapter.ts +4 -9
  333. package/src/events/domain-events.ts +0 -8
  334. package/src/events/tool-audit-listener.ts +3 -1
  335. package/src/events/tool-domain-event-publisher.ts +0 -10
  336. package/src/events/tool-metrics-listener.ts +0 -17
  337. package/src/events/tool-trace-listener.ts +0 -14
  338. package/src/filing/filing-service.ts +13 -1
  339. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +6 -2
  340. package/src/heartbeat/heartbeat-service.ts +23 -5
  341. package/src/home/__tests__/feed-writer.test.ts +0 -4
  342. package/src/home/__tests__/relationship-state-writer.test.ts +30 -0
  343. package/src/home/feed-writer.ts +1 -2
  344. package/src/home/relationship-state-writer.ts +16 -3
  345. package/src/ipc/__tests__/browser-ipc.test.ts +2 -12
  346. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -1
  347. package/src/ipc/assistant-server.ts +3 -10
  348. package/src/ipc/routes/__tests__/memory-v2-backfill.test.ts +39 -20
  349. package/src/ipc/routes/route-adapter.ts +1 -1
  350. package/src/ipc/routes/trust-rules.test.ts +0 -95
  351. package/src/ipc/skill-ipc-types.ts +41 -0
  352. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +13 -27
  353. package/src/ipc/skill-routes/__tests__/identity.test.ts +4 -23
  354. package/src/ipc/skill-routes/events.ts +12 -23
  355. package/src/ipc/skill-routes/identity.ts +4 -17
  356. package/src/ipc/skill-routes/index.ts +1 -1
  357. package/src/ipc/skill-server.ts +6 -39
  358. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -8
  359. package/src/live-voice/protocol.ts +4 -13
  360. package/src/mcp/manager.ts +0 -5
  361. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +55 -0
  362. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +127 -0
  363. package/src/memory/app-git-service.ts +0 -32
  364. package/src/memory/app-store.ts +154 -0
  365. package/src/memory/attachments-store.ts +6 -0
  366. package/src/memory/context-search/sources/memory-v2.ts +578 -0
  367. package/src/memory/context-search/sources/memory.ts +5 -0
  368. package/src/memory/context-search/sources/pkb.ts +10 -1
  369. package/src/memory/context-search/sources/workspace.ts +3 -2
  370. package/src/memory/conversation-crud.ts +29 -4
  371. package/src/memory/conversation-disk-view.ts +1 -5
  372. package/src/memory/conversation-starter-checkpoints.ts +63 -0
  373. package/src/memory/db-connection.ts +62 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +3 -21
  376. package/src/memory/embedding-gemini.ts +0 -2
  377. package/src/memory/embedding-local.ts +6 -6
  378. package/src/memory/embedding-ollama.ts +6 -6
  379. package/src/memory/embedding-openai.ts +6 -6
  380. package/src/memory/embedding-types.ts +21 -0
  381. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +3 -7
  382. package/src/memory/graph/conversation-graph-memory.ts +35 -13
  383. package/src/memory/graph/injection.test.ts +2 -2
  384. package/src/memory/graph/injection.ts +1 -1
  385. package/src/memory/guardian-action-store.ts +0 -83
  386. package/src/memory/guardian-approvals.ts +0 -48
  387. package/src/memory/indexer.ts +1 -15
  388. package/src/memory/job-handlers/conversation-starters.ts +36 -53
  389. package/src/memory/job-utils.ts +0 -6
  390. package/src/memory/jobs-store.ts +0 -1
  391. package/src/memory/jobs-worker.ts +2 -16
  392. package/src/memory/llm-request-log-store.ts +0 -41
  393. package/src/memory/llm-usage-store.ts +129 -43
  394. package/src/memory/memory-v2-activation-log-store.ts +115 -0
  395. package/src/memory/migrations/233-document-conversations.ts +54 -0
  396. package/src/memory/migrations/234-memory-v2-activation-logs.ts +55 -0
  397. package/src/memory/migrations/235-llm-usage-attribution.ts +31 -0
  398. package/src/memory/migrations/235-slack-compaction-watermark.ts +44 -0
  399. package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +26 -0
  400. package/src/memory/migrations/__tests__/234-memory-v2-activation-logs.test.ts +182 -0
  401. package/src/memory/migrations/index.ts +14 -0
  402. package/src/memory/migrations/registry.ts +24 -0
  403. package/src/memory/raw-query.ts +2 -68
  404. package/src/memory/schema/conversations.ts +7 -0
  405. package/src/memory/schema/infrastructure.ts +25 -0
  406. package/src/memory/search/semantic.ts +5 -16
  407. package/src/memory/tool-usage-store.ts +2 -0
  408. package/src/memory/usage-buckets.ts +40 -1
  409. package/src/memory/usage-grouped-buckets.ts +127 -0
  410. package/src/memory/v2/__tests__/activation.test.ts +289 -90
  411. package/src/memory/v2/__tests__/backfill-jobs.test.ts +2 -129
  412. package/src/memory/v2/__tests__/consolidation-job.test.ts +28 -11
  413. package/src/memory/v2/__tests__/edge-index.test.ts +278 -0
  414. package/src/memory/v2/__tests__/injection.test.ts +384 -15
  415. package/src/memory/v2/__tests__/migration.test.ts +64 -36
  416. package/src/memory/v2/__tests__/page-store.test.ts +191 -8
  417. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +181 -0
  418. package/src/memory/v2/__tests__/skill-store.test.ts +115 -3
  419. package/src/memory/v2/__tests__/static-context.test.ts +153 -0
  420. package/src/memory/v2/activation.ts +168 -97
  421. package/src/memory/v2/backfill-jobs.ts +15 -100
  422. package/src/memory/v2/consolidation-job.ts +14 -12
  423. package/src/memory/v2/edge-index.ts +191 -0
  424. package/src/memory/v2/injection.ts +182 -58
  425. package/src/memory/v2/migration.ts +57 -64
  426. package/src/memory/v2/now-text.ts +2 -3
  427. package/src/memory/v2/page-store.ts +168 -31
  428. package/src/memory/v2/prompts/consolidation.ts +118 -42
  429. package/src/memory/v2/prompts/sweep.ts +3 -3
  430. package/src/memory/v2/skill-store.ts +55 -7
  431. package/src/memory/v2/static-context.ts +62 -0
  432. package/src/memory/v2/types.ts +10 -20
  433. package/src/memory/validation.ts +0 -11
  434. package/src/messaging/draft-store.ts +0 -6
  435. package/src/messaging/provider-types.ts +8 -0
  436. package/src/messaging/provider.ts +7 -0
  437. package/src/messaging/providers/gmail/client.ts +1 -121
  438. package/src/messaging/providers/outlook/client.ts +0 -73
  439. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +226 -0
  440. package/src/messaging/providers/slack/adapter.ts +122 -21
  441. package/src/messaging/providers/slack/backfill.test.ts +95 -6
  442. package/src/messaging/providers/slack/backfill.ts +89 -11
  443. package/src/messaging/providers/slack/client.ts +10 -124
  444. package/src/messaging/providers/slack/message-metadata.ts +12 -2
  445. package/src/messaging/providers/slack/render-transcript.test.ts +56 -0
  446. package/src/messaging/providers/slack/render-transcript.ts +126 -25
  447. package/src/messaging/providers/slack/types.ts +1 -0
  448. package/src/oauth/connection-resolver.test.ts +8 -0
  449. package/src/oauth/connection-resolver.ts +8 -16
  450. package/src/oauth/credential-token-resolver.ts +97 -0
  451. package/src/oauth/manual-token-connection.ts +30 -34
  452. package/src/oauth/oauth-store.ts +6 -4
  453. package/src/outbound-proxy/certs.ts +0 -7
  454. package/src/outbound-proxy/config.ts +0 -74
  455. package/src/outbound-proxy/health.ts +0 -44
  456. package/src/outbound-proxy/index.ts +0 -22
  457. package/src/permissions/approval-provenance.test.ts +184 -0
  458. package/src/permissions/approval-provenance.ts +70 -0
  459. package/src/permissions/checker.ts +4 -1
  460. package/src/permissions/gateway-threshold-reader.ts +4 -1
  461. package/src/permissions/prompter.ts +9 -2
  462. package/src/permissions/secret-prompter.ts +21 -48
  463. package/src/permissions/types.ts +33 -0
  464. package/src/permissions/workspace-policy.ts +0 -5
  465. package/src/platform/sync-identity.ts +0 -8
  466. package/src/plugins/defaults/injectors.ts +69 -2
  467. package/src/plugins/defaults/overflow-reduce.ts +3 -2
  468. package/src/plugins/types.ts +8 -0
  469. package/src/prompts/system-prompt.ts +34 -70
  470. package/src/prompts/templates/BOOTSTRAP.md +52 -6
  471. package/src/prompts/update-bulletin-job.ts +2 -0
  472. package/src/providers/__tests__/retry-callsite.test.ts +138 -1
  473. package/src/providers/anthropic/client.ts +72 -33
  474. package/src/providers/call-site-routing.ts +42 -3
  475. package/src/providers/gemini/client.ts +18 -2
  476. package/src/providers/managed-proxy/context.ts +0 -5
  477. package/src/providers/model-catalog.ts +105 -19
  478. package/src/providers/openai/chat-completions-provider.ts +6 -0
  479. package/src/providers/openai/responses-provider.ts +7 -1
  480. package/src/providers/provider-send-message.ts +45 -2
  481. package/src/providers/ratelimit.ts +7 -2
  482. package/src/providers/registry.ts +14 -9
  483. package/src/providers/retry.ts +96 -8
  484. package/src/providers/types.ts +13 -0
  485. package/src/providers/usage-tracking.ts +96 -0
  486. package/src/runtime/AGENTS.md +10 -6
  487. package/src/runtime/__tests__/agent-wake.test.ts +89 -0
  488. package/src/runtime/agent-wake.ts +39 -2
  489. package/src/runtime/assistant-event-hub.ts +541 -45
  490. package/src/runtime/assistant-event.ts +1 -6
  491. package/src/runtime/auth/context.ts +0 -9
  492. package/src/runtime/auth/middleware.ts +1 -1
  493. package/src/runtime/auth/route-policy.ts +11 -9
  494. package/src/runtime/auth/token-service.ts +0 -11
  495. package/src/runtime/channel-approvals.ts +6 -2
  496. package/src/runtime/channel-verification-service.ts +3 -5
  497. package/src/runtime/http-errors.ts +0 -34
  498. package/src/runtime/http-router.ts +6 -3
  499. package/src/runtime/http-server.ts +22 -82
  500. package/src/runtime/http-types.ts +5 -0
  501. package/src/runtime/interactive-ui.ts +0 -1
  502. package/src/runtime/middleware/auth.ts +0 -20
  503. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +112 -0
  504. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +11 -4
  505. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +253 -0
  506. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +19 -6
  507. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +71 -27
  508. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +41 -2
  509. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +143 -79
  510. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +143 -23
  511. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +2 -2
  512. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +371 -0
  513. package/src/runtime/migrations/migration-transport.ts +46 -13
  514. package/src/runtime/migrations/migration-wizard.ts +2 -2
  515. package/src/runtime/migrations/origin-mode.ts +40 -0
  516. package/src/runtime/migrations/vbundle-builder.ts +133 -79
  517. package/src/runtime/migrations/vbundle-import-analyzer.ts +9 -7
  518. package/src/runtime/migrations/vbundle-importer.ts +7 -7
  519. package/src/runtime/migrations/vbundle-metadata-merge.ts +1 -1
  520. package/src/runtime/migrations/vbundle-streaming-importer.ts +3 -3
  521. package/src/runtime/migrations/vbundle-streaming-validator.ts +48 -26
  522. package/src/runtime/migrations/vbundle-validator.ts +214 -41
  523. package/src/runtime/pending-interactions.ts +13 -4
  524. package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -1
  525. package/src/runtime/routes/__tests__/backup-routes.test.ts +28 -19
  526. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +235 -0
  527. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +58 -0
  528. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +54 -0
  529. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -6
  530. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +7 -7
  531. package/src/runtime/routes/acp-routes.test.ts +0 -3
  532. package/src/runtime/routes/acp-routes.ts +3 -7
  533. package/src/runtime/routes/app-management-routes.ts +18 -9
  534. package/src/runtime/routes/approval-routes.ts +55 -14
  535. package/src/runtime/routes/avatar-routes.ts +3 -5
  536. package/src/runtime/routes/browser-routes.ts +1 -15
  537. package/src/runtime/routes/channel-guardian-routes.ts +1 -5
  538. package/src/runtime/routes/channel-readiness-routes.ts +3 -7
  539. package/src/runtime/routes/channel-route-shared.ts +2 -28
  540. package/src/runtime/routes/client-routes.ts +45 -12
  541. package/src/runtime/routes/consolidation-routes.ts +115 -0
  542. package/src/runtime/routes/conversation-list-routes.ts +12 -29
  543. package/src/runtime/routes/conversation-management-routes.ts +14 -51
  544. package/src/runtime/routes/conversation-query-routes.ts +120 -8
  545. package/src/runtime/routes/conversation-routes.ts +44 -528
  546. package/src/runtime/routes/conversation-starter-routes.ts +19 -40
  547. package/src/runtime/routes/documents-routes.ts +53 -18
  548. package/src/runtime/routes/events-routes.ts +59 -91
  549. package/src/runtime/routes/filing-routes.ts +18 -1
  550. package/src/runtime/routes/guardian-action-routes.ts +4 -9
  551. package/src/runtime/routes/host-bash-routes.ts +3 -2
  552. package/src/runtime/routes/host-browser-routes.ts +9 -33
  553. package/src/runtime/routes/host-cu-routes.ts +6 -1
  554. package/src/runtime/routes/host-file-routes.ts +3 -2
  555. package/src/runtime/routes/host-transfer-routes.ts +11 -15
  556. package/src/runtime/routes/identity-routes.ts +78 -6
  557. package/src/runtime/routes/inbound-message-handler.ts +580 -137
  558. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -88
  559. package/src/runtime/routes/inbound-stages/background-dispatch.ts +3 -0
  560. package/src/runtime/routes/index.ts +4 -0
  561. package/src/runtime/routes/integrations/slack/channel.ts +0 -24
  562. package/src/runtime/routes/llm-call-sites-routes.ts +22 -0
  563. package/src/runtime/routes/memory-v2-routes.ts +10 -15
  564. package/src/runtime/routes/migration-routes.ts +188 -31
  565. package/src/runtime/routes/playground/guard.ts +1 -1
  566. package/src/runtime/routes/playground/index.ts +0 -2
  567. package/src/runtime/routes/recording-routes.ts +4 -24
  568. package/src/runtime/routes/rename-conversation-routes.ts +2 -6
  569. package/src/runtime/routes/schedule-routes.ts +3 -6
  570. package/src/runtime/routes/secret-routes.ts +87 -18
  571. package/src/runtime/routes/settings-routes.ts +29 -28
  572. package/src/runtime/routes/skills-routes.ts +12 -31
  573. package/src/runtime/routes/suggest-trust-rule-routes.ts +32 -1
  574. package/src/runtime/routes/task-routes.ts +6 -6
  575. package/src/runtime/routes/trust-rules-routes.ts +3 -94
  576. package/src/runtime/routes/types.ts +4 -4
  577. package/src/runtime/routes/upgrade-broadcast-routes.ts +3 -10
  578. package/src/runtime/routes/usage-routes.ts +87 -10
  579. package/src/runtime/routes/user-routes.ts +17 -31
  580. package/src/runtime/routes/work-items-routes.ts +1 -4
  581. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -2
  582. package/src/runtime/services/analyze-conversation.ts +7 -17
  583. package/src/runtime/services/conversation-serializer.ts +2 -4
  584. package/src/runtime/verification-outbound-actions.ts +1 -1
  585. package/src/runtime/verification-rate-limiter.ts +1 -1
  586. package/src/schedule/schedule-store.ts +0 -16
  587. package/src/security/secret-scanner.ts +14 -547
  588. package/src/security/secure-keys.ts +31 -11
  589. package/src/security/token-manager.ts +7 -3
  590. package/src/signals/cancel.ts +16 -25
  591. package/src/signals/conversation-undo.ts +2 -27
  592. package/src/signals/emit-event.ts +1 -2
  593. package/src/signals/user-message.ts +108 -22
  594. package/src/skills/catalog-install.ts +1 -0
  595. package/src/skills/clawhub.ts +2 -2
  596. package/src/skills/inline-command-runner.ts +1 -7
  597. package/src/subagent/manager.ts +67 -84
  598. package/src/tasks/task-store.ts +1 -28
  599. package/src/telemetry/types.ts +6 -0
  600. package/src/telemetry/usage-telemetry-reporter.test.ts +38 -15
  601. package/src/telemetry/usage-telemetry-reporter.ts +3 -5
  602. package/src/tools/acp/spawn.test.ts +1 -2
  603. package/src/tools/acp/steer.test.ts +1 -2
  604. package/src/tools/browser/__tests__/browser-status.test.ts +44 -127
  605. package/src/tools/browser/browser-execution.ts +31 -147
  606. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +92 -68
  607. package/src/tools/browser/cdp-client/factory.ts +48 -76
  608. package/src/tools/browser/cdp-client/index.ts +1 -14
  609. package/src/tools/executor.ts +44 -31
  610. package/src/tools/host-filesystem/edit.ts +3 -2
  611. package/src/tools/host-filesystem/read.ts +3 -2
  612. package/src/tools/host-filesystem/transfer.test.ts +45 -42
  613. package/src/tools/host-filesystem/transfer.ts +4 -3
  614. package/src/tools/host-filesystem/write.ts +3 -2
  615. package/src/tools/host-terminal/host-shell.ts +4 -3
  616. package/src/tools/network/script-proxy/index.ts +1 -10
  617. package/src/tools/permission-checker.ts +66 -1
  618. package/src/tools/skills/sandbox-runner.ts +1 -6
  619. package/src/tools/skills/skill-tool-factory.ts +32 -0
  620. package/src/tools/terminal/safe-env.ts +1 -0
  621. package/src/tools/terminal/shell.ts +2 -78
  622. package/src/tools/types.ts +12 -39
  623. package/src/tts/__tests__/provider-catalog.test.ts +2 -2
  624. package/src/tts/provider-catalog.ts +1 -1
  625. package/src/usage/actors.ts +2 -1
  626. package/src/usage/attribution.ts +185 -0
  627. package/src/usage/pricing.ts +166 -0
  628. package/src/usage/types.ts +14 -0
  629. package/src/util/json.ts +13 -0
  630. package/src/util/logger.ts +3 -3
  631. package/src/util/pricing.ts +50 -3
  632. package/src/work-items/work-item-runner.ts +15 -42
  633. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +4 -3
  634. package/src/workspace/migrations/052-seed-default-inference-profiles.ts +3 -3
  635. package/src/workspace/migrations/060-memory-v2-init.ts +2 -18
  636. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +59 -0
  637. package/src/workspace/migrations/062-drop-memory-v2-edges-json.ts +27 -0
  638. package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +70 -0
  639. package/src/workspace/migrations/064-unwind-main-agent-opus-seed.ts +64 -0
  640. package/src/workspace/migrations/registry.ts +8 -0
  641. package/src/workspace/provider-commit-message-generator.ts +3 -3
  642. package/src/__tests__/sandbox-diagnostics.test.ts +0 -138
  643. package/src/__tests__/sandbox-host-parity.test.ts +0 -1024
  644. package/src/__tests__/secret-detection-handler.test.ts +0 -67
  645. package/src/__tests__/secret-scanner-executor.test.ts +0 -450
  646. package/src/__tests__/tcc-sandbox-deny.test.ts +0 -198
  647. package/src/__tests__/terminal-sandbox.test.ts +0 -374
  648. package/src/__tests__/tool-notification-listener.test.ts +0 -65
  649. package/src/context/__tests__/microcompact.test.ts +0 -805
  650. package/src/context/microcompact.ts +0 -443
  651. package/src/daemon/handlers/slack-channel-oauth-install.ts +0 -197
  652. package/src/events/tool-notification-listener.ts +0 -17
  653. package/src/ipc/routes/__tests__/memory-v2-validate.test.ts +0 -219
  654. package/src/memory/v2/__tests__/edges.test.ts +0 -435
  655. package/src/memory/v2/edges.ts +0 -217
  656. package/src/prompts/__tests__/system-prompt-memory-v2.test.ts +0 -197
  657. package/src/runtime/__tests__/chrome-extension-registry.test.ts +0 -518
  658. package/src/runtime/__tests__/client-registry.test.ts +0 -271
  659. package/src/runtime/chrome-extension-registry.ts +0 -368
  660. package/src/runtime/client-registry.ts +0 -254
  661. package/src/runtime/routes/inbound-stages/verification-intercept.ts +0 -329
  662. package/src/tools/secret-detection-handler.ts +0 -269
  663. package/src/tools/terminal/backends/native.ts +0 -327
  664. package/src/tools/terminal/backends/types.ts +0 -37
  665. package/src/tools/terminal/sandbox-diagnostics.ts +0 -87
  666. package/src/tools/terminal/sandbox.ts +0 -40
@@ -1,805 +0,0 @@
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
- });