@vellumai/assistant 0.11.8 → 0.11.9-staging.2

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 (326) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/Dockerfile +8 -48
  3. package/docker-entrypoint.sh +5 -1
  4. package/docker-kata-apt-env.sh +3 -0
  5. package/docker-kata-apt-shims.sh +127 -0
  6. package/docker-kata-apt-wrapper.sh +45 -0
  7. package/docker-kata-chroot-exec.sh +35 -0
  8. package/docker-kata-pip.sh +8 -2
  9. package/docs/architecture/memory.md +8 -4
  10. package/docs/guardian-request-flow.md +18 -14
  11. package/docs/trusted-contact-access.md +1 -7
  12. package/node_modules/@vellumai/avatar-catalog/src/catalog.ts +7 -1
  13. package/node_modules/@vellumai/avatar-catalog/src/index.ts +1 -1
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +4 -1
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -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/platform-credential.ts +41 -0
  18. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +4 -1
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
  23. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  24. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +0 -18
  25. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +110 -5
  26. package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +5 -33
  27. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +3 -0
  28. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +100 -9
  29. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -2
  30. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +9 -0
  31. package/node_modules/@vellumai/service-contracts/package.json +4 -1
  32. package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
  33. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  34. package/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
  35. package/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  36. package/openapi.yaml +193 -4
  37. package/package.json +2 -2
  38. package/src/__tests__/access-request-card-view.test.ts +6 -5
  39. package/src/__tests__/access-request-seed-content-blocks.test.ts +5 -2
  40. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +43 -82
  41. package/src/__tests__/always-loaded-tools-guard.test.ts +8 -2
  42. package/src/__tests__/attachment-stored-path-annotation.test.ts +80 -0
  43. package/src/__tests__/attachments-store.test.ts +27 -0
  44. package/src/__tests__/attachments.test.ts +43 -0
  45. package/src/__tests__/canned-reply-release.test.ts +48 -5
  46. package/src/__tests__/channel-delivery-store.test.ts +129 -18
  47. package/src/__tests__/channel-reply-delivery.test.ts +553 -93
  48. package/src/__tests__/channel-retry-sweep.test.ts +199 -0
  49. package/src/__tests__/client-os-metadata-persistence.test.ts +32 -3
  50. package/src/__tests__/conversation-error.test.ts +15 -0
  51. package/src/__tests__/conversation-load-history-repair.test.ts +118 -0
  52. package/src/__tests__/conversation-pairing.test.ts +141 -1
  53. package/src/__tests__/conversation-routes-disk-view.test.ts +28 -1
  54. package/src/__tests__/conversation-routes-slash-commands.test.ts +125 -0
  55. package/src/__tests__/conversation-runtime-assembly.test.ts +79 -0
  56. package/src/__tests__/conversation-store-ephemeral.test.ts +323 -10
  57. package/src/__tests__/conversation-sync-tags.test.ts +2 -37
  58. package/src/__tests__/credential-health-service.test.ts +98 -10
  59. package/src/__tests__/delete-propagation.test.ts +469 -0
  60. package/src/__tests__/dm-persistence.test.ts +16 -0
  61. package/src/__tests__/docker-kata-apt-shims.test.ts +135 -0
  62. package/src/__tests__/forbidden-legacy-symbols.test.ts +12 -0
  63. package/src/__tests__/gemini-provider.test.ts +46 -0
  64. package/src/__tests__/guardian-card-withdrawal.test.ts +0 -22
  65. package/src/__tests__/guardian-gateway-sim.ts +0 -23
  66. package/src/__tests__/guardian-question-mode.test.ts +108 -0
  67. package/src/__tests__/guardian-reply-router-answer-mode.test.ts +0 -2
  68. package/src/__tests__/guardian-routing-invariants.test.ts +26 -12
  69. package/src/__tests__/host-proxy-interface.test.ts +10 -0
  70. package/src/__tests__/inbound-slack-persistence.test.ts +16 -0
  71. package/src/__tests__/injector-v3-suppression.test.ts +43 -9
  72. package/src/__tests__/list-messages-page-latest.test.ts +127 -0
  73. package/src/__tests__/list-messages-system-card.test.ts +103 -0
  74. package/src/__tests__/media-resolve-image-validation.test.ts +77 -0
  75. package/src/__tests__/notification-decision-fallback.test.ts +199 -77
  76. package/src/__tests__/notification-decision-strategy.test.ts +198 -213
  77. package/src/__tests__/notification-discord-adapter.test.ts +25 -0
  78. package/src/__tests__/notification-slack-adapter.test.ts +133 -0
  79. package/src/__tests__/notification-telegram-adapter.test.ts +36 -0
  80. package/src/__tests__/openai-provider.test.ts +5 -5
  81. package/src/__tests__/outbound-slack-persistence.test.ts +85 -72
  82. package/src/__tests__/persist-user-message-set-processing-failure.test.ts +83 -65
  83. package/src/__tests__/platform-client-verify-credential.test.ts +100 -0
  84. package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -1
  85. package/src/__tests__/pricing.test.ts +11 -0
  86. package/src/__tests__/process-message-display-content.test.ts +20 -9
  87. package/src/__tests__/processing-acquire-fenced-guard.test.ts +56 -0
  88. package/src/__tests__/provider-meta-persistence.test.ts +67 -0
  89. package/src/__tests__/reaction-persistence.test.ts +22 -198
  90. package/src/__tests__/run-conversation-turn-persistence.test.ts +5 -2
  91. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +16 -0
  92. package/src/__tests__/skill-load-tool.test.ts +27 -0
  93. package/src/__tests__/skills.test.ts +34 -1
  94. package/src/__tests__/strip-memory-injections.test.ts +3 -4
  95. package/src/__tests__/terminal-tools.test.ts +9 -0
  96. package/src/__tests__/thread-backfill.test.ts +5 -3
  97. package/src/__tests__/unified-turn-context-location.test.ts +76 -0
  98. package/src/__tests__/voice-session-bridge.test.ts +18 -0
  99. package/src/__tests__/watch-retro-report-payload.test.ts +185 -0
  100. package/src/__tests__/watch-retro-tool-availability.test.ts +82 -0
  101. package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +235 -0
  102. package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +233 -0
  103. package/src/agent/attachments.ts +13 -2
  104. package/src/agent/loop.ts +3 -19
  105. package/src/api/README.md +9 -5
  106. package/src/api/index.ts +9 -8
  107. package/src/api/package.json +1 -0
  108. package/src/api/responses/conversation-message.ts +8 -0
  109. package/src/api/responses/home.ts +7 -18
  110. package/src/api/surfaces.ts +114 -7
  111. package/src/approvals/AGENTS.md +1 -1
  112. package/src/channels/__tests__/gateway-guardian-requests.test.ts +1 -23
  113. package/src/channels/__tests__/types.test.ts +22 -1
  114. package/src/channels/gateway-guardian-requests.ts +0 -22
  115. package/src/channels/types.ts +8 -6
  116. package/src/cli/__tests__/catalog-search-help.test.ts +18 -0
  117. package/src/cli/commands/channels/__tests__/channels.test.ts +26 -0
  118. package/src/cli/commands/channels/index.help.ts +19 -6
  119. package/src/cli/commands/channels/index.ts +6 -2
  120. package/src/cli/commands/db/__tests__/status.test.ts +22 -0
  121. package/src/cli/commands/db/index.help.ts +1 -1
  122. package/src/cli/commands/db/status.ts +172 -1
  123. package/src/cli/commands/platform/__tests__/connect.test.ts +29 -0
  124. package/src/cli/commands/platform/connect.ts +14 -5
  125. package/src/cli/commands/plugins.help.ts +6 -5
  126. package/src/cli/lib/__tests__/install-from-github.test.ts +0 -8
  127. package/src/cli/lib/__tests__/install-from-platform.test.ts +72 -0
  128. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +33 -0
  129. package/src/cli/lib/bundled-marketplace.json +14 -0
  130. package/src/cli/lib/install-from-github.ts +9 -5
  131. package/src/cli/lib/install-from-platform.ts +12 -1
  132. package/src/config/__tests__/assistant-initiated-threads-gate.test.ts +61 -0
  133. package/src/config/assistant-initiated-threads-gate.ts +50 -0
  134. package/src/config/bundled-skills/acp/SKILL.md +10 -3
  135. package/src/config/bundled-skills/schedule/SKILL.md +25 -11
  136. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +3 -1
  137. package/src/config/call-site-defaults.ts +4 -1
  138. package/src/config/feature-flag-registry.json +21 -4
  139. package/src/config/schemas/memory-v3.ts +4 -3
  140. package/src/context/strip-injections.ts +17 -56
  141. package/src/conversations/__tests__/message-consolidation.test.ts +39 -0
  142. package/src/conversations/message-consolidation.ts +4 -3
  143. package/src/credential-health/credential-health-service.ts +130 -0
  144. package/src/daemon/__tests__/conversation-tool-setup.test.ts +31 -0
  145. package/src/daemon/conversation-agent-loop-handlers.ts +55 -59
  146. package/src/daemon/conversation-error.ts +2 -0
  147. package/src/daemon/conversation-messaging.ts +195 -47
  148. package/src/daemon/conversation-process.ts +20 -5
  149. package/src/daemon/conversation-runtime-assembly.ts +48 -39
  150. package/src/daemon/conversation-store.ts +159 -11
  151. package/src/daemon/conversation-tool-setup.ts +9 -2
  152. package/src/daemon/conversation.ts +290 -19
  153. package/src/daemon/dictation-text-processing.ts +2 -7
  154. package/src/daemon/handlers/config-channels.ts +33 -3
  155. package/src/daemon/handlers/config-model.test.ts +1 -0
  156. package/src/daemon/handlers/shared.ts +9 -0
  157. package/src/daemon/port-oversized-content.test.ts +117 -0
  158. package/src/daemon/port-oversized-content.ts +110 -0
  159. package/src/daemon/process-message.ts +87 -16
  160. package/src/daemon/reaction-record.test.ts +23 -11
  161. package/src/daemon/reaction-record.ts +6 -9
  162. package/src/documents/document-store.ts +1 -5
  163. package/src/home/conversation-starter-validation.ts +1 -5
  164. package/src/live-voice/__tests__/live-voice-photo.test.ts +910 -50
  165. package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +31 -25
  166. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +70 -1
  167. package/src/live-voice/live-voice-photo.ts +544 -61
  168. package/src/live-voice/live-voice-session.ts +6 -2
  169. package/src/live-voice/protocol.ts +20 -1
  170. package/src/messaging/provider-message-metadata.ts +40 -1
  171. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +10 -2
  172. package/src/messaging/providers/channel-transport.ts +28 -0
  173. package/src/messaging/providers/discord/send.ts +3 -2
  174. package/src/messaging/providers/slack/api.ts +11 -1
  175. package/src/messaging/providers/slack/message-metadata.test.ts +133 -0
  176. package/src/messaging/providers/slack/message-metadata.ts +141 -1
  177. package/src/messaging/providers/slack/send.test.ts +58 -0
  178. package/src/messaging/providers/slack/send.ts +4 -4
  179. package/src/messaging/providers/slack/transport.ts +28 -2
  180. package/src/messaging/providers/telegram-bot/send.test.ts +159 -0
  181. package/src/messaging/providers/telegram-bot/send.ts +93 -0
  182. package/src/messaging/providers/telegram-bot/transport.ts +71 -0
  183. package/src/messaging/reaction-envelopes.test.ts +73 -0
  184. package/src/messaging/reaction-envelopes.ts +33 -5
  185. package/src/messaging/read-provider-metadata.ts +4 -3
  186. package/src/monitoring/recovery/__tests__/stranded-delivery-events.test.ts +208 -0
  187. package/src/monitoring/recovery/db.ts +35 -0
  188. package/src/monitoring/recovery/orphaned-channel-events.ts +3 -22
  189. package/src/monitoring/recovery/run-recovery.ts +6 -2
  190. package/src/monitoring/recovery/stale-processing.ts +3 -20
  191. package/src/monitoring/recovery/stranded-delivery-events.ts +68 -0
  192. package/src/notifications/AGENTS.md +2 -2
  193. package/src/notifications/README.md +2 -2
  194. package/src/notifications/__tests__/assistant-reply-producer.test.ts +11 -9
  195. package/src/notifications/__tests__/broadcaster.test.ts +35 -4
  196. package/src/notifications/__tests__/notification-utils.test.ts +31 -0
  197. package/src/notifications/access-request-copy.ts +126 -114
  198. package/src/notifications/adapters/discord.ts +9 -5
  199. package/src/notifications/adapters/shared.ts +17 -4
  200. package/src/notifications/adapters/slack.ts +14 -4
  201. package/src/notifications/adapters/telegram.ts +8 -4
  202. package/src/notifications/approval-card-data.ts +5 -2
  203. package/src/notifications/assistant-reply-producer.ts +7 -7
  204. package/src/notifications/broadcaster.ts +57 -25
  205. package/src/notifications/conversation-pairing.ts +68 -2
  206. package/src/notifications/copy-composer.ts +13 -32
  207. package/src/notifications/decision-engine.ts +56 -237
  208. package/src/notifications/guardian-delivery-recorder.ts +5 -9
  209. package/src/notifications/guardian-feed-projection.ts +0 -15
  210. package/src/notifications/guardian-question-mode.ts +147 -6
  211. package/src/notifications/notification-utils.ts +117 -1
  212. package/src/permissions/confirmation-guardian-request.ts +2 -2
  213. package/src/permissions/prompter.ts +1 -1
  214. package/src/persistence/conversation-crud.ts +93 -14
  215. package/src/persistence/conversation-queries.ts +144 -17
  216. package/src/persistence/conversation-types.ts +39 -5
  217. package/src/persistence/delivery-crud.ts +224 -28
  218. package/src/persistence/delivery-status.ts +21 -2
  219. package/src/persistence/migrations/374-channel-inbound-message-id-index.ts +26 -0
  220. package/src/persistence/migrations/375-create-channel-outbound-posts.ts +52 -0
  221. package/src/persistence/migrations/__tests__/375-create-channel-outbound-posts.test.ts +84 -0
  222. package/src/persistence/schema/conversations.ts +71 -2
  223. package/src/persistence/schema-contract.test.ts +78 -0
  224. package/src/persistence/schema-contract.ts +96 -0
  225. package/src/persistence/steps.ts +4 -0
  226. package/src/platform/client.ts +55 -0
  227. package/src/plugins/__tests__/mcp-servers.test.ts +46 -0
  228. package/src/plugins/defaults/injector-order.ts +2 -1
  229. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +2 -2
  230. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +13 -15
  231. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +10 -0
  232. package/src/plugins/defaults/memory/injectors.ts +1 -1
  233. package/src/plugins/defaults/memory/memory-marker.ts +17 -7
  234. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +1 -5
  235. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +17 -0
  236. package/src/plugins/defaults/memory/substrate/skill-content.ts +7 -0
  237. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +54 -41
  238. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +3 -3
  239. package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +22 -0
  240. package/src/plugins/defaults/memory/v3/injector.ts +14 -15
  241. package/src/plugins/defaults/memory/v3/prune.test.ts +20 -2
  242. package/src/plugins/defaults/memory/v3/render-injection.ts +10 -1
  243. package/src/plugins/defaults/memory/v3/types.ts +15 -4
  244. package/src/plugins/defaults/turn-context/injectors.ts +3 -0
  245. package/src/plugins/defaults/turn-context/unified-turn-context.ts +24 -0
  246. package/src/plugins/mcp-servers.ts +14 -2
  247. package/src/providers/__tests__/dispatch-connection-routing.test.ts +50 -0
  248. package/src/providers/__tests__/registry-native-web-search.test.ts +49 -2
  249. package/src/providers/anthropic/client.ts +21 -24
  250. package/src/providers/call-site-routing.ts +5 -4
  251. package/src/providers/connection-resolution.ts +29 -1
  252. package/src/providers/content-block-size.test.ts +82 -0
  253. package/src/providers/content-block-size.ts +99 -0
  254. package/src/providers/file-block-text.test.ts +64 -0
  255. package/src/providers/file-block-text.ts +28 -0
  256. package/src/providers/gemini/client.ts +15 -9
  257. package/src/providers/inference/auth.ts +6 -6
  258. package/src/providers/media-resolve.ts +14 -0
  259. package/src/providers/model-catalog.ts +47 -34
  260. package/src/providers/openai/chat-completions-provider.ts +9 -25
  261. package/src/providers/openai/responses-provider.ts +11 -18
  262. package/src/providers/registry.ts +10 -7
  263. package/src/providers/routing-identity.ts +2 -1
  264. package/src/providers/types.ts +1 -4
  265. package/src/providers/vellum-model-routing.ts +3 -2
  266. package/src/runtime/AGENTS.md +40 -15
  267. package/src/runtime/approval-message-composer.ts +1 -6
  268. package/src/runtime/assistant-event-hub.ts +2 -39
  269. package/src/runtime/channel-approval-types.ts +1 -5
  270. package/src/runtime/channel-reply-delivery.ts +182 -115
  271. package/src/runtime/{slack-reply-session.test.ts → channel-reply-session.test.ts} +294 -156
  272. package/src/runtime/{slack-reply-session.ts → channel-reply-session.ts} +107 -86
  273. package/src/runtime/channel-retry-sweep.ts +102 -1
  274. package/src/runtime/finalize-event-delivery.ts +4 -4
  275. package/src/runtime/guardian-action-message-composer.ts +1 -4
  276. package/src/runtime/guardian-reply-router.ts +4 -75
  277. package/src/runtime/http-router.ts +1 -5
  278. package/src/runtime/question-request-guardian-bridge.ts +2 -3
  279. package/src/runtime/routes/__tests__/conversation-list-assistant-section.test.ts +359 -0
  280. package/src/runtime/routes/__tests__/dictation-command-mode.test.ts +120 -0
  281. package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +487 -0
  282. package/src/runtime/routes/acp-routes.ts +1 -1
  283. package/src/runtime/routes/canned-reply-release.ts +19 -9
  284. package/src/runtime/routes/channel-route-shared.ts +0 -39
  285. package/src/runtime/routes/channel-verification-routes.ts +4 -1
  286. package/src/runtime/routes/conversation-list-routes.ts +44 -3
  287. package/src/runtime/routes/conversation-management-routes.ts +2 -1
  288. package/src/runtime/routes/conversation-routes.ts +138 -59
  289. package/src/runtime/routes/diagnostics-routes.ts +111 -60
  290. package/src/runtime/routes/guardian-action-routes.ts +10 -14
  291. package/src/runtime/routes/guardian-approval-interception.ts +0 -5
  292. package/src/runtime/routes/inbound-message-handler.ts +170 -48
  293. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +4 -3
  294. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +169 -8
  295. package/src/runtime/routes/inbound-stages/background-dispatch.ts +82 -14
  296. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +23 -48
  297. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +454 -94
  298. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +309 -102
  299. package/src/runtime/routes/index.ts +2 -0
  300. package/src/runtime/routes/platform-routes.ts +99 -10
  301. package/src/runtime/routes/sight-frame-routes.ts +136 -0
  302. package/src/runtime/sync/resource-sync-events.ts +14 -37
  303. package/src/runtime/sync/sync-publisher.test.ts +5 -4
  304. package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
  305. package/src/tools/client-os.ts +11 -1
  306. package/src/tools/host-filesystem/edit.ts +2 -2
  307. package/src/tools/host-filesystem/read.ts +2 -2
  308. package/src/tools/host-filesystem/transfer.ts +2 -2
  309. package/src/tools/host-filesystem/write.ts +2 -2
  310. package/src/tools/host-terminal/host-shell.ts +2 -2
  311. package/src/tools/skills/load.ts +1 -1
  312. package/src/tools/terminal/safe-env.ts +4 -0
  313. package/src/tools/tool-input-schemas.ts +2 -0
  314. package/src/tools/tool-manifest.ts +2 -0
  315. package/src/tools/ui-surface/definitions.ts +4 -3
  316. package/src/tools/watch/watch-retro-report.ts +205 -0
  317. package/src/watch/__tests__/watch-retro.test.ts +268 -40
  318. package/src/watch/watch-retro.ts +190 -77
  319. package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +195 -0
  320. package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +198 -0
  321. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +0 -10
  322. package/src/workspace/migrations/registry.ts +4 -0
  323. package/docker-kata-pip-chroot.sh +0 -22
  324. package/src/__tests__/slack-reaction-approvals.test.ts +0 -97
  325. package/src/__tests__/slack-reaction-guardian-approval.test.ts +0 -307
  326. package/src/api/events/conversation-list-invalidated.ts +0 -38
@@ -208,8 +208,8 @@ import { initializeDb } from "../../persistence/db-init.js";
208
208
  import { attachments } from "../../persistence/schema.js";
209
209
  import { mediaBlockAttachmentId, type Message } from "../../providers/types.js";
210
210
  import {
211
+ persistAmbientSightFrame,
211
212
  persistLiveVoicePhoto,
212
- persistLiveVoiceSightFrame,
213
213
  } from "../live-voice-photo.js";
214
214
 
215
215
  await initializeDb();
@@ -291,9 +291,9 @@ describe("a camera frame that falls back to inline bytes", () => {
291
291
  try {
292
292
  const inlineFrame = await uploadUnstorableFrame("inline.png");
293
293
 
294
- expect((await persistLiveVoiceSightFrame(live.id, inlineFrame)).ok).toBe(
295
- true,
296
- );
294
+ expect(
295
+ (await persistAmbientSightFrame(live.id, inlineFrame, "voice")).ok,
296
+ ).toBe(true);
297
297
 
298
298
  const [row] = getMessages(live.id);
299
299
  // The scenario really fired: bytes in the row, no reference to a row of
@@ -321,14 +321,14 @@ describe("a camera frame that falls back to inline bytes", () => {
321
321
  const live = liveConversation("Live voice inline fallback retention");
322
322
  try {
323
323
  const inlineFrame = await uploadUnstorableFrame("inline-aged.png");
324
- expect((await persistLiveVoiceSightFrame(live.id, inlineFrame)).ok).toBe(
325
- true,
326
- );
324
+ expect(
325
+ (await persistAmbientSightFrame(live.id, inlineFrame, "voice")).ok,
326
+ ).toBe(true);
327
327
  for (const name of ["fresh-a.png", "fresh-b.png"]) {
328
328
  const fresh = await uploadFrame(name);
329
- expect((await persistLiveVoiceSightFrame(live.id, fresh)).ok).toBe(
330
- true,
331
- );
329
+ expect(
330
+ (await persistAmbientSightFrame(live.id, fresh, "voice")).ok,
331
+ ).toBe(true);
332
332
  }
333
333
 
334
334
  const assembled: Message[] = getMessages(live.id).map((row) => ({
@@ -372,9 +372,9 @@ describe("a camera frame whose message link fails", () => {
372
372
  realStore.linkAttachmentToMessage(elsewhere.id, arrivedAs, 0);
373
373
 
374
374
  failNextLink = true;
375
- expect((await persistLiveVoiceSightFrame(live.id, arrivedAs)).ok).toBe(
376
- true,
377
- );
375
+ expect(
376
+ (await persistAmbientSightFrame(live.id, arrivedAs, "voice")).ok,
377
+ ).toBe(true);
378
378
  expect(failNextLink).toBe(false);
379
379
 
380
380
  const [row] = getMessages(live.id);
@@ -411,7 +411,7 @@ describe("a camera frame whose persist throws", () => {
411
411
  messagesChangedFor.length = 0;
412
412
  failNextLink = true;
413
413
  failNextContentUpdate = true;
414
- const result = await persistLiveVoiceSightFrame(live.id, frame);
414
+ const result = await persistAmbientSightFrame(live.id, frame, "voice");
415
415
  expect(failNextLink).toBe(false);
416
416
  expect(failNextContentUpdate).toBe(false);
417
417
 
@@ -469,7 +469,7 @@ describe("a camera frame whose persist throws", () => {
469
469
  const frame = await uploadFrame("throws-early.png");
470
470
 
471
471
  failNextResolve = true;
472
- expect(await persistLiveVoiceSightFrame(live.id, frame)).toEqual({
472
+ expect(await persistAmbientSightFrame(live.id, frame, "voice")).toEqual({
473
473
  ok: false,
474
474
  });
475
475
  expect(failNextResolve).toBe(false);
@@ -492,7 +492,7 @@ describe("a camera frame whose persist throws", () => {
492
492
  failNextResolve = true;
493
493
  failNextMessageLookup = true;
494
494
  // No success claim either: doubt blocks the delete and the ok alike.
495
- expect(await persistLiveVoiceSightFrame(live.id, frame)).toEqual({
495
+ expect(await persistAmbientSightFrame(live.id, frame, "voice")).toEqual({
496
496
  ok: false,
497
497
  });
498
498
  expect(failNextMessageLookup).toBe(false);
@@ -535,7 +535,9 @@ describe("attachments a failed persist attempt cloned for itself", () => {
535
535
  scopedAttachmentIds.length = 0;
536
536
 
537
537
  failNextAddMessage = true;
538
- expect(await persistLiveVoiceSightFrame(live.id, arrivedAs)).toEqual({
538
+ expect(
539
+ await persistAmbientSightFrame(live.id, arrivedAs, "voice"),
540
+ ).toEqual({
539
541
  ok: false,
540
542
  });
541
543
  expect(failNextAddMessage).toBe(false);
@@ -571,9 +573,9 @@ describe("attachments a failed persist attempt cloned for itself", () => {
571
573
  failNextLink = true;
572
574
  failNextContentUpdate = true;
573
575
  // Committed despite the throw, which is what makes the clone the row's.
574
- expect((await persistLiveVoiceSightFrame(live.id, arrivedAs)).ok).toBe(
575
- true,
576
- );
576
+ expect(
577
+ (await persistAmbientSightFrame(live.id, arrivedAs, "voice")).ok,
578
+ ).toBe(true);
577
579
  expect(failNextLink).toBe(false);
578
580
  expect(failNextContentUpdate).toBe(false);
579
581
 
@@ -605,7 +607,9 @@ describe("attachments a failed persist attempt cloned for itself", () => {
605
607
  scopedAttachmentIds.length = 0;
606
608
 
607
609
  failNextContentRead = true;
608
- expect(await persistLiveVoiceSightFrame(live.id, arrivedAs)).toEqual({
610
+ expect(
611
+ await persistAmbientSightFrame(live.id, arrivedAs, "voice"),
612
+ ).toEqual({
609
613
  ok: false,
610
614
  });
611
615
  expect(failNextContentRead).toBe(false);
@@ -692,9 +696,9 @@ describe("a clone whose materialization fails", () => {
692
696
  mkdirSync(dirname(attachDir), { recursive: true });
693
697
  writeFileSync(attachDir, "");
694
698
 
695
- expect((await persistLiveVoiceSightFrame(live.id, arrivedAs)).ok).toBe(
696
- true,
697
- );
699
+ expect(
700
+ (await persistAmbientSightFrame(live.id, arrivedAs, "voice")).ok,
701
+ ).toBe(true);
698
702
 
699
703
  // The recovery is unchanged: the frame still lands, inline.
700
704
  const [row] = getMessages(live.id);
@@ -756,7 +760,9 @@ describe("ambient frames stay out of the indexing pipeline", () => {
756
760
  const frame = await uploadFrame("not-indexed.png");
757
761
 
758
762
  lexicallyIndexed.length = 0;
759
- expect((await persistLiveVoiceSightFrame(live.id, frame)).ok).toBe(true);
763
+ expect((await persistAmbientSightFrame(live.id, frame, "voice")).ok).toBe(
764
+ true,
765
+ );
760
766
 
761
767
  const [row] = getMessages(live.id);
762
768
  expect(row).toBeDefined();
@@ -9,7 +9,9 @@
9
9
  * The two neighbouring contracts are pinned by their own suites: a deliberate
10
10
  * snap persists standalone and untagged (`live-voice-attach-image.test.ts`), a
11
11
  * parked frame persists nothing and rides the next turn
12
- * (`live-voice-attach-frame.test.ts`).
12
+ * (`live-voice-attach-frame.test.ts`). One photo case does live here, because
13
+ * it pins the edge of something this suite owns: the refusal's attachment
14
+ * echo, which is the keep stream's alone.
13
15
  */
14
16
 
15
17
  import { describe, expect, mock, test } from "bun:test";
@@ -276,6 +278,9 @@ describe("live-voice camera frames kept mid-call", () => {
276
278
  ).toMatchObject({
277
279
  type: "error",
278
280
  frameType: "sight_frame",
281
+ // The id it refused, so the client retires that keep rather than
282
+ // guessing among the sends it has not heard back on.
283
+ attachmentId: "att-missing",
279
284
  recoverable: true,
280
285
  });
281
286
  // Nothing landed and the call carries on.
@@ -286,6 +291,70 @@ describe("live-voice camera frames kept mid-call", () => {
286
291
  }
287
292
  });
288
293
 
294
+ test("each refused keep names its own attachment", async () => {
295
+ // The point of the echo: keeps overlap, so a shared `frameType` cannot
296
+ // tell two outstanding sends apart.
297
+ const harness = createSessionHarness("Sight keep two refusals");
298
+ try {
299
+ await harness.session.start();
300
+ const before = harness.frames.length;
301
+
302
+ await harness.session.handleClientFrame({
303
+ type: "sight_frame",
304
+ attachmentId: "att-missing-1",
305
+ });
306
+ await harness.session.handleClientFrame({
307
+ type: "sight_frame",
308
+ attachmentId: "att-missing-2",
309
+ });
310
+ await waitFor(
311
+ () =>
312
+ harness.frames.slice(before).filter((frame) => frame.type === "error")
313
+ .length === 2,
314
+ { message: "Timed out waiting for both refusals" },
315
+ );
316
+
317
+ const refused = harness.frames
318
+ .slice(before)
319
+ .filter((frame) => frame.type === "error")
320
+ .map((frame) => frame.attachmentId);
321
+ expect(refused.sort()).toEqual(["att-missing-1", "att-missing-2"]);
322
+ } finally {
323
+ harness.dispose();
324
+ }
325
+ });
326
+
327
+ test("a photo that cannot be stored names no attachment", async () => {
328
+ // Scope pin: the echo is the keep stream's, where sends overlap. A photo
329
+ // is one deliberate snap at a time, and its receipt strip already knows
330
+ // which one it is waiting on.
331
+ const harness = createSessionHarness("Sight keep photo scope");
332
+ try {
333
+ await harness.session.start();
334
+ const before = harness.frames.length;
335
+
336
+ await harness.session.handleClientFrame({
337
+ type: "attach_image",
338
+ attachmentId: "att-missing",
339
+ });
340
+ await waitFor(() => harness.frames.length > before, {
341
+ message: "Timed out waiting for the rejected photo's error",
342
+ });
343
+
344
+ const error = harness.frames
345
+ .slice(before)
346
+ .find((frame) => frame.type === "error");
347
+ expect(error).toMatchObject({
348
+ type: "error",
349
+ frameType: "attach_image",
350
+ recoverable: true,
351
+ });
352
+ expect(error?.attachmentId).toBeUndefined();
353
+ } finally {
354
+ harness.dispose();
355
+ }
356
+ });
357
+
289
358
  test("refuses an unknown id while a turn is running, without waiting it out", async () => {
290
359
  // The id is checked before the persist is scheduled. Behind the persist it
291
360
  // would be answered only once the turn released the lock, long after the