@vellumai/assistant 0.4.46 → 0.4.49

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 (382) hide show
  1. package/ARCHITECTURE.md +7 -7
  2. package/README.md +2 -23
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/architecture/security.md +5 -5
  6. package/docs/runbook-trusted-contacts.md +3 -8
  7. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  8. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  9. package/package.json +1 -1
  10. package/src/__tests__/actor-token-service.test.ts +0 -1
  11. package/src/__tests__/anthropic-provider.test.ts +156 -0
  12. package/src/__tests__/approval-cascade.test.ts +810 -0
  13. package/src/__tests__/approval-primitive.test.ts +0 -1
  14. package/src/__tests__/approval-routes-http.test.ts +2 -0
  15. package/src/__tests__/assistant-attachments.test.ts +12 -34
  16. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  17. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  18. package/src/__tests__/browser-fill-credential.test.ts +5 -2
  19. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  20. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
  21. package/src/__tests__/channel-guardian.test.ts +0 -2
  22. package/src/__tests__/channel-readiness-routes.test.ts +35 -25
  23. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  24. package/src/__tests__/checker.test.ts +9 -29
  25. package/src/__tests__/cli.test.ts +23 -0
  26. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  27. package/src/__tests__/computer-use-tools.test.ts +2 -19
  28. package/src/__tests__/config-watcher.test.ts +0 -1
  29. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  30. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  31. package/src/__tests__/context-token-estimator.test.ts +196 -13
  32. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  33. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  34. package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  36. package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
  37. package/src/__tests__/credential-broker-server-use.test.ts +22 -21
  38. package/src/__tests__/credential-broker.test.ts +2 -1
  39. package/src/__tests__/credential-metadata-store.test.ts +239 -26
  40. package/src/__tests__/credential-resolve.test.ts +5 -4
  41. package/src/__tests__/credential-security-e2e.test.ts +8 -8
  42. package/src/__tests__/credential-security-invariants.test.ts +111 -7
  43. package/src/__tests__/credential-vault-unit.test.ts +287 -54
  44. package/src/__tests__/credential-vault.test.ts +406 -12
  45. package/src/__tests__/credentials-cli.test.ts +82 -6
  46. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  47. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  48. package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
  49. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  50. package/src/__tests__/gemini-image-service.test.ts +75 -45
  51. package/src/__tests__/gemini-provider.test.ts +9 -6
  52. package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
  53. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
  54. package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
  55. package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
  56. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  57. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  58. package/src/__tests__/guardian-grant-minting.test.ts +35 -0
  59. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  60. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  61. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  62. package/src/__tests__/heartbeat-service.test.ts +0 -1
  63. package/src/__tests__/host-cu-proxy.test.ts +629 -0
  64. package/src/__tests__/host-shell-tool.test.ts +27 -15
  65. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  66. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  67. package/src/__tests__/integration-status.test.ts +38 -25
  68. package/src/__tests__/intent-routing.test.ts +0 -1
  69. package/src/__tests__/invite-routes-http.test.ts +10 -9
  70. package/src/__tests__/keychain-broker-client.test.ts +11 -43
  71. package/src/__tests__/managed-proxy-context.test.ts +5 -3
  72. package/src/__tests__/media-generate-image.test.ts +63 -2
  73. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
  74. package/src/__tests__/messaging-send-tool.test.ts +4 -6
  75. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  76. package/src/__tests__/oauth-cli.test.ts +373 -14
  77. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  78. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  79. package/src/__tests__/oauth-store.test.ts +756 -0
  80. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  81. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  82. package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
  83. package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
  84. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  85. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  86. package/src/__tests__/recording-handler.test.ts +3 -4
  87. package/src/__tests__/registry.test.ts +2 -2
  88. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  89. package/src/__tests__/schedule-store.test.ts +0 -1
  90. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  91. package/src/__tests__/schema-transforms.test.ts +226 -0
  92. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  93. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  94. package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
  95. package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
  96. package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
  97. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  98. package/src/__tests__/secret-onetime-send.test.ts +5 -3
  99. package/src/__tests__/send-endpoint-busy.test.ts +21 -6
  100. package/src/__tests__/sequence-store.test.ts +0 -1
  101. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  102. package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
  103. package/src/__tests__/skill-include-graph.test.ts +66 -0
  104. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  105. package/src/__tests__/skill-load-tool.test.ts +149 -1
  106. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  107. package/src/__tests__/skills-uninstall.test.ts +3 -3
  108. package/src/__tests__/skills.test.ts +3 -12
  109. package/src/__tests__/slack-channel-config.test.ts +76 -11
  110. package/src/__tests__/slack-share-routes.test.ts +17 -14
  111. package/src/__tests__/system-prompt.test.ts +0 -1
  112. package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
  113. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  114. package/src/__tests__/terminal-tools.test.ts +4 -3
  115. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  116. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  117. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  118. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  119. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  120. package/src/__tests__/tool-executor.test.ts +0 -1
  121. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  122. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  123. package/src/__tests__/trust-store.test.ts +1 -22
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  126. package/src/__tests__/twilio-config.test.ts +2 -1
  127. package/src/__tests__/twilio-provider.test.ts +4 -2
  128. package/src/__tests__/twilio-routes.test.ts +5 -20
  129. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  130. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  131. package/src/agent/ax-tree-compaction.test.ts +235 -0
  132. package/src/agent/loop.ts +76 -130
  133. package/src/calls/call-domain.ts +8 -10
  134. package/src/calls/relay-server.ts +9 -13
  135. package/src/calls/twilio-config.ts +4 -8
  136. package/src/calls/twilio-provider.ts +2 -1
  137. package/src/calls/twilio-rest.ts +2 -1
  138. package/src/calls/twilio-routes.ts +1 -2
  139. package/src/calls/voice-ingress-preflight.ts +1 -1
  140. package/src/cli/commands/browser-relay.ts +46 -15
  141. package/src/cli/commands/completions.ts +0 -3
  142. package/src/cli/commands/credentials.ts +110 -23
  143. package/src/cli/commands/oauth/apps.ts +255 -0
  144. package/src/cli/commands/oauth/connections.ts +299 -0
  145. package/src/cli/commands/oauth/index.ts +52 -0
  146. package/src/cli/commands/oauth/providers.ts +242 -0
  147. package/src/cli/commands/skills.ts +4 -338
  148. package/src/cli/program.ts +1 -5
  149. package/src/cli/reference.ts +1 -3
  150. package/src/cli.ts +3 -2
  151. package/src/config/assistant-feature-flags.ts +0 -3
  152. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  153. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
  154. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  155. package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
  156. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
  157. package/src/config/bundled-skills/gmail/SKILL.md +4 -4
  158. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
  159. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
  160. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
  161. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
  162. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
  163. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
  164. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
  165. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
  166. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
  167. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
  168. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
  169. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
  170. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
  171. package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
  172. package/src/config/bundled-skills/google-calendar/calendar-client.ts +90 -44
  173. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
  174. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
  175. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
  176. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
  177. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
  178. package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
  179. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
  180. package/src/config/bundled-skills/messaging/SKILL.md +6 -6
  181. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
  182. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
  183. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
  184. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
  185. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
  186. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
  187. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
  188. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
  189. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
  190. package/src/config/bundled-skills/messaging/tools/shared.ts +12 -15
  191. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  192. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  193. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  194. package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
  195. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
  196. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
  197. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
  198. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
  199. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
  200. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
  201. package/src/config/env-registry.ts +14 -83
  202. package/src/config/env.ts +11 -50
  203. package/src/config/feature-flag-registry.json +16 -16
  204. package/src/config/schema.ts +3 -1
  205. package/src/config/skills.ts +21 -2
  206. package/src/context/image-dimensions.ts +229 -0
  207. package/src/context/token-estimator.ts +75 -12
  208. package/src/context/window-manager.ts +49 -10
  209. package/src/daemon/assistant-attachments.ts +1 -13
  210. package/src/daemon/guardian-action-generators.ts +4 -5
  211. package/src/daemon/handlers/config-ingress.ts +8 -33
  212. package/src/daemon/handlers/config-slack-channel.ts +76 -56
  213. package/src/daemon/handlers/config-telegram.ts +53 -24
  214. package/src/daemon/handlers/sessions.ts +10 -24
  215. package/src/daemon/handlers/shared.ts +0 -130
  216. package/src/daemon/host-cu-proxy.ts +401 -0
  217. package/src/daemon/lifecycle.ts +39 -63
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/computer-use.ts +2 -119
  220. package/src/daemon/message-types/host-cu.ts +19 -0
  221. package/src/daemon/message-types/integrations.ts +1 -0
  222. package/src/daemon/message-types/messages.ts +3 -0
  223. package/src/daemon/server.ts +14 -21
  224. package/src/daemon/session-agent-loop-handlers.ts +2 -0
  225. package/src/daemon/session-attachments.ts +1 -2
  226. package/src/daemon/session-messaging.ts +3 -1
  227. package/src/daemon/session-slash.ts +1 -1
  228. package/src/daemon/session-surfaces.ts +40 -28
  229. package/src/daemon/session-tool-setup.ts +20 -11
  230. package/src/daemon/session.ts +139 -16
  231. package/src/daemon/tool-side-effects.ts +2 -8
  232. package/src/daemon/watch-handler.ts +2 -2
  233. package/src/email/providers/index.ts +2 -1
  234. package/src/events/tool-metrics-listener.ts +2 -2
  235. package/src/hooks/manager.ts +1 -4
  236. package/src/inbound/public-ingress-urls.ts +7 -7
  237. package/src/instrument.ts +15 -1
  238. package/src/logfire.ts +16 -5
  239. package/src/media/app-icon-generator.ts +30 -4
  240. package/src/media/avatar-router.ts +26 -3
  241. package/src/media/gemini-image-service.ts +28 -2
  242. package/src/memory/conversation-key-store.ts +21 -0
  243. package/src/memory/db-init.ts +4 -0
  244. package/src/memory/guardian-action-store.ts +1 -1
  245. package/src/memory/migrations/149-oauth-tables.ts +60 -0
  246. package/src/memory/migrations/index.ts +1 -0
  247. package/src/memory/schema/guardian.ts +1 -1
  248. package/src/memory/schema/index.ts +1 -0
  249. package/src/memory/schema/oauth.ts +65 -0
  250. package/src/messaging/provider.ts +19 -13
  251. package/src/messaging/providers/gmail/adapter.ts +40 -23
  252. package/src/messaging/providers/gmail/client.ts +283 -122
  253. package/src/messaging/providers/gmail/people-client.ts +32 -24
  254. package/src/messaging/providers/slack/adapter.ts +29 -19
  255. package/src/messaging/providers/slack/client.ts +265 -78
  256. package/src/messaging/providers/telegram-bot/adapter.ts +19 -18
  257. package/src/messaging/providers/whatsapp/adapter.ts +17 -11
  258. package/src/messaging/registry.ts +2 -31
  259. package/src/notifications/copy-composer.ts +0 -5
  260. package/src/notifications/signal.ts +4 -5
  261. package/src/oauth/byo-connection.test.ts +537 -0
  262. package/src/oauth/byo-connection.ts +128 -0
  263. package/src/oauth/connect-orchestrator.ts +139 -56
  264. package/src/oauth/connect-types.ts +17 -23
  265. package/src/oauth/connection-resolver.ts +58 -0
  266. package/src/oauth/connection.ts +38 -0
  267. package/src/oauth/manual-token-connection.ts +104 -0
  268. package/src/oauth/oauth-store.ts +496 -0
  269. package/src/oauth/platform-connection.test.ts +192 -0
  270. package/src/oauth/platform-connection.ts +111 -0
  271. package/src/oauth/provider-behaviors.ts +124 -0
  272. package/src/oauth/scope-policy.ts +9 -2
  273. package/src/oauth/seed-providers.ts +161 -0
  274. package/src/oauth/token-persistence.ts +74 -78
  275. package/src/permissions/checker.ts +8 -4
  276. package/src/permissions/defaults.ts +0 -1
  277. package/src/permissions/prompter.ts +10 -1
  278. package/src/permissions/trust-store.ts +13 -0
  279. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  280. package/src/prompts/system-prompt.ts +70 -45
  281. package/src/providers/anthropic/client.ts +133 -24
  282. package/src/providers/gemini/client.ts +15 -6
  283. package/src/providers/managed-proxy/constants.ts +2 -2
  284. package/src/providers/managed-proxy/context.ts +5 -1
  285. package/src/providers/ratelimit.ts +17 -0
  286. package/src/providers/registry.ts +2 -2
  287. package/src/providers/retry.ts +1 -27
  288. package/src/runtime/AGENTS.md +17 -0
  289. package/src/runtime/auth/route-policy.ts +0 -3
  290. package/src/runtime/channel-invite-transports/telegram.ts +2 -1
  291. package/src/runtime/channel-readiness-service.ts +168 -195
  292. package/src/runtime/channel-readiness-types.ts +4 -0
  293. package/src/runtime/channel-reply-delivery.ts +0 -40
  294. package/src/runtime/gateway-client.ts +0 -7
  295. package/src/runtime/guardian-action-conversation-turn.ts +1 -3
  296. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  297. package/src/runtime/guardian-action-message-composer.ts +3 -23
  298. package/src/runtime/http-server.ts +17 -10
  299. package/src/runtime/http-types.ts +2 -3
  300. package/src/runtime/middleware/rate-limiter.ts +74 -20
  301. package/src/runtime/middleware/twilio-validation.ts +1 -11
  302. package/src/runtime/pending-interactions.ts +14 -12
  303. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  304. package/src/runtime/routes/channel-readiness-routes.ts +2 -0
  305. package/src/runtime/routes/conversation-routes.ts +73 -19
  306. package/src/runtime/routes/diagnostics-routes.ts +11 -9
  307. package/src/runtime/routes/events-routes.ts +21 -11
  308. package/src/runtime/routes/guardian-approval-interception.ts +20 -5
  309. package/src/runtime/routes/host-cu-routes.ts +97 -0
  310. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  311. package/src/runtime/routes/integrations/slack/share.ts +6 -6
  312. package/src/runtime/routes/integrations/twilio.ts +6 -5
  313. package/src/runtime/routes/log-export-routes.ts +126 -8
  314. package/src/runtime/routes/secret-routes.ts +3 -2
  315. package/src/runtime/routes/settings-routes.ts +113 -48
  316. package/src/runtime/routes/surface-action-routes.ts +1 -1
  317. package/src/runtime/routes/watch-routes.ts +128 -0
  318. package/src/schedule/integration-status.ts +10 -8
  319. package/src/security/credential-key.ts +14 -0
  320. package/src/security/keychain-broker-client.ts +5 -6
  321. package/src/security/oauth2.ts +1 -1
  322. package/src/security/token-manager.ts +145 -43
  323. package/src/skills/catalog-install.ts +358 -0
  324. package/src/skills/include-graph.ts +32 -0
  325. package/src/telegram/bot-username.ts +2 -3
  326. package/src/tools/apps/definitions.ts +0 -5
  327. package/src/tools/assets/materialize.ts +0 -5
  328. package/src/tools/assets/search.ts +0 -5
  329. package/src/tools/browser/headless-browser.ts +1 -67
  330. package/src/tools/browser/network-recorder.ts +1 -1
  331. package/src/tools/browser/network-recording-types.ts +1 -1
  332. package/src/tools/claude-code/claude-code.ts +0 -5
  333. package/src/tools/computer-use/definitions.ts +46 -11
  334. package/src/tools/computer-use/registry.ts +4 -5
  335. package/src/tools/credentials/broker.ts +5 -4
  336. package/src/tools/credentials/metadata-store.ts +22 -74
  337. package/src/tools/credentials/resolve.ts +2 -1
  338. package/src/tools/credentials/vault.ts +139 -151
  339. package/src/tools/filesystem/edit.ts +1 -6
  340. package/src/tools/filesystem/read.ts +0 -5
  341. package/src/tools/filesystem/write.ts +1 -6
  342. package/src/tools/host-filesystem/edit.ts +1 -6
  343. package/src/tools/host-filesystem/read.ts +1 -6
  344. package/src/tools/host-filesystem/write.ts +1 -6
  345. package/src/tools/mcp/mcp-tool-factory.ts +18 -1
  346. package/src/tools/memory/definitions.ts +0 -5
  347. package/src/tools/network/web-fetch.ts +0 -5
  348. package/src/tools/network/web-search.ts +0 -5
  349. package/src/tools/registry.ts +2 -7
  350. package/src/tools/schema-transforms.ts +99 -0
  351. package/src/tools/skills/load.ts +62 -8
  352. package/src/tools/swarm/delegate.ts +0 -5
  353. package/src/tools/system/avatar-generator.ts +0 -5
  354. package/src/tools/ui-surface/definitions.ts +0 -15
  355. package/src/tools/watch/screen-watch.ts +0 -5
  356. package/src/tools/watch/watch-state.ts +0 -12
  357. package/src/util/logger.ts +7 -41
  358. package/src/util/platform.ts +9 -28
  359. package/src/version.ts +10 -0
  360. package/src/watcher/providers/github.ts +51 -52
  361. package/src/watcher/providers/gmail.ts +88 -80
  362. package/src/watcher/providers/google-calendar.ts +94 -86
  363. package/src/watcher/providers/linear.ts +87 -93
  364. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  365. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  366. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  367. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  368. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  369. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  370. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  371. package/src/cli/commands/dev.ts +0 -129
  372. package/src/cli/commands/map.ts +0 -391
  373. package/src/cli/commands/oauth.ts +0 -77
  374. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
  375. package/src/daemon/computer-use-session.ts +0 -1020
  376. package/src/daemon/ride-shotgun-handler.ts +0 -567
  377. package/src/oauth/provider-profiles.ts +0 -192
  378. package/src/prompts/computer-use-prompt.ts +0 -98
  379. package/src/runtime/routes/computer-use-routes.ts +0 -641
  380. package/src/runtime/telegram-streaming-delivery.test.ts +0 -597
  381. package/src/runtime/telegram-streaming-delivery.ts +0 -383
  382. package/src/tools/computer-use/request-computer-control.ts +0 -61
@@ -24,13 +24,6 @@ function flag(name: string): boolean {
24
24
  return raw === "true" || raw === "1";
25
25
  }
26
26
 
27
- function flagTriState(name: string): boolean | undefined {
28
- const raw = str(name);
29
- if (raw === "true" || raw === "1") return true;
30
- if (raw === "false" || raw === "0") return false;
31
- return undefined;
32
- }
33
-
34
27
  // ── Registry ─────────────────────────────────────────────────────────────────
35
28
  // Each entry documents the env var name, type, default, and purpose.
36
29
 
@@ -43,62 +36,6 @@ export function getBaseDataDir(): string | undefined {
43
36
  return str("BASE_DATA_DIR");
44
37
  }
45
38
 
46
- /**
47
- * VELLUM_DAEMON_TCP_PORT — number, default: 8765
48
- * TCP port for the daemon's TCP listener (used by iOS clients).
49
- */
50
- export function getDaemonTcpPort(): number {
51
- const raw = str("VELLUM_DAEMON_TCP_PORT");
52
- if (raw) {
53
- const port = parseInt(raw, 10);
54
- if (!isNaN(port) && port > 0 && port <= 65535) return port;
55
- }
56
- return 8765;
57
- }
58
-
59
- /**
60
- * VELLUM_DAEMON_TCP_ENABLED — boolean tri-state, default: undefined (falls back to flag file)
61
- * Whether the daemon TCP listener should be active.
62
- * 'true'/'1' → on, 'false'/'0' → off, unset → check flag file.
63
- */
64
- export function getDaemonTcpEnabled(): boolean | undefined {
65
- return flagTriState("VELLUM_DAEMON_TCP_ENABLED");
66
- }
67
-
68
- /**
69
- * VELLUM_DAEMON_TCP_HOST — string, default: context-dependent (127.0.0.1 or 0.0.0.0)
70
- * Hostname/address for the TCP listener. When unset, platform.ts resolves
71
- * based on whether iOS pairing is enabled.
72
- */
73
- export function getDaemonTcpHost(): string | undefined {
74
- return str("VELLUM_DAEMON_TCP_HOST");
75
- }
76
-
77
- /**
78
- * VELLUM_DAEMON_IOS_PAIRING — boolean tri-state, default: undefined (falls back to flag file)
79
- * Whether iOS pairing mode is enabled. When on, TCP binds to 0.0.0.0.
80
- * 'true'/'1' → on, 'false'/'0' → off, unset → check flag file.
81
- */
82
- export function getDaemonIosPairing(): boolean | undefined {
83
- return flagTriState("VELLUM_DAEMON_IOS_PAIRING");
84
- }
85
-
86
- /**
87
- * VELLUM_DEBUG — boolean, default: false
88
- * Enables debug-level logging and verbose output.
89
- */
90
- export function getDebugMode(): boolean {
91
- return flag("VELLUM_DEBUG");
92
- }
93
-
94
- /**
95
- * VELLUM_LOG_STDERR — boolean, default: false
96
- * Forces logger output to stderr instead of log files.
97
- */
98
- export function getLogStderr(): boolean {
99
- return flag("VELLUM_LOG_STDERR");
100
- }
101
-
102
39
  /**
103
40
  * DEBUG_STDOUT_LOGS — boolean, default: false
104
41
  * Enables additional log output to stdout (alongside file logging).
@@ -107,14 +44,6 @@ export function getDebugStdoutLogs(): boolean {
107
44
  return flag("DEBUG_STDOUT_LOGS");
108
45
  }
109
46
 
110
- /**
111
- * VELLUM_ENABLE_MONITORING — boolean, default: false
112
- * Enables monitoring/telemetry (Logfire, etc.).
113
- */
114
- export function getEnableMonitoring(): boolean {
115
- return flag("VELLUM_ENABLE_MONITORING");
116
- }
117
-
118
47
  /**
119
48
  * IS_CONTAINERIZED — boolean, default: false
120
49
  * When true, indicates the assistant is running inside a container (e.g. Docker).
@@ -132,24 +61,26 @@ export function getIsContainerized(): boolean {
132
61
  * to warn about typos or unrecognized variables.
133
62
  */
134
63
  const KNOWN_VELLUM_VARS = new Set([
135
- "VELLUM_DAEMON_TCP_PORT",
136
- "VELLUM_DAEMON_TCP_ENABLED",
137
- "VELLUM_DAEMON_TCP_HOST",
138
- "VELLUM_DAEMON_IOS_PAIRING",
139
- "VELLUM_DAEMON_NOAUTH",
64
+ "VELLUM_ASSISTANT_NAME",
65
+ "VELLUM_AWS_ROLE_ARN",
66
+ "VELLUM_CLAUDE_CODE_DEPTH",
67
+ "VELLUM_CUSTOM_QR_CODE_PATH",
140
68
  "VELLUM_DAEMON_AUTOSTART",
141
- "VELLUM_DEBUG",
142
- "VELLUM_LOG_STDERR",
143
- "VELLUM_ENABLE_MONITORING",
69
+ "VELLUM_DAEMON_NOAUTH",
70
+ "VELLUM_DATA_DIR",
71
+ "VELLUM_DESKTOP_APP",
72
+ "VELLUM_DEV",
73
+ "VELLUM_ENABLE_INSECURE_LAN_PAIRING",
74
+ "VELLUM_HATCHED_BY",
144
75
  "VELLUM_HOOK_EVENT",
145
76
  "VELLUM_HOOK_NAME",
146
77
  "VELLUM_HOOK_SETTINGS",
78
+ "VELLUM_LOCKFILE_DIR",
79
+ "VELLUM_PLATFORM_URL",
147
80
  "VELLUM_ROOT_DIR",
148
- "VELLUM_WORKSPACE_DIR",
149
- "VELLUM_CLAUDE_CODE_DEPTH",
150
- "VELLUM_ASSISTANT_PLATFORM_URL",
81
+ "VELLUM_SSH_USER",
151
82
  "VELLUM_UNSAFE_AUTH_BYPASS",
152
- "VELLUM_DATA_DIR",
83
+ "VELLUM_WORKSPACE_DIR",
153
84
  ]);
154
85
 
155
86
  /**
package/src/config/env.ts CHANGED
@@ -8,17 +8,13 @@
8
8
  * - Fail-fast validation via validateEnv() at startup
9
9
  * - Shared derived values (e.g. gateway base URL) instead of duplicated logic
10
10
  *
11
- * Bootstrap-level env vars (BASE_DATA_DIR, VELLUM_DAEMON_*, VELLUM_DEBUG,
12
- * VELLUM_LOG_STDERR, DEBUG_STDOUT_LOGS) are defined in config/env-registry.ts
13
- * which has no internal dependencies and can be imported from platform/logger
14
- * without circular imports.
11
+ * Bootstrap-level env vars (BASE_DATA_DIR, DEBUG_STDOUT_LOGS) are defined
12
+ * in config/env-registry.ts which has no internal dependencies and can be
13
+ * imported from platform/logger without circular imports.
15
14
  */
16
15
 
17
16
  import { getLogger } from "../util/logger.js";
18
- import {
19
- checkUnrecognizedEnvVars,
20
- getEnableMonitoring,
21
- } from "./env-registry.js";
17
+ import { checkUnrecognizedEnvVars } from "./env-registry.js";
22
18
 
23
19
  const log = getLogger("env");
24
20
 
@@ -55,33 +51,23 @@ export function getGatewayPort(): number {
55
51
  return int("GATEWAY_PORT", DEFAULT_GATEWAY_PORT);
56
52
  }
57
53
 
58
- /**
59
- * Resolve the gateway base URL for internal service-to-service calls.
60
- * Prefers GATEWAY_INTERNAL_BASE_URL if set, then INTERNAL_GATEWAY_BASE_URL
61
- * (used by skill subprocesses), otherwise derives from port.
62
- */
54
+ /** Resolve the gateway base URL for internal service-to-service calls. */
63
55
  export function getGatewayInternalBaseUrl(): string {
64
- const explicit = str("GATEWAY_INTERNAL_BASE_URL");
65
- if (explicit) return explicit.replace(/\/+$/, "");
66
- const skillInjected = str("INTERNAL_GATEWAY_BASE_URL");
67
- if (skillInjected) return skillInjected.replace(/\/+$/, "");
68
56
  return `http://127.0.0.1:${getGatewayPort()}`;
69
57
  }
70
58
 
71
59
  // ── Ingress ──────────────────────────────────────────────────────────────────
72
60
 
73
- /** Read the INGRESS_PUBLIC_BASE_URL env var (may be mutated at runtime by config handlers). */
61
+ let _ingressPublicBaseUrl: string | undefined;
62
+
63
+ /** Read the ingress public base URL (module-level state, mutated at runtime by config handlers). */
74
64
  export function getIngressPublicBaseUrl(): string | undefined {
75
- return str("INGRESS_PUBLIC_BASE_URL");
65
+ return _ingressPublicBaseUrl;
76
66
  }
77
67
 
78
- /** Set or clear the INGRESS_PUBLIC_BASE_URL env var (used by config handlers). */
68
+ /** Set or clear the ingress public base URL (used by config handlers). */
79
69
  export function setIngressPublicBaseUrl(value: string | undefined): void {
80
- if (value) {
81
- process.env.INGRESS_PUBLIC_BASE_URL = value;
82
- } else {
83
- delete process.env.INGRESS_PUBLIC_BASE_URL;
84
- }
70
+ _ingressPublicBaseUrl = value;
85
71
  }
86
72
 
87
73
  // ── Runtime HTTP ─────────────────────────────────────────────────────────────
@@ -117,37 +103,12 @@ export function hasUngatedHttpAuthDisabled(): boolean {
117
103
  return str("VELLUM_UNSAFE_AUTH_BYPASS")?.trim() !== "1";
118
104
  }
119
105
 
120
- // ── Twilio ───────────────────────────────────────────────────────────────────
121
-
122
- export function getTwilioPhoneNumberEnv(): string | undefined {
123
- return str("TWILIO_PHONE_NUMBER");
124
- }
125
-
126
- export function getTwilioUserPhoneNumber(): string | undefined {
127
- return str("TWILIO_USER_PHONE_NUMBER");
128
- }
129
-
130
- export function isTwilioWebhookValidationDisabled(): boolean {
131
- // Intentionally strict: only exact "true" disables validation (not "1").
132
- // This is a security-sensitive bypass — we don't want environments that
133
- // template booleans as "1" to silently skip webhook signature checks.
134
- return process.env.TWILIO_WEBHOOK_VALIDATION_DISABLED === "true";
135
- }
136
-
137
- export function getCallWelcomeGreeting(): string | undefined {
138
- return str("CALL_WELCOME_GREETING");
139
- }
140
-
141
106
  // ── Monitoring ───────────────────────────────────────────────────────────────
142
107
 
143
108
  export function getLogfireToken(): string | undefined {
144
109
  return str("LOGFIRE_TOKEN");
145
110
  }
146
111
 
147
- export function isMonitoringEnabled(): boolean {
148
- return getEnableMonitoring();
149
- }
150
-
151
112
  const DEFAULT_SENTRY_DSN =
152
113
  "https://db2d38a082e4ee35eeaea08c44b376ec@o4504590528675840.ingest.us.sentry.io/4510874712276992";
153
114
 
@@ -33,22 +33,6 @@
33
33
  "description": "Enable messaging skill section in the system prompt",
34
34
  "defaultEnabled": true
35
35
  },
36
- {
37
- "id": "messaging-gmail",
38
- "scope": "assistant",
39
- "key": "feature_flags.messaging.gmail.enabled",
40
- "label": "Messaging: Gmail",
41
- "description": "Allow messaging tools to operate on the Gmail platform",
42
- "defaultEnabled": true
43
- },
44
- {
45
- "id": "messaging-telegram",
46
- "scope": "assistant",
47
- "key": "feature_flags.messaging.telegram.enabled",
48
- "label": "Messaging: Telegram",
49
- "description": "Allow messaging tools to operate on the Telegram platform",
50
- "defaultEnabled": false
51
- },
52
36
  {
53
37
  "id": "collect-usage-data",
54
38
  "scope": "assistant",
@@ -81,6 +65,14 @@
81
65
  "description": "Show the Contacts tab in Settings for viewing and managing contacts",
82
66
  "defaultEnabled": false
83
67
  },
68
+ {
69
+ "id": "email-channel",
70
+ "scope": "assistant",
71
+ "key": "feature_flags.email-channel.enabled",
72
+ "label": "Email Channel",
73
+ "description": "Show the Email channel card on the Contacts page and enable the email-setup skill",
74
+ "defaultEnabled": false
75
+ },
84
76
  {
85
77
  "id": "outbound-proxy-sidecar",
86
78
  "scope": "assistant",
@@ -128,6 +120,14 @@
128
120
  "label": "Settings Developer Nav",
129
121
  "description": "Control Developer nav visibility in macOS settings",
130
122
  "defaultEnabled": true
123
+ },
124
+ {
125
+ "id": "logfire",
126
+ "scope": "assistant",
127
+ "key": "feature_flags.logfire.enabled",
128
+ "label": "Logfire LLM Observability",
129
+ "description": "Enable Logfire tracing for LLM request/response telemetry when LOGFIRE_TOKEN is set",
130
+ "defaultEnabled": false
131
131
  }
132
132
  ]
133
133
  }
@@ -289,7 +289,9 @@ export const AssistantConfigSchema = z
289
289
  PermissionsConfigSchema.parse({}),
290
290
  ),
291
291
  auditLog: AuditLogConfigSchema.default(AuditLogConfigSchema.parse({})),
292
- logFile: LogFileConfigSchema.default(LogFileConfigSchema.parse({})),
292
+ logFile: LogFileConfigSchema.default(
293
+ LogFileConfigSchema.parse({ dir: getDataDir() + "/logs" }),
294
+ ),
293
295
  pricingOverrides: z.array(ModelPricingOverrideSchema).default([]),
294
296
  heartbeat: HeartbeatConfigSchema.default(HeartbeatConfigSchema.parse({})),
295
297
  swarm: SwarmConfigSchema.default(SwarmConfigSchema.parse({})),
@@ -146,14 +146,23 @@ export interface SkillDefinition extends SkillSummary {
146
146
  body: string;
147
147
  }
148
148
 
149
+ export type SkillLookupErrorCode =
150
+ | "not_found"
151
+ | "ambiguous"
152
+ | "empty_catalog"
153
+ | "invalid_selector"
154
+ | "load_failed";
155
+
149
156
  export interface SkillLookupResult {
150
157
  skill?: SkillDefinition;
151
158
  error?: string;
159
+ errorCode?: SkillLookupErrorCode;
152
160
  }
153
161
 
154
162
  export interface SkillSelectorResult {
155
163
  skill?: SkillSummary;
156
164
  error?: string;
165
+ errorCode?: SkillLookupErrorCode;
157
166
  }
158
167
 
159
168
  // ─── Skill Tool Manifest Types ────────────────────────────────────────────────
@@ -1210,6 +1219,7 @@ export function resolveSkillSelector(
1210
1219
  if (!needle) {
1211
1220
  return {
1212
1221
  error: "Skill selector is required and must be a non-empty string.",
1222
+ errorCode: "invalid_selector",
1213
1223
  };
1214
1224
  }
1215
1225
 
@@ -1218,6 +1228,7 @@ export function resolveSkillSelector(
1218
1228
  return {
1219
1229
  error:
1220
1230
  "No skills are available. Configure ~/.vellum/workspace/skills/SKILLS.md or add skill directories.",
1231
+ errorCode: "empty_catalog",
1221
1232
  };
1222
1233
  }
1223
1234
 
@@ -1236,7 +1247,10 @@ export function resolveSkillSelector(
1236
1247
  }
1237
1248
  if (exactNameMatches.length > 1) {
1238
1249
  const ids = exactNameMatches.map((skill) => skill.id).join(", ");
1239
- return { error: `Ambiguous skill name "${needle}". Matching IDs: ${ids}` };
1250
+ return {
1251
+ error: `Ambiguous skill name "${needle}". Matching IDs: ${ids}`,
1252
+ errorCode: "ambiguous",
1253
+ };
1240
1254
  }
1241
1255
 
1242
1256
  const idPrefixMatches = catalog.filter((skill) =>
@@ -1249,12 +1263,14 @@ export function resolveSkillSelector(
1249
1263
  const ids = idPrefixMatches.map((skill) => skill.id).join(", ");
1250
1264
  return {
1251
1265
  error: `Ambiguous skill id prefix "${needle}". Matching IDs: ${ids}`,
1266
+ errorCode: "ambiguous",
1252
1267
  };
1253
1268
  }
1254
1269
 
1255
1270
  const knownSkills = catalog.map((skill) => skill.id).join(", ");
1256
1271
  return {
1257
1272
  error: `No skill matched "${needle}". Available skills: ${knownSkills}`,
1273
+ errorCode: "not_found",
1258
1274
  };
1259
1275
  }
1260
1276
 
@@ -1264,7 +1280,10 @@ export function loadSkillBySelector(
1264
1280
  ): SkillLookupResult {
1265
1281
  const resolved = resolveSkillSelector(selector, workspaceSkillsDir);
1266
1282
  if (!resolved.skill) {
1267
- return { error: resolved.error ?? "Failed to resolve skill selector." };
1283
+ return {
1284
+ error: resolved.error ?? "Failed to resolve skill selector.",
1285
+ errorCode: resolved.errorCode ?? "load_failed",
1286
+ };
1268
1287
  }
1269
1288
  return loadSkillDefinition(resolved.skill);
1270
1289
  }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Parses image dimensions from base64-encoded image data by reading binary headers.
3
+ * Supports PNG, JPEG, GIF, and WebP formats.
4
+ * Returns null if parsing fails for any reason (corrupt, truncated, unrecognized).
5
+ */
6
+ export function parseImageDimensions(
7
+ base64Data: string,
8
+ mediaType: string,
9
+ ): { width: number; height: number } | null {
10
+ try {
11
+ switch (mediaType) {
12
+ case "image/png":
13
+ return parsePng(base64Data);
14
+ case "image/jpeg":
15
+ return parseJpeg(base64Data);
16
+ case "image/gif":
17
+ return parseGif(base64Data);
18
+ case "image/webp":
19
+ return parseWebp(base64Data);
20
+ default:
21
+ return null;
22
+ }
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ function decodeBase64Bytes(
29
+ base64Data: string,
30
+ maxBytes: number,
31
+ ): Buffer | null {
32
+ // Estimate how much base64 we need: every 4 base64 chars = 3 bytes
33
+ const charsNeeded = Math.ceil((maxBytes * 4) / 3);
34
+ const slice = base64Data.slice(0, charsNeeded + 4); // a little extra for padding
35
+ try {
36
+ const buf = Buffer.from(slice, "base64");
37
+ return buf.length > 0 ? buf : null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ function readUint32BE(buf: Buffer, offset: number): number {
44
+ if (offset + 4 > buf.length) return -1;
45
+ return buf.readUInt32BE(offset);
46
+ }
47
+
48
+ function readUint16BE(buf: Buffer, offset: number): number {
49
+ if (offset + 2 > buf.length) return -1;
50
+ return buf.readUInt16BE(offset);
51
+ }
52
+
53
+ function readUint16LE(buf: Buffer, offset: number): number {
54
+ if (offset + 2 > buf.length) return -1;
55
+ return buf.readUInt16LE(offset);
56
+ }
57
+
58
+ function readUint32LE(buf: Buffer, offset: number): number {
59
+ if (offset + 4 > buf.length) return -1;
60
+ return buf.readUInt32LE(offset);
61
+ }
62
+
63
+ function readUint24LE(buf: Buffer, offset: number): number {
64
+ if (offset + 3 > buf.length) return -1;
65
+ return buf[offset]! | (buf[offset + 1]! << 8) | (buf[offset + 2]! << 16);
66
+ }
67
+
68
+ function parsePng(
69
+ base64Data: string,
70
+ ): { width: number; height: number } | null {
71
+ const buf = decodeBase64Bytes(base64Data, 32);
72
+ if (!buf || buf.length < 24) return null;
73
+
74
+ // Validate PNG signature: 89 50 4E 47
75
+ if (
76
+ buf[0] !== 0x89 ||
77
+ buf[1] !== 0x50 ||
78
+ buf[2] !== 0x4e ||
79
+ buf[3] !== 0x47
80
+ ) {
81
+ return null;
82
+ }
83
+
84
+ const width = readUint32BE(buf, 16);
85
+ const height = readUint32BE(buf, 20);
86
+ if (width <= 0 || height <= 0) return null;
87
+
88
+ return { width, height };
89
+ }
90
+
91
+ function parseJpeg(
92
+ base64Data: string,
93
+ ): { width: number; height: number } | null {
94
+ // Scan up to 1 MiB to handle JPEGs with large EXIF/ICC metadata before the SOF marker
95
+ const buf = decodeBase64Bytes(base64Data, 1_048_576);
96
+ if (!buf || buf.length < 2) return null;
97
+
98
+ // Validate JPEG SOI marker
99
+ if (buf[0] !== 0xff || buf[1] !== 0xd8) return null;
100
+
101
+ let offset = 2;
102
+ while (offset < buf.length - 1) {
103
+ // Find next marker
104
+ if (buf[offset] !== 0xff) {
105
+ offset++;
106
+ continue;
107
+ }
108
+
109
+ // Skip padding 0xFF bytes
110
+ while (offset < buf.length && buf[offset] === 0xff) {
111
+ offset++;
112
+ }
113
+ if (offset >= buf.length) return null;
114
+
115
+ const marker = buf[offset]!;
116
+ offset++;
117
+
118
+ // Check for SOF markers: C0-CF excluding C4 (DHT) and CC (DAC)
119
+ if (
120
+ marker >= 0xc0 &&
121
+ marker <= 0xcf &&
122
+ marker !== 0xc4 &&
123
+ marker !== 0xcc
124
+ ) {
125
+ // SOF marker found: skip 2-byte length + 1-byte precision
126
+ if (offset + 7 > buf.length) return null;
127
+ const height = readUint16BE(buf, offset + 3);
128
+ const width = readUint16BE(buf, offset + 5);
129
+ if (width <= 0 || height <= 0) return null;
130
+ return { width, height };
131
+ }
132
+
133
+ // Skip this marker's payload
134
+ if (offset + 1 >= buf.length) return null;
135
+ const segmentLength = readUint16BE(buf, offset);
136
+ if (segmentLength < 2) return null;
137
+ offset += segmentLength;
138
+ }
139
+
140
+ return null;
141
+ }
142
+
143
+ function parseGif(
144
+ base64Data: string,
145
+ ): { width: number; height: number } | null {
146
+ const buf = decodeBase64Bytes(base64Data, 12);
147
+ if (!buf || buf.length < 10) return null;
148
+
149
+ // Validate GIF signature: 47 49 46 38 (GIF8)
150
+ if (
151
+ buf[0] !== 0x47 ||
152
+ buf[1] !== 0x49 ||
153
+ buf[2] !== 0x46 ||
154
+ buf[3] !== 0x38
155
+ ) {
156
+ return null;
157
+ }
158
+
159
+ const width = readUint16LE(buf, 6);
160
+ const height = readUint16LE(buf, 8);
161
+ if (width <= 0 || height <= 0) return null;
162
+
163
+ return { width, height };
164
+ }
165
+
166
+ function parseWebp(
167
+ base64Data: string,
168
+ ): { width: number; height: number } | null {
169
+ const buf = decodeBase64Bytes(base64Data, 32);
170
+ if (!buf || buf.length < 16) return null;
171
+
172
+ // Validate RIFF signature
173
+ if (
174
+ buf[0] !== 0x52 ||
175
+ buf[1] !== 0x49 ||
176
+ buf[2] !== 0x46 ||
177
+ buf[3] !== 0x46
178
+ ) {
179
+ return null;
180
+ }
181
+ // Validate WEBP signature at offset 8
182
+ if (
183
+ buf[8] !== 0x57 ||
184
+ buf[9] !== 0x45 ||
185
+ buf[10] !== 0x42 ||
186
+ buf[11] !== 0x50
187
+ ) {
188
+ return null;
189
+ }
190
+
191
+ // Identify sub-format at offset 12
192
+ const subFormat =
193
+ String.fromCharCode(buf[12]!) +
194
+ String.fromCharCode(buf[13]!) +
195
+ String.fromCharCode(buf[14]!) +
196
+ String.fromCharCode(buf[15]!);
197
+
198
+ if (subFormat === "VP8 ") {
199
+ // VP8 lossy
200
+ if (buf.length < 30) return null;
201
+ const width = readUint16LE(buf, 26) & 0x3fff;
202
+ const height = readUint16LE(buf, 28) & 0x3fff;
203
+ if (width <= 0 || height <= 0) return null;
204
+ return { width, height };
205
+ }
206
+
207
+ if (subFormat === "VP8L") {
208
+ // VP8L lossless — validate signature byte 0x2f at offset 20
209
+ if (buf.length < 25) return null;
210
+ if (buf[20] !== 0x2f) return null;
211
+ const bits = readUint32LE(buf, 21);
212
+ if (bits < 0) return null;
213
+ const width = (bits & 0x3fff) + 1;
214
+ const height = ((bits >> 14) & 0x3fff) + 1;
215
+ if (width <= 0 || height <= 0) return null;
216
+ return { width, height };
217
+ }
218
+
219
+ if (subFormat === "VP8X") {
220
+ // VP8X extended
221
+ if (buf.length < 30) return null;
222
+ const width = readUint24LE(buf, 24) + 1;
223
+ const height = readUint24LE(buf, 27) + 1;
224
+ if (width <= 0 || height <= 0) return null;
225
+ return { width, height };
226
+ }
227
+
228
+ return null;
229
+ }