@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,8 +1,8 @@
1
- import { getConfig } from "../../config/loader.js";
2
1
  import type {
3
2
  WebSearchMetadata,
4
3
  WebSearchResultItem,
5
- } from "../../daemon/message-types/web-activity.js";
4
+ } from "../../api/events/tool-result.js";
5
+ import { getConfig } from "../../config/loader.js";
6
6
  import { RiskLevel } from "../../permissions/types.js";
7
7
  import { getProviderKeyAsync } from "../../security/secure-keys.js";
8
8
  import { wrapUntrustedContent } from "../../security/untrusted-content.js";
@@ -44,6 +44,7 @@ const PERPLEXITY_API_URL = "https://api.perplexity.ai/chat/completions";
44
44
  const TAVILY_API_URL = "https://api.tavily.com/search";
45
45
  const FIRECRAWL_API_URL = "https://api.firecrawl.dev/v2/search";
46
46
  const FASTCRW_SEARCH_PATH = "/v1/search";
47
+ const TINYFISH_DEFAULT_SEARCH_API_BASE = "https://api.search.tinyfish.ai";
47
48
  // Keenable is keyless by default: the public path needs no key (rate-limited);
48
49
  // a key switches to the authenticated path and lifts the cap.
49
50
  const KEENABLE_API_BASE_URL = "https://api.keenable.ai";
@@ -56,7 +57,9 @@ type WebSearchProvider =
56
57
  | "tavily"
57
58
  | "firecrawl"
58
59
  | "keenable"
59
- | "fastcrw";
60
+ | "fastcrw"
61
+ | "searxng"
62
+ | "tinyfish";
60
63
 
61
64
  /**
62
65
  * Arguments passed to every {@link WebSearchAdapter}. The full superset is
@@ -167,6 +170,38 @@ interface KeenableSearchResponse {
167
170
  results?: KeenableSearchResult[];
168
171
  }
169
172
 
173
+ interface SearxngSearchResult {
174
+ title?: string;
175
+ url?: string;
176
+ content?: string;
177
+ }
178
+
179
+ interface SearxngSearchResponse {
180
+ query?: string;
181
+ results?: SearxngSearchResult[];
182
+ }
183
+
184
+ interface TinyfishSearchResult {
185
+ position?: number;
186
+ site_name?: string;
187
+ title?: string;
188
+ snippet?: string;
189
+ url?: string;
190
+ date?: string;
191
+ }
192
+
193
+ interface TinyfishSearchResponse {
194
+ query?: string;
195
+ results?: TinyfishSearchResult[];
196
+ total_results?: number;
197
+ page?: number;
198
+ }
199
+
200
+ const SEARXNG_MISSING_INSTANCE_MESSAGE =
201
+ "SearXNG needs an instance URL. Set API Base in Settings under Web Search.";
202
+ const SEARXNG_JSON_DISABLED_MESSAGE =
203
+ "This SearXNG instance did not return JSON. Enable the JSON format on the instance, or point API Base at an instance that allows it.";
204
+
170
205
  function getWebSearchProvider(): WebSearchProvider {
171
206
  const config = getConfig();
172
207
  const configured = config.services["web-search"].provider ?? "perplexity";
@@ -573,6 +608,159 @@ function buildKeenableMetadata(
573
608
  };
574
609
  }
575
610
 
611
+ function formatSearxngResults(
612
+ data: SearxngSearchResponse,
613
+ query: string,
614
+ ): string {
615
+ const results = data.results ?? [];
616
+ if (results.length === 0) {
617
+ return `No results found for "${query}".`;
618
+ }
619
+
620
+ const lines: string[] = [`Web search results for "${query}":\n`];
621
+ for (let i = 0; i < results.length; i++) {
622
+ const r = results[i];
623
+ const title = r.title?.trim() || r.url?.trim() || "Untitled result";
624
+ lines.push(`${i + 1}. ${title}`);
625
+ if (r.url) {
626
+ lines.push(` URL: ${r.url}`);
627
+ }
628
+ if (r.content) {
629
+ lines.push(` ${r.content}`);
630
+ }
631
+ lines.push("");
632
+ }
633
+ return lines.join("\n");
634
+ }
635
+
636
+ function buildSearxngMetadata(
637
+ data: SearxngSearchResponse,
638
+ query: string,
639
+ durationMs: number,
640
+ ): WebSearchMetadata {
641
+ const results = data.results ?? [];
642
+ const items: WebSearchResultItem[] = results.map((r, i) => {
643
+ const url = r.url ?? "";
644
+ const domain = extractDomain(url);
645
+ return {
646
+ rank: i + 1,
647
+ title: r.title?.trim() || url.trim() || "Untitled result",
648
+ url,
649
+ domain,
650
+ faviconUrl: faviconUrlForDomain(domain),
651
+ snippet: r.content?.trim() || undefined,
652
+ };
653
+ });
654
+ return {
655
+ query,
656
+ provider: "searxng",
657
+ resultCount: items.length,
658
+ durationMs,
659
+ results: items,
660
+ };
661
+ }
662
+
663
+ function formatTinyfishResults(
664
+ data: TinyfishSearchResponse,
665
+ query: string,
666
+ ): string {
667
+ const results = data.results ?? [];
668
+ if (results.length === 0) {
669
+ return `No results found for "${query}".`;
670
+ }
671
+
672
+ const lines: string[] = [`Web search results for "${query}":\n`];
673
+ for (let i = 0; i < results.length; i++) {
674
+ const result = results[i];
675
+ const title =
676
+ result.title?.trim() || result.url?.trim() || "Untitled result";
677
+ lines.push(`${i + 1}. ${title}`);
678
+ if (result.url) {
679
+ lines.push(` URL: ${result.url}`);
680
+ }
681
+ if (result.snippet) {
682
+ lines.push(` ${result.snippet}`);
683
+ }
684
+ if (result.date) {
685
+ lines.push(` Published: ${result.date}`);
686
+ }
687
+ lines.push("");
688
+ }
689
+ return lines.join("\n");
690
+ }
691
+
692
+ function buildTinyfishMetadata(
693
+ data: TinyfishSearchResponse,
694
+ query: string,
695
+ durationMs: number,
696
+ ): WebSearchMetadata {
697
+ const results = data.results ?? [];
698
+ const items: WebSearchResultItem[] = results.map((result, index) => {
699
+ const url = result.url ?? "";
700
+ const domain = extractDomain(url);
701
+ return {
702
+ rank: index + 1,
703
+ title: result.title?.trim() || url.trim() || "Untitled result",
704
+ url,
705
+ domain,
706
+ faviconUrl: faviconUrlForDomain(domain),
707
+ snippet: result.snippet?.trim() || undefined,
708
+ };
709
+ });
710
+ return {
711
+ query,
712
+ provider: "tinyfish",
713
+ resultCount: items.length,
714
+ durationMs,
715
+ results: items,
716
+ };
717
+ }
718
+
719
+ function tinyfishRecencyMinutesForFreshness(
720
+ freshness: string | undefined,
721
+ ): number | undefined {
722
+ switch (freshness) {
723
+ case "pd":
724
+ return 24 * 60;
725
+ case "pw":
726
+ return 7 * 24 * 60;
727
+ case "pm":
728
+ return 30 * 24 * 60;
729
+ case "py":
730
+ return 365 * 24 * 60;
731
+ default:
732
+ return undefined;
733
+ }
734
+ }
735
+
736
+ /**
737
+ * Map the tool's `freshness` window onto SearXNG's `time_range`
738
+ * (`day` / `month` / `year`). SearXNG has no week filter, so `pw` is omitted.
739
+ */
740
+ function searxngTimeRangeForFreshness(
741
+ freshness: string | undefined,
742
+ ): "day" | "month" | "year" | undefined {
743
+ switch (freshness) {
744
+ case "pd":
745
+ return "day";
746
+ case "pm":
747
+ return "month";
748
+ case "py":
749
+ return "year";
750
+ default:
751
+ return undefined;
752
+ }
753
+ }
754
+
755
+ function isNonJsonSearchBody(contentType: string, bodyText: string): boolean {
756
+ const type = contentType.toLowerCase();
757
+ if (type.includes("text/html") || type.includes("application/xhtml")) {
758
+ return true;
759
+ }
760
+ const trimmed = bodyText.trimStart();
761
+ return trimmed.startsWith("<");
762
+ }
763
+
576
764
  /**
577
765
  * Map the tool's `freshness` window onto a Keenable `published_after` date
578
766
  * (YYYY-MM-DD). Keenable filters by absolute publication date rather than a
@@ -1358,6 +1546,261 @@ async function executeKeenableSearch(
1358
1546
  );
1359
1547
  }
1360
1548
 
1549
+ async function executeSearxngSearch(
1550
+ query: string,
1551
+ count: number,
1552
+ freshness: string | undefined,
1553
+ apiKey: string,
1554
+ signal?: AbortSignal,
1555
+ ): Promise<ToolExecutionResult> {
1556
+ const startedAt = Date.now();
1557
+ const apiBase = getConfig().services["web-search"]?.apiBase?.trim() ?? "";
1558
+ if (apiBase.length === 0) {
1559
+ return errorResult(
1560
+ query,
1561
+ "searxng",
1562
+ startedAt,
1563
+ SEARXNG_MISSING_INSTANCE_MESSAGE,
1564
+ );
1565
+ }
1566
+
1567
+ const params = new URLSearchParams({
1568
+ q: query,
1569
+ format: "json",
1570
+ });
1571
+ const timeRange = searxngTimeRangeForFreshness(freshness);
1572
+ if (timeRange) {
1573
+ params.set("time_range", timeRange);
1574
+ }
1575
+ const url = `${resolveProviderApiUrl(apiBase, "/search", apiBase)}?${params.toString()}`;
1576
+
1577
+ const headers: Record<string, string> = {
1578
+ Accept: "application/json",
1579
+ };
1580
+ const trimmedKey = apiKey.trim();
1581
+ if (trimmedKey.length > 0) {
1582
+ headers.Authorization = `Bearer ${trimmedKey}`;
1583
+ }
1584
+
1585
+ for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
1586
+ let response: Response;
1587
+ try {
1588
+ response = await fetch(url, {
1589
+ headers,
1590
+ signal,
1591
+ });
1592
+ } catch (err) {
1593
+ return networkFailureResult(query, "searxng", startedAt, err, signal);
1594
+ }
1595
+
1596
+ const bodyText = await response.text();
1597
+ const contentType = response.headers.get("content-type") ?? "";
1598
+ if (isNonJsonSearchBody(contentType, bodyText)) {
1599
+ return errorResult(
1600
+ query,
1601
+ "searxng",
1602
+ startedAt,
1603
+ SEARXNG_JSON_DISABLED_MESSAGE,
1604
+ );
1605
+ }
1606
+
1607
+ if (response.ok) {
1608
+ let data: SearxngSearchResponse;
1609
+ try {
1610
+ data = JSON.parse(bodyText) as SearxngSearchResponse;
1611
+ } catch {
1612
+ return errorResult(
1613
+ query,
1614
+ "searxng",
1615
+ startedAt,
1616
+ SEARXNG_JSON_DISABLED_MESSAGE,
1617
+ );
1618
+ }
1619
+ if (data.results && data.results.length > count) {
1620
+ data.results = data.results.slice(0, count);
1621
+ }
1622
+ const durationMs = Date.now() - startedAt;
1623
+ return {
1624
+ content:
1625
+ wrapUntrustedContent(formatSearxngResults(data, query), {
1626
+ source: "search",
1627
+ sourceDetail: "searxng",
1628
+ }) + CITATION_INSTRUCTION,
1629
+ isError: false,
1630
+ activityMetadata: {
1631
+ webSearch: buildSearxngMetadata(data, query, durationMs),
1632
+ },
1633
+ };
1634
+ }
1635
+
1636
+ if (response.status === 401 || response.status === 403) {
1637
+ return errorResult(
1638
+ query,
1639
+ "searxng",
1640
+ startedAt,
1641
+ "SearXNG rejected the request. Check the instance URL, optional token, and that JSON search is enabled.",
1642
+ );
1643
+ }
1644
+
1645
+ if (response.status === 429 && attempt < DEFAULT_MAX_RETRIES) {
1646
+ const delayMs = getHttpRetryDelay(
1647
+ response,
1648
+ attempt,
1649
+ DEFAULT_BASE_DELAY_MS,
1650
+ );
1651
+ log.warn(
1652
+ { attempt: attempt + 1, delayMs },
1653
+ "SearXNG Search rate limited, retrying",
1654
+ );
1655
+ await sleep(delayMs);
1656
+ continue;
1657
+ }
1658
+
1659
+ log.warn({ status: response.status }, "SearXNG Search API error");
1660
+ return backendFailureResult(
1661
+ query,
1662
+ "searxng",
1663
+ startedAt,
1664
+ { statusCode: response.status, error: rawBodyDetail(bodyText) },
1665
+ response.status === 429
1666
+ ? "SearXNG Search rate limit exceeded after retries. Try again shortly."
1667
+ : `SearXNG Search API returned status ${response.status}`,
1668
+ );
1669
+ }
1670
+
1671
+ return backendFailureResult(
1672
+ query,
1673
+ "searxng",
1674
+ startedAt,
1675
+ { statusCode: 429 },
1676
+ "SearXNG Search rate limit exceeded after retries. Try again shortly.",
1677
+ );
1678
+ }
1679
+
1680
+ async function executeTinyfishSearch(
1681
+ query: string,
1682
+ count: number,
1683
+ freshness: string | undefined,
1684
+ apiKey: string,
1685
+ signal?: AbortSignal,
1686
+ ): Promise<ToolExecutionResult> {
1687
+ const startedAt = Date.now();
1688
+ const apiBase = getConfig().services["web-search"]?.apiBase;
1689
+ const endpoint = resolveProviderApiUrl(
1690
+ apiBase,
1691
+ "/",
1692
+ TINYFISH_DEFAULT_SEARCH_API_BASE,
1693
+ );
1694
+ const url = new URL(endpoint);
1695
+ url.searchParams.set("query", query);
1696
+ const recencyMinutes = tinyfishRecencyMinutesForFreshness(freshness);
1697
+ if (recencyMinutes !== undefined) {
1698
+ url.searchParams.set("recency_minutes", String(recencyMinutes));
1699
+ }
1700
+
1701
+ const headers = {
1702
+ Accept: "application/json",
1703
+ "X-API-Key": apiKey.trim(),
1704
+ };
1705
+
1706
+ for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
1707
+ let response: Response;
1708
+ try {
1709
+ response = await fetch(url.toString(), { headers, signal });
1710
+ } catch (err) {
1711
+ return networkFailureResult(query, "tinyfish", startedAt, err, signal);
1712
+ }
1713
+
1714
+ const bodyText = await response.text();
1715
+ if (response.ok) {
1716
+ let data: TinyfishSearchResponse;
1717
+ try {
1718
+ data = JSON.parse(bodyText) as TinyfishSearchResponse;
1719
+ } catch {
1720
+ return errorResult(
1721
+ query,
1722
+ "tinyfish",
1723
+ startedAt,
1724
+ "TinyFish Search returned an invalid JSON payload.",
1725
+ );
1726
+ }
1727
+ if (data.results && data.results.length > count) {
1728
+ data.results = data.results.slice(0, count);
1729
+ }
1730
+ const durationMs = Date.now() - startedAt;
1731
+ return {
1732
+ content:
1733
+ wrapUntrustedContent(formatTinyfishResults(data, query), {
1734
+ source: "search",
1735
+ sourceDetail: "tinyfish",
1736
+ }) + CITATION_INSTRUCTION,
1737
+ isError: false,
1738
+ activityMetadata: {
1739
+ webSearch: buildTinyfishMetadata(data, query, durationMs),
1740
+ },
1741
+ };
1742
+ }
1743
+
1744
+ if (response.status === 401) {
1745
+ return errorResult(
1746
+ query,
1747
+ "tinyfish",
1748
+ startedAt,
1749
+ "Invalid or expired TinyFish API key",
1750
+ );
1751
+ }
1752
+ if (response.status === 402) {
1753
+ return errorResult(
1754
+ query,
1755
+ "tinyfish",
1756
+ startedAt,
1757
+ "TinyFish Search API access is not enabled for this account.",
1758
+ );
1759
+ }
1760
+ if (response.status === 403) {
1761
+ return errorResult(
1762
+ query,
1763
+ "tinyfish",
1764
+ startedAt,
1765
+ "TinyFish Search request was forbidden by the upstream service.",
1766
+ );
1767
+ }
1768
+
1769
+ if (response.status === 429 && attempt < DEFAULT_MAX_RETRIES) {
1770
+ const delayMs = getHttpRetryDelay(
1771
+ response,
1772
+ attempt,
1773
+ DEFAULT_BASE_DELAY_MS,
1774
+ );
1775
+ log.warn(
1776
+ { attempt: attempt + 1, delayMs },
1777
+ "TinyFish Search rate limited, retrying",
1778
+ );
1779
+ await sleep(delayMs);
1780
+ continue;
1781
+ }
1782
+
1783
+ log.warn({ status: response.status }, "TinyFish Search API error");
1784
+ return backendFailureResult(
1785
+ query,
1786
+ "tinyfish",
1787
+ startedAt,
1788
+ { statusCode: response.status, error: rawBodyDetail(bodyText) },
1789
+ response.status === 429
1790
+ ? "TinyFish Search rate limit exceeded after retries. Try again shortly."
1791
+ : `TinyFish Search API returned status ${response.status}`,
1792
+ );
1793
+ }
1794
+
1795
+ return backendFailureResult(
1796
+ query,
1797
+ "tinyfish",
1798
+ startedAt,
1799
+ { statusCode: 429 },
1800
+ "TinyFish Search rate limit exceeded after retries. Try again shortly.",
1801
+ );
1802
+ }
1803
+
1361
1804
  // ----------------------------------------------------------------------------
1362
1805
  // Adapter registry
1363
1806
  //
@@ -1424,6 +1867,23 @@ const fastcrwSearchAdapter: WebSearchAdapter = {
1424
1867
  executeFastcrwSearch(query, count, freshness, apiKey, signal),
1425
1868
  };
1426
1869
 
1870
+ const searxngSearchAdapter: WebSearchAdapter = {
1871
+ id: "searxng",
1872
+ providerKeyName: "searxng",
1873
+ fallbackOrder: 7,
1874
+ keyless: true,
1875
+ execute: ({ query, count, freshness, apiKey, signal }) =>
1876
+ executeSearxngSearch(query, count, freshness, apiKey, signal),
1877
+ };
1878
+
1879
+ const tinyfishSearchAdapter: WebSearchAdapter = {
1880
+ id: "tinyfish",
1881
+ providerKeyName: "tinyfish",
1882
+ fallbackOrder: 8,
1883
+ execute: ({ query, count, freshness, apiKey, signal }) =>
1884
+ executeTinyfishSearch(query, count, freshness, apiKey, signal),
1885
+ };
1886
+
1427
1887
  /**
1428
1888
  * All built-in web-search adapters keyed by provider id. The
1429
1889
  * `Record<WebSearchProvider, ...>` shape forces TypeScript to flag any
@@ -1436,6 +1896,8 @@ const WEB_SEARCH_ADAPTERS: Record<WebSearchProvider, WebSearchAdapter> = {
1436
1896
  firecrawl: firecrawlSearchAdapter,
1437
1897
  keenable: keenableSearchAdapter,
1438
1898
  fastcrw: fastcrwSearchAdapter,
1899
+ searxng: searxngSearchAdapter,
1900
+ tinyfish: tinyfishSearchAdapter,
1439
1901
  };
1440
1902
 
1441
1903
  /**
@@ -1467,7 +1929,7 @@ export const webSearchTool = {
1467
1929
  count: {
1468
1930
  type: "number",
1469
1931
  description:
1470
- "Number of results to return (1-20, default 10). Used with Brave, Tavily, Firecrawl, Keenable, and fastCRW providers.",
1932
+ "Number of results to return (1-20, default 10). Used with Brave, Tavily, Firecrawl, Keenable, fastCRW, SearXNG, and TinyFish providers.",
1471
1933
  },
1472
1934
  offset: {
1473
1935
  type: "number",
@@ -1477,7 +1939,7 @@ export const webSearchTool = {
1477
1939
  freshness: {
1478
1940
  type: "string",
1479
1941
  description:
1480
- 'Filter by recency: "pd" (past day), "pw" (past week), "pm" (past month), "py" (past year). Used with Brave, Tavily, Firecrawl, Keenable, and fastCRW providers.',
1942
+ 'Filter by recency: "pd" (past day), "pw" (past week), "pm" (past month), "py" (past year). Used with Brave, Tavily, Firecrawl, Keenable, fastCRW, SearXNG, and TinyFish providers. SearXNG maps day/month/year and omits week.',
1481
1943
  },
1482
1944
  },
1483
1945
  required: ["query"],
@@ -1593,7 +2055,7 @@ export const webSearchTool = {
1593
2055
  query,
1594
2056
  provider,
1595
2057
  startedAt,
1596
- "No web search API key configured. Set it via `keys set perplexity <key>`, `keys set brave <key>`, `keys set tavily <key>`, `keys set firecrawl <key>`, or `keys set fastcrw <key>`, or configure it from the Settings page under API Keys. Or switch the web-search provider to Keenable, which works without a key.",
2058
+ "No web search API key configured. Set it via `keys set perplexity <key>`, `keys set brave <key>`, `keys set tavily <key>`, `keys set firecrawl <key>`, or `keys set fastcrw <key>`, or `keys set tinyfish <key>`, or configure it from the Settings page under API Keys. Or switch the web-search provider to Keenable, which works without a key, or SearXNG with your instance URL.",
1597
2059
  );
1598
2060
  }
1599
2061
  }
@@ -119,8 +119,8 @@ export class PermissionChecker {
119
119
  >,
120
120
  classification?: RiskClassificationWithMeta,
121
121
  ): Promise<PermissionDecision> {
122
- // Sandbox/host routing for the prompt comes from the tool's manifest.
123
- const executionTarget = resolveExecutionTarget(tool);
122
+ // Policy and prompts use the invocation's execution target.
123
+ const executionTarget = resolveExecutionTarget(tool, input, context);
124
124
  classification ??= await classifyRisk(
125
125
  name,
126
126
  input,
@@ -149,7 +149,7 @@ export class PermissionChecker {
149
149
  // this, the executor's catch block would fall back to the default
150
150
  // low risk, degrading audit/alert accuracy for high-risk attempts.
151
151
  try {
152
- const policyContext = buildPolicyContext(tool, context);
152
+ const policyContext = buildPolicyContext(tool, context, input);
153
153
  const result = await check(
154
154
  name,
155
155
  input,
@@ -3,6 +3,7 @@ import { isSkillImprovementActive } from "../config/memory-v3-gate.js";
3
3
  import type { ExecutionContext } from "../permissions/approval-policy.js";
4
4
  import type { ChannelPermissionCoordinates } from "../permissions/channel-permission-query.js";
5
5
  import type { PolicyContext } from "../permissions/types.js";
6
+ import { resolveExecutionTarget } from "./execution-target.js";
6
7
  import { getToolOwner } from "./registry.js";
7
8
  import type { Tool, ToolContext } from "./types.js";
8
9
 
@@ -48,6 +49,7 @@ function deriveExecutionContext(context?: ToolContext): ExecutionContext {
48
49
  export function buildPolicyContext(
49
50
  tool: Tool,
50
51
  context?: ToolContext,
52
+ input?: Record<string, unknown>,
51
53
  ): PolicyContext {
52
54
  const executionContext = deriveExecutionContext(context);
53
55
 
@@ -73,7 +75,7 @@ export function buildPolicyContext(
73
75
  const ownerKind = getToolOwner(tool.name)?.kind;
74
76
  if (ownerKind === "skill" || ownerKind === "plugin") {
75
77
  return {
76
- executionTarget: tool.executionTarget,
78
+ executionTarget: resolveExecutionTarget(tool, input, context),
77
79
  executionContext,
78
80
  conversationId,
79
81
  ...originSignals,
@@ -90,12 +90,13 @@ function formatToolSchemas(
90
90
  manifest: SkillToolManifest,
91
91
  context: Pick<
92
92
  ToolContext,
93
- "clientOs" | "transportInterface" | "sourceActorPrincipalId"
93
+ "clientOs" | "transportInterface" | "sourceActorPrincipalId" | "trustClass"
94
94
  >,
95
95
  childSkillName?: string,
96
96
  ): string | undefined {
97
97
  const tools = manifest.tools.filter((tool) =>
98
98
  supportsClientOsForSkillTool(tool.supported_client_os, tool.name, {
99
+ trustClass: context.trustClass,
99
100
  clientOs: context.clientOs,
100
101
  transportInterface: context.transportInterface,
101
102
  sourceActorPrincipalId: context.sourceActorPrincipalId,
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import { mkdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join, resolve } from "node:path";
5
5
 
6
+ import { PLUGIN_NAME_ENV } from "../../plugin-api/plugin-name-env.js";
6
7
  import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
7
8
  import { computeSkillVersionHash } from "../../skills/version-hash.js";
8
9
  import {
@@ -74,6 +75,7 @@ export async function runSkillToolScriptSandbox(
74
75
  timeoutMs?: number;
75
76
  expectedSkillVersionHash?: string;
76
77
  skillDirHashResolver?: (skillDir: string) => string;
78
+ pluginOwner?: string;
77
79
  },
78
80
  ): Promise<ToolExecutionResult> {
79
81
  const scriptPath = resolve(join(skillDir, executorPath));
@@ -117,7 +119,9 @@ export async function runSkillToolScriptSandbox(
117
119
  "utf-8",
118
120
  );
119
121
 
120
- return await spawnRunner(runDir, input, context, timeoutMs, executorPath);
122
+ return await spawnRunner(runDir, input, context, timeoutMs, executorPath, {
123
+ pluginOwner: options?.pluginOwner,
124
+ });
121
125
  } catch (err) {
122
126
  const message = err instanceof Error ? err.message : String(err);
123
127
  return {
@@ -139,6 +143,7 @@ function spawnRunner(
139
143
  context: ToolContext,
140
144
  timeoutMs: number,
141
145
  executorPath: string,
146
+ pluginContext?: { pluginOwner?: string },
142
147
  ): Promise<ToolExecutionResult> {
143
148
  return new Promise<ToolExecutionResult>((resolve) => {
144
149
  const stdoutChunks: Buffer[] = [];
@@ -156,9 +161,13 @@ function spawnRunner(
156
161
  conversationId: context.conversationId,
157
162
  });
158
163
  env.__CONVERSATION_ID = context.conversationId;
159
- // Secret binding for reveal-derived chat authority — see reveal-nonce.ts.
164
+ // Secret binding for reveal-derived chat authority. See reveal-nonce.ts.
160
165
  env.__REVEAL_NONCE = conversationRevealNonce(context.conversationId);
161
166
 
167
+ if (pluginContext?.pluginOwner) {
168
+ env[PLUGIN_NAME_ENV] = pluginContext.pluginOwner;
169
+ }
170
+
162
171
  const child = spawn(wrapped.command, wrapped.args, {
163
172
  cwd: runDir,
164
173
  env,
@@ -24,6 +24,12 @@ export interface RunSkillToolScriptOptions {
24
24
  * with `execution_target: host` (in-process); non-bundled skills are forced to
25
25
  * execute in the sandbox. */
26
26
  bundled?: boolean;
27
+ /**
28
+ * Catalog owner id when this skill is plugin-resident. The sandbox runner
29
+ * sets VELLUM_PLUGIN_NAME so the child can resolve credentials under
30
+ * that plugin's service.
31
+ */
32
+ pluginOwner?: string;
27
33
  }
28
34
 
29
35
  /**
@@ -42,6 +48,7 @@ export async function runSkillToolScript(
42
48
  timeoutMs: options.timeoutMs,
43
49
  expectedSkillVersionHash: options.expectedSkillVersionHash,
44
50
  skillDirHashResolver: options.skillDirHashResolver,
51
+ pluginOwner: options.pluginOwner,
45
52
  });
46
53
  }
47
54