@vellumai/assistant 0.12.3-staging.1 → 0.12.3-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (560) hide show
  1. package/ARCHITECTURE.md +43 -29
  2. package/Dockerfile +63 -0
  3. package/docs/architecture/security.md +10 -5
  4. package/docs/architecture/turn-actor.md +2 -0
  5. package/docs/desktop-browser-cli.md +45 -4
  6. package/docs/live-voice-task-outcomes.md +100 -0
  7. package/docs/mode-sessions.md +183 -0
  8. package/docs/voice-input-diagnostics.md +58 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  15. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
  16. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
  17. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
  18. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
  19. package/node_modules/@vellumai/service-contracts/package.json +2 -1
  20. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  22. package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
  23. package/openapi.yaml +665 -14
  24. package/package.json +1 -1
  25. package/scripts/smoke-desktop-browser-cli.ts +10 -26
  26. package/scripts/smoke-desktop-window-theme.ts +2 -3
  27. package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
  28. package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
  29. package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
  30. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  31. package/src/__tests__/answered-question-persistence.test.ts +2 -0
  32. package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
  33. package/src/__tests__/approval-message-composer.test.ts +1 -118
  34. package/src/__tests__/approval-routes-http.test.ts +3 -0
  35. package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
  36. package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
  37. package/src/__tests__/assistant-event-targeting.test.ts +186 -0
  38. package/src/__tests__/assistant-stream-state.test.ts +29 -9
  39. package/src/__tests__/browser-fill-credential.test.ts +11 -22
  40. package/src/__tests__/btw-routes.test.ts +4 -1
  41. package/src/__tests__/build-persisted-content.test.ts +1 -1
  42. package/src/__tests__/call-conversation-messages.test.ts +5 -0
  43. package/src/__tests__/call-recovery.test.ts +7 -2
  44. package/src/__tests__/call-routes-http.test.ts +12 -0
  45. package/src/__tests__/call-store.test.ts +7 -2
  46. package/src/__tests__/channel-retry-sweep.test.ts +9 -0
  47. package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
  48. package/src/__tests__/checker.test.ts +11 -3
  49. package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
  50. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
  51. package/src/__tests__/computer-use-tools.test.ts +1 -233
  52. package/src/__tests__/config-model-image-provider.test.ts +39 -0
  53. package/src/__tests__/config-schema.test.ts +16 -0
  54. package/src/__tests__/config-watcher.test.ts +24 -0
  55. package/src/__tests__/context-search-agent-runner.test.ts +260 -1
  56. package/src/__tests__/context-search-fanout.test.ts +1 -1
  57. package/src/__tests__/context-search-types.test.ts +7 -7
  58. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
  59. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
  60. package/src/__tests__/conversation-agent-loop.test.ts +546 -16
  61. package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
  62. package/src/__tests__/conversation-evictor.test.ts +62 -37
  63. package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
  64. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
  65. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  66. package/src/__tests__/conversation-retry-route.test.ts +22 -0
  67. package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
  68. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
  69. package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
  70. package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  72. package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
  73. package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
  74. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
  75. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
  76. package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
  77. package/src/__tests__/conversation-undo.test.ts +61 -0
  78. package/src/__tests__/credential-execution-client.test.ts +6 -6
  79. package/src/__tests__/credential-prompt-route.test.ts +35 -3
  80. package/src/__tests__/cu-unified-flow.test.ts +82 -1
  81. package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
  82. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
  83. package/src/__tests__/dm-backfill.test.ts +91 -1
  84. package/src/__tests__/document-create-route.test.ts +88 -0
  85. package/src/__tests__/document-sync-tags.test.ts +27 -0
  86. package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
  87. package/src/__tests__/events-tail-route.test.ts +25 -0
  88. package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
  89. package/src/__tests__/guardian-action-sweep.test.ts +16 -0
  90. package/src/__tests__/guardian-dispatch.test.ts +13 -0
  91. package/src/__tests__/headless-browser-interactions.test.ts +64 -20
  92. package/src/__tests__/heartbeat-service.test.ts +2 -59
  93. package/src/__tests__/helpers/mock-conversation.ts +21 -0
  94. package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
  95. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
  96. package/src/__tests__/host-cu-proxy.test.ts +46 -2
  97. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  98. package/src/__tests__/image-credentials.test.ts +49 -0
  99. package/src/__tests__/image-service-dispatcher.test.ts +45 -0
  100. package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
  101. package/src/__tests__/interrupt-on-send.test.ts +57 -43
  102. package/src/__tests__/interrupt-turn-note.test.ts +5 -6
  103. package/src/__tests__/llm-catalog-parity.test.ts +2 -2
  104. package/src/__tests__/media-generate-image.test.ts +49 -1
  105. package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
  106. package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
  107. package/src/__tests__/mode-session-history.test.ts +245 -0
  108. package/src/__tests__/mode-session-recovery.test.ts +222 -0
  109. package/src/__tests__/mode-session-wire.test.ts +141 -0
  110. package/src/__tests__/notification-guardian-path.test.ts +7 -0
  111. package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
  112. package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  115. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  116. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +5 -0
  118. package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
  119. package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
  120. package/src/__tests__/process-message-display-content.test.ts +56 -0
  121. package/src/__tests__/reaction-persistence.test.ts +5 -0
  122. package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
  123. package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
  124. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
  125. package/src/__tests__/secret-ingress-http.test.ts +2 -0
  126. package/src/__tests__/secure-keys.test.ts +43 -1
  127. package/src/__tests__/send-endpoint-busy.test.ts +163 -1
  128. package/src/__tests__/server-history-render.test.ts +91 -1
  129. package/src/__tests__/settings-routes.test.ts +50 -1
  130. package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
  132. package/src/__tests__/subagent-manager-notify.test.ts +1 -2
  133. package/src/__tests__/subagent-notify-parent.test.ts +146 -39
  134. package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
  135. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
  136. package/src/__tests__/thread-backfill.test.ts +53 -0
  137. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  138. package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
  139. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  140. package/src/__tests__/twilio-routes.test.ts +1 -0
  141. package/src/__tests__/web-search-backend-failure.test.ts +1 -1
  142. package/src/activation/progress-store.test.ts +12 -7
  143. package/src/agent/loop.ts +99 -41
  144. package/src/api/events/assistant-turn-start.ts +3 -0
  145. package/src/api/events/generation-handoff.ts +2 -0
  146. package/src/api/events/message-complete.ts +2 -0
  147. package/src/api/events/notification-conversation-created.ts +14 -16
  148. package/src/api/events/notification-intent.ts +4 -2
  149. package/src/api/events/tool-result.ts +85 -0
  150. package/src/api/events/tool-use-start.ts +3 -0
  151. package/src/api/events/user-message-echo.ts +3 -0
  152. package/src/api/index.ts +31 -6
  153. package/src/api/mode-session.test.ts +131 -0
  154. package/src/api/mode-session.ts +148 -0
  155. package/src/api/remember-tool.ts +15 -0
  156. package/src/api/responses/conversation-message.ts +6 -0
  157. package/src/approvals/AGENTS.md +4 -2
  158. package/src/browser/__tests__/operations.test.ts +29 -0
  159. package/src/browser/operation-meta.ts +2 -1
  160. package/src/browser/operations.ts +29 -0
  161. package/src/browser/virtual-desktop-target.ts +2 -8
  162. package/src/bundler/bundle-signer.ts +1 -3
  163. package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
  164. package/src/calls/__tests__/call-controller.test.ts +94 -0
  165. package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
  166. package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
  167. package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
  168. package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
  169. package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
  170. package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
  171. package/src/calls/barge-in-guard.ts +5 -0
  172. package/src/calls/call-controller.ts +196 -1
  173. package/src/calls/call-domain.ts +4 -0
  174. package/src/calls/call-store.ts +87 -2
  175. package/src/calls/guardian-action-sweep.ts +22 -8
  176. package/src/calls/media-stream-audio-transcode.ts +1 -3
  177. package/src/calls/typesafe-noul.ts +156 -0
  178. package/src/calls/voice-control-protocol.ts +6 -0
  179. package/src/calls/voice-escalation-judge.ts +145 -0
  180. package/src/calls/voice-escalation-target.ts +10 -0
  181. package/src/calls/voice-leg-coordinator.ts +121 -3
  182. package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
  183. package/src/calls/voice-session-bridge.ts +256 -112
  184. package/src/calls/voice-triage-escalate.ts +7 -0
  185. package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
  186. package/src/cli/commands/__tests__/notifications.test.ts +35 -10
  187. package/src/cli/commands/browser.help.ts +4 -5
  188. package/src/cli/commands/browser.ts +0 -1
  189. package/src/cli/commands/clients.help.ts +46 -0
  190. package/src/cli/commands/clients.ts +116 -0
  191. package/src/cli/commands/credentials.help.ts +6 -1
  192. package/src/cli/commands/notifications.help.ts +16 -2
  193. package/src/cli/commands/notifications.ts +30 -8
  194. package/src/cli/commands/roadmap.help.ts +18 -21
  195. package/src/cli/commands/roadmap.ts +4 -21
  196. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
  197. package/src/cli/lib/bundled-marketplace.json +84 -34
  198. package/src/cli/lib/plugin-marketplace.ts +8 -0
  199. package/src/cli/lib/publish-plugin.ts +1 -2
  200. package/src/config/__tests__/default-provider.test.ts +3 -1
  201. package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
  202. package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
  203. package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
  204. package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
  205. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
  206. package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
  207. package/src/config/call-site-defaults.ts +9 -0
  208. package/src/config/feature-flag-registry.json +16 -0
  209. package/src/config/profile-text-generation.test.ts +2 -2
  210. package/src/config/schemas/__tests__/tools.test.ts +31 -0
  211. package/src/config/schemas/call-site-catalog.ts +14 -0
  212. package/src/config/schemas/channels.ts +0 -18
  213. package/src/config/schemas/llm.ts +3 -1
  214. package/src/config/schemas/mcp.ts +2 -1
  215. package/src/config/schemas/memory-retrospective.ts +11 -2
  216. package/src/config/schemas/services.ts +15 -6
  217. package/src/config/schemas/tools.ts +10 -0
  218. package/src/config/session-groups-gate.ts +21 -0
  219. package/src/config/skills.ts +2 -6
  220. package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
  221. package/src/conversations/message-consolidation.ts +102 -5
  222. package/src/credential-execution/ces-connect.test.ts +69 -0
  223. package/src/credential-execution/ces-connect.ts +115 -0
  224. package/src/credential-execution/ces-runtime.ts +55 -93
  225. package/src/credential-execution/executable-discovery.ts +1 -11
  226. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
  227. package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
  228. package/src/daemon/browser-mode-session.test.ts +402 -0
  229. package/src/daemon/browser-mode-session.ts +186 -0
  230. package/src/daemon/computer-use-mode-session.test.ts +210 -0
  231. package/src/daemon/computer-use-mode-session.ts +105 -0
  232. package/src/daemon/config-watcher.ts +10 -0
  233. package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
  234. package/src/daemon/conversation-agent-loop.ts +219 -25
  235. package/src/daemon/conversation-evictor.ts +5 -24
  236. package/src/daemon/conversation-history.ts +10 -0
  237. package/src/daemon/conversation-interrupt-repair.ts +4 -43
  238. package/src/daemon/conversation-interrupt.ts +7 -11
  239. package/src/daemon/conversation-messaging.ts +58 -5
  240. package/src/daemon/conversation-mode-session.test.ts +1667 -0
  241. package/src/daemon/conversation-mode-session.ts +1008 -0
  242. package/src/daemon/conversation-process.ts +5 -0
  243. package/src/daemon/conversation-runtime-assembly.ts +10 -20
  244. package/src/daemon/conversation-skill-tools.ts +1 -0
  245. package/src/daemon/conversation-store.ts +4 -4
  246. package/src/daemon/conversation-surface-snapshots.ts +135 -0
  247. package/src/daemon/conversation-surfaces.ts +113 -5
  248. package/src/daemon/conversation-tool-setup.ts +8 -3
  249. package/src/daemon/conversation.ts +143 -41
  250. package/src/daemon/handlers/config-model.ts +7 -5
  251. package/src/daemon/handlers/shared.ts +55 -16
  252. package/src/daemon/host-cu-proxy.ts +51 -22
  253. package/src/daemon/lifecycle.ts +30 -13
  254. package/src/daemon/message-protocol.ts +0 -1
  255. package/src/daemon/message-provenance.ts +28 -4
  256. package/src/daemon/mode-session-startup-recovery.ts +23 -0
  257. package/src/daemon/mode-session-tracking.ts +54 -0
  258. package/src/daemon/process-message.ts +13 -4
  259. package/src/daemon/virtual-desktop-context.ts +21 -0
  260. package/src/desktop/__tests__/fake-desktop.ts +1 -0
  261. package/src/desktop/desktop-accessibility-script.ts +88 -0
  262. package/src/desktop/desktop-accessibility.test.ts +160 -0
  263. package/src/desktop/desktop-accessibility.ts +169 -0
  264. package/src/desktop/desktop-automation-lease.test.ts +76 -68
  265. package/src/desktop/desktop-automation-lease.ts +41 -50
  266. package/src/desktop/desktop-browser-endpoint.ts +4 -0
  267. package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
  268. package/src/desktop/desktop-computer-use.test.ts +449 -0
  269. package/src/desktop/desktop-computer-use.ts +502 -0
  270. package/src/desktop/desktop-dependencies.ts +47 -293
  271. package/src/desktop/desktop-display.ts +2 -2
  272. package/src/desktop/desktop-panel-config.test.ts +13 -3
  273. package/src/desktop/desktop-panel-config.ts +109 -15
  274. package/src/desktop/desktop-session-manager.test.ts +69 -21
  275. package/src/desktop/desktop-session-manager.ts +76 -22
  276. package/src/desktop/desktop-stream-bridge.test.ts +11 -122
  277. package/src/desktop/desktop-stream-bridge.ts +6 -8
  278. package/src/desktop/desktop-window-theme.ts +5 -25
  279. package/src/desktop/virtual-desktop-feature.ts +52 -2
  280. package/src/desktop/virtual-desktop-platform.test.ts +5 -9
  281. package/src/documents/document-store.ts +23 -0
  282. package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
  283. package/src/heartbeat/heartbeat-service.ts +5 -33
  284. package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
  285. package/src/ipc/assistant-server.ts +2 -0
  286. package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
  287. package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
  288. package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
  289. package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
  290. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
  291. package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
  292. package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
  293. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
  294. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
  295. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
  296. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
  297. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
  298. package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
  299. package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
  300. package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
  301. package/src/live-voice/__tests__/session-controls.test.ts +23 -0
  302. package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
  303. package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
  304. package/src/live-voice/camera-mode-session.test.ts +265 -0
  305. package/src/live-voice/camera-mode-session.ts +277 -0
  306. package/src/live-voice/continuation-judge.ts +123 -0
  307. package/src/live-voice/live-activity-reporter.ts +115 -6
  308. package/src/live-voice/live-voice-manager.ts +36 -1
  309. package/src/live-voice/live-voice-photo.ts +88 -7
  310. package/src/live-voice/live-voice-session-manager.ts +15 -0
  311. package/src/live-voice/live-voice-session.ts +1443 -747
  312. package/src/live-voice/live-voice-tts.ts +22 -4
  313. package/src/live-voice/microphone-idle-gate.ts +56 -0
  314. package/src/live-voice/protocol.ts +171 -4
  315. package/src/live-voice/session-controls.ts +11 -3
  316. package/src/live-voice/task-outcomes.ts +131 -0
  317. package/src/live-voice/voice-input-diagnostics.ts +117 -0
  318. package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
  319. package/src/mcp/__tests__/tool-caps.test.ts +28 -1
  320. package/src/mcp/manager.ts +3 -0
  321. package/src/mcp/tool-caps.ts +12 -4
  322. package/src/media/__tests__/image-models.test.ts +58 -0
  323. package/src/media/image-credentials.ts +5 -0
  324. package/src/media/image-models.ts +52 -0
  325. package/src/media/image-service.ts +8 -0
  326. package/src/media/openrouter-image-service.test.ts +133 -0
  327. package/src/media/openrouter-image-service.ts +153 -0
  328. package/src/media/types.ts +1 -1
  329. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
  330. package/src/messaging/providers/discord/send.ts +4 -17
  331. package/src/messaging/providers/discord/transport.ts +2 -2
  332. package/src/messaging/providers/outlook/client.ts +16 -22
  333. package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
  334. package/src/messaging/providers/telegram-bot/send.ts +10 -7
  335. package/src/messaging/providers/telegram-bot/transport.ts +3 -10
  336. package/src/messaging/providers/whatsapp/send.test.ts +169 -4
  337. package/src/monitoring/__tests__/control.test.ts +17 -5
  338. package/src/monitoring/control.ts +15 -5
  339. package/src/notifications/AGENTS.md +1 -1
  340. package/src/notifications/README.md +83 -99
  341. package/src/notifications/__tests__/broadcaster.test.ts +1 -1
  342. package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
  343. package/src/notifications/__tests__/copy-composer.test.ts +1 -1
  344. package/src/notifications/__tests__/decision-engine.test.ts +79 -2
  345. package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
  346. package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
  347. package/src/notifications/__tests__/notification-utils.test.ts +18 -0
  348. package/src/notifications/adapters/macos.ts +37 -46
  349. package/src/notifications/broadcaster.ts +5 -6
  350. package/src/notifications/channel-allowlist.ts +44 -0
  351. package/src/notifications/copy-composer.ts +0 -23
  352. package/src/notifications/decision-engine.ts +24 -14
  353. package/src/notifications/deterministic-checks.ts +1 -1
  354. package/src/notifications/emit-signal.ts +123 -37
  355. package/src/notifications/home-feed-side-effect.ts +9 -7
  356. package/src/notifications/notification-utils.ts +19 -0
  357. package/src/notifications/signal.ts +4 -19
  358. package/src/notifications/types.ts +15 -0
  359. package/src/oauth/connection-resolver.ts +1 -2
  360. package/src/oauth/oauth-store.ts +1 -3
  361. package/src/oauth/provider-serializer.ts +7 -0
  362. package/src/oauth/seed-providers.ts +30 -0
  363. package/src/onboarding/onboarding-events-store.ts +99 -12
  364. package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
  365. package/src/persistence/client-connection-events-store.ts +377 -0
  366. package/src/persistence/conversation-crud.ts +55 -6
  367. package/src/persistence/conversation-mode-sessions.test.ts +424 -0
  368. package/src/persistence/conversation-mode-sessions.ts +460 -0
  369. package/src/persistence/conversation-queries.ts +6 -19
  370. package/src/persistence/llm-usage-store.ts +3 -9
  371. package/src/persistence/message-metadata.ts +25 -0
  372. package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
  373. package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
  374. package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
  375. package/src/persistence/schema/conversations.ts +67 -0
  376. package/src/persistence/schema/infrastructure.ts +29 -0
  377. package/src/persistence/steps.ts +17 -0
  378. package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
  379. package/src/plugin-api/effective-context-window.ts +33 -0
  380. package/src/plugin-api/index.ts +24 -5
  381. package/src/plugin-api/plugin-name-env.ts +125 -0
  382. package/src/plugin-api/resolve-credential.ts +79 -20
  383. package/src/plugin-api/vision-support.test.ts +22 -0
  384. package/src/plugin-api/vision-support.ts +17 -5
  385. package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
  386. package/src/plugins/active-plugin-env.ts +51 -0
  387. package/src/plugins/defaults/index.ts +24 -0
  388. package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
  389. package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
  390. package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
  391. package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
  392. package/src/plugins/defaults/memory/context-search/format.ts +51 -7
  393. package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
  394. package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
  395. package/src/plugins/defaults/memory/context-search/search.ts +8 -14
  396. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
  397. package/src/plugins/defaults/memory/context-search/types.ts +1 -11
  398. package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
  399. package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
  400. package/src/plugins/defaults/memory/graph/tools.ts +35 -64
  401. package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
  402. package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
  403. package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
  404. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
  405. package/src/plugins/defaults/memory/tools.test.ts +124 -9
  406. package/src/plugins/defaults/memory/tools.ts +38 -11
  407. package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
  408. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
  409. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
  410. package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
  411. package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
  412. package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
  413. package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
  414. package/src/plugins/defaults/memory/worker-control.ts +15 -5
  415. package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
  416. package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
  417. package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
  418. package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
  419. package/src/plugins/defaults/task-progress/package.json +14 -0
  420. package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
  421. package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
  422. package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
  423. package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
  424. package/src/providers/anthropic/client.ts +10 -1
  425. package/src/providers/anthropic-gateway-shared.ts +6 -0
  426. package/src/providers/fetch-provider-catalog.ts +11 -0
  427. package/src/providers/gemini/client.ts +25 -8
  428. package/src/providers/inference/adapter-factory.ts +1 -1
  429. package/src/providers/jev/client.test.ts +10 -0
  430. package/src/providers/jev/client.ts +20 -4
  431. package/src/providers/model-catalog.ts +4 -3
  432. package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
  433. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
  434. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
  435. package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
  436. package/src/providers/openai/chat-completions-provider.ts +100 -29
  437. package/src/providers/openai/google-thought-signature.ts +0 -6
  438. package/src/providers/openai/responses-provider.ts +23 -1
  439. package/src/providers/ratelimit.ts +1 -3
  440. package/src/providers/search-provider-catalog.ts +24 -0
  441. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
  442. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
  443. package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
  444. package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
  445. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
  446. package/src/providers/types.ts +5 -0
  447. package/src/routes/control.ts +7 -5
  448. package/src/runtime/AGENTS.md +4 -2
  449. package/src/runtime/actor-trust-resolver.ts +3 -3
  450. package/src/runtime/agent-wake.ts +1 -1
  451. package/src/runtime/approval-message-composer.ts +3 -69
  452. package/src/runtime/assistant-event-hub.ts +73 -75
  453. package/src/runtime/assistant-event-publish-options.ts +7 -0
  454. package/src/runtime/assistant-event-targeting.ts +79 -0
  455. package/src/runtime/assistant-stream-state.ts +13 -100
  456. package/src/runtime/background-job-runner.ts +5 -5
  457. package/src/runtime/channel-retry-sweep.ts +1 -0
  458. package/src/runtime/guardian-action-message-composer.ts +8 -161
  459. package/src/runtime/guardian-reply-router.ts +28 -32
  460. package/src/runtime/http-types.ts +6 -0
  461. package/src/runtime/message-composer-types.ts +3 -61
  462. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
  463. package/src/runtime/migrations/vbundle-builder.ts +24 -1
  464. package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
  465. package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
  466. package/src/runtime/migrations/vbundle-importer.ts +4 -1
  467. package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
  468. package/src/runtime/migrations/vbundle-validator.ts +1 -0
  469. package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
  470. package/src/runtime/pending-interactions.ts +29 -0
  471. package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
  472. package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
  473. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
  474. package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
  475. package/src/runtime/routes/browser-context.ts +31 -11
  476. package/src/runtime/routes/browser-routes.ts +43 -4
  477. package/src/runtime/routes/btw-routes.ts +9 -4
  478. package/src/runtime/routes/client-routes.ts +108 -0
  479. package/src/runtime/routes/conversation-management-routes.ts +4 -1
  480. package/src/runtime/routes/conversation-routes.ts +113 -27
  481. package/src/runtime/routes/credential-prompt-routes.ts +4 -3
  482. package/src/runtime/routes/credential-routes.ts +1 -2
  483. package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
  484. package/src/runtime/routes/desktop-setup-routes.ts +4 -6
  485. package/src/runtime/routes/documents-routes.ts +51 -0
  486. package/src/runtime/routes/email-routes.ts +3 -7
  487. package/src/runtime/routes/events-routes.ts +75 -54
  488. package/src/runtime/routes/identity-routes.ts +1 -5
  489. package/src/runtime/routes/image-generation-routes.ts +8 -14
  490. package/src/runtime/routes/inbound-message-handler.ts +35 -38
  491. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
  492. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
  493. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
  494. package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
  495. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
  496. package/src/runtime/routes/llm-context-normalization.ts +1 -7
  497. package/src/runtime/routes/migration-routes.ts +73 -2
  498. package/src/runtime/routes/notification-routes.ts +10 -0
  499. package/src/runtime/routes/oauth-providers.ts +7 -0
  500. package/src/runtime/routes/plugins-routes.ts +6 -0
  501. package/src/runtime/routes/roadmap-routes.ts +47 -8
  502. package/src/runtime/routes/secret-routes.ts +1 -1
  503. package/src/runtime/routes/settings-routes.ts +10 -4
  504. package/src/runtime/routes/watcher-routes.ts +1 -3
  505. package/src/runtime/trust-class.test.ts +7 -6
  506. package/src/runtime/trust-class.ts +9 -17
  507. package/src/schedule/__tests__/worker-control.test.ts +36 -5
  508. package/src/schedule/worker-control.ts +15 -5
  509. package/src/security/secure-keys.ts +53 -103
  510. package/src/skills/clawhub.ts +3 -8
  511. package/src/skills/managed-store.ts +1 -5
  512. package/src/stt/types.ts +4 -0
  513. package/src/subagent/notify.ts +54 -34
  514. package/src/subagent/parent-notification.ts +6 -0
  515. package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
  516. package/src/telemetry/phone-call-funnel.ts +144 -0
  517. package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
  518. package/src/tools/ask-question/ask-question-tool.ts +6 -4
  519. package/src/tools/browser/browser-execution.ts +22 -59
  520. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
  521. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
  522. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
  523. package/src/tools/client-os.ts +15 -0
  524. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
  525. package/src/tools/computer-use/target.ts +42 -0
  526. package/src/tools/document/document-tool.ts +13 -18
  527. package/src/tools/execution-target.ts +14 -16
  528. package/src/tools/host-terminal/host-shell.ts +4 -1
  529. package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
  530. package/src/tools/network/__tests__/web-search.test.ts +203 -0
  531. package/src/tools/network/web-fetch.ts +274 -7
  532. package/src/tools/network/web-search.ts +468 -6
  533. package/src/tools/permission-checker.ts +3 -3
  534. package/src/tools/policy-context.ts +3 -1
  535. package/src/tools/skills/load.ts +2 -1
  536. package/src/tools/skills/sandbox-runner.ts +11 -2
  537. package/src/tools/skills/skill-script-runner.ts +7 -0
  538. package/src/tools/skills/skill-tool-factory.ts +15 -1
  539. package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
  540. package/src/tools/terminal/safe-env.ts +4 -0
  541. package/src/tools/terminal/shell.ts +2 -0
  542. package/src/tools/tool-approval-handler.ts +5 -1
  543. package/src/tools/tool-types.ts +3 -3
  544. package/src/tools/types.ts +8 -2
  545. package/src/util/__tests__/worker-process-command.test.ts +5 -4
  546. package/src/util/abort-reasons.ts +18 -26
  547. package/src/util/canonicalize-identity.test.ts +21 -0
  548. package/src/util/canonicalize-identity.ts +16 -0
  549. package/src/util/errors.ts +13 -3
  550. package/src/util/provider-error-patterns.ts +3 -0
  551. package/src/util/worker-process.ts +77 -35
  552. package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
  553. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
  554. package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
  555. package/src/workspace/migrations/registry.ts +2 -0
  556. package/src/__tests__/computer-use-window-schema.test.ts +0 -24
  557. package/src/api/events/heartbeat-alert.ts +0 -20
  558. package/src/daemon/message-types/web-activity.ts +0 -65
  559. package/src/desktop/desktop-dependencies.test.ts +0 -131
  560. package/src/tools/computer-use/definitions.ts +0 -607
@@ -1,4 +1,7 @@
1
+ import { join, resolve } from "node:path";
2
+
1
3
  import type { SkillToolEntry } from "../../config/skills.js";
4
+ import { getBundledSkillsDir } from "../../config/skills.js";
2
5
  import { RiskLevel } from "../../permissions/types.js";
3
6
  import {
4
7
  coerceArrayShapes,
@@ -6,6 +9,7 @@ import {
6
9
  coerceStringNumbers,
7
10
  validateInputAgainstSchema,
8
11
  } from "../../skills/validate-input.js";
12
+ import { computerUseExecutionTarget } from "../computer-use/target.js";
9
13
  import { withActivityProperty } from "../schema-transforms.js";
10
14
  import { bundledToolInputMisuseMessage } from "../shared/input-misuse.js";
11
15
  import { bundledToolInputRepairs } from "../shared/input-repairs.js";
@@ -32,13 +36,21 @@ export function createSkillTool(
32
36
  skillDir: string,
33
37
  versionHash: string,
34
38
  bundled?: boolean,
39
+ pluginOwner?: string,
35
40
  ): Tool {
41
+ const isComputerUse =
42
+ bundled &&
43
+ !pluginOwner &&
44
+ resolve(skillDir) === resolve(join(getBundledSkillsDir(), "computer-use"));
36
45
  return {
37
46
  name: entry.name,
38
47
  description: entry.description,
39
48
  category: entry.category,
40
49
  defaultRiskLevel: riskMap[entry.risk],
41
50
  executionTarget: entry.execution_target as ExecutionTarget,
51
+ ...(isComputerUse
52
+ ? { getExecutionTarget: computerUseExecutionTarget }
53
+ : {}),
42
54
  supportedClientOs: entry.supported_client_os,
43
55
 
44
56
  input_schema: entry.input_schema as object,
@@ -96,6 +108,7 @@ export function createSkillTool(
96
108
  target: entry.execution_target,
97
109
  expectedSkillVersionHash: versionHash,
98
110
  bundled,
111
+ pluginOwner,
99
112
  },
100
113
  );
101
114
  },
@@ -113,8 +126,9 @@ export function createSkillToolsFromManifest(
113
126
  skillDir: string,
114
127
  versionHash: string,
115
128
  bundled?: boolean,
129
+ pluginOwner?: string,
116
130
  ): Tool[] {
117
131
  return entries.map((entry) =>
118
- createSkillTool(entry, skillDir, versionHash, bundled),
132
+ createSkillTool(entry, skillDir, versionHash, bundled, pluginOwner),
119
133
  );
120
134
  }
@@ -50,6 +50,17 @@ describe("safe-env Qdrant forwarding", () => {
50
50
  });
51
51
  });
52
52
 
53
+ describe("safe-env plugin name stripping", () => {
54
+ test("does not forward a parent-supplied VELLUM_PLUGIN_NAME", () => {
55
+ expect(SAFE_ENV_VARS).not.toContain("VELLUM_PLUGIN_NAME");
56
+
57
+ const env = buildSanitizedEnv("linux", {
58
+ VELLUM_PLUGIN_NAME: "forged-plugin",
59
+ });
60
+ expect(env.VELLUM_PLUGIN_NAME).toBeUndefined();
61
+ });
62
+ });
63
+
53
64
  describe("safe-env CES child forwarding", () => {
54
65
  test("forwards CES IPC discovery env and CES HTTP credentials", () => {
55
66
  expect(SAFE_ENV_VARS).toContain("CES_BOOTSTRAP_SOCKET_DIR");
@@ -86,6 +86,10 @@ export const SAFE_ENV_VARS = [
86
86
  "VELLUM_MINIKUBE_STORAGE_SIZE",
87
87
  "VELLUM_BACKUP_DIR",
88
88
  "VELLUM_BACKUP_KEY_PATH",
89
+ // VELLUM_PLUGIN_NAME is intentionally absent. The daemon injects the
90
+ // owning plugin install name after sanitizing the child environment.
91
+ // Inheriting a parent-supplied value would let an arbitrary process
92
+ // claim another plugin's credential scope.
89
93
  ] as const;
90
94
 
91
95
  export const WINDOWS_SAFE_ENV_VARS = [
@@ -5,6 +5,7 @@ import { z } from "zod";
5
5
  import type { BackgroundToolCompletedEvent } from "../../api/events/background-tool-completed.js";
6
6
  import { getConfig } from "../../config/loader.js";
7
7
  import { RiskLevel } from "../../permissions/types.js";
8
+ import { applyActivePluginName } from "../../plugins/active-plugin-env.js";
8
9
  import { wakeAgentForOpportunity } from "../../runtime/agent-wake.js";
9
10
  import { broadcastMessage } from "../../runtime/assistant-event-hub.js";
10
11
  import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
@@ -290,6 +291,7 @@ export const shellTool = {
290
291
 
291
292
  const env = buildSanitizedEnv();
292
293
  env.__CONVERSATION_ID = context.conversationId;
294
+ applyActivePluginName(env, context.conversationId);
293
295
  // Secret binding for reveal-derived chat authority — see reveal-nonce.ts.
294
296
  env.__REVEAL_NONCE = conversationRevealNonce(context.conversationId);
295
297
  // Surface the resolving model to assistant CLI commands so they can tailor
@@ -837,7 +837,11 @@ export class ToolApprovalHandler {
837
837
  // sandbox/host routing reflects the tool actually registered under this
838
838
  // name at execution time.
839
839
  const tool = getTool(name);
840
- const executionTarget = resolveExecutionTarget(tool ?? { name });
840
+ const executionTarget = resolveExecutionTarget(
841
+ tool ?? { name },
842
+ input,
843
+ context,
844
+ );
841
845
 
842
846
  // Determine whether this invocation requires a scoped grant. Capture
843
847
  // the consume params now but defer the actual atomic consumption until
@@ -279,9 +279,9 @@ export interface ToolContext {
279
279
  principal?: string;
280
280
  /**
281
281
  * Trust classification of the actor who initiated this tool invocation.
282
- * Broadened to `string` here; the assistant narrows this back to its
283
- * concrete `TrustClass` union ("guardian" | "trusted_contact" | "unknown")
284
- * in `assistant/src/tools/types.ts`.
282
+ * Broadened to `string` here; the assistant narrows this back to
283
+ * `TrustClass` (declared in `@vellumai/gateway-client`) in
284
+ * `assistant/src/tools/types.ts`.
285
285
  */
286
286
  trustClass: string;
287
287
  /** Channel through which the tool invocation originates (e.g. 'telegram', 'phone'). Used for scoped grant consumption. */
@@ -6,6 +6,7 @@ import type {
6
6
  import { z } from "zod";
7
7
 
8
8
  import type { AnsweredQuestion } from "../api/events/question-answered.js";
9
+ import type { ToolActivityMetadata } from "../api/events/tool-result.js";
9
10
  import {
10
11
  CLIENT_OS_VALUES,
11
12
  type ClientOs,
@@ -13,7 +14,6 @@ import {
13
14
  } from "../channels/types.js";
14
15
  import type { LLMCallSite } from "../config/schemas/llm.js";
15
16
  import { SEND_USER_MESSAGE_TOOL_NAME } from "../config/send-user-message-constants.js";
16
- import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
17
17
  import type { SecretPromptResult } from "../permissions/secret-prompt-types.js";
18
18
  import type { ContentBlock } from "../providers/types.js";
19
19
  import type { TrustClass } from "../runtime/trust-class.js";
@@ -605,7 +605,13 @@ export type ToolDefinition = z.infer<typeof ToolDefinitionSchema>;
605
605
  export type Tool = Required<
606
606
  Omit<ToolDefinition, "exclusive" | "supportedClientOs">
607
607
  > &
608
- Pick<ToolDefinition, "exclusive" | "supportedClientOs">;
608
+ Pick<ToolDefinition, "exclusive" | "supportedClientOs"> & {
609
+ /** Invocation-specific boundary for trusted tool implementations. */
610
+ getExecutionTarget?: (
611
+ input: Record<string, unknown>,
612
+ context?: ToolContext,
613
+ ) => "sandbox" | "host";
614
+ };
609
615
 
610
616
  /**
611
617
  * The kind of entity that owns a tool. `"default"` is the built-in tool set
@@ -205,11 +205,12 @@ describe("decideWorkerSlot", () => {
205
205
  ).toEqual({ action: "adopt", pid: WORKER });
206
206
  });
207
207
 
208
- // The cases below are the ones that must never reach a kill.
208
+ // The cases below must never reach a kill. Unmatched command lines spawn
209
+ // a new worker without signalling the stranger holding the recycled PID.
209
210
  test("never signals an unrelated process on a recycled PID", () => {
210
211
  expect(
211
212
  decide({ pid: WORKER, ppid: 1, command: "/usr/bin/postgres -D /data" }),
212
- ).toEqual({ action: "adopt", pid: WORKER });
213
+ ).toEqual({ action: "spawn" });
213
214
  });
214
215
 
215
216
  test("never signals another project's worker.ts on a recycled PID", () => {
@@ -219,7 +220,7 @@ describe("decideWorkerSlot", () => {
219
220
  ppid: 1,
220
221
  command: "bun run /home/dev/side-project/worker.ts",
221
222
  }),
222
- ).toEqual({ action: "adopt", pid: WORKER });
223
+ ).toEqual({ action: "spawn" });
223
224
  });
224
225
 
225
226
  test("never signals a different worker kind holding this slot", () => {
@@ -229,7 +230,7 @@ describe("decideWorkerSlot", () => {
229
230
  ppid: 1,
230
231
  command: "bun --smol run /app/runtime/0.10.11/src/monitoring/worker.ts",
231
232
  }),
232
- ).toEqual({ action: "adopt", pid: WORKER });
233
+ ).toEqual({ action: "spawn" });
233
234
  });
234
235
 
235
236
  test("never signals when the process table could not be read", () => {
@@ -78,49 +78,41 @@ export function isUserInterruptAbort(reason: AbortReason | undefined): boolean {
78
78
  */
79
79
  export const CANCELLED_TOOL_RESULT_TEXT = "Cancelled by user";
80
80
 
81
- /**
82
- * The instruction both interrupt annotations end on: answer the message that
83
- * interrupted, then decide what happens to the work it cut off.
84
- *
85
- * Shared so the two annotations cannot drift. Resuming the work inline is the
86
- * trap the last clause closes: the reply the user is waiting for arrives only
87
- * once the resumed work finishes, so an interrupt that was meant to get their
88
- * question answered first buys nothing. A subagent carries the work in
89
- * parallel and leaves the conversation free.
90
- */
91
- const INTERRUPT_PRIORITY_INSTRUCTION =
92
- "reply to it first, then decide whether to resume or abandon the work that was in progress, and if it is still wanted and too big to finish inline, hand it to a subagent so it continues in parallel instead of holding up the conversation. This is the normal way a conversation flows, not a problem: do not apologize, do not mention that anything was stopped or interrupted, and do not ask the user to wait.";
93
-
94
81
  /**
95
82
  * Synthetic `tool_result` text for a tool call a newly arrived user message
96
83
  * cut off.
97
84
  *
85
+ * States the fact about this one call and nothing else. What the model should
86
+ * do about the interruption belongs on the interrupting message
87
+ * ({@link INTERRUPTED_TURN_NOTE_TEXT}), which is read once no matter how many
88
+ * calls the abort stopped and which sits after the user's words rather than
89
+ * before them.
90
+ *
98
91
  * Distinct from {@link CANCELLED_TOOL_RESULT_TEXT} because the model's next
99
92
  * decision is different. A plain cancel ends the work; a preemption means a
100
- * message is waiting, the abandoned call may well have taken effect on the
101
- * outside world before the abort landed, and the model has to answer the new
102
- * message before it decides what to do about the work it was doing.
93
+ * message is waiting, and the abandoned call may well have taken effect on the
94
+ * outside world before the abort landed.
103
95
  */
104
- export const PREEMPTED_TOOL_RESULT_TEXT = `A new message from the user arrived, so this tool call was stopped early. It may still have completed; check before repeating it. Treat the new message as the priority: ${INTERRUPT_PRIORITY_INSTRUCTION}`;
96
+ export const PREEMPTED_TOOL_RESULT_TEXT =
97
+ "Stopped early because the user sent a new message. This call may have completed anyway; check before repeating it.";
105
98
 
106
99
  /**
107
100
  * Annotation appended to the LLM-facing content of the user message that
108
- * interrupted a turn, for the interrupt that landed with no tool call in
109
- * flight.
101
+ * interrupted a turn.
110
102
  *
111
- * A preemption caught mid-tool tells the model what happened through the
112
- * synthetic {@link PREEMPTED_TOOL_RESULT_TEXT} result. An abort that lands
113
- * during the provider call has no `tool_use` to answer, so the history the
114
- * next turn reads is the abandoned turn followed by this message, with nothing
115
- * saying the plan behind it was cut off. Without the note the model answers
116
- * the new message and drops the work it had just committed to.
103
+ * The one place the behavior after an interrupt is spelled out, and it rides
104
+ * on every interrupt, whether or not a tool call was stopped. Leading with the
105
+ * short reply is the point: the user is waiting to learn they were heard, and
106
+ * a turn that opens with more reasoning or another tool call leaves them
107
+ * staring at silence.
117
108
  *
118
109
  * Tagged rather than written as prose so it reads as a system annotation on
119
110
  * the message instead of something the user typed. It rides on the LLM-facing
120
111
  * content only: the persisted row stays exactly what the user sent, so no
121
112
  * client renders it.
122
113
  */
123
- export const INTERRUPTED_TURN_NOTE_TEXT = `<interrupted_turn>This message arrived while the previous turn was still thinking, before it had made any tool call, so that turn ended here. Treat this message as the priority: ${INTERRUPT_PRIORITY_INSTRUCTION}</interrupted_turn>`;
114
+ export const INTERRUPTED_TURN_NOTE_TEXT =
115
+ "<interrupted_turn>The user sent this while you were still working, so that work stopped. Reply right away, in a line or two, before any more thinking or tool calls, so the user knows you heard them. Then pick the earlier work back up only if it is still wanted. Never apologize for or mention the interruption.</interrupted_turn>";
124
116
 
125
117
  /**
126
118
  * The synthetic `tool_result` text that matches why the turn was aborted.
@@ -0,0 +1,21 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { inboundIdentitiesMatch } from "./canonicalize-identity.js";
4
+
5
+ describe("inboundIdentitiesMatch", () => {
6
+ test("matches ids that canonicalize to the same sender", () => {
7
+ expect(inboundIdentitiesMatch("slack", " U123 ", "U123")).toBe(true);
8
+ expect(
9
+ inboundIdentitiesMatch("email", "USER@example.com", "user@example.com"),
10
+ ).toBe(true);
11
+ });
12
+
13
+ test("distinct senders do not match", () => {
14
+ expect(inboundIdentitiesMatch("slack", "U123", "U456")).toBe(false);
15
+ });
16
+
17
+ test("an empty identifier matches nothing, not even another empty one", () => {
18
+ expect(inboundIdentitiesMatch("slack", "", "")).toBe(false);
19
+ expect(inboundIdentitiesMatch("slack", " ", "U123")).toBe(false);
20
+ });
21
+ });
@@ -55,3 +55,19 @@ export function canonicalizeInboundIdentity(
55
55
 
56
56
  return trimmed;
57
57
  }
58
+
59
+ /**
60
+ * Whether two raw identifiers name the same sender on `channel`. An empty
61
+ * identifier matches nothing.
62
+ */
63
+ export function inboundIdentitiesMatch(
64
+ channel: ChannelId,
65
+ left: string,
66
+ right: string,
67
+ ): boolean {
68
+ const canonicalLeft = canonicalizeInboundIdentity(channel, left);
69
+ return (
70
+ canonicalLeft !== null &&
71
+ canonicalLeft === canonicalizeInboundIdentity(channel, right)
72
+ );
73
+ }
@@ -151,11 +151,19 @@ export class ProviderError extends AssistantError {
151
151
  public readonly apiErrorParam?: string;
152
152
  public readonly requestId?: string;
153
153
  /**
154
- * Verbatim upstream non-2xx body (possibly truncated). Persisted so the
155
- * inspector's Raw tab can show the actual provider error payload, not just
156
- * the extracted fields.
154
+ * Verbatim upstream non-2xx response body (possibly truncated). This is
155
+ * what the provider returned after rejecting the call, not the request
156
+ * we sent. The inspector's Raw tab uses it when a 4xx/5xx body carries
157
+ * detail the extracted `apiError*` fields drop.
157
158
  */
158
159
  public readonly rawBody?: string;
160
+ /**
161
+ * Inspectable SDK/wire request payload for the call that failed. This is
162
+ * the outbound request, not the error response (`rawBody`). Provider
163
+ * `sendMessage` throws attach it so inspector rows show the same request
164
+ * shape as a successful `usage` row.
165
+ */
166
+ public readonly rawRequest?: unknown;
159
167
  /**
160
168
  * Tagged daemon-owned abort reason carried over from the AbortSignal that
161
169
  * triggered this error. Untyped here to avoid a daemon→util import cycle;
@@ -181,6 +189,7 @@ export class ProviderError extends AssistantError {
181
189
  apiErrorParam?: string;
182
190
  requestId?: string;
183
191
  rawBody?: string;
192
+ rawRequest?: unknown;
184
193
  reason?: ProviderErrorReason;
185
194
  },
186
195
  ) {
@@ -192,6 +201,7 @@ export class ProviderError extends AssistantError {
192
201
  this.apiErrorParam = options?.apiErrorParam;
193
202
  this.requestId = options?.requestId;
194
203
  this.rawBody = options?.rawBody;
204
+ this.rawRequest = options?.rawRequest;
195
205
  this.abortReason = options?.abortReason;
196
206
  this.reason = options?.reason;
197
207
  }
@@ -33,6 +33,9 @@ export function isVisionNotSupportedError(message: string): boolean {
33
33
  export const CHAT_TEMPLATE_FAILURE_PATTERNS = [
34
34
  /appl\w* (?:the )?chat[ _-]?template/i,
35
35
  /chat[ _-]?template (?:error|render)/i,
36
+ // Fireworks surfaces the renderer's raw Python str.join TypeError without
37
+ // naming the template: "sequence item 0: expected str instance, list found".
38
+ /sequence item \d+: expected str instance, list found/i,
36
39
  ];
37
40
 
38
41
  /**
@@ -50,13 +50,11 @@ function isEsrchError(err: unknown): boolean {
50
50
  );
51
51
  }
52
52
 
53
- /**
54
- * Read a PID file and report liveness. A missing or malformed file reports
55
- * not_running; a file pointing at a dead process is cleaned up and reported as
56
- * not_running. Intended for worker-process PID files whose PID is a normal
57
- * spawned child (never PID 1), so `process.kill(pid, 0)` liveness is reliable.
58
- */
59
- export function probeWorkerPidFile(path: string): WorkerProcessStatus {
53
+ /** Kill-0 plus command-line identity for a PID file. */
54
+ function probePidFile(
55
+ path: string,
56
+ signature: readonly string[],
57
+ ): WorkerProcessStatus {
60
58
  if (!existsSync(path)) {
61
59
  return { status: "not_running" };
62
60
  }
@@ -69,22 +67,49 @@ export function probeWorkerPidFile(path: string): WorkerProcessStatus {
69
67
 
70
68
  try {
71
69
  process.kill(pid, 0);
72
- return { status: "running", pid };
73
70
  } catch (err: unknown) {
74
71
  if (isEsrchError(err)) {
75
- // Stale file — clean it up.
76
- try {
77
- unlinkSync(path);
78
- } catch {
79
- // best-effort
80
- }
72
+ unlinkPidFileIfNames(path, pid);
81
73
  return { status: "not_running" };
82
74
  }
83
75
  // Any other error (e.g. EPERM: the process exists but this caller may not
84
- // signal it) means the process is alive. Report it running rather than
85
- // letting the error escape a status probe.
86
- return { status: "running", pid };
76
+ // signal it) means the process is alive. Fall through to the identity
77
+ // check rather than letting the error escape a status probe.
78
+ }
79
+
80
+ const fate = classifyOrphanAfterWait(
81
+ true,
82
+ readRawProcessCommand(pid),
83
+ signature,
84
+ );
85
+ if (fate === "gone") {
86
+ log.info(
87
+ { pid, pidPath: path },
88
+ "Worker PID file names a live process this runtime does not recognise as its worker; releasing the slot rather than treating it as running",
89
+ );
90
+ unlinkPidFileIfNames(path, pid);
91
+ return { status: "not_running" };
87
92
  }
93
+ return { status: "running", pid };
94
+ }
95
+
96
+ /**
97
+ * Read a PID file and report whether this worker is actually running there.
98
+ *
99
+ * Identity comes from the same `entry` / `packagedEntry` spawn uses. A missing
100
+ * or malformed file reports not_running. A file pointing at a dead process, or
101
+ * at a live process whose command line is not this worker (a recycled PID
102
+ * after a container restart), is cleaned up and reported as not_running. A
103
+ * live PID whose command line cannot be read is reported running: uncertainty
104
+ * must not start a second worker next to a maybe-live one, and must not be
105
+ * treated as a license to signal a stranger.
106
+ */
107
+ export function probeWorkerPidFile(
108
+ pidPath: string,
109
+ entry: URL,
110
+ packagedEntry?: PackagedWorkerEntry,
111
+ ): WorkerProcessStatus {
112
+ return probePidFile(pidPath, workerKindSignature(entry, packagedEntry));
88
113
  }
89
114
 
90
115
  /** Thrown when a worker process fails to come up within the wait window. */
@@ -394,11 +419,13 @@ async function stopOrphanedWorker(
394
419
  /**
395
420
  * What to do about the process a worker's PID file currently names.
396
421
  *
397
- * - `adopt`: reuse it. Either it is this process's own worker, or it belongs
398
- * to another live owner, or it is not recognisably one of our workers at
399
- * all and must never be signalled.
422
+ * - `adopt`: reuse it. It is this process's own worker, it belongs to
423
+ * another live owner, or its identity could not be read so a second
424
+ * worker must not be started next to a maybe-live one.
400
425
  * - `reclaim`: an orphan left by an owner that is gone. Stop it, then spawn.
401
- * - `spawn`: nothing is holding the slot.
426
+ * - `spawn`: nothing is holding the slot, including a live PID whose
427
+ * command line is not this worker (a recycled PID). Never signal that
428
+ * process.
402
429
  */
403
430
  export type WorkerSlotDecision =
404
431
  | { action: "adopt"; pid: number }
@@ -410,9 +437,12 @@ export type WorkerSlotDecision =
410
437
  *
411
438
  * `reclaim` is the only outcome that signals a process, and it requires two
412
439
  * independent things to agree: the command line marks the process as this
413
- * worker, and parentage says no daemon owns it. A missing row, an unreadable
414
- * command line, and a command line that does not match all fall back to
415
- * `adopt`, so an uncertain answer never costs a process its life.
440
+ * worker, and parentage says no daemon owns it. A missing row and an
441
+ * unreadable command line fall back to `adopt`, so an uncertain answer never
442
+ * costs a process its life and never starts a duplicate. A command line that
443
+ * does not match is `spawn` without a signal: the PID file is stale, and
444
+ * treating that PID as the worker would leave the slot occupied by a
445
+ * stranger forever.
416
446
  *
417
447
  * `isOwnerAlive` is the caller's definition of a legitimate owner. These
418
448
  * workers have exactly one, the daemon, so passing a plain liveness probe
@@ -435,7 +465,7 @@ export function decideWorkerSlot(
435
465
  return { action: "adopt", pid: status.pid };
436
466
  }
437
467
  if (!matchesSignature(row.command, signature)) {
438
- return { action: "adopt", pid: status.pid };
468
+ return { action: "spawn" };
439
469
  }
440
470
  const ownership = classifyWorkerOwnership(
441
471
  row,
@@ -458,7 +488,7 @@ function inspectWorkerSlot(
458
488
  pidPath: string,
459
489
  signature: readonly string[],
460
490
  ): WorkerSlotDecision {
461
- const status = probeWorkerPidFile(pidPath);
491
+ const status = probePidFile(pidPath, signature);
462
492
  if (status.status !== "running" || status.pid == null) {
463
493
  return { action: "spawn" };
464
494
  }
@@ -479,11 +509,11 @@ function inspectWorkerSlot(
479
509
  if (row && !matchesSignature(row.command, signature)) {
480
510
  log.info(
481
511
  { pid: row.pid, pidPath },
482
- "Worker PID file names a live process this runtime does not recognise as its worker; reusing it rather than signalling it",
512
+ "Worker PID file names a live process this runtime does not recognise as its worker; releasing the slot rather than treating it as running",
483
513
  );
484
514
  }
485
515
 
486
- return decideWorkerSlot(
516
+ const decision = decideWorkerSlot(
487
517
  status,
488
518
  row,
489
519
  signature,
@@ -491,6 +521,12 @@ function inspectWorkerSlot(
491
521
  (pid) => isDaemonCommand(commandOf(pid)),
492
522
  pid1OwnsWorkers(commandOf(1)),
493
523
  );
524
+ // A spawn decision while the PID file still names a live stranger would
525
+ // make waitForWorkerPidFile treat that stale file as readiness.
526
+ if (decision.action === "spawn") {
527
+ unlinkPidFileIfNames(pidPath, status.pid);
528
+ }
529
+ return decision;
494
530
  }
495
531
 
496
532
  /**
@@ -508,7 +544,7 @@ async function reclaimWorkerSlot(
508
544
  // Still running and beyond our reach. One stale worker beats two live ones.
509
545
  return pid;
510
546
  }
511
- const replacement = probeWorkerPidFile(pidPath);
547
+ const replacement = probePidFile(pidPath, signature);
512
548
  return replacement.status === "running" && replacement.pid != null
513
549
  ? replacement.pid
514
550
  : null;
@@ -519,7 +555,8 @@ async function reclaimWorkerSlot(
519
555
  * process, and wait for it to report readiness by writing its PID file. The
520
556
  * child is `unref`'d, so the spawning process never blocks on it.
521
557
  *
522
- * If a worker is already running (per the PID file), returns its PID with
558
+ * If a worker is already running (the PID file names a live process whose
559
+ * command line matches this worker), returns its PID with
523
560
  * `alreadyRunning: true` rather than spawning a second one. Throws
524
561
  * {@link WorkerProcessSpawnError} if the child crashes during startup or
525
562
  * never writes its PID file within the wait window.
@@ -633,14 +670,19 @@ export async function spawnWorkerProcess(args: {
633
670
 
634
671
  /**
635
672
  * Send SIGTERM to the worker process behind `pidPath` if it is actually
636
- * running.
673
+ * this worker.
637
674
  *
638
675
  * Returns the status observed before signalling, so callers can report
639
- * whether anything was stopped. Only throws if `process.kill` itself fails
640
- * (e.g. EPERM) — a not-running worker is a no-op.
676
+ * whether anything was stopped. A PID file that names a dead process or a
677
+ * live process that is not this worker is a no-op. Only throws if
678
+ * `process.kill` itself fails (e.g. EPERM).
641
679
  */
642
- export function stopWorkerProcess(pidPath: string): WorkerProcessStatus {
643
- const current = probeWorkerPidFile(pidPath);
680
+ export function stopWorkerProcess(
681
+ pidPath: string,
682
+ entry: URL,
683
+ packagedEntry?: PackagedWorkerEntry,
684
+ ): WorkerProcessStatus {
685
+ const current = probeWorkerPidFile(pidPath, entry, packagedEntry);
644
686
  if (current.status === "running" && current.pid != null) {
645
687
  process.kill(current.pid, "SIGTERM");
646
688
  }
@@ -0,0 +1,94 @@
1
+ import {
2
+ linkSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ renameSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { pathToFileURL } from "node:url";
11
+
12
+ import type { WorkspaceMigration } from "./types.js";
13
+
14
+ export const addDesktopFilesLauncherMigration: WorkspaceMigration = {
15
+ id: "157-add-desktop-files-launcher",
16
+ description: "Add Files to existing virtual desktop docks",
17
+ retryFailedCheckpoint: true,
18
+ run(workspaceDir): void {
19
+ const configDir = join(workspaceDir, "data", "desktop-panel");
20
+ const settingsPath = join(configDir, "glib-2.0", "settings", "keyfile");
21
+ let settings: string;
22
+ try {
23
+ settings = readFileSync(settingsPath, "utf8");
24
+ } catch (err) {
25
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
26
+ return;
27
+ }
28
+ throw err;
29
+ }
30
+
31
+ const section =
32
+ /(?:^|\n)\[net\/launchpad\/plank\/docks\/dock1\]\r?\n([^]*?)(?=\n\[|$)/;
33
+ const match = settings.match(section);
34
+ if (!match) {
35
+ return;
36
+ }
37
+ const pins = /^(dock-items\s*=\s*)(?:@as\s+)?\[([^\r\n]*)\](\r?)$/m;
38
+ const existing = match[1]!.match(pins);
39
+ if (existing && !existing[2]!.includes("'files.dockitem'")) {
40
+ const items = existing[2]!.trim();
41
+ let updatedItems: string;
42
+ if (items.includes("'chrome.dockitem'")) {
43
+ updatedItems = items.replace(
44
+ "'chrome.dockitem'",
45
+ "'chrome.dockitem', 'files.dockitem'",
46
+ );
47
+ } else if (items.includes("'terminal.dockitem'")) {
48
+ updatedItems = items.replace(
49
+ "'terminal.dockitem'",
50
+ "'files.dockitem', 'terminal.dockitem'",
51
+ );
52
+ } else {
53
+ updatedItems = `${items}${items ? ", " : ""}'files.dockitem'`;
54
+ }
55
+ const updated = match[0].replace(
56
+ pins,
57
+ () => `${existing[1]}[${updatedItems}]${existing[3]}`,
58
+ );
59
+ settings = settings.replace(section, () => updated);
60
+ } else if (!existing) {
61
+ settings = settings.replace(
62
+ section,
63
+ () =>
64
+ `${match[0]}${match[0].endsWith("\n") ? "" : "\n"}dock-items=['files.dockitem']\n`,
65
+ );
66
+ }
67
+
68
+ const launchersDir = join(configDir, "plank", "dock1", "launchers");
69
+ mkdirSync(launchersDir, { recursive: true });
70
+ const launcher = pathToFileURL(
71
+ join(configDir, "applications", "thunar.desktop"),
72
+ ).href;
73
+ const pinPath = join(launchersDir, "files.dockitem");
74
+ const pinTemporaryPath = `${pinPath}.migration.tmp`;
75
+ try {
76
+ writeFileSync(
77
+ pinTemporaryPath,
78
+ `[PlankDockItemPreferences]\nLauncher=${launcher}\n`,
79
+ { flush: true },
80
+ );
81
+ linkSync(pinTemporaryPath, pinPath);
82
+ } catch (err) {
83
+ if ((err as NodeJS.ErrnoException).code !== "EEXIST") {
84
+ throw err;
85
+ }
86
+ } finally {
87
+ rmSync(pinTemporaryPath, { force: true });
88
+ }
89
+ const temporaryPath = `${settingsPath}.files-migration.tmp`;
90
+ writeFileSync(temporaryPath, settings, { flush: true });
91
+ renameSync(temporaryPath, settingsPath);
92
+ },
93
+ down(): void {},
94
+ };