@vellumai/assistant 0.12.5 → 0.12.6-dev.202609292116.41f7caa

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 (295) hide show
  1. package/.env.example +2 -0
  2. package/ARCHITECTURE.md +26 -3
  3. package/docs/automatic-done.md +32 -0
  4. package/docs/desktop-browser-cli.md +3 -2
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
  7. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
  9. package/node_modules/@vellumai/environments/src/shell.test.ts +13 -1
  10. package/node_modules/@vellumai/environments/src/shell.ts +22 -1
  11. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
  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/mcp-oauth.ts +60 -0
  15. package/node_modules/@vellumai/gateway-client/src/__tests__/status-contracts.test.ts +39 -0
  16. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +25 -0
  17. package/node_modules/@vellumai/service-contracts/package.json +2 -1
  18. package/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
  19. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  20. package/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
  21. package/openapi.yaml +270 -10
  22. package/package.json +1 -1
  23. package/src/__tests__/agent-loop.test.ts +76 -0
  24. package/src/__tests__/anthropic-provider.test.ts +2 -0
  25. package/src/__tests__/byok-default-profile-ensure.test.ts +7 -0
  26. package/src/__tests__/config-get-vision-flag.test.ts +1 -1
  27. package/src/__tests__/config-loader-validation-reset-notice.test.ts +2 -2
  28. package/src/__tests__/conversation-agent-loop-overflow.test.ts +160 -154
  29. package/src/__tests__/conversation-evictor.test.ts +54 -0
  30. package/src/__tests__/conversation-pairing.test.ts +112 -0
  31. package/src/__tests__/document-append-idempotency.test.ts +13 -0
  32. package/src/__tests__/document-create-dedupe.test.ts +13 -0
  33. package/src/__tests__/document-find-replace.test.ts +13 -0
  34. package/src/__tests__/document-revision-history.test.ts +390 -0
  35. package/src/__tests__/document-revision.test.ts +425 -0
  36. package/src/__tests__/document-tool-security.test.ts +13 -0
  37. package/src/__tests__/document-update-default-surface.test.ts +13 -0
  38. package/src/__tests__/emit-signal-routing-intent.test.ts +270 -0
  39. package/src/__tests__/gateway-flag-listener.test.ts +59 -6
  40. package/src/__tests__/headless-browser-navigate.test.ts +20 -30
  41. package/src/__tests__/host-cu-proxy.test.ts +79 -0
  42. package/src/__tests__/host-cu-routes-targeted.test.ts +14 -5
  43. package/src/__tests__/host-shell-tool.test.ts +6 -2
  44. package/src/__tests__/identity-routes.test.ts +4 -3
  45. package/src/__tests__/list-messages-client-message-id.test.ts +43 -0
  46. package/src/__tests__/llm-catalog-parity.test.ts +4 -4
  47. package/src/__tests__/llm-schema.test.ts +1 -1
  48. package/src/__tests__/notification-platform-adapter.test.ts +83 -1
  49. package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
  50. package/src/__tests__/openai-provider.test.ts +38 -11
  51. package/src/__tests__/pricing.test.ts +11 -0
  52. package/src/__tests__/public-ingress-urls.test.ts +26 -0
  53. package/src/__tests__/retry-thinking-tool-choice.test.ts +47 -0
  54. package/src/__tests__/skill-execute-input.test.ts +11 -0
  55. package/src/__tests__/tool-approval-handler.test.ts +4 -0
  56. package/src/__tests__/tool-preview-lifecycle.test.ts +26 -0
  57. package/src/__tests__/usage-attribution.test.ts +31 -0
  58. package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
  59. package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +262 -0
  60. package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +233 -0
  61. package/src/acp/agent-process.ts +6 -1
  62. package/src/acp/client-handler.ts +6 -1
  63. package/src/agent/loop-unparseable-tool-retry.test.ts +333 -0
  64. package/src/agent/loop.ts +161 -17
  65. package/src/api/events/document-editor-show.ts +5 -0
  66. package/src/api/events/document-editor-update.ts +10 -0
  67. package/src/api/events/tool-result.ts +1 -0
  68. package/src/calls/__tests__/call-controller.test.ts +273 -1
  69. package/src/calls/__tests__/media-stream-server-integration.test.ts +40 -0
  70. package/src/calls/__tests__/media-stream-stt-session.test.ts +33 -0
  71. package/src/calls/__tests__/voice-session-bridge.test.ts +54 -0
  72. package/src/calls/call-constants.ts +10 -0
  73. package/src/calls/call-controller.ts +359 -25
  74. package/src/calls/channel-admission-reader.ts +15 -9
  75. package/src/calls/media-stream-server.ts +18 -0
  76. package/src/calls/media-stream-stt-session.ts +30 -9
  77. package/src/calls/twilio-provider.ts +14 -31
  78. package/src/calls/voice-session-bridge.ts +18 -1
  79. package/src/cli/commands/browser.ts +1 -2
  80. package/src/cli/lib/__tests__/plugin-artifact.test.ts +23 -0
  81. package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +2 -0
  82. package/src/cli/lib/__tests__/plugin-details.test.ts +16 -0
  83. package/src/cli/lib/bundled-marketplace.json +54 -2
  84. package/src/cli/lib/inspect-plugin.ts +15 -15
  85. package/src/cli/lib/list-installed-plugins.ts +29 -28
  86. package/src/cli/lib/plugin-artifact.ts +74 -63
  87. package/src/cli/lib/plugin-catalog-visibility.ts +1 -0
  88. package/src/cli/lib/plugin-details.ts +27 -42
  89. package/src/cli/lib/plugin-marketplace.ts +6 -8
  90. package/src/cli/lib/plugin-surfaces.ts +8 -7
  91. package/src/cli.ts +1 -3
  92. package/src/config/__tests__/chat-settings.test.ts +121 -0
  93. package/src/config/assistant-feature-flags.ts +28 -23
  94. package/src/config/bundled-skills/computer-use/SKILL.md +20 -0
  95. package/src/config/bundled-skills/computer-use/TOOLS.json +11 -11
  96. package/src/config/chat-settings.ts +100 -0
  97. package/src/config/default-profile-catalog.ts +1 -1
  98. package/src/config/feature-flag-cache.ts +7 -25
  99. package/src/config/feature-flag-registry.json +9 -9
  100. package/src/config/loader.ts +5 -1
  101. package/src/config/profile-text-generation.ts +25 -21
  102. package/src/config/schemas/conversations.ts +14 -0
  103. package/src/config/schemas/llm.ts +1 -0
  104. package/src/config/schemas/notifications.ts +8 -1
  105. package/src/contacts/gateway-channel-read.ts +1 -1
  106. package/src/conversations/__tests__/auto-archive-activity.test.ts +136 -0
  107. package/src/conversations/__tests__/auto-archive.test.ts +304 -0
  108. package/src/conversations/auto-archive-activity.ts +61 -0
  109. package/src/conversations/auto-archive.ts +201 -0
  110. package/src/daemon/computer-use-observation.test.ts +81 -0
  111. package/src/daemon/computer-use-observation.ts +23 -0
  112. package/src/daemon/conversation-agent-loop-handlers.ts +18 -2
  113. package/src/daemon/conversation-agent-loop.ts +14 -6
  114. package/src/daemon/conversation-evictor.ts +50 -16
  115. package/src/daemon/conversation-messaging.ts +4 -0
  116. package/src/daemon/conversation-process.ts +15 -0
  117. package/src/daemon/conversation-queue-manager.ts +2 -0
  118. package/src/daemon/conversation.ts +2 -0
  119. package/src/daemon/doordash-steps.ts +1 -3
  120. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +43 -1
  121. package/src/daemon/handlers/__tests__/config-channels.test.ts +1 -1
  122. package/src/daemon/handlers/config-a2a.ts +75 -2
  123. package/src/daemon/handlers/shared.ts +13 -2
  124. package/src/daemon/host-cu-proxy.ts +34 -3
  125. package/src/daemon/lifecycle.ts +15 -2
  126. package/src/daemon/process-message.ts +5 -0
  127. package/src/daemon/shutdown-handlers.ts +3 -0
  128. package/src/daemon/turn-finalization.ts +4 -0
  129. package/src/desktop/desktop-browser-endpoint.ts +11 -2
  130. package/src/desktop/desktop-chrome-policy.test.ts +1 -0
  131. package/src/desktop/desktop-chrome-policy.ts +7 -1
  132. package/src/desktop/desktop-computer-use.test.ts +14 -4
  133. package/src/desktop/desktop-session-manager.ts +5 -3
  134. package/src/documents/document-revisions-store.ts +153 -0
  135. package/src/documents/document-store.ts +471 -133
  136. package/src/followups/followup-store.ts +0 -4
  137. package/src/i18n/__tests__/i18n.test.ts +25 -0
  138. package/src/i18n/assistant-text.ts +18 -0
  139. package/src/i18n/index.ts +1 -0
  140. package/src/i18n/messages.ts +11 -0
  141. package/src/inbound/__tests__/oauth-callback-url.test.ts +25 -2
  142. package/src/inbound/oauth-callback-url.ts +30 -1
  143. package/src/inbound/public-ingress-urls.ts +12 -2
  144. package/src/ipc/gateway-client.test.ts +40 -0
  145. package/src/ipc/gateway-client.ts +12 -29
  146. package/src/ipc/gateway-flag-listener.ts +7 -10
  147. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +71 -0
  148. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +83 -0
  149. package/src/live-voice/__tests__/protocol.test.ts +77 -0
  150. package/src/live-voice/__tests__/share-targets.test.ts +163 -0
  151. package/src/live-voice/live-voice-session.ts +23 -6
  152. package/src/live-voice/protocol.ts +22 -0
  153. package/src/live-voice/share-targets.ts +225 -0
  154. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +144 -3
  155. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +2 -0
  156. package/src/mcp/client.ts +1 -19
  157. package/src/mcp/mcp-oauth-provider.ts +66 -49
  158. package/src/media/openrouter-image-service.ts +39 -22
  159. package/src/monitoring/__tests__/oom-inheritance-sweep.test.ts +86 -0
  160. package/src/monitoring/__tests__/oom-kill-reporter.test.ts +374 -0
  161. package/src/monitoring/control.ts +2 -1
  162. package/src/monitoring/oom-inheritance-sweep.ts +96 -0
  163. package/src/monitoring/oom-kill-reporter.ts +469 -0
  164. package/src/monitoring/resource-sampler.ts +30 -0
  165. package/src/notifications/README.md +53 -4
  166. package/src/notifications/__tests__/assistant-initiated-thread.test.ts +254 -0
  167. package/src/notifications/__tests__/broadcaster.test.ts +558 -6
  168. package/src/notifications/__tests__/completion-policy.test.ts +37 -0
  169. package/src/notifications/__tests__/from-me-alert-integration.test.ts +192 -0
  170. package/src/notifications/adapters/platform.ts +14 -0
  171. package/src/notifications/assistant-initiated-thread.ts +101 -0
  172. package/src/notifications/broadcaster.ts +94 -16
  173. package/src/notifications/chat-reply-policy.ts +11 -0
  174. package/src/notifications/completion-policy.ts +12 -2
  175. package/src/notifications/conversation-pairing.ts +15 -70
  176. package/src/notifications/emit-signal.ts +34 -3
  177. package/src/notifications/types.ts +2 -0
  178. package/src/oauth/connect-orchestrator.ts +1 -2
  179. package/src/persistence/__tests__/conversation-auto-archive.test.ts +330 -0
  180. package/src/persistence/conversation-auto-archive.ts +170 -0
  181. package/src/persistence/conversation-crud.ts +2 -1
  182. package/src/persistence/conversation-queries.ts +15 -8
  183. package/src/persistence/embeddings/embed.test.ts +96 -0
  184. package/src/persistence/embeddings/embed.ts +20 -2
  185. package/src/persistence/embeddings/embedding-gemini.test.ts +39 -3
  186. package/src/persistence/embeddings/embedding-gemini.ts +39 -1
  187. package/src/persistence/embeddings/embedding-local.ts +6 -0
  188. package/src/persistence/embeddings/qdrant-manager.ts +5 -0
  189. package/src/persistence/job-utils.ts +10 -0
  190. package/src/persistence/migrations/384-add-document-revision.test.ts +83 -0
  191. package/src/persistence/migrations/384-add-document-revision.ts +21 -0
  192. package/src/persistence/migrations/385-create-document-revisions.test.ts +67 -0
  193. package/src/persistence/migrations/385-create-document-revisions.ts +28 -0
  194. package/src/persistence/migrations/386-conversations-last-reopened-at.ts +10 -0
  195. package/src/persistence/migrations/__tests__/386-conversations-last-reopened-at.test.ts +32 -0
  196. package/src/persistence/schema/conversations.ts +1 -0
  197. package/src/persistence/schema/documents.ts +20 -0
  198. package/src/persistence/steps.ts +6 -0
  199. package/src/platform/managed-speech.ts +42 -46
  200. package/src/plugin-api/vision-support.test.ts +21 -21
  201. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +11 -6
  202. package/src/providers/__tests__/dispatch-connection-routing.test.ts +1 -1
  203. package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
  204. package/src/providers/__tests__/unparseable-tool-args.test.ts +25 -0
  205. package/src/providers/anthropic/client.ts +1 -1
  206. package/src/providers/fireworks/client.test.ts +168 -0
  207. package/src/providers/fireworks/client.ts +24 -0
  208. package/src/providers/inference/adapter-factory.ts +6 -0
  209. package/src/providers/ionet/client.test.ts +143 -0
  210. package/src/providers/ionet/client.ts +37 -0
  211. package/src/providers/model-catalog.ts +141 -84
  212. package/src/providers/model-intents.ts +4 -4
  213. package/src/providers/openai/chat-completions-provider.ts +33 -2
  214. package/src/providers/retry.ts +56 -4
  215. package/src/providers/search-provider-catalog.ts +10 -0
  216. package/src/providers/types.ts +10 -2
  217. package/src/providers/unparseable-tool-args.ts +29 -5
  218. package/src/providers/usage-tracking.ts +5 -3
  219. package/src/runtime/assistant-event-hub.ts +23 -0
  220. package/src/runtime/auth/__tests__/guard-tests.test.ts +0 -7
  221. package/src/runtime/auth/__tests__/route-policy.test.ts +1 -1
  222. package/src/runtime/channel-reply-delivery.ts +2 -0
  223. package/src/runtime/channel-retry-sweep.ts +1 -2
  224. package/src/runtime/http-types.ts +3 -0
  225. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +35 -10
  226. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +48 -0
  227. package/src/runtime/migrations/vbundle-builder.ts +19 -3
  228. package/src/runtime/migrations/vbundle-streaming-importer.ts +14 -19
  229. package/src/runtime/migrations/vbundle-streaming-validator.ts +14 -7
  230. package/src/runtime/migrations/vbundle-validator.ts +54 -1
  231. package/src/runtime/pending-interactions.ts +20 -2
  232. package/src/runtime/routes/__tests__/contact-routes.test.ts +5 -3
  233. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +36 -0
  234. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +375 -2
  235. package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
  236. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +12 -0
  237. package/src/runtime/routes/contact-routes.ts +2 -2
  238. package/src/runtime/routes/conversation-list-routes.ts +10 -2
  239. package/src/runtime/routes/conversation-query-routes.ts +48 -10
  240. package/src/runtime/routes/conversation-routes.ts +7 -0
  241. package/src/runtime/routes/documents-routes.ts +185 -47
  242. package/src/runtime/routes/events-routes.ts +1 -3
  243. package/src/runtime/routes/host-app-control-routes.ts +1 -0
  244. package/src/runtime/routes/host-bash-routes.ts +1 -0
  245. package/src/runtime/routes/host-browser-routes.ts +15 -0
  246. package/src/runtime/routes/host-cu-routes.ts +4 -1
  247. package/src/runtime/routes/host-file-routes.ts +1 -0
  248. package/src/runtime/routes/host-proxy-result-binding.test.ts +83 -0
  249. package/src/runtime/routes/host-proxy-result-binding.ts +25 -0
  250. package/src/runtime/routes/host-transfer-routes.ts +1 -0
  251. package/src/runtime/routes/identity-routes.ts +3 -3
  252. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
  253. package/src/runtime/routes/integrations/a2a.ts +4 -1
  254. package/src/runtime/routes/migration-routes.ts +3 -1
  255. package/src/runtime/routes/notification-routes.ts +1 -2
  256. package/src/runtime/routes/ui-snapshot-routes.test.ts +1 -0
  257. package/src/runtime/routes/user-route-dispatcher.ts +5 -1
  258. package/src/runtime/verification-templates.ts +1 -2
  259. package/src/sequence/analytics.ts +0 -4
  260. package/src/sequence/engine.ts +1 -11
  261. package/src/sequence/guardrails.ts +1 -6
  262. package/src/sequence/importer.ts +0 -8
  263. package/src/sequence/store.ts +0 -10
  264. package/src/signals/user-message.ts +0 -6
  265. package/src/skills/catalog-install.ts +52 -23
  266. package/src/tools/browser/browser-execution.ts +20 -23
  267. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +1 -3
  268. package/src/tools/document/document-tool.ts +13 -11
  269. package/src/tools/executor.ts +1 -2
  270. package/src/tools/host-terminal/host-shell.ts +4 -2
  271. package/src/tools/network/__tests__/web-search.test.ts +105 -0
  272. package/src/tools/network/web-fetch.ts +2 -4
  273. package/src/tools/network/web-search.ts +239 -12
  274. package/src/tools/shared/filesystem/format-diff.ts +1 -2
  275. package/src/tools/shared/shell-output.test.ts +42 -0
  276. package/src/tools/shared/shell-output.ts +27 -4
  277. package/src/tools/skills/execute.ts +10 -7
  278. package/src/tools/terminal/shell.ts +3 -3
  279. package/src/tools/tool-approval-handler.ts +9 -1
  280. package/src/tools/ui-surface/visual-validation.ts +1 -2
  281. package/src/usage/attribution.ts +18 -1
  282. package/src/util/__tests__/oom-priority.test.ts +55 -0
  283. package/src/util/__tests__/worker-memory.test.ts +16 -0
  284. package/src/util/debounce.ts +0 -4
  285. package/src/util/oom-priority.ts +79 -0
  286. package/src/util/platform.ts +0 -2
  287. package/src/util/plugin-manifest.ts +5 -7
  288. package/src/util/pricing.ts +25 -0
  289. package/src/util/worker-memory.ts +9 -3
  290. package/src/util/worker-process.ts +8 -0
  291. package/src/workspace/byok-default-profile-ensure.ts +11 -0
  292. package/src/workspace/hatched-date.ts +11 -4
  293. package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +209 -0
  294. package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +197 -0
  295. package/src/workspace/migrations/registry.ts +4 -0
@@ -9,8 +9,10 @@
9
9
  * inbound mu-law audio is decoded to 16 kHz PCM16 and fed to a
10
10
  * {@link StreamingTranscriber} resolved for the configured
11
11
  * `services.stt` provider. Replies trigger on the provider's
12
- * utterance-boundary `final` events; barge-in (`onSpeechStart`) always
13
- * fires from the local energy VAD, never from transcriber partials.
12
+ * utterance-boundary `final` events; interim `partial` events are
13
+ * forwarded as the freshest words spoken since that final but start no
14
+ * turn of their own, and barge-in (`onSpeechStart`) always fires from
15
+ * the local energy VAD, never from transcriber partials.
14
16
  * Frames arriving while the provider session is still starting are
15
17
  * held in a bounded buffer and flushed on start (overflow drops the
16
18
  * oldest frames and is counted + logged at teardown).
@@ -20,10 +22,11 @@
20
22
  * transcriber is available for the configured provider, or the
21
23
  * provider closed the streaming session unexpectedly mid-call.
22
24
  *
23
- * This module is **transport-neutral** — it exposes callback hooks
24
- * (`onSpeechStart`, `onTranscriptFinal`, `onDtmf`, `onStop`) rather than
25
- * driving any call flow itself; `media-stream-server.ts` instantiates and
26
- * connects it to the media-stream WebSocket ingress.
25
+ * This module is **transport-neutral**: it exposes callback hooks
26
+ * (`onSpeechStart`, `onTranscriptFinal`, `onTranscriptPartial`, `onDtmf`,
27
+ * `onStop`) rather than driving any call flow itself;
28
+ * `media-stream-server.ts` instantiates and connects it to the
29
+ * media-stream WebSocket ingress.
27
30
  *
28
31
  * Error handling:
29
32
  * - When the telephony resolver returns a non-supported status, the
@@ -142,6 +145,19 @@ export interface MediaStreamSttSessionCallbacks {
142
145
  */
143
146
  onTranscriptFinal?: (text: string, durationMs: number) => void;
144
147
 
148
+ /**
149
+ * Called for each interim transcript the streaming provider revises on
150
+ * its way to the next final. The text is the provider's running guess at
151
+ * the words spoken since that final, so each call supersedes the last and
152
+ * the run resets when {@link onTranscriptFinal} commits it. Empty
153
+ * partials are suppressed, and batch mode never emits one.
154
+ *
155
+ * Turn taking still runs off the finals: this is the freshest text a
156
+ * boundary the front door held mid-thought can be re-judged on, not a
157
+ * boundary of its own.
158
+ */
159
+ onTranscriptPartial?: (text: string) => void;
160
+
145
161
  /**
146
162
  * Called when a DTMF digit is received from Twilio.
147
163
  */
@@ -542,8 +558,8 @@ export class MediaStreamSttSession {
542
558
  /**
543
559
  * Map streaming transcriber events onto the session callbacks.
544
560
  *
545
- * Partials are ignored for turn-taking: replies trigger only on the
546
- * provider's utterance-boundary finals, and barge-in comes from the
561
+ * Partials are forwarded but own no turn taking: replies trigger only on
562
+ * the provider's utterance-boundary finals, and barge-in comes from the
547
563
  * local VAD.
548
564
  */
549
565
  private handleStreamingEvent(event: SttStreamServerEvent): void {
@@ -552,8 +568,13 @@ export class MediaStreamSttSession {
552
568
  }
553
569
 
554
570
  switch (event.type) {
555
- case "partial":
571
+ case "partial": {
572
+ const text = event.text.trim();
573
+ if (text.length > 0) {
574
+ this.callbacks.onTranscriptPartial?.(text);
575
+ }
556
576
  return;
577
+ }
557
578
  case "finalized":
558
579
  return;
559
580
  case "turn-start":
@@ -23,27 +23,10 @@ const log = getLogger("twilio-provider");
23
23
  export class TwilioVoiceProvider implements VoiceProvider {
24
24
  readonly name = "twilio";
25
25
 
26
- // ── Credential helpers ──────────────────────────────────────────────
27
-
28
- private async getCredentials(): Promise<{
29
- accountSid: string;
30
- authToken: string;
31
- }> {
32
- return await getTwilioCredentials();
33
- }
34
-
35
- private authHeader(accountSid: string, authToken: string): string {
36
- return twilioAuthHeader(accountSid, authToken);
37
- }
38
-
39
- private baseUrl(accountSid: string): string {
40
- return twilioBaseUrl(accountSid);
41
- }
42
-
43
26
  // ── VoiceProvider interface ─────────────────────────────────────────
44
27
 
45
28
  async initiateCall(opts: InitiateCallOptions): Promise<{ callSid: string }> {
46
- const { accountSid, authToken } = await this.getCredentials();
29
+ const { accountSid, authToken } = await getTwilioCredentials();
47
30
 
48
31
  const body = new URLSearchParams({
49
32
  From: opts.from,
@@ -80,10 +63,10 @@ export class TwilioVoiceProvider implements VoiceProvider {
80
63
 
81
64
  log.info({ from: opts.from, to: opts.to }, "Initiating Twilio call");
82
65
 
83
- const res = await fetch(`${this.baseUrl(accountSid)}/Calls.json`, {
66
+ const res = await fetch(`${twilioBaseUrl(accountSid)}/Calls.json`, {
84
67
  method: "POST",
85
68
  headers: {
86
- Authorization: this.authHeader(accountSid, authToken),
69
+ Authorization: twilioAuthHeader(accountSid, authToken),
87
70
  "Content-Type": "application/x-www-form-urlencoded",
88
71
  },
89
72
  body: body.toString(),
@@ -108,18 +91,18 @@ export class TwilioVoiceProvider implements VoiceProvider {
108
91
  }
109
92
 
110
93
  async endCall(callSid: string): Promise<void> {
111
- const { accountSid, authToken } = await this.getCredentials();
94
+ const { accountSid, authToken } = await getTwilioCredentials();
112
95
 
113
96
  log.info({ callSid }, "Ending Twilio call");
114
97
 
115
98
  const body = new URLSearchParams({ Status: "completed" });
116
99
 
117
100
  const res = await fetch(
118
- `${this.baseUrl(accountSid)}/Calls/${callSid}.json`,
101
+ `${twilioBaseUrl(accountSid)}/Calls/${callSid}.json`,
119
102
  {
120
103
  method: "POST",
121
104
  headers: {
122
- Authorization: this.authHeader(accountSid, authToken),
105
+ Authorization: twilioAuthHeader(accountSid, authToken),
123
106
  "Content-Type": "application/x-www-form-urlencoded",
124
107
  },
125
108
  body: body.toString(),
@@ -143,14 +126,14 @@ export class TwilioVoiceProvider implements VoiceProvider {
143
126
  }
144
127
 
145
128
  async getCallStatus(callSid: string): Promise<string> {
146
- const { accountSid, authToken } = await this.getCredentials();
129
+ const { accountSid, authToken } = await getTwilioCredentials();
147
130
 
148
131
  const res = await fetch(
149
- `${this.baseUrl(accountSid)}/Calls/${callSid}.json`,
132
+ `${twilioBaseUrl(accountSid)}/Calls/${callSid}.json`,
150
133
  {
151
134
  method: "GET",
152
135
  headers: {
153
- Authorization: this.authHeader(accountSid, authToken),
136
+ Authorization: twilioAuthHeader(accountSid, authToken),
154
137
  },
155
138
  },
156
139
  );
@@ -183,7 +166,7 @@ export class TwilioVoiceProvider implements VoiceProvider {
183
166
  async checkCallerIdEligibility(
184
167
  phoneNumber: string,
185
168
  ): Promise<{ eligible: boolean; reason?: string }> {
186
- const { accountSid, authToken } = await this.getCredentials();
169
+ const { accountSid, authToken } = await getTwilioCredentials();
187
170
  const encodedNumber = encodeURIComponent(phoneNumber);
188
171
 
189
172
  let incomingOk = false;
@@ -191,13 +174,13 @@ export class TwilioVoiceProvider implements VoiceProvider {
191
174
 
192
175
  // Check incoming phone numbers (owned by this account)
193
176
  const incomingRes = await fetch(
194
- `${this.baseUrl(
177
+ `${twilioBaseUrl(
195
178
  accountSid,
196
179
  )}/IncomingPhoneNumbers.json?PhoneNumber=${encodedNumber}`,
197
180
  {
198
181
  method: "GET",
199
182
  headers: {
200
- Authorization: this.authHeader(accountSid, authToken),
183
+ Authorization: twilioAuthHeader(accountSid, authToken),
201
184
  },
202
185
  },
203
186
  );
@@ -223,13 +206,13 @@ export class TwilioVoiceProvider implements VoiceProvider {
223
206
 
224
207
  // Check outgoing caller IDs (verified with this account)
225
208
  const outgoingRes = await fetch(
226
- `${this.baseUrl(
209
+ `${twilioBaseUrl(
227
210
  accountSid,
228
211
  )}/OutgoingCallerIds.json?PhoneNumber=${encodedNumber}`,
229
212
  {
230
213
  method: "GET",
231
214
  headers: {
232
- Authorization: this.authHeader(accountSid, authToken),
215
+ Authorization: twilioAuthHeader(accountSid, authToken),
233
216
  },
234
217
  },
235
218
  );
@@ -41,6 +41,10 @@ import {
41
41
  pendingStandaloneImagePersist,
42
42
  SIGHT_FRAME_TURN_HOLD_MS,
43
43
  } from "../live-voice/live-voice-photo.js";
44
+ import {
45
+ formatShareTargetsForPrompt,
46
+ type ShareTargetSnapshot,
47
+ } from "../live-voice/share-targets.js";
44
48
  import { resolveAttachmentsForPersist } from "../persistence/attachments-store.js";
45
49
  import {
46
50
  deleteMessageById,
@@ -462,6 +466,13 @@ export interface VoiceTurnOptions {
462
466
  macosDesktopSession?: boolean;
463
467
  /** The desktop client currently shares a surface with this voice session. */
464
468
  screenSharing?: boolean;
469
+ /**
470
+ * The controls the shared surface offers to be pointed at, from the
471
+ * client's newest read of its accessibility tree. Offered alongside the
472
+ * screen-annotation instructions so the first `screen_point_at` names a
473
+ * control the lookup can resolve.
474
+ */
475
+ shareTargets?: ShareTargetSnapshot;
465
476
  /** Front-door verdict: this action needs only the shared screen and current context. */
466
477
  screenAction?: boolean;
467
478
  /** Whether this is an inbound call (no outbound task). */
@@ -2145,8 +2156,14 @@ export async function startVoiceTurn(
2145
2156
  signal: opts.signal,
2146
2157
  });
2147
2158
  if (annotation !== null) {
2159
+ const controls =
2160
+ opts.shareTargets !== undefined
2161
+ ? formatShareTargetsForPrompt(opts.shareTargets)
2162
+ : null;
2148
2163
  conversation.setVoiceCallControlPrompt(
2149
- [voiceCallControlPrompt, annotation].filter(Boolean).join("\n\n"),
2164
+ [voiceCallControlPrompt, annotation, controls]
2165
+ .filter(Boolean)
2166
+ .join("\n\n"),
2150
2167
  );
2151
2168
  }
2152
2169
  }
@@ -214,8 +214,7 @@ function attachOperationAction(
214
214
  opts.output &&
215
215
  result.screenshots?.length
216
216
  ) {
217
- const screenshot = result.screenshots[0];
218
- const buffer = Buffer.from(screenshot.data, "base64");
217
+ const buffer = Buffer.from(result.screenshots[0].data, "base64");
219
218
  try {
220
219
  writeFileSync(String(opts.output), buffer);
221
220
  } catch (err) {
@@ -123,6 +123,23 @@ describe("parsePluginArtifact", () => {
123
123
  });
124
124
  });
125
125
 
126
+ test("ignores a malformed icon beside a valid artifact", () => {
127
+ expect(
128
+ parsePluginArtifact({
129
+ vellum: {
130
+ artifact: {
131
+ url: "https://example.com/App.dmg",
132
+ sha256: VALID_SHA,
133
+ },
134
+ icon: 42,
135
+ },
136
+ }),
137
+ ).toEqual({
138
+ url: "https://example.com/App.dmg",
139
+ sha256: VALID_SHA,
140
+ });
141
+ });
142
+
126
143
  test.each([
127
144
  ["no vellum block", { name: "x" }],
128
145
  ["no artifact block", { vellum: {} }],
@@ -202,6 +219,12 @@ describe("parsePluginIcon", () => {
202
219
  expect(parsePluginIcon({ vellum: { icon: sixteen } })).toBe(sixteen);
203
220
  });
204
221
 
222
+ test("ignores a malformed artifact beside a valid icon", () => {
223
+ expect(
224
+ parsePluginIcon({ vellum: { artifact: "App.dmg", icon: "🚀" } }),
225
+ ).toBe("🚀");
226
+ });
227
+
205
228
  test.each([
206
229
  ["no vellum block", { name: "x" }],
207
230
  ["no icon field", { vellum: {} }],
@@ -33,6 +33,7 @@ describe("filterPluginCatalogByFeatureFlags", () => {
33
33
  "ramp",
34
34
  "semrush",
35
35
  "typeform",
36
+ "upwork",
36
37
  "notion",
37
38
  ]);
38
39
 
@@ -56,6 +57,7 @@ describe("filterPluginCatalogByFeatureFlags", () => {
56
57
  "ramp",
57
58
  "semrush",
58
59
  "typeform",
60
+ "upwork",
59
61
  ]);
60
62
 
61
63
  const result = filterPluginCatalogByFeatureFlags(input, () => true);
@@ -309,6 +309,22 @@ describe("getPluginDetails (bundled catalog, offline)", () => {
309
309
  expect(details.icon).toBeNull();
310
310
  });
311
311
 
312
+ test("reads a legacy object-form license from an installed package.json", async () => {
313
+ const target = join(workspace, "caveman");
314
+ mkdirSync(target, { recursive: true });
315
+ writeFileSync(
316
+ join(target, "package.json"),
317
+ JSON.stringify({ version: "2.0.0", license: { type: "MIT" } }),
318
+ );
319
+
320
+ const details = await getPluginDetails(
321
+ { name: "caveman" },
322
+ { fetch: makeFetch({}), workspacePluginsDir: workspace },
323
+ );
324
+
325
+ expect(details.license).toBe("MIT");
326
+ });
327
+
312
328
  test("reads metadata from an installed standard-only plugin.json", async () => {
313
329
  const target = join(workspace, "caveman");
314
330
  mkdirSync(target, { recursive: true });
@@ -135,6 +135,32 @@
135
135
  "homepage": "https://github.com/topoteretes/cognee-integrations/tree/main/integrations/vellum-assistant",
136
136
  "icon": "\ud83d\udd78\ufe0f"
137
137
  },
138
+ {
139
+ "name": "contractor-missed-call-dispatcher",
140
+ "description": "Captures missed trade contractor inbound calls, fires an instant 5-second SMS qualification text-back, triages emergencies, and stages draft jobs in Jobber or ServiceTitan.",
141
+ "category": "commerce",
142
+ "icon": "📲",
143
+ "license": "MIT",
144
+ "homepage": "https://github.com/ZeebBoyBlue/contractor-missed-call-dispatcher",
145
+ "source": {
146
+ "source": "github",
147
+ "repo": "ZeebBoyBlue/contractor-missed-call-dispatcher",
148
+ "ref": "2d293cdcb68af03d446b61a9915a0ee3dc842d4e"
149
+ }
150
+ },
151
+ {
152
+ "name": "contractor-photo-to-quote",
153
+ "description": "Turns job-site and equipment photos into standardized trade estimates and quotes in Jobber or QuickBooks with multi-tier Good/Better/Best options in 5 minutes.",
154
+ "category": "commerce",
155
+ "icon": "📸",
156
+ "license": "MIT",
157
+ "homepage": "https://github.com/ZeebBoyBlue/contractor-photo-to-quote",
158
+ "source": {
159
+ "source": "github",
160
+ "repo": "ZeebBoyBlue/contractor-photo-to-quote",
161
+ "ref": "6c136186faaf03bcf125d202f0b1f51f97d94544"
162
+ }
163
+ },
138
164
  {
139
165
  "name": "contractor-voice-invoicer",
140
166
  "description": "Converts 20-second truck voice memos and job photos into itemized client invoices with custom labor rates, material markups, and one-click accounting staging.",
@@ -461,6 +487,19 @@
461
487
  "license": "MIT",
462
488
  "icon": "\ud83d\udcac"
463
489
  },
490
+ {
491
+ "name": "quickbooks-expense-categorizer",
492
+ "description": "Scans uncategorized QuickBooks Online expenses and bank feeds, matches receipts, and stages categorized entries with confidence scoring for 1-click batch review.",
493
+ "category": "commerce",
494
+ "icon": "🧾",
495
+ "license": "MIT",
496
+ "homepage": "https://github.com/ZeebBoyBlue/quickbooks-expense-categorizer",
497
+ "source": {
498
+ "source": "github",
499
+ "repo": "ZeebBoyBlue/quickbooks-expense-categorizer",
500
+ "ref": "120776619698cc3e9490caf5c756465f5caaa6c2"
501
+ }
502
+ },
464
503
  {
465
504
  "name": "reading-pal",
466
505
  "source": {
@@ -487,6 +526,19 @@
487
526
  "ref": "f1905e639941e98172c5032b6d722f668882d75d"
488
527
  }
489
528
  },
529
+ {
530
+ "name": "shopify-payout-reconciler",
531
+ "description": "Unbundles lump-sum Shopify merchant bank deposits into gross sales, processor fees, customer refunds, shipping labels, and sales tax for penny-accurate QuickBooks reconciliation.",
532
+ "category": "commerce",
533
+ "icon": "🛍️",
534
+ "license": "MIT",
535
+ "homepage": "https://github.com/ZeebBoyBlue/shopify-payout-reconciler",
536
+ "source": {
537
+ "source": "github",
538
+ "repo": "ZeebBoyBlue/shopify-payout-reconciler",
539
+ "ref": "079fe0d86629686243a588cf3c4867d38d6ab8b8"
540
+ }
541
+ },
490
542
  {
491
543
  "name": "simple-memory",
492
544
  "source": {
@@ -1368,9 +1420,9 @@
1368
1420
  "source": {
1369
1421
  "source": "github",
1370
1422
  "repo": "vellum-ai/runway-plugin",
1371
- "ref": "2a642f14ee8ca5032efefae06b6a610dec13e1a1"
1423
+ "ref": "bb017a1e018c7be140c02c8463273c2d85f3b9d9"
1372
1424
  },
1373
- "description": "Collaboratively direct, approve, create, track, cancel, and download Runway Gen-4.5 videos.",
1425
+ "description": "Securely set up, collaboratively direct, approve, create, track, cancel, and download Runway Gen-4.5 videos.",
1374
1426
  "category": "other",
1375
1427
  "submittedBy": "marina"
1376
1428
  },
@@ -16,6 +16,8 @@
16
16
  * is a thin wrapper that supplies production deps and formats the result.
17
17
  */
18
18
 
19
+ import { z } from "zod";
20
+
19
21
  import { PRESERVED_ENTRIES } from "../../plugins/plugin-tree-walk.js";
20
22
  import type { FetchLike } from "./fetch-like.js";
21
23
  import {
@@ -50,6 +52,14 @@ import type { PluginCatalog, PluginSearchMatch } from "./search-plugins.js";
50
52
  /** Full commit SHA (40 hex SHA-1 or 64 hex SHA-256). */
51
53
  const FULL_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i;
52
54
 
55
+ const GitHubCommitResponseSchema = z.object({
56
+ commit: z.object({
57
+ committer: z.object({
58
+ date: z.string(),
59
+ }),
60
+ }),
61
+ });
62
+
53
63
  /**
54
64
  * Drift classification between the installed copy and the marketplace pin.
55
65
  *
@@ -294,23 +304,13 @@ export async function fetchCommitDate(
294
304
  if (!res.ok) {
295
305
  return null;
296
306
  }
297
- const json: unknown = JSON.parse(await res.text());
298
- if (typeof json !== "object" || json === null) {
299
- return null;
300
- }
301
- const commit = (json as Record<string, unknown>).commit;
302
- if (typeof commit !== "object" || commit === null) {
303
- return null;
304
- }
305
- const committer = (commit as Record<string, unknown>).committer;
306
- if (typeof committer !== "object" || committer === null) {
307
- return null;
308
- }
309
- const date = (committer as Record<string, unknown>).date;
310
- if (typeof date !== "string") {
307
+ const parsed = GitHubCommitResponseSchema.safeParse(
308
+ JSON.parse(await res.text()),
309
+ );
310
+ if (!parsed.success) {
311
311
  return null;
312
312
  }
313
- const ms = Date.parse(date);
313
+ const ms = Date.parse(parsed.data.commit.committer.date);
314
314
  return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
315
315
  } catch {
316
316
  return null;
@@ -16,12 +16,14 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
16
16
  import { dirname, join } from "node:path";
17
17
  import { fileURLToPath } from "node:url";
18
18
 
19
+ import { z } from "zod";
20
+
19
21
  import { getWorkspacePluginsDir } from "../../util/platform.js";
20
22
  import {
21
23
  LEGACY_PLUGIN_MANIFEST,
22
24
  readPluginManifest,
23
25
  } from "../../util/plugin-manifest.js";
24
- import { parsePluginIcon } from "./plugin-artifact.js";
26
+ import { PluginPackageJsonSchema } from "./plugin-artifact.js";
25
27
  import { readValidatedPluginIcon } from "./plugin-icon-file.js";
26
28
 
27
29
  /**
@@ -39,6 +41,15 @@ const DEFAULT_PLUGINS_DIR = join(
39
41
  "defaults",
40
42
  );
41
43
 
44
+ const DefaultPluginManifestSchema = z.object({
45
+ name: z.string(),
46
+ version: z.string().optional().catch(undefined),
47
+ });
48
+
49
+ const InstallMetadataSchema = z.object({
50
+ installedAt: z.string(),
51
+ });
52
+
42
53
  /** Minimal manifest fields surfaced to the CLI. */
43
54
  export interface PluginPackageMetadata {
44
55
  readonly name?: string;
@@ -163,23 +174,17 @@ function readPluginEntry(
163
174
  issues.push(err instanceof Error ? err.message : String(err));
164
175
  return { name, target, packageJson: null, issues, ...iconFields };
165
176
  }
166
- const meta = manifest.raw;
167
- const icon =
168
- manifest.source === LEGACY_PLUGIN_MANIFEST
169
- ? parsePluginIcon(meta)
170
- : undefined;
177
+ const parsedPackageJson = PluginPackageJsonSchema.safeParse(manifest.raw);
178
+ const legacyMetadata =
179
+ manifest.source === LEGACY_PLUGIN_MANIFEST && parsedPackageJson.success
180
+ ? parsedPackageJson.data
181
+ : null;
171
182
  const packageJson: PluginPackageMetadata = {
172
183
  name: manifest.name,
173
184
  version: manifest.version,
174
185
  description: manifest.description,
175
- peerDependencies:
176
- manifest.source === LEGACY_PLUGIN_MANIFEST &&
177
- typeof meta.peerDependencies === "object" &&
178
- meta.peerDependencies !== null &&
179
- !Array.isArray(meta.peerDependencies)
180
- ? (meta.peerDependencies as Record<string, string>)
181
- : undefined,
182
- ...(icon ? { icon } : {}),
186
+ peerDependencies: legacyMetadata?.peerDependencies,
187
+ ...(legacyMetadata?.icon ? { icon: legacyMetadata.icon } : {}),
183
188
  };
184
189
 
185
190
  return { name, target, packageJson, issues, ...iconFields };
@@ -319,14 +324,11 @@ function readDefaultPluginManifests(): readonly DefaultPluginManifest[] {
319
324
  continue;
320
325
  }
321
326
  try {
322
- const raw = readFileSync(pkgJsonPath, "utf8");
323
- const parsed = JSON.parse(raw) as Record<string, unknown>;
324
- if (typeof parsed.name === "string") {
325
- manifests.push({
326
- name: parsed.name,
327
- version:
328
- typeof parsed.version === "string" ? parsed.version : undefined,
329
- });
327
+ const parsed = DefaultPluginManifestSchema.safeParse(
328
+ JSON.parse(readFileSync(pkgJsonPath, "utf8")),
329
+ );
330
+ if (parsed.success) {
331
+ manifests.push(parsed.data);
330
332
  }
331
333
  } catch {
332
334
  // Skip malformed entries — lenient like listInstalledPlugins.
@@ -345,12 +347,11 @@ function getPluginInstallDate(plugin: AllPluginInfo): number {
345
347
  const metaPath = join(plugin.target, "install-meta.json");
346
348
  try {
347
349
  if (existsSync(metaPath)) {
348
- const raw = JSON.parse(readFileSync(metaPath, "utf8")) as Record<
349
- string,
350
- unknown
351
- >;
352
- if (typeof raw.installedAt === "string") {
353
- const ms = Date.parse(raw.installedAt);
350
+ const parsed = InstallMetadataSchema.safeParse(
351
+ JSON.parse(readFileSync(metaPath, "utf8")),
352
+ );
353
+ if (parsed.success) {
354
+ const ms = Date.parse(parsed.data.installedAt);
354
355
  if (Number.isFinite(ms)) {
355
356
  return ms;
356
357
  }