@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
@@ -23,6 +23,7 @@
23
23
  import { randomUUID } from "node:crypto";
24
24
 
25
25
  import {
26
+ DELIVERY_STATUS,
26
27
  deriveGuardianRequestSourceType,
27
28
  isGuardianRequestExpired,
28
29
  } from "@vellumai/gateway-client";
@@ -177,7 +178,7 @@ export function createGuardianGatewaySim() {
177
178
  destinationConversationId: params.destinationConversationId ?? null,
178
179
  destinationChatId: params.destinationChatId ?? null,
179
180
  destinationMessageId: params.destinationMessageId ?? null,
180
- status: params.status ?? "pending",
181
+ status: params.status ?? DELIVERY_STATUS.pending,
181
182
  createdAt: params.createdAt ?? now,
182
183
  updatedAt: params.updatedAt ?? now,
183
184
  };
@@ -290,6 +291,11 @@ export function createGuardianGatewaySim() {
290
291
  if (!row || row.status !== params.expectedStatus) {
291
292
  return { applied: false, reason: "status_conflict" };
292
293
  }
294
+ // Mirrors the gateway decide CAS: the deadline is part of the
295
+ // arbitration, so a decision arriving past expiresAt loses atomically.
296
+ if (isGuardianRequestExpired(row, Date.now())) {
297
+ return { applied: false, reason: "status_conflict" };
298
+ }
293
299
  if (state.outcomeError && params.aclOutcome) {
294
300
  // Gateway transaction rollback: the CAS never lands.
295
301
  throw state.outcomeError;
@@ -344,14 +350,22 @@ export function createGuardianGatewaySim() {
344
350
  }
345
351
 
346
352
  async function expireGuardianRequest(id: string): Promise<void> {
353
+ // Mirrors the gateway: deliveries expire only when the request CAS
354
+ // applies, so a decided request's rows are never restamped.
347
355
  const row = requests.get(id);
348
- if (row?.status === "pending") {
349
- row.status = "expired";
350
- row.updatedAt = Date.now();
356
+ if (row?.status !== "pending") {
357
+ return;
351
358
  }
359
+ row.status = "expired";
360
+ row.updatedAt = Date.now();
352
361
  for (const delivery of deliveries) {
353
- if (delivery.requestId === id) {
354
- delivery.status = "expired";
362
+ // A withdrawn row is the daemon's per-surface receipt; the gateway
363
+ // preserves it through the bulk flip.
364
+ if (
365
+ delivery.requestId === id &&
366
+ delivery.status !== DELIVERY_STATUS.withdrawn
367
+ ) {
368
+ delivery.status = DELIVERY_STATUS.expired;
355
369
  delivery.updatedAt = Date.now();
356
370
  }
357
371
  }
@@ -364,9 +378,13 @@ export function createGuardianGatewaySim() {
364
378
  if (row.status !== "pending") {
365
379
  continue;
366
380
  }
381
+ // Mirrors the gateway contract: only the kinds that die with the
382
+ // daemon's in-memory pendingInteractions map expire at boot.
383
+ // Persistent kinds stay pending, whatever their deadline; their
384
+ // expiry belongs to the sweep, which owns the fan-out.
367
385
  const interactionBound =
368
386
  row.kind === "tool_approval" || row.kind === "pending_question";
369
- if (interactionBound || isGuardianRequestExpired(row, now)) {
387
+ if (interactionBound) {
370
388
  row.status = "expired";
371
389
  row.updatedAt = now;
372
390
  expired += 1;
@@ -375,19 +393,18 @@ export function createGuardianGatewaySim() {
375
393
  return expired;
376
394
  }
377
395
 
378
- async function sweepExpiredGuardianRequests(
379
- now?: number,
396
+ async function listExpiredPendingGuardianRequests(
397
+ limit = 50,
380
398
  ): Promise<SimGuardianRequest[]> {
381
- const cutoff = now ?? Date.now();
382
- const expired: SimGuardianRequest[] = [];
399
+ const cutoff = Date.now();
400
+ const stale: SimGuardianRequest[] = [];
383
401
  for (const row of requests.values()) {
384
402
  if (row.status === "pending" && isGuardianRequestExpired(row, cutoff)) {
385
- row.status = "expired";
386
- row.updatedAt = cutoff;
387
- expired.push({ ...row });
403
+ stale.push({ ...row });
388
404
  }
389
405
  }
390
- return expired;
406
+ stale.sort((a, b) => (a.expiresAt ?? 0) - (b.expiresAt ?? 0));
407
+ return stale.slice(0, limit);
391
408
  }
392
409
 
393
410
  async function createGuardianRequestDelivery(
@@ -406,7 +423,13 @@ export function createGuardianGatewaySim() {
406
423
  if (!row) {
407
424
  throw new Error(`sim: delivery ${id} not found`);
408
425
  }
409
- Object.assign(row, patch, { updatedAt: Date.now() });
426
+ // Mirrors the gateway store: `withdrawn` is a terminal per-surface
427
+ // receipt a later status patch never overwrites.
428
+ const effectivePatch =
429
+ row.status === "withdrawn" && patch.status !== undefined
430
+ ? { ...patch, status: row.status }
431
+ : patch;
432
+ Object.assign(row, effectivePatch, { updatedAt: Date.now() });
410
433
  }
411
434
 
412
435
  async function listGuardianRequestDeliveries(
@@ -418,6 +441,19 @@ export function createGuardianGatewaySim() {
418
441
  .map((d) => ({ ...d }));
419
442
  }
420
443
 
444
+ async function listGuardianRequestDeliveriesByChat(
445
+ channel: string,
446
+ chatId: string,
447
+ ): Promise<SimGuardianDelivery[]> {
448
+ throwIfReadError();
449
+ return deliveries
450
+ .filter(
451
+ (d) =>
452
+ d.destinationChannel === channel && d.destinationChatId === chatId,
453
+ )
454
+ .map((d) => ({ ...d }));
455
+ }
456
+
421
457
  async function getPendingRequestByDestinationMessage(
422
458
  channel: string,
423
459
  chatId: string,
@@ -504,7 +540,6 @@ export function createGuardianGatewaySim() {
504
540
  async function isGuardianRequestInScope(
505
541
  requestId: string,
506
542
  conversationId: string,
507
- channel?: string,
508
543
  ): Promise<boolean> {
509
544
  throwIfReadError();
510
545
  const request = requests.get(requestId);
@@ -517,8 +552,7 @@ export function createGuardianGatewaySim() {
517
552
  return deliveries.some(
518
553
  (d) =>
519
554
  d.requestId === requestId &&
520
- d.destinationConversationId === conversationId &&
521
- (!channel || d.destinationChannel === channel),
555
+ d.destinationConversationId === conversationId,
522
556
  );
523
557
  }
524
558
 
@@ -575,15 +609,17 @@ export function createGuardianGatewaySim() {
575
609
  getGuardianRequest,
576
610
  getGuardianRequestOrNull: degrade(getGuardianRequest, null),
577
611
  getGuardianRequestByCodeOrNull: degrade(getGuardianRequestByCode, null),
612
+ listGuardianRequests,
578
613
  listGuardianRequestsOrEmpty: degrade(listGuardianRequests, []),
579
614
  updateGuardianRequest,
580
615
  decideGuardianRequest,
581
616
  expireGuardianRequest,
582
617
  expireInteractionBoundGuardianRequests,
583
- sweepExpiredGuardianRequests,
618
+ listExpiredPendingGuardianRequests,
584
619
  createGuardianRequestDelivery,
585
620
  updateGuardianRequestDelivery,
586
621
  listGuardianRequestDeliveries,
622
+ listGuardianRequestDeliveriesByChat,
587
623
  listGuardianRequestDeliveriesOrEmpty: degrade(
588
624
  listGuardianRequestDeliveries,
589
625
  [],
@@ -34,7 +34,13 @@ mock.module("../approvals/guardian-decision-primitive.js", () => ({
34
34
  applyGuardianDecisionMock(params),
35
35
  }));
36
36
 
37
+ // Spread the actual module so transitive importers of names this
38
+ // factory does not stub keep resolving (a partial factory breaks at
39
+ // import time when the graph gains a new named import).
40
+ const actualGatewayGuardianRequests =
41
+ await import("../channels/gateway-guardian-requests.js");
37
42
  mock.module("../channels/gateway-guardian-requests.js", () => ({
43
+ ...actualGatewayGuardianRequests,
38
44
  getGuardianRequestOrNull: (id: string) =>
39
45
  Promise.resolve(requestsById.get(id) ?? null),
40
46
  getGuardianRequestByCodeOrNull: () => Promise.resolve(null),
@@ -0,0 +1,162 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ const completeSurfaceAndNotify = mock(() => true);
4
+ const markSurfaceCompleted = mock(() => true);
5
+ mock.module("../daemon/conversation-surfaces.js", () => ({
6
+ completeSurfaceAndNotify,
7
+ markSurfaceCompleted,
8
+ }));
9
+
10
+ const withdrawSlackApprovalCard = mock(
11
+ async (_params: Record<string, unknown>) => {},
12
+ );
13
+ mock.module("../messaging/providers/slack/withdraw.js", () => ({
14
+ withdrawSlackApprovalCard,
15
+ }));
16
+
17
+ const withdrawDiscordApprovalCard = mock(async (_params: unknown) => undefined);
18
+ mock.module("../messaging/providers/discord/withdraw.js", () => ({
19
+ withdrawDiscordApprovalCard,
20
+ }));
21
+
22
+ const withdrawTelegramApprovalCard = mock(
23
+ async (_params: Record<string, unknown>) => ({ complete: true }),
24
+ );
25
+ mock.module("../messaging/providers/telegram-bot/withdraw.js", () => ({
26
+ withdrawTelegramApprovalCard,
27
+ }));
28
+
29
+ import {
30
+ bridgeState,
31
+ gatewayGuardianRequestsStoreBridge,
32
+ } from "./helpers/gateway-guardian-requests-store-bridge.js";
33
+
34
+ mock.module(
35
+ "../channels/gateway-guardian-requests.js",
36
+ () => gatewayGuardianRequestsStoreBridge,
37
+ );
38
+
39
+ import { syncTerminalGuardianRequestStatus } from "../approvals/guardian-request-status-sync.js";
40
+ import { initializeDb } from "../persistence/db-init.js";
41
+
42
+ await initializeDb();
43
+
44
+ const PRINCIPAL_ID = "status-sync-test-principal";
45
+
46
+ function makeRequest(overrides: Record<string, unknown> = {}) {
47
+ return bridgeState.seedRequest({
48
+ kind: "tool_approval",
49
+ sourceType: "channel",
50
+ sourceChannel: "slack",
51
+ toolName: "shell",
52
+ guardianPrincipalId: PRINCIPAL_ID,
53
+ ...overrides,
54
+ });
55
+ }
56
+
57
+ function deliveriesFor(requestId: string) {
58
+ return bridgeState.deliveries.filter((d) => d.requestId === requestId);
59
+ }
60
+
61
+ describe("syncTerminalGuardianRequestStatus", () => {
62
+ beforeEach(() => {
63
+ bridgeState.reset();
64
+ completeSurfaceAndNotify.mockClear();
65
+ markSurfaceCompleted.mockClear();
66
+ withdrawSlackApprovalCard.mockClear();
67
+ });
68
+
69
+ test("a landed CAS withdraws every delivered projection", async () => {
70
+ const req = makeRequest();
71
+ const vellum = bridgeState.seedDelivery({
72
+ requestId: req.id,
73
+ destinationChannel: "vellum",
74
+ destinationConversationId: "conv-thread",
75
+ });
76
+ const slack = bridgeState.seedDelivery({
77
+ requestId: req.id,
78
+ destinationChannel: "slack",
79
+ destinationChatId: "C1",
80
+ destinationMessageId: "1.0",
81
+ destinationConversationId: "conv-dm",
82
+ });
83
+
84
+ await syncTerminalGuardianRequestStatus({
85
+ requestId: req.id,
86
+ status: "approved",
87
+ syncContext: "test",
88
+ });
89
+
90
+ expect(bridgeState.requests.get(req.id)?.status).toBe("approved");
91
+ // Both in-app projections converge live (no origin channel: the acting
92
+ // surface was the confirmation prompt, never the card).
93
+ expect(completeSurfaceAndNotify).toHaveBeenCalledWith(
94
+ "conv-thread",
95
+ `tool-approval-${req.id}`,
96
+ "Approved",
97
+ );
98
+ expect(completeSurfaceAndNotify).toHaveBeenCalledWith(
99
+ "conv-dm",
100
+ `tool-approval-${req.id}`,
101
+ "Approved",
102
+ );
103
+ expect(withdrawSlackApprovalCard).toHaveBeenCalledTimes(1);
104
+ const byId = new Map(deliveriesFor(req.id).map((d) => [d.id, d.status]));
105
+ expect(byId.get(vellum.id)).toBe("withdrawn");
106
+ expect(byId.get(slack.id)).toBe("withdrawn");
107
+ });
108
+
109
+ test("a denial renders Denied on the withdrawn cards", async () => {
110
+ const req = makeRequest();
111
+ bridgeState.seedDelivery({
112
+ requestId: req.id,
113
+ destinationChannel: "vellum",
114
+ destinationConversationId: "conv-thread",
115
+ });
116
+
117
+ await syncTerminalGuardianRequestStatus({
118
+ requestId: req.id,
119
+ status: "denied",
120
+ syncContext: "test",
121
+ });
122
+
123
+ expect(bridgeState.requests.get(req.id)?.status).toBe("denied");
124
+ expect(completeSurfaceAndNotify).toHaveBeenCalledWith(
125
+ "conv-thread",
126
+ `tool-approval-${req.id}`,
127
+ "Denied",
128
+ );
129
+ });
130
+
131
+ test("a CAS miss leaves withdrawal to the path that resolved the request", async () => {
132
+ const req = makeRequest({ status: "approved" });
133
+ bridgeState.seedDelivery({
134
+ requestId: req.id,
135
+ destinationChannel: "vellum",
136
+ destinationConversationId: "conv-thread",
137
+ });
138
+
139
+ await syncTerminalGuardianRequestStatus({
140
+ requestId: req.id,
141
+ status: "approved",
142
+ syncContext: "test",
143
+ });
144
+
145
+ expect(completeSurfaceAndNotify).not.toHaveBeenCalled();
146
+ expect(withdrawSlackApprovalCard).not.toHaveBeenCalled();
147
+ });
148
+
149
+ test("never throws when the gateway is unreachable", async () => {
150
+ const req = makeRequest();
151
+ bridgeState.state.decideError = new Error("gateway down");
152
+ await expect(
153
+ syncTerminalGuardianRequestStatus({
154
+ requestId: req.id,
155
+ status: "approved",
156
+ syncContext: "test",
157
+ }),
158
+ ).resolves.toBeUndefined();
159
+ bridgeState.state.decideError = null;
160
+ expect(completeSurfaceAndNotify).not.toHaveBeenCalled();
161
+ });
162
+ });
@@ -37,7 +37,13 @@ mock.module("../util/logger.js", () => ({
37
37
 
38
38
  // Gateway guardian-request client — in-memory map driven by tests.
39
39
  const guardianRequests = new Map<string, GuardianRequestWire>();
40
+ // Spread the actual module so transitive importers of names this
41
+ // factory does not stub keep resolving (a partial factory breaks at
42
+ // import time when the graph gains a new named import).
43
+ const actualGatewayGuardianRequests =
44
+ await import("../channels/gateway-guardian-requests.js");
40
45
  mock.module("../channels/gateway-guardian-requests.js", () => ({
46
+ ...actualGatewayGuardianRequests,
41
47
  getGuardianRequestOrNull: async (id: string) =>
42
48
  guardianRequests.get(id) ?? null,
43
49
  }));
@@ -148,6 +148,7 @@ import {
148
148
  formatModeSelectionFailure,
149
149
  parseBrowserMode,
150
150
  } from "../tools/browser/browser-execution.js";
151
+ import { CHROME_WEB_STORE_INSTALL_URL } from "../tools/browser/browser-status-constants.js";
151
152
  import type { ToolContext } from "../tools/types.js";
152
153
 
153
154
  // Restore all module mocks after this file completes so they don't
@@ -305,6 +306,7 @@ describe("formatModeSelectionFailure", () => {
305
306
  );
306
307
  expect(formatted).toContain("Remediation:");
307
308
  expect(formatted).toContain("extension is installed and enabled");
309
+ expect(formatted).toContain(CHROME_WEB_STORE_INSTALL_URL);
308
310
  });
309
311
 
310
312
  test("renders cdp-inspect transport_error with remediation", () => {
@@ -331,6 +333,29 @@ describe("formatModeSelectionFailure", () => {
331
333
  expect(formatted).toContain("Remediation:");
332
334
  expect(formatted).toContain("--remote-debugging-port");
333
335
  });
336
+
337
+ test("includes the Chrome Web Store install URL for host-bridge failures", () => {
338
+ const diagnostics: AttemptDiagnostic[] = [
339
+ {
340
+ candidateKind: "host-bridge",
341
+ inclusionReason: "auto candidate",
342
+ stage: "send",
343
+ errorCode: "transport_error",
344
+ errorMessage: "desktop client could not reach Chrome",
345
+ },
346
+ ];
347
+
348
+ const error = new CdpError(
349
+ "transport_error",
350
+ "Host bridge unreachable",
351
+ { attemptDiagnostics: diagnostics },
352
+ );
353
+
354
+ const formatted = formatModeSelectionFailure("auto", error);
355
+
356
+ expect(formatted).toContain("Remediation:");
357
+ expect(formatted).toContain(CHROME_WEB_STORE_INSTALL_URL);
358
+ });
334
359
  });
335
360
 
336
361
  describe("browser_mode wiring through tool execution", () => {
@@ -398,6 +423,7 @@ describe("browser_mode wiring through tool execution", () => {
398
423
  );
399
424
  expect(result.content).toContain("Remediation:");
400
425
  expect(result.content).toContain("extension is installed and enabled");
426
+ expect(result.content).toContain(CHROME_WEB_STORE_INSTALL_URL);
401
427
  // Factory should not have been called for CDP
402
428
  expect(cdpSendCalls).toEqual([]);
403
429
  });
@@ -585,6 +611,7 @@ describe("browser_mode wiring through tool execution", () => {
585
611
  expect(result.content).toContain("Reason: Host browser not reachable");
586
612
  expect(result.content).toContain("Remediation:");
587
613
  expect(result.content).toContain("extension is installed and enabled");
614
+ expect(result.content).toContain(CHROME_WEB_STORE_INSTALL_URL);
588
615
  });
589
616
 
590
617
  test("pinned extension with timeout transport error surfaces failover diagnostics in snapshot", async () => {
@@ -36,6 +36,8 @@ export function asConversation<TExtra extends object = object>(
36
36
  getTrustContext: () => merged.trustContext,
37
37
  getTurnOrRestingTrust: () =>
38
38
  merged.currentTurnTrustContext ?? merged.trustContext,
39
+ isStale: () => false,
40
+ hasInFlightWork: () => false,
39
41
  ...mock,
40
42
  };
41
43
  return merged as unknown as Conversation & TExtra;
@@ -19,6 +19,7 @@ import { ConversationMessageSchema } from "../api/responses/conversation-message
19
19
  import {
20
20
  addMessage,
21
21
  createConversation,
22
+ NO_RESPONSE_MESSAGE_KIND,
22
23
  SYSTEM_CARD_MESSAGE_KIND,
23
24
  } from "../persistence/conversation-crud.js";
24
25
  import { getDb } from "../persistence/db-connection.js";
@@ -39,6 +40,7 @@ interface ProjectedMessage {
39
40
  id: string;
40
41
  role: string;
41
42
  systemCard?: boolean;
43
+ noResponse?: boolean;
42
44
  }
43
45
 
44
46
  describe("handleListMessages system-card projection", () => {
@@ -74,6 +76,74 @@ describe("handleListMessages system-card projection", () => {
74
76
  expect(cardRow?.systemCard).toBe(true);
75
77
  });
76
78
 
79
+ test("projects noResponse from the deliberate-silence marker", async () => {
80
+ const conv = createConversation();
81
+ await addMessage(
82
+ conv.id,
83
+ "user",
84
+ JSON.stringify([{ type: "text", text: "thanks!" }]),
85
+ );
86
+ // Mirror the agent loop's turn-boundary stamp: the row keeps the raw
87
+ // sentinel as content and carries the marker in metadata.
88
+ const silent = await addMessage(
89
+ conv.id,
90
+ "assistant",
91
+ JSON.stringify([{ type: "text", text: "<no_response/>" }]),
92
+ { metadata: { messageKind: NO_RESPONSE_MESSAGE_KIND } },
93
+ );
94
+
95
+ const response = (await handleListMessages({
96
+ queryParams: { conversationId: conv.id },
97
+ })) as { messages: ProjectedMessage[] };
98
+
99
+ for (const message of response.messages) {
100
+ expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
101
+ }
102
+
103
+ const silentRow = response.messages.find((m) => m.id === silent.id);
104
+ expect(silentRow?.noResponse).toBe(true);
105
+ });
106
+
107
+ test("projects the reaction fact from a reaction row's envelope", async () => {
108
+ const conv = createConversation();
109
+ const row = await addMessage(
110
+ conv.id,
111
+ "assistant",
112
+ JSON.stringify([{ type: "text", text: "[reaction]" }]),
113
+ {
114
+ metadata: {
115
+ messageKind: "reaction",
116
+ providerMeta: JSON.stringify({
117
+ source: "discord",
118
+ conversationExternalId: "chan-1",
119
+ eventKind: "reaction",
120
+ reaction: {
121
+ targetMessageId: "555.1",
122
+ emoji: "🎉",
123
+ op: "added",
124
+ },
125
+ }),
126
+ },
127
+ },
128
+ );
129
+
130
+ const response = (await handleListMessages({
131
+ queryParams: { conversationId: conv.id },
132
+ })) as {
133
+ messages: Array<
134
+ ProjectedMessage & {
135
+ reaction?: { emoji: string; selfAuthored?: boolean };
136
+ }
137
+ >;
138
+ };
139
+ for (const message of response.messages) {
140
+ expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
141
+ }
142
+ const projected = response.messages.find((m) => m.id === row.id);
143
+ expect(projected?.reaction?.emoji).toBe("🎉");
144
+ expect(projected?.reaction?.selfAuthored).toBe(true);
145
+ });
146
+
77
147
  test("omits systemCard on ordinary user and assistant rows", async () => {
78
148
  const conv = createConversation();
79
149
  await addMessage(
@@ -51,6 +51,7 @@ interface ClientCatalogModel {
51
51
  longContextMode?: "native-model" | "provider-request-option" | "unsupported";
52
52
  supportsThinking?: boolean;
53
53
  adaptiveThinkingOnly?: boolean;
54
+ thinkingFloor?: "minimal" | "low";
54
55
  supportsCaching?: boolean;
55
56
  supportsVision?: boolean;
56
57
  supportsToolUse?: boolean;
@@ -198,6 +199,7 @@ describe("LLM catalog parity: daemon vs client", () => {
198
199
  expect(clientModel.adaptiveThinkingOnly).toBe(
199
200
  daemonModel.adaptiveThinkingOnly,
200
201
  );
202
+ expect(clientModel.thinkingFloor).toBe(daemonModel.thinkingFloor);
201
203
  expect(clientModel.supportsCaching).toBe(daemonModel.supportsCaching);
202
204
  expect(clientModel.supportsVision).toBe(daemonModel.supportsVision);
203
205
  expect(clientModel.supportsToolUse).toBe(daemonModel.supportsToolUse);
@@ -1118,7 +1118,7 @@ describe("code-owned default profiles — wire view and write normalization", ()
1118
1118
  });
1119
1119
  const response = (await getRoute.handler({})) as Record<string, any>;
1120
1120
  const wireBalanced = response.llm.profiles.balanced;
1121
- expect(wireBalanced.model).toBe("accounts/fireworks/models/glm-5p2");
1121
+ expect(wireBalanced.model).toBe("accounts/fireworks/models/glm-5p3-flash");
1122
1122
  expect(wireBalanced.provider).toBe("vellum");
1123
1123
  expect(wireBalanced.provider_connection).toBeUndefined();
1124
1124
  expect(wireBalanced.status).toBe("disabled");
@@ -1366,7 +1366,7 @@ describe("code-owned default profiles — echoes over stale on-disk bodies", ()
1366
1366
  const response = (await getRoute.handler({})) as Record<string, any>;
1367
1367
  // The wire view serves catalog content, not the stale body.
1368
1368
  expect(response.llm.profiles.balanced.model).toBe(
1369
- "accounts/fireworks/models/glm-5p2",
1369
+ "accounts/fireworks/models/glm-5p3-flash",
1370
1370
  );
1371
1371
  const result = await patchRoute.handler({
1372
1372
  body: { llm: { profiles: response.llm.profiles } },
@@ -13,6 +13,7 @@ import { getDb } from "../persistence/db-connection.js";
13
13
  import { initializeDb } from "../persistence/db-init.js";
14
14
  import {
15
15
  isUnsendableImageSource,
16
+ mediaSourceDescriptor,
16
17
  resolveMediaReferences,
17
18
  UNSENDABLE_IMAGE_FORMAT_NOTE,
18
19
  } from "../providers/media-resolve.js";
@@ -221,6 +222,111 @@ describe("resolveMediaReferences image validation", () => {
221
222
  });
222
223
  });
223
224
 
225
+ describe("resolveMediaReferences attachment id", () => {
226
+ beforeEach(resetTables);
227
+
228
+ test("carries a file block's attachment id across reference resolution", async () => {
229
+ // Resolving a reference rebuilds the block around freshly read bytes. The
230
+ // id is the block's only remaining link to its attachment row, so it has to
231
+ // survive the rebuild.
232
+ const conv = createConversation();
233
+ const stored = await createInlineAttachment(
234
+ conv.id,
235
+ conv.createdAt,
236
+ "notes.txt",
237
+ "text/plain",
238
+ Buffer.from("hello").toString("base64"),
239
+ );
240
+ const message = userMessage([
241
+ {
242
+ type: "file",
243
+ source: {
244
+ type: "workspace_ref",
245
+ media_type: "text/plain",
246
+ attachmentId: stored.id,
247
+ sizeBytes: stored.sizeBytes,
248
+ filename: "notes.txt",
249
+ },
250
+ _attachmentId: stored.id,
251
+ },
252
+ ]);
253
+
254
+ const [resolved] = await resolveMediaReferences([message]);
255
+
256
+ const block = resolved.content[0];
257
+ expect(block.type).toBe("file");
258
+ expect(block).toMatchObject({
259
+ source: { type: "base64" },
260
+ _attachmentId: stored.id,
261
+ });
262
+ });
263
+
264
+ test("omits the field when the source block carries no id", async () => {
265
+ const conv = createConversation();
266
+ const stored = await createInlineAttachment(
267
+ conv.id,
268
+ conv.createdAt,
269
+ "plain.txt",
270
+ "text/plain",
271
+ Buffer.from("hi").toString("base64"),
272
+ );
273
+ const message = userMessage([
274
+ {
275
+ type: "file",
276
+ source: {
277
+ type: "workspace_ref",
278
+ media_type: "text/plain",
279
+ attachmentId: stored.id,
280
+ sizeBytes: stored.sizeBytes,
281
+ filename: "plain.txt",
282
+ },
283
+ },
284
+ ]);
285
+
286
+ const [resolved] = await resolveMediaReferences([message]);
287
+
288
+ expect("_attachmentId" in resolved.content[0]).toBe(false);
289
+ });
290
+ });
291
+
292
+ describe("mediaSourceDescriptor", () => {
293
+ // The fragment every media stub embeds. Both the retry path's stubs and the
294
+ // camera-frame stub read it from here, so this pins what they all report.
295
+ test("describes a reference from its persisted size hint", () => {
296
+ expect(
297
+ mediaSourceDescriptor({
298
+ type: "workspace_ref",
299
+ media_type: "image/jpeg",
300
+ attachmentId: "att-1",
301
+ sizeBytes: 1024,
302
+ }),
303
+ ).toBe("image/jpeg, 1024 bytes");
304
+ });
305
+
306
+ test("describes an inline block from its base64 length", () => {
307
+ // 8 base64 chars decode to 6 bytes.
308
+ expect(
309
+ mediaSourceDescriptor({
310
+ type: "base64",
311
+ media_type: "image/png",
312
+ data: "AAAAAAAA",
313
+ }),
314
+ ).toBe("image/png, 6 bytes");
315
+ });
316
+
317
+ test("covers non-image media too", () => {
318
+ expect(
319
+ mediaSourceDescriptor({
320
+ type: "workspace_ref",
321
+ media_type: "application/pdf",
322
+ attachmentId: "att-2",
323
+ sizeBytes: 42,
324
+ filename: "notes.pdf",
325
+ }),
326
+ ).toBe("application/pdf, 42 bytes");
327
+ });
328
+ });
329
+
224
330
  describe("isUnsendableImageSource", () => {
225
331
  beforeEach(resetTables);
226
332