@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
@@ -35,6 +35,7 @@ import * as pendingInteractions from "../../runtime/pending-interactions.js";
35
35
  import { getSubagentManager } from "../../subagent/index.js";
36
36
  import { truncate } from "../../util/truncate.js";
37
37
  import { HostBashProxy } from "../host-bash-proxy.js";
38
+ import { HostCuProxy } from "../host-cu-proxy.js";
38
39
  import { HostFileProxy } from "../host-file-proxy.js";
39
40
  import type {
40
41
  CancelRequest,
@@ -60,7 +61,6 @@ import {
60
61
  type HandlerContext,
61
62
  log,
62
63
  pendingStandaloneSecrets,
63
- wireEscalationHandler,
64
64
  } from "./shared.js";
65
65
 
66
66
  /**
@@ -165,6 +165,12 @@ export function makeEventSender(params: {
165
165
  conversationId,
166
166
  kind: "host_file",
167
167
  });
168
+ } else if (event.type === "host_cu_request") {
169
+ pendingInteractions.register(event.requestId, {
170
+ session,
171
+ conversationId,
172
+ kind: "host_cu",
173
+ });
168
174
  }
169
175
 
170
176
  ctx.send(event);
@@ -195,21 +201,6 @@ export function handleConfirmationResponse(
195
201
  }
196
202
  }
197
203
 
198
- // Also check computer-use sessions — they have their own PermissionPrompter
199
- for (const [, cuSession] of ctx.cuSessions) {
200
- if (cuSession.hasPendingConfirmation(msg.requestId)) {
201
- cuSession.handleConfirmationResponse(
202
- msg.requestId,
203
- msg.decision,
204
- msg.selectedPattern,
205
- msg.selectedScope,
206
- );
207
- syncCanonicalStatusFromConfirmationDecision(msg.requestId, msg.decision);
208
- pendingInteractions.resolve(msg.requestId);
209
- return;
210
- }
211
- }
212
-
213
204
  log.warn(
214
205
  { requestId: msg.requestId },
215
206
  "No session found with pending confirmation for requestId",
@@ -362,7 +353,6 @@ export async function handleSessionCreate(
362
353
  maxResponseTokens: msg.maxResponseTokens,
363
354
  transport: msg.transport,
364
355
  });
365
- wireEscalationHandler(session, ctx);
366
356
 
367
357
  // Pre-activate skills before sending session_info so they're available
368
358
  // for the initial message processing.
@@ -431,6 +421,11 @@ export async function handleSessionCreate(
431
421
  pendingInteractions.resolve(requestId);
432
422
  });
433
423
  session.setHostFileProxy(fileProxy);
424
+ const cuProxy = new HostCuProxy(sendEvent, (requestId) => {
425
+ pendingInteractions.resolve(requestId);
426
+ });
427
+ session.setHostCuProxy(cuProxy);
428
+ session.addPreactivatedSkillId("computer-use");
434
429
  }
435
430
  session.updateClient(sendEvent, false);
436
431
  session
@@ -492,13 +487,7 @@ export async function switchSession(
492
487
  // Load the session without rebinding the client — the session stays headless
493
488
  await ctx.getOrCreateSession(sessionId);
494
489
  } else {
495
- const session = await ctx.getOrCreateSession(sessionId);
496
- // Only wire the escalation handler if one isn't already set — handleTaskSubmit
497
- // sets a handler with the client's actual screen dimensions, and overwriting it
498
- // here would replace those dimensions with the daemon's defaults.
499
- if (!session.hasEscalationHandler()) {
500
- wireEscalationHandler(session, ctx);
501
- }
490
+ await ctx.getOrCreateSession(sessionId);
502
491
  }
503
492
 
504
493
  return {
@@ -589,23 +578,24 @@ export function handleCancel(msg: CancelRequest, ctx: HandlerContext): void {
589
578
  }
590
579
 
591
580
  /**
592
- * Undo the last message in a session. Returns the removed count, or null if session not found.
581
+ * Undo the last message in a session. Returns the removed count, or null if
582
+ * the conversation does not exist. Restores evicted sessions from the database.
593
583
  */
594
- export function undoLastMessage(
584
+ export async function undoLastMessage(
595
585
  sessionId: string,
596
586
  ctx: HandlerContext,
597
- ): { removedCount: number } | null {
598
- const session = ctx.sessions.get(sessionId);
599
- if (!session) {
587
+ ): Promise<{ removedCount: number } | null> {
588
+ if (!getConversation(sessionId)) {
600
589
  return null;
601
590
  }
591
+ const session = await ctx.getOrCreateSession(sessionId);
602
592
  ctx.touchSession(sessionId);
603
593
  const removedCount = session.undo();
604
594
  return { removedCount };
605
595
  }
606
596
 
607
- export function handleUndo(msg: UndoRequest, ctx: HandlerContext): void {
608
- const result = undoLastMessage(msg.sessionId, ctx);
597
+ export async function handleUndo(msg: UndoRequest, ctx: HandlerContext): Promise<void> {
598
+ const result = await undoLastMessage(msg.sessionId, ctx);
609
599
  if (!result) {
610
600
  ctx.send({ type: "error", message: "No active session" });
611
601
  return;
@@ -620,17 +610,18 @@ export function handleUndo(msg: UndoRequest, ctx: HandlerContext): void {
620
610
  /**
621
611
  * Regenerate the last assistant response for a session. The caller provides
622
612
  * a `sendEvent` callback for delivering streaming events via HTTP/SSE.
623
- * Returns null if the session is not found. Throws on regeneration errors.
613
+ * Returns null if the conversation does not exist. Restores evicted sessions
614
+ * from the database when needed. Throws on regeneration errors.
624
615
  */
625
616
  export async function regenerateResponse(
626
617
  sessionId: string,
627
618
  ctx: HandlerContext,
628
619
  sendEvent: (event: ServerMessage) => void,
629
620
  ): Promise<{ requestId: string } | null> {
630
- const session = ctx.sessions.get(sessionId);
631
- if (!session) {
621
+ if (!getConversation(sessionId)) {
632
622
  return null;
633
623
  }
624
+ const session = await ctx.getOrCreateSession(sessionId);
634
625
  ctx.touchSession(sessionId);
635
626
  session.updateClient(sendEvent, false);
636
627
  const requestId = uuid();
@@ -661,11 +652,11 @@ export async function handleRegenerate(
661
652
  msg: RegenerateRequest,
662
653
  ctx: HandlerContext,
663
654
  ): Promise<void> {
664
- const session = ctx.sessions.get(msg.sessionId);
665
- if (!session) {
655
+ if (!getConversation(msg.sessionId)) {
666
656
  ctx.send({ type: "error", message: "No active session" });
667
657
  return;
668
658
  }
659
+ const session = await ctx.getOrCreateSession(msg.sessionId);
669
660
 
670
661
  const regenerateChannel =
671
662
  parseChannelId(session.getTurnChannelContext()?.assistantMessageChannel) ??
@@ -1,17 +1,12 @@
1
- import { execSync } from "node:child_process";
2
-
3
1
  import { v4 as uuid } from "uuid";
4
2
 
5
3
  import { getConfig } from "../../config/loader.js";
6
4
  import type { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
7
5
  import type { SecretPromptResult } from "../../permissions/secret-prompter.js";
8
- import { RateLimitProvider } from "../../providers/ratelimit.js";
9
- import { getFailoverProvider } from "../../providers/registry.js";
10
6
  import type { AuthContext } from "../../runtime/auth/types.js";
11
7
  import type { DebouncerMap } from "../../util/debounce.js";
12
8
  import { getLogger } from "../../util/logger.js";
13
9
  import { estimateBase64Bytes } from "../assistant-attachments.js";
14
- import { ComputerUseSession } from "../computer-use-session.js";
15
10
  import type {
16
11
  ServerMessage,
17
12
  SessionTransportMetadata,
@@ -28,9 +23,6 @@ export const CONFIG_RELOAD_DEBOUNCE_MS = 300;
28
23
 
29
24
  const HISTORY_ATTACHMENT_TEXT_LIMIT = 500;
30
25
 
31
- export const FALLBACK_SCREEN = { width: 1920, height: 1080 };
32
- let cachedScreenDims: { width: number; height: number } | null = null;
33
-
34
26
  // Module-level map for non-session secret prompts (e.g. publish_page)
35
27
  export const pendingStandaloneSecrets = new Map<
36
28
  string,
@@ -150,8 +142,6 @@ export interface SessionCreateOptions {
150
142
  */
151
143
  export interface HandlerContext {
152
144
  sessions: Map<string, Session>;
153
- cuSessions: Map<string, ComputerUseSession>;
154
- cuObservationParseSequence: Map<string, number>;
155
145
  sharedRequestTimestamps: number[];
156
146
  debounceTimers: DebouncerMap;
157
147
  suppressConfigReload: boolean;
@@ -170,126 +160,6 @@ export interface HandlerContext {
170
160
  heartbeatService?: HeartbeatService;
171
161
  }
172
162
 
173
- /**
174
- * Query the main display dimensions via CoreGraphics.
175
- * Cached after the first successful call; falls back to 1920x1080.
176
- */
177
- export function getScreenDimensions(): { width: number; height: number } {
178
- if (cachedScreenDims) return cachedScreenDims;
179
- if (process.platform !== "darwin") return FALLBACK_SCREEN;
180
- try {
181
- // Use osascript (JXA) instead of `swift` to avoid the
182
- // "Install Command Line Developer Tools" popup on fresh macOS installs.
183
- const out = execSync(
184
- `osascript -l JavaScript -e 'ObjC.import("AppKit"); var f = $.NSScreen.mainScreen.frame; Math.round(f.size.width) + "x" + Math.round(f.size.height)'`,
185
- { timeout: 10_000, encoding: "utf-8" },
186
- ).trim();
187
- const [w, h] = out.split("x").map(Number);
188
- if (w > 0 && h > 0) {
189
- cachedScreenDims = { width: w, height: h };
190
- return cachedScreenDims;
191
- }
192
- } catch (err) {
193
- log.debug({ err }, "Failed to query screen dimensions, using fallback");
194
- }
195
- return FALLBACK_SCREEN;
196
- }
197
-
198
- /**
199
- * Wire the escalation handler on a text_qa session so that invoking
200
- * `computer_use_request_control` creates a CU session and notifies the client.
201
- *
202
- * In the HTTP-only world, the escalation handler broadcasts events via
203
- * `ctx.broadcast` instead of targeting a specific socket.
204
- */
205
- export function wireEscalationHandler(
206
- session: Session,
207
- ctx: HandlerContext,
208
- explicitWidth?: number,
209
- explicitHeight?: number,
210
- ): void {
211
- const dims =
212
- explicitWidth && explicitHeight
213
- ? { width: explicitWidth, height: explicitHeight }
214
- : getScreenDimensions();
215
- const screenWidth = dims.width;
216
- const screenHeight = dims.height;
217
- session.setEscalationHandler(
218
- (task: string, sourceSessionId: string): boolean => {
219
- const cuSessionId = uuid();
220
-
221
- // Inline CU session creation (previously delegated to deleted handlers/computer-use.ts)
222
- const existingSession = ctx.cuSessions.get(cuSessionId);
223
- if (existingSession) {
224
- existingSession.abort();
225
- ctx.cuSessions.delete(cuSessionId);
226
- ctx.cuObservationParseSequence.delete(cuSessionId);
227
- }
228
-
229
- const config = getConfig();
230
- let provider = getFailoverProvider(config.provider, config.providerOrder);
231
- const { rateLimit } = config;
232
- if (
233
- rateLimit.maxRequestsPerMinute > 0 ||
234
- rateLimit.maxTokensPerSession > 0
235
- ) {
236
- provider = new RateLimitProvider(
237
- provider,
238
- rateLimit,
239
- ctx.sharedRequestTimestamps,
240
- );
241
- }
242
-
243
- const sendToClient = (serverMsg: ServerMessage) => {
244
- ctx.send(serverMsg);
245
- };
246
-
247
- const sessionRef: { current?: ComputerUseSession } = {};
248
- const onTerminal = (sid: string) => {
249
- const current = ctx.cuSessions.get(sid);
250
- if (sessionRef.current && current && current !== sessionRef.current) {
251
- return;
252
- }
253
- ctx.cuSessions.delete(sid);
254
- ctx.cuObservationParseSequence.delete(sid);
255
- log.info(
256
- { sessionId: sid },
257
- "Computer-use session cleaned up after terminal state",
258
- );
259
- };
260
-
261
- const cuSession = new ComputerUseSession(
262
- cuSessionId,
263
- task,
264
- screenWidth,
265
- screenHeight,
266
- provider,
267
- sendToClient,
268
- "computer_use",
269
- onTerminal,
270
- );
271
- sessionRef.current = cuSession;
272
-
273
- ctx.cuSessions.set(cuSessionId, cuSession);
274
-
275
- log.info(
276
- { sessionId: cuSessionId, taskLength: task.length },
277
- "Computer-use session created via escalation",
278
- );
279
-
280
- ctx.broadcast({
281
- type: "task_routed",
282
- sessionId: cuSessionId,
283
- interactionType: "computer_use",
284
- task,
285
- escalatedFrom: sourceSessionId,
286
- });
287
-
288
- return true;
289
- },
290
- );
291
- }
292
-
293
163
  export function isRecord(value: unknown): value is Record<string, unknown> {
294
164
  return typeof value === "object" && value != null;
295
165
  }
@@ -250,12 +250,20 @@ export interface SkillListItem {
250
250
  provenance: SkillProvenance;
251
251
  }
252
252
 
253
+ /** Sorting rank for provenance-based ordering: first-party first, local last. */
254
+ function provenanceSortRank(p: SkillProvenance): number {
255
+ if (p.kind === "first-party") return 0;
256
+ if (p.kind === "third-party" && p.provider) return 1;
257
+ if (p.kind === "third-party") return 2;
258
+ return 3; // local
259
+ }
260
+
253
261
  export function listSkills(_ctx: SkillOperationContext): SkillListItem[] {
254
262
  const config = getConfig();
255
263
  const catalog = loadSkillCatalog();
256
264
  const resolved = resolveSkillStates(catalog, config);
257
265
 
258
- return resolved.map((r) => ({
266
+ const items = resolved.map((r) => ({
259
267
  id: r.summary.id,
260
268
  name: r.summary.displayName,
261
269
  description: r.summary.description,
@@ -272,6 +280,17 @@ export function listSkills(_ctx: SkillOperationContext): SkillListItem[] {
272
280
  userInvocable: r.summary.userInvocable,
273
281
  provenance: resolveProvenance(r.summary),
274
282
  }));
283
+
284
+ // Sort: first-party > third-party with provider > third-party without > local,
285
+ // alphabetical by name within each tier.
286
+ items.sort((a, b) => {
287
+ const rankDiff =
288
+ provenanceSortRank(a.provenance) - provenanceSortRank(b.provenance);
289
+ if (rankDiff !== 0) return rankDiff;
290
+ return a.name.localeCompare(b.name);
291
+ });
292
+
293
+ return items;
275
294
  }
276
295
 
277
296
  export function enableSkill(
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  ContentBlock,
3
3
  Message,
4
+ ServerToolUseContent,
4
5
  ToolResultContent,
5
6
  ToolUseContent,
6
7
  } from "../providers/types.js";
@@ -20,6 +21,11 @@ export interface RepairResult {
20
21
  const SYNTHETIC_RESULT =
21
22
  "<synthesized_result>tool result missing from history</synthesized_result>";
22
23
 
24
+ const SYNTHETIC_WEB_SEARCH_ERROR = {
25
+ type: "web_search_tool_result_error",
26
+ error_code: "unavailable",
27
+ };
28
+
23
29
  export function repairHistory(messages: Message[]): RepairResult {
24
30
  const stats: RepairStats = {
25
31
  assistantToolResultsMigrated: 0,
@@ -45,12 +51,15 @@ export function repairHistory(messages: Message[]): RepairResult {
45
51
  recoveredResults = new Map();
46
52
  }
47
53
 
48
- // Strip tool_result blocks from assistant messages, preserving them
49
- // so they can be migrated to the correct user message position
54
+ // Strip client-side tool_result blocks from assistant messages,
55
+ // preserving them so they can be migrated to the correct user message.
56
+ // Server-side tools (server_tool_use / web_search_tool_result) are
57
+ // self-paired within the assistant message and must NOT be separated.
50
58
  const cleanedContent: ContentBlock[] = [];
51
59
  const newRecovered = new Map<string, ToolResultContent>();
52
60
  for (const block of msg.content) {
53
61
  if (block.type === "tool_result") {
62
+ // guard:allow-tool-result-only — only client-side tool_result belongs in recovered; web_search_tool_result stays in the assistant message
54
63
  const tr = block as ToolResultContent;
55
64
  newRecovered.set(tr.tool_use_id, tr);
56
65
  stats.assistantToolResultsMigrated++;
@@ -59,9 +68,34 @@ export function repairHistory(messages: Message[]): RepairResult {
59
68
  }
60
69
  }
61
70
 
71
+ // Ensure every server_tool_use has a paired web_search_tool_result
72
+ // in the same assistant message (handles interrupted streams)
73
+ const serverToolIds = new Set(
74
+ cleanedContent
75
+ .filter(
76
+ (b): b is ServerToolUseContent => b.type === "server_tool_use",
77
+ )
78
+ .map((b) => b.id),
79
+ );
80
+ const matchedServerIds = new Set(
81
+ cleanedContent
82
+ .filter((b) => b.type === "web_search_tool_result")
83
+ .map((b) => (b as { tool_use_id: string }).tool_use_id),
84
+ );
85
+ for (const id of serverToolIds) {
86
+ if (!matchedServerIds.has(id)) {
87
+ cleanedContent.push({
88
+ type: "web_search_tool_result",
89
+ tool_use_id: id,
90
+ content: SYNTHETIC_WEB_SEARCH_ERROR,
91
+ });
92
+ stats.missingToolResultsInserted++;
93
+ }
94
+ }
95
+
62
96
  result.push({ role: "assistant", content: cleanedContent });
63
97
 
64
- // Collect tool_use IDs from this assistant message
98
+ // Only track client-side tool_use IDs as pending (not server_tool_use)
65
99
  pendingToolUseIds = new Set(
66
100
  cleanedContent
67
101
  .filter((b): b is ToolUseContent => b.type === "tool_use")
@@ -76,14 +110,28 @@ export function repairHistory(messages: Message[]): RepairResult {
76
110
 
77
111
  for (const block of msg.content) {
78
112
  if (block.type === "tool_result") {
113
+ // guard:allow-tool-result-only — matches client-side tool_use; web_search_tool_result is handled separately below
79
114
  const tr = block as ToolResultContent;
80
115
  if (pendingToolUseIds.has(tr.tool_use_id)) {
81
116
  matchedIds.add(tr.tool_use_id);
82
117
  newContent.push(block);
83
118
  } else {
84
119
  stats.orphanToolResultsDowngraded++;
85
- newContent.push(downgradeToolResult(tr));
120
+ newContent.push(downgradeResult(tr));
86
121
  }
122
+ } else if (block.type === "web_search_tool_result") {
123
+ // web_search_tool_result in a user message is orphaned — server-side
124
+ // results belong in the assistant message, not here
125
+ stats.orphanToolResultsDowngraded++;
126
+ newContent.push(
127
+ downgradeResult(
128
+ block as {
129
+ type: "web_search_tool_result";
130
+ tool_use_id: string;
131
+ content: unknown;
132
+ },
133
+ ),
134
+ );
87
135
  } else {
88
136
  newContent.push(block);
89
137
  }
@@ -112,11 +160,21 @@ export function repairHistory(messages: Message[]): RepairResult {
112
160
  pendingToolUseIds = new Set();
113
161
  recoveredResults = new Map();
114
162
  } else {
115
- // No pending tool_use — any tool_result here is orphaned
163
+ // No pending tool_use — any tool_result/web_search_tool_result here is orphaned
116
164
  const newContent: ContentBlock[] = msg.content.map((block) => {
117
165
  if (block.type === "tool_result") {
118
166
  stats.orphanToolResultsDowngraded++;
119
- return downgradeToolResult(block as ToolResultContent);
167
+ return downgradeResult(block as ToolResultContent);
168
+ }
169
+ if (block.type === "web_search_tool_result") {
170
+ stats.orphanToolResultsDowngraded++;
171
+ return downgradeResult(
172
+ block as {
173
+ type: "web_search_tool_result";
174
+ tool_use_id: string;
175
+ content: unknown;
176
+ },
177
+ );
120
178
  }
121
179
  return block;
122
180
  });
@@ -207,9 +265,15 @@ export function deepRepairHistory(messages: Message[]): RepairResult {
207
265
  return repairHistory(merged);
208
266
  }
209
267
 
210
- function downgradeToolResult(tr: ToolResultContent): ContentBlock {
268
+ function downgradeResult(tr: {
269
+ type: string;
270
+ tool_use_id: string;
271
+ content?: unknown;
272
+ }): ContentBlock {
273
+ const content =
274
+ tr.type === "tool_result" ? tr.content : "[web search result]"; // guard:allow-tool-result-only — distinguishes content format between the two types
211
275
  return {
212
276
  type: "text",
213
- text: `[orphaned tool_result for ${tr.tool_use_id}]: ${tr.content}`,
277
+ text: `[orphaned ${tr.type} for ${tr.tool_use_id}]: ${content}`,
214
278
  };
215
279
  }