@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
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Migration 371 adds the nullable `auth_error_credential` column that names
3
+ * which Claude token a run's credential failure was refused on, and is
4
+ * idempotent so a repair flow can re-run it.
5
+ */
6
+
7
+ import { Database } from "bun:sqlite";
8
+ import { describe, expect, test } from "bun:test";
9
+
10
+ import { drizzle } from "drizzle-orm/bun-sqlite";
11
+
12
+ import { getSqliteFrom } from "../../db-connection.js";
13
+ import * as schema from "../../schema.js";
14
+ import { migrateAcpSessionHistoryAuthErrorCredential } from "../371-acp-session-history-auth-error-credential.js";
15
+
16
+ function createTestDb() {
17
+ const sqlite = new Database(":memory:");
18
+ sqlite.exec("PRAGMA journal_mode=WAL");
19
+ sqlite.exec(
20
+ "CREATE TABLE acp_session_history (id TEXT PRIMARY KEY, status TEXT)",
21
+ );
22
+ return drizzle(sqlite, { schema });
23
+ }
24
+
25
+ function columnNames(db: ReturnType<typeof createTestDb>): string[] {
26
+ const rows = getSqliteFrom(db)
27
+ .prepare("PRAGMA table_info(acp_session_history)")
28
+ .all() as { name: string }[];
29
+ return rows.map((row) => row.name);
30
+ }
31
+
32
+ describe("migrateAcpSessionHistoryAuthErrorCredential", () => {
33
+ test("adds the column when it is missing", () => {
34
+ const db = createTestDb();
35
+
36
+ migrateAcpSessionHistoryAuthErrorCredential(db);
37
+
38
+ expect(columnNames(db)).toContain("auth_error_credential");
39
+ });
40
+
41
+ test("re-running is a no-op", () => {
42
+ const db = createTestDb();
43
+
44
+ migrateAcpSessionHistoryAuthErrorCredential(db);
45
+ migrateAcpSessionHistoryAuthErrorCredential(db);
46
+
47
+ expect(columnNames(db)).toContain("auth_error_credential");
48
+ });
49
+
50
+ test("leaves existing rows null, which reads as a marker worth serving", () => {
51
+ // A row marked before this column existed names no credential. The
52
+ // comparison treats that as no evidence the failure was repaired and
53
+ // serves the marker, so an upgrade cannot silently swallow a card.
54
+ const db = createTestDb();
55
+ getSqliteFrom(db).exec(
56
+ "INSERT INTO acp_session_history (id, status) VALUES ('run-1', 'failed')",
57
+ );
58
+
59
+ migrateAcpSessionHistoryAuthErrorCredential(db);
60
+
61
+ const row = getSqliteFrom(db)
62
+ .prepare(
63
+ "SELECT auth_error_credential FROM acp_session_history WHERE id = 'run-1'",
64
+ )
65
+ .get() as { auth_error_credential: string | null };
66
+ expect(row.auth_error_credential).toBeNull();
67
+ });
68
+ });
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Migration 372 creates `acp_refused_credentials`, the record of which Claude
3
+ * tokens Claude has refused. It is separate from the session-history marker so
4
+ * that clearing session history cannot change which credential a spawn picks.
5
+ */
6
+
7
+ import { Database } from "bun:sqlite";
8
+ import { describe, expect, test } from "bun:test";
9
+
10
+ import { drizzle } from "drizzle-orm/bun-sqlite";
11
+
12
+ import { getSqliteFrom } from "../../db-connection.js";
13
+ import * as schema from "../../schema.js";
14
+ import { migrateCreateAcpRefusedCredentials } from "../372-create-acp-refused-credentials.js";
15
+
16
+ function createTestDb() {
17
+ const sqlite = new Database(":memory:");
18
+ sqlite.exec("PRAGMA journal_mode=WAL");
19
+ return drizzle(sqlite, { schema });
20
+ }
21
+
22
+ function tableNames(db: ReturnType<typeof createTestDb>): string[] {
23
+ const rows = getSqliteFrom(db)
24
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table'")
25
+ .all() as { name: string }[];
26
+ return rows.map((row) => row.name);
27
+ }
28
+
29
+ describe("migrateCreateAcpRefusedCredentials", () => {
30
+ test("creates the table", () => {
31
+ const db = createTestDb();
32
+
33
+ migrateCreateAcpRefusedCredentials(db);
34
+
35
+ expect(tableNames(db)).toContain("acp_refused_credentials");
36
+ });
37
+
38
+ test("re-running is a no-op", () => {
39
+ const db = createTestDb();
40
+
41
+ migrateCreateAcpRefusedCredentials(db);
42
+ migrateCreateAcpRefusedCredentials(db);
43
+
44
+ expect(tableNames(db)).toContain("acp_refused_credentials");
45
+ });
46
+
47
+ test("keeps one row per digest, so a retried refusal is not an error", () => {
48
+ const db = createTestDb();
49
+ migrateCreateAcpRefusedCredentials(db);
50
+ const raw = getSqliteFrom(db);
51
+
52
+ raw.exec(
53
+ "INSERT OR IGNORE INTO acp_refused_credentials (digest, refused_at) VALUES ('abc', 1)",
54
+ );
55
+ raw.exec(
56
+ "INSERT OR IGNORE INTO acp_refused_credentials (digest, refused_at) VALUES ('abc', 2)",
57
+ );
58
+
59
+ const rows = raw
60
+ .prepare("SELECT digest, refused_at FROM acp_refused_credentials")
61
+ .all() as { digest: string; refused_at: number }[];
62
+ expect(rows).toEqual([{ digest: "abc", refused_at: 1 }]);
63
+ });
64
+ });
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Migration 373 indexes a conversation's credential-failure markers, which is
3
+ * the query every snapshot refetch runs.
4
+ */
5
+
6
+ import { Database } from "bun:sqlite";
7
+ import { describe, expect, test } from "bun:test";
8
+
9
+ import { drizzle } from "drizzle-orm/bun-sqlite";
10
+
11
+ import { getSqliteFrom } from "../../db-connection.js";
12
+ import * as schema from "../../schema.js";
13
+ import { migrateAcpAuthMarkerIndex } from "../373-acp-auth-marker-index.js";
14
+
15
+ const MARKER_QUERY = `
16
+ SELECT agent_id FROM acp_session_history
17
+ WHERE parent_conversation_id = 'conv-1' AND auth_error_code IS NOT NULL
18
+ ORDER BY started_at DESC LIMIT 20
19
+ `;
20
+
21
+ function createTestDb() {
22
+ const sqlite = new Database(":memory:");
23
+ sqlite.exec("PRAGMA journal_mode=WAL");
24
+ sqlite.exec(
25
+ `CREATE TABLE acp_session_history (
26
+ id TEXT PRIMARY KEY,
27
+ agent_id TEXT,
28
+ parent_conversation_id TEXT,
29
+ started_at INTEGER,
30
+ auth_error_code TEXT
31
+ )`,
32
+ );
33
+ return drizzle(sqlite, { schema });
34
+ }
35
+
36
+ function indexNames(db: ReturnType<typeof createTestDb>): string[] {
37
+ const rows = getSqliteFrom(db)
38
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'index'")
39
+ .all() as { name: string }[];
40
+ return rows.map((row) => row.name);
41
+ }
42
+
43
+ function queryPlan(db: ReturnType<typeof createTestDb>): string {
44
+ const rows = getSqliteFrom(db)
45
+ .prepare(`EXPLAIN QUERY PLAN ${MARKER_QUERY}`)
46
+ .all() as { detail: string }[];
47
+ return rows.map((row) => row.detail).join(" | ");
48
+ }
49
+
50
+ describe("migrateAcpAuthMarkerIndex", () => {
51
+ test("creates the index", () => {
52
+ const db = createTestDb();
53
+
54
+ migrateAcpAuthMarkerIndex(db);
55
+
56
+ expect(indexNames(db)).toContain("idx_acp_session_history_auth_marker");
57
+ });
58
+
59
+ test("re-running is a no-op", () => {
60
+ const db = createTestDb();
61
+
62
+ migrateAcpAuthMarkerIndex(db);
63
+ migrateAcpAuthMarkerIndex(db);
64
+
65
+ expect(indexNames(db)).toContain("idx_acp_session_history_auth_marker");
66
+ });
67
+
68
+ test("the marker lookup uses it, and stops sorting to answer", () => {
69
+ // The point of the index, not merely its existence. Without it the planner
70
+ // gathers the conversation's rows and sorts them, which is work that grows
71
+ // with how many runs it has had rather than with the few the answer needs.
72
+ const db = createTestDb();
73
+ const before = queryPlan(db);
74
+
75
+ migrateAcpAuthMarkerIndex(db);
76
+
77
+ const after = queryPlan(db);
78
+ expect(before).toContain("SCAN");
79
+ expect(after).toContain("idx_acp_session_history_auth_marker");
80
+ expect(after).not.toContain("TEMP B-TREE");
81
+ });
82
+ });
@@ -33,6 +33,10 @@ export const acpSessionHistory = sqliteTable(
33
33
  // Usage metadata. Null for rows written before these columns existed.
34
34
  task: text("task"),
35
35
  parentToolUseId: text("parent_tool_use_id"),
36
+ /** Credential failure that ended the run, when one did. Drives the inline
37
+ * Connect card on reopen; cleared when a replacement token is stored. */
38
+ authErrorCode: text("auth_error_code"),
39
+ authErrorCredential: text("auth_error_credential"),
36
40
  usedTokens: integer("used_tokens"),
37
41
  contextSize: integer("context_size"),
38
42
  costAmount: real("cost_amount"),
@@ -47,5 +51,26 @@ export const acpSessionHistory = sqliteTable(
47
51
  index("idx_acp_session_history_parent_conversation_id").on(
48
52
  table.parentConversationId,
49
53
  ),
54
+ // Partial in the database (see migration 373): only rows carrying a
55
+ // credential failure are indexed, which is what keeps the marker lookup a
56
+ // fixed cost rather than one that grows with a conversation's run count.
57
+ // Declared here without the predicate because the schema builder has no
58
+ // way to express one; the migration is the source of truth for its shape.
59
+ index("idx_acp_session_history_auth_marker").on(
60
+ table.parentConversationId,
61
+ table.startedAt,
62
+ ),
50
63
  ],
51
64
  );
65
+
66
+ /**
67
+ * Claude tokens Claude has refused, by digest.
68
+ *
69
+ * Separate from the session-history marker on purpose: a marker is about
70
+ * showing a card for one run and is deleted with it, while this is about which
71
+ * credential a spawn may resolve and must survive the user clearing history.
72
+ */
73
+ export const acpRefusedCredentials = sqliteTable("acp_refused_credentials", {
74
+ digest: text("digest").primaryKey(),
75
+ refusedAt: integer("refused_at").notNull(),
76
+ });
@@ -477,6 +477,10 @@ import { migrateChatgptSubscriptionRowIdentity } from "./migrations/366-chatgpt-
477
477
  import { migrateCreateWatchTimelineEntries } from "./migrations/367-create-watch-timeline-entries.js";
478
478
  import { migrateWatchTimelineScreenshotBlob } from "./migrations/368-watch-timeline-screenshot-blob.js";
479
479
  import { migrateCreateAppPins } from "./migrations/369-create-app-pins.js";
480
+ import { migrateAcpSessionHistoryAuthErrorCode } from "./migrations/370-acp-session-history-auth-error-code.js";
481
+ import { migrateAcpSessionHistoryAuthErrorCredential } from "./migrations/371-acp-session-history-auth-error-credential.js";
482
+ import { migrateCreateAcpRefusedCredentials } from "./migrations/372-create-acp-refused-credentials.js";
483
+ import { migrateAcpAuthMarkerIndex } from "./migrations/373-acp-auth-marker-index.js";
480
484
  import type { MigrationStep } from "./migrations/run-migrations.js";
481
485
 
482
486
  export const migrationSteps: MigrationStep[] = [
@@ -1588,4 +1592,8 @@ export const migrationSteps: MigrationStep[] = [
1588
1592
  migrateCreateWatchTimelineEntries,
1589
1593
  migrateWatchTimelineScreenshotBlob,
1590
1594
  migrateCreateAppPins,
1595
+ migrateAcpSessionHistoryAuthErrorCode,
1596
+ migrateAcpSessionHistoryAuthErrorCredential,
1597
+ migrateCreateAcpRefusedCredentials,
1598
+ migrateAcpAuthMarkerIndex,
1591
1599
  ];
@@ -3,6 +3,7 @@ import {
3
3
  beforeEach,
4
4
  describe,
5
5
  expect,
6
+ jest,
6
7
  mock,
7
8
  setSystemTime,
8
9
  test,
@@ -66,14 +67,30 @@ function makeQueue(
66
67
  return queue;
67
68
  }
68
69
 
70
+ const FROZEN_NOW = new Date("2026-01-01T00:00:00.000Z").getTime();
71
+
72
+ /**
73
+ * Flush already-queued microtasks. Fake timers stop the clock but not the
74
+ * promise chain `enqueue` appends onto, so the PATCH work a timer kicks off
75
+ * lands here.
76
+ */
69
77
  async function settle(): Promise<void> {
70
- for (let i = 0; i < 10; i++) {
71
- await new Promise((r) => setTimeout(r, 5));
78
+ for (let i = 0; i < 20; i += 1) {
79
+ await Promise.resolve();
72
80
  }
73
81
  }
74
82
 
83
+ /** Advance `Date.now()` and the queue's `setTimeout` handles together. */
84
+ async function elapse(ms: number): Promise<void> {
85
+ setSystemTime(new Date(Date.now() + ms));
86
+ jest.advanceTimersByTime(ms);
87
+ await settle();
88
+ }
89
+
75
90
  describe("createPlatformPatchQueue", () => {
76
91
  beforeEach(() => {
92
+ jest.useFakeTimers();
93
+ setSystemTime(new Date(FROZEN_NOW));
77
94
  patches = [];
78
95
  builds = 0;
79
96
  respond = () => new Response("{}", { status: 200 });
@@ -85,6 +102,8 @@ describe("createPlatformPatchQueue", () => {
85
102
  for (const queue of queues) {
86
103
  queue.dispose();
87
104
  }
105
+ jest.useRealTimers();
106
+ setSystemTime();
88
107
  });
89
108
 
90
109
  test("PATCHes once and dedups an unchanged key", async () => {
@@ -203,21 +222,16 @@ describe("createPlatformPatchQueue", () => {
203
222
  saveSyncedKey: (synced) => saved.push(synced),
204
223
  });
205
224
  const start = Date.now();
206
- setSystemTime(new Date(start));
207
- try {
208
- queue.enqueue("a");
209
- await settle();
210
- setSystemTime(new Date(start + 999));
211
- queue.enqueue("a");
212
- await settle();
213
- setSystemTime(new Date(start + 1000));
214
- queue.enqueue("a");
215
- await settle();
216
- queue.enqueue("a");
217
- await settle();
218
- } finally {
219
- setSystemTime();
220
- }
225
+ queue.enqueue("a");
226
+ await settle();
227
+ setSystemTime(new Date(start + 999));
228
+ queue.enqueue("a");
229
+ await settle();
230
+ setSystemTime(new Date(start + 1000));
231
+ queue.enqueue("a");
232
+ await settle();
233
+ queue.enqueue("a");
234
+ await settle();
221
235
 
222
236
  expect(patches).toHaveLength(2);
223
237
  expect(saved.map((s) => s.syncedAt)).toEqual([start, start + 1000]);
@@ -243,14 +257,12 @@ describe("createPlatformPatchQueue", () => {
243
257
  await settle();
244
258
  expect(patches).toHaveLength(1);
245
259
 
246
- await new Promise((r) => setTimeout(r, 300));
247
- await settle();
260
+ await elapse(300);
248
261
  expect(patches).toHaveLength(2);
249
262
  expect(builds).toBe(2);
250
263
 
251
264
  queue.dispose();
252
- await new Promise((r) => setTimeout(r, 300));
253
- await settle();
265
+ await elapse(300);
254
266
  expect(patches).toHaveLength(2);
255
267
  });
256
268
 
@@ -266,8 +278,7 @@ describe("createPlatformPatchQueue", () => {
266
278
  await settle();
267
279
  expect(patches).toHaveLength(0);
268
280
 
269
- await new Promise((r) => setTimeout(r, 300));
270
- await settle();
281
+ await elapse(300);
271
282
  expect(patches).toHaveLength(1);
272
283
  });
273
284
 
@@ -279,12 +290,10 @@ describe("createPlatformPatchQueue", () => {
279
290
  expect(patches).toHaveLength(1);
280
291
 
281
292
  respond = () => new Response("{}", { status: 200 });
282
- await new Promise((r) => setTimeout(r, 100));
283
- await settle();
293
+ await elapse(100);
284
294
  expect(patches).toHaveLength(2);
285
295
 
286
- await new Promise((r) => setTimeout(r, 100));
287
- await settle();
296
+ await elapse(100);
288
297
  queue.enqueue("a");
289
298
  await settle();
290
299
  expect(patches).toHaveLength(2);
@@ -301,8 +310,7 @@ describe("createPlatformPatchQueue", () => {
301
310
  queue.enqueue("a");
302
311
  await settle();
303
312
  mockClient = makeClient();
304
- await new Promise((r) => setTimeout(r, 100));
305
- await settle();
313
+ await elapse(100);
306
314
 
307
315
  expect(patches).toHaveLength(1);
308
316
  });
@@ -312,14 +320,11 @@ describe("createPlatformPatchQueue", () => {
312
320
  respond = () => new Response("nope", { status: 500 });
313
321
  queue.enqueue("a");
314
322
  await settle();
315
- await new Promise((r) => setTimeout(r, 50));
316
- await settle();
317
- await new Promise((r) => setTimeout(r, 50));
318
- await settle();
323
+ await elapse(50);
324
+ await elapse(50);
319
325
  expect(patches).toHaveLength(3);
320
326
 
321
- await new Promise((r) => setTimeout(r, 100));
322
- await settle();
327
+ await elapse(100);
323
328
  expect(patches).toHaveLength(3);
324
329
  });
325
330
 
@@ -332,12 +337,10 @@ describe("createPlatformPatchQueue", () => {
332
337
  await settle();
333
338
  expect(patches.map((p) => p.body.value)).toEqual(["a", "b"]);
334
339
 
335
- await new Promise((r) => setTimeout(r, 100));
336
- await settle();
340
+ await elapse(100);
337
341
  expect(patches.map((p) => p.body.value)).toEqual(["a", "b", "b"]);
338
342
 
339
- await new Promise((r) => setTimeout(r, 100));
340
- await settle();
343
+ await elapse(100);
341
344
  expect(patches).toHaveLength(3);
342
345
  });
343
346
 
@@ -347,8 +350,7 @@ describe("createPlatformPatchQueue", () => {
347
350
  queue.enqueue("a");
348
351
  await settle();
349
352
  queue.dispose();
350
- await new Promise((r) => setTimeout(r, 300));
351
- await settle();
353
+ await elapse(300);
352
354
 
353
355
  expect(patches).toHaveLength(1);
354
356
  });
@@ -223,6 +223,18 @@ export { getConfiguredProvider } from "../providers/provider-send-message.js";
223
223
  // an image, embedding it, re-encoding it — use this instead of reaching into
224
224
  // the host attachment store, so they stay agnostic to how media is persisted.
225
225
  export { resolveMediaSourceData } from "../providers/media-resolve.js";
226
+ // Build the `_attachmentId` property of a media block as a spreadable fragment,
227
+ // omitting it when there is no usable id. A plugin that rebuilds a media block
228
+ // (resizing an image, re-encoding a file) uses this to carry the block's link
229
+ // to its attachment row across the rebuild, so host consumers that correlate a
230
+ // block back to its attachment still can.
231
+ export { attachmentIdFragment } from "../providers/types.js";
232
+ // Read the attachment row a media block came from, whichever shape it is in: a
233
+ // reference names it on `source.attachmentId`, an inline block on
234
+ // `_attachmentId`. A plugin that rebuilds a block into inline bytes derives the
235
+ // id through this before stamping it, since the reference shape's id would
236
+ // otherwise be lost with the source it replaced.
237
+ export { mediaBlockAttachmentId } from "../providers/types.js";
226
238
  // Classify a provider stop reason: whether the turn was truncated at the
227
239
  // output token cap (vs. a natural stop or a tool call). A `post-model-call`
228
240
  // hook reads it off `PostModelCallContext.stopReason` to decide whether to
@@ -83,6 +83,8 @@ export interface ContextWindowResult {
83
83
  summaryOutputTokens: number;
84
84
  summaryModel: string;
85
85
  summaryCallSite?: LLMCallSite;
86
+ /** See {@link CompactionRunResult.summaryResolutionCallSite}. */
87
+ summaryResolutionCallSite?: LLMCallSite;
86
88
  summaryOverrideProfile?: string | null;
87
89
  /** See {@link CompactionRunResult.summaryActualProvider}. */
88
90
  summaryActualProvider?: string;
@@ -15,7 +15,9 @@
15
15
  */
16
16
 
17
17
  import {
18
+ attachmentIdFragment,
18
19
  type ContentBlock,
20
+ mediaBlockAttachmentId,
19
21
  type Message,
20
22
  resolveMediaSourceData,
21
23
  } from "@vellumai/plugin-api";
@@ -105,10 +107,13 @@ export async function unsendableImageReplacement(
105
107
  if (mediaTypeMismatch) {
106
108
  // Only the label is wrong, so keep the source shape: a workspace_ref
107
109
  // stays a reference (inlining it would bake the full payload into the
108
- // stored message row) and only media_type is corrected.
110
+ // stored message row) and only media_type is corrected. Because the
111
+ // source survives, a reference still names its own attachment through
112
+ // it, and only an inline block's top-level id needs carrying.
109
113
  return {
110
114
  type: "image",
111
115
  source: { ...block.source, media_type: effectiveMediaType },
116
+ ...attachmentIdFragment(block._attachmentId),
112
117
  };
113
118
  }
114
119
  return null;
@@ -128,6 +133,12 @@ export async function unsendableImageReplacement(
128
133
  media_type: optimized.mediaType,
129
134
  data: optimized.data,
130
135
  },
136
+ // The resized block is inline bytes whatever the input was, so a
137
+ // reference's `source.attachmentId` is about to be replaced along with
138
+ // the source that held it. Derive the id from either shape before
139
+ // stamping, or a reference-backed image loses its attachment here and
140
+ // the durable rewrite makes that permanent.
141
+ ...attachmentIdFragment(mediaBlockAttachmentId(block)),
131
142
  };
132
143
  }
133
144
  return { type: "text", text: UNSENDABLE_IMAGE_NOTE };