@vellumai/assistant 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -0,0 +1,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,7 +11,32 @@ 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
+ import type { ManifestOverride } from '../tools/execution-target.js';
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);
15
40
 
16
41
  // Ensures the legacy mode deprecation warning fires at most once per process.
17
42
  let _legacyDeprecationWarned = false;
@@ -32,8 +57,8 @@ const LOW_RISK_PROGRAMS = new Set([
32
57
  'man', 'help', 'info',
33
58
  'env', 'printenv', 'set',
34
59
  'diff', 'sort', 'uniq', 'cut', 'tr', 'tee', 'xargs',
35
- 'jq', 'yq', 'sed', 'awk',
36
- 'curl', 'wget', 'http', 'dig', 'nslookup', 'ping',
60
+ 'jq', 'yq',
61
+ 'http', 'dig', 'nslookup', 'ping',
37
62
  'tree', 'du', 'df',
38
63
  ]);
39
64
 
@@ -55,6 +80,41 @@ const LOW_RISK_GIT_SUBCOMMANDS = new Set([
55
80
  'cat-file', 'reflog',
56
81
  ]);
57
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
+
58
118
  function isHighRiskRm(args: string[]): boolean {
59
119
  // rm with -r, -rf, -fr, or targeting root/home
60
120
  for (const arg of args) {
@@ -244,7 +304,36 @@ async function buildCommandCandidates(toolName: string, input: Record<string, un
244
304
  return [...new Set(candidates)];
245
305
  }
246
306
 
247
- export async function classifyRisk(toolName: string, input: Record<string, unknown>, workingDir?: string, preParsed?: ParsedCommand): 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> {
248
337
  if (toolName === 'file_read') return RiskLevel.Low;
249
338
  if (toolName === 'file_write' || toolName === 'file_edit') {
250
339
  const filePath = getStringField(input, 'path', 'file_path');
@@ -284,7 +373,7 @@ export async function classifyRisk(toolName: string, input: Record<string, unkno
284
373
  const command = (input.command as string) ?? '';
285
374
  if (!command.trim()) return RiskLevel.Low;
286
375
 
287
- const parsed = preParsed ?? await parse(command);
376
+ const parsed = preParsed ?? await cachedParse(command);
288
377
 
289
378
  // Dangerous patterns → High
290
379
  if (parsed.dangerousPatterns.length > 0) return RiskLevel.High;
@@ -306,11 +395,27 @@ export async function classifyRisk(toolName: string, input: Record<string, unkno
306
395
  continue;
307
396
  }
308
397
 
309
- if (prog === 'chmod' || prog === 'chown' || prog === 'chgrp') {
398
+ if (prog === 'chmod' || prog === 'chown' || prog === 'chgrp'
399
+ || prog === 'sed' || prog === 'awk') {
310
400
  maxRisk = RiskLevel.Medium;
311
401
  continue;
312
402
  }
313
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') {
407
+ maxRisk = RiskLevel.Medium;
408
+ continue;
409
+ }
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
+
314
419
  if (prog === 'git') {
315
420
  const subcommand = seg.args[0];
316
421
  if (subcommand && LOW_RISK_GIT_SUBCOMMANDS.has(subcommand)) {
@@ -342,6 +447,13 @@ export async function classifyRisk(toolName: string, input: Record<string, unkno
342
447
  const tool = getTool(toolName);
343
448
  if (tool) return tool.defaultRiskLevel;
344
449
 
450
+ // Use manifest metadata for unregistered skill tools so the Permission
451
+ // Simulator shows accurate risk levels instead of defaulting to Medium.
452
+ if (manifestOverride) {
453
+ const riskMap: Record<string, RiskLevel> = { low: RiskLevel.Low, medium: RiskLevel.Medium, high: RiskLevel.High };
454
+ return riskMap[manifestOverride.risk] ?? RiskLevel.Medium;
455
+ }
456
+
345
457
  // Unknown tool → Medium
346
458
  return RiskLevel.Medium;
347
459
  }
@@ -351,17 +463,21 @@ export async function check(
351
463
  input: Record<string, unknown>,
352
464
  workingDir: string,
353
465
  policyContext?: PolicyContext,
466
+ manifestOverride?: ManifestOverride,
467
+ signal?: AbortSignal,
354
468
  ): Promise<PermissionCheckResult> {
469
+ if (signal?.aborted) throw new Error('Cancelled');
470
+
355
471
  // For shell tools, parse once and share the result to avoid duplicate tree-sitter work.
356
472
  let shellParsed: ParsedCommand | undefined;
357
473
  if (toolName === 'bash' || toolName === 'host_bash') {
358
474
  const command = ((input.command as string) ?? '').trim();
359
475
  if (command) {
360
- shellParsed = await parse(command);
476
+ shellParsed = await cachedParse(command);
361
477
  }
362
478
  }
363
479
 
364
- const risk = await classifyRisk(toolName, input, workingDir, shellParsed);
480
+ const risk = await classifyRisk(toolName, input, workingDir, shellParsed, manifestOverride, signal);
365
481
 
366
482
  // Build command string candidates for rule matching
367
483
  const commandCandidates = await buildCommandCandidates(toolName, input, workingDir, shellParsed);
@@ -406,11 +522,16 @@ export async function check(
406
522
  // Third-party skill-origin tools default to prompting when no trust rule
407
523
  // matches, regardless of risk level. Bundled skill tools are first-party
408
524
  // and trusted, so they fall through to the normal risk-based policy.
525
+ // When manifestOverride is present, the tool comes from a skill manifest
526
+ // but isn't registered — treat it as a third-party skill tool.
409
527
  if (!matchedRule) {
410
528
  const tool = getTool(toolName);
411
529
  if (tool?.origin === 'skill' && !tool.ownerSkillBundled) {
412
530
  return { decision: 'prompt', reason: 'Skill tool: requires approval by default' };
413
531
  }
532
+ if (!tool && manifestOverride) {
533
+ return { decision: 'prompt', reason: 'Skill tool: requires approval by default' };
534
+ }
414
535
  }
415
536
 
416
537
  // In strict mode, every tool without an explicit matching rule must be
@@ -486,7 +607,8 @@ function friendlyHostname(url: URL): string {
486
607
  return url.hostname.replace(/^www\./, '');
487
608
  }
488
609
 
489
- 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');
490
612
  if (toolName === 'bash' || toolName === 'host_bash') {
491
613
  const command = ((input.command as string) ?? '').trim();
492
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;