@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
@@ -59,6 +59,12 @@ export interface CatalogModel {
59
59
  * `adaptiveThinkingOnly`.
60
60
  */
61
61
  adaptiveThinkingUnsupported?: boolean;
62
+ /**
63
+ * Lowest thinking level the model accepts on the Gemini wire. `"low"`
64
+ * means `"minimal"` is not a valid request. Omit when `"minimal"` is
65
+ * accepted.
66
+ */
67
+ thinkingFloor?: "minimal" | "low";
62
68
  supportsCaching?: boolean;
63
69
  supportsVision?: boolean;
64
70
  /**
@@ -77,6 +83,16 @@ export interface CatalogModel {
77
83
  * default.
78
84
  */
79
85
  maxEffort?: "high" | "xhigh" | "max";
86
+ /**
87
+ * Wire `reasoning_effort` values this model's upstream accepts, for models
88
+ * whose accepted set is sparse rather than a contiguous range under
89
+ * `maxEffort` (e.g. GLM 5.3 accepts only low/high/max). After the
90
+ * `maxEffort` ceiling clamp, provider clients snap an unsupported value
91
+ * down to the nearest listed value ("none" is exempt: it is the explicit
92
+ * opt-out and keeps its own rejection handling). Daemon-only: not
93
+ * projected into the client catalog (see scripts/sync-llm-catalog.ts).
94
+ */
95
+ supportedEfforts?: readonly ("low" | "medium" | "high" | "xhigh" | "max")[];
80
96
  /**
81
97
  * Daemon-only: when true, the direct-OpenAI Responses transport sends
82
98
  * explicit prompt-cache breakpoints for this model (GPT-5.6+ semantics:
@@ -597,6 +613,22 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
597
613
  linkLabel: "Open Google AI Studio",
598
614
  },
599
615
  models: [
616
+ {
617
+ id: "gemini-3.7-flash",
618
+ displayName: "Gemini 3.7 Flash",
619
+ contextWindowTokens: 1048576,
620
+ maxOutputTokens: 65536,
621
+ supportsThinking: true,
622
+ thinkingFloor: "low",
623
+ supportsCaching: true,
624
+ supportsVision: true,
625
+ supportsToolUse: true,
626
+ pricing: {
627
+ inputPer1mTokens: 1.5,
628
+ outputPer1mTokens: 7.5,
629
+ cacheReadPer1mTokens: 0.15,
630
+ },
631
+ },
600
632
  {
601
633
  id: "gemini-3.6-flash",
602
634
  displayName: "Gemini 3.6 Flash",
@@ -649,6 +681,7 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
649
681
  maxOutputTokens: 65536,
650
682
  longContextPricingThresholdTokens: 200000,
651
683
  supportsThinking: true,
684
+ thinkingFloor: "low",
652
685
  supportsCaching: true,
653
686
  supportsVision: true,
654
687
  supportsToolUse: true,
@@ -673,6 +706,7 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
673
706
  maxOutputTokens: 65536,
674
707
  longContextPricingThresholdTokens: 200000,
675
708
  supportsThinking: true,
709
+ thinkingFloor: "low",
676
710
  supportsCaching: true,
677
711
  supportsVision: true,
678
712
  supportsToolUse: true,
@@ -893,6 +927,45 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
893
927
  cacheReadPer1mTokens: 0.26,
894
928
  },
895
929
  },
930
+ {
931
+ id: "accounts/fireworks/models/glm-5p3",
932
+ displayName: "GLM 5.3",
933
+ contextWindowTokens: 1040000,
934
+ maxOutputTokens: 131072,
935
+ supportsThinking: true,
936
+ // GLM 5.3 reasoning is always on (reasoning_effort low/high/max);
937
+ // it cannot be disabled upstream.
938
+ adaptiveThinkingOnly: true,
939
+ supportsCaching: true,
940
+ supportsVision: false,
941
+ supportsToolUse: true,
942
+ maxEffort: "max",
943
+ supportedEfforts: ["low", "high", "max"],
944
+ pricing: {
945
+ inputPer1mTokens: 1.4,
946
+ outputPer1mTokens: 4.4,
947
+ cacheReadPer1mTokens: 0.26,
948
+ },
949
+ },
950
+ {
951
+ id: "accounts/fireworks/models/glm-5p3-flash",
952
+ displayName: "GLM 5.3 Flash",
953
+ contextWindowTokens: 1040000,
954
+ maxOutputTokens: 131072,
955
+ supportsThinking: true,
956
+ // Same always-on reasoning as GLM 5.3.
957
+ adaptiveThinkingOnly: true,
958
+ supportsCaching: true,
959
+ supportsVision: true,
960
+ supportsToolUse: true,
961
+ maxEffort: "max",
962
+ supportedEfforts: ["low", "high", "max"],
963
+ pricing: {
964
+ inputPer1mTokens: 0.15,
965
+ outputPer1mTokens: 0.5,
966
+ cacheReadPer1mTokens: 0.029,
967
+ },
968
+ },
896
969
  // Kimi K2.5 (accounts/fireworks/models/kimi-k2p5) is intentionally
897
970
  // absent: Fireworks serves it on-demand/dedicated only, so serverless
898
971
  // chat/completions calls 404 ("not found, inaccessible, and/or not
@@ -2366,6 +2439,24 @@ export function modelEffortCeilings(
2366
2439
  );
2367
2440
  }
2368
2441
 
2442
+ /**
2443
+ * Per-model sparse `reasoning_effort` support for a provider, keyed by model
2444
+ * ID (same derivation pattern as {@link modelEffortCeilings}). Models without
2445
+ * `supportedEfforts` are absent and accept any value under their ceiling.
2446
+ */
2447
+ export function modelSupportedEfforts(
2448
+ providerId: string,
2449
+ ): ReadonlyMap<
2450
+ string,
2451
+ readonly ("low" | "medium" | "high" | "xhigh" | "max")[]
2452
+ > {
2453
+ return new Map(
2454
+ PROVIDER_CATALOG.find((p) => p.id === providerId)?.models.flatMap((m) =>
2455
+ m.supportedEfforts ? ([[m.id, m.supportedEfforts]] as const) : [],
2456
+ ) ?? [],
2457
+ );
2458
+ }
2459
+
2369
2460
  /**
2370
2461
  * Return the catalog provider that owns a model ID, if known. When multiple
2371
2462
  * providers list the same ID (e.g. OpenRouter and the Vercel AI Gateway share
@@ -225,6 +225,28 @@ export function clampReasoningEffort(
225
225
  : value;
226
226
  }
227
227
 
228
+ /** Snap a wire effort onto a model's sparse accepted set: the nearest
229
+ * supported value at or below it, or the smallest supported value when the
230
+ * request sits below all of them. Models like GLM 5.3 accept only
231
+ * `low|high|max` and 4xx on the in-between tiers instead of rounding, so the
232
+ * client rounds for them. `"none"` is never snapped: it is the explicit
233
+ * opt-out and has its own rejection retry. */
234
+ export function snapReasoningEffortToSupported(
235
+ value: ReasoningEffortWire,
236
+ supported: readonly ReasoningEffortWire[],
237
+ ): ReasoningEffortWire {
238
+ if (value === "none" || supported.includes(value)) {
239
+ return value;
240
+ }
241
+ const ranked = [...supported].sort(
242
+ (a, b) => REASONING_EFFORT_RANK[a] - REASONING_EFFORT_RANK[b],
243
+ );
244
+ const atOrBelow = ranked.filter(
245
+ (s) => REASONING_EFFORT_RANK[s] < REASONING_EFFORT_RANK[value],
246
+ );
247
+ return atOrBelow[atOrBelow.length - 1] ?? ranked[0] ?? value;
248
+ }
249
+
228
250
  /** Human-readable text from an OpenAI-compatible error, including wrapped
229
251
  * upstream detail (OpenRouter `metadata.raw`). Used by the one-shot
230
252
  * compatibility retries so a generic SDK wrapper message cannot hide the
@@ -781,12 +803,16 @@ export class OpenAIChatCompletionsProvider implements Provider {
781
803
  ? EFFORT_TO_REASONING_EFFORT[effort]
782
804
  : undefined;
783
805
  if (reasoningEffort && typeof nestedReasoningEffort !== "string") {
784
- const ceiling = this.resolveMaxReasoningEffort(
785
- modelOverride ?? this.model,
786
- );
787
- params.reasoning_effort = clampReasoningEffort(
806
+ const effortModel = modelOverride ?? this.model;
807
+ const clamped = clampReasoningEffort(
788
808
  reasoningEffort,
789
- ceiling,
809
+ this.resolveMaxReasoningEffort(effortModel),
810
+ );
811
+ const supported = this.resolveSupportedReasoningEfforts(effortModel);
812
+ params.reasoning_effort = (
813
+ supported
814
+ ? snapReasoningEffortToSupported(clamped, supported)
815
+ : clamped
790
816
  ) as OpenAI.Chat.Completions.ChatCompletionCreateParams["reasoning_effort"];
791
817
  }
792
818
 
@@ -1352,6 +1378,19 @@ export class OpenAIChatCompletionsProvider implements Provider {
1352
1378
  return this.maxReasoningEffort;
1353
1379
  }
1354
1380
 
1381
+ /**
1382
+ * Per-model sparse `reasoning_effort` support. Subclasses return the
1383
+ * accepted values for models that 4xx on in-between tiers (e.g. GLM 5.3's
1384
+ * `low|high|max`); the outbound value is snapped onto that set after the
1385
+ * ceiling clamp (see {@link snapReasoningEffortToSupported}). Undefined
1386
+ * means any value under the ceiling is accepted.
1387
+ */
1388
+ protected resolveSupportedReasoningEfforts(
1389
+ _model: string,
1390
+ ): readonly ReasoningEffortWire[] | undefined {
1391
+ return undefined;
1392
+ }
1393
+
1355
1394
  /** Convert neutral messages + system prompt to OpenAI message format. */
1356
1395
  private async toOpenAIMessages(
1357
1396
  messages: Message[],
@@ -813,17 +813,15 @@ function normalizeSendMessageOptions(
813
813
  }
814
814
  }
815
815
 
816
- if (
817
- isThinkingConfigDisabled(nextConfig.thinking) &&
818
- disabledThinkingForcesEffortNone(providerName, nextConfig.model)
819
- ) {
820
- nextConfig.effort = "none";
821
- }
822
-
823
- // Claude Fable always reasons with adaptive thinking and rejects an explicit
824
- // `thinking: { type: "disabled" }` (Anthropic 400s the request). Drop a
825
- // disabled thinking config for these models so they fall back to their
826
- // always-on adaptive thinking; effort and other params are unaffected.
816
+ // Adaptive-thinking-only models (Claude Fable, Fireworks Kimi K3 / GLM 5.3)
817
+ // always reason and reject an explicit opt-out: Anthropic 400s
818
+ // `thinking: { type: "disabled" }`, and effort-encoding providers 4xx a
819
+ // generated `reasoning_effort: "none"`. Drop a disabled thinking config for
820
+ // these models BEFORE the opt-out is encoded as `effort: "none"` below, so
821
+ // they fall back to their always-on thinking with the user's chosen effort
822
+ // and other params unaffected. Profiles can retain a stale
823
+ // `thinking: { enabled: false }` after switching to such a model, so this
824
+ // state is reachable from the profile editor.
827
825
  if (
828
826
  typeof nextConfig.model === "string" &&
829
827
  isAdaptiveThinkingOnlyModel(nextConfig.model) &&
@@ -832,6 +830,13 @@ function normalizeSendMessageOptions(
832
830
  delete nextConfig.thinking;
833
831
  }
834
832
 
833
+ if (
834
+ isThinkingConfigDisabled(nextConfig.thinking) &&
835
+ disabledThinkingForcesEffortNone(providerName, nextConfig.model)
836
+ ) {
837
+ nextConfig.effort = "none";
838
+ }
839
+
835
840
  // Pre-adaptive Claude models (Haiku 4.5, Opus 4.5, Sonnet 4.5) reject
836
841
  // `thinking: { type: "adaptive" }` (Anthropic 400s the request), and Vellum
837
842
  // never sends the legacy budget_tokens form. Drop an adaptive thinking
@@ -80,6 +80,19 @@ export type MediaSource = Base64MediaSource | WorkspaceRefMediaSource;
80
80
  export interface ImageContent {
81
81
  type: "image";
82
82
  source: MediaSource;
83
+ /**
84
+ * Internal id linking a base64 image block to a row in the attachments table,
85
+ * the same correlation {@link FileContent._attachmentId} carries. A live turn
86
+ * sends its uploads inline while persisting them as references, so this is
87
+ * the only handle on the in-memory copy of an image whose stored form is a
88
+ * `workspace_ref`; camera-frame retention matches on it. Redundant once the
89
+ * block is a reference (use `source.attachmentId`).
90
+ *
91
+ * Never reaches a provider: every client builds its image payload out of
92
+ * `source` alone (see the `case "image"` arms of the Anthropic, Gemini, and
93
+ * both OpenAI clients), so top-level fields are dropped by construction.
94
+ */
95
+ _attachmentId?: string;
83
96
  }
84
97
 
85
98
  export interface FileContent {
@@ -97,6 +110,49 @@ export interface FileContent {
97
110
  _attachmentId?: string;
98
111
  }
99
112
 
113
+ /**
114
+ * The `_attachmentId` property as a spreadable fragment, or nothing when there
115
+ * is no usable id.
116
+ *
117
+ * Every producer of a media block faces the same choice: it either knows the
118
+ * attachment row the block came from, or it does not, and the field has to be
119
+ * absent rather than present-and-empty in the second case. This helper is the
120
+ * one place that decision lives, so every producer answers it the same way.
121
+ * Callers pass whatever id they hold: the source block's own
122
+ * ({@link ImageContent._attachmentId}) when carrying it across a rebuild, or an
123
+ * upload's row id when stamping a fresh block.
124
+ *
125
+ * Empty counts as absent, matching the read side, which requires a non-empty
126
+ * string before it will treat the field as an id (see
127
+ * `daemon/handlers/shared.ts`).
128
+ */
129
+ export function attachmentIdFragment(attachmentId: string | undefined): {
130
+ _attachmentId?: string;
131
+ } {
132
+ return attachmentId ? { _attachmentId: attachmentId } : {};
133
+ }
134
+
135
+ /**
136
+ * The attachment row a media block came from, whichever of the two shapes it
137
+ * is in, or undefined for a block that came from none (tool-generated and
138
+ * assistant-authored media).
139
+ *
140
+ * A reference names its row on `source.attachmentId`; an inline block has
141
+ * nowhere to put it but the top-level `_attachmentId`. Both are the same fact,
142
+ * so anything asking "which attachment is this?" has to read both or it goes
143
+ * blind on half the histories. Reading only `_attachmentId` in particular is
144
+ * the sharp edge: it is absent on every reference block, so a rebuild that
145
+ * flattens a reference to inline bytes drops the link unless it derives the id
146
+ * through here first.
147
+ */
148
+ export function mediaBlockAttachmentId(
149
+ block: ImageContent | FileContent,
150
+ ): string | undefined {
151
+ return block.source.type === "workspace_ref"
152
+ ? block.source.attachmentId
153
+ : block._attachmentId;
154
+ }
155
+
100
156
  export interface ToolUseContent {
101
157
  type: "tool_use";
102
158
  id: string;
@@ -14,7 +14,7 @@ import * as realLogger from "../../util/logger.js";
14
14
  import * as realWorkerProcess from "../../util/worker-process.js";
15
15
 
16
16
  let enabled = false;
17
- let spawnArgs: Array<{ options?: { detached?: boolean } }> = [];
17
+ let spawnArgs: Array<{ options?: { terminateOnTimeout?: boolean } }> = [];
18
18
  let stopStatus: { status: "running" | "not_running"; pid?: number } = {
19
19
  status: "not_running",
20
20
  };
@@ -39,7 +39,9 @@ mock.module("../../util/logger.js", () => ({
39
39
 
40
40
  mock.module("../../util/worker-process.js", () => ({
41
41
  ...realWorkerProcess,
42
- spawnWorkerProcess: async (args: { options?: { detached?: boolean } }) => {
42
+ spawnWorkerProcess: async (args: {
43
+ options?: { terminateOnTimeout?: boolean };
44
+ }) => {
43
45
  spawnArgs.push(args);
44
46
  return { pid: 4242, alreadyRunning: false };
45
47
  },
@@ -76,7 +78,6 @@ describe("startRouteHost", () => {
76
78
  startRouteHost();
77
79
  await Promise.resolve();
78
80
  expect(spawnArgs).toHaveLength(1);
79
- expect(spawnArgs[0].options?.detached).toBe(false);
80
81
  });
81
82
  });
82
83
 
@@ -101,7 +101,7 @@ export function startRouteHost(): void {
101
101
  if (!isRouteHostEnabled()) {
102
102
  return;
103
103
  }
104
- void spawnRouteHostWorkerProcess({ detached: false })
104
+ void spawnRouteHostWorkerProcess()
105
105
  .then((r) =>
106
106
  log.info(
107
107
  { pid: r.pid, alreadyRunning: r.alreadyRunning },
@@ -153,7 +153,7 @@ export class RouteHostClient {
153
153
  workerLabel: "Route host",
154
154
  // Owned by the daemon (appears in its process tree, torn down with it);
155
155
  // kill it if it hangs during startup so a failed spawn leaves nothing.
156
- options: { detached: false, terminateOnTimeout: true },
156
+ options: { terminateOnTimeout: true },
157
157
  }));
158
158
  } catch (err) {
159
159
  // A failed bring-up (crash-on-startup, PID-file timeout) is a retryable
@@ -156,7 +156,7 @@ All CDP-backed browser tools (`browser_navigate`, `browser_snapshot`, `browser_s
156
156
 
157
157
  **Auto-mode fallback logging**: In auto mode, fallback transitions are logged at `warn` level with structured metadata including the full candidate sequence and per-candidate failure reasons. This ensures fallback events are always observable in production logs.
158
158
 
159
- **Test coverage:** Regression tests for `browser_mode` wiring live in `__tests__/headless-browser-mode.test.ts`. E2E regression tests for backend precedence live in `__tests__/host-browser-e2e-cloud.test.ts` (extension path and macOS SSE bridge path) and `__tests__/conversation-routes-disk-view.test.ts` (macOS fallback path). Unit tests for pinned candidate construction and failover live in `tools/browser/cdp-client/__tests__/factory.test.ts`. Browser status tests covering macOS host-browser diagnostics live in `tools/browser/__tests__/browser-status.test.ts`.
159
+ **Test coverage:** Regression tests for `browser_mode` wiring live in `__tests__/headless-browser-mode.test.ts`. Route-wiring coverage for the macOS fallback path lives in `__tests__/conversation-routes-disk-view.test.ts`, which asserts a macOS turn carries its interface context rather than exercising the backends themselves. Unit tests for pinned candidate construction and failover live in `tools/browser/cdp-client/__tests__/factory.test.ts`. Browser status tests covering macOS host-browser diagnostics live in `tools/browser/__tests__/browser-status.test.ts`.
160
160
 
161
161
  ### Interactive requests on channels (approvals, questions)
162
162
 
@@ -190,18 +190,36 @@ the gateway's Channel Identity Vocabulary, which covers the wire side.
190
190
  - **`slackMeta`** is the per-row key describing what a row is in its Slack
191
191
  conversation: `channelTs` for the row's own id, `threadTs` for its thread,
192
192
  `reaction.targetChannelTs` for the message a reaction was attached to, plus
193
- Slack's own file markers and timezone labels. Every channel-path reader goes
194
- through it today.
193
+ Slack's own file markers and timezone labels. Most channel-path readers
194
+ still go through it directly; the lookups in `persistence/delivery-crud.ts`
195
+ read through `readProviderMetadata` instead, which serves this envelope and
196
+ the neutral shape below from one call.
195
197
  - **`providerMeta`** is the channel-neutral counterpart of that key
196
- (`messaging/provider-message-metadata.ts`): `conversationExternalId`,
197
- `messageId`, `threadId`, `actorExternalId`, `eventKind` and
198
- `reaction.targetMessageId`, deliberately the same vocabulary the inbound
199
- wire uses in `InboundEventBase`, so a channel that can describe itself at
200
- ingress can describe its stored rows without a translation of its own.
201
- Writable by any channel, including one this repo has no code for. `readProviderMetadata` reads it and maps `slackMeta` onto it.
202
- **Nothing writes or reads it yet**; it is the home a new channel should use
203
- rather than inventing a sixth key, and existing readers move onto it as
204
- channels adopt it.
198
+ (`messaging/provider-message-metadata.ts`): `source`,
199
+ `conversationExternalId`, `messageId`, `threadId`, `actorExternalId`,
200
+ `eventKind`, the `reaction` sub-key, and the `editedAt` / `deletedAt`
201
+ marks. Deliberately the same vocabulary the inbound wire uses in
202
+ `InboundEventBase`, so a channel that can describe itself at ingress can
203
+ describe its stored rows without a translation of its own. Writable by any
204
+ channel, including one this repo has no code for, which is why a new
205
+ channel belongs here rather than in a sixth key of its own.
206
+
207
+ Every channel except Slack writes it: a reaction row carries the whole
208
+ shape (`inbound-stages/reaction-intercept.ts`), an edit or a delete
209
+ stamps `editedAt` / `deletedAt` onto whatever the row already said about
210
+ itself through `mergeProviderMessageMetadata`
211
+ (`inbound-stages/edit-intercept.ts`, `inbound-message-handler.ts`), and an
212
+ outbound assistant reply is stamped with a partial envelope at reserve time
213
+ (`buildAssistantChannelMetadata`) whose `messageId` (and, for a reply
214
+ split into several posts, `additionalMessageIds`) the post-send
215
+ reconciliation in `channel-reply-delivery.ts` back-fills from the
216
+ transport's delivery results, which is what lets a later reaction on the
217
+ assistant's own post resolve back to its row. Slack
218
+ keeps writing `slackMeta`, and `readProviderMetadata` maps that envelope
219
+ onto this shape on read, so the channel-agnostic readers in
220
+ `persistence/delivery-crud.ts` (thread evidence, and finding the
221
+ conversation that holds a given provider message id) serve both without a
222
+ per-channel branch.
205
223
 
206
224
  ### Channel verification: gateway-owned
207
225
 
@@ -347,6 +347,7 @@ import {
347
347
  deleteConversation,
348
348
  setConversation,
349
349
  } from "../../daemon/conversation-registry.js";
350
+ import { stripAgedSightFrames } from "../../daemon/conversation-sight-frames.js";
350
351
  import { ContextOverflowError, type Message } from "../../providers/types.js";
351
352
  import {
352
353
  __resetWakeChainForTests,
@@ -521,6 +522,11 @@ function makeWakeConversation(options: {
521
522
  return runBody(input, onEvent, options);
522
523
  },
523
524
  },
525
+ // The wake trims its own run input, the way the orchestrator's pre-run
526
+ // pass trims a normal turn's. Empty capture times leave the array
527
+ // untouched, which is what every case but the camera one wants.
528
+ trimAgedSightFrames: (msgs: Message[]) =>
529
+ stripAgedSightFrames(msgs, wakeSightFrameCaptureTimes).messages,
524
530
  messages,
525
531
  getMessages: () => messages,
526
532
  isProcessing: () => processing,
@@ -605,8 +611,12 @@ function makeWakeConversation(options: {
605
611
  return conversation as unknown as WakeConversation;
606
612
  }
607
613
 
614
+ /** Tagged camera frames the fake conversation's trim resolves against. */
615
+ let wakeSightFrameCaptureTimes = new Map<string, number>();
616
+
608
617
  beforeEach(() => {
609
618
  __resetWakeChainForTests();
619
+ wakeSightFrameCaptureTimes = new Map();
610
620
  wakeConvRegistry.clear();
611
621
  recordRequestLogCalls.length = 0;
612
622
  recordUsageCalls.length = 0;
@@ -934,6 +944,62 @@ describe("wakeAgentForOpportunity", () => {
934
944
  expect(conversation.personaOverrideSets).toEqual([]);
935
945
  });
936
946
 
947
+ test("bounds camera frames in the run input it sends", async () => {
948
+ // The wake sends its run input itself and keeps the in-loop budget gate
949
+ // disabled, so neither the orchestrator's pre-run pass nor the loop's
950
+ // post-compaction transform ever sees this array. Without its own trim a
951
+ // long camera session would send every frame it captured.
952
+ wakeSightFrameCaptureTimes = new Map([
953
+ ["f1", 1000],
954
+ ["f2", 2000],
955
+ ["f3", 3000],
956
+ ["f4", 4000],
957
+ ]);
958
+ const frame = (attachmentId: string): Message => ({
959
+ role: "user",
960
+ content: [
961
+ {
962
+ type: "image",
963
+ source: { type: "base64", media_type: "image/png", data: "AAAAAAAA" },
964
+ _attachmentId: attachmentId,
965
+ },
966
+ ],
967
+ });
968
+ const conversation = makeWakeConversation({
969
+ baseline: [frame("f1"), frame("f2"), frame("f3"), frame("f4")],
970
+ scriptedAssistant: {
971
+ role: "assistant",
972
+ content: [{ type: "text", text: "" }],
973
+ },
974
+ });
975
+
976
+ await wakeAgentForOpportunity(
977
+ {
978
+ conversationId: conversation.conversationId,
979
+ hint: "look around",
980
+ source: "unit-test",
981
+ },
982
+ { resolveTarget: async () => conversation },
983
+ );
984
+
985
+ const input = conversation.runCalls[0]!.input;
986
+ const images = input.flatMap((message) =>
987
+ message.content.filter((block) => block.type === "image"),
988
+ );
989
+ const stubs = input.flatMap((message) =>
990
+ message.content.filter(
991
+ (block) =>
992
+ block.type === "text" &&
993
+ block.text.startsWith("[Camera frame omitted from context:"),
994
+ ),
995
+ );
996
+ expect(images).toHaveLength(2);
997
+ expect(stubs).toHaveLength(2);
998
+ // The trim replaces blocks in place, so the wake's tail accounting still
999
+ // indexes the same positions.
1000
+ expect(input.filter((m) => m.role === "user").length).toBeGreaterThan(0);
1001
+ });
1002
+
937
1003
  test("silent no-op when agent produces no tool calls and no text", async () => {
938
1004
  const conversation = makeWakeConversation({
939
1005
  baseline: [
@@ -1092,10 +1158,10 @@ describe("wakeAgentForOpportunity", () => {
1092
1158
  },
1093
1159
  });
1094
1160
 
1095
- // Mirror formatShellOutput on large output: ~20KB body + a recovery marker
1096
- // pointing at the full-output temp file. The default tool_result budget
1097
- // would re-truncate the marker off; the caller passes a larger maxChars.
1098
- const marker = '<output_truncated limit="20K" file="/tmp/bg-xyz.txt" />';
1161
+ // Mirror formatShellOutput on large output: ~20KB body + a truncation
1162
+ // marker. The default tool_result budget would slice the marker off; the
1163
+ // caller passes a larger maxChars.
1164
+ const marker = '<output_truncated limit="20K" />';
1099
1165
  const big = `${"x".repeat(20_000)}\n${marker}`;
1100
1166
 
1101
1167
  await wakeAgentForOpportunity(
@@ -1116,8 +1182,7 @@ describe("wakeAgentForOpportunity", () => {
1116
1182
  const text = (
1117
1183
  conversation.persistedTailCalls[0]!.content as Array<{ text: string }>
1118
1184
  )[0]!.text;
1119
- // The trailing recovery marker survives (not re-truncated off) and the
1120
- // fence is still well-formed.
1185
+ // The trailing truncation marker survives and the fence is well-formed.
1121
1186
  expect(text).toContain(marker);
1122
1187
  expect(text).toContain('<external_content source="tool_result">');
1123
1188
  expect(text.endsWith("</background_event>")).toBe(true);