@vellumai/assistant 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (486) 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 +26 -2
  164. package/src/config/env-registry.ts +162 -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 +156 -1137
  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 +217 -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 +54 -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 +315 -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 +60 -0
  236. package/src/daemon/ipc-contract-inventory.ts +55 -29
  237. package/src/daemon/ipc-contract.ts +226 -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 +96 -4
  254. package/src/daemon/session-runtime-assembly.ts +149 -15
  255. package/src/daemon/session-surfaces.ts +19 -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 +10 -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 +160 -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 +119 -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/channel-approval-parser.ts +36 -2
  377. package/src/runtime/channel-approvals.ts +0 -21
  378. package/src/runtime/channel-guardian-service.ts +48 -7
  379. package/src/runtime/channel-readiness-service.ts +160 -34
  380. package/src/runtime/channel-readiness-types.ts +10 -4
  381. package/src/runtime/channel-retry-sweep.ts +184 -0
  382. package/src/runtime/guardian-context-resolver.ts +108 -0
  383. package/src/runtime/http-server.ts +275 -743
  384. package/src/runtime/http-types.ts +56 -3
  385. package/src/runtime/middleware/auth.ts +116 -0
  386. package/src/runtime/middleware/error-handler.ts +33 -0
  387. package/src/runtime/middleware/twilio-validation.ts +127 -0
  388. package/src/runtime/routes/app-routes.ts +1 -1
  389. package/src/runtime/routes/call-routes.ts +49 -6
  390. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  391. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  392. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  393. package/src/runtime/routes/channel-route-shared.ts +144 -0
  394. package/src/runtime/routes/channel-routes.ts +32 -1634
  395. package/src/runtime/routes/conversation-routes.ts +50 -7
  396. package/src/runtime/routes/events-routes.ts +2 -2
  397. package/src/runtime/routes/identity-routes.ts +126 -0
  398. package/src/runtime/routes/pairing-routes.ts +143 -0
  399. package/src/runtime/routes/run-routes.ts +15 -1
  400. package/src/runtime/run-orchestrator.ts +52 -34
  401. package/src/schedule/schedule-store.ts +36 -32
  402. package/src/schedule/scheduler.ts +3 -3
  403. package/src/security/encrypted-store.ts +5 -7
  404. package/src/security/oauth2.ts +45 -15
  405. package/src/security/parental-control-store.ts +183 -0
  406. package/src/security/secret-allowlist.ts +4 -3
  407. package/src/security/secret-scanner.ts +5 -5
  408. package/src/security/secure-keys.ts +1 -1
  409. package/src/security/token-manager.ts +3 -2
  410. package/src/services/vercel-deploy.ts +6 -2
  411. package/src/skills/tool-manifest.ts +3 -3
  412. package/src/skills/vellum-catalog-remote.ts +75 -16
  413. package/src/slack/slack-webhook.ts +2 -1
  414. package/src/swarm/orchestrator.ts +92 -1
  415. package/src/swarm/router-planner.ts +6 -9
  416. package/src/swarm/worker-prompts.ts +9 -12
  417. package/src/tasks/task-compiler.ts +19 -28
  418. package/src/tasks/task-runner.ts +1 -1
  419. package/src/tools/assets/search.ts +15 -14
  420. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  421. package/src/tools/browser/auto-navigate.ts +1 -0
  422. package/src/tools/browser/browser-execution.ts +10 -1
  423. package/src/tools/browser/browser-manager.ts +119 -4
  424. package/src/tools/browser/network-recorder.ts +5 -0
  425. package/src/tools/credentials/broker.ts +11 -2
  426. package/src/tools/credentials/metadata-store.ts +18 -14
  427. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  428. package/src/tools/credentials/vault.ts +49 -23
  429. package/src/tools/executor.ts +68 -9
  430. package/src/tools/host-terminal/cli-discover.ts +1 -1
  431. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  432. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  433. package/src/tools/network/script-proxy/server.ts +1 -1
  434. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  435. package/src/tools/network/web-fetch.ts +18 -2
  436. package/src/tools/network/web-search.ts +7 -3
  437. package/src/tools/reminder/reminder-store.ts +14 -15
  438. package/src/tools/schedule/create.ts +1 -0
  439. package/src/tools/schedule/list.ts +2 -1
  440. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  441. package/src/tools/skills/skill-script-runner.ts +24 -9
  442. package/src/tools/skills/skill-tool-factory.ts +1 -0
  443. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  444. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  445. package/src/tools/terminal/parser.ts +50 -0
  446. package/src/tools/watcher/delete.ts +6 -0
  447. package/src/tools/weather/service.ts +1 -1
  448. package/src/twitter/client.ts +190 -24
  449. package/src/twitter/session.ts +4 -3
  450. package/src/util/clipboard.ts +1 -1
  451. package/src/util/errors.ts +65 -8
  452. package/src/util/fs.ts +40 -0
  453. package/src/util/json.ts +10 -0
  454. package/src/util/log-redact.ts +189 -0
  455. package/src/util/logger.ts +19 -17
  456. package/src/util/object.ts +3 -0
  457. package/src/util/platform.ts +72 -365
  458. package/src/util/pricing.ts +1 -1
  459. package/src/util/promise-guard.ts +1 -1
  460. package/src/util/retry.ts +19 -0
  461. package/src/util/row-mapper.ts +79 -0
  462. package/src/util/silently.ts +21 -0
  463. package/src/watcher/engine.ts +5 -1
  464. package/src/watcher/provider-types.ts +20 -0
  465. package/src/watcher/providers/github.ts +156 -0
  466. package/src/watcher/providers/gmail.ts +1 -0
  467. package/src/watcher/providers/google-calendar.ts +1 -0
  468. package/src/watcher/providers/linear.ts +460 -0
  469. package/src/watcher/providers/slack.ts +1 -0
  470. package/src/work-items/work-item-runner.ts +1 -1
  471. package/src/workspace/git-service.ts +1 -1
  472. package/src/workspace/provider-commit-message-generator.ts +51 -22
  473. package/src/__tests__/call-bridge.test.ts +0 -517
  474. package/src/__tests__/session-process-bridge.test.ts +0 -244
  475. package/src/calls/call-bridge.ts +0 -168
  476. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +0 -137
  477. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
  478. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
  479. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
  480. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
  481. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
  482. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
  483. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
  484. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
  485. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
  486. 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,33 @@ 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 (toolName, inputHash).
20
+ // Invalidated when trust rules change since risk classification for file tools
21
+ // depends on skill source path checks which reference config, but the core
22
+ // risk logic is input-deterministic.
23
+ const RISK_CACHE_MAX = 256;
24
+ const riskCache = new Map<string, RiskLevel>();
25
+
26
+ function riskCacheKey(toolName: string, input: Record<string, unknown>): string {
27
+ const inputJson = JSON.stringify(input);
28
+ const hash = createHash('sha256').update(inputJson).digest('hex');
29
+ return `${toolName}\0${hash}`;
30
+ }
31
+
32
+ /** Clear the risk classification cache. Called when trust rules change. */
33
+ export function clearRiskCache(): void {
34
+ riskCache.clear();
35
+ }
36
+
37
+ // Invalidate risk cache whenever trust rules change so that risk decisions
38
+ // referencing config-dependent checks (e.g. skill source paths) stay fresh.
39
+ onRulesChanged(clearRiskCache);
40
+
17
41
  // Ensures the legacy mode deprecation warning fires at most once per process.
18
42
  let _legacyDeprecationWarned = false;
19
43
 
@@ -33,8 +57,8 @@ const LOW_RISK_PROGRAMS = new Set([
33
57
  'man', 'help', 'info',
34
58
  'env', 'printenv', 'set',
35
59
  'diff', 'sort', 'uniq', 'cut', 'tr', 'tee', 'xargs',
36
- 'jq', 'yq', 'sed', 'awk',
37
- 'curl', 'wget', 'http', 'dig', 'nslookup', 'ping',
60
+ 'jq', 'yq',
61
+ 'http', 'dig', 'nslookup', 'ping',
38
62
  'tree', 'du', 'df',
39
63
  ]);
40
64
 
@@ -56,6 +80,41 @@ const LOW_RISK_GIT_SUBCOMMANDS = new Set([
56
80
  'cat-file', 'reflog',
57
81
  ]);
58
82
 
83
+ // Commands that wrap another program — the real program appears as the first
84
+ // non-flag argument. When one of these is the segment program we look through
85
+ // its args to find the effective program (e.g. `env curl …` → curl).
86
+ const WRAPPER_PROGRAMS = new Set([
87
+ 'env', 'nice', 'nohup', 'time', 'command', 'exec',
88
+ 'strace', 'ltrace', 'ionice', 'taskset', 'timeout',
89
+ ]);
90
+
91
+ // `env` flags that consume the next positional argument as their value.
92
+ // Without this, `env -u curl echo` would incorrectly identify `curl` (the
93
+ // value of -u) as the wrapped program instead of `echo`.
94
+ const ENV_VALUE_FLAGS = new Set(['-u', '--unset', '-C', '--chdir']);
95
+
96
+ /**
97
+ * Given a segment whose program is a known wrapper, return the first
98
+ * non-flag argument (i.e. the wrapped program name). Returns `undefined`
99
+ * when no suitable argument is found.
100
+ *
101
+ * Handles `env` specially: skips `VAR=value` pairs and value-taking flags
102
+ * like `-u NAME` and `-C DIR`.
103
+ */
104
+ function getWrappedProgram(seg: { program: string; args: string[] }): string | undefined {
105
+ const isEnv = seg.program === 'env';
106
+ for (let i = 0; i < seg.args.length; i++) {
107
+ const arg = seg.args[i];
108
+ if (arg.startsWith('-')) {
109
+ if (isEnv && ENV_VALUE_FLAGS.has(arg)) i++; // skip the value argument
110
+ continue;
111
+ }
112
+ if (isEnv && arg.includes('=')) continue; // skip env VAR=value pairs
113
+ return arg;
114
+ }
115
+ return undefined;
116
+ }
117
+
59
118
  function isHighRiskRm(args: string[]): boolean {
60
119
  // rm with -r, -rf, -fr, or targeting root/home
61
120
  for (const arg of args) {
@@ -245,7 +304,36 @@ async function buildCommandCandidates(toolName: string, input: Record<string, un
245
304
  return [...new Set(candidates)];
246
305
  }
247
306
 
248
- export async function classifyRisk(toolName: string, input: Record<string, unknown>, workingDir?: string, preParsed?: ParsedCommand, manifestOverride?: ManifestOverride): Promise<RiskLevel> {
307
+ export async function classifyRisk(toolName: string, input: Record<string, unknown>, workingDir?: string, preParsed?: ParsedCommand, manifestOverride?: ManifestOverride, signal?: AbortSignal): Promise<RiskLevel> {
308
+ if (signal?.aborted) throw new Error('Cancelled');
309
+
310
+ // Check cache first (skip when preParsed is provided since caller already
311
+ // parsed and we'd just be duplicating the key computation cost).
312
+ const cacheKey = preParsed ? null : riskCacheKey(toolName, input);
313
+ if (cacheKey) {
314
+ const cached = riskCache.get(cacheKey);
315
+ if (cached !== undefined) {
316
+ // LRU refresh
317
+ riskCache.delete(cacheKey);
318
+ riskCache.set(cacheKey, cached);
319
+ return cached;
320
+ }
321
+ }
322
+
323
+ const result = await classifyRiskUncached(toolName, input, workingDir, preParsed, manifestOverride);
324
+
325
+ if (cacheKey) {
326
+ if (riskCache.size >= RISK_CACHE_MAX) {
327
+ const oldest = riskCache.keys().next().value;
328
+ if (oldest !== undefined) riskCache.delete(oldest);
329
+ }
330
+ riskCache.set(cacheKey, result);
331
+ }
332
+
333
+ return result;
334
+ }
335
+
336
+ async function classifyRiskUncached(toolName: string, input: Record<string, unknown>, workingDir?: string, preParsed?: ParsedCommand, manifestOverride?: ManifestOverride): Promise<RiskLevel> {
249
337
  if (toolName === 'file_read') return RiskLevel.Low;
250
338
  if (toolName === 'file_write' || toolName === 'file_edit') {
251
339
  const filePath = getStringField(input, 'path', 'file_path');
@@ -285,7 +373,7 @@ export async function classifyRisk(toolName: string, input: Record<string, unkno
285
373
  const command = (input.command as string) ?? '';
286
374
  if (!command.trim()) return RiskLevel.Low;
287
375
 
288
- const parsed = preParsed ?? await parse(command);
376
+ const parsed = preParsed ?? await cachedParse(command);
289
377
 
290
378
  // Dangerous patterns → High
291
379
  if (parsed.dangerousPatterns.length > 0) return RiskLevel.High;
@@ -307,11 +395,27 @@ export async function classifyRisk(toolName: string, input: Record<string, unkno
307
395
  continue;
308
396
  }
309
397
 
310
- if (prog === 'chmod' || prog === 'chown' || prog === 'chgrp') {
398
+ if (prog === 'chmod' || prog === 'chown' || prog === 'chgrp'
399
+ || prog === 'sed' || prog === 'awk') {
400
+ maxRisk = RiskLevel.Medium;
401
+ continue;
402
+ }
403
+
404
+ // curl/wget can download and execute arbitrary code from the internet.
405
+ // Also catch wrapped invocations like `env curl …` or `nice wget …`.
406
+ if (prog === 'curl' || prog === 'wget') {
311
407
  maxRisk = RiskLevel.Medium;
312
408
  continue;
313
409
  }
314
410
 
411
+ if (WRAPPER_PROGRAMS.has(prog)) {
412
+ const wrapped = getWrappedProgram(seg);
413
+ if (wrapped === 'curl' || wrapped === 'wget') {
414
+ maxRisk = RiskLevel.Medium;
415
+ continue;
416
+ }
417
+ }
418
+
315
419
  if (prog === 'git') {
316
420
  const subcommand = seg.args[0];
317
421
  if (subcommand && LOW_RISK_GIT_SUBCOMMANDS.has(subcommand)) {
@@ -360,17 +464,20 @@ export async function check(
360
464
  workingDir: string,
361
465
  policyContext?: PolicyContext,
362
466
  manifestOverride?: ManifestOverride,
467
+ signal?: AbortSignal,
363
468
  ): Promise<PermissionCheckResult> {
469
+ if (signal?.aborted) throw new Error('Cancelled');
470
+
364
471
  // For shell tools, parse once and share the result to avoid duplicate tree-sitter work.
365
472
  let shellParsed: ParsedCommand | undefined;
366
473
  if (toolName === 'bash' || toolName === 'host_bash') {
367
474
  const command = ((input.command as string) ?? '').trim();
368
475
  if (command) {
369
- shellParsed = await parse(command);
476
+ shellParsed = await cachedParse(command);
370
477
  }
371
478
  }
372
479
 
373
- const risk = await classifyRisk(toolName, input, workingDir, shellParsed, manifestOverride);
480
+ const risk = await classifyRisk(toolName, input, workingDir, shellParsed, manifestOverride, signal);
374
481
 
375
482
  // Build command string candidates for rule matching
376
483
  const commandCandidates = await buildCommandCandidates(toolName, input, workingDir, shellParsed);
@@ -500,7 +607,8 @@ function friendlyHostname(url: URL): string {
500
607
  return url.hostname.replace(/^www\./, '');
501
608
  }
502
609
 
503
- export async function generateAllowlistOptions(toolName: string, input: Record<string, unknown>): Promise<AllowlistOption[]> {
610
+ export async function generateAllowlistOptions(toolName: string, input: Record<string, unknown>, signal?: AbortSignal): Promise<AllowlistOption[]> {
611
+ if (signal?.aborted) throw new Error('Cancelled');
504
612
  if (toolName === 'bash' || toolName === 'host_bash') {
505
613
  const command = ((input.command as string) ?? '').trim();
506
614
  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;