@vellumai/assistant 0.12.5 → 0.12.6-dev.202609292019.a6f6d85

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 (264) hide show
  1. package/.env.example +2 -0
  2. package/ARCHITECTURE.md +23 -0
  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__/llm-catalog-parity.test.ts +4 -4
  46. package/src/__tests__/llm-schema.test.ts +1 -1
  47. package/src/__tests__/notification-platform-adapter.test.ts +83 -1
  48. package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
  49. package/src/__tests__/openai-provider.test.ts +11 -11
  50. package/src/__tests__/pricing.test.ts +11 -0
  51. package/src/__tests__/public-ingress-urls.test.ts +26 -0
  52. package/src/__tests__/retry-thinking-tool-choice.test.ts +47 -0
  53. package/src/__tests__/usage-attribution.test.ts +31 -0
  54. package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
  55. package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +262 -0
  56. package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +233 -0
  57. package/src/acp/agent-process.ts +6 -1
  58. package/src/acp/client-handler.ts +6 -1
  59. package/src/agent/loop.ts +29 -6
  60. package/src/api/events/document-editor-show.ts +5 -0
  61. package/src/api/events/document-editor-update.ts +10 -0
  62. package/src/api/events/tool-result.ts +1 -0
  63. package/src/calls/__tests__/voice-session-bridge.test.ts +54 -0
  64. package/src/calls/channel-admission-reader.ts +15 -9
  65. package/src/calls/twilio-provider.ts +14 -31
  66. package/src/calls/voice-session-bridge.ts +18 -1
  67. package/src/cli/commands/browser.ts +1 -2
  68. package/src/cli/lib/__tests__/plugin-artifact.test.ts +23 -0
  69. package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +2 -0
  70. package/src/cli/lib/__tests__/plugin-details.test.ts +16 -0
  71. package/src/cli/lib/bundled-marketplace.json +41 -2
  72. package/src/cli/lib/inspect-plugin.ts +15 -15
  73. package/src/cli/lib/list-installed-plugins.ts +29 -28
  74. package/src/cli/lib/plugin-artifact.ts +74 -63
  75. package/src/cli/lib/plugin-catalog-visibility.ts +1 -0
  76. package/src/cli/lib/plugin-details.ts +27 -42
  77. package/src/cli/lib/plugin-marketplace.ts +6 -8
  78. package/src/cli/lib/plugin-surfaces.ts +8 -7
  79. package/src/cli.ts +1 -3
  80. package/src/config/__tests__/chat-settings.test.ts +121 -0
  81. package/src/config/assistant-feature-flags.ts +28 -23
  82. package/src/config/bundled-skills/computer-use/SKILL.md +20 -0
  83. package/src/config/bundled-skills/computer-use/TOOLS.json +11 -11
  84. package/src/config/chat-settings.ts +100 -0
  85. package/src/config/default-profile-catalog.ts +1 -1
  86. package/src/config/feature-flag-cache.ts +7 -25
  87. package/src/config/feature-flag-registry.json +1 -9
  88. package/src/config/loader.ts +5 -1
  89. package/src/config/profile-text-generation.ts +25 -21
  90. package/src/config/schemas/conversations.ts +14 -0
  91. package/src/config/schemas/llm.ts +1 -0
  92. package/src/config/schemas/notifications.ts +8 -1
  93. package/src/contacts/gateway-channel-read.ts +1 -1
  94. package/src/conversations/__tests__/auto-archive-activity.test.ts +136 -0
  95. package/src/conversations/__tests__/auto-archive.test.ts +304 -0
  96. package/src/conversations/auto-archive-activity.ts +61 -0
  97. package/src/conversations/auto-archive.ts +201 -0
  98. package/src/daemon/computer-use-observation.test.ts +81 -0
  99. package/src/daemon/computer-use-observation.ts +23 -0
  100. package/src/daemon/conversation-agent-loop.ts +11 -6
  101. package/src/daemon/conversation-evictor.ts +50 -16
  102. package/src/daemon/doordash-steps.ts +1 -3
  103. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +43 -1
  104. package/src/daemon/handlers/__tests__/config-channels.test.ts +1 -1
  105. package/src/daemon/handlers/config-a2a.ts +75 -2
  106. package/src/daemon/host-cu-proxy.ts +34 -3
  107. package/src/daemon/lifecycle.ts +15 -2
  108. package/src/daemon/shutdown-handlers.ts +3 -0
  109. package/src/daemon/turn-finalization.ts +4 -0
  110. package/src/desktop/desktop-browser-endpoint.ts +11 -2
  111. package/src/desktop/desktop-chrome-policy.test.ts +1 -0
  112. package/src/desktop/desktop-chrome-policy.ts +7 -1
  113. package/src/desktop/desktop-computer-use.test.ts +14 -4
  114. package/src/desktop/desktop-session-manager.ts +5 -3
  115. package/src/documents/document-revisions-store.ts +153 -0
  116. package/src/documents/document-store.ts +471 -133
  117. package/src/followups/followup-store.ts +0 -4
  118. package/src/inbound/__tests__/oauth-callback-url.test.ts +25 -2
  119. package/src/inbound/oauth-callback-url.ts +30 -1
  120. package/src/inbound/public-ingress-urls.ts +12 -2
  121. package/src/ipc/gateway-client.test.ts +40 -0
  122. package/src/ipc/gateway-client.ts +12 -29
  123. package/src/ipc/gateway-flag-listener.ts +7 -10
  124. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +71 -0
  125. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +83 -0
  126. package/src/live-voice/__tests__/protocol.test.ts +77 -0
  127. package/src/live-voice/__tests__/share-targets.test.ts +163 -0
  128. package/src/live-voice/live-voice-session.ts +23 -6
  129. package/src/live-voice/protocol.ts +22 -0
  130. package/src/live-voice/share-targets.ts +225 -0
  131. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +144 -3
  132. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +2 -0
  133. package/src/mcp/client.ts +1 -19
  134. package/src/mcp/mcp-oauth-provider.ts +66 -49
  135. package/src/media/openrouter-image-service.ts +39 -22
  136. package/src/monitoring/__tests__/oom-inheritance-sweep.test.ts +86 -0
  137. package/src/monitoring/__tests__/oom-kill-reporter.test.ts +374 -0
  138. package/src/monitoring/control.ts +2 -1
  139. package/src/monitoring/oom-inheritance-sweep.ts +96 -0
  140. package/src/monitoring/oom-kill-reporter.ts +469 -0
  141. package/src/monitoring/resource-sampler.ts +30 -0
  142. package/src/notifications/README.md +53 -4
  143. package/src/notifications/__tests__/assistant-initiated-thread.test.ts +254 -0
  144. package/src/notifications/__tests__/broadcaster.test.ts +558 -6
  145. package/src/notifications/__tests__/completion-policy.test.ts +37 -0
  146. package/src/notifications/__tests__/from-me-alert-integration.test.ts +192 -0
  147. package/src/notifications/adapters/platform.ts +14 -0
  148. package/src/notifications/assistant-initiated-thread.ts +101 -0
  149. package/src/notifications/broadcaster.ts +94 -16
  150. package/src/notifications/chat-reply-policy.ts +11 -0
  151. package/src/notifications/completion-policy.ts +12 -2
  152. package/src/notifications/conversation-pairing.ts +15 -70
  153. package/src/notifications/emit-signal.ts +34 -3
  154. package/src/notifications/types.ts +2 -0
  155. package/src/oauth/connect-orchestrator.ts +1 -2
  156. package/src/persistence/__tests__/conversation-auto-archive.test.ts +330 -0
  157. package/src/persistence/conversation-auto-archive.ts +170 -0
  158. package/src/persistence/conversation-crud.ts +2 -1
  159. package/src/persistence/conversation-queries.ts +15 -8
  160. package/src/persistence/embeddings/embed.test.ts +96 -0
  161. package/src/persistence/embeddings/embed.ts +20 -2
  162. package/src/persistence/embeddings/embedding-gemini.test.ts +39 -3
  163. package/src/persistence/embeddings/embedding-gemini.ts +39 -1
  164. package/src/persistence/embeddings/embedding-local.ts +6 -0
  165. package/src/persistence/embeddings/qdrant-manager.ts +5 -0
  166. package/src/persistence/job-utils.ts +10 -0
  167. package/src/persistence/migrations/384-add-document-revision.test.ts +83 -0
  168. package/src/persistence/migrations/384-add-document-revision.ts +21 -0
  169. package/src/persistence/migrations/385-create-document-revisions.test.ts +67 -0
  170. package/src/persistence/migrations/385-create-document-revisions.ts +28 -0
  171. package/src/persistence/migrations/386-conversations-last-reopened-at.ts +10 -0
  172. package/src/persistence/migrations/__tests__/386-conversations-last-reopened-at.test.ts +32 -0
  173. package/src/persistence/schema/conversations.ts +1 -0
  174. package/src/persistence/schema/documents.ts +20 -0
  175. package/src/persistence/steps.ts +6 -0
  176. package/src/platform/managed-speech.ts +42 -46
  177. package/src/plugin-api/vision-support.test.ts +21 -21
  178. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +11 -6
  179. package/src/providers/__tests__/dispatch-connection-routing.test.ts +1 -1
  180. package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
  181. package/src/providers/anthropic/client.ts +1 -1
  182. package/src/providers/fireworks/client.test.ts +168 -0
  183. package/src/providers/fireworks/client.ts +24 -0
  184. package/src/providers/inference/adapter-factory.ts +6 -0
  185. package/src/providers/ionet/client.test.ts +143 -0
  186. package/src/providers/ionet/client.ts +37 -0
  187. package/src/providers/model-catalog.ts +141 -84
  188. package/src/providers/model-intents.ts +4 -4
  189. package/src/providers/openai/chat-completions-provider.ts +23 -1
  190. package/src/providers/retry.ts +56 -4
  191. package/src/providers/search-provider-catalog.ts +10 -0
  192. package/src/providers/types.ts +10 -2
  193. package/src/providers/usage-tracking.ts +5 -3
  194. package/src/runtime/assistant-event-hub.ts +23 -0
  195. package/src/runtime/auth/__tests__/guard-tests.test.ts +0 -7
  196. package/src/runtime/auth/__tests__/route-policy.test.ts +1 -1
  197. package/src/runtime/channel-retry-sweep.ts +1 -2
  198. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +35 -10
  199. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +48 -0
  200. package/src/runtime/migrations/vbundle-builder.ts +19 -3
  201. package/src/runtime/migrations/vbundle-streaming-importer.ts +14 -19
  202. package/src/runtime/migrations/vbundle-streaming-validator.ts +14 -7
  203. package/src/runtime/migrations/vbundle-validator.ts +54 -1
  204. package/src/runtime/pending-interactions.ts +20 -2
  205. package/src/runtime/routes/__tests__/contact-routes.test.ts +5 -3
  206. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +36 -0
  207. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +375 -2
  208. package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
  209. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +12 -0
  210. package/src/runtime/routes/contact-routes.ts +2 -2
  211. package/src/runtime/routes/conversation-list-routes.ts +10 -2
  212. package/src/runtime/routes/conversation-query-routes.ts +48 -10
  213. package/src/runtime/routes/documents-routes.ts +185 -47
  214. package/src/runtime/routes/events-routes.ts +1 -3
  215. package/src/runtime/routes/host-app-control-routes.ts +1 -0
  216. package/src/runtime/routes/host-bash-routes.ts +1 -0
  217. package/src/runtime/routes/host-browser-routes.ts +15 -0
  218. package/src/runtime/routes/host-cu-routes.ts +4 -1
  219. package/src/runtime/routes/host-file-routes.ts +1 -0
  220. package/src/runtime/routes/host-proxy-result-binding.test.ts +83 -0
  221. package/src/runtime/routes/host-proxy-result-binding.ts +25 -0
  222. package/src/runtime/routes/host-transfer-routes.ts +1 -0
  223. package/src/runtime/routes/identity-routes.ts +3 -3
  224. package/src/runtime/routes/integrations/a2a.ts +4 -1
  225. package/src/runtime/routes/migration-routes.ts +3 -1
  226. package/src/runtime/routes/notification-routes.ts +1 -2
  227. package/src/runtime/routes/ui-snapshot-routes.test.ts +1 -0
  228. package/src/runtime/routes/user-route-dispatcher.ts +5 -1
  229. package/src/runtime/verification-templates.ts +1 -2
  230. package/src/sequence/analytics.ts +0 -4
  231. package/src/sequence/engine.ts +1 -11
  232. package/src/sequence/guardrails.ts +1 -6
  233. package/src/sequence/importer.ts +0 -8
  234. package/src/sequence/store.ts +0 -10
  235. package/src/signals/user-message.ts +0 -6
  236. package/src/skills/catalog-install.ts +52 -23
  237. package/src/tools/browser/browser-execution.ts +20 -23
  238. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +1 -3
  239. package/src/tools/document/document-tool.ts +13 -11
  240. package/src/tools/executor.ts +1 -2
  241. package/src/tools/host-terminal/host-shell.ts +4 -2
  242. package/src/tools/network/__tests__/web-search.test.ts +105 -0
  243. package/src/tools/network/web-fetch.ts +2 -4
  244. package/src/tools/network/web-search.ts +239 -12
  245. package/src/tools/shared/filesystem/format-diff.ts +1 -2
  246. package/src/tools/shared/shell-output.test.ts +42 -0
  247. package/src/tools/shared/shell-output.ts +27 -4
  248. package/src/tools/terminal/shell.ts +3 -3
  249. package/src/tools/ui-surface/visual-validation.ts +1 -2
  250. package/src/usage/attribution.ts +18 -1
  251. package/src/util/__tests__/oom-priority.test.ts +55 -0
  252. package/src/util/__tests__/worker-memory.test.ts +16 -0
  253. package/src/util/debounce.ts +0 -4
  254. package/src/util/oom-priority.ts +79 -0
  255. package/src/util/platform.ts +0 -2
  256. package/src/util/plugin-manifest.ts +5 -7
  257. package/src/util/pricing.ts +25 -0
  258. package/src/util/worker-memory.ts +9 -3
  259. package/src/util/worker-process.ts +8 -0
  260. package/src/workspace/byok-default-profile-ensure.ts +11 -0
  261. package/src/workspace/hatched-date.ts +11 -4
  262. package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +209 -0
  263. package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +197 -0
  264. package/src/workspace/migrations/registry.ts +4 -0
@@ -2397,6 +2397,60 @@ describe("desktop skill preactivation", () => {
2397
2397
  expect(result.promptDuringLoop).toContain("skill_execute");
2398
2398
  });
2399
2399
 
2400
+ test("a shared-screen turn is offered the surface's controls beside the instructions", async () => {
2401
+ registerHubClient({
2402
+ hub: assistantEventHub,
2403
+ clientId: "annotation-client",
2404
+ interfaceId: "macos",
2405
+ actorPrincipalId: "user-123",
2406
+ capabilities: ["host_cu", "host_cu_annotate"],
2407
+ });
2408
+ const result = await preactivatedFor({
2409
+ routingLeg: "escalated",
2410
+ macosDesktopSession: true,
2411
+ screenSharing: true,
2412
+ actorPrincipalId: "user-123",
2413
+ shareTargets: {
2414
+ targets: [
2415
+ {
2416
+ id: "t1",
2417
+ label: "root_Filters",
2418
+ role: "AXButton",
2419
+ section: "Toolbar",
2420
+ x: 0.8,
2421
+ y: 0.05,
2422
+ width: 0.05,
2423
+ height: 0.03,
2424
+ },
2425
+ ],
2426
+ total: 1,
2427
+ },
2428
+ });
2429
+ expect(result.promptDuringLoop).toContain("ID: screen-annotation");
2430
+ expect(result.promptDuringLoop).toContain("<shared_screen_controls>");
2431
+ expect(result.promptDuringLoop).toContain(
2432
+ '- "root_Filters" (button, top right, in "Toolbar")',
2433
+ );
2434
+ });
2435
+
2436
+ test("a shared-screen turn with no snapshot keeps the instructions alone", async () => {
2437
+ registerHubClient({
2438
+ hub: assistantEventHub,
2439
+ clientId: "annotation-client",
2440
+ interfaceId: "macos",
2441
+ actorPrincipalId: "user-123",
2442
+ capabilities: ["host_cu", "host_cu_annotate"],
2443
+ });
2444
+ const result = await preactivatedFor({
2445
+ routingLeg: "escalated",
2446
+ macosDesktopSession: true,
2447
+ screenSharing: true,
2448
+ actorPrincipalId: "user-123",
2449
+ });
2450
+ expect(result.promptDuringLoop).toContain("ID: screen-annotation");
2451
+ expect(result.promptDuringLoop).not.toContain("<shared_screen_controls>");
2452
+ });
2453
+
2400
2454
  test.each([
2401
2455
  { screenSharing: false },
2402
2456
  { actorPrincipalId: "other-user" },
@@ -26,11 +26,15 @@ import {
26
26
  type AdmissionPolicy,
27
27
  isAdmissionPolicy,
28
28
  } from "@vellumai/gateway-client";
29
+ import { z } from "zod";
29
30
 
30
31
  import type { ChannelId } from "../channels/types.js";
31
32
  import { ipcCall } from "../ipc/gateway-client.js";
32
33
 
33
34
  const CACHE_TTL_MS = 5_000;
35
+ const AdmissionPolicyResponseSchema = z.object({
36
+ policy: z.unknown(),
37
+ });
34
38
 
35
39
  // Short IPC timeout so admission fails closed PROMPTLY: a gateway that accepts
36
40
  // the socket but stalls must never delay a live call handshake. 1s is generous
@@ -64,20 +68,22 @@ async function fetchAdmissionPolicy(
64
68
  // ipcCall() returns undefined on transport failure (socket not found,
65
69
  // timeout, parse error). That, a throw, or a malformed shape is a FAILURE:
66
70
  // fail closed without caching so the next setup re-attempts the IPC.
67
- const result = (await ipcCall(
68
- "get_channel_admission_policy",
69
- { channelType },
70
- ADMISSION_IPC_TIMEOUT_MS,
71
- )) as { policy?: unknown } | null | undefined;
71
+ const parsed = AdmissionPolicyResponseSchema.safeParse(
72
+ await ipcCall(
73
+ "get_channel_admission_policy",
74
+ { channelType },
75
+ ADMISSION_IPC_TIMEOUT_MS,
76
+ ),
77
+ );
72
78
 
73
- if (result === undefined) {
79
+ if (!parsed.success) {
74
80
  return { ok: false };
75
81
  }
76
- if (result && isAdmissionPolicy(result.policy)) {
77
- return { ok: true, policy: result.policy };
82
+ if (isAdmissionPolicy(parsed.data.policy)) {
83
+ return { ok: true, policy: parsed.data.policy };
78
84
  }
79
85
  // Explicit "no enforcement" — the gateway successfully answered. Cacheable.
80
- if (result && result.policy === null) {
86
+ if (parsed.data.policy === null) {
81
87
  return { ok: true, policy: null };
82
88
  }
83
89
  // Anything else (missing/invalid policy field) is a malformed shape.
@@ -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": {
@@ -1368,9 +1407,9 @@
1368
1407
  "source": {
1369
1408
  "source": "github",
1370
1409
  "repo": "vellum-ai/runway-plugin",
1371
- "ref": "2a642f14ee8ca5032efefae06b6a610dec13e1a1"
1410
+ "ref": "bb017a1e018c7be140c02c8463273c2d85f3b9d9"
1372
1411
  },
1373
- "description": "Collaboratively direct, approve, create, track, cancel, and download Runway Gen-4.5 videos.",
1412
+ "description": "Securely set up, collaboratively direct, approve, create, track, cancel, and download Runway Gen-4.5 videos.",
1374
1413
  "category": "other",
1375
1414
  "submittedBy": "marina"
1376
1415
  },
@@ -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
  }