@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
@@ -1,26 +1,8 @@
1
- import { randomUUID } from "node:crypto";
2
- import { unlinkSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
-
6
1
  import { safeStringSlice } from "../../util/unicode.js";
7
2
 
8
3
  export const MAX_OUTPUT_LENGTH = 20_000;
9
4
 
10
- /** Tracks temp files created for truncated shell output so they can be cleaned up on shutdown. */
11
- const trackedTempFiles = new Set<string>();
12
-
13
- /** Remove all tracked truncated-output temp files. Safe to call multiple times. */
14
- export function cleanupShellOutputTempFiles(): void {
15
- for (const filePath of trackedTempFiles) {
16
- try {
17
- unlinkSync(filePath);
18
- } catch {
19
- // File may already be gone — ignore.
20
- }
21
- }
22
- trackedTempFiles.clear();
23
- }
5
+ export const OUTPUT_TRUNCATED_TAG = `<output_truncated limit="20K" />`;
24
6
 
25
7
  export interface ShellOutputResult {
26
8
  content: string;
@@ -28,11 +10,20 @@ export interface ShellOutputResult {
28
10
  isError: boolean;
29
11
  }
30
12
 
13
+ type StdioStream = "stdout" | "stderr";
14
+
15
+ type DataListener = (data: Buffer | string) => void;
16
+
17
+ type ReadableLike = {
18
+ on(event: "data", listener: DataListener): unknown;
19
+ };
20
+
31
21
  /**
32
- * Format the raw stdout/stderr/exit-code from a spawned shell command into the
33
- * final tool result. Both `shell.ts` (sandbox bash) and `host-shell.ts`
34
- * (host_bash) must produce identical output formatting - this shared function
35
- * is the single source of truth for that logic.
22
+ * Format already-materialized stdout/stderr into the tool result. Spawn sites
23
+ * that read pipes should collect through {@link BoundedStdioCollector} so the
24
+ * oversized tail never lands in memory. This helper still truncates a string
25
+ * that is already over the cap (desktop host-bash payloads) without writing
26
+ * the discarded tail to disk.
36
27
  */
37
28
  export function formatShellOutput(
38
29
  stdout: string,
@@ -40,6 +31,7 @@ export function formatShellOutput(
40
31
  code: number | null,
41
32
  timedOut: boolean,
42
33
  timeoutSec: number,
34
+ options?: { truncated?: boolean },
43
35
  ): ShellOutputResult {
44
36
  let output = stdout;
45
37
  if (stderr) {
@@ -54,22 +46,13 @@ export function formatShellOutput(
54
46
  statusParts.push(msg);
55
47
  }
56
48
 
57
- if (output.length > MAX_OUTPUT_LENGTH) {
58
- let fullOutputPath: string | undefined;
59
- try {
60
- fullOutputPath = join(
61
- tmpdir(),
62
- `vellum-shell-output-${randomUUID()}.txt`,
63
- );
64
- writeFileSync(fullOutputPath, output, { encoding: "utf-8", mode: 0o600 });
65
- trackedTempFiles.add(fullOutputPath);
66
- } catch {
67
- fullOutputPath = undefined;
68
- }
69
- const fileAttr = fullOutputPath ? ` file="${fullOutputPath}"` : "";
70
- const msg = `<output_truncated limit="20K"${fileAttr} />`;
71
- output = safeStringSlice(output, 0, MAX_OUTPUT_LENGTH) + `\n${msg}`;
72
- statusParts.push(msg);
49
+ const truncated =
50
+ options?.truncated === true || output.length > MAX_OUTPUT_LENGTH;
51
+ if (truncated) {
52
+ output =
53
+ safeStringSlice(output, 0, MAX_OUTPUT_LENGTH) +
54
+ `\n${OUTPUT_TRUNCATED_TAG}`;
55
+ statusParts.push(OUTPUT_TRUNCATED_TAG);
73
56
  }
74
57
 
75
58
  if (!output.trim()) {
@@ -90,3 +73,93 @@ export function formatShellOutput(
90
73
  isError: code !== 0 || timedOut,
91
74
  };
92
75
  }
76
+
77
+ function toBuffer(data: Buffer | string): Buffer {
78
+ if (Buffer.isBuffer(data)) {
79
+ return data;
80
+ }
81
+ return Buffer.from(data);
82
+ }
83
+
84
+ /**
85
+ * Caps bash stdout/stderr while they stream. Bytes past {@link MAX_OUTPUT_LENGTH}
86
+ * are dropped (the process is left running so the pipe can drain). stdout and
87
+ * stderr share one budget, matching the concatenated tool-result cap.
88
+ */
89
+ export class BoundedStdioCollector {
90
+ private readonly stdoutParts: Buffer[] = [];
91
+ private readonly stderrParts: Buffer[] = [];
92
+ private keptBytes = 0;
93
+ private truncated = false;
94
+
95
+ consume(
96
+ stream: StdioStream,
97
+ chunk: Buffer,
98
+ onOutput?: (text: string) => void,
99
+ ): void {
100
+ if (this.truncated || chunk.length === 0) {
101
+ return;
102
+ }
103
+
104
+ const remaining = MAX_OUTPUT_LENGTH - this.keptBytes;
105
+ if (remaining <= 0) {
106
+ this.truncated = true;
107
+ return;
108
+ }
109
+
110
+ // Copy a prefix so a single oversized chunk can be GC'd. subarray()
111
+ // would keep the whole allocation alive through the kept view.
112
+ const kept =
113
+ chunk.length <= remaining
114
+ ? chunk
115
+ : Buffer.from(chunk.subarray(0, remaining));
116
+ if (stream === "stdout") {
117
+ this.stdoutParts.push(kept);
118
+ } else {
119
+ this.stderrParts.push(kept);
120
+ }
121
+ this.keptBytes += kept.length;
122
+ if (kept.length < chunk.length) {
123
+ this.truncated = true;
124
+ }
125
+ onOutput?.(kept.toString());
126
+ }
127
+
128
+ get keptByteLength(): number {
129
+ return this.keptBytes;
130
+ }
131
+
132
+ get didTruncate(): boolean {
133
+ return this.truncated;
134
+ }
135
+
136
+ format(
137
+ code: number | null,
138
+ timedOut: boolean,
139
+ timeoutSec: number,
140
+ ): ShellOutputResult {
141
+ return formatShellOutput(
142
+ Buffer.concat(this.stdoutParts).toString(),
143
+ Buffer.concat(this.stderrParts).toString(),
144
+ code,
145
+ timedOut,
146
+ timeoutSec,
147
+ { truncated: this.truncated },
148
+ );
149
+ }
150
+ }
151
+
152
+ /** Attach a shared-budget collector to a child's stdout and stderr pipes. */
153
+ export function attachBoundedStdio(
154
+ child: { stdout?: ReadableLike | null; stderr?: ReadableLike | null },
155
+ options?: { onOutput?: (text: string) => void },
156
+ ): BoundedStdioCollector {
157
+ const collector = new BoundedStdioCollector();
158
+ child.stdout?.on("data", (data) => {
159
+ collector.consume("stdout", toBuffer(data), options?.onOutput);
160
+ });
161
+ child.stderr?.on("data", (data) => {
162
+ collector.consume("stderr", toBuffer(data), options?.onOutput);
163
+ });
164
+ return collector;
165
+ }
@@ -161,6 +161,7 @@ function spawnRunner(
161
161
  env,
162
162
  stdio: ["ignore", "pipe", "pipe"],
163
163
  detached: true,
164
+ windowsHide: true,
164
165
  });
165
166
 
166
167
  const timer = setTimeout(() => {
@@ -0,0 +1,34 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import type { ToolContext } from "../types.js";
4
+ import { requestSystemPermissionTool } from "./request-permission.js";
5
+
6
+ const ctx = (clientOs?: string): ToolContext =>
7
+ ({ conversationId: "c", workingDir: "/", clientOs }) as ToolContext;
8
+
9
+ describe("request_system_permission", () => {
10
+ test("uses Windows Settings URIs for Windows clients", async () => {
11
+ const result = await requestSystemPermissionTool.execute(
12
+ { permission_type: "microphone" },
13
+ ctx("windows"),
14
+ );
15
+ expect(result.isError).toBe(false);
16
+ expect(result.content).toContain("ms-settings:privacy-microphone");
17
+ });
18
+
19
+ test("errors for kinds the Windows client cannot route", async () => {
20
+ const result = await requestSystemPermissionTool.execute(
21
+ { permission_type: "camera" },
22
+ ctx("windows"),
23
+ );
24
+ expect(result.isError).toBe(true);
25
+ });
26
+
27
+ test("defaults to macOS System Settings", async () => {
28
+ const result = await requestSystemPermissionTool.execute(
29
+ { permission_type: "camera" },
30
+ ctx(),
31
+ );
32
+ expect(result.content).toContain("x-apple.systempreferences");
33
+ });
34
+ });
@@ -25,7 +25,7 @@ const PERMISSION_TYPES = [
25
25
 
26
26
  type PermissionType = (typeof PERMISSION_TYPES)[number];
27
27
 
28
- const SETTINGS_URLS: Record<PermissionType, string> = {
28
+ const MACOS_SETTINGS_URLS: Record<PermissionType, string> = {
29
29
  full_disk_access:
30
30
  "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles",
31
31
  accessibility:
@@ -46,6 +46,21 @@ const SETTINGS_URLS: Record<PermissionType, string> = {
46
46
  "x-apple.systempreferences:com.apple.preference.security?Privacy_Camera",
47
47
  };
48
48
 
49
+ // Only the pages the Windows desktop client can route through its
50
+ // permissions bridge; other kinds have no in-app remediation there yet.
51
+ const WINDOWS_SETTINGS_URLS: Partial<Record<PermissionType, string>> = {
52
+ screen_recording: "ms-settings:privacy-graphicscaptureprogrammatic",
53
+ microphone: "ms-settings:privacy-microphone",
54
+ };
55
+
56
+ export const resolveSystemPermissionSettingsUrl = (
57
+ permType: PermissionType,
58
+ clientOs: string | undefined,
59
+ ): string | undefined =>
60
+ clientOs === "windows"
61
+ ? WINDOWS_SETTINGS_URLS[permType]
62
+ : MACOS_SETTINGS_URLS[permType];
63
+
49
64
  const FRIENDLY_NAMES: Record<PermissionType, string> = {
50
65
  full_disk_access: "Full Disk Access",
51
66
  accessibility: "Accessibility",
@@ -68,7 +83,7 @@ const FRIENDLY_NAMES: Record<PermissionType, string> = {
68
83
  export const requestSystemPermissionInputSchema = z.looseObject({
69
84
  permission_type: z
70
85
  .enum(PERMISSION_TYPES)
71
- .describe("The macOS system permission to request"),
86
+ .describe("The system permission (macOS or Windows) to request"),
72
87
  activity: z
73
88
  .string()
74
89
  .describe(
@@ -81,7 +96,7 @@ export const requestSystemPermissionInputSchema = z.looseObject({
81
96
  export const requestSystemPermissionTool = {
82
97
  name: "request_system_permission",
83
98
  description:
84
- "Request a macOS system permission via System Settings. " +
99
+ "Request a system permission via macOS System Settings or Windows Settings. " +
85
100
  "Use when a tool fails with a permission/access error (e.g. 'Operation not permitted', 'EACCES', sandbox denial). " +
86
101
  "Do not explain how to open System Settings manually - this tool handles it with a clickable button.",
87
102
  category: "system",
@@ -94,7 +109,7 @@ export const requestSystemPermissionTool = {
94
109
 
95
110
  async execute(
96
111
  input: Record<string, unknown>,
97
- _context: ToolContext,
112
+ context: ToolContext,
98
113
  ): Promise<ToolExecutionResult> {
99
114
  const parsed = requestSystemPermissionInputSchema.safeParse(input);
100
115
  if (!parsed.success) {
@@ -103,7 +118,16 @@ export const requestSystemPermissionTool = {
103
118
  const permType = parsed.data.permission_type;
104
119
 
105
120
  const friendly = FRIENDLY_NAMES[permType];
106
- const settingsUrl = SETTINGS_URLS[permType];
121
+ const settingsUrl = resolveSystemPermissionSettingsUrl(
122
+ permType,
123
+ context.clientOs,
124
+ );
125
+ if (!settingsUrl) {
126
+ return {
127
+ content: `${friendly} cannot be requested in-app on ${context.clientOs}. Ask the user to check the matching Privacy page in Windows Settings.`,
128
+ isError: true,
129
+ };
130
+ }
107
131
 
108
132
  return {
109
133
  content: [
@@ -1,6 +1,13 @@
1
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
1
4
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
5
 
3
6
  import { setConfig } from "../../__tests__/helpers/set-config.js";
7
+ import {
8
+ MAX_OUTPUT_LENGTH,
9
+ OUTPUT_TRUNCATED_TAG,
10
+ } from "../shared/shell-output.js";
4
11
  import type { ToolContext } from "../types.js";
5
12
 
6
13
  // ---------------------------------------------------------------------------
@@ -199,3 +206,59 @@ describe("foreground stdin handling", () => {
199
206
  expect(result.content).not.toContain("ENXIO");
200
207
  });
201
208
  });
209
+
210
+ describe("streaming stdout cap", () => {
211
+ let tmpDir = "";
212
+
213
+ beforeEach(async () => {
214
+ events.length = 0;
215
+ _clearRegistryForTesting();
216
+ tmpDir = await mkdtemp(join(tmpdir(), "bash-cap-"));
217
+ });
218
+
219
+ afterEach(async () => {
220
+ _clearRegistryForTesting();
221
+ if (tmpDir) {
222
+ await rm(tmpDir, { recursive: true, force: true });
223
+ }
224
+ });
225
+
226
+ test("foreground drops stdout past the cap and lets the process finish", async () => {
227
+ const result = await shellTool.execute(
228
+ {
229
+ command: `${process.execPath} -e "process.stdout.write('x'.repeat(100000)); require('node:fs').writeFileSync('sentinel.txt','done')"`,
230
+ activity: "test",
231
+ },
232
+ { ...makeContext(), workingDir: tmpDir },
233
+ );
234
+
235
+ expect(result.isError).toBeFalsy();
236
+ expect(result.content).toContain(OUTPUT_TRUNCATED_TAG);
237
+ expect(result.content).not.toContain("file=");
238
+ expect(result.content.length).toBeLessThan(MAX_OUTPUT_LENGTH + 80);
239
+ expect(await readFile(join(tmpDir, "sentinel.txt"), "utf-8")).toBe("done");
240
+ });
241
+
242
+ test("background drops stdout past the cap and lets the process finish", async () => {
243
+ const result = await shellTool.execute(
244
+ {
245
+ command: `${process.execPath} -e "process.stdout.write('x'.repeat(100000)); require('node:fs').writeFileSync('sentinel.txt','done')"`,
246
+ activity: "test",
247
+ background: true,
248
+ },
249
+ { ...makeContext(), workingDir: tmpDir },
250
+ );
251
+ expect(result.isError).toBeFalsy();
252
+
253
+ await waitFor(() => completedEvents().length > 0);
254
+ const completed = completedEvents();
255
+ expect(completed).toHaveLength(1);
256
+ expect(completed[0]).toMatchObject({ status: "completed", exitCode: 0 });
257
+ expect(String(completed[0]?.output)).toContain(OUTPUT_TRUNCATED_TAG);
258
+ expect(String(completed[0]?.output)).not.toContain("file=");
259
+ expect(String(completed[0]?.output).length).toBeLessThan(
260
+ MAX_OUTPUT_LENGTH + 80,
261
+ );
262
+ expect(await readFile(join(tmpDir, "sentinel.txt"), "utf-8")).toBe("done");
263
+ });
264
+ });
@@ -32,7 +32,7 @@ import {
32
32
  getSessionEnv,
33
33
  } from "../network/script-proxy/index.js";
34
34
  import {
35
- formatShellOutput,
35
+ attachBoundedStdio,
36
36
  MAX_OUTPUT_LENGTH,
37
37
  } from "../shared/shell-output.js";
38
38
  import {
@@ -316,8 +316,6 @@ export const shellTool = {
316
316
  }
317
317
 
318
318
  const bgId = generateBackgroundToolId();
319
- const stdoutChunks: Buffer[] = [];
320
- const stderrChunks: Buffer[] = [];
321
319
  let timedOut = false;
322
320
  let aborted = false;
323
321
  const startedAt = Date.now();
@@ -327,7 +325,9 @@ export const shellTool = {
327
325
  env,
328
326
  stdio: ["ignore", "pipe", "pipe"],
329
327
  detached: true,
328
+ windowsHide: true,
330
329
  });
330
+ const collector = attachBoundedStdio(child);
331
331
 
332
332
  const killTree = buildKillTree(child, {
333
333
  toolName: "bash",
@@ -342,14 +342,6 @@ export const shellTool = {
342
342
  killTree("timeout");
343
343
  }, timeoutMs);
344
344
 
345
- child.stdout.on("data", (data: Buffer) => {
346
- stdoutChunks.push(data);
347
- });
348
-
349
- child.stderr.on("data", (data: Buffer) => {
350
- stderrChunks.push(data);
351
- });
352
-
353
345
  // Guard against double-wake: when spawn fails (e.g. invalid cwd),
354
346
  // Node emits both 'error' and 'close' for the same child process.
355
347
  // Only the first handler to fire should wake the agent.
@@ -375,15 +367,7 @@ export const shellTool = {
375
367
  timedOut,
376
368
  });
377
369
 
378
- const stdout = Buffer.concat(stdoutChunks).toString();
379
- const stderr = Buffer.concat(stderrChunks).toString();
380
- const fmtResult = formatShellOutput(
381
- stdout,
382
- stderr,
383
- code,
384
- timedOut,
385
- timeoutSec,
386
- );
370
+ const fmtResult = collector.format(code, timedOut, timeoutSec);
387
371
 
388
372
  const status: BackgroundToolCompletedEvent["status"] = aborted
389
373
  ? "cancelled"
@@ -392,7 +376,7 @@ export const shellTool = {
392
376
  : code === 0
393
377
  ? "completed"
394
378
  : "failed";
395
- // A cancelled command exits with a null code, which formatShellOutput
379
+ // A cancelled command exits with a null code, which the formatter
396
380
  // frames as "failed"; surface the cancellation instead.
397
381
  const output =
398
382
  status === "cancelled"
@@ -427,8 +411,8 @@ export const shellTool = {
427
411
  untrustedOutput: {
428
412
  content: output,
429
413
  source: "tool_result",
430
- // Already bounded + recovery-marked by formatShellOutput; a larger
431
- // budget keeps wrapUntrustedContent from re-truncating the marker.
414
+ // Bounded output plus the truncation marker; a larger budget
415
+ // keeps wrapUntrustedContent from slicing the marker off.
432
416
  maxChars: MAX_OUTPUT_LENGTH * 2,
433
417
  },
434
418
  });
@@ -538,8 +522,6 @@ export const shellTool = {
538
522
  // Foreground mode: await the process and return its output.
539
523
  // -----------------------------------------------------------------------
540
524
  const result = await new Promise<ToolExecutionResult>((resolve) => {
541
- const stdoutChunks: Buffer[] = [];
542
- const stderrChunks: Buffer[] = [];
543
525
  let timedOut = false;
544
526
  const startedAt = Date.now();
545
527
 
@@ -548,6 +530,10 @@ export const shellTool = {
548
530
  env,
549
531
  stdio: ["ignore", "pipe", "pipe"],
550
532
  detached: true,
533
+ windowsHide: true,
534
+ });
535
+ const collector = attachBoundedStdio(child, {
536
+ onOutput: context.onOutput,
551
537
  });
552
538
 
553
539
  const killTree = buildKillTree(child, {
@@ -572,16 +558,6 @@ export const shellTool = {
572
558
  }
573
559
  }
574
560
 
575
- child.stdout.on("data", (data: Buffer) => {
576
- stdoutChunks.push(data);
577
- context.onOutput?.(data.toString());
578
- });
579
-
580
- child.stderr.on("data", (data: Buffer) => {
581
- stderrChunks.push(data);
582
- context.onOutput?.(data.toString());
583
- });
584
-
585
561
  child.on("close", (code, signal) => {
586
562
  clearTimeout(timer);
587
563
  context.signal?.removeEventListener("abort", onAbort);
@@ -597,15 +573,7 @@ export const shellTool = {
597
573
  timedOut,
598
574
  });
599
575
 
600
- const stdout = Buffer.concat(stdoutChunks).toString();
601
- const stderr = Buffer.concat(stderrChunks).toString();
602
- const fmtResult = formatShellOutput(
603
- stdout,
604
- stderr,
605
- code,
606
- timedOut,
607
- timeoutSec,
608
- );
576
+ const fmtResult = collector.format(code, timedOut, timeoutSec);
609
577
 
610
578
  resolve({
611
579
  content: fmtResult.content,
@@ -1,6 +1,7 @@
1
1
  import type { z } from "zod";
2
2
 
3
3
  import { askQuestionInputSchema } from "./ask-question/ask-question-tool.js";
4
+ import { reactToMessageInputSchema } from "./channel/react-to-message.js";
4
5
  import { fileEditInputSchema } from "./filesystem/edit.js";
5
6
  import { fileListInputSchema } from "./filesystem/list.js";
6
7
  import { fileReadInputSchema } from "./filesystem/read.js";
@@ -56,6 +57,7 @@ export const TOOL_INPUT_SCHEMAS: Readonly<Record<string, z.ZodType>> = {
56
57
  host_file_read: hostFileReadInputSchema,
57
58
  host_file_write: hostFileWriteInputSchema,
58
59
  notify_parent: notifyParentInputSchema,
60
+ react_to_message: reactToMessageInputSchema,
59
61
  request_system_permission: requestSystemPermissionInputSchema,
60
62
  };
61
63
 
@@ -12,6 +12,7 @@ import {
12
12
  rememberTool,
13
13
  } from "../plugins/defaults/memory/tools.js";
14
14
  import { askQuestionTool } from "./ask-question/ask-question-tool.js";
15
+ import { reactToMessageTool } from "./channel/react-to-message.js";
15
16
  import { fileEditTool } from "./filesystem/edit.js";
16
17
  import { fileListTool } from "./filesystem/list.js";
17
18
  import { fileReadTool } from "./filesystem/read.js";
@@ -65,6 +66,7 @@ export const explicitTools: ToolDefinition[] = [
65
66
  deleteMemoryPageTool,
66
67
  notifyParentTool,
67
68
  askQuestionTool,
69
+ reactToMessageTool,
68
70
  hostFileReadTool,
69
71
  hostFileWriteTool,
70
72
  hostFileEditTool,
@@ -19,6 +19,13 @@ export type UsageAttributionProfileSource =
19
19
 
20
20
  export interface UsageAttributionInput {
21
21
  callSite: LLMCallSite | null;
22
+ /**
23
+ * Call site used to pick the winning profile. Defaults to {@link callSite}.
24
+ * Compaction invokes the provider as `mainAgent` (prefix cache) but tags
25
+ * the usage row as `compactionAgent`; pass `mainAgent` here so
26
+ * `appliedProfile` / `profileSource` match the request that ran.
27
+ */
28
+ profileResolutionCallSite?: LLMCallSite | null;
22
29
  overrideProfile?: string | null;
23
30
  /**
24
31
  * Mirrors `ResolveCallSiteOpts.forceOverrideProfile`: the override profile
@@ -130,9 +137,10 @@ export function resolveUsageAttribution(
130
137
  ): UsageAttributionSnapshot {
131
138
  const llm = getConfig().llm;
132
139
  const callSite = input.callSite;
140
+ const resolutionCallSite = input.profileResolutionCallSite ?? callSite;
133
141
  const overrideProfile = normalizeProfileId(input.overrideProfile);
134
142
 
135
- if (callSite == null) {
143
+ if (resolutionCallSite == null) {
136
144
  const resolvedMainAgent = resolveCallSiteConfig("mainAgent", llm, {
137
145
  ...(input.selectionSeed != null
138
146
  ? { selectionSeed: input.selectionSeed }
@@ -158,7 +166,7 @@ export function resolveUsageAttribution(
158
166
  // Capture which arm each expanded mix resolved to so we can attribute usage
159
167
  // to the arm behind the applied (mix) profile below.
160
168
  const mixSelections = new Map<string, string>();
161
- const resolved = resolveCallSiteConfig(callSite, llm, {
169
+ const resolved = resolveCallSiteConfig(resolutionCallSite, llm, {
162
170
  ...(overrideProfile != null ? { overrideProfile } : {}),
163
171
  ...(input.forceOverrideProfile === true
164
172
  ? { forceOverrideProfile: true }
@@ -171,13 +179,13 @@ export function resolveUsageAttribution(
171
179
  });
172
180
  const activeProfile = normalizeProfileId(llm.activeProfile);
173
181
  const callSiteProfile = normalizeProfileId(
174
- llm.callSites?.[callSite]?.profile,
182
+ llm.callSites?.[resolutionCallSite]?.profile,
175
183
  );
176
184
  // The resolver's own winner selection is the single source of truth for which
177
185
  // profile applied — attribution must never re-derive precedence and drift
178
186
  // from dispatch.
179
187
  const profile = appliedProfileFromWinnerSelection(
180
- callSite,
188
+ resolutionCallSite,
181
189
  llm,
182
190
  overrideProfile,
183
191
  input,
@@ -0,0 +1,77 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { isDaemonCommand } from "../worker-ownership.js";
4
+
5
+ // This predicate authorises an irreversible signal in killStaleDaemon, so it
6
+ // judges argv shape rather than substring. The negative cases are the
7
+ // collision cli/src/lib/orphan-detection.test.ts already pins as something we
8
+ // must not claim.
9
+ describe("isDaemonCommand", () => {
10
+ test("source daemon: bun with the entry script as an argument", () => {
11
+ expect(
12
+ isDaemonCommand(
13
+ "bun run /app/runtime/0.11.7/node_modules/@vellumai/assistant/src/daemon/main.ts",
14
+ ),
15
+ ).toBe(true);
16
+ });
17
+
18
+ test("packaged daemon as argv0", () => {
19
+ expect(isDaemonCommand("/Applications/Vellum/vellum-daemon")).toBe(true);
20
+ });
21
+
22
+ // The ordinary Windows install path contains a space, so splitting on
23
+ // whitespace before honouring quotes would yield argv0 "Program" and make
24
+ // the packaged daemon invisible to the recovery path this predicate serves.
25
+ test("packaged Windows daemon under an install path containing spaces", () => {
26
+ expect(
27
+ isDaemonCommand(
28
+ '"C:\\Program Files\\Vellum\\resources\\cli-runtime\\vellum-daemon.exe"',
29
+ ),
30
+ ).toBe(true);
31
+ expect(
32
+ isDaemonCommand(
33
+ '"C:\\Program Files\\Vellum\\resources\\cli-runtime\\vellum-daemon.exe" --serve',
34
+ ),
35
+ ).toBe(true);
36
+ });
37
+
38
+ test("quoted bun entry-script argument under a path containing spaces", () => {
39
+ expect(
40
+ isDaemonCommand(
41
+ '"C:\\Program Files\\bun\\bun.exe" run "C:\\Program Files\\Vellum\\src\\daemon\\main.ts"',
42
+ ),
43
+ ).toBe(true);
44
+ });
45
+
46
+ // POSIX joins argv with spaces, so an entry path containing a space arrives
47
+ // pre-split; the tail token still carries the entry, which is what matches.
48
+ test("unquoted POSIX entry path containing a space", () => {
49
+ expect(
50
+ isDaemonCommand(
51
+ "/home/mary/.bun/bin/bun run /home/Mary Jane/runtime/src/daemon/main.ts",
52
+ ),
53
+ ).toBe(true);
54
+ });
55
+
56
+ // process.title rewrites the source daemon's argv on Linux.
57
+ test("a daemon that renamed its own process title", () => {
58
+ expect(isDaemonCommand("vellum-daemon")).toBe(true);
59
+ });
60
+
61
+ test("unrelated service whose argv merely contains a daemon/main path", () => {
62
+ expect(isDaemonCommand("node /opt/unrelated-service/daemon/main.ts")).toBe(
63
+ false,
64
+ );
65
+ });
66
+
67
+ test("an editor or tool holding a recycled PID", () => {
68
+ expect(isDaemonCommand("vim /app/runtime/0.11.7/src/daemon/main.ts")).toBe(
69
+ false,
70
+ );
71
+ });
72
+
73
+ test("unreadable or empty command line", () => {
74
+ expect(isDaemonCommand(null)).toBe(false);
75
+ expect(isDaemonCommand("")).toBe(false);
76
+ });
77
+ });