@vellumai/assistant 0.4.49 → 0.4.51

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 (353) hide show
  1. package/ARCHITECTURE.md +24 -33
  2. package/README.md +3 -3
  3. package/docs/architecture/integrations.md +2 -2
  4. package/docs/architecture/keychain-broker.md +6 -6
  5. package/docs/architecture/memory.md +180 -119
  6. package/knip.json +32 -0
  7. package/package.json +3 -2
  8. package/src/__tests__/agent-loop.test.ts +3 -1
  9. package/src/__tests__/anthropic-provider.test.ts +114 -23
  10. package/src/__tests__/approval-cascade.test.ts +1 -15
  11. package/src/__tests__/approval-routes-http.test.ts +2 -0
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  13. package/src/__tests__/btw-routes.test.ts +61 -5
  14. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  15. package/src/__tests__/checker.test.ts +13 -0
  16. package/src/__tests__/config-schema.test.ts +1 -68
  17. package/src/__tests__/config-watcher.test.ts +8 -0
  18. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  19. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  20. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  21. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  22. package/src/__tests__/credential-security-invariants.test.ts +8 -7
  23. package/src/__tests__/credential-vault-unit.test.ts +23 -18
  24. package/src/__tests__/credential-vault.test.ts +30 -18
  25. package/src/__tests__/credentials-cli.test.ts +257 -82
  26. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  27. package/src/__tests__/date-context.test.ts +93 -77
  28. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  29. package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
  30. package/src/__tests__/history-repair.test.ts +245 -0
  31. package/src/__tests__/host-cu-proxy.test.ts +165 -3
  32. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  33. package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
  34. package/src/__tests__/integration-status.test.ts +31 -30
  35. package/src/__tests__/invite-redemption-service.test.ts +166 -13
  36. package/src/__tests__/invite-routes-http.test.ts +166 -5
  37. package/src/__tests__/keychain-broker-client.test.ts +4 -4
  38. package/src/__tests__/list-messages-attachments.test.ts +193 -0
  39. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  40. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  41. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  42. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  43. package/src/__tests__/memory-regressions.test.ts +477 -2841
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  45. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  46. package/src/__tests__/mime-builder.test.ts +28 -0
  47. package/src/__tests__/native-web-search.test.ts +1 -0
  48. package/src/__tests__/oauth-cli.test.ts +824 -31
  49. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  50. package/src/__tests__/oauth-store.test.ts +363 -17
  51. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  52. package/src/__tests__/registry.test.ts +0 -1
  53. package/src/__tests__/relay-server.test.ts +55 -1
  54. package/src/__tests__/schedule-tools.test.ts +32 -0
  55. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  56. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  57. package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
  58. package/src/__tests__/secure-keys.test.ts +78 -18
  59. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  60. package/src/__tests__/server-history-render.test.ts +2 -2
  61. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  62. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  63. package/src/__tests__/session-agent-loop.test.ts +19 -15
  64. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  65. package/src/__tests__/session-error.test.ts +124 -2
  66. package/src/__tests__/session-history-web-search.test.ts +918 -0
  67. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  68. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  69. package/src/__tests__/session-queue.test.ts +37 -27
  70. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  71. package/src/__tests__/session-slash-known.test.ts +1 -15
  72. package/src/__tests__/session-slash-queue.test.ts +1 -15
  73. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  74. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  75. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  76. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  77. package/src/__tests__/skills-install-extract.test.ts +93 -0
  78. package/src/__tests__/skills.test.ts +2 -2
  79. package/src/__tests__/skillssh-registry.test.ts +451 -0
  80. package/src/__tests__/slack-channel-config.test.ts +10 -8
  81. package/src/__tests__/trust-store.test.ts +15 -0
  82. package/src/__tests__/twilio-config.test.ts +11 -10
  83. package/src/__tests__/twilio-provider.test.ts +9 -4
  84. package/src/__tests__/voice-invite-redemption.test.ts +85 -5
  85. package/src/agent/ax-tree-compaction.test.ts +51 -0
  86. package/src/agent/loop.ts +39 -12
  87. package/src/approvals/AGENTS.md +1 -1
  88. package/src/approvals/guardian-request-resolvers.ts +14 -2
  89. package/src/bundler/compiler-tools.ts +66 -2
  90. package/src/calls/call-domain.ts +134 -3
  91. package/src/calls/call-store.ts +6 -0
  92. package/src/calls/relay-server.ts +44 -6
  93. package/src/calls/relay-setup-router.ts +17 -1
  94. package/src/calls/twilio-config.ts +5 -4
  95. package/src/calls/twilio-provider.ts +14 -9
  96. package/src/calls/twilio-rest.ts +10 -7
  97. package/src/calls/types.ts +3 -1
  98. package/src/cli/commands/config.ts +14 -9
  99. package/src/cli/commands/contacts.ts +3 -0
  100. package/src/cli/commands/credentials.ts +170 -174
  101. package/src/cli/commands/doctor.ts +11 -8
  102. package/src/cli/commands/keys.ts +9 -9
  103. package/src/cli/commands/mcp.ts +46 -59
  104. package/src/cli/commands/memory.ts +16 -165
  105. package/src/cli/commands/oauth/apps.ts +68 -10
  106. package/src/cli/commands/oauth/connections.ts +475 -105
  107. package/src/cli/commands/oauth/index.ts +3 -3
  108. package/src/cli/commands/oauth/providers.ts +18 -4
  109. package/src/cli/commands/sessions.ts +5 -2
  110. package/src/cli/commands/skills.ts +173 -1
  111. package/src/cli/http-client.ts +0 -20
  112. package/src/cli/main-screen.tsx +2 -2
  113. package/src/cli/program.ts +5 -6
  114. package/src/cli.ts +20 -22
  115. package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
  116. package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
  117. package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +35 -11
  119. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  120. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  121. package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
  135. package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
  136. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
  137. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
  138. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
  139. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
  140. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
  141. package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
  142. package/src/config/bundled-skills/messaging/SKILL.md +1 -1
  143. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  144. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  145. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
  146. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
  148. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
  149. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
  150. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
  151. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
  152. package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
  153. package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
  154. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
  155. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
  156. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
  157. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
  158. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
  159. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
  160. package/src/config/bundled-tool-registry.ts +2 -5
  161. package/src/config/loader.ts +6 -42
  162. package/src/config/schema.ts +1 -12
  163. package/src/config/schemas/memory-lifecycle.ts +0 -9
  164. package/src/config/schemas/memory-processing.ts +0 -180
  165. package/src/config/schemas/memory-retrieval.ts +32 -104
  166. package/src/config/schemas/memory.ts +0 -10
  167. package/src/config/types.ts +0 -4
  168. package/src/contacts/contact-store.ts +39 -2
  169. package/src/contacts/contacts-write.ts +9 -0
  170. package/src/context/window-manager.ts +4 -1
  171. package/src/daemon/config-watcher.ts +55 -2
  172. package/src/daemon/daemon-control.ts +1 -1
  173. package/src/daemon/date-context.ts +114 -31
  174. package/src/daemon/handlers/config-ingress.ts +2 -2
  175. package/src/daemon/handlers/config-slack-channel.ts +59 -39
  176. package/src/daemon/handlers/config-telegram.ts +23 -14
  177. package/src/daemon/handlers/session-history.ts +1 -358
  178. package/src/daemon/handlers/sessions.ts +18 -13
  179. package/src/daemon/handlers/shared.ts +3 -17
  180. package/src/daemon/handlers/skills.ts +20 -1
  181. package/src/daemon/history-repair.ts +72 -8
  182. package/src/daemon/host-cu-proxy.ts +55 -26
  183. package/src/daemon/lifecycle.ts +39 -4
  184. package/src/daemon/mcp-reload-service.ts +2 -2
  185. package/src/daemon/message-types/computer-use.ts +1 -12
  186. package/src/daemon/message-types/memory.ts +4 -16
  187. package/src/daemon/message-types/messages.ts +1 -0
  188. package/src/daemon/message-types/sessions.ts +4 -42
  189. package/src/daemon/server.ts +6 -1
  190. package/src/daemon/session-agent-loop-handlers.ts +38 -0
  191. package/src/daemon/session-agent-loop.ts +334 -48
  192. package/src/daemon/session-error.ts +89 -6
  193. package/src/daemon/session-history.ts +17 -7
  194. package/src/daemon/session-media-retry.ts +6 -2
  195. package/src/daemon/session-memory.ts +69 -149
  196. package/src/daemon/session-process.ts +10 -1
  197. package/src/daemon/session-runtime-assembly.ts +49 -19
  198. package/src/daemon/session-slash.ts +3 -5
  199. package/src/daemon/session-surfaces.ts +4 -1
  200. package/src/daemon/session-tool-setup.ts +7 -1
  201. package/src/daemon/session.ts +12 -2
  202. package/src/email/providers/index.ts +2 -2
  203. package/src/instrument.ts +61 -1
  204. package/src/media/avatar-router.ts +1 -1
  205. package/src/memory/admin.ts +2 -191
  206. package/src/memory/canonical-guardian-store.ts +38 -2
  207. package/src/memory/conversation-crud.ts +0 -33
  208. package/src/memory/conversation-queries.ts +25 -83
  209. package/src/memory/db-init.ts +32 -0
  210. package/src/memory/embedding-backend.ts +84 -8
  211. package/src/memory/embedding-types.ts +9 -1
  212. package/src/memory/indexer.ts +7 -46
  213. package/src/memory/invite-store.ts +19 -0
  214. package/src/memory/items-extractor.ts +274 -76
  215. package/src/memory/job-handlers/backfill.ts +2 -127
  216. package/src/memory/job-handlers/cleanup.ts +2 -16
  217. package/src/memory/job-handlers/extraction.ts +2 -138
  218. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  219. package/src/memory/job-handlers/summarization.ts +3 -148
  220. package/src/memory/job-utils.ts +21 -59
  221. package/src/memory/jobs-store.ts +1 -159
  222. package/src/memory/jobs-worker.ts +9 -52
  223. package/src/memory/migrations/104-core-indexes.ts +3 -3
  224. package/src/memory/migrations/149-oauth-tables.ts +2 -0
  225. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  226. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  227. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  228. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  229. package/src/memory/migrations/154-drop-fts.ts +20 -0
  230. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  231. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  232. package/src/memory/migrations/157-invite-contact-id.ts +104 -0
  233. package/src/memory/migrations/index.ts +8 -0
  234. package/src/memory/migrations/registry.ts +6 -0
  235. package/src/memory/qdrant-client.ts +148 -51
  236. package/src/memory/raw-query.ts +1 -1
  237. package/src/memory/retriever.test.ts +294 -273
  238. package/src/memory/retriever.ts +421 -645
  239. package/src/memory/schema/calls.ts +2 -0
  240. package/src/memory/schema/contacts.ts +1 -0
  241. package/src/memory/schema/memory-core.ts +3 -48
  242. package/src/memory/schema/oauth.ts +2 -0
  243. package/src/memory/search/formatting.ts +263 -176
  244. package/src/memory/search/lexical.ts +1 -254
  245. package/src/memory/search/ranking.ts +0 -455
  246. package/src/memory/search/semantic.ts +100 -14
  247. package/src/memory/search/staleness.ts +47 -0
  248. package/src/memory/search/tier-classifier.ts +21 -0
  249. package/src/memory/search/types.ts +15 -77
  250. package/src/memory/task-memory-cleanup.ts +4 -6
  251. package/src/messaging/provider.ts +1 -1
  252. package/src/messaging/providers/gmail/adapter.ts +1 -1
  253. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  254. package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
  255. package/src/messaging/providers/whatsapp/adapter.ts +13 -9
  256. package/src/messaging/registry.ts +9 -5
  257. package/src/oauth/byo-connection.test.ts +40 -25
  258. package/src/oauth/connect-orchestrator.ts +4 -10
  259. package/src/oauth/connection-resolver.ts +20 -6
  260. package/src/oauth/manual-token-connection.ts +5 -5
  261. package/src/oauth/oauth-store.ts +183 -31
  262. package/src/oauth/platform-connection.test.ts +1 -1
  263. package/src/oauth/provider-behaviors.ts +503 -4
  264. package/src/oauth/seed-providers.ts +214 -8
  265. package/src/oauth/token-persistence.ts +31 -16
  266. package/src/permissions/defaults.ts +1 -0
  267. package/src/permissions/trust-store.ts +23 -1
  268. package/src/playbooks/playbook-compiler.ts +1 -1
  269. package/src/prompts/system-prompt.ts +18 -2
  270. package/src/providers/anthropic/client.ts +56 -126
  271. package/src/providers/types.ts +7 -1
  272. package/src/runtime/AGENTS.md +9 -0
  273. package/src/runtime/auth/route-policy.ts +6 -3
  274. package/src/runtime/channel-readiness-service.ts +48 -40
  275. package/src/runtime/guardian-reply-router.ts +24 -22
  276. package/src/runtime/http-server.ts +2 -2
  277. package/src/runtime/http-types.ts +2 -0
  278. package/src/runtime/invite-redemption-service.ts +72 -12
  279. package/src/runtime/invite-service.ts +43 -0
  280. package/src/runtime/middleware/twilio-validation.ts +1 -1
  281. package/src/runtime/pending-interactions.ts +2 -2
  282. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  283. package/src/runtime/routes/btw-routes.ts +10 -5
  284. package/src/runtime/routes/conversation-routes.ts +56 -11
  285. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  286. package/src/runtime/routes/integrations/slack/channel.ts +2 -2
  287. package/src/runtime/routes/integrations/telegram.ts +2 -2
  288. package/src/runtime/routes/integrations/twilio.ts +17 -17
  289. package/src/runtime/routes/invite-routes.ts +29 -4
  290. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  291. package/src/runtime/routes/memory-item-routes.ts +503 -0
  292. package/src/runtime/routes/secret-routes.ts +17 -0
  293. package/src/runtime/routes/session-management-routes.ts +3 -3
  294. package/src/runtime/routes/settings-routes.ts +3 -3
  295. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  296. package/src/runtime/routes/workspace-routes.ts +9 -4
  297. package/src/runtime/routes/workspace-utils.ts +8 -2
  298. package/src/schedule/integration-status.ts +26 -19
  299. package/src/security/keychain-broker-client.ts +17 -4
  300. package/src/security/oauth2.ts +6 -7
  301. package/src/security/secure-keys.ts +44 -19
  302. package/src/security/token-manager.ts +46 -39
  303. package/src/services/vercel-deploy.ts +0 -24
  304. package/src/signals/confirm.ts +78 -0
  305. package/src/signals/mcp-reload.ts +18 -0
  306. package/src/skills/catalog-install.ts +74 -18
  307. package/src/skills/skillssh-registry.ts +503 -0
  308. package/src/tools/assets/search.ts +5 -1
  309. package/src/tools/computer-use/definitions.ts +0 -10
  310. package/src/tools/computer-use/registry.ts +1 -1
  311. package/src/tools/credentials/vault.ts +22 -7
  312. package/src/tools/memory/definitions.ts +4 -13
  313. package/src/tools/memory/handlers.test.ts +83 -103
  314. package/src/tools/memory/handlers.ts +50 -85
  315. package/src/tools/network/script-proxy/session-manager.ts +8 -8
  316. package/src/tools/schedule/create.ts +10 -3
  317. package/src/tools/schedule/update.ts +8 -1
  318. package/src/tools/skills/load.ts +25 -2
  319. package/src/watcher/provider-types.ts +1 -1
  320. package/src/watcher/providers/github.ts +1 -1
  321. package/src/watcher/providers/gmail.ts +3 -3
  322. package/src/watcher/providers/google-calendar.ts +3 -3
  323. package/src/watcher/providers/linear.ts +1 -1
  324. package/src/__tests__/clarification-resolver.test.ts +0 -193
  325. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  326. package/src/__tests__/conflict-policy.test.ts +0 -269
  327. package/src/__tests__/conflict-store.test.ts +0 -372
  328. package/src/__tests__/contradiction-checker.test.ts +0 -361
  329. package/src/__tests__/entity-extractor.test.ts +0 -211
  330. package/src/__tests__/entity-search.test.ts +0 -1117
  331. package/src/__tests__/profile-compiler.test.ts +0 -392
  332. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  333. package/src/__tests__/session-profile-injection.test.ts +0 -557
  334. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  335. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  336. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  337. package/src/daemon/session-conflict-gate.ts +0 -167
  338. package/src/daemon/session-dynamic-profile.ts +0 -77
  339. package/src/memory/clarification-resolver.ts +0 -417
  340. package/src/memory/conflict-intent.ts +0 -205
  341. package/src/memory/conflict-policy.ts +0 -127
  342. package/src/memory/conflict-store.ts +0 -410
  343. package/src/memory/contradiction-checker.ts +0 -508
  344. package/src/memory/entity-extractor.ts +0 -535
  345. package/src/memory/format-recall.ts +0 -47
  346. package/src/memory/fts-reconciler.ts +0 -165
  347. package/src/memory/job-handlers/conflict.ts +0 -200
  348. package/src/memory/profile-compiler.ts +0 -195
  349. package/src/memory/recall-cache.ts +0 -117
  350. package/src/memory/search/entity.ts +0 -535
  351. package/src/memory/search/query-expansion.test.ts +0 -70
  352. package/src/memory/search/query-expansion.ts +0 -118
  353. package/src/runtime/routes/mcp-routes.ts +0 -20
@@ -63,20 +63,6 @@ function isToolUseBlock(block: unknown): block is Anthropic.ToolUseBlockParam {
63
63
  );
64
64
  }
65
65
 
66
- /** Type-guard for server_tool_use blocks (e.g. native web search). */
67
- function isServerToolUseBlock(block: unknown): block is {
68
- type: "server_tool_use";
69
- id: string;
70
- name: string;
71
- input: unknown;
72
- } {
73
- return (
74
- typeof block === "object" &&
75
- block != null &&
76
- (block as { type: string }).type === "server_tool_use"
77
- );
78
- }
79
-
80
66
  /** Type-guard for tool_result blocks in Anthropic-formatted content. */
81
67
  function isToolResultBlock(
82
68
  block: unknown,
@@ -88,19 +74,6 @@ function isToolResultBlock(
88
74
  );
89
75
  }
90
76
 
91
- /** Type-guard for web_search_tool_result blocks. */
92
- function isWebSearchToolResultBlock(block: unknown): block is {
93
- type: "web_search_tool_result";
94
- tool_use_id: string;
95
- content: unknown;
96
- } {
97
- return (
98
- typeof block === "object" &&
99
- block != null &&
100
- (block as { type: string }).type === "web_search_tool_result"
101
- );
102
- }
103
-
104
77
  /**
105
78
  * Build a short diagnostic summary of a message array for error logging.
106
79
  * Shows role + block types (with tool_use/tool_result IDs) for each message.
@@ -134,79 +107,55 @@ function buildSyntheticToolResult(
134
107
  };
135
108
  }
136
109
 
137
- function buildSyntheticWebSearchToolResult(
138
- toolUseId: string,
139
- ): Anthropic.ContentBlockParam {
140
- return {
141
- type: "web_search_tool_result",
142
- tool_use_id: toolUseId,
143
- content: {
144
- type: "web_search_tool_result_error",
145
- error_code: "unavailable",
146
- },
147
- } as unknown as Anthropic.ContentBlockParam;
148
- }
149
-
150
- /** Build the appropriate synthetic result block based on whether the ID is for a server tool or regular tool. */
151
- function buildSyntheticResult(
152
- toolUseId: string,
153
- serverToolIds: ReadonlySet<string>,
154
- ): Anthropic.ContentBlockParam {
155
- if (serverToolIds.has(toolUseId)) {
156
- return buildSyntheticWebSearchToolResult(toolUseId);
157
- }
158
- return buildSyntheticToolResult(toolUseId);
159
- }
160
110
 
161
- function getOrderedToolUseIds(content: Anthropic.ContentBlockParam[]): {
162
- ids: string[];
163
- serverToolIds: Set<string>;
164
- } {
111
+ /**
112
+ * Collect ordered IDs of client-side tool_use blocks only.
113
+ * Server-side tools (server_tool_use / web_search_tool_result) are self-paired
114
+ * within the assistant message and do not need cross-message pairing.
115
+ */
116
+ function getOrderedToolUseIds(
117
+ content: Anthropic.ContentBlockParam[],
118
+ ): string[] {
165
119
  const ids: string[] = [];
166
120
  const seen = new Set<string>();
167
- const serverToolIds = new Set<string>();
168
121
  for (const block of content) {
169
122
  if (isToolUseBlock(block)) {
170
123
  if (!seen.has(block.id)) {
171
124
  seen.add(block.id);
172
125
  ids.push(block.id);
173
126
  }
174
- } else if (isServerToolUseBlock(block)) {
175
- if (!seen.has(block.id)) {
176
- seen.add(block.id);
177
- ids.push(block.id);
178
- serverToolIds.add(block.id);
179
- }
180
127
  }
181
128
  }
182
- return { ids, serverToolIds };
129
+ return ids;
183
130
  }
184
131
 
185
132
  function hasOrderedToolResultPrefix(
186
133
  content: Anthropic.ContentBlockParam[],
187
134
  orderedToolUseIds: string[],
188
- serverToolIds: ReadonlySet<string>,
189
135
  ): boolean {
190
136
  if (content.length < orderedToolUseIds.length) return false;
191
137
  for (let idx = 0; idx < orderedToolUseIds.length; idx++) {
192
138
  const block = content[idx];
193
139
  const expectedId = orderedToolUseIds[idx];
194
- if (serverToolIds.has(expectedId)) {
195
- if (!isWebSearchToolResultBlock(block)) return false;
196
- if (block.tool_use_id !== expectedId) return false;
197
- } else {
198
- if (!isToolResultBlock(block)) return false;
199
- if (block.tool_use_id !== expectedId) return false;
200
- }
140
+ if (!isToolResultBlock(block)) return false;
141
+ if (block.tool_use_id !== expectedId) return false;
201
142
  }
202
143
  return true;
203
144
  }
204
145
 
146
+ /**
147
+ * Split an assistant message into:
148
+ * - pairedContent: everything up to and including client-side tool_use blocks
149
+ * - carryoverContent: trailing non-tool blocks after the last tool_use
150
+ *
151
+ * Server-side tools (server_tool_use / web_search_tool_result) are treated as
152
+ * regular content — they are self-paired within the assistant message and must
153
+ * not be separated by the cross-message pairing logic.
154
+ */
205
155
  function splitAssistantForToolPairing(content: Anthropic.ContentBlockParam[]): {
206
156
  pairedContent: Anthropic.ContentBlockParam[];
207
157
  carryoverContent: Anthropic.ContentBlockParam[];
208
158
  toolUseIds: string[];
209
- serverToolIds: Set<string>;
210
159
  } {
211
160
  const leading: Anthropic.ContentBlockParam[] = [];
212
161
  const toolUseBlocks: Anthropic.ContentBlockParam[] = [];
@@ -214,7 +163,7 @@ function splitAssistantForToolPairing(content: Anthropic.ContentBlockParam[]): {
214
163
  let seenToolUse = false;
215
164
 
216
165
  for (const block of content) {
217
- if (isToolUseBlock(block) || isServerToolUseBlock(block)) {
166
+ if (isToolUseBlock(block)) {
218
167
  seenToolUse = true;
219
168
  toolUseBlocks.push(block);
220
169
  continue;
@@ -231,7 +180,6 @@ function splitAssistantForToolPairing(content: Anthropic.ContentBlockParam[]): {
231
180
  pairedContent: content,
232
181
  carryoverContent: [],
233
182
  toolUseIds: [],
234
- serverToolIds: new Set(),
235
183
  };
236
184
  }
237
185
 
@@ -239,19 +187,16 @@ function splitAssistantForToolPairing(content: Anthropic.ContentBlockParam[]): {
239
187
  ...leading,
240
188
  ...toolUseBlocks,
241
189
  ];
242
- const { ids, serverToolIds } = getOrderedToolUseIds(pairedContent);
243
190
  return {
244
191
  pairedContent,
245
192
  carryoverContent: carryover,
246
- toolUseIds: ids,
247
- serverToolIds,
193
+ toolUseIds: getOrderedToolUseIds(pairedContent),
248
194
  };
249
195
  }
250
196
 
251
197
  function normalizeFollowingUserContent(
252
198
  nextContent: Anthropic.ContentBlockParam[],
253
199
  orderedToolUseIds: string[],
254
- serverToolIds: ReadonlySet<string>,
255
200
  ): {
256
201
  toolResultPrefix: Anthropic.ContentBlockParam[];
257
202
  remainingContent: Anthropic.ContentBlockParam[];
@@ -266,41 +211,24 @@ function normalizeFollowingUserContent(
266
211
  if (
267
212
  isToolResultBlock(block) &&
268
213
  pendingIds.has(block.tool_use_id) &&
269
- !matchedById.has(block.tool_use_id) &&
270
- !serverToolIds.has(block.tool_use_id)
214
+ !matchedById.has(block.tool_use_id)
271
215
  ) {
272
216
  matchedById.set(block.tool_use_id, block);
273
217
  continue;
274
218
  }
275
- if (
276
- isWebSearchToolResultBlock(block) &&
277
- pendingIds.has(block.tool_use_id) &&
278
- !matchedById.has(block.tool_use_id) &&
279
- serverToolIds.has(block.tool_use_id)
280
- ) {
281
- matchedById.set(
282
- block.tool_use_id,
283
- block as unknown as Anthropic.ContentBlockParam,
284
- );
285
- continue;
286
- }
287
219
  remaining.push(block);
288
220
  }
289
221
 
290
222
  const missingIds = orderedToolUseIds.filter((id) => !matchedById.has(id));
291
223
  const orderedResults = orderedToolUseIds.map(
292
- (id) => matchedById.get(id) ?? buildSyntheticResult(id, serverToolIds),
224
+ (id) => matchedById.get(id) ?? buildSyntheticToolResult(id),
293
225
  );
294
226
 
295
227
  return {
296
228
  toolResultPrefix: orderedResults,
297
229
  remainingContent: remaining,
298
230
  missingIds,
299
- hadOrderedPrefix: hasOrderedToolResultPrefix(
300
- nextContent,
301
- orderedToolUseIds,
302
- serverToolIds,
303
- ),
231
+ hadOrderedPrefix: hasOrderedToolResultPrefix(nextContent, orderedToolUseIds),
304
232
  };
305
233
  }
306
234
 
@@ -328,7 +256,7 @@ function ensureToolPairing(
328
256
  }
329
257
 
330
258
  const content = Array.isArray(msg.content) ? msg.content : [];
331
- const { pairedContent, carryoverContent, toolUseIds, serverToolIds } =
259
+ const { pairedContent, carryoverContent, toolUseIds } =
332
260
  splitAssistantForToolPairing(content);
333
261
 
334
262
  if (toolUseIds.length === 0) {
@@ -337,7 +265,7 @@ function ensureToolPairing(
337
265
  continue;
338
266
  }
339
267
 
340
- // Assistant message — push the paired portion (pre-tool text + tool_use/server_tool_use blocks)
268
+ // Assistant message — push the paired portion (pre-tool text + tool_use blocks)
341
269
  result.push({
342
270
  role: "assistant" as const,
343
271
  content: pairedContent,
@@ -358,11 +286,7 @@ function ensureToolPairing(
358
286
  const next = messages[i + 1];
359
287
  if (next && next.role === "user") {
360
288
  const nextContent = Array.isArray(next.content) ? next.content : [];
361
- const normalized = normalizeFollowingUserContent(
362
- nextContent,
363
- toolUseIds,
364
- serverToolIds,
365
- );
289
+ const normalized = normalizeFollowingUserContent(nextContent, toolUseIds);
366
290
  if (normalized.missingIds.length > 0) {
367
291
  log.warn(
368
292
  {
@@ -427,9 +351,7 @@ function ensureToolPairing(
427
351
  );
428
352
  result.push({
429
353
  role: "user" as const,
430
- content: toolUseIds.map((id) =>
431
- buildSyntheticResult(id, serverToolIds),
432
- ),
354
+ content: toolUseIds.map((id) => buildSyntheticToolResult(id)),
433
355
  });
434
356
 
435
357
  // If the assistant contained collapsed post-tool text, preserve it as a
@@ -445,13 +367,14 @@ function ensureToolPairing(
445
367
  }
446
368
  }
447
369
 
448
- // Self-validation: verify no tool_use/tool_result mismatches remain
370
+ // Self-validation: verify no client-side tool_use/tool_result mismatches remain.
371
+ // Server-side tools (server_tool_use / web_search_tool_result) are self-paired
372
+ // within assistant messages and are not validated here.
449
373
  for (let j = 0; j < result.length; j++) {
450
374
  const m = result[j];
451
375
  if (m.role !== "assistant") continue;
452
376
  const c = Array.isArray(m.content) ? m.content : [];
453
- const { ids: validationIds, serverToolIds: validationServerToolIds } =
454
- getOrderedToolUseIds(c);
377
+ const validationIds = getOrderedToolUseIds(c);
455
378
  if (validationIds.length === 0) continue;
456
379
 
457
380
  const nxt = result[j + 1];
@@ -459,20 +382,9 @@ function ensureToolPairing(
459
382
  nxt && nxt.role === "user" && Array.isArray(nxt.content)
460
383
  ? nxt.content
461
384
  : [];
462
- if (
463
- !hasOrderedToolResultPrefix(
464
- nxtContent,
465
- validationIds,
466
- validationServerToolIds,
467
- )
468
- ) {
385
+ if (!hasOrderedToolResultPrefix(nxtContent, validationIds)) {
469
386
  const unmatchedIds = validationIds.filter((id, idx) => {
470
387
  const block = nxtContent[idx];
471
- if (validationServerToolIds.has(id)) {
472
- return !(
473
- isWebSearchToolResultBlock(block) && block.tool_use_id === id
474
- );
475
- }
476
388
  return !(isToolResultBlock(block) && block.tool_use_id === id);
477
389
  });
478
390
  log.error(
@@ -768,10 +680,14 @@ export class AnthropicProvider implements Provider {
768
680
  onEvent?.({ type: "text_delta", text: " " });
769
681
  }
770
682
  hasSeenTextBlock = true;
771
- } else if (event.type === "content_block_start") {
772
- // Reset on non-text blocks so that text separated by tool_use
773
- // (text -> tool_use -> text) doesn't get a spurious leading space
774
- // in the second text segment.
683
+ } else if (
684
+ event.type === "content_block_start" &&
685
+ event.content_block.type === "tool_use"
686
+ ) {
687
+ // Reset only for client-side tool_use blocks, which create visual
688
+ // separators in the UI. Server-side tool blocks (server_tool_use,
689
+ // web_search_tool_result) are transparent in the text stream and
690
+ // need the space preserved between surrounding text blocks.
775
691
  hasSeenTextBlock = false;
776
692
  }
777
693
  if (
@@ -796,6 +712,20 @@ export class AnthropicProvider implements Provider {
796
712
  type: "server_tool_start",
797
713
  name: event.content_block.name,
798
714
  toolUseId: event.content_block.id,
715
+ input: (
716
+ event.content_block as { input?: Record<string, unknown> }
717
+ ).input ?? {},
718
+ });
719
+ }
720
+ if (
721
+ event.type === "content_block_start" &&
722
+ event.content_block.type === "web_search_tool_result"
723
+ ) {
724
+ onEvent?.({
725
+ type: "server_tool_complete",
726
+ toolUseId: (
727
+ event.content_block as { tool_use_id: string }
728
+ ).tool_use_id,
799
729
  });
800
730
  }
801
731
  if (event.type === "content_block_stop") {
@@ -117,7 +117,13 @@ export type ProviderEvent =
117
117
  toolUseId: string;
118
118
  accumulatedJson: string;
119
119
  }
120
- | { type: "server_tool_start"; name: string; toolUseId: string };
120
+ | {
121
+ type: "server_tool_start";
122
+ name: string;
123
+ toolUseId: string;
124
+ input: Record<string, unknown>;
125
+ }
126
+ | { type: "server_tool_complete"; toolUseId: string };
121
127
 
122
128
  export interface SendMessageConfig {
123
129
  model?: string;
@@ -43,6 +43,15 @@ Host file allows the assistant to perform file operations (read, write, edit) on
43
43
  - `POST /v1/host-file-result` — `{ requestId, content, isError }`
44
44
  - **Tracking**: Uses the same `pending-interactions` tracker as approvals and host bash, with `kind: "host_file"`. The endpoint validates the interaction kind before resolving.
45
45
 
46
+ ### Host CU (desktop proxy computer-use execution)
47
+
48
+ Host CU allows the assistant to proxy computer-use actions (screenshots, mouse/keyboard input) to the desktop host via the client, following the same pattern as host bash and host file.
49
+
50
+ - **Discovery**: Clients discover pending host CU requests via SSE events (`host_cu_request`) which include a `requestId`.
51
+ - **Resolution**: Clients execute the CU action on the host and respond via:
52
+ - `POST /v1/host-cu-result` — `{ requestId, axTree?, axDiff?, screenshot?, screenshotWidthPx?, screenshotHeightPx?, screenWidthPt?, screenHeightPt?, executionResult?, executionError?, secondaryWindows?, userGuidance? }`
53
+ - **Tracking**: Uses the same `pending-interactions` tracker as the other host proxy types, with `kind: "host_cu"`. Registration happens in `conversation-routes.ts` and the route handler is in `host-cu-routes.ts`.
54
+
46
55
  ### Channel approvals (Telegram, Slack)
47
56
 
48
57
  Channel approval flows use `requestId` (not `runId`) as the primary identifier:
@@ -347,6 +347,12 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
347
347
  { endpoint: "skills:DELETE", scopes: ["settings.write"] },
348
348
  { endpoint: "skills:PATCH", scopes: ["settings.write"] },
349
349
 
350
+ // Memory items
351
+ { endpoint: "memory-items:GET", scopes: ["settings.read"] },
352
+ { endpoint: "memory-items:POST", scopes: ["settings.write"] },
353
+ { endpoint: "memory-items:PATCH", scopes: ["settings.write"] },
354
+ { endpoint: "memory-items:DELETE", scopes: ["settings.write"] },
355
+
350
356
  // Trust rule CRUD management
351
357
  { endpoint: "trust-rules/manage:GET", scopes: ["settings.read"] },
352
358
  { endpoint: "trust-rules/manage:POST", scopes: ["settings.write"] },
@@ -378,9 +384,6 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
378
384
  // Delivery ack
379
385
  { endpoint: "channels/delivery-ack", scopes: ["internal.write"] },
380
386
 
381
- // MCP
382
- { endpoint: "mcp/reload", scopes: ["settings.write"] },
383
-
384
387
  // Migrations
385
388
  { endpoint: "migrations/validate", scopes: ["settings.write"] },
386
389
  { endpoint: "migrations/export", scopes: ["settings.write"] },
@@ -4,7 +4,7 @@ import { getChannelInvitePolicy } from "../channels/config.js";
4
4
  import { loadRawConfig } from "../config/loader.js";
5
5
  import { getEmailService } from "../email/service.js";
6
6
  import { credentialKey } from "../security/credential-key.js";
7
- import { getSecureKey } from "../security/secure-keys.js";
7
+ import { getSecureKeyAsync } from "../security/secure-keys.js";
8
8
  import { resolveWhatsAppDisplayNumber } from "./channel-invite-transports/whatsapp.js";
9
9
  import type {
10
10
  ChannelId,
@@ -46,13 +46,13 @@ function check(
46
46
  }
47
47
 
48
48
  /** Check that a secure credential key exists. */
49
- function checkCredential(
49
+ async function checkCredential(
50
50
  name: string,
51
51
  service: string,
52
52
  field: string,
53
53
  label: string,
54
- ): ReadinessCheckResult {
55
- const exists = !!getSecureKey(credentialKey(service, field));
54
+ ): Promise<ReadinessCheckResult> {
55
+ const exists = !!(await getSecureKeyAsync(credentialKey(service, field)));
56
56
  return check(
57
57
  name,
58
58
  exists,
@@ -77,7 +77,7 @@ function checkIngress(): ReadinessCheckResult {
77
77
  const voiceProbe: ChannelProbe = {
78
78
  channel: "phone",
79
79
  async runLocalChecks(): Promise<ReadinessCheckResult[]> {
80
- const hasCreds = hasTwilioCredentials();
80
+ const hasCreds = await hasTwilioCredentials();
81
81
  const hasPhone = !!resolveTwilioPhoneNumber();
82
82
  const ingress = checkIngress();
83
83
 
@@ -117,26 +117,33 @@ const voiceProbe: ChannelProbe = {
117
117
 
118
118
  const telegramProbe: ChannelProbe = {
119
119
  channel: "telegram",
120
- runLocalChecks: () => [
121
- checkCredential("bot_token", "telegram", "bot_token", "Telegram bot token"),
122
- checkCredential(
123
- "webhook_secret",
124
- "telegram",
125
- "webhook_secret",
126
- "Telegram webhook secret",
127
- ),
128
- checkIngress(),
129
- ],
120
+ async runLocalChecks(): Promise<ReadinessCheckResult[]> {
121
+ return [
122
+ await checkCredential(
123
+ "bot_token",
124
+ "telegram",
125
+ "bot_token",
126
+ "Telegram bot token",
127
+ ),
128
+ await checkCredential(
129
+ "webhook_secret",
130
+ "telegram",
131
+ "webhook_secret",
132
+ "Telegram webhook secret",
133
+ ),
134
+ checkIngress(),
135
+ ];
136
+ },
130
137
  };
131
138
 
132
139
  // ── Email Probe ─────────────────────────────────────────────────────────────
133
140
 
134
141
  const emailProbe: ChannelProbe = {
135
142
  channel: "email",
136
- runLocalChecks(): ReadinessCheckResult[] {
143
+ async runLocalChecks(): Promise<ReadinessCheckResult[]> {
137
144
  const hasApiKey = !!(
138
- getSecureKey("agentmail") ||
139
- getSecureKey(credentialKey("agentmail", "api_key"))
145
+ (await getSecureKeyAsync("agentmail")) ||
146
+ (await getSecureKeyAsync(credentialKey("agentmail", "api_key")))
140
147
  );
141
148
  const invitePolicy = getChannelInvitePolicy("email");
142
149
  return [
@@ -155,12 +162,11 @@ const emailProbe: ChannelProbe = {
155
162
  checkIngress(),
156
163
  ];
157
164
  },
158
- // runRemoteChecks UNCHANGED — keep the existing inbox_configured check as-is
159
165
  async runRemoteChecks(): Promise<ReadinessCheckResult[]> {
160
166
  // Only worth checking if the API key is present
161
167
  const hasApiKey = !!(
162
- getSecureKey("agentmail") ||
163
- getSecureKey(credentialKey("agentmail", "api_key"))
168
+ (await getSecureKeyAsync("agentmail")) ||
169
+ (await getSecureKeyAsync(credentialKey("agentmail", "api_key")))
164
170
  );
165
171
  if (!hasApiKey) return [];
166
172
 
@@ -193,29 +199,29 @@ const emailProbe: ChannelProbe = {
193
199
 
194
200
  const whatsappProbe: ChannelProbe = {
195
201
  channel: "whatsapp",
196
- runLocalChecks(): ReadinessCheckResult[] {
202
+ async runLocalChecks(): Promise<ReadinessCheckResult[]> {
197
203
  const displayNumber = resolveWhatsAppDisplayNumber();
198
204
  const invitePolicy = getChannelInvitePolicy("whatsapp");
199
205
  return [
200
- checkCredential(
206
+ await checkCredential(
201
207
  "whatsapp_phone_number_id",
202
208
  "whatsapp",
203
209
  "phone_number_id",
204
210
  "WhatsApp phone number ID",
205
211
  ),
206
- checkCredential(
212
+ await checkCredential(
207
213
  "whatsapp_access_token",
208
214
  "whatsapp",
209
215
  "access_token",
210
216
  "WhatsApp access token",
211
217
  ),
212
- checkCredential(
218
+ await checkCredential(
213
219
  "whatsapp_app_secret",
214
220
  "whatsapp",
215
221
  "app_secret",
216
222
  "WhatsApp app secret",
217
223
  ),
218
- checkCredential(
224
+ await checkCredential(
219
225
  "whatsapp_webhook_verify_token",
220
226
  "whatsapp",
221
227
  "webhook_verify_token",
@@ -242,20 +248,22 @@ const whatsappProbe: ChannelProbe = {
242
248
 
243
249
  const slackProbe: ChannelProbe = {
244
250
  channel: "slack",
245
- runLocalChecks: () => [
246
- checkCredential(
247
- "bot_token",
248
- "slack_channel",
249
- "bot_token",
250
- "Slack bot token",
251
- ),
252
- checkCredential(
253
- "app_token",
254
- "slack_channel",
255
- "app_token",
256
- "Slack app token",
257
- ),
258
- ],
251
+ async runLocalChecks(): Promise<ReadinessCheckResult[]> {
252
+ return [
253
+ await checkCredential(
254
+ "bot_token",
255
+ "slack_channel",
256
+ "bot_token",
257
+ "Slack bot token",
258
+ ),
259
+ await checkCredential(
260
+ "app_token",
261
+ "slack_channel",
262
+ "app_token",
263
+ "Slack app token",
264
+ ),
265
+ ];
266
+ },
259
267
  };
260
268
 
261
269
  // ── Service ─────────────────────────────────────────────────────────────────
@@ -30,6 +30,7 @@ import {
30
30
  type CanonicalGuardianRequest,
31
31
  getCanonicalGuardianRequest,
32
32
  getCanonicalGuardianRequestByCode,
33
+ isRequestExpired,
33
34
  listCanonicalGuardianRequests,
34
35
  } from "../memory/canonical-guardian-store.js";
35
36
  import {
@@ -198,49 +199,50 @@ function findPendingCanonicalRequests(
198
199
  pendingRequestIds?: string[],
199
200
  conversationId?: string,
200
201
  ): CanonicalGuardianRequest[] {
202
+ let results: CanonicalGuardianRequest[];
203
+
201
204
  // When explicit IDs are provided, look them up directly
202
205
  if (pendingRequestIds) {
203
206
  if (pendingRequestIds.length === 0) {
204
207
  return [];
205
208
  }
206
- return pendingRequestIds
209
+ results = pendingRequestIds
207
210
  .map(getCanonicalGuardianRequest)
208
211
  .filter((r): r is CanonicalGuardianRequest => r?.status === "pending");
209
- }
210
-
211
- // Query by guardian identity when available
212
- if (actor.actorExternalUserId) {
213
- return listCanonicalGuardianRequests({
212
+ } else if (actor.actorExternalUserId) {
213
+ // Query by guardian identity when available
214
+ results = listCanonicalGuardianRequests({
214
215
  status: "pending",
215
216
  guardianExternalUserId: actor.actorExternalUserId,
216
217
  });
217
- }
218
-
219
- // Actors without an actorExternalUserId: scope by conversationId so the NL
220
- // path can discover pending requests bound to this conversation.
221
- // Include guardianPrincipalId filter when available so the guardian only
222
- // sees requests they are authorized to act on.
223
- if (conversationId) {
224
- return listCanonicalGuardianRequests({
218
+ } else if (conversationId) {
219
+ // Actors without an actorExternalUserId: scope by conversationId so the NL
220
+ // path can discover pending requests bound to this conversation.
221
+ // Include guardianPrincipalId filter when available so the guardian only
222
+ // sees requests they are authorized to act on.
223
+ results = listCanonicalGuardianRequests({
225
224
  status: "pending",
226
225
  conversationId,
227
226
  ...(actor.guardianPrincipalId
228
227
  ? { guardianPrincipalId: actor.guardianPrincipalId }
229
228
  : {}),
230
229
  });
231
- }
232
-
233
- // Actors with a guardianPrincipalId but no actorExternalUserId or
234
- // conversationId: query by principal so desktop sessions can still
235
- // discover pending guardian work via their bound principal.
236
- if (actor.guardianPrincipalId) {
237
- return listCanonicalGuardianRequests({
230
+ } else if (actor.guardianPrincipalId) {
231
+ // Actors with a guardianPrincipalId but no actorExternalUserId or
232
+ // conversationId: query by principal so desktop sessions can still
233
+ // discover pending guardian work via their bound principal.
234
+ results = listCanonicalGuardianRequests({
238
235
  status: "pending",
239
236
  guardianPrincipalId: actor.guardianPrincipalId,
240
237
  });
238
+ } else {
239
+ return [];
241
240
  }
242
241
 
243
- return [];
242
+ // Exclude requests that have passed their expiresAt deadline — they can
243
+ // no longer be resolved and should not trigger disambiguation or NL
244
+ // classification.
245
+ return results.filter((r) => !isRequestExpired(r));
244
246
  }
245
247
 
246
248
  /** Map an approval action string to the NL engine's allowed actions for guardians. */
@@ -135,7 +135,7 @@ import { telegramRouteDefinitions } from "./routes/integrations/telegram.js";
135
135
  import { twilioRouteDefinitions } from "./routes/integrations/twilio.js";
136
136
  import { inviteRouteDefinitions } from "./routes/invite-routes.js";
137
137
  import { logExportRouteDefinitions } from "./routes/log-export-routes.js";
138
- import { mcpRouteDefinitions } from "./routes/mcp-routes.js";
138
+ import { memoryItemRouteDefinitions } from "./routes/memory-item-routes.js";
139
139
  import { migrationRouteDefinitions } from "./routes/migration-routes.js";
140
140
  import type { PairingHandlerContext } from "./routes/pairing-routes.js";
141
141
  import {
@@ -723,9 +723,9 @@ export class RuntimeHttpServer {
723
723
  ...secretRouteDefinitions(),
724
724
  ...identityRouteDefinitions(),
725
725
  ...debugRouteDefinitions(),
726
- ...mcpRouteDefinitions(),
727
726
  ...usageRouteDefinitions(),
728
727
  ...workspaceRouteDefinitions(),
728
+ ...memoryItemRouteDefinitions(),
729
729
  ...settingsRouteDefinitions(),
730
730
  ...scheduleRouteDefinitions({
731
731
  sendMessageDeps: this.sendMessageDeps,
@@ -231,6 +231,8 @@ export interface RuntimeAttachmentMetadata {
231
231
  mimeType: string;
232
232
  sizeBytes: number;
233
233
  kind: string;
234
+ data?: string;
235
+ thumbnailData?: string;
234
236
  }
235
237
 
236
238
  export interface RuntimeMessagePayload {