@vellumai/assistant 0.11.7 → 0.11.8-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. package/ARCHITECTURE.md +10 -10
  2. package/Dockerfile +1 -0
  3. package/docs/architecture/security.md +1 -1
  4. package/docs/error-handling.md +1 -2
  5. package/docs/guardian-request-flow.md +51 -5
  6. package/docs/trusted-contact-access.md +13 -12
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/avatar-catalog/package.json +19 -0
  9. package/{src/avatar/character-components.ts → node_modules/@vellumai/avatar-catalog/src/catalog.ts} +12 -44
  10. package/node_modules/@vellumai/avatar-catalog/src/colors.ts +19 -0
  11. package/node_modules/@vellumai/avatar-catalog/src/index.ts +25 -0
  12. package/node_modules/@vellumai/avatar-catalog/src/types.ts +47 -0
  13. package/node_modules/@vellumai/avatar-catalog/tsconfig.json +20 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  17. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
  18. package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +43 -12
  19. package/node_modules/@vellumai/environments/package.json +2 -1
  20. package/node_modules/@vellumai/environments/src/__tests__/test-path-guard.test.ts +89 -0
  21. package/node_modules/@vellumai/environments/src/test-path-guard.ts +113 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  23. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
  24. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  25. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
  26. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +8 -18
  27. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +46 -0
  28. package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +1 -0
  29. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +46 -0
  30. package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +62 -21
  31. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +14 -0
  32. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +53 -0
  33. package/node_modules/@vellumai/gateway-client/src/index.ts +11 -5
  34. package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +1 -0
  35. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  36. package/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
  37. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  38. package/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
  39. package/openapi.yaml +481 -9
  40. package/package.json +3 -1
  41. package/scripts/sync-llm-catalog.ts +3 -0
  42. package/src/__tests__/agent-loop-callsite-precedence.test.ts +16 -0
  43. package/src/__tests__/agent-loop-compacted-history-transform.test.ts +304 -0
  44. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +3 -0
  45. package/src/__tests__/agent-wake-override-profile.test.ts +3 -0
  46. package/src/__tests__/anthropic-provider.test.ts +56 -0
  47. package/src/__tests__/approval-cascade.test.ts +0 -13
  48. package/src/__tests__/attachments-store.test.ts +117 -6
  49. package/src/__tests__/attachments.test.ts +122 -0
  50. package/src/__tests__/bundled-asset.test.ts +22 -1
  51. package/src/__tests__/channel-approval.test.ts +14 -7
  52. package/src/__tests__/channel-approvals.test.ts +3 -3
  53. package/src/__tests__/channel-delivery-store.test.ts +58 -0
  54. package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -0
  55. package/src/__tests__/channel-invite-transport.test.ts +1 -0
  56. package/src/__tests__/channel-readiness-slack-remote.test.ts +3 -3
  57. package/src/__tests__/compaction-events.test.ts +0 -12
  58. package/src/__tests__/compactor-call-site-logging.test.ts +7 -1
  59. package/src/__tests__/compactor-retained-image-validation.test.ts +15 -0
  60. package/src/__tests__/config-schema.test.ts +35 -0
  61. package/src/__tests__/contacts-write.test.ts +141 -3
  62. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -13
  63. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -12
  64. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -13
  65. package/src/__tests__/conversation-agent-loop.test.ts +95 -13
  66. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -13
  67. package/src/__tests__/conversation-load-history-repair.test.ts +418 -0
  68. package/src/__tests__/conversation-media-retry.test.ts +33 -2
  69. package/src/__tests__/conversation-pairing.test.ts +54 -0
  70. package/src/__tests__/conversation-process-callsite.test.ts +0 -12
  71. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -13
  72. package/src/__tests__/conversation-queue.test.ts +4 -13
  73. package/src/__tests__/conversation-retry-route.test.ts +5 -0
  74. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  75. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  76. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  77. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  78. package/src/__tests__/conversation-sight-frames.test.ts +615 -0
  79. package/src/__tests__/conversation-slash-queue.test.ts +0 -13
  80. package/src/__tests__/conversation-slash-unknown.test.ts +0 -13
  81. package/src/__tests__/conversation-speed-override.test.ts +0 -12
  82. package/src/__tests__/conversation-store-ephemeral.test.ts +0 -12
  83. package/src/__tests__/conversation-summarize-route.test.ts +5 -0
  84. package/src/__tests__/conversation-summarize-up-to.test.ts +102 -0
  85. package/src/__tests__/conversation-wait-for-idle.test.ts +0 -12
  86. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -5
  87. package/src/__tests__/conversation-workspace-injection.test.ts +0 -17
  88. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -17
  89. package/src/__tests__/credential-health-service.test.ts +45 -0
  90. package/src/__tests__/credential-record-retire.test.ts +256 -0
  91. package/src/__tests__/credential-record-write-through.test.ts +8 -4
  92. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +0 -4
  93. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +0 -4
  94. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
  95. package/src/__tests__/delete-propagation.test.ts +121 -3
  96. package/src/__tests__/evict-conversations-for-reload.test.ts +46 -6
  97. package/src/__tests__/file-ops-service.test.ts +8 -3
  98. package/src/__tests__/gemini-provider.test.ts +64 -0
  99. package/src/__tests__/guardian-action-service-scope.test.ts +118 -0
  100. package/src/__tests__/guardian-card-withdrawal.test.ts +299 -5
  101. package/src/__tests__/guardian-expiry-notifier.test.ts +117 -13
  102. package/src/__tests__/guardian-gateway-sim.test.ts +119 -0
  103. package/src/__tests__/guardian-gateway-sim.ts +56 -20
  104. package/src/__tests__/guardian-reply-router-answer-mode.test.ts +6 -0
  105. package/src/__tests__/guardian-request-status-sync.test.ts +162 -0
  106. package/src/__tests__/guardian-wait-controller.test.ts +6 -0
  107. package/src/__tests__/headless-browser-mode.test.ts +27 -0
  108. package/src/__tests__/helpers/mock-conversation.ts +2 -0
  109. package/src/__tests__/list-messages-system-card.test.ts +70 -0
  110. package/src/__tests__/llm-catalog-parity.test.ts +2 -0
  111. package/src/__tests__/managed-profile-guard.test.ts +2 -2
  112. package/src/__tests__/media-resolve-image-validation.test.ts +106 -0
  113. package/src/__tests__/media-stream-server-integration.test.ts +6 -0
  114. package/src/__tests__/non-member-access-request.test.ts +12 -0
  115. package/src/__tests__/notification-discord-adapter.test.ts +76 -4
  116. package/src/__tests__/oauth-commands-routes.test.ts +21 -0
  117. package/src/__tests__/openai-provider.test.ts +63 -0
  118. package/src/__tests__/outbound-slack-persistence.test.ts +114 -2
  119. package/src/__tests__/persist-unsendable-image-downscale.test.ts +63 -1
  120. package/src/__tests__/persist-unsendable-image.test.ts +19 -0
  121. package/src/__tests__/plugin-app-serve-routes.test.ts +2 -2
  122. package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
  123. package/src/__tests__/process-message-background-slack.test.ts +1 -1
  124. package/src/__tests__/process-message-display-content.test.ts +1 -1
  125. package/src/__tests__/processing-flag-persist-failure.test.ts +0 -5
  126. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +0 -17
  127. package/src/__tests__/qdrant-manager.test.ts +6 -0
  128. package/src/__tests__/reaction-persistence.test.ts +58 -62
  129. package/src/__tests__/retry-thinking-adaptive-only.test.ts +67 -0
  130. package/src/__tests__/server-history-render.test.ts +22 -0
  131. package/src/__tests__/sight-frame-retention.test.ts +362 -0
  132. package/src/__tests__/slack-reaction-approvals.test.ts +44 -24
  133. package/src/__tests__/slack-reaction-guardian-approval.test.ts +29 -1
  134. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  135. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  136. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  137. package/src/__tests__/subagent-notify-stale-rebuild.test.ts +85 -0
  138. package/src/__tests__/subagent-tool-gate-mode.test.ts +4 -4
  139. package/src/__tests__/terminal-tools.test.ts +9 -9
  140. package/src/__tests__/tool-result-spool.test.ts +5 -8
  141. package/src/__tests__/usage-attribution.test.ts +26 -0
  142. package/src/acp/__tests__/acp-auth-marker-store.test.ts +191 -0
  143. package/src/acp/__tests__/acp-claude-oauth.test.ts +262 -0
  144. package/src/acp/__tests__/acp-connect-card-state.test.ts +17 -0
  145. package/src/acp/__tests__/helpers/acp-history-db.ts +10 -3
  146. package/src/acp/acp-auth-marker-store.ts +196 -0
  147. package/src/acp/acp-claude-oauth.ts +154 -4
  148. package/src/acp/acp-connect-card-state.ts +49 -8
  149. package/src/acp/agent-process.ts +1 -0
  150. package/src/acp/auto-install.ts +1 -0
  151. package/src/acp/client-handler.ts +1 -0
  152. package/src/acp/prepare-agent-env.ts +79 -1
  153. package/src/acp/resolve-agent.ts +12 -0
  154. package/src/acp/session-manager.test.ts +183 -0
  155. package/src/acp/session-manager.ts +110 -6
  156. package/src/acp/types.ts +17 -0
  157. package/src/agent/attachments.ts +7 -2
  158. package/src/agent/loop.ts +59 -1
  159. package/src/api/events/contact-form-closed.ts +32 -0
  160. package/src/api/events/contact-record-request.ts +63 -0
  161. package/src/api/events/contact-request.ts +10 -3
  162. package/src/api/index.ts +20 -0
  163. package/src/api/responses/conversation-message.ts +20 -0
  164. package/src/api/responses/home.ts +101 -0
  165. package/src/approvals/AGENTS.md +1 -1
  166. package/src/approvals/guardian-card-withdrawal.ts +127 -30
  167. package/src/approvals/guardian-decision-primitive.ts +9 -0
  168. package/src/approvals/guardian-expiry-notifier.ts +22 -16
  169. package/src/approvals/guardian-request-status-sync.ts +98 -0
  170. package/src/avatar/svg-compositor.ts +1 -1
  171. package/src/avatar/traits-png-sync.ts +1 -1
  172. package/src/bundler/app-compiler.ts +1 -0
  173. package/src/bundler/compiler-tools.ts +1 -0
  174. package/src/bundler/package-resolver.ts +1 -0
  175. package/src/calls/__tests__/voice-session-bridge.test.ts +183 -3
  176. package/src/calls/guardian-action-sweep.ts +6 -1
  177. package/src/calls/voice-session-bridge.ts +46 -1
  178. package/src/channels/__tests__/gateway-guardian-requests.test.ts +13 -18
  179. package/src/channels/gateway-guardian-requests.ts +37 -18
  180. package/src/cli/__tests__/contacts-record-prompt-cli.test.ts +329 -0
  181. package/src/cli/commands/__tests__/browser.test.ts +65 -1
  182. package/src/cli/commands/browser.ts +30 -10
  183. package/src/cli/commands/contacts.help.ts +173 -11
  184. package/src/cli/commands/contacts.ts +343 -4
  185. package/src/cli/commands/db/refresh.ts +1 -0
  186. package/src/cli/commands/oauth/index.help.ts +6 -3
  187. package/src/cli/commands/oauth/request.test.ts +37 -0
  188. package/src/cli/commands/oauth/request.ts +7 -6
  189. package/src/cli/lib/bundled-marketplace.json +1 -1
  190. package/src/cli/lib/install-from-github.ts +20 -19
  191. package/src/cli/lib/install-plugin-dependencies.ts +3 -5
  192. package/src/cli/lib/merge-plugin-tree.ts +1 -0
  193. package/src/cli/lib/publish-plugin.ts +4 -1
  194. package/src/config/__tests__/balanced-model-experiment.test.ts +63 -31
  195. package/src/config/__tests__/default-profile-catalog.test.ts +2 -2
  196. package/src/config/balanced-model-experiment.ts +15 -9
  197. package/src/config/bundled-skills/contacts/SKILL.md +61 -18
  198. package/src/config/default-profile-catalog.ts +12 -11
  199. package/src/config/feature-flag-registry.json +22 -13
  200. package/src/config/schema.ts +2 -0
  201. package/src/config/schemas/__tests__/live-voice.test.ts +13 -0
  202. package/src/config/schemas/live-voice.ts +7 -0
  203. package/src/config/schemas/sight.ts +45 -0
  204. package/src/config/skills.ts +2 -1
  205. package/src/contacts/__tests__/mirror-reconciler.test.ts +352 -0
  206. package/src/contacts/contact-store.ts +47 -4
  207. package/src/contacts/contacts-write.ts +5 -2
  208. package/src/contacts/mirror-reconciler.ts +210 -0
  209. package/src/contacts/mirror-tombstones.ts +42 -0
  210. package/src/contacts/notify-contacts-changed.ts +11 -2
  211. package/src/context/compactor.ts +47 -19
  212. package/src/context/tool-result-spool.ts +21 -14
  213. package/src/conversations/message-consolidation.ts +2 -2
  214. package/src/credential-health/credential-health-service.ts +37 -6
  215. package/src/daemon/conversation-agent-loop-handlers.ts +19 -2
  216. package/src/daemon/conversation-agent-loop.ts +100 -1
  217. package/src/daemon/conversation-media-retry.ts +11 -6
  218. package/src/daemon/conversation-messaging.ts +293 -30
  219. package/src/daemon/conversation-runtime-assembly.ts +67 -0
  220. package/src/daemon/conversation-sight-frames.ts +187 -0
  221. package/src/daemon/conversation-store.ts +18 -28
  222. package/src/daemon/conversation-surfaces.ts +12 -2
  223. package/src/daemon/conversation-tool-setup.ts +39 -10
  224. package/src/daemon/conversation.ts +159 -16
  225. package/src/daemon/daemon-control.ts +6 -17
  226. package/src/daemon/handlers/config-model.test.ts +1 -0
  227. package/src/daemon/handlers/conversations.ts +10 -11
  228. package/src/daemon/install-assistant-command.ts +1 -0
  229. package/src/daemon/lifecycle.ts +10 -13
  230. package/src/daemon/message-types/sync.ts +4 -0
  231. package/src/daemon/process-message.ts +2 -17
  232. package/src/daemon/reaction-history-render.test.ts +162 -0
  233. package/src/daemon/reaction-history-render.ts +115 -0
  234. package/src/daemon/reaction-record.test.ts +162 -0
  235. package/src/daemon/reaction-record.ts +83 -0
  236. package/src/daemon/shutdown-handlers.ts +0 -2
  237. package/src/daemon/summarize-boundary.ts +12 -0
  238. package/src/daemon/video-thumbnail.ts +1 -1
  239. package/src/home/feed-types.ts +4 -0
  240. package/src/home/feed-writer.ts +25 -0
  241. package/src/live-voice/__tests__/live-voice-attach-frame.test.ts +679 -0
  242. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +8 -5
  243. package/src/live-voice/__tests__/live-voice-photo.test.ts +637 -1
  244. package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +787 -0
  245. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +347 -0
  246. package/src/live-voice/__tests__/live-voice-vad.test.ts +109 -0
  247. package/src/live-voice/live-voice-photo.ts +420 -65
  248. package/src/live-voice/live-voice-session.ts +311 -27
  249. package/src/live-voice/protocol.ts +147 -11
  250. package/src/messaging/provider-message-metadata.ts +7 -0
  251. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +113 -1
  252. package/src/messaging/providers/channel-transport.ts +37 -0
  253. package/src/messaging/providers/discord/api.ts +4 -4
  254. package/src/messaging/providers/discord/send.test.ts +177 -2
  255. package/src/messaging/providers/discord/send.ts +197 -10
  256. package/src/messaging/providers/discord/transport.ts +10 -0
  257. package/src/messaging/providers/index.ts +42 -0
  258. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +129 -3
  259. package/src/messaging/providers/slack/__tests__/auth.test.ts +35 -0
  260. package/src/messaging/providers/slack/__tests__/message-content.test.ts +152 -0
  261. package/src/messaging/providers/slack/adapter.ts +112 -27
  262. package/src/messaging/providers/slack/api.test.ts +10 -0
  263. package/src/messaging/providers/slack/auth.ts +32 -6
  264. package/src/messaging/providers/slack/client.ts +0 -15
  265. package/src/messaging/providers/slack/message-content.ts +137 -0
  266. package/src/messaging/providers/slack/send.test.ts +43 -2
  267. package/src/messaging/providers/slack/send.ts +18 -5
  268. package/src/messaging/providers/slack/transport.ts +10 -0
  269. package/src/messaging/providers/slack/types.ts +21 -2
  270. package/src/messaging/providers/telegram-bot/adapter.ts +5 -1
  271. package/src/messaging/providers/telegram-bot/send.test.ts +49 -2
  272. package/src/messaging/providers/telegram-bot/send.ts +41 -1
  273. package/src/messaging/providers/telegram-bot/transport.ts +10 -0
  274. package/src/messaging/providers/telegram-bot/withdraw.ts +13 -5
  275. package/src/messaging/reaction-envelopes.ts +79 -0
  276. package/src/monitoring/control.ts +2 -2
  277. package/src/monitoring/page-cache.ts +5 -1
  278. package/src/notifications/AGENTS.md +1 -1
  279. package/src/notifications/__tests__/guardian-feed-projection.test.ts +360 -0
  280. package/src/notifications/__tests__/home-feed-side-effect.test.ts +8 -0
  281. package/src/notifications/adapters/discord.ts +62 -9
  282. package/src/notifications/conversation-pairing.ts +31 -4
  283. package/src/notifications/emit-signal.ts +8 -5
  284. package/src/notifications/guardian-delivery-recorder.ts +47 -6
  285. package/src/notifications/guardian-feed-projection.ts +492 -0
  286. package/src/notifications/home-feed-side-effect.ts +59 -11
  287. package/src/oauth/byo-connection.test.ts +22 -0
  288. package/src/oauth/byo-connection.ts +11 -5
  289. package/src/oauth/connection.ts +3 -2
  290. package/src/oauth/platform-connection.test.ts +35 -0
  291. package/src/oauth/platform-connection.ts +18 -13
  292. package/src/oauth/scope-utils.ts +48 -0
  293. package/src/oauth/seed-providers.ts +12 -0
  294. package/src/outbound-proxy/certs.ts +5 -4
  295. package/src/permissions/question-guardian-request.test.ts +7 -1
  296. package/src/persistence/attachments-store.ts +134 -31
  297. package/src/persistence/conversation-crud.ts +137 -4
  298. package/src/persistence/conversation-types.ts +109 -0
  299. package/src/persistence/db-connection.ts +3 -21
  300. package/src/persistence/delivery-crud.ts +42 -12
  301. package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +15 -2
  302. package/src/persistence/embeddings/embedding-local.ts +7 -112
  303. package/src/persistence/embeddings/qdrant-manager.ts +4 -1
  304. package/src/persistence/llm-request-log-store.ts +1 -1
  305. package/src/persistence/migrations/370-acp-session-history-auth-error-code.ts +36 -0
  306. package/src/persistence/migrations/371-acp-session-history-auth-error-credential.ts +40 -0
  307. package/src/persistence/migrations/372-create-acp-refused-credentials.ts +34 -0
  308. package/src/persistence/migrations/373-acp-auth-marker-index.ts +27 -0
  309. package/src/persistence/migrations/__tests__/370-acp-session-history-auth-error-code.test.ts +65 -0
  310. package/src/persistence/migrations/__tests__/371-acp-session-history-auth-error-credential.test.ts +68 -0
  311. package/src/persistence/migrations/__tests__/372-create-acp-refused-credentials.test.ts +64 -0
  312. package/src/persistence/migrations/__tests__/373-acp-auth-marker-index.test.ts +82 -0
  313. package/src/persistence/schema/acp.ts +25 -0
  314. package/src/persistence/steps.ts +8 -0
  315. package/src/platform/platform-patch-queue.test.ts +43 -41
  316. package/src/plugin-api/index.ts +12 -0
  317. package/src/plugins/defaults/compaction/window-manager.ts +2 -0
  318. package/src/plugins/defaults/image-recovery/recover.ts +12 -1
  319. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +166 -1
  320. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +3 -0
  321. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +3 -0
  322. package/src/plugins/defaults/memory/jobs-worker.ts +1 -2
  323. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +53 -9
  324. package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +4 -7
  325. package/src/plugins/defaults/memory/src/memory-worker-routes.ts +1 -1
  326. package/src/plugins/defaults/memory/v1/job-handlers/index-maintenance.ts +15 -1
  327. package/src/plugins/defaults/memory/worker-control.ts +1 -2
  328. package/src/providers/content-block-schema.ts +1 -0
  329. package/src/providers/fireworks/client.ts +9 -1
  330. package/src/providers/gemini/client.ts +22 -15
  331. package/src/providers/media-resolve.ts +24 -10
  332. package/src/providers/model-catalog.ts +91 -0
  333. package/src/providers/openai/chat-completions-provider.ts +44 -5
  334. package/src/providers/retry.ts +16 -11
  335. package/src/providers/types.ts +56 -0
  336. package/src/routes/__tests__/control.test.ts +4 -3
  337. package/src/routes/control.ts +1 -1
  338. package/src/routes/route-host-client.ts +1 -1
  339. package/src/runtime/AGENTS.md +30 -12
  340. package/src/runtime/__tests__/agent-wake.test.ts +71 -6
  341. package/src/runtime/__tests__/guardian-form-registry.test.ts +242 -0
  342. package/src/runtime/access-request-helper.ts +15 -7
  343. package/src/runtime/agent-wake.ts +16 -4
  344. package/src/runtime/channel-approval-types.ts +8 -5
  345. package/src/runtime/channel-invite-transport.ts +2 -0
  346. package/src/runtime/channel-invite-transports/discord.ts +27 -0
  347. package/src/runtime/channel-readiness-service.ts +4 -4
  348. package/src/runtime/channel-reply-delivery.ts +72 -28
  349. package/src/runtime/guardian-action-service.ts +9 -5
  350. package/src/runtime/guardian-form-registry.ts +256 -0
  351. package/src/runtime/guardian-reply-router.ts +33 -14
  352. package/src/runtime/invite-instruction-generator.ts +14 -2
  353. package/src/runtime/migrations/vbundle-metadata-merge.ts +4 -7
  354. package/src/runtime/no-response.ts +13 -74
  355. package/src/runtime/routes/__tests__/acp-routes.test.ts +527 -0
  356. package/src/runtime/routes/__tests__/app-management-shared-apps.test.ts +74 -0
  357. package/src/runtime/routes/__tests__/contact-form-kinds.test.ts +22 -0
  358. package/src/runtime/routes/__tests__/contact-record-prompt-route.test.ts +303 -0
  359. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +3 -4
  360. package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +6 -0
  361. package/src/runtime/routes/__tests__/route-host-worker-routes.test.ts +3 -4
  362. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +5 -6
  363. package/src/runtime/routes/acp-routes-list.test.ts +58 -0
  364. package/src/runtime/routes/acp-routes.ts +253 -47
  365. package/src/runtime/routes/app-management-routes.ts +80 -47
  366. package/src/runtime/routes/approval-routes.ts +9 -0
  367. package/src/runtime/routes/archive-utils.ts +6 -1
  368. package/src/runtime/routes/avatar-routes.ts +1 -1
  369. package/src/runtime/routes/channel-route-shared.ts +18 -20
  370. package/src/runtime/routes/contact-prompt-routes.ts +271 -105
  371. package/src/runtime/routes/conversation-routes.ts +41 -32
  372. package/src/runtime/routes/credential-prompt-routes.ts +10 -1
  373. package/src/runtime/routes/debug-bash-routes.ts +1 -0
  374. package/src/runtime/routes/guardian-expiry-sweep.ts +89 -31
  375. package/src/runtime/routes/guardian-form-routes.ts +115 -0
  376. package/src/runtime/routes/inbound-message-handler.ts +82 -29
  377. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -3
  378. package/src/runtime/routes/inbound-stages/edit-intercept.ts +6 -0
  379. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +6 -0
  380. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +6 -1
  381. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +50 -17
  382. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +97 -94
  383. package/src/runtime/routes/index.ts +2 -0
  384. package/src/runtime/routes/integrations/slack/channel.ts +39 -18
  385. package/src/runtime/routes/migration-routes.ts +1 -3
  386. package/src/runtime/routes/monitoring-routes.ts +0 -3
  387. package/src/runtime/routes/oauth-commands-routes.ts +18 -5
  388. package/src/runtime/routes/route-host-worker-routes.ts +1 -1
  389. package/src/runtime/routes/schedule-worker-routes.ts +1 -1
  390. package/src/runtime/tool-grant-request-helper.ts +4 -0
  391. package/src/schedule/run-script.ts +1 -0
  392. package/src/schedule/scheduler.ts +1 -1
  393. package/src/schedule/worker-control.ts +2 -2
  394. package/src/security/oauth2.ts +2 -11
  395. package/src/security/secure-keys.ts +55 -8
  396. package/src/skills/catalog-install.ts +7 -4
  397. package/src/skills/clawhub.ts +1 -0
  398. package/src/skills/inline-command-runner.ts +1 -0
  399. package/src/stt/__tests__/room-noise-floor.test.ts +108 -0
  400. package/src/stt/room-noise-floor.ts +140 -0
  401. package/src/subagent/notify.ts +49 -3
  402. package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
  403. package/src/tools/acp/spawn.ts +2 -2
  404. package/src/tools/browser/__tests__/browser-status.test.ts +9 -0
  405. package/src/tools/browser/browser-execution.ts +10 -43
  406. package/src/tools/browser/browser-manager.ts +1 -0
  407. package/src/tools/browser/browser-status-constants.ts +6 -5
  408. package/src/tools/browser/runtime-check.ts +2 -0
  409. package/src/tools/channel/react-to-message.test.ts +193 -0
  410. package/src/tools/channel/react-to-message.ts +159 -0
  411. package/src/tools/credentials/metadata-store.ts +115 -19
  412. package/src/tools/credentials/store.ts +0 -2
  413. package/src/tools/filesystem/read.ts +2 -2
  414. package/src/tools/host-filesystem/read.ts +2 -2
  415. package/src/tools/host-terminal/host-shell.test.ts +24 -0
  416. package/src/tools/host-terminal/host-shell.ts +11 -38
  417. package/src/tools/shared/filesystem/file-ops-service.ts +10 -7
  418. package/src/tools/shared/shell-output.test.ts +104 -0
  419. package/src/tools/shared/shell-output.ts +112 -39
  420. package/src/tools/skills/sandbox-runner.ts +1 -0
  421. package/src/tools/system/request-permission.test.ts +34 -0
  422. package/src/tools/system/request-permission.ts +29 -5
  423. package/src/tools/terminal/shell.test.ts +63 -0
  424. package/src/tools/terminal/shell.ts +12 -44
  425. package/src/tools/tool-input-schemas.ts +2 -0
  426. package/src/tools/tool-manifest.ts +2 -0
  427. package/src/usage/attribution.ts +12 -4
  428. package/src/util/__tests__/worker-ownership.test.ts +77 -0
  429. package/src/util/__tests__/worker-process-command.test.ts +246 -1
  430. package/src/util/bun-runtime.ts +44 -13
  431. package/src/util/bundled-asset.ts +9 -1
  432. package/src/util/disk-usage.ts +1 -0
  433. package/src/util/file-use.ts +4 -1
  434. package/src/util/guardian-form-timeouts.ts +50 -0
  435. package/src/util/image-conversion.ts +1 -0
  436. package/src/util/oauth-request-body.test.ts +59 -0
  437. package/src/util/oauth-request-body.ts +56 -5
  438. package/src/util/platform.ts +69 -73
  439. package/src/util/process-table.ts +16 -5
  440. package/src/util/read-stdin.ts +5 -0
  441. package/src/util/spawn.ts +1 -0
  442. package/src/util/sqlite3-runtime.ts +25 -5
  443. package/src/util/worker-ownership.ts +193 -0
  444. package/src/util/worker-process.ts +375 -37
  445. package/src/workspace/git-service.ts +4 -12
  446. package/src/workspace/migrations/010-app-dir-rename.ts +1 -0
@@ -39,6 +39,31 @@ mock.module("../../plugin-api/vision-support.js", () => ({
39
39
  doesSupportVision: () => pinProfileSupportsVision,
40
40
  }));
41
41
 
42
+ // Attachment hydration for the parked-camera-frame path. Only `att-frame-*`
43
+ // ids exist; anything else resolves to nothing, which is how an id the client
44
+ // invented reaches the bridge. `collectedAttachmentIds` is the store's other
45
+ // half: the fake `deleteMessageById` below writes into it under the real
46
+ // orphan rule, so an id the cleanup ate stops resolving here, exactly as a
47
+ // deleted attachment row would.
48
+ import * as realAttachmentsStore from "../../persistence/attachments-store.js";
49
+
50
+ const collectedAttachmentIds = new Set<string>();
51
+
52
+ mock.module("../../persistence/attachments-store.js", () => ({
53
+ ...realAttachmentsStore,
54
+ resolveAttachmentsForPersist: (ids: string[]) =>
55
+ ids
56
+ .filter(
57
+ (id) => id.startsWith("att-frame-") && !collectedAttachmentIds.has(id),
58
+ )
59
+ .map((id) => ({
60
+ id,
61
+ filename: `${id}.png`,
62
+ mimeType: "image/png",
63
+ data: "ZnJhbWU=",
64
+ })),
65
+ }));
66
+
42
67
  // Conversation-CRUD doubles for the teardown transcript-hygiene pass. The
43
68
  // real module is spread so every other export keeps its production behavior;
44
69
  // only the functions the hygiene pass (and discard) touch are recorded.
@@ -48,15 +73,22 @@ let getMessageByIdImpl: (
48
73
  messageId: string,
49
74
  conversationId?: string,
50
75
  ) => unknown = () => null;
76
+ // Attachment ids each persisted message links, so the fake delete below can
77
+ // apply the orphan rule to the same candidate set the real one would collect.
78
+ const messageAttachmentLinks = new Map<string, string[]>();
51
79
  const crudLog: {
52
80
  reads: string[];
53
81
  updates: Array<{ messageId: string; content: string }>;
54
82
  deletes: string[];
55
- } = { reads: [], updates: [], deletes: [] };
83
+ retained: Array<{ messageId: string; ids: readonly string[] }>;
84
+ } = { reads: [], updates: [], deletes: [], retained: [] };
56
85
  function resetCrudLog(): void {
57
86
  crudLog.reads.length = 0;
58
87
  crudLog.updates.length = 0;
59
88
  crudLog.deletes.length = 0;
89
+ crudLog.retained.length = 0;
90
+ messageAttachmentLinks.clear();
91
+ collectedAttachmentIds.clear();
60
92
  getMessageByIdImpl = () => null;
61
93
  }
62
94
 
@@ -69,8 +101,23 @@ mock.module("../../persistence/conversation-crud.js", () => ({
69
101
  updateMessageContent: (messageId: string, content: string) => {
70
102
  crudLog.updates.push({ messageId, content });
71
103
  },
72
- deleteMessageById: (messageId: string) => {
104
+ deleteMessageById: (
105
+ messageId: string,
106
+ options?: { retainAttachmentIds?: readonly string[] },
107
+ ) => {
73
108
  crudLog.deletes.push(messageId);
109
+ const retained = options?.retainAttachmentIds ?? [];
110
+ crudLog.retained.push({ messageId, ids: retained });
111
+ // Mirrors the real cleanup: the row's own attachment links are the
112
+ // candidate set, and a candidate nothing else references is collected
113
+ // unless the caller retained it. Every id here is referenced by this one
114
+ // row, which is the case the rollback path is about.
115
+ for (const id of messageAttachmentLinks.get(messageId) ?? []) {
116
+ if (!retained.includes(id)) {
117
+ collectedAttachmentIds.add(id);
118
+ }
119
+ }
120
+ messageAttachmentLinks.delete(messageId);
74
121
  return { segmentIds: [], deletedSummaryIds: [] };
75
122
  },
76
123
  // The echo path advances the snapshot anchor for a real-user turn; the
@@ -132,6 +179,7 @@ interface FakeConversation {
132
179
  content: string;
133
180
  requestId: string;
134
181
  metadata?: Record<string, unknown>;
182
+ attachments?: Array<Record<string, unknown>>;
135
183
  }) => Promise<{ id: string }>;
136
184
  workingDir: string;
137
185
  addEventObserver: (observer: unknown) => () => void;
@@ -167,7 +215,12 @@ function makeFakeConversation(opts: {
167
215
  let clientCallback: ((msg: unknown) => Promise<void>) | undefined;
168
216
  let persistCount = 0;
169
217
  let lastPersistOpts:
170
- | { content: string; requestId: string; metadata?: Record<string, unknown> }
218
+ | {
219
+ content: string;
220
+ requestId: string;
221
+ metadata?: Record<string, unknown>;
222
+ attachments?: Array<Record<string, unknown>>;
223
+ }
171
224
  | undefined;
172
225
  const conversation: FakeConversation = {
173
226
  conversationId: "conv-voice-bridge-test",
@@ -196,6 +249,14 @@ function makeFakeConversation(opts: {
196
249
  persistUserMessage: async (persistOpts) => {
197
250
  persistCount += 1;
198
251
  lastPersistOpts = persistOpts;
252
+ // The link rows the real persist writes, which is what makes an
253
+ // attachment a cleanup candidate when this message is deleted.
254
+ if (persistOpts.attachments && persistOpts.attachments.length > 0) {
255
+ messageAttachmentLinks.set(
256
+ `msg-${persistCount}`,
257
+ persistOpts.attachments.map((a) => a.id as string),
258
+ );
259
+ }
199
260
  // Recorded before `onPersist` so scripted persist FAILURES also
200
261
  // appear in the event stream — ordering tests need the losing
201
262
  // attempt visible.
@@ -496,6 +557,125 @@ describe("startVoiceTurn escalation-continuation persistence", () => {
496
557
  });
497
558
  });
498
559
 
560
+ describe("startVoiceTurn camera-frame attachments", () => {
561
+ test("hydrates parked ids onto the turn's own user message", async () => {
562
+ // The live-voice session parks an ambient camera frame and hands the id
563
+ // over; the row it lands on is the one carrying the user's words, so the
564
+ // picture and the sentence about it are one message.
565
+ const fake = makeFakeConversation({ processing: false });
566
+ fakeConversation = fake.conversation;
567
+
568
+ await startVoiceTurn({
569
+ ...makeTurnOptions(),
570
+ content: "what is this",
571
+ attachments: ["att-frame-1"],
572
+ });
573
+
574
+ expect(fake.lastPersistOpts()?.attachments).toEqual([
575
+ {
576
+ id: "att-frame-1",
577
+ filename: "att-frame-1.png",
578
+ mimeType: "image/png",
579
+ data: "ZnJhbWU=",
580
+ },
581
+ ]);
582
+ // `UserMessageAttachment` carries no metadata of its own, so which of a
583
+ // row's attachments were ambient frames is recorded on the message.
584
+ expect(fake.lastPersistOpts()?.metadata).toMatchObject({
585
+ voiceSessionTurn: true,
586
+ sightFrameAttachmentIds: ["att-frame-1"],
587
+ });
588
+ });
589
+
590
+ test("an id that resolves to nothing costs the caller no turn", async () => {
591
+ const fake = makeFakeConversation({ processing: false });
592
+ fakeConversation = fake.conversation;
593
+
594
+ await startVoiceTurn({
595
+ ...makeTurnOptions(),
596
+ content: "what is this",
597
+ attachments: ["att-missing"],
598
+ });
599
+
600
+ expect(fake.persistCount()).toBe(1);
601
+ expect(fake.lastPersistOpts()).not.toHaveProperty("attachments");
602
+ expect(fake.lastPersistOpts()?.metadata).not.toHaveProperty(
603
+ "sightFrameAttachmentIds",
604
+ );
605
+ });
606
+
607
+ test("a discarded turn leaves its camera frame attachable again", async () => {
608
+ // The hold verdict rolls the speculative turn back and the session parks
609
+ // the id again for the replay. The rollback deletes the row, and the row
610
+ // was the attachment's only reference, so without the retention the
611
+ // cleanup takes the frame with it and the replayed utterance speaks
612
+ // without the picture the user is holding up, with nothing said.
613
+ resetCrudLog();
614
+ const fake = makeFakeConversation({ processing: false });
615
+ fakeConversation = fake.conversation;
616
+
617
+ const handle = await startVoiceTurn({
618
+ ...makeTurnOptions(),
619
+ content: "what is this",
620
+ attachments: ["att-frame-1"],
621
+ });
622
+ expect(fake.lastPersistOpts()?.attachments).toHaveLength(1);
623
+
624
+ await handle.discard?.();
625
+
626
+ // The replay: the same parked id, and the row it lands on still carries
627
+ // the frame.
628
+ await startVoiceTurn({
629
+ ...makeTurnOptions(),
630
+ content: "what is this",
631
+ attachments: ["att-frame-1"],
632
+ });
633
+
634
+ expect(fake.lastPersistOpts()?.attachments).toEqual([
635
+ {
636
+ id: "att-frame-1",
637
+ filename: "att-frame-1.png",
638
+ mimeType: "image/png",
639
+ data: "ZnJhbWU=",
640
+ },
641
+ ]);
642
+ expect(fake.lastPersistOpts()?.metadata).toMatchObject({
643
+ sightFrameAttachmentIds: ["att-frame-1"],
644
+ });
645
+ // The rollback asked for exactly the ids its own row linked.
646
+ expect(crudLog.retained).toEqual([
647
+ { messageId: "msg-1", ids: ["att-frame-1"] },
648
+ ]);
649
+ });
650
+
651
+ test("a discard with no parked frame retains nothing", async () => {
652
+ resetCrudLog();
653
+ const fake = makeFakeConversation({ processing: false });
654
+ fakeConversation = fake.conversation;
655
+
656
+ const handle = await startVoiceTurn({
657
+ ...makeTurnOptions(),
658
+ content: "what is this",
659
+ });
660
+ await handle.discard?.();
661
+
662
+ expect(crudLog.deletes).toEqual(["msg-1"]);
663
+ expect(crudLog.retained).toEqual([{ messageId: "msg-1", ids: [] }]);
664
+ });
665
+
666
+ test("a turn with no parked frame persists exactly as before", async () => {
667
+ const fake = makeFakeConversation({ processing: false });
668
+ fakeConversation = fake.conversation;
669
+
670
+ await startVoiceTurn({ ...makeTurnOptions(), content: "what is this" });
671
+
672
+ expect(fake.lastPersistOpts()).not.toHaveProperty("attachments");
673
+ expect(fake.lastPersistOpts()?.metadata).toEqual({
674
+ voiceSessionTurn: true,
675
+ });
676
+ });
677
+ });
678
+
499
679
  describe("startVoiceTurn hiddenSyntheticPrompt", () => {
500
680
  // A caller whose internal instruction is composed per call carries no
501
681
  // sentinel for the content comparisons to recognize, so it declares itself.
@@ -6,6 +6,8 @@
6
6
  * channels (telegram, slack) get a direct channel reply.
7
7
  */
8
8
 
9
+ import { DELIVERY_STATUS } from "@vellumai/gateway-client";
10
+
9
11
  import { addMessage } from "../persistence/conversation-crud.js";
10
12
  import { deliverChannelReply } from "../runtime/gateway-client.js";
11
13
  import { composeGuardianActionMessageGenerative } from "../runtime/guardian-action-message-composer.js";
@@ -35,7 +37,10 @@ export async function sendGuardianExpiryNotices(
35
37
  assistantId: string,
36
38
  ): Promise<void> {
37
39
  for (const delivery of deliveries) {
38
- if (delivery.status !== "sent" && delivery.status !== "pending") {
40
+ if (
41
+ delivery.status !== DELIVERY_STATUS.sent &&
42
+ delivery.status !== DELIVERY_STATUS.pending
43
+ ) {
39
44
  continue;
40
45
  }
41
46
 
@@ -28,6 +28,7 @@ import { CONVERSATION_BUSY_MESSAGE } from "../daemon/conversation-messaging.js";
28
28
  import { resolveChannelCapabilities } from "../daemon/conversation-runtime-assembly.js";
29
29
  import { getOrCreateConversation } from "../daemon/conversation-store.js";
30
30
  import type { TrustContext } from "../daemon/trust-context-types.js";
31
+ import { resolveAttachmentsForPersist } from "../persistence/attachments-store.js";
31
32
  import {
32
33
  deleteMessageById,
33
34
  getMessageById,
@@ -323,6 +324,20 @@ export interface VoiceTurnOptions {
323
324
  };
324
325
  /** Per-turn control prompt. Undefined uses the phone prompt; null disables it. */
325
326
  voiceControlPrompt?: string | null;
327
+ /**
328
+ * Ids of already-uploaded attachments to hang on this turn's persisted user
329
+ * message, hydrated here into the shape the message stores.
330
+ *
331
+ * Filled by the live-voice session from the camera frame a client parked on
332
+ * it (`attach_frame`), which is why these are ambient rather than chosen:
333
+ * they ride the turn's own user message so the picture and the words about it
334
+ * are one message, and so the media survives a context-overflow retry, which
335
+ * preserves it only on the newest user message.
336
+ *
337
+ * Ids that resolve to nothing are dropped: an unresolvable frame must not
338
+ * cost the caller their turn.
339
+ */
340
+ attachments?: readonly string[];
326
341
  /** The transcribed caller utterance or synthetic marker. */
327
342
  content: string;
328
343
  /** Assistant scope for multi-assistant channels. */
@@ -1023,9 +1038,20 @@ export async function startVoiceTurn(
1023
1038
 
1024
1039
  const requestId = uuidv7();
1025
1040
  const turnId = crypto.randomUUID();
1041
+ // Ids this turn's row actually links, read back by `discardFn` so a rollback
1042
+ // does not take the attachments down with the row.
1043
+ let linkedAttachmentIds: string[] = [];
1026
1044
  const persistTurnUserMessage = async (): Promise<string> => {
1045
+ // Resolved inside the persist, not once outside it, so the retry after a
1046
+ // lost lock race stores exactly what the first attempt would have.
1047
+ const turnAttachments =
1048
+ opts.attachments && opts.attachments.length > 0
1049
+ ? resolveAttachmentsForPersist([...opts.attachments])
1050
+ : [];
1051
+ linkedAttachmentIds = turnAttachments.map((a) => a.id);
1027
1052
  const persistResult = await conversation.persistUserMessage({
1028
1053
  content: persistedContent,
1054
+ ...(turnAttachments.length > 0 ? { attachments: turnAttachments } : {}),
1029
1055
  requestId,
1030
1056
  metadata: {
1031
1057
  // Durable "this turn came from an open voice session" marker; see
@@ -1069,6 +1095,14 @@ export async function startVoiceTurn(
1069
1095
  },
1070
1096
  }
1071
1097
  : {}),
1098
+ // Names which of the row's attachments arrived as ambient camera
1099
+ // frames rather than files the user picked, so retention can treat
1100
+ // them differently. `UserMessageAttachment` carries no metadata of its
1101
+ // own, so the marking lives on the message and refers to the
1102
+ // attachments by id.
1103
+ ...(turnAttachments.length > 0
1104
+ ? { sightFrameAttachmentIds: turnAttachments.map((a) => a.id) }
1105
+ : {}),
1072
1106
  },
1073
1107
  });
1074
1108
  return persistResult.id;
@@ -1865,7 +1899,18 @@ export async function startVoiceTurn(
1865
1899
  // Same rollback pattern as the pointer-turn runner: delete the row,
1866
1900
  // then rebuild in-memory history from the clean DB (a plain pop is
1867
1901
  // fragile against concurrent compaction reassigning the array).
1868
- deleteMessageById(messageId);
1902
+ //
1903
+ // The row's own attachments are exempted from the orphan cleanup. A
1904
+ // discard unwinds the turn as though it never ran, so an ambient camera
1905
+ // frame goes back to being uploaded-but-unsent, which is exactly what
1906
+ // the live-voice session's parked slot expects to find when the held
1907
+ // utterance is replayed. Collecting it here would delete the row and
1908
+ // the bytes, and the replay would silently speak without the picture.
1909
+ deleteMessageById(messageId, {
1910
+ ...(linkedAttachmentIds.length > 0
1911
+ ? { retainAttachmentIds: linkedAttachmentIds }
1912
+ : {}),
1913
+ });
1869
1914
  await conversation.loadFromDb();
1870
1915
  publishConversationMessagesChanged(opts.conversationId);
1871
1916
  } catch (err) {
@@ -524,30 +524,30 @@ describe("expiry lifecycle", () => {
524
524
  ]);
525
525
  });
526
526
 
527
- test("sweepExpiredGuardianRequests passes now and returns the expired rows", async () => {
528
- const expiredRow = makeWireRequest({ status: "expired" });
529
- ipcResponse = { expired: [expiredRow] };
530
- expect(
531
- await client.sweepExpiredGuardianRequests(1_700_000_500_000),
532
- ).toEqual([expiredRow]);
527
+ test("listExpiredPendingGuardianRequests passes the limit and returns pending rows", async () => {
528
+ const staleRow = makeWireRequest({ status: "pending" });
529
+ ipcResponse = [staleRow];
530
+ expect(await client.listExpiredPendingGuardianRequests(50)).toEqual([
531
+ staleRow,
532
+ ]);
533
533
  expect(ipcCalls).toEqual([
534
534
  {
535
- method: "guardian_requests_sweep_expired",
536
- params: { now: 1_700_000_500_000 },
535
+ method: "guardian_requests_list_expired_pending",
536
+ params: { limit: 50 },
537
537
  },
538
538
  ]);
539
539
  });
540
540
 
541
- test("sweeps throw on transport failure and malformed responses", async () => {
541
+ test("the expired-pending list throws on transport failure and malformed responses", async () => {
542
542
  ipcError = new Error("gateway unavailable");
543
- await expect(client.sweepExpiredGuardianRequests()).rejects.toThrow(
543
+ await expect(client.listExpiredPendingGuardianRequests()).rejects.toThrow(
544
544
  "gateway unavailable",
545
545
  );
546
546
 
547
547
  ipcError = null;
548
548
  ipcResponse = { expired: 3 };
549
- await expect(client.sweepExpiredGuardianRequests()).rejects.toThrow(
550
- "guardian_requests_sweep_expired",
549
+ await expect(client.listExpiredPendingGuardianRequests()).rejects.toThrow(
550
+ "guardian_requests_list_expired_pending",
551
551
  );
552
552
  });
553
553
  });
@@ -631,11 +631,7 @@ describe("isGuardianRequestInScopeOrFalse", () => {
631
631
  test("returns the gateway scope verdict", async () => {
632
632
  ipcResponse = { inScope: true };
633
633
  expect(
634
- await client.isGuardianRequestInScopeOrFalse(
635
- "req-1",
636
- "conv-1",
637
- "telegram",
638
- ),
634
+ await client.isGuardianRequestInScopeOrFalse("req-1", "conv-1"),
639
635
  ).toBe(true);
640
636
  expect(ipcCalls).toEqual([
641
637
  {
@@ -643,7 +639,6 @@ describe("isGuardianRequestInScopeOrFalse", () => {
643
639
  params: {
644
640
  requestId: "req-1",
645
641
  conversationId: "conv-1",
646
- channel: "telegram",
647
642
  },
648
643
  },
649
644
  ]);
@@ -42,7 +42,6 @@ import {
42
42
  type GuardianRequestWire,
43
43
  type ListGuardianRequestsIpcParams,
44
44
  type ListPendingGuardianRequestsByDestinationIpcParams,
45
- SweepExpiredGuardianRequestsIpcResponseSchema,
46
45
  type UpdateGuardianRequestDeliveryIpcParams,
47
46
  } from "@vellumai/gateway-client";
48
47
  import type { ZodType } from "zod";
@@ -158,7 +157,7 @@ export const getGuardianRequestByCodeOrNull = degradeOnFailure(
158
157
  );
159
158
 
160
159
  /** List guardian requests matching the filters. Throws on transport failure. */
161
- async function listGuardianRequests(
160
+ export async function listGuardianRequests(
162
161
  filters: ListGuardianRequestsIpcParams = {},
163
162
  ): Promise<GuardianRequestWire[]> {
164
163
  return callGateway(
@@ -210,9 +209,10 @@ export async function expireGuardianRequest(id: string): Promise<void> {
210
209
 
211
210
  /**
212
211
  * Daemon-boot expiry: interaction-bound kinds die with the daemon's
213
- * in-memory pendingInteractions map, plus persistent kinds already past
214
- * `expiresAt`. Returns the expired count. Throws on any failure
215
- * (fail-closed).
212
+ * in-memory pendingInteractions map. Persistent kinds are never touched
213
+ * here, whatever their deadline; their expiry belongs to the sweep, whose
214
+ * per-request confirmation keeps the fan-out recoverable. Returns the
215
+ * expired count. Throws on any failure (fail-closed).
216
216
  */
217
217
  export async function expireInteractionBoundGuardianRequests(): Promise<number> {
218
218
  const response = await callGateway(
@@ -224,19 +224,21 @@ export async function expireInteractionBoundGuardianRequests(): Promise<number>
224
224
  }
225
225
 
226
226
  /**
227
- * Sweep persistent requests past their `expiresAt` (gateway CAS-expires;
228
- * `now` defaults gateway-side). Returns the expired rows so the daemon fan-out
229
- * needs no follow-up read. Throws on any failure (fail-closed).
227
+ * Bounded, read-only list of pending requests past their `expiresAt`,
228
+ * oldest deadline first. Rows stay pending (and so stay listed) until the
229
+ * sweep has run their expiry side effects and confirmed each with
230
+ * `expireGuardianRequest`, which is what makes the fan-out recoverable
231
+ * from state after a lost response or a crash. Throws on any failure
232
+ * (fail-closed).
230
233
  */
231
- export async function sweepExpiredGuardianRequests(
232
- now?: number,
234
+ export async function listExpiredPendingGuardianRequests(
235
+ limit?: number,
233
236
  ): Promise<GuardianRequestWire[]> {
234
- const response = await callGateway(
235
- GUARDIAN_REQUESTS_IPC_METHODS.sweepExpired,
236
- { now },
237
- SweepExpiredGuardianRequestsIpcResponseSchema,
237
+ return callGateway(
238
+ GUARDIAN_REQUESTS_IPC_METHODS.listExpiredPending,
239
+ { limit },
240
+ GuardianRequestListIpcResponseSchema,
238
241
  );
239
- return response.expired;
240
242
  }
241
243
 
242
244
  /**
@@ -281,6 +283,24 @@ export const listGuardianRequestDeliveriesOrEmpty = degradeOnFailure(
281
283
  [],
282
284
  );
283
285
 
286
+ /**
287
+ * Every delivery row addressed to one channel-native chat, across all
288
+ * requests. Transcript importers use the recorded message ids to
289
+ * recognize guardian card messages as delivery projections. Throws on
290
+ * transport failure so callers can fail safe instead of importing a
291
+ * card as conversation content.
292
+ */
293
+ export async function listGuardianRequestDeliveriesByChat(
294
+ channel: string,
295
+ chatId: string,
296
+ ): Promise<GuardianRequestDeliveryWire[]> {
297
+ return callGateway(
298
+ GUARDIAN_REQUESTS_IPC_METHODS.listDeliveriesByChat,
299
+ { channel, chatId },
300
+ GuardianRequestDeliveryListIpcResponseSchema,
301
+ );
302
+ }
303
+
284
304
  /**
285
305
  * Reaction routing: the pending request whose delivered card is the
286
306
  * reacted-to message. Throws on transport failure.
@@ -347,17 +367,16 @@ export const listPendingRequestsByScopeOrEmpty = degradeOnFailure(
347
367
 
348
368
  /**
349
369
  * Is a decision from this conversation allowed for the request (source
350
- * match, or delivery match optionally narrowed by `channel`)? Throws on
370
+ * match, or delivery match on any channel's paired conversation)? Throws on
351
371
  * transport failure.
352
372
  */
353
373
  async function isGuardianRequestInScope(
354
374
  requestId: string,
355
375
  conversationId: string,
356
- channel?: string,
357
376
  ): Promise<boolean> {
358
377
  const response = await callGateway(
359
378
  GUARDIAN_REQUESTS_IPC_METHODS.inScope,
360
- { requestId, conversationId, channel },
379
+ { requestId, conversationId },
361
380
  GuardianRequestInScopeIpcResponseSchema,
362
381
  );
363
382
  return response.inScope;