@vellumai/assistant 0.3.5 → 0.3.7

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 (487) hide show
  1. package/README.md +51 -0
  2. package/eslint.config.mjs +31 -0
  3. package/package.json +1 -1
  4. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  5. package/scripts/ipc/generate-swift.ts +18 -2
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +338 -1
  7. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  8. package/src/__tests__/browser-manager.test.ts +1 -0
  9. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  10. package/src/__tests__/call-orchestrator.test.ts +752 -271
  11. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  12. package/src/__tests__/call-recovery.test.ts +3 -0
  13. package/src/__tests__/call-routes-http.test.ts +5 -0
  14. package/src/__tests__/call-store.test.ts +3 -0
  15. package/src/__tests__/channel-approval-routes.test.ts +1260 -85
  16. package/src/__tests__/channel-approval.test.ts +37 -0
  17. package/src/__tests__/channel-approvals.test.ts +4 -65
  18. package/src/__tests__/channel-guardian.test.ts +556 -0
  19. package/src/__tests__/channel-readiness-service.test.ts +74 -7
  20. package/src/__tests__/checker.test.ts +14 -7
  21. package/src/__tests__/clarification-resolver.test.ts +44 -24
  22. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  23. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  24. package/src/__tests__/config-schema.test.ts +12 -7
  25. package/src/__tests__/context-window-manager.test.ts +30 -2
  26. package/src/__tests__/contradiction-checker.test.ts +20 -5
  27. package/src/__tests__/credential-security-invariants.test.ts +6 -2
  28. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  29. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  30. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  31. package/src/__tests__/guardian-action-store.test.ts +123 -0
  32. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  33. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  34. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  35. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  36. package/src/__tests__/handlers-twilio-config.test.ts +126 -0
  37. package/src/__tests__/intent-routing.test.ts +2 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +228 -1
  39. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  40. package/src/__tests__/model-intents.test.ts +96 -0
  41. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  42. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  43. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  44. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  45. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  46. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  47. package/src/__tests__/qdrant-manager.test.ts +27 -20
  48. package/src/__tests__/relay-server.test.ts +779 -40
  49. package/src/__tests__/run-orchestrator-assistant-events.test.ts +2 -0
  50. package/src/__tests__/run-orchestrator.test.ts +20 -4
  51. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  52. package/src/__tests__/runtime-runs.test.ts +16 -0
  53. package/src/__tests__/schedule-store.test.ts +18 -4
  54. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  55. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  56. package/src/__tests__/session-agent-loop.test.ts +857 -0
  57. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  58. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  59. package/src/__tests__/session-profile-injection.test.ts +6 -0
  60. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  61. package/src/__tests__/session-queue.test.ts +6 -0
  62. package/src/__tests__/session-runtime-assembly.test.ts +237 -13
  63. package/src/__tests__/session-slash-known.test.ts +6 -0
  64. package/src/__tests__/session-slash-queue.test.ts +6 -0
  65. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  66. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  67. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  68. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  69. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  70. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  71. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  72. package/src/__tests__/skills.test.ts +2 -0
  73. package/src/__tests__/sms-messaging-provider.test.ts +2 -1
  74. package/src/__tests__/starter-task-flow.test.ts +2 -0
  75. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  76. package/src/__tests__/system-prompt.test.ts +2 -0
  77. package/src/__tests__/task-management-tools.test.ts +2 -2
  78. package/src/__tests__/task-runner.test.ts +14 -4
  79. package/src/__tests__/terminal-tools.test.ts +25 -19
  80. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  81. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  82. package/src/__tests__/tool-executor.test.ts +23 -24
  83. package/src/__tests__/trust-store.test.ts +3 -3
  84. package/src/__tests__/twilio-rest.test.ts +29 -0
  85. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  86. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  87. package/src/__tests__/twilio-routes.test.ts +141 -21
  88. package/src/__tests__/user-reference.test.ts +2 -0
  89. package/src/__tests__/voice-quality.test.ts +222 -0
  90. package/src/__tests__/web-search.test.ts +45 -29
  91. package/src/agent/loop.ts +1 -1
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  93. package/src/amazon/client.ts +1418 -0
  94. package/src/amazon/request-extractor.ts +135 -0
  95. package/src/amazon/session.ts +109 -0
  96. package/src/autonomy/autonomy-store.ts +5 -5
  97. package/src/browser-extension-relay/client.ts +124 -0
  98. package/src/browser-extension-relay/protocol.ts +63 -0
  99. package/src/browser-extension-relay/server.ts +177 -0
  100. package/src/bundler/app-bundler.ts +3 -3
  101. package/src/bundler/bundle-signer.ts +1 -1
  102. package/src/bundler/signature-verifier.ts +1 -1
  103. package/src/calls/call-conversation-messages.ts +33 -0
  104. package/src/calls/call-domain.ts +106 -5
  105. package/src/calls/call-orchestrator.ts +252 -54
  106. package/src/calls/call-pointer-messages.ts +53 -0
  107. package/src/calls/call-recovery.ts +3 -8
  108. package/src/calls/call-store.ts +69 -87
  109. package/src/calls/elevenlabs-config.ts +3 -2
  110. package/src/calls/guardian-action-sweep.ts +105 -0
  111. package/src/calls/guardian-dispatch.ts +203 -0
  112. package/src/calls/guardian-question-copy.ts +133 -0
  113. package/src/calls/relay-server.ts +466 -8
  114. package/src/calls/speaker-identification.ts +1 -1
  115. package/src/calls/twilio-config.ts +7 -5
  116. package/src/calls/twilio-provider.ts +6 -4
  117. package/src/calls/twilio-rest.ts +40 -15
  118. package/src/calls/twilio-routes.ts +60 -45
  119. package/src/calls/types.ts +3 -1
  120. package/src/channels/types.ts +25 -0
  121. package/src/cli/amazon.ts +815 -0
  122. package/src/cli/config-commands.ts +2 -2
  123. package/src/cli/core-commands.ts +4 -3
  124. package/src/cli/influencer.ts +244 -0
  125. package/src/cli/map.ts +89 -6
  126. package/src/cli.ts +1 -1
  127. package/src/config/agent-schema.ts +171 -0
  128. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  129. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  130. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  131. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  132. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  133. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  134. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  135. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  136. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  137. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  138. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  139. package/src/config/bundled-skills/media-processing/SKILL.md +72 -95
  140. package/src/config/bundled-skills/media-processing/TOOLS.json +57 -147
  141. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  142. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  143. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  144. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  145. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  146. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  147. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  148. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +7 -9
  149. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  150. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +88 -253
  151. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +22 -153
  152. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  153. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +28 -51
  154. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +35 -270
  155. package/src/config/bundled-skills/messaging/SKILL.md +12 -2
  156. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  157. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  158. package/src/config/bundled-skills/phone-calls/SKILL.md +86 -21
  159. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  160. package/src/config/bundled-tool-registry.ts +310 -0
  161. package/src/config/calls-schema.ts +181 -0
  162. package/src/config/core-schema.ts +309 -0
  163. package/src/config/defaults.ts +27 -3
  164. package/src/config/env-registry.ts +169 -0
  165. package/src/config/env.ts +175 -0
  166. package/src/config/loader.ts +6 -6
  167. package/src/config/memory-schema.ts +528 -0
  168. package/src/config/sandbox-schema.ts +55 -0
  169. package/src/config/schema.ts +157 -1138
  170. package/src/config/skill-state.ts +1 -1
  171. package/src/config/skills-schema.ts +32 -0
  172. package/src/config/skills.ts +35 -24
  173. package/src/config/system-prompt.ts +107 -56
  174. package/src/config/templates/SOUL.md +1 -1
  175. package/src/config/types.ts +1 -0
  176. package/src/config/user-reference.ts +4 -9
  177. package/src/config/vellum-skills/catalog.json +0 -7
  178. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  179. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +1 -0
  180. package/src/config/vellum-skills/sms-setup/SKILL.md +112 -14
  181. package/src/context/window-manager.ts +27 -7
  182. package/src/daemon/approval-generators.ts +186 -0
  183. package/src/daemon/approved-devices-store.ts +140 -0
  184. package/src/daemon/assistant-attachments.ts +1 -1
  185. package/src/daemon/classifier.ts +35 -32
  186. package/src/daemon/config-watcher.ts +1 -1
  187. package/src/daemon/daemon-control.ts +254 -0
  188. package/src/daemon/handlers/apps.ts +2 -3
  189. package/src/daemon/handlers/config-channels.ts +158 -0
  190. package/src/daemon/handlers/config-inbox.ts +540 -0
  191. package/src/daemon/handlers/config-ingress.ts +231 -0
  192. package/src/daemon/handlers/config-integrations.ts +258 -0
  193. package/src/daemon/handlers/config-model.ts +143 -0
  194. package/src/daemon/handlers/config-parental.ts +163 -0
  195. package/src/daemon/handlers/config-scheduling.ts +172 -0
  196. package/src/daemon/handlers/config-slack.ts +92 -0
  197. package/src/daemon/handlers/config-telegram.ts +301 -0
  198. package/src/daemon/handlers/config-tools.ts +177 -0
  199. package/src/daemon/handlers/config-trust.ts +104 -0
  200. package/src/daemon/handlers/config-twilio.ts +1080 -0
  201. package/src/daemon/handlers/config.ts +53 -2463
  202. package/src/daemon/handlers/diagnostics.ts +1 -1
  203. package/src/daemon/handlers/dictation.ts +4 -6
  204. package/src/daemon/handlers/documents.ts +18 -32
  205. package/src/daemon/handlers/index.ts +9 -0
  206. package/src/daemon/handlers/misc.ts +3 -5
  207. package/src/daemon/handlers/pairing.ts +98 -0
  208. package/src/daemon/handlers/sessions.ts +74 -5
  209. package/src/daemon/handlers/shared.ts +3 -1
  210. package/src/daemon/handlers/skills.ts +1 -1
  211. package/src/daemon/handlers/twitter-auth.ts +2 -0
  212. package/src/daemon/handlers/work-items.ts +2 -2
  213. package/src/daemon/handlers/workspace-files.ts +4 -3
  214. package/src/daemon/install-cli-launchers.ts +113 -0
  215. package/src/daemon/ipc-contract/apps.ts +356 -0
  216. package/src/daemon/ipc-contract/browser.ts +74 -0
  217. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  218. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  219. package/src/daemon/ipc-contract/documents.ts +74 -0
  220. package/src/daemon/ipc-contract/inbox.ts +209 -0
  221. package/src/daemon/ipc-contract/integrations.ts +284 -0
  222. package/src/daemon/ipc-contract/memory.ts +48 -0
  223. package/src/daemon/ipc-contract/messages.ts +211 -0
  224. package/src/daemon/ipc-contract/pairing.ts +45 -0
  225. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  226. package/src/daemon/ipc-contract/schedules.ts +97 -0
  227. package/src/daemon/ipc-contract/sessions.ts +321 -0
  228. package/src/daemon/ipc-contract/shared.ts +42 -0
  229. package/src/daemon/ipc-contract/skills.ts +120 -0
  230. package/src/daemon/ipc-contract/subagents.ts +58 -0
  231. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  232. package/src/daemon/ipc-contract/trust.ts +60 -0
  233. package/src/daemon/ipc-contract/work-items.ts +225 -0
  234. package/src/daemon/ipc-contract/workspace.ts +113 -0
  235. package/src/daemon/ipc-contract-inventory.json +62 -0
  236. package/src/daemon/ipc-contract-inventory.ts +55 -29
  237. package/src/daemon/ipc-contract.ts +227 -2527
  238. package/src/daemon/ipc-protocol.ts +1 -1
  239. package/src/daemon/ipc-validate.ts +7 -0
  240. package/src/daemon/lifecycle.ts +97 -379
  241. package/src/daemon/pairing-store.ts +177 -0
  242. package/src/daemon/providers-setup.ts +43 -0
  243. package/src/daemon/ride-shotgun-handler.ts +67 -2
  244. package/src/daemon/server.ts +60 -44
  245. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  246. package/src/daemon/session-agent-loop.ts +113 -275
  247. package/src/daemon/session-dynamic-profile.ts +1 -1
  248. package/src/daemon/session-history.ts +1 -1
  249. package/src/daemon/session-media-retry.ts +1 -1
  250. package/src/daemon/session-messaging.ts +37 -2
  251. package/src/daemon/session-notifiers.ts +5 -25
  252. package/src/daemon/session-process.ts +99 -59
  253. package/src/daemon/session-queue-manager.ts +98 -4
  254. package/src/daemon/session-runtime-assembly.ts +149 -15
  255. package/src/daemon/session-surfaces.ts +26 -4
  256. package/src/daemon/session-tool-setup.ts +28 -30
  257. package/src/daemon/session-workspace.ts +1 -1
  258. package/src/daemon/session.ts +24 -1
  259. package/src/daemon/shutdown-handlers.ts +122 -0
  260. package/src/daemon/trace-emitter.ts +1 -1
  261. package/src/daemon/watch-handler.ts +36 -33
  262. package/src/doordash/cart-queries.ts +787 -0
  263. package/src/doordash/client.ts +144 -127
  264. package/src/doordash/order-queries.ts +85 -0
  265. package/src/doordash/queries.ts +10 -1308
  266. package/src/doordash/search-queries.ts +203 -0
  267. package/src/doordash/session.ts +3 -2
  268. package/src/doordash/store-queries.ts +246 -0
  269. package/src/doordash/types.ts +367 -0
  270. package/src/email/providers/agentmail.ts +2 -1
  271. package/src/email/providers/index.ts +3 -2
  272. package/src/email/service.ts +3 -2
  273. package/src/errors.ts +43 -0
  274. package/src/home-base/prebuilt/seed.ts +1 -1
  275. package/src/hooks/cli.ts +6 -5
  276. package/src/hooks/config.ts +6 -8
  277. package/src/hooks/discovery.ts +6 -5
  278. package/src/hooks/manager.ts +4 -3
  279. package/src/hooks/runner.ts +2 -2
  280. package/src/hooks/templates.ts +5 -5
  281. package/src/inbound/public-ingress-urls.ts +3 -1
  282. package/src/index.ts +4 -2
  283. package/src/influencer/client.ts +1104 -0
  284. package/src/instrument.ts +4 -3
  285. package/src/logfire.ts +4 -3
  286. package/src/memory/admin.ts +25 -35
  287. package/src/memory/attachments-store.ts +4 -7
  288. package/src/memory/channel-delivery-store.ts +30 -1
  289. package/src/memory/channel-guardian-store.ts +200 -1
  290. package/src/memory/clarification-resolver.ts +37 -33
  291. package/src/memory/conflict-store.ts +67 -61
  292. package/src/memory/contradiction-checker.ts +141 -117
  293. package/src/memory/conversation-store.ts +335 -51
  294. package/src/memory/db-connection.ts +27 -4
  295. package/src/memory/db-init.ts +121 -4
  296. package/src/memory/db.ts +14 -1
  297. package/src/memory/embedding-backend.ts +27 -5
  298. package/src/memory/embedding-ollama.ts +2 -1
  299. package/src/memory/entity-extractor.ts +38 -35
  300. package/src/memory/guardian-action-store.ts +430 -0
  301. package/src/memory/inbox-escalation-projection.ts +59 -0
  302. package/src/memory/inbox-thread-store.ts +218 -0
  303. package/src/memory/ingress-invite-store.ts +338 -0
  304. package/src/memory/ingress-member-store.ts +350 -0
  305. package/src/memory/items-extractor.ts +91 -97
  306. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  307. package/src/memory/job-handlers/media-processing.ts +11 -42
  308. package/src/memory/job-handlers/summarization.ts +32 -26
  309. package/src/memory/job-utils.ts +3 -10
  310. package/src/memory/jobs-store.ts +6 -9
  311. package/src/memory/jobs-worker.ts +51 -36
  312. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  313. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  314. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  315. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  316. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  317. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  318. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  319. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  320. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  321. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  322. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  323. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  324. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  325. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  326. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  327. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  328. package/src/memory/migrations/017-memory-items-indexes.ts +12 -0
  329. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  330. package/src/memory/migrations/index.ts +24 -0
  331. package/src/memory/migrations/registry.ts +79 -0
  332. package/src/memory/migrations/validate-migration-state.ts +69 -0
  333. package/src/memory/qdrant-manager.ts +49 -8
  334. package/src/memory/query-builder.ts +1 -1
  335. package/src/memory/raw-query.ts +119 -0
  336. package/src/memory/recall-cache.ts +4 -1
  337. package/src/memory/retriever.ts +163 -47
  338. package/src/memory/schema-migration.ts +25 -984
  339. package/src/memory/schema.ts +130 -7
  340. package/src/memory/search/entity.ts +10 -19
  341. package/src/memory/search/lexical.ts +81 -52
  342. package/src/memory/search/ranking.ts +21 -22
  343. package/src/memory/search/semantic.ts +157 -19
  344. package/src/memory/shared-app-links-store.ts +4 -5
  345. package/src/memory/validation.ts +19 -0
  346. package/src/messaging/draft-store.ts +5 -6
  347. package/src/messaging/providers/sms/adapter.ts +3 -6
  348. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  349. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  350. package/src/messaging/providers/whatsapp/client.ts +67 -0
  351. package/src/messaging/style-analyzer.ts +5 -4
  352. package/src/messaging/thread-summarizer.ts +61 -69
  353. package/src/messaging/triage-engine.ts +62 -71
  354. package/src/migrations/config-merge.ts +53 -0
  355. package/src/migrations/data-layout.ts +68 -0
  356. package/src/migrations/data-merge.ts +33 -0
  357. package/src/migrations/hooks-merge.ts +90 -0
  358. package/src/migrations/index.ts +6 -0
  359. package/src/migrations/log.ts +23 -0
  360. package/src/migrations/skills-merge.ts +33 -0
  361. package/src/migrations/workspace-layout.ts +79 -0
  362. package/src/permissions/checker.ts +126 -11
  363. package/src/permissions/prompter.ts +14 -0
  364. package/src/permissions/shell-identity.ts +31 -1
  365. package/src/permissions/trust-store.ts +21 -1
  366. package/src/providers/anthropic/client.ts +4 -4
  367. package/src/providers/failover.ts +2 -2
  368. package/src/providers/model-intents.ts +70 -0
  369. package/src/providers/ollama/client.ts +2 -1
  370. package/src/providers/provider-send-message.ts +176 -0
  371. package/src/providers/registry.ts +71 -30
  372. package/src/providers/retry.ts +35 -1
  373. package/src/providers/types.ts +12 -1
  374. package/src/runtime/approval-conversation-turn.ts +97 -0
  375. package/src/runtime/approval-message-composer.ts +115 -5
  376. package/src/runtime/assistant-event-hub.ts +3 -1
  377. package/src/runtime/channel-approval-parser.ts +36 -2
  378. package/src/runtime/channel-approvals.ts +0 -21
  379. package/src/runtime/channel-guardian-service.ts +48 -7
  380. package/src/runtime/channel-readiness-service.ts +160 -34
  381. package/src/runtime/channel-readiness-types.ts +10 -4
  382. package/src/runtime/channel-retry-sweep.ts +184 -0
  383. package/src/runtime/guardian-context-resolver.ts +108 -0
  384. package/src/runtime/http-server.ts +289 -745
  385. package/src/runtime/http-types.ts +56 -3
  386. package/src/runtime/middleware/auth.ts +116 -0
  387. package/src/runtime/middleware/error-handler.ts +33 -0
  388. package/src/runtime/middleware/twilio-validation.ts +127 -0
  389. package/src/runtime/routes/app-routes.ts +1 -1
  390. package/src/runtime/routes/call-routes.ts +49 -6
  391. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  392. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  393. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  394. package/src/runtime/routes/channel-route-shared.ts +144 -0
  395. package/src/runtime/routes/channel-routes.ts +32 -1634
  396. package/src/runtime/routes/conversation-routes.ts +50 -7
  397. package/src/runtime/routes/events-routes.ts +2 -2
  398. package/src/runtime/routes/identity-routes.ts +126 -0
  399. package/src/runtime/routes/pairing-routes.ts +144 -0
  400. package/src/runtime/routes/run-routes.ts +15 -1
  401. package/src/runtime/run-orchestrator.ts +52 -34
  402. package/src/schedule/schedule-store.ts +36 -32
  403. package/src/schedule/scheduler.ts +3 -3
  404. package/src/security/encrypted-store.ts +5 -7
  405. package/src/security/oauth2.ts +45 -15
  406. package/src/security/parental-control-store.ts +183 -0
  407. package/src/security/secret-allowlist.ts +4 -3
  408. package/src/security/secret-scanner.ts +5 -5
  409. package/src/security/secure-keys.ts +1 -1
  410. package/src/security/token-manager.ts +3 -2
  411. package/src/services/vercel-deploy.ts +6 -2
  412. package/src/skills/tool-manifest.ts +3 -3
  413. package/src/skills/vellum-catalog-remote.ts +75 -16
  414. package/src/slack/slack-webhook.ts +2 -1
  415. package/src/swarm/orchestrator.ts +92 -1
  416. package/src/swarm/router-planner.ts +6 -9
  417. package/src/swarm/worker-prompts.ts +9 -12
  418. package/src/tasks/task-compiler.ts +19 -28
  419. package/src/tasks/task-runner.ts +1 -1
  420. package/src/tools/assets/search.ts +15 -14
  421. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  422. package/src/tools/browser/auto-navigate.ts +1 -0
  423. package/src/tools/browser/browser-execution.ts +13 -1
  424. package/src/tools/browser/browser-manager.ts +119 -4
  425. package/src/tools/browser/network-recorder.ts +5 -0
  426. package/src/tools/credentials/broker.ts +11 -2
  427. package/src/tools/credentials/metadata-store.ts +18 -14
  428. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  429. package/src/tools/credentials/vault.ts +49 -23
  430. package/src/tools/executor.ts +80 -18
  431. package/src/tools/host-terminal/cli-discover.ts +1 -1
  432. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  433. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  434. package/src/tools/network/script-proxy/server.ts +1 -1
  435. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  436. package/src/tools/network/web-fetch.ts +18 -2
  437. package/src/tools/network/web-search.ts +7 -3
  438. package/src/tools/reminder/reminder-store.ts +14 -15
  439. package/src/tools/schedule/create.ts +1 -0
  440. package/src/tools/schedule/list.ts +2 -1
  441. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  442. package/src/tools/skills/skill-script-runner.ts +24 -9
  443. package/src/tools/skills/skill-tool-factory.ts +1 -0
  444. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  445. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  446. package/src/tools/terminal/parser.ts +50 -0
  447. package/src/tools/watcher/delete.ts +6 -0
  448. package/src/tools/weather/service.ts +1 -1
  449. package/src/twitter/client.ts +190 -24
  450. package/src/twitter/session.ts +4 -3
  451. package/src/util/clipboard.ts +1 -1
  452. package/src/util/errors.ts +65 -8
  453. package/src/util/fs.ts +40 -0
  454. package/src/util/json.ts +10 -0
  455. package/src/util/log-redact.ts +189 -0
  456. package/src/util/logger.ts +25 -18
  457. package/src/util/object.ts +3 -0
  458. package/src/util/platform.ts +72 -365
  459. package/src/util/pricing.ts +1 -1
  460. package/src/util/promise-guard.ts +1 -1
  461. package/src/util/retry.ts +19 -0
  462. package/src/util/row-mapper.ts +79 -0
  463. package/src/util/silently.ts +21 -0
  464. package/src/watcher/engine.ts +5 -1
  465. package/src/watcher/provider-types.ts +20 -0
  466. package/src/watcher/providers/github.ts +156 -0
  467. package/src/watcher/providers/gmail.ts +1 -0
  468. package/src/watcher/providers/google-calendar.ts +1 -0
  469. package/src/watcher/providers/linear.ts +460 -0
  470. package/src/watcher/providers/slack.ts +1 -0
  471. package/src/work-items/work-item-runner.ts +1 -1
  472. package/src/workspace/git-service.ts +1 -1
  473. package/src/workspace/provider-commit-message-generator.ts +51 -22
  474. package/src/__tests__/call-bridge.test.ts +0 -517
  475. package/src/__tests__/session-process-bridge.test.ts +0 -244
  476. package/src/calls/call-bridge.ts +0 -168
  477. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +0 -137
  478. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
  479. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
  480. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
  481. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
  482. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
  483. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
  484. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
  485. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
  486. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
  487. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -0,0 +1,79 @@
1
+ import { existsSync, mkdirSync, renameSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { getRootDir, getWorkspaceDir } from '../util/platform.js';
4
+ import { migrationLog } from './log.js';
5
+ import { mergeSkippedConfigKeys } from './config-merge.js';
6
+ import { mergeLegacyHooks } from './hooks-merge.js';
7
+ import { mergeLegacySkills } from './skills-merge.js';
8
+ import { mergeLegacyDataEntries } from './data-merge.js';
9
+
10
+ /**
11
+ * Idempotent move: relocates source to destination for migration.
12
+ * - No-op if source is missing (already migrated or never existed).
13
+ * - No-op if destination already exists (avoids clobbering).
14
+ * - Creates destination parent directories as needed.
15
+ * - Logs warning on failure instead of throwing.
16
+ *
17
+ * Exported for testing; not intended for general use outside migrations.
18
+ */
19
+ export function migratePath(source: string, destination: string): void {
20
+ if (!existsSync(source)) return;
21
+ if (existsSync(destination)) {
22
+ migrationLog('debug', 'Migration skipped: destination already exists', { source, destination });
23
+ return;
24
+ }
25
+ try {
26
+ const destDir = dirname(destination);
27
+ if (!existsSync(destDir)) {
28
+ mkdirSync(destDir, { recursive: true });
29
+ }
30
+ renameSync(source, destination);
31
+ migrationLog('info', 'Migrated path', { from: source, to: destination });
32
+ } catch (err) {
33
+ migrationLog('warn', 'Failed to migrate path', { err: String(err), from: source, to: destination });
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Migrate from the flat ~/.vellum layout to the workspace-based layout.
39
+ *
40
+ * Step (a) is special: if the workspace dir doesn't exist yet but the old
41
+ * sandbox working dir (data/sandbox/fs) does, its contents are "extracted"
42
+ * to become the new workspace root via rename. All subsequent moves then
43
+ * land inside that workspace directory.
44
+ *
45
+ * Idempotent: safe to call on every startup — already-migrated items are
46
+ * skipped, and a second run is a no-op.
47
+ */
48
+ export function migrateToWorkspaceLayout(): void {
49
+ const root = getRootDir();
50
+ if (!existsSync(root)) return;
51
+
52
+ const ws = getWorkspaceDir();
53
+
54
+ // (a) Extract data/sandbox/fs -> workspace (only when workspace doesn't exist yet)
55
+ if (!existsSync(ws)) {
56
+ const sandboxFs = join(root, 'data', 'sandbox', 'fs');
57
+ if (existsSync(sandboxFs)) {
58
+ try {
59
+ renameSync(sandboxFs, ws);
60
+ migrationLog('info', 'Extracted sandbox/fs as workspace root', { from: sandboxFs, to: ws });
61
+ } catch (err) {
62
+ migrationLog('warn', 'Failed to extract sandbox/fs', { err: String(err), from: sandboxFs, to: ws });
63
+ }
64
+ }
65
+ }
66
+
67
+ // (b)-(h) Move legacy root-level items into workspace
68
+ migratePath(join(root, 'config.json'), join(ws, 'config.json'));
69
+ mergeSkippedConfigKeys(join(root, 'config.json'), join(ws, 'config.json'));
70
+ migratePath(join(root, 'data'), join(ws, 'data'));
71
+ mergeLegacyDataEntries(join(root, 'data'), join(ws, 'data'));
72
+ migratePath(join(root, 'hooks'), join(ws, 'hooks'));
73
+ mergeLegacyHooks(join(root, 'hooks'), join(ws, 'hooks'));
74
+ migratePath(join(root, 'IDENTITY.md'), join(ws, 'IDENTITY.md'));
75
+ migratePath(join(root, 'skills'), join(ws, 'skills'));
76
+ mergeLegacySkills(join(root, 'skills'), join(ws, 'skills'));
77
+ migratePath(join(root, 'SOUL.md'), join(ws, 'SOUL.md'));
78
+ migratePath(join(root, 'USER.md'), join(ws, 'USER.md'));
79
+ }
@@ -1,6 +1,6 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import { RiskLevel, type PermissionCheckResult, type AllowlistOption, type ScopeOption, type PolicyContext } from './types.js';
2
- import { findHighestPriorityRule } from './trust-store.js';
3
- import { parse } from '../tools/terminal/parser.js';
3
+ import { findHighestPriorityRule, onRulesChanged } from './trust-store.js';
4
4
  import { resolveSkillSelector } from '../config/skills.js';
5
5
  import { computeSkillVersionHash } from '../skills/version-hash.js';
6
6
  import { getTool } from '../tools/registry.js';
@@ -11,9 +11,40 @@ import { homedir } from 'node:os';
11
11
  import { looksLikeHostPortShorthand, looksLikePathOnlyInput } from '../tools/network/url-safety.js';
12
12
  import { normalizeFilePath, isSkillSourcePath } from '../skills/path-classifier.js';
13
13
  import { isWorkspaceScopedInvocation } from './workspace-policy.js';
14
- import { buildShellCommandCandidates, buildShellAllowlistOptions, type ParsedCommand } from './shell-identity.js';
14
+ import { buildShellCommandCandidates, buildShellAllowlistOptions, cachedParse, type ParsedCommand } from './shell-identity.js';
15
15
  import type { ManifestOverride } from '../tools/execution-target.js';
16
16
 
17
+ // ── Risk classification cache ────────────────────────────────────────────────
18
+ // classifyRisk() is called on every permission check and can invoke WASM
19
+ // parsing for shell commands. Cache results keyed on
20
+ // (toolName, inputHash, workingDir, manifestOverride).
21
+ // Invalidated when trust rules change since risk classification for file tools
22
+ // depends on skill source path checks which reference config, but the core
23
+ // risk logic is input-deterministic.
24
+ const RISK_CACHE_MAX = 256;
25
+ const riskCache = new Map<string, RiskLevel>();
26
+
27
+ function riskCacheKey(toolName: string, input: Record<string, unknown>, workingDir?: string, manifestOverride?: ManifestOverride): string {
28
+ const inputJson = JSON.stringify(input);
29
+ const hash = createHash('sha256')
30
+ .update(inputJson)
31
+ .update('\0')
32
+ .update(workingDir ?? '')
33
+ .update('\0')
34
+ .update(manifestOverride ? JSON.stringify(manifestOverride) : '')
35
+ .digest('hex');
36
+ return `${toolName}\0${hash}`;
37
+ }
38
+
39
+ /** Clear the risk classification cache. Called when trust rules change. */
40
+ export function clearRiskCache(): void {
41
+ riskCache.clear();
42
+ }
43
+
44
+ // Invalidate risk cache whenever trust rules change so that risk decisions
45
+ // referencing config-dependent checks (e.g. skill source paths) stay fresh.
46
+ onRulesChanged(clearRiskCache);
47
+
17
48
  // Ensures the legacy mode deprecation warning fires at most once per process.
18
49
  let _legacyDeprecationWarned = false;
19
50
 
@@ -33,8 +64,8 @@ const LOW_RISK_PROGRAMS = new Set([
33
64
  'man', 'help', 'info',
34
65
  'env', 'printenv', 'set',
35
66
  'diff', 'sort', 'uniq', 'cut', 'tr', 'tee', 'xargs',
36
- 'jq', 'yq', 'sed', 'awk',
37
- 'curl', 'wget', 'http', 'dig', 'nslookup', 'ping',
67
+ 'jq', 'yq',
68
+ 'http', 'dig', 'nslookup', 'ping',
38
69
  'tree', 'du', 'df',
39
70
  ]);
40
71
 
@@ -56,6 +87,41 @@ const LOW_RISK_GIT_SUBCOMMANDS = new Set([
56
87
  'cat-file', 'reflog',
57
88
  ]);
58
89
 
90
+ // Commands that wrap another program — the real program appears as the first
91
+ // non-flag argument. When one of these is the segment program we look through
92
+ // its args to find the effective program (e.g. `env curl …` → curl).
93
+ const WRAPPER_PROGRAMS = new Set([
94
+ 'env', 'nice', 'nohup', 'time', 'command', 'exec',
95
+ 'strace', 'ltrace', 'ionice', 'taskset', 'timeout',
96
+ ]);
97
+
98
+ // `env` flags that consume the next positional argument as their value.
99
+ // Without this, `env -u curl echo` would incorrectly identify `curl` (the
100
+ // value of -u) as the wrapped program instead of `echo`.
101
+ const ENV_VALUE_FLAGS = new Set(['-u', '--unset', '-C', '--chdir']);
102
+
103
+ /**
104
+ * Given a segment whose program is a known wrapper, return the first
105
+ * non-flag argument (i.e. the wrapped program name). Returns `undefined`
106
+ * when no suitable argument is found.
107
+ *
108
+ * Handles `env` specially: skips `VAR=value` pairs and value-taking flags
109
+ * like `-u NAME` and `-C DIR`.
110
+ */
111
+ function getWrappedProgram(seg: { program: string; args: string[] }): string | undefined {
112
+ const isEnv = seg.program === 'env';
113
+ for (let i = 0; i < seg.args.length; i++) {
114
+ const arg = seg.args[i];
115
+ if (arg.startsWith('-')) {
116
+ if (isEnv && ENV_VALUE_FLAGS.has(arg)) i++; // skip the value argument
117
+ continue;
118
+ }
119
+ if (isEnv && arg.includes('=')) continue; // skip env VAR=value pairs
120
+ return arg;
121
+ }
122
+ return undefined;
123
+ }
124
+
59
125
  function isHighRiskRm(args: string[]): boolean {
60
126
  // rm with -r, -rf, -fr, or targeting root/home
61
127
  for (const arg of args) {
@@ -245,7 +311,36 @@ async function buildCommandCandidates(toolName: string, input: Record<string, un
245
311
  return [...new Set(candidates)];
246
312
  }
247
313
 
248
- export async function classifyRisk(toolName: string, input: Record<string, unknown>, workingDir?: string, preParsed?: ParsedCommand, manifestOverride?: ManifestOverride): Promise<RiskLevel> {
314
+ export async function classifyRisk(toolName: string, input: Record<string, unknown>, workingDir?: string, preParsed?: ParsedCommand, manifestOverride?: ManifestOverride, signal?: AbortSignal): Promise<RiskLevel> {
315
+ signal?.throwIfAborted();
316
+
317
+ // Check cache first (skip when preParsed is provided since caller already
318
+ // parsed and we'd just be duplicating the key computation cost).
319
+ const cacheKey = preParsed ? null : riskCacheKey(toolName, input, workingDir, manifestOverride);
320
+ if (cacheKey) {
321
+ const cached = riskCache.get(cacheKey);
322
+ if (cached !== undefined) {
323
+ // LRU refresh
324
+ riskCache.delete(cacheKey);
325
+ riskCache.set(cacheKey, cached);
326
+ return cached;
327
+ }
328
+ }
329
+
330
+ const result = await classifyRiskUncached(toolName, input, workingDir, preParsed, manifestOverride);
331
+
332
+ if (cacheKey) {
333
+ if (riskCache.size >= RISK_CACHE_MAX) {
334
+ const oldest = riskCache.keys().next().value;
335
+ if (oldest !== undefined) riskCache.delete(oldest);
336
+ }
337
+ riskCache.set(cacheKey, result);
338
+ }
339
+
340
+ return result;
341
+ }
342
+
343
+ async function classifyRiskUncached(toolName: string, input: Record<string, unknown>, workingDir?: string, preParsed?: ParsedCommand, manifestOverride?: ManifestOverride): Promise<RiskLevel> {
249
344
  if (toolName === 'file_read') return RiskLevel.Low;
250
345
  if (toolName === 'file_write' || toolName === 'file_edit') {
251
346
  const filePath = getStringField(input, 'path', 'file_path');
@@ -285,7 +380,7 @@ export async function classifyRisk(toolName: string, input: Record<string, unkno
285
380
  const command = (input.command as string) ?? '';
286
381
  if (!command.trim()) return RiskLevel.Low;
287
382
 
288
- const parsed = preParsed ?? await parse(command);
383
+ const parsed = preParsed ?? await cachedParse(command);
289
384
 
290
385
  // Dangerous patterns → High
291
386
  if (parsed.dangerousPatterns.length > 0) return RiskLevel.High;
@@ -307,11 +402,27 @@ export async function classifyRisk(toolName: string, input: Record<string, unkno
307
402
  continue;
308
403
  }
309
404
 
310
- if (prog === 'chmod' || prog === 'chown' || prog === 'chgrp') {
405
+ if (prog === 'chmod' || prog === 'chown' || prog === 'chgrp'
406
+ || prog === 'sed' || prog === 'awk') {
407
+ maxRisk = RiskLevel.Medium;
408
+ continue;
409
+ }
410
+
411
+ // curl/wget can download and execute arbitrary code from the internet.
412
+ // Also catch wrapped invocations like `env curl …` or `nice wget …`.
413
+ if (prog === 'curl' || prog === 'wget') {
311
414
  maxRisk = RiskLevel.Medium;
312
415
  continue;
313
416
  }
314
417
 
418
+ if (WRAPPER_PROGRAMS.has(prog)) {
419
+ const wrapped = getWrappedProgram(seg);
420
+ if (wrapped === 'curl' || wrapped === 'wget') {
421
+ maxRisk = RiskLevel.Medium;
422
+ continue;
423
+ }
424
+ }
425
+
315
426
  if (prog === 'git') {
316
427
  const subcommand = seg.args[0];
317
428
  if (subcommand && LOW_RISK_GIT_SUBCOMMANDS.has(subcommand)) {
@@ -360,17 +471,20 @@ export async function check(
360
471
  workingDir: string,
361
472
  policyContext?: PolicyContext,
362
473
  manifestOverride?: ManifestOverride,
474
+ signal?: AbortSignal,
363
475
  ): Promise<PermissionCheckResult> {
476
+ signal?.throwIfAborted();
477
+
364
478
  // For shell tools, parse once and share the result to avoid duplicate tree-sitter work.
365
479
  let shellParsed: ParsedCommand | undefined;
366
480
  if (toolName === 'bash' || toolName === 'host_bash') {
367
481
  const command = ((input.command as string) ?? '').trim();
368
482
  if (command) {
369
- shellParsed = await parse(command);
483
+ shellParsed = await cachedParse(command);
370
484
  }
371
485
  }
372
486
 
373
- const risk = await classifyRisk(toolName, input, workingDir, shellParsed, manifestOverride);
487
+ const risk = await classifyRisk(toolName, input, workingDir, shellParsed, manifestOverride, signal);
374
488
 
375
489
  // Build command string candidates for rule matching
376
490
  const commandCandidates = await buildCommandCandidates(toolName, input, workingDir, shellParsed);
@@ -500,7 +614,8 @@ function friendlyHostname(url: URL): string {
500
614
  return url.hostname.replace(/^www\./, '');
501
615
  }
502
616
 
503
- export async function generateAllowlistOptions(toolName: string, input: Record<string, unknown>): Promise<AllowlistOption[]> {
617
+ export async function generateAllowlistOptions(toolName: string, input: Record<string, unknown>, signal?: AbortSignal): Promise<AllowlistOption[]> {
618
+ signal?.throwIfAborted();
504
619
  if (toolName === 'bash' || toolName === 'host_bash') {
505
620
  const command = ((input.command as string) ?? '').trim();
506
621
  return buildShellAllowlistOptions(command);
@@ -43,12 +43,15 @@ export class PermissionPrompter {
43
43
  sessionId?: string,
44
44
  executionTarget?: ExecutionTarget,
45
45
  persistentDecisionsAllowed?: boolean,
46
+ signal?: AbortSignal,
46
47
  ): Promise<{
47
48
  decision: UserDecision;
48
49
  selectedPattern?: string;
49
50
  selectedScope?: string;
50
51
  decisionContext?: string;
51
52
  }> {
53
+ if (signal?.aborted) return { decision: 'deny' };
54
+
52
55
  const requestId = uuid();
53
56
 
54
57
  return new Promise((resolve, reject) => {
@@ -61,6 +64,17 @@ export class PermissionPrompter {
61
64
 
62
65
  this.pending.set(requestId, { resolve, reject, timer });
63
66
 
67
+ if (signal) {
68
+ const onAbort = () => {
69
+ if (this.pending.has(requestId)) {
70
+ clearTimeout(timer);
71
+ this.pending.delete(requestId);
72
+ resolve({ decision: 'deny' });
73
+ }
74
+ };
75
+ signal.addEventListener('abort', onAbort, { once: true });
76
+ }
77
+
64
78
  this.sendToClient({
65
79
  type: 'confirmation_request',
66
80
  requestId,
@@ -3,6 +3,36 @@ import type { AllowlistOption } from './types.js';
3
3
 
4
4
  export type { ParsedCommand };
5
5
 
6
+ // ── Shell parse result cache ─────────────────────────────────────────────────
7
+ // Shell parsing via web-tree-sitter WASM is deterministic — the same command
8
+ // string always produces the same ParsedCommand. Cache results to avoid
9
+ // redundant WASM invocations on repeated permission checks.
10
+ const PARSE_CACHE_MAX = 256;
11
+ const parseCache = new Map<string, ParsedCommand>();
12
+
13
+ export async function cachedParse(command: string): Promise<ParsedCommand> {
14
+ const cached = parseCache.get(command);
15
+ if (cached !== undefined) {
16
+ // LRU refresh: move to end of insertion order
17
+ parseCache.delete(command);
18
+ parseCache.set(command, cached);
19
+ return cached;
20
+ }
21
+ const result = await parse(command);
22
+ // Evict oldest entry if at capacity
23
+ if (parseCache.size >= PARSE_CACHE_MAX) {
24
+ const oldest = parseCache.keys().next().value;
25
+ if (oldest !== undefined) parseCache.delete(oldest);
26
+ }
27
+ parseCache.set(command, result);
28
+ return result;
29
+ }
30
+
31
+ /** Clear the shell parse cache. Exposed for testing. */
32
+ export function clearShellParseCache(): void {
33
+ parseCache.clear();
34
+ }
35
+
6
36
  export interface ShellActionKey {
7
37
  /** e.g. "action:gh", "action:gh pr", "action:gh pr view" */
8
38
  key: string;
@@ -40,7 +70,7 @@ const MAX_ACTION_KEY_DEPTH = 3;
40
70
  * identity information for permission decisions.
41
71
  */
42
72
  export async function analyzeShellCommand(command: string, preParsed?: ParsedCommand): Promise<ShellIdentityAnalysis> {
43
- const parsed = preParsed ?? await parse(command);
73
+ const parsed = preParsed ?? await cachedParse(command);
44
74
 
45
75
  const operators: string[] = [];
46
76
  for (const seg of parsed.segments) {
@@ -21,6 +21,21 @@ interface TrustFile {
21
21
  let cachedRules: TrustRule[] | null = null;
22
22
  let cachedStarterBundleAccepted: boolean | null = null;
23
23
 
24
+ // Callbacks invoked when trust rules change (add/update/remove/clear).
25
+ // Used by the permission checker to invalidate dependent caches.
26
+ const rulesChangedListeners: Array<() => void> = [];
27
+
28
+ /** Register a callback to be invoked whenever trust rules change. */
29
+ export function onRulesChanged(listener: () => void): void {
30
+ rulesChangedListeners.push(listener);
31
+ }
32
+
33
+ function notifyRulesChanged(): void {
34
+ for (const listener of rulesChangedListeners) {
35
+ listener();
36
+ }
37
+ }
38
+
24
39
  /**
25
40
  * Cache of pre-compiled Minimatch objects keyed by pattern string.
26
41
  * Rebuilt whenever cachedRules changes. Avoids re-parsing glob patterns
@@ -326,7 +341,7 @@ function saveToDisk(rules: TrustRule[]): void {
326
341
  }
327
342
 
328
343
  function getRules(): TrustRule[] {
329
- if (cachedRules === null) {
344
+ if (cachedRules == null) {
330
345
  cachedRules = loadFromDisk();
331
346
  rebuildPatternCache(cachedRules);
332
347
  }
@@ -368,6 +383,7 @@ export function addRule(
368
383
  cachedRules = rules;
369
384
  rebuildPatternCache(rules);
370
385
  saveToDisk(rules);
386
+ notifyRulesChanged();
371
387
  log.info({ rule }, 'Added trust rule');
372
388
  return rule;
373
389
  }
@@ -395,6 +411,7 @@ export function updateRule(
395
411
  cachedRules = rules;
396
412
  rebuildPatternCache(rules);
397
413
  saveToDisk(rules);
414
+ notifyRulesChanged();
398
415
  log.info({ rule }, 'Updated trust rule');
399
416
  return rule;
400
417
  }
@@ -412,6 +429,7 @@ export function removeRule(id: string): boolean {
412
429
  cachedRules = rules;
413
430
  rebuildPatternCache(rules);
414
431
  saveToDisk(rules);
432
+ notifyRulesChanged();
415
433
  log.info({ id }, 'Removed trust rule');
416
434
  return true;
417
435
  }
@@ -508,6 +526,7 @@ export function clearAllRules(): void {
508
526
  cachedRules = rules;
509
527
  rebuildPatternCache(rules);
510
528
  saveToDisk(rules);
529
+ notifyRulesChanged();
511
530
  log.info('Cleared all user trust rules (default rules preserved)');
512
531
  }
513
532
 
@@ -608,6 +627,7 @@ export function acceptStarterBundle(): AcceptStarterBundleResult {
608
627
  cachedRules = rules;
609
628
  rebuildPatternCache(rules);
610
629
  saveToDisk(rules);
630
+ notifyRulesChanged();
611
631
  log.info({ rulesAdded: added }, 'Starter approval bundle accepted');
612
632
 
613
633
  return { accepted: true, rulesAdded: added, alreadyAccepted: false };
@@ -58,12 +58,12 @@ export const PLACEHOLDER_BLOCKS_OMITTED = '\x00__PLACEHOLDER__[internal blocks o
58
58
 
59
59
  /** Type-guard for tool_use blocks in Anthropic-formatted content. */
60
60
  function isToolUseBlock(block: unknown): block is Anthropic.ToolUseBlockParam {
61
- return typeof block === 'object' && block !== null && (block as { type: string }).type === 'tool_use';
61
+ return typeof block === 'object' && block != null && (block as { type: string }).type === 'tool_use';
62
62
  }
63
63
 
64
64
  /** Type-guard for tool_result blocks in Anthropic-formatted content. */
65
65
  function isToolResultBlock(block: unknown): block is Anthropic.ToolResultBlockParam {
66
- return typeof block === 'object' && block !== null && (block as { type: string }).type === 'tool_result';
66
+ return typeof block === 'object' && block != null && (block as { type: string }).type === 'tool_result';
67
67
  }
68
68
 
69
69
  /**
@@ -379,12 +379,12 @@ export class AnthropicProvider implements Provider {
379
379
  const content = m.content
380
380
  .map((block) => {
381
381
  const result = this.toAnthropicBlockSafe(block);
382
- if (result === null) {
382
+ if (result == null) {
383
383
  droppedUnknownBlock = true;
384
384
  }
385
385
  return result;
386
386
  })
387
- .filter((block): block is Anthropic.ContentBlockParam => block !== null)
387
+ .filter((block): block is Anthropic.ContentBlockParam => block != null)
388
388
  .filter((block) => !(block.type === 'text' && !(block as { text?: string }).text?.trim()));
389
389
 
390
390
  // Preserve assistant turns that would otherwise become empty after filtering
@@ -77,7 +77,7 @@ export class FailoverProvider implements Provider {
77
77
  const now = Date.now();
78
78
 
79
79
  // Skip providers that are still in cooldown
80
- if (health.unhealthySince !== null) {
80
+ if (health.unhealthySince != null) {
81
81
  const elapsed = now - health.unhealthySince;
82
82
  if (elapsed < this.cooldownMs) {
83
83
  log.debug(
@@ -93,7 +93,7 @@ export class FailoverProvider implements Provider {
93
93
  try {
94
94
  const response = await provider.sendMessage(messages, tools, systemPrompt, options);
95
95
  // Success — mark healthy
96
- if (health.unhealthySince !== null) {
96
+ if (health.unhealthySince != null) {
97
97
  log.info({ provider: provider.name }, 'Provider recovered, marking healthy');
98
98
  health.unhealthySince = null;
99
99
  }
@@ -0,0 +1,70 @@
1
+ import type { ModelIntent } from './types.js';
2
+
3
+ const PROVIDER_DEFAULT_MODELS = {
4
+ anthropic: 'claude-opus-4-6',
5
+ openai: 'gpt-5.2',
6
+ gemini: 'gemini-3-flash',
7
+ ollama: 'llama3.2',
8
+ fireworks: 'accounts/fireworks/models/kimi-k2p5',
9
+ openrouter: 'x-ai/grok-4',
10
+ } as const;
11
+
12
+ type KnownProviderName = keyof typeof PROVIDER_DEFAULT_MODELS;
13
+
14
+ const PROVIDER_MODEL_INTENTS: Record<KnownProviderName, Record<ModelIntent, string>> = {
15
+ anthropic: {
16
+ 'latency-optimized': 'claude-haiku-4-5-20251001',
17
+ 'quality-optimized': 'claude-opus-4-6',
18
+ 'vision-optimized': 'claude-sonnet-4-6',
19
+ },
20
+ openai: {
21
+ 'latency-optimized': 'gpt-4o-mini',
22
+ 'quality-optimized': 'gpt-5.2',
23
+ 'vision-optimized': 'gpt-4o',
24
+ },
25
+ gemini: {
26
+ 'latency-optimized': 'gemini-3-flash',
27
+ 'quality-optimized': 'gemini-3-flash',
28
+ 'vision-optimized': 'gemini-3-flash',
29
+ },
30
+ ollama: {
31
+ 'latency-optimized': 'llama3.2',
32
+ 'quality-optimized': 'llama3.2',
33
+ 'vision-optimized': 'llama3.2',
34
+ },
35
+ fireworks: {
36
+ 'latency-optimized': 'accounts/fireworks/models/kimi-k2p5',
37
+ 'quality-optimized': 'accounts/fireworks/models/kimi-k2p5',
38
+ 'vision-optimized': 'accounts/fireworks/models/kimi-k2p5',
39
+ },
40
+ openrouter: {
41
+ 'latency-optimized': 'x-ai/grok-4',
42
+ 'quality-optimized': 'x-ai/grok-4',
43
+ 'vision-optimized': 'x-ai/grok-4',
44
+ },
45
+ };
46
+
47
+ const MODEL_INTENTS = new Set<ModelIntent>([
48
+ 'latency-optimized',
49
+ 'quality-optimized',
50
+ 'vision-optimized',
51
+ ]);
52
+
53
+ export function isModelIntent(value: unknown): value is ModelIntent {
54
+ return typeof value === 'string' && MODEL_INTENTS.has(value as ModelIntent);
55
+ }
56
+
57
+ export function getProviderDefaultModel(providerName: string): string {
58
+ const knownProvider = providerName as KnownProviderName;
59
+ return PROVIDER_DEFAULT_MODELS[knownProvider] ?? PROVIDER_DEFAULT_MODELS.anthropic;
60
+ }
61
+
62
+ export function resolveModelIntent(providerName: string, intent: ModelIntent): string {
63
+ const knownProvider = providerName as KnownProviderName;
64
+ const providerIntentModels = PROVIDER_MODEL_INTENTS[knownProvider];
65
+ if (providerIntentModels) {
66
+ return providerIntentModels[intent];
67
+ }
68
+ return getProviderDefaultModel(providerName);
69
+ }
70
+
@@ -1,4 +1,5 @@
1
1
  import { OpenAIProvider } from '../openai/client.js';
2
+ import { getOllamaBaseUrlEnv } from '../../config/env.js';
2
3
 
3
4
  export interface OllamaProviderOptions {
4
5
  apiKey?: string;
@@ -20,7 +21,7 @@ export class OllamaProvider extends OpenAIProvider {
20
21
  }
21
22
 
22
23
  function resolveBaseUrl(optionBaseUrl?: string): string {
23
- for (const candidate of [optionBaseUrl, process.env.OLLAMA_BASE_URL]) {
24
+ for (const candidate of [optionBaseUrl, getOllamaBaseUrlEnv()]) {
24
25
  if (typeof candidate === 'string') {
25
26
  const trimmed = candidate.trim();
26
27
  if (trimmed.length > 0) return trimmed;