@vellumai/assistant 0.4.48 → 0.4.50

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 (423) hide show
  1. package/ARCHITECTURE.md +26 -35
  2. package/README.md +5 -26
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/architecture/memory.md +180 -119
  6. package/docs/runbook-trusted-contacts.md +3 -8
  7. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  8. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  9. package/package.json +2 -2
  10. package/src/__tests__/actor-token-service.test.ts +0 -1
  11. package/src/__tests__/agent-loop.test.ts +3 -1
  12. package/src/__tests__/anthropic-provider.test.ts +249 -2
  13. package/src/__tests__/approval-cascade.test.ts +796 -0
  14. package/src/__tests__/approval-primitive.test.ts +0 -1
  15. package/src/__tests__/approval-routes-http.test.ts +4 -0
  16. package/src/__tests__/assistant-attachments.test.ts +12 -34
  17. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  18. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  19. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  22. package/src/__tests__/channel-guardian.test.ts +0 -2
  23. package/src/__tests__/channel-readiness-routes.test.ts +15 -6
  24. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  25. package/src/__tests__/checker.test.ts +13 -20
  26. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  27. package/src/__tests__/computer-use-tools.test.ts +2 -19
  28. package/src/__tests__/config-schema.test.ts +1 -68
  29. package/src/__tests__/config-watcher.test.ts +0 -1
  30. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  31. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  32. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  33. package/src/__tests__/context-token-estimator.test.ts +196 -13
  34. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  35. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  36. package/src/__tests__/conversation-routes-guardian-reply.test.ts +152 -0
  37. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  38. package/src/__tests__/credential-metadata-store.test.ts +64 -73
  39. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  40. package/src/__tests__/credential-security-invariants.test.ts +13 -7
  41. package/src/__tests__/credential-vault-unit.test.ts +284 -49
  42. package/src/__tests__/credential-vault.test.ts +150 -16
  43. package/src/__tests__/credentials-cli.test.ts +71 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  45. package/src/__tests__/date-context.test.ts +93 -77
  46. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  47. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  48. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  49. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  50. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  51. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  52. package/src/__tests__/guardian-routing-invariants.test.ts +93 -1
  53. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  54. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  55. package/src/__tests__/heartbeat-service.test.ts +0 -1
  56. package/src/__tests__/history-repair.test.ts +245 -0
  57. package/src/__tests__/host-cu-proxy.test.ts +791 -0
  58. package/src/__tests__/host-shell-tool.test.ts +27 -15
  59. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  60. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  61. package/src/__tests__/integration-status.test.ts +32 -51
  62. package/src/__tests__/intent-routing.test.ts +0 -1
  63. package/src/__tests__/invite-redemption-service.test.ts +65 -1
  64. package/src/__tests__/invite-routes-http.test.ts +10 -9
  65. package/src/__tests__/keychain-broker-client.test.ts +14 -46
  66. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  67. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  68. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  69. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  70. package/src/__tests__/memory-regressions.test.ts +477 -2841
  71. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  72. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  73. package/src/__tests__/mime-builder.test.ts +28 -0
  74. package/src/__tests__/native-web-search.test.ts +1 -0
  75. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  76. package/src/__tests__/oauth-cli.test.ts +941 -15
  77. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  78. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  79. package/src/__tests__/oauth-store.test.ts +870 -0
  80. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  81. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  82. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  83. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  84. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  85. package/src/__tests__/recording-handler.test.ts +3 -4
  86. package/src/__tests__/registry.test.ts +2 -3
  87. package/src/__tests__/relay-server.test.ts +46 -1
  88. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  89. package/src/__tests__/schedule-store.test.ts +0 -1
  90. package/src/__tests__/schedule-tools.test.ts +32 -0
  91. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  92. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  93. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  94. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  95. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  96. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  97. package/src/__tests__/secure-keys.test.ts +7 -2
  98. package/src/__tests__/send-endpoint-busy.test.ts +24 -6
  99. package/src/__tests__/sequence-store.test.ts +0 -1
  100. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  101. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  102. package/src/__tests__/session-agent-loop.test.ts +19 -15
  103. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  104. package/src/__tests__/session-error.test.ts +124 -2
  105. package/src/__tests__/session-history-web-search.test.ts +918 -0
  106. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  107. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  108. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  109. package/src/__tests__/session-queue.test.ts +37 -27
  110. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  111. package/src/__tests__/session-slash-known.test.ts +1 -15
  112. package/src/__tests__/session-slash-queue.test.ts +1 -15
  113. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  114. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  115. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  116. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  117. package/src/__tests__/skill-include-graph.test.ts +66 -0
  118. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  119. package/src/__tests__/skill-load-tool.test.ts +149 -1
  120. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  121. package/src/__tests__/skills-install-extract.test.ts +93 -0
  122. package/src/__tests__/skills-uninstall.test.ts +1 -1
  123. package/src/__tests__/skills.test.ts +3 -3
  124. package/src/__tests__/skillssh-registry.test.ts +451 -0
  125. package/src/__tests__/slack-channel-config.test.ts +67 -3
  126. package/src/__tests__/slack-share-routes.test.ts +17 -19
  127. package/src/__tests__/system-prompt.test.ts +0 -1
  128. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  129. package/src/__tests__/terminal-tools.test.ts +4 -3
  130. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  131. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  132. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  133. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  134. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  135. package/src/__tests__/tool-executor.test.ts +0 -1
  136. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  137. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  138. package/src/__tests__/trust-store.test.ts +7 -13
  139. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  140. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  141. package/src/__tests__/twilio-routes.test.ts +0 -16
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  143. package/src/__tests__/voice-invite-redemption.test.ts +32 -1
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  145. package/src/agent/ax-tree-compaction.test.ts +286 -0
  146. package/src/agent/loop.ts +104 -131
  147. package/src/approvals/AGENTS.md +1 -1
  148. package/src/approvals/guardian-request-resolvers.ts +14 -2
  149. package/src/bundler/compiler-tools.ts +66 -2
  150. package/src/calls/call-domain.ts +133 -6
  151. package/src/calls/call-store.ts +6 -0
  152. package/src/calls/relay-server.ts +52 -18
  153. package/src/calls/relay-setup-router.ts +17 -1
  154. package/src/calls/twilio-config.ts +3 -8
  155. package/src/calls/twilio-routes.ts +1 -2
  156. package/src/calls/types.ts +3 -1
  157. package/src/calls/voice-ingress-preflight.ts +1 -1
  158. package/src/cli/commands/browser-relay.ts +18 -12
  159. package/src/cli/commands/completions.ts +0 -3
  160. package/src/cli/commands/credentials.ts +101 -15
  161. package/src/cli/commands/doctor.ts +4 -3
  162. package/src/cli/commands/mcp.ts +46 -59
  163. package/src/cli/commands/memory.ts +16 -165
  164. package/src/cli/commands/oauth/apps.ts +284 -0
  165. package/src/cli/commands/oauth/connections.ts +633 -0
  166. package/src/cli/commands/oauth/index.ts +52 -0
  167. package/src/cli/commands/oauth/providers.ts +256 -0
  168. package/src/cli/commands/sessions.ts +5 -2
  169. package/src/cli/commands/skills.ts +177 -339
  170. package/src/cli/http-client.ts +0 -20
  171. package/src/cli/main-screen.tsx +2 -2
  172. package/src/cli/program.ts +6 -11
  173. package/src/cli/reference.ts +1 -3
  174. package/src/cli.ts +4 -10
  175. package/src/config/assistant-feature-flags.ts +0 -3
  176. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  177. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  178. package/src/config/bundled-skills/computer-use/TOOLS.json +23 -5
  179. package/src/config/bundled-skills/computer-use/tools/{computer-use-request-control.ts → computer-use-observe.ts} +1 -5
  180. package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
  181. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
  182. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  183. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  184. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  185. package/src/config/bundled-tool-registry.ts +2 -5
  186. package/src/config/env-registry.ts +14 -83
  187. package/src/config/env.ts +11 -50
  188. package/src/config/feature-flag-registry.json +16 -16
  189. package/src/config/loader.ts +0 -6
  190. package/src/config/schema.ts +4 -13
  191. package/src/config/schemas/memory-lifecycle.ts +0 -9
  192. package/src/config/schemas/memory-processing.ts +0 -180
  193. package/src/config/schemas/memory-retrieval.ts +32 -104
  194. package/src/config/schemas/memory.ts +0 -10
  195. package/src/config/skills.ts +21 -2
  196. package/src/config/types.ts +0 -4
  197. package/src/context/image-dimensions.ts +229 -0
  198. package/src/context/token-estimator.ts +75 -12
  199. package/src/context/window-manager.ts +53 -11
  200. package/src/daemon/assistant-attachments.ts +1 -13
  201. package/src/daemon/config-watcher.ts +61 -3
  202. package/src/daemon/daemon-control.ts +1 -1
  203. package/src/daemon/date-context.ts +114 -31
  204. package/src/daemon/handlers/config-ingress.ts +8 -33
  205. package/src/daemon/handlers/config-slack-channel.ts +49 -46
  206. package/src/daemon/handlers/config-telegram.ts +32 -16
  207. package/src/daemon/handlers/sessions.ts +27 -36
  208. package/src/daemon/handlers/shared.ts +0 -130
  209. package/src/daemon/handlers/skills.ts +20 -1
  210. package/src/daemon/history-repair.ts +72 -8
  211. package/src/daemon/host-cu-proxy.ts +430 -0
  212. package/src/daemon/lifecycle.ts +67 -71
  213. package/src/daemon/mcp-reload-service.ts +2 -2
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/computer-use.ts +1 -129
  216. package/src/daemon/message-types/host-cu.ts +19 -0
  217. package/src/daemon/message-types/memory.ts +4 -16
  218. package/src/daemon/message-types/messages.ts +4 -0
  219. package/src/daemon/message-types/sessions.ts +4 -0
  220. package/src/daemon/server.ts +25 -21
  221. package/src/daemon/session-agent-loop-handlers.ts +40 -0
  222. package/src/daemon/session-agent-loop.ts +334 -48
  223. package/src/daemon/session-attachments.ts +1 -2
  224. package/src/daemon/session-error.ts +89 -6
  225. package/src/daemon/session-history.ts +17 -7
  226. package/src/daemon/session-media-retry.ts +6 -2
  227. package/src/daemon/session-memory.ts +69 -149
  228. package/src/daemon/session-process.ts +10 -1
  229. package/src/daemon/session-runtime-assembly.ts +49 -19
  230. package/src/daemon/session-slash.ts +1 -1
  231. package/src/daemon/session-surfaces.ts +43 -28
  232. package/src/daemon/session-tool-setup.ts +9 -10
  233. package/src/daemon/session.ts +150 -17
  234. package/src/daemon/tool-side-effects.ts +2 -8
  235. package/src/daemon/watch-handler.ts +2 -2
  236. package/src/events/tool-metrics-listener.ts +2 -2
  237. package/src/hooks/manager.ts +1 -4
  238. package/src/inbound/public-ingress-urls.ts +7 -7
  239. package/src/instrument.ts +61 -1
  240. package/src/logfire.ts +16 -5
  241. package/src/memory/admin.ts +2 -191
  242. package/src/memory/canonical-guardian-store.ts +38 -2
  243. package/src/memory/conversation-crud.ts +0 -33
  244. package/src/memory/conversation-key-store.ts +21 -0
  245. package/src/memory/conversation-queries.ts +22 -3
  246. package/src/memory/db-init.ts +32 -0
  247. package/src/memory/embedding-backend.ts +84 -8
  248. package/src/memory/embedding-types.ts +9 -1
  249. package/src/memory/indexer.ts +7 -46
  250. package/src/memory/items-extractor.ts +274 -76
  251. package/src/memory/job-handlers/backfill.ts +2 -127
  252. package/src/memory/job-handlers/cleanup.ts +2 -16
  253. package/src/memory/job-handlers/extraction.ts +2 -138
  254. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  255. package/src/memory/job-handlers/summarization.ts +3 -148
  256. package/src/memory/job-utils.ts +21 -59
  257. package/src/memory/jobs-store.ts +1 -159
  258. package/src/memory/jobs-worker.ts +9 -52
  259. package/src/memory/migrations/104-core-indexes.ts +3 -3
  260. package/src/memory/migrations/149-oauth-tables.ts +62 -0
  261. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  262. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  263. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  264. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  265. package/src/memory/migrations/154-drop-fts.ts +20 -0
  266. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  267. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  268. package/src/memory/migrations/index.ts +8 -0
  269. package/src/memory/qdrant-client.ts +148 -51
  270. package/src/memory/raw-query.ts +1 -1
  271. package/src/memory/retriever.test.ts +294 -273
  272. package/src/memory/retriever.ts +421 -645
  273. package/src/memory/schema/calls.ts +2 -0
  274. package/src/memory/schema/index.ts +1 -0
  275. package/src/memory/schema/memory-core.ts +3 -48
  276. package/src/memory/schema/oauth.ts +67 -0
  277. package/src/memory/search/formatting.ts +263 -176
  278. package/src/memory/search/lexical.ts +1 -254
  279. package/src/memory/search/ranking.ts +0 -455
  280. package/src/memory/search/semantic.ts +100 -14
  281. package/src/memory/search/staleness.ts +47 -0
  282. package/src/memory/search/tier-classifier.ts +21 -0
  283. package/src/memory/search/types.ts +15 -77
  284. package/src/memory/task-memory-cleanup.ts +4 -6
  285. package/src/messaging/provider.ts +4 -4
  286. package/src/messaging/providers/gmail/client.ts +82 -2
  287. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  288. package/src/messaging/providers/gmail/people-client.ts +10 -10
  289. package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
  290. package/src/messaging/providers/whatsapp/adapter.ts +11 -8
  291. package/src/messaging/registry.ts +2 -32
  292. package/src/notifications/copy-composer.ts +0 -5
  293. package/src/notifications/signal.ts +4 -5
  294. package/src/oauth/byo-connection.test.ts +133 -25
  295. package/src/oauth/byo-connection.ts +22 -6
  296. package/src/oauth/connect-orchestrator.ts +113 -57
  297. package/src/oauth/connect-types.ts +17 -23
  298. package/src/oauth/connection-resolver.ts +35 -11
  299. package/src/oauth/connection.ts +1 -1
  300. package/src/oauth/manual-token-connection.ts +104 -0
  301. package/src/oauth/oauth-store.ts +582 -0
  302. package/src/oauth/platform-connection.test.ts +29 -0
  303. package/src/oauth/platform-connection.ts +6 -5
  304. package/src/oauth/provider-behaviors.ts +124 -0
  305. package/src/oauth/scope-policy.ts +9 -2
  306. package/src/oauth/seed-providers.ts +167 -0
  307. package/src/oauth/token-persistence.ts +81 -77
  308. package/src/permissions/checker.ts +3 -3
  309. package/src/permissions/defaults.ts +1 -1
  310. package/src/permissions/prompter.ts +10 -1
  311. package/src/permissions/trust-store.ts +36 -1
  312. package/src/playbooks/playbook-compiler.ts +1 -1
  313. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  314. package/src/prompts/system-prompt.ts +46 -42
  315. package/src/providers/anthropic/client.ts +59 -20
  316. package/src/providers/retry.ts +1 -27
  317. package/src/providers/types.ts +7 -1
  318. package/src/runtime/AGENTS.md +9 -0
  319. package/src/runtime/auth/route-policy.ts +6 -6
  320. package/src/runtime/channel-reply-delivery.ts +0 -40
  321. package/src/runtime/gateway-client.ts +0 -7
  322. package/src/runtime/guardian-reply-router.ts +24 -22
  323. package/src/runtime/http-server.ts +10 -8
  324. package/src/runtime/http-types.ts +2 -2
  325. package/src/runtime/invite-redemption-service.ts +19 -1
  326. package/src/runtime/invite-service.ts +25 -0
  327. package/src/runtime/middleware/twilio-validation.ts +1 -11
  328. package/src/runtime/pending-interactions.ts +14 -12
  329. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  330. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  331. package/src/runtime/routes/conversation-routes.ts +81 -19
  332. package/src/runtime/routes/events-routes.ts +21 -11
  333. package/src/runtime/routes/host-cu-routes.ts +97 -0
  334. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  335. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  336. package/src/runtime/routes/integrations/slack/share.ts +6 -7
  337. package/src/runtime/routes/log-export-routes.ts +126 -8
  338. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  339. package/src/runtime/routes/memory-item-routes.ts +503 -0
  340. package/src/runtime/routes/session-management-routes.ts +3 -3
  341. package/src/runtime/routes/settings-routes.ts +55 -48
  342. package/src/runtime/routes/surface-action-routes.ts +1 -1
  343. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  344. package/src/runtime/routes/watch-routes.ts +128 -0
  345. package/src/runtime/routes/workspace-routes.ts +2 -1
  346. package/src/schedule/integration-status.ts +10 -9
  347. package/src/security/credential-key.ts +0 -156
  348. package/src/security/keychain-broker-client.ts +22 -10
  349. package/src/security/oauth2.ts +1 -1
  350. package/src/security/secure-keys.ts +25 -3
  351. package/src/security/token-manager.ts +137 -64
  352. package/src/skills/catalog-install.ts +414 -0
  353. package/src/skills/include-graph.ts +32 -0
  354. package/src/skills/skillssh-registry.ts +503 -0
  355. package/src/telegram/bot-username.ts +2 -3
  356. package/src/tools/assets/search.ts +5 -1
  357. package/src/tools/browser/network-recorder.ts +1 -1
  358. package/src/tools/browser/network-recording-types.ts +1 -1
  359. package/src/tools/computer-use/definitions.ts +36 -11
  360. package/src/tools/computer-use/registry.ts +5 -6
  361. package/src/tools/credentials/broker.ts +1 -2
  362. package/src/tools/credentials/metadata-store.ts +17 -121
  363. package/src/tools/credentials/vault.ts +92 -167
  364. package/src/tools/memory/definitions.ts +4 -13
  365. package/src/tools/memory/handlers.test.ts +83 -103
  366. package/src/tools/memory/handlers.ts +50 -85
  367. package/src/tools/registry.ts +2 -7
  368. package/src/tools/schedule/create.ts +8 -1
  369. package/src/tools/schedule/update.ts +8 -1
  370. package/src/tools/skills/load.ts +85 -3
  371. package/src/tools/watch/watch-state.ts +0 -12
  372. package/src/util/logger.ts +7 -41
  373. package/src/util/platform.ts +9 -28
  374. package/src/watcher/providers/google-calendar.ts +2 -1
  375. package/src/__tests__/clarification-resolver.test.ts +0 -193
  376. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  377. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  378. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  379. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  380. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  381. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  382. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  383. package/src/__tests__/conflict-policy.test.ts +0 -269
  384. package/src/__tests__/conflict-store.test.ts +0 -372
  385. package/src/__tests__/contradiction-checker.test.ts +0 -361
  386. package/src/__tests__/entity-extractor.test.ts +0 -211
  387. package/src/__tests__/entity-search.test.ts +0 -1117
  388. package/src/__tests__/profile-compiler.test.ts +0 -392
  389. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  390. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  391. package/src/__tests__/session-profile-injection.test.ts +0 -557
  392. package/src/cli/commands/dev.ts +0 -129
  393. package/src/cli/commands/map.ts +0 -391
  394. package/src/cli/commands/oauth.ts +0 -77
  395. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  396. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  397. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  398. package/src/daemon/computer-use-session.ts +0 -1026
  399. package/src/daemon/ride-shotgun-handler.ts +0 -569
  400. package/src/daemon/session-conflict-gate.ts +0 -167
  401. package/src/daemon/session-dynamic-profile.ts +0 -77
  402. package/src/memory/clarification-resolver.ts +0 -417
  403. package/src/memory/conflict-intent.ts +0 -205
  404. package/src/memory/conflict-policy.ts +0 -127
  405. package/src/memory/conflict-store.ts +0 -410
  406. package/src/memory/contradiction-checker.ts +0 -508
  407. package/src/memory/entity-extractor.ts +0 -535
  408. package/src/memory/format-recall.ts +0 -47
  409. package/src/memory/fts-reconciler.ts +0 -165
  410. package/src/memory/job-handlers/conflict.ts +0 -200
  411. package/src/memory/profile-compiler.ts +0 -195
  412. package/src/memory/recall-cache.ts +0 -117
  413. package/src/memory/search/entity.ts +0 -535
  414. package/src/memory/search/query-expansion.test.ts +0 -70
  415. package/src/memory/search/query-expansion.ts +0 -118
  416. package/src/oauth/provider-base-urls.ts +0 -21
  417. package/src/oauth/provider-profiles.ts +0 -192
  418. package/src/prompts/computer-use-prompt.ts +0 -98
  419. package/src/runtime/routes/computer-use-routes.ts +0 -641
  420. package/src/runtime/routes/mcp-routes.ts +0 -20
  421. package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
  422. package/src/runtime/telegram-streaming-delivery.ts +0 -393
  423. package/src/tools/computer-use/request-computer-control.ts +0 -56
@@ -0,0 +1,286 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import type { Message } from "../providers/types.js";
4
+ import { compactAxTreeHistory, escapeAxTreeContent } from "./loop.js";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Helpers
8
+ // ---------------------------------------------------------------------------
9
+
10
+ /** Build a user message with a single tool_result containing an AX tree. */
11
+ function axTreeToolResult(id: string, axContent: string): Message {
12
+ return {
13
+ role: "user",
14
+ content: [
15
+ {
16
+ type: "tool_result",
17
+ tool_use_id: id,
18
+ content: `Some preamble\n<ax-tree>\n${axContent}\n</ax-tree>`,
19
+ is_error: false,
20
+ },
21
+ ],
22
+ };
23
+ }
24
+
25
+ /** Build an assistant message (no tool use). */
26
+ function assistantText(text: string): Message {
27
+ return {
28
+ role: "assistant",
29
+ content: [{ type: "text", text }],
30
+ };
31
+ }
32
+
33
+ /** Build a user message without AX tree content. */
34
+ function userText(text: string): Message {
35
+ return {
36
+ role: "user",
37
+ content: [{ type: "text", text }],
38
+ };
39
+ }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // compactAxTreeHistory
43
+ // ---------------------------------------------------------------------------
44
+
45
+ describe("compactAxTreeHistory", () => {
46
+ test("returns messages unchanged when fewer than MAX_AX_TREES_IN_HISTORY AX trees", () => {
47
+ const messages: Message[] = [
48
+ axTreeToolResult("t1", "tree-1"),
49
+ assistantText("ok"),
50
+ axTreeToolResult("t2", "tree-2"),
51
+ ];
52
+ const result = compactAxTreeHistory(messages);
53
+ expect(result).toBe(messages); // same reference — no copy
54
+ });
55
+
56
+ test("returns messages unchanged when exactly MAX_AX_TREES_IN_HISTORY AX trees", () => {
57
+ const messages: Message[] = [
58
+ axTreeToolResult("t1", "tree-1"),
59
+ assistantText("ok"),
60
+ axTreeToolResult("t2", "tree-2"),
61
+ ];
62
+ const result = compactAxTreeHistory(messages);
63
+ expect(result).toBe(messages);
64
+ });
65
+
66
+ test("strips oldest AX trees, keeps only last 2 from 5", () => {
67
+ const messages: Message[] = [
68
+ axTreeToolResult("t1", "tree-1"),
69
+ assistantText("ok"),
70
+ axTreeToolResult("t2", "tree-2"),
71
+ assistantText("ok"),
72
+ axTreeToolResult("t3", "tree-3"),
73
+ assistantText("ok"),
74
+ axTreeToolResult("t4", "tree-4"),
75
+ assistantText("ok"),
76
+ axTreeToolResult("t5", "tree-5"),
77
+ ];
78
+
79
+ const result = compactAxTreeHistory(messages);
80
+
81
+ // Messages at indices 0, 2, 4 should have AX trees stripped (t1, t2, t3)
82
+ for (const idx of [0, 2, 4]) {
83
+ const block = result[idx].content[0];
84
+ expect(block.type).toBe("tool_result");
85
+ if (block.type === "tool_result") {
86
+ expect(block.content).not.toContain("<ax-tree>");
87
+ expect(block.content).toContain("<ax_tree_omitted />");
88
+ }
89
+ }
90
+
91
+ // Messages at indices 6, 8 should still have AX trees (t4, t5)
92
+ for (const idx of [6, 8]) {
93
+ const block = result[idx].content[0];
94
+ expect(block.type).toBe("tool_result");
95
+ if (block.type === "tool_result") {
96
+ expect(block.content).toContain("<ax-tree>");
97
+ expect(block.content).not.toContain("<ax_tree_omitted />");
98
+ }
99
+ }
100
+ });
101
+
102
+ test("does not modify assistant messages", () => {
103
+ const messages: Message[] = [
104
+ axTreeToolResult("t1", "tree-1"),
105
+ assistantText("ok"),
106
+ axTreeToolResult("t2", "tree-2"),
107
+ assistantText("response with <ax-tree>fake</ax-tree>"),
108
+ axTreeToolResult("t3", "tree-3"),
109
+ ];
110
+
111
+ const result = compactAxTreeHistory(messages);
112
+
113
+ // Assistant message should be unchanged
114
+ const assistantMsg = result[3];
115
+ expect(assistantMsg.content[0].type).toBe("text");
116
+ if (assistantMsg.content[0].type === "text") {
117
+ expect(assistantMsg.content[0].text).toContain("<ax-tree>");
118
+ }
119
+ });
120
+
121
+ test("does not modify user messages without tool_result blocks", () => {
122
+ const messages: Message[] = [
123
+ axTreeToolResult("t1", "tree-1"),
124
+ assistantText("ok"),
125
+ axTreeToolResult("t2", "tree-2"),
126
+ assistantText("ok"),
127
+ userText("Please help"),
128
+ axTreeToolResult("t3", "tree-3"),
129
+ ];
130
+
131
+ const result = compactAxTreeHistory(messages);
132
+
133
+ // The plain user text message should be untouched
134
+ expect(result[4]).toBe(messages[4]);
135
+ });
136
+
137
+ test("preserves non-AX-tree tool_result blocks in stripped messages", () => {
138
+ const messages: Message[] = [
139
+ {
140
+ role: "user",
141
+ content: [
142
+ {
143
+ type: "tool_result",
144
+ tool_use_id: "t1",
145
+ content: "normal result without ax tree",
146
+ is_error: false,
147
+ },
148
+ {
149
+ type: "tool_result",
150
+ tool_use_id: "t1-ax",
151
+ content: "<ax-tree>\ntree-1\n</ax-tree>",
152
+ is_error: false,
153
+ },
154
+ ],
155
+ },
156
+ assistantText("ok"),
157
+ axTreeToolResult("t2", "tree-2"),
158
+ assistantText("ok"),
159
+ axTreeToolResult("t3", "tree-3"),
160
+ ];
161
+
162
+ const result = compactAxTreeHistory(messages);
163
+
164
+ // First message should have the AX tree stripped but normal result preserved
165
+ const firstMsg = result[0];
166
+ const normalBlock = firstMsg.content[0];
167
+ expect(normalBlock.type).toBe("tool_result");
168
+ if (normalBlock.type === "tool_result") {
169
+ expect(normalBlock.content).toBe("normal result without ax tree");
170
+ }
171
+
172
+ const axBlock = firstMsg.content[1];
173
+ expect(axBlock.type).toBe("tool_result");
174
+ if (axBlock.type === "tool_result") {
175
+ expect(axBlock.content).toContain("<ax_tree_omitted />");
176
+ expect(axBlock.content).not.toContain("<ax-tree>");
177
+ }
178
+ });
179
+
180
+ test("returns empty array for empty input", () => {
181
+ const result = compactAxTreeHistory([]);
182
+ expect(result).toEqual([]);
183
+ });
184
+
185
+ test("counts AX trees per block, not per message", () => {
186
+ // One message has two AX tree blocks — they should count as 2 trees
187
+ const messages: Message[] = [
188
+ {
189
+ role: "user",
190
+ content: [
191
+ {
192
+ type: "tool_result",
193
+ tool_use_id: "t1a",
194
+ content: "<ax-tree>\ntree-1a\n</ax-tree>",
195
+ is_error: false,
196
+ },
197
+ {
198
+ type: "tool_result",
199
+ tool_use_id: "t1b",
200
+ content: "<ax-tree>\ntree-1b\n</ax-tree>",
201
+ is_error: false,
202
+ },
203
+ ],
204
+ },
205
+ assistantText("ok"),
206
+ axTreeToolResult("t2", "tree-2"),
207
+ ];
208
+
209
+ const result = compactAxTreeHistory(messages);
210
+
211
+ // 3 total AX tree blocks, keep last 2 → strip only first block (t1a)
212
+ const msg0 = result[0];
213
+ const block0 = msg0.content[0];
214
+ expect(block0.type).toBe("tool_result");
215
+ if (block0.type === "tool_result") {
216
+ expect(block0.content).toContain("<ax_tree_omitted />");
217
+ expect(block0.content).not.toContain("<ax-tree>");
218
+ }
219
+
220
+ // Second block in same message (t1b) should be kept
221
+ const block1 = msg0.content[1];
222
+ expect(block1.type).toBe("tool_result");
223
+ if (block1.type === "tool_result") {
224
+ expect(block1.content).toContain("<ax-tree>");
225
+ expect(block1.content).not.toContain("<ax_tree_omitted />");
226
+ }
227
+
228
+ // Last message (t2) should also be kept
229
+ const lastBlock = result[2].content[0];
230
+ expect(lastBlock.type).toBe("tool_result");
231
+ if (lastBlock.type === "tool_result") {
232
+ expect(lastBlock.content).toContain("<ax-tree>");
233
+ }
234
+ });
235
+
236
+ test("is pure — does not mutate input messages", () => {
237
+ const messages: Message[] = [
238
+ axTreeToolResult("t1", "tree-1"),
239
+ assistantText("ok"),
240
+ axTreeToolResult("t2", "tree-2"),
241
+ assistantText("ok"),
242
+ axTreeToolResult("t3", "tree-3"),
243
+ ];
244
+
245
+ // Deep copy to compare later
246
+ const originalContent = messages[0].content[0];
247
+ const originalText =
248
+ originalContent.type === "tool_result" ? originalContent.content : "";
249
+
250
+ compactAxTreeHistory(messages);
251
+
252
+ // Original message should be unchanged
253
+ const afterContent = messages[0].content[0];
254
+ const afterText =
255
+ afterContent.type === "tool_result" ? afterContent.content : "";
256
+ expect(afterText).toBe(originalText);
257
+ });
258
+ });
259
+
260
+ // ---------------------------------------------------------------------------
261
+ // escapeAxTreeContent
262
+ // ---------------------------------------------------------------------------
263
+
264
+ describe("escapeAxTreeContent", () => {
265
+ test("escapes literal </ax-tree> inside content", () => {
266
+ const input = "Some XML: <div></ax-tree></div>";
267
+ const result = escapeAxTreeContent(input);
268
+ expect(result).toBe("Some XML: <div>&lt;/ax-tree&gt;</div>");
269
+ });
270
+
271
+ test("handles case-insensitive matches", () => {
272
+ const input = "</AX-TREE> and </Ax-Tree>";
273
+ const result = escapeAxTreeContent(input);
274
+ expect(result).toBe("&lt;/ax-tree&gt; and &lt;/ax-tree&gt;");
275
+ });
276
+
277
+ test("returns content unchanged when no closing tags present", () => {
278
+ const input = "Normal AX tree content with <ax-tree> opening tag";
279
+ const result = escapeAxTreeContent(input);
280
+ expect(result).toBe(input);
281
+ });
282
+
283
+ test("handles empty string", () => {
284
+ expect(escapeAxTreeContent("")).toBe("");
285
+ });
286
+ });
package/src/agent/loop.ts CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  applyStreamingSubstitution,
15
15
  applySubstitutions,
16
16
  } from "../tools/sensitive-output-placeholders.js";
17
- import { getLogger, isDebug, truncateForLog } from "../util/logger.js";
17
+ import { getLogger } from "../util/logger.js";
18
18
 
19
19
  const log = getLogger("agent-loop");
20
20
 
@@ -35,6 +35,7 @@ export interface CheckpointInfo {
35
35
  turnIndex: number;
36
36
  toolCount: number;
37
37
  hasToolUse: boolean;
38
+ history: Message[]; // current history snapshot for token estimation
38
39
  }
39
40
 
40
41
  export type CheckpointDecision = "continue" | "yield";
@@ -71,7 +72,13 @@ export type AgentEvent =
71
72
  toolUseId: string;
72
73
  accumulatedJson: string;
73
74
  }
74
- | { type: "server_tool_start"; name: string; toolUseId: string }
75
+ | {
76
+ type: "server_tool_start";
77
+ name: string;
78
+ toolUseId: string;
79
+ input: Record<string, unknown>;
80
+ }
81
+ | { type: "server_tool_complete"; toolUseId: string }
75
82
  | { type: "error"; error: Error }
76
83
  | {
77
84
  type: "usage";
@@ -179,7 +186,6 @@ export class AgentLoop {
179
186
  let toolUseTurns = 0;
180
187
  let nudgedForEmptyResponse = false;
181
188
  let lastLlmCallTime = 0;
182
- const debug = isDebug();
183
189
  const rlog = requestId ? log.child({ requestId }) : log;
184
190
 
185
191
  // Per-run substitution map for sensitive output placeholders.
@@ -191,7 +197,6 @@ export class AgentLoop {
191
197
  while (true) {
192
198
  if (signal?.aborted) break;
193
199
 
194
- const turnStart = Date.now();
195
200
  let toolUseBlocks: Extract<ContentBlock, { type: "tool_use" }>[] = [];
196
201
 
197
202
  try {
@@ -227,22 +232,6 @@ export class AgentLoop {
227
232
  providerConfig.tool_choice = this.config.toolChoice;
228
233
  }
229
234
 
230
- if (debug) {
231
- rlog.debug(
232
- {
233
- systemPrompt: truncateForLog(turnSystemPrompt, 200),
234
- messageCount: history.length,
235
- lastMessage:
236
- history.length > 0
237
- ? summarizeMessage(history[history.length - 1])
238
- : null,
239
- toolCount: currentTools.length,
240
- config: providerConfig,
241
- },
242
- "Sending request to provider",
243
- );
244
- }
245
-
246
235
  const preLlmResult = await getHookManager().trigger("pre-llm-call", {
247
236
  systemPrompt: turnSystemPrompt,
248
237
  messages: history,
@@ -275,7 +264,11 @@ export class AgentLoop {
275
264
  // screenshots from accumulating in the context window. The LLM
276
265
  // already saw each image on the turn it was captured; keeping
277
266
  // base64 blobs in history rapidly exhausts the context budget.
278
- const providerHistory = stripOldImageBlocks(history);
267
+ // Also strip old AX tree snapshots to keep TTFT from growing
268
+ // linearly with step count in computer-use sessions.
269
+ const providerHistory = compactAxTreeHistory(
270
+ stripOldImageBlocks(history),
271
+ );
279
272
 
280
273
  const response = await this.provider.sendMessage(
281
274
  providerHistory,
@@ -319,6 +312,12 @@ export class AgentLoop {
319
312
  type: "server_tool_start",
320
313
  name: event.name,
321
314
  toolUseId: event.toolUseId,
315
+ input: event.input,
316
+ });
317
+ } else if (event.type === "server_tool_complete") {
318
+ onEvent({
319
+ type: "server_tool_complete",
320
+ toolUseId: event.toolUseId,
322
321
  });
323
322
  }
324
323
  },
@@ -328,33 +327,6 @@ export class AgentLoop {
328
327
 
329
328
  const providerDurationMs = Date.now() - providerStart;
330
329
 
331
- if (debug) {
332
- rlog.debug(
333
- {
334
- providerDurationMs,
335
- model: response.model,
336
- stopReason: response.stopReason,
337
- inputTokens: response.usage.inputTokens,
338
- outputTokens: response.usage.outputTokens,
339
- cacheCreationInputTokens: response.usage.cacheCreationInputTokens,
340
- cacheReadInputTokens: response.usage.cacheReadInputTokens,
341
- contentBlocks: response.content.map((b) => ({
342
- type: b.type,
343
- ...(b.type === "text"
344
- ? { text: truncateForLog(b.text, 1200) }
345
- : {}),
346
- ...(b.type === "tool_use"
347
- ? {
348
- name: b.name,
349
- input: truncateForLog(JSON.stringify(b.input), 1200),
350
- }
351
- : {}),
352
- })),
353
- },
354
- "Provider response received",
355
- );
356
- }
357
-
358
330
  onEvent({
359
331
  type: "usage",
360
332
  inputTokens: response.usage.inputTokens,
@@ -443,16 +415,6 @@ export class AgentLoop {
443
415
  name: toolUse.name,
444
416
  input: toolUse.input,
445
417
  });
446
-
447
- if (debug) {
448
- rlog.debug(
449
- {
450
- tool: toolUse.name,
451
- input: truncateForLog(JSON.stringify(toolUse.input), 300),
452
- },
453
- "Executing tool",
454
- );
455
- }
456
418
  }
457
419
 
458
420
  // If already cancelled, synthesize cancelled results and stop
@@ -469,49 +431,11 @@ export class AgentLoop {
469
431
  break;
470
432
  }
471
433
 
472
- // Guard against dual-control-mode conflicts in a single turn.
473
- // If the model escalates to foreground computer control, browser_* tools
474
- // in the same response create competing browser sessions/windows and can
475
- // thrash renderer CPU. Reject browser_* calls in that turn.
476
- const hasComputerUseEscalation = toolUseBlocks.some(
477
- (toolUse) => toolUse.name === "computer_use_request_control",
478
- );
479
- const blockedBrowserToolIds = hasComputerUseEscalation
480
- ? new Set(
481
- toolUseBlocks
482
- .filter((toolUse) => toolUse.name.startsWith("browser_"))
483
- .map((toolUse) => toolUse.id),
484
- )
485
- : new Set<string>();
486
-
487
- if (blockedBrowserToolIds.size > 0) {
488
- log.warn(
489
- {
490
- blockedBrowserToolCount: blockedBrowserToolIds.size,
491
- toolNames: toolUseBlocks.map((toolUse) => toolUse.name),
492
- },
493
- "Blocking browser_* tools: computer_use_request_control was requested in same turn",
494
- );
495
- }
496
-
497
434
  // Execute all tools concurrently for reduced latency.
498
435
  // Race against the abort signal so cancellation isn't blocked by
499
436
  // stuck tools (e.g. a hung browser navigation).
500
437
  const toolExecutionPromise = Promise.all(
501
438
  toolUseBlocks.map(async (toolUse) => {
502
- const toolStart = Date.now();
503
-
504
- if (blockedBrowserToolIds.has(toolUse.id)) {
505
- return {
506
- toolUse,
507
- result: {
508
- content:
509
- "Error: browser_* tools cannot run in the same turn as computer_use_request_control. Continue using the foreground computer-use session only.",
510
- isError: true,
511
- },
512
- };
513
- }
514
-
515
439
  const result = await this.toolExecutor!(
516
440
  toolUse.name,
517
441
  toolUse.input,
@@ -525,20 +449,6 @@ export class AgentLoop {
525
449
  toolUse.id,
526
450
  );
527
451
 
528
- const toolDurationMs = Date.now() - toolStart;
529
-
530
- if (debug) {
531
- rlog.debug(
532
- {
533
- tool: toolUse.name,
534
- toolDurationMs,
535
- isError: result.isError,
536
- output: truncateForLog(result.content, 300),
537
- },
538
- "Tool execution complete",
539
- );
540
- }
541
-
542
452
  return { toolUse, result };
543
453
  }),
544
454
  );
@@ -658,25 +568,13 @@ export class AgentLoop {
658
568
  // Add tool results as a user message and continue the loop
659
569
  history.push({ role: "user", content: resultBlocks });
660
570
 
661
- if (debug) {
662
- const turnDurationMs = Date.now() - turnStart;
663
- rlog.debug(
664
- {
665
- turnDurationMs,
666
- providerDurationMs,
667
- toolCount: toolUseBlocks.length,
668
- turn: toolUseTurns,
669
- },
670
- "Turn complete",
671
- );
672
- }
673
-
674
571
  // Invoke checkpoint callback after tool results are in history
675
572
  if (onCheckpoint) {
676
573
  const decision = onCheckpoint({
677
574
  turnIndex: toolUseTurns - 1, // 0-based (toolUseTurns was already incremented)
678
575
  toolCount: toolUseBlocks.length,
679
576
  hasToolUse: true,
577
+ history,
680
578
  });
681
579
  if (decision === "yield") {
682
580
  break;
@@ -715,14 +613,89 @@ export class AgentLoop {
715
613
  }
716
614
  }
717
615
 
718
- function summarizeMessage(msg: Message): {
719
- role: string;
720
- blockTypes: string[];
721
- } {
722
- return {
723
- role: msg.role,
724
- blockTypes: msg.content.map((b) => b.type),
725
- };
616
+ /** Number of most-recent AX tree snapshots to keep in conversation history. */
617
+ const MAX_AX_TREES_IN_HISTORY = 2;
618
+
619
+ /** Regex that matches the `<ax-tree>...</ax-tree>` markers. */
620
+ const AX_TREE_PATTERN = /<ax-tree>[\s\S]*?<\/ax-tree>/g;
621
+ const AX_TREE_PLACEHOLDER = "<ax_tree_omitted />";
622
+
623
+ /**
624
+ * Escapes any literal `</ax-tree>` occurrences inside AX tree content so
625
+ * that the non-greedy compaction regex (`AX_TREE_PATTERN`) does not stop
626
+ * prematurely when the user happens to be viewing XML/HTML source that
627
+ * contains the closing tag. The escaped content does not need to be
628
+ * unescaped because compaction replaces the entire block with a placeholder.
629
+ */
630
+ export function escapeAxTreeContent(content: string): string {
631
+ return content.replace(/<\/ax-tree>/gi, "&lt;/ax-tree&gt;");
632
+ }
633
+
634
+ /**
635
+ * Returns a shallow copy of `messages` where all but the most recent
636
+ * `MAX_AX_TREES_IN_HISTORY` `<ax-tree>` blocks have been replaced with a
637
+ * short placeholder. This keeps the conversation context small so that
638
+ * TTFT does not grow linearly with step count in computer-use sessions.
639
+ *
640
+ * Counting is per-block, not per-message — a single user message can
641
+ * contain multiple tool_result blocks each with their own AX tree snapshot.
642
+ */
643
+ export function compactAxTreeHistory(messages: Message[]): Message[] {
644
+ // Collect (messageIndex, blockIndex) for every tool_result block with <ax-tree>
645
+ const axBlocks: Array<{ msgIdx: number; blockIdx: number }> = [];
646
+ for (let i = 0; i < messages.length; i++) {
647
+ const msg = messages[i];
648
+ if (msg.role !== "user") continue;
649
+ for (let j = 0; j < msg.content.length; j++) {
650
+ const block = msg.content[j];
651
+ if (
652
+ block.type === "tool_result" &&
653
+ typeof block.content === "string" &&
654
+ block.content.includes("<ax-tree>")
655
+ ) {
656
+ axBlocks.push({ msgIdx: i, blockIdx: j });
657
+ }
658
+ }
659
+ }
660
+
661
+ if (axBlocks.length <= MAX_AX_TREES_IN_HISTORY) {
662
+ return messages;
663
+ }
664
+
665
+ // Build a set of "msgIdx:blockIdx" keys for blocks that should be stripped
666
+ const toStrip = new Set(
667
+ axBlocks
668
+ .slice(0, -MAX_AX_TREES_IN_HISTORY)
669
+ .map((b) => `${b.msgIdx}:${b.blockIdx}`),
670
+ );
671
+
672
+ return messages.map((msg, idx) => {
673
+ // Quick check: does this message have any blocks to strip?
674
+ const hasStripTarget = msg.content.some((_, j) =>
675
+ toStrip.has(`${idx}:${j}`),
676
+ );
677
+ if (!hasStripTarget) return msg;
678
+
679
+ return {
680
+ ...msg,
681
+ content: msg.content.map((block, j) => {
682
+ if (
683
+ toStrip.has(`${idx}:${j}`) &&
684
+ block.type === "tool_result" &&
685
+ typeof block.content === "string"
686
+ ) {
687
+ return {
688
+ ...block,
689
+ content: block.content.replace(
690
+ AX_TREE_PATTERN,
691
+ AX_TREE_PLACEHOLDER,
692
+ ),
693
+ };
694
+ }
695
+ return block;
696
+ }),
697
+ };
698
+ });
726
699
  }
727
700
 
728
701
  /**
@@ -16,7 +16,7 @@ Conversational guardian verification control-plane invocation is guardian-only.
16
16
 
17
17
  ## Memory Provenance Invariant
18
18
 
19
- All memory extraction and retrieval decisions must consider actor-role provenance. Untrusted actors (non-guardian, unverified_channel) must not trigger profile extraction or receive memory recall/conflict disclosures. This invariant is enforced in `indexer.ts` (write gate) and `session-memory.ts` (read gate).
19
+ All memory retrieval decisions must consider actor-role provenance. Untrusted actors (non-guardian, unverified_channel) must not receive memory recall results. This invariant is enforced in `indexer.ts` (write gate) and `session-memory.ts` (read gate).
20
20
 
21
21
  ## Guardian Privilege Isolation Invariant
22
22
 
@@ -424,11 +424,17 @@ const accessRequestResolver: GuardianRequestResolver = {
424
424
  dedupeKey: `trusted-contact:denied:${request.id}`,
425
425
  });
426
426
  } else if (desktopDeliverUrl && requesterChatId) {
427
+ // For Slack, route to DM via requesterExternalUserId (user ID) instead
428
+ // of requesterChatId (channel ID) to avoid posting in public channels.
429
+ const targetChatId =
430
+ channel === "slack" && requesterExternalUserId
431
+ ? requesterExternalUserId
432
+ : requesterChatId;
427
433
  try {
428
434
  await deliverChannelReply(
429
435
  desktopDeliverUrl,
430
436
  {
431
- chatId: requesterChatId,
437
+ chatId: targetChatId,
432
438
  text: "Your access request has been denied by the guardian.",
433
439
  assistantId,
434
440
  },
@@ -601,11 +607,17 @@ const accessRequestResolver: GuardianRequestResolver = {
601
607
  });
602
608
  }
603
609
  } else if (desktopDeliverUrl && requesterChatId) {
610
+ // For Slack, route to DM via requesterExternalUserId (user ID) instead
611
+ // of requesterChatId (channel ID) to avoid posting in public channels.
612
+ const targetChatId =
613
+ channel === "slack" && requesterExternalUserId
614
+ ? requesterExternalUserId
615
+ : requesterChatId;
604
616
  try {
605
617
  await deliverChannelReply(
606
618
  desktopDeliverUrl,
607
619
  {
608
- chatId: requesterChatId,
620
+ chatId: targetChatId,
609
621
  text:
610
622
  "Your access request has been approved! " +
611
623
  "Please enter the 6-digit verification code you receive from the guardian.",