@vellumai/assistant 0.3.4 → 0.3.6

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 (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Extracts REST endpoint templates from a session recording and persists them
3
+ * to disk so the Amazon client can use captured URL patterns instead of
4
+ * stale static fallbacks.
5
+ *
6
+ * Captured requests are saved to ~/.vellum/workspace/data/amazon/captured-requests.json
7
+ */
8
+
9
+ import { join } from 'node:path';
10
+ import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
11
+ import { getDataDir } from '../util/platform.js';
12
+ import type { SessionRecording } from '../tools/browser/network-recording-types.js';
13
+
14
+ export type AmazonRequestKey =
15
+ | 'search'
16
+ | 'addToCart'
17
+ | 'addToCartFresh'
18
+ | 'viewCart'
19
+ | 'freshDeliveryWindows'
20
+ | 'placeOrder';
21
+
22
+ export interface CapturedRequest {
23
+ key: AmazonRequestKey;
24
+ method: string;
25
+ urlPattern: string;
26
+ capturedAt: number;
27
+ /** POST body fields extracted from recordings (keys only, values omitted). */
28
+ bodyFields?: string[];
29
+ }
30
+
31
+ function getCapturedRequestsPath(): string {
32
+ return join(getDataDir(), 'amazon', 'captured-requests.json');
33
+ }
34
+
35
+ /**
36
+ * Classify an Amazon URL to a logical key.
37
+ * Returns null if the URL doesn't match any known pattern.
38
+ */
39
+ function classifyUrl(url: string, method: string): AmazonRequestKey | null {
40
+ const withoutQuery = url.split('?')[0];
41
+
42
+ if (url.includes('/s?') || /\/s\/[^/]/.test(withoutQuery)) return 'search';
43
+ if (withoutQuery.includes('/alm/addtofreshcart') && method === 'POST') return 'addToCartFresh';
44
+ if (withoutQuery.includes('/gp/add-to-cart') || withoutQuery.includes('/cart/smart-add')) return 'addToCart';
45
+ // Prefer the lightweight JSON endpoint over the HTML cart page
46
+ if (withoutQuery.includes('/cart/add-to-cart/get-cart-items')) return 'viewCart';
47
+ if (withoutQuery.includes('/gp/cart/view') || withoutQuery.endsWith('/cart/') || withoutQuery.endsWith('/cart')) return 'viewCart';
48
+ if (withoutQuery.includes('/fresh/deliverywindows')) return 'freshDeliveryWindows';
49
+ if (withoutQuery.includes('/gp/buy/spc') && method === 'POST') return 'placeOrder';
50
+
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * Extract REST endpoint templates from a session recording's network entries.
56
+ * Filters for amazon.com URLs, classifies them, deduplicates by key
57
+ * (keeps last occurrence).
58
+ */
59
+ export function extractRequests(recording: SessionRecording): CapturedRequest[] {
60
+ const byKey = new Map<AmazonRequestKey, CapturedRequest>();
61
+
62
+ for (const entry of recording.networkEntries) {
63
+ const url = entry.request.url;
64
+ if (!url.includes('amazon.com')) continue;
65
+
66
+ const method = (entry.request.method ?? 'GET').toUpperCase();
67
+ const key = classifyUrl(url, method);
68
+ if (!key) continue;
69
+
70
+ // Use base URL (without query params) as the pattern
71
+ const urlPattern = url.split('?')[0];
72
+
73
+ // Extract field names from JSON POST bodies (values omitted — they're session-specific)
74
+ let bodyFields: string[] | undefined;
75
+ const postData = entry.request.postData;
76
+ if (postData && method === 'POST') {
77
+ try {
78
+ const parsed = JSON.parse(postData);
79
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
80
+ bodyFields = Object.keys(parsed);
81
+ }
82
+ } catch {
83
+ // form-encoded body — extract field names
84
+ bodyFields = postData
85
+ .split('&')
86
+ .map(p => decodeURIComponent(p.split('=')[0]))
87
+ .filter(Boolean);
88
+ }
89
+ }
90
+
91
+ byKey.set(key, {
92
+ key,
93
+ method,
94
+ urlPattern,
95
+ capturedAt: entry.timestamp,
96
+ ...(bodyFields ? { bodyFields } : {}),
97
+ });
98
+ }
99
+
100
+ return Array.from(byKey.values());
101
+ }
102
+
103
+ /**
104
+ * Merge new captured requests with existing ones on disk (newer wins),
105
+ * then write to disk.
106
+ */
107
+ export function saveRequests(requests: CapturedRequest[]): string {
108
+ const existing = loadCapturedRequests();
109
+
110
+ for (const req of requests) {
111
+ const prev = existing[req.key];
112
+ if (!prev || req.capturedAt >= prev.capturedAt) {
113
+ existing[req.key] = req;
114
+ }
115
+ }
116
+
117
+ const filePath = getCapturedRequestsPath();
118
+ mkdirSync(join(filePath, '..'), { recursive: true });
119
+ writeFileSync(filePath, JSON.stringify(existing, null, 2), 'utf-8');
120
+ return filePath;
121
+ }
122
+
123
+ /**
124
+ * Load captured requests from disk. Returns a map keyed by logical key.
125
+ */
126
+ export function loadCapturedRequests(): Record<string, CapturedRequest> {
127
+ const filePath = getCapturedRequestsPath();
128
+ if (!existsSync(filePath)) return {};
129
+ try {
130
+ const data = readFileSync(filePath, 'utf-8');
131
+ return JSON.parse(data) as Record<string, CapturedRequest>;
132
+ } catch {
133
+ return {};
134
+ }
135
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Amazon session persistence.
3
+ * Stores/loads auth cookies from a recording or manual login.
4
+ */
5
+
6
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { getDataDir } from '../util/platform.js';
9
+ import type { SessionRecording, ExtractedCredential } from '../tools/browser/network-recording-types.js';
10
+
11
+ export interface AmazonSession {
12
+ cookies: ExtractedCredential[];
13
+ importedAt: string;
14
+ recordingId?: string;
15
+ }
16
+
17
+ function getSessionDir(): string {
18
+ return join(getDataDir(), 'amazon');
19
+ }
20
+
21
+ function getSessionPath(): string {
22
+ return join(getSessionDir(), 'session.json');
23
+ }
24
+
25
+ export function loadSession(): AmazonSession | null {
26
+ const path = getSessionPath();
27
+ if (!existsSync(path)) return null;
28
+ try {
29
+ return JSON.parse(readFileSync(path, 'utf-8')) as AmazonSession;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ export function saveSession(session: AmazonSession): void {
36
+ const dir = getSessionDir();
37
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
38
+ writeFileSync(getSessionPath(), JSON.stringify(session, null, 2));
39
+ }
40
+
41
+ export function clearSession(): void {
42
+ const path = getSessionPath();
43
+ if (existsSync(path)) {
44
+ unlinkSync(path);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Import cookies from a Ride Shotgun recording file.
50
+ * Validates that the recording contains Amazon's required auth cookies.
51
+ */
52
+ export function importFromRecording(recordingPath: string): AmazonSession {
53
+ if (!existsSync(recordingPath)) {
54
+ throw new Error(`Recording not found: ${recordingPath}`);
55
+ }
56
+ const recording = JSON.parse(readFileSync(recordingPath, 'utf-8')) as SessionRecording;
57
+ if (!recording.cookies?.length) {
58
+ throw new Error('Recording contains no cookies');
59
+ }
60
+
61
+ const cookieNames = new Set(recording.cookies.map(c => c.name));
62
+
63
+ if (!cookieNames.has('session-id')) {
64
+ throw new Error(
65
+ 'Recording is missing required Amazon cookie: session-id. ' +
66
+ 'Make sure you are logged in to Amazon.',
67
+ );
68
+ }
69
+ if (!cookieNames.has('ubid-main')) {
70
+ throw new Error(
71
+ 'Recording is missing required Amazon cookie: ubid-main. ' +
72
+ 'Make sure you are logged in to Amazon.',
73
+ );
74
+ }
75
+ if (!cookieNames.has('at-main') && !cookieNames.has('x-main')) {
76
+ throw new Error(
77
+ 'Recording is missing required Amazon auth cookie (at-main or x-main). ' +
78
+ 'Make sure you are fully logged in to Amazon.',
79
+ );
80
+ }
81
+
82
+ const session: AmazonSession = {
83
+ cookies: recording.cookies,
84
+ importedAt: new Date().toISOString(),
85
+ recordingId: recording.id,
86
+ };
87
+ saveSession(session);
88
+ return session;
89
+ }
90
+
91
+ /**
92
+ * Build a Cookie header string from the session.
93
+ */
94
+ export function getCookieHeader(session: AmazonSession): string {
95
+ return session.cookies
96
+ .map(c => `${c.name}=${c.value}`)
97
+ .join('; ');
98
+ }
99
+
100
+ /**
101
+ * Get the anti-CSRF token from session cookies.
102
+ * Amazon uses anti-csrftoken-a2z or csrf-main for cart POST requests.
103
+ */
104
+ export function getAntiCsrfToken(session: AmazonSession): string | undefined {
105
+ return (
106
+ session.cookies.find(c => c.name === 'anti-csrftoken-a2z')?.value ??
107
+ session.cookies.find(c => c.name === 'csrf-main')?.value
108
+ );
109
+ }
@@ -7,9 +7,10 @@
7
7
  * preferences), so it belongs on disk rather than in the SQLite database.
8
8
  */
9
9
 
10
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
10
+ import { writeFileSync, mkdirSync } from 'node:fs';
11
11
  import { join, dirname } from 'node:path';
12
12
  import { getWorkspaceDir } from '../util/platform.js';
13
+ import { readTextFileSync } from '../util/fs.js';
13
14
  import { getLogger } from '../util/logger.js';
14
15
  import type { AutonomyConfig, AutonomyTier } from './types.js';
15
16
  import { DEFAULT_AUTONOMY_CONFIG, AUTONOMY_TIERS } from './types.js';
@@ -25,14 +26,13 @@ function getAutonomyConfigPath(): string {
25
26
  * Returns defaults if the file doesn't exist or is malformed.
26
27
  */
27
28
  export function getAutonomyConfig(): AutonomyConfig {
28
- const configPath = getAutonomyConfigPath();
29
- if (!existsSync(configPath)) {
29
+ const raw = readTextFileSync(getAutonomyConfigPath());
30
+ if (raw == null) {
30
31
  return structuredClone(DEFAULT_AUTONOMY_CONFIG);
31
32
  }
32
33
 
33
34
  try {
34
- const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
35
- return validateAutonomyConfig(raw);
35
+ return validateAutonomyConfig(JSON.parse(raw));
36
36
  } catch (err) {
37
37
  log.warn({ err }, 'Failed to parse autonomy config; using defaults');
38
38
  return structuredClone(DEFAULT_AUTONOMY_CONFIG);
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Higher-level API over ExtensionRelayServer.
3
+ *
4
+ * Provides convenience wrappers for common operations:
5
+ * - relayEval — evaluate JS in a tab (drop-in for cdpEval)
6
+ * - relayCookies — fetch cookies for a domain
7
+ * - waitForExtension — poll until the extension connects
8
+ */
9
+
10
+ import { extensionRelayServer } from './server.js';
11
+ import type { CookieSpec } from './protocol.js';
12
+
13
+ const WAIT_POLL_INTERVAL_MS = 250;
14
+
15
+ /**
16
+ * Evaluate a JavaScript expression in a tab matching the given URL pattern.
17
+ *
18
+ * @param urlPattern Glob or substring matched against open tab URLs.
19
+ * @param script JS source string to evaluate in the tab's MAIN world.
20
+ * @param timeoutMs Per-command timeout (default: server default).
21
+ * @returns The return value of the script, as returned by the extension.
22
+ */
23
+ export async function relayEval(
24
+ urlPattern: string,
25
+ script: string,
26
+ timeoutMs?: number,
27
+ ): Promise<unknown> {
28
+ // Find the tab first
29
+ const findResp = await extensionRelayServer.sendCommand(
30
+ { action: 'find_tab', url: urlPattern },
31
+ timeoutMs,
32
+ );
33
+ if (!findResp.success) {
34
+ throw new Error(`relayEval: find_tab failed — ${findResp.error ?? 'unknown error'}`);
35
+ }
36
+ const tabId = findResp.tabId;
37
+ if (tabId === undefined) {
38
+ throw new Error(`relayEval: no tab found matching "${urlPattern}"`);
39
+ }
40
+
41
+ const evalResp = await extensionRelayServer.sendCommand(
42
+ { action: 'evaluate', tabId, code: script },
43
+ timeoutMs,
44
+ );
45
+ if (!evalResp.success) {
46
+ throw new Error(`relayEval: evaluate failed — ${evalResp.error ?? 'unknown error'}`);
47
+ }
48
+ return evalResp.result;
49
+ }
50
+
51
+ /**
52
+ * Retrieve cookies for a domain.
53
+ *
54
+ * @param domain e.g. "amazon.com"
55
+ * @returns Array of cookie objects returned by the extension.
56
+ */
57
+ export async function relayCookies(domain: string): Promise<unknown[]> {
58
+ const resp = await extensionRelayServer.sendCommand({ action: 'get_cookies', domain });
59
+ if (!resp.success) {
60
+ throw new Error(`relayCookies: failed — ${resp.error ?? 'unknown error'}`);
61
+ }
62
+ return Array.isArray(resp.result) ? resp.result : [];
63
+ }
64
+
65
+ /**
66
+ * Set a cookie via the extension.
67
+ */
68
+ export async function relaySetCookie(cookie: CookieSpec): Promise<void> {
69
+ const resp = await extensionRelayServer.sendCommand({ action: 'set_cookie', cookie });
70
+ if (!resp.success) {
71
+ throw new Error(`relaySetCookie: failed — ${resp.error ?? 'unknown error'}`);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Navigate a tab (or open a new one) to a URL.
77
+ */
78
+ export async function relayNavigate(url: string, tabId?: number): Promise<number> {
79
+ const resp = await extensionRelayServer.sendCommand({ action: 'navigate', url, tabId });
80
+ if (!resp.success) {
81
+ throw new Error(`relayNavigate: failed — ${resp.error ?? 'unknown error'}`);
82
+ }
83
+ return resp.tabId!;
84
+ }
85
+
86
+ /**
87
+ * Open a new tab and navigate to a URL.
88
+ */
89
+ export async function relayNewTab(url: string): Promise<number> {
90
+ const resp = await extensionRelayServer.sendCommand({ action: 'new_tab', url });
91
+ if (!resp.success) {
92
+ throw new Error(`relayNewTab: failed — ${resp.error ?? 'unknown error'}`);
93
+ }
94
+ return resp.tabId!;
95
+ }
96
+
97
+ /**
98
+ * Take a screenshot of the currently visible tab.
99
+ *
100
+ * @param tabId Optional tab ID; if omitted, captures the active tab in the focused window.
101
+ * @returns Base64-encoded PNG data URL.
102
+ */
103
+ export async function relayScreenshot(tabId?: number): Promise<string> {
104
+ const resp = await extensionRelayServer.sendCommand({ action: 'screenshot', tabId });
105
+ if (!resp.success) {
106
+ throw new Error(`relayScreenshot: failed — ${resp.error ?? 'unknown error'}`);
107
+ }
108
+ return resp.result as string;
109
+ }
110
+
111
+ /**
112
+ * Poll until the Chrome extension connects or the timeout expires.
113
+ *
114
+ * @param timeoutMs Total wait time in ms (default: 10 000).
115
+ * @throws Error if the extension does not connect within the timeout.
116
+ */
117
+ export async function waitForExtension(timeoutMs = 10_000): Promise<void> {
118
+ const deadline = Date.now() + timeoutMs;
119
+ while (Date.now() < deadline) {
120
+ if (extensionRelayServer.getStatus().connected) return;
121
+ await new Promise<void>((resolve) => setTimeout(resolve, WAIT_POLL_INTERVAL_MS));
122
+ }
123
+ throw new Error(`waitForExtension: Chrome extension did not connect within ${timeoutMs}ms`);
124
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Protocol types for the Chrome extension relay bridge.
3
+ *
4
+ * Messages flow:
5
+ * Assistant → ExtensionRelayServer → WebSocket → Chrome Extension → Tab (JS eval)
6
+ */
7
+
8
+ export interface CookieSpec {
9
+ url: string;
10
+ name: string;
11
+ value: string;
12
+ domain?: string;
13
+ path?: string;
14
+ secure?: boolean;
15
+ httpOnly?: boolean;
16
+ expirationDate?: number;
17
+ }
18
+
19
+ /**
20
+ * Command sent from the server to the extension over WebSocket.
21
+ */
22
+ export interface ExtensionCommand {
23
+ id: string; // UUID
24
+ action:
25
+ | 'evaluate'
26
+ | 'navigate'
27
+ | 'get_cookies'
28
+ | 'set_cookie'
29
+ | 'screenshot'
30
+ | 'find_tab'
31
+ | 'new_tab';
32
+ tabId?: number;
33
+ code?: string; // for evaluate
34
+ url?: string; // for navigate / find_tab / new_tab
35
+ domain?: string; // for get_cookies
36
+ cookie?: CookieSpec;
37
+ timeoutMs?: number;
38
+ }
39
+
40
+ /**
41
+ * Response sent from the extension back to the server.
42
+ */
43
+ export interface ExtensionResponse {
44
+ id: string;
45
+ success: boolean;
46
+ result?: unknown;
47
+ error?: string;
48
+ tabId?: number;
49
+ }
50
+
51
+ /**
52
+ * Periodic heartbeat from the extension to the server.
53
+ */
54
+ export interface ExtensionHeartbeat {
55
+ type: 'heartbeat';
56
+ extensionVersion: string;
57
+ connectedTabs: number;
58
+ }
59
+
60
+ /**
61
+ * Any message received from the extension (heartbeat or command response).
62
+ */
63
+ export type ExtensionInboundMessage = ExtensionHeartbeat | ExtensionResponse;
@@ -0,0 +1,177 @@
1
+ /**
2
+ * WebSocket server for the Chrome extension relay bridge.
3
+ *
4
+ * Holds a single active extension connection. Commands are sent as JSON
5
+ * and matched to pending Promises by UUID.
6
+ */
7
+
8
+ import type { ServerWebSocket } from 'bun';
9
+ import { getLogger } from '../util/logger.js';
10
+ import type {
11
+ ExtensionCommand,
12
+ ExtensionResponse,
13
+ ExtensionHeartbeat,
14
+ ExtensionInboundMessage,
15
+ } from './protocol.js';
16
+
17
+ const log = getLogger('browser-extension-relay');
18
+
19
+ const DEFAULT_COMMAND_TIMEOUT_MS = 30_000;
20
+
21
+ interface PendingCommand {
22
+ resolve: (value: ExtensionResponse) => void;
23
+ reject: (reason: Error) => void;
24
+ timer: ReturnType<typeof setTimeout>;
25
+ }
26
+
27
+ export interface BrowserRelayWebSocketData {
28
+ wsType: 'browser-relay';
29
+ connectionId: string;
30
+ }
31
+
32
+ export interface ExtensionRelayStatus {
33
+ connected: boolean;
34
+ connectionId: string | null;
35
+ lastHeartbeatAt: number | null;
36
+ pendingCommandCount: number;
37
+ }
38
+
39
+ /**
40
+ * Manages the single active Chrome extension WebSocket connection and
41
+ * dispatches commands to it.
42
+ */
43
+ export class ExtensionRelayServer {
44
+ private ws: ServerWebSocket<BrowserRelayWebSocketData> | null = null;
45
+ private connectionId: string | null = null;
46
+ private lastHeartbeatAt: number | null = null;
47
+ private pendingCommands = new Map<string, PendingCommand>();
48
+
49
+ // ── WebSocket lifecycle ────────────────────────────────────────────
50
+
51
+ handleOpen(ws: ServerWebSocket<BrowserRelayWebSocketData>): void {
52
+ const newId = ws.data.connectionId;
53
+
54
+ if (this.ws) {
55
+ log.warn(
56
+ { oldConnectionId: this.connectionId, newConnectionId: newId },
57
+ 'Browser extension relay: new connection displaced an existing one',
58
+ );
59
+ try {
60
+ this.ws.close(1001, 'Displaced by new connection');
61
+ } catch {
62
+ // best-effort
63
+ }
64
+ this.rejectAllPending(new Error('Extension reconnected — previous commands cancelled'));
65
+ }
66
+
67
+ this.ws = ws;
68
+ this.connectionId = newId;
69
+ log.info({ connectionId: newId }, 'Browser extension relay connected');
70
+ }
71
+
72
+ handleMessage(ws: ServerWebSocket<BrowserRelayWebSocketData>, raw: string): void {
73
+ let msg: ExtensionInboundMessage;
74
+ try {
75
+ msg = JSON.parse(raw) as ExtensionInboundMessage;
76
+ } catch {
77
+ log.warn({ connectionId: ws.data.connectionId }, 'Browser extension relay: failed to parse message');
78
+ return;
79
+ }
80
+
81
+ if ('type' in msg && msg.type === 'heartbeat') {
82
+ this.handleHeartbeat(msg);
83
+ return;
84
+ }
85
+
86
+ // Otherwise it's a command response
87
+ const response = msg as ExtensionResponse;
88
+ const pending = this.pendingCommands.get(response.id);
89
+ if (!pending) {
90
+ log.warn({ id: response.id }, 'Browser extension relay: received response for unknown command id');
91
+ return;
92
+ }
93
+
94
+ clearTimeout(pending.timer);
95
+ this.pendingCommands.delete(response.id);
96
+ pending.resolve(response);
97
+ }
98
+
99
+ handleClose(ws: ServerWebSocket<BrowserRelayWebSocketData>, code: number, reason?: string): void {
100
+ const closedId = ws.data.connectionId;
101
+ if (this.connectionId !== closedId) {
102
+ // Stale close for a displaced connection — ignore
103
+ return;
104
+ }
105
+
106
+ log.info({ connectionId: closedId, code, reason }, 'Browser extension relay disconnected');
107
+ this.ws = null;
108
+ this.connectionId = null;
109
+ this.rejectAllPending(new Error(`Extension disconnected (code=${code})`));
110
+ }
111
+
112
+ // ── Command dispatch ───────────────────────────────────────────────
113
+
114
+ /**
115
+ * Send a command to the extension and wait for its response.
116
+ */
117
+ sendCommand(
118
+ command: Omit<ExtensionCommand, 'id'>,
119
+ timeoutMs: number = DEFAULT_COMMAND_TIMEOUT_MS,
120
+ ): Promise<ExtensionResponse> {
121
+ if (!this.ws) {
122
+ return Promise.reject(new Error('Browser extension is not connected'));
123
+ }
124
+
125
+ const id = crypto.randomUUID();
126
+ const fullCommand: ExtensionCommand = { ...command, id };
127
+
128
+ return new Promise<ExtensionResponse>((resolve, reject) => {
129
+ const timer = setTimeout(() => {
130
+ this.pendingCommands.delete(id);
131
+ reject(new Error(`Browser extension command timed out after ${timeoutMs}ms (action=${command.action})`));
132
+ }, timeoutMs);
133
+
134
+ this.pendingCommands.set(id, { resolve, reject, timer });
135
+
136
+ try {
137
+ this.ws!.send(JSON.stringify(fullCommand));
138
+ } catch (err) {
139
+ clearTimeout(timer);
140
+ this.pendingCommands.delete(id);
141
+ reject(err instanceof Error ? err : new Error(String(err)));
142
+ }
143
+ });
144
+ }
145
+
146
+ // ── Status ─────────────────────────────────────────────────────────
147
+
148
+ getStatus(): ExtensionRelayStatus {
149
+ return {
150
+ connected: !!this.ws,
151
+ connectionId: this.connectionId,
152
+ lastHeartbeatAt: this.lastHeartbeatAt,
153
+ pendingCommandCount: this.pendingCommands.size,
154
+ };
155
+ }
156
+
157
+ // ── Private helpers ────────────────────────────────────────────────
158
+
159
+ private handleHeartbeat(msg: ExtensionHeartbeat): void {
160
+ this.lastHeartbeatAt = Date.now();
161
+ log.debug(
162
+ { extensionVersion: msg.extensionVersion, connectedTabs: msg.connectedTabs },
163
+ 'Browser extension heartbeat received',
164
+ );
165
+ }
166
+
167
+ private rejectAllPending(err: Error): void {
168
+ for (const [id, pending] of this.pendingCommands) {
169
+ clearTimeout(pending.timer);
170
+ pending.reject(err);
171
+ this.pendingCommands.delete(id);
172
+ }
173
+ }
174
+ }
175
+
176
+ /** Module-level singleton — imported by http-server and client. */
177
+ export const extensionRelayServer = new ExtensionRelayServer();
@@ -45,7 +45,7 @@ export function extractRemoteUrls(html: string): string[] {
45
45
  // Match src="..." attributes on any element
46
46
  const srcRe = /\bsrc\s*=\s*(?:"([^"]*?)"|'([^']*?)'|([^\s>]+))/gi;
47
47
  let m: RegExpExecArray | null;
48
- while ((m = srcRe.exec(html)) !== null) {
48
+ while ((m = srcRe.exec(html)) != null) {
49
49
  const url = m[1] ?? m[2] ?? m[3];
50
50
  if (url && /^https?:\/\//i.test(url)) {
51
51
  urls.add(url);
@@ -55,7 +55,7 @@ export function extractRemoteUrls(html: string): string[] {
55
55
  // Match href="..." on any element except navigation/resolution tags (not assets).
56
56
  // Captures the tag name and href value so we can skip them.
57
57
  const hrefRe = /<(\w+)\b[^>]*?\bhref\s*=\s*(?:"([^"]*?)"|'([^']*?)'|([^\s>]+))[^>]*?\/?>/gi;
58
- while ((m = hrefRe.exec(html)) !== null) {
58
+ while ((m = hrefRe.exec(html)) != null) {
59
59
  const tagName = m[1];
60
60
  if (['a', 'base', 'area'].includes(tagName.toLowerCase())) continue;
61
61
  const url = m[2] ?? m[3] ?? m[4];
@@ -66,7 +66,7 @@ export function extractRemoteUrls(html: string): string[] {
66
66
 
67
67
  // Match CSS url() references (inline styles and <style> blocks)
68
68
  const urlRe = /url\(\s*(?:"([^"]*?)"|'([^']*?)'|([^)"'\s]+))\s*\)/gi;
69
- while ((m = urlRe.exec(html)) !== null) {
69
+ while ((m = urlRe.exec(html)) != null) {
70
70
  const url = m[1] ?? m[2] ?? m[3];
71
71
  if (url && /^https?:\/\//i.test(url)) {
72
72
  urls.add(url);
@@ -34,7 +34,7 @@ export type SigningCallback = (payload: string) => Promise<{
34
34
  * Recursively sort object keys alphabetically for canonical JSON.
35
35
  */
36
36
  function sortKeysDeep(obj: unknown): unknown {
37
- if (obj === null || obj === undefined || typeof obj !== 'object') {
37
+ if (obj == null || typeof obj !== 'object') {
38
38
  return obj;
39
39
  }
40
40
  if (Array.isArray(obj)) {
@@ -23,7 +23,7 @@ export interface SignatureVerificationResult {
23
23
  * Recursively sort object keys alphabetically for canonical JSON.
24
24
  */
25
25
  function sortKeysDeep(obj: unknown): unknown {
26
- if (obj === null || obj === undefined || typeof obj !== 'object') {
26
+ if (obj == null || typeof obj !== 'object') {
27
27
  return obj;
28
28
  }
29
29
  if (Array.isArray(obj)) {