@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
@@ -1,4 +1,5 @@
1
- import { readFileSync, existsSync, statSync } from 'node:fs';
1
+ import { readFileSync } from 'node:fs';
2
+ import { pathExists, safeStatSync } from '../util/fs.js';
2
3
  import { getTool, getAllTools } from './registry.js';
3
4
  import type { ToolContext, ToolExecutionResult, ToolLifecycleEvent } from './types.js';
4
5
  import { RiskLevel } from '../permissions/types.js';
@@ -20,6 +21,7 @@ import { getTaskRunRules } from '../tasks/ephemeral-permissions.js';
20
21
  import { safeTimeoutMs, executeWithTimeout } from './execution-timeout.js';
21
22
  import { buildPolicyContext } from './policy-context.js';
22
23
  import { resolveExecutionTarget } from './execution-target.js';
24
+ import { isToolBlocked } from '../security/parental-control-store.js';
23
25
 
24
26
  const log = getLogger('tool-executor');
25
27
 
@@ -52,6 +54,61 @@ export class ToolExecutor {
52
54
  startedAtMs: startTime,
53
55
  });
54
56
 
57
+ // Bail out immediately if the session was aborted before this tool started.
58
+ // Individual tool implementations check the signal themselves for mid-run
59
+ // cancellation, but a pre-execution check prevents expensive permission
60
+ // lookups and prompter interactions after the user has already cancelled.
61
+ if (context.signal?.aborted) {
62
+ const durationMs = Date.now() - startTime;
63
+ emitLifecycleEvent(context, {
64
+ type: 'error',
65
+ toolName: name,
66
+ executionTarget,
67
+ input,
68
+ workingDir: context.workingDir,
69
+ sessionId: context.sessionId,
70
+ conversationId: context.conversationId,
71
+ requestId: context.requestId,
72
+ riskLevel,
73
+ decision: 'error',
74
+ durationMs,
75
+ errorMessage: 'Cancelled',
76
+ isExpected: true,
77
+ errorCategory: 'tool_failure',
78
+ });
79
+ return { content: 'Cancelled', isError: true };
80
+ }
81
+
82
+ // Reject tools blocked by parental control settings before any permission check.
83
+ if (isToolBlocked(name)) {
84
+ log.warn(
85
+ {
86
+ toolName: name,
87
+ sessionId: context.sessionId,
88
+ conversationId: context.conversationId,
89
+ principal: context.principal,
90
+ reason: 'blocked_by_parental_controls',
91
+ },
92
+ 'Parental control blocked tool invocation',
93
+ );
94
+ const durationMs = Date.now() - startTime;
95
+ emitLifecycleEvent(context, {
96
+ type: 'permission_denied',
97
+ toolName: name,
98
+ executionTarget,
99
+ input,
100
+ workingDir: context.workingDir,
101
+ sessionId: context.sessionId,
102
+ conversationId: context.conversationId,
103
+ requestId: context.requestId,
104
+ riskLevel,
105
+ decision: 'deny',
106
+ reason: 'Blocked by parental control settings',
107
+ durationMs,
108
+ });
109
+ return { content: 'This tool is blocked by parental control settings.', isError: true };
110
+ }
111
+
55
112
  // Gate tools not active for the current turn
56
113
  if (context.allowedToolNames && !context.allowedToolNames.has(name)) {
57
114
  const msg = `Tool "${name}" is not currently active. Load the skill that provides this tool first.`;
@@ -129,14 +186,14 @@ export class ToolExecutor {
129
186
 
130
187
  try {
131
188
  // Check permissions
132
- const risk = await classifyRisk(name, input, context.workingDir);
189
+ const risk = await classifyRisk(name, input, context.workingDir, undefined, undefined, context.signal);
133
190
  riskLevel = risk;
134
191
 
135
192
  // Build principal context from tool metadata so policy rules can
136
193
  // distinguish skill-provided tools from core built-ins. Also includes
137
194
  // ephemeral rules when executing within a task run.
138
195
  const policyContext = buildPolicyContext(tool, context);
139
- const result = await check(name, input, context.workingDir, policyContext);
196
+ const result = await check(name, input, context.workingDir, policyContext, undefined, context.signal);
140
197
 
141
198
  // Private threads force prompting for side-effect tools even when a
142
199
  // trust/allow rule would auto-allow. Deny decisions are preserved —
@@ -198,7 +255,7 @@ export class ToolExecutor {
198
255
  }
199
256
 
200
257
  // Need user approval
201
- const allowlistOptions = await generateAllowlistOptions(name, input);
258
+ const allowlistOptions = await generateAllowlistOptions(name, input, context.signal);
202
259
  const scopeOptions = generateScopeOptions(context.workingDir, name);
203
260
 
204
261
  // Compute preview diff for file tools so the user sees what will change
@@ -256,6 +313,7 @@ export class ToolExecutor {
256
313
  context.conversationId,
257
314
  executionTarget,
258
315
  persistentDecisionsAllowed,
316
+ context.signal,
259
317
  );
260
318
 
261
319
  decision = response.decision;
@@ -575,6 +633,7 @@ export class ToolExecutor {
575
633
  context.conversationId,
576
634
  executionTarget,
577
635
  false, // no persistent decisions
636
+ context.signal,
578
637
  );
579
638
 
580
639
  if (response.decision === 'deny' || response.decision === 'always_deny') {
@@ -790,10 +849,10 @@ function computePreviewDiff(
790
849
  const pathCheck = sandboxPolicy(rawPath, workingDir, { mustExist: false });
791
850
  if (!pathCheck.ok) return undefined;
792
851
  const filePath = pathCheck.resolved;
793
- const isNewFile = !existsSync(filePath);
852
+ const isNewFile = !pathExists(filePath);
794
853
  if (!isNewFile) {
795
- const stat = statSync(filePath);
796
- if (stat.size > MAX_FILE_SIZE_BYTES) return undefined;
854
+ const stat = safeStatSync(filePath);
855
+ if (!stat || stat.size > MAX_FILE_SIZE_BYTES) return undefined;
797
856
  }
798
857
  const oldContent = isNewFile ? '' : readFileSync(filePath, 'utf-8');
799
858
  return { filePath, oldContent, newContent: content, isNewFile };
@@ -807,8 +866,8 @@ function computePreviewDiff(
807
866
  const pathCheck = sandboxPolicy(rawPath, workingDir);
808
867
  if (!pathCheck.ok) return undefined;
809
868
  const filePath = pathCheck.resolved;
810
- if (!existsSync(filePath)) return undefined;
811
- const stat = statSync(filePath);
869
+ const stat = safeStatSync(filePath);
870
+ if (!stat) return undefined;
812
871
  if (stat.size > MAX_FILE_SIZE_BYTES) return undefined;
813
872
  const content = readFileSync(filePath, 'utf-8');
814
873
  const replaceAll = input.replace_all === true;
@@ -139,7 +139,7 @@ class CliDiscoverTool implements Tool {
139
139
  if (checkAuth && AUTH_CHECK_COMMANDS[name]) {
140
140
  const [cmd, ...args] = AUTH_CHECK_COMMANDS[name];
141
141
  const authOutput = await runQuick(cmd, args);
142
- result.authenticated = authOutput !== null && authOutput.length > 0;
142
+ result.authenticated = authOutput != null && authOutput.length > 0;
143
143
  if (authOutput) {
144
144
  // Keep auth info brief — first line, max 200 chars
145
145
  result.authInfo = truncate(authOutput.split('\n')[0], 200, '');
@@ -132,7 +132,7 @@ export function forwardHttpRequest(
132
132
  if (policyCallback) {
133
133
  policyCallback(hostname, parsed.port ? Number(parsed.port) : null, path, 'http')
134
134
  .then((extraHeaders) => {
135
- if (extraHeaders === null) {
135
+ if (extraHeaders == null) {
136
136
  clientRes.writeHead(403, { 'Content-Type': 'text/plain' });
137
137
  clientRes.end('Forbidden');
138
138
  return;
@@ -200,7 +200,7 @@ async function processRequest(
200
200
  port,
201
201
  });
202
202
 
203
- if (rewriteResult === null) {
203
+ if (rewriteResult == null) {
204
204
  const body = 'Forbidden';
205
205
  tlsSocket.write(
206
206
  `HTTP/1.1 403 Forbidden\r\nContent-Length: ${body.length}\r\nContent-Type: text/plain\r\n\r\n${body}`,
@@ -111,7 +111,7 @@ export function createProxyServer(config: ProxyServerConfig = {}): Server {
111
111
 
112
112
  config.policyCallback(connectTarget.host, connectTarget.port === 443 ? null : connectTarget.port, '/', 'https')
113
113
  .then((extraHeaders) => {
114
- if (extraHeaders === null) {
114
+ if (extraHeaders == null) {
115
115
  clientSocket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
116
116
  clientSocket.destroy();
117
117
  return;
@@ -21,6 +21,7 @@ import type { CredentialInjectionTemplate } from '../../credentials/policy-types
21
21
  import { getSecureKey } from '../../../security/secure-keys.js';
22
22
  import { buildDecisionTrace, stripQueryString } from './logging.js';
23
23
  import { getLogger } from '../../../util/logger.js';
24
+ import { silentlyWithLog } from '../../../util/silently.js';
24
25
 
25
26
  const log = getLogger('proxy-session');
26
27
 
@@ -63,12 +64,12 @@ function cloneSession(s: ProxySession): ProxySession {
63
64
  }
64
65
 
65
66
  function resetIdleTimer(managed: ManagedSession): void {
66
- if (managed.idleTimer !== null) {
67
+ if (managed.idleTimer != null) {
67
68
  clearTimeout(managed.idleTimer);
68
69
  }
69
70
  managed.idleTimer = setTimeout(() => {
70
71
  if (managed.session.status === 'active') {
71
- stopSession(managed.session.id).catch(() => {});
72
+ silentlyWithLog(stopSession(managed.session.id), 'idle session cleanup');
72
73
  }
73
74
  }, managed.config.idleTimeoutMs);
74
75
  }
@@ -343,7 +344,7 @@ export async function stopSession(sessionId: ProxySessionId): Promise<void> {
343
344
  managed.session.status = 'stopping';
344
345
 
345
346
  const doStop = async () => {
346
- if (managed.idleTimer !== null) {
347
+ if (managed.idleTimer != null) {
347
348
  clearTimeout(managed.idleTimer);
348
349
  managed.idleTimer = null;
349
350
  }
@@ -375,7 +376,7 @@ export function getSessionEnv(
375
376
  ): ProxyEnvVars {
376
377
  const managed = sessions.get(sessionId);
377
378
  if (!managed) throw new Error(`Session not found: ${sessionId}`);
378
- if (managed.session.status !== 'active' || managed.session.port === null) {
379
+ if (managed.session.status !== 'active' || managed.session.port == null) {
379
380
  throw new Error(`Session ${sessionId} is not active`);
380
381
  }
381
382
 
@@ -529,6 +530,6 @@ export function getSessionsForConversation(conversationId: string): ProxySession
529
530
  */
530
531
  export async function stopAllSessions(): Promise<void> {
531
532
  const ids = [...sessions.keys()];
532
- await Promise.all(ids.map((id) => stopSession(id).catch(() => {})));
533
+ await Promise.all(ids.map((id) => stopSession(id).catch((err: unknown) => log.debug({ err, id }, 'session shutdown error'))));
533
534
  sessions.clear();
534
535
  }
@@ -63,6 +63,7 @@ type WebFetchRequestExecutor = (
63
63
  type ExecuteWebFetchOptions = {
64
64
  resolveHostAddresses?: ResolveHostAddresses;
65
65
  requestExecutor?: WebFetchRequestExecutor;
66
+ signal?: AbortSignal;
66
67
  };
67
68
 
68
69
  type NodeHttpResponseLike = {
@@ -451,6 +452,17 @@ export async function executeWebFetch(
451
452
  controller.abort();
452
453
  }, timeoutSeconds * 1000);
453
454
 
455
+ // Forward external cancellation signal to our controller
456
+ const externalSignal = options?.signal;
457
+ const onExternalAbort = () => controller.abort();
458
+ if (externalSignal) {
459
+ if (externalSignal.aborted) {
460
+ controller.abort();
461
+ } else {
462
+ externalSignal.addEventListener('abort', onExternalAbort, { once: true });
463
+ }
464
+ }
465
+
454
466
  try {
455
467
  log.debug({ url: safeRequestedUrl, timeoutSeconds, maxChars, startIndex, rawMode }, 'Fetching webpage');
456
468
 
@@ -651,6 +663,9 @@ export async function executeWebFetch(
651
663
  };
652
664
  } catch (err) {
653
665
  if (err instanceof Error && err.name === 'AbortError') {
666
+ if (externalSignal?.aborted) {
667
+ return { content: 'Error: web fetch was cancelled', isError: true };
668
+ }
654
669
  return { content: `Error: web fetch timed out after ${timeoutSeconds}s`, isError: true };
655
670
  }
656
671
 
@@ -659,6 +674,7 @@ export async function executeWebFetch(
659
674
  return { content: `Error: Web fetch failed: ${msg}`, isError: true };
660
675
  } finally {
661
676
  clearTimeout(timeoutHandle);
677
+ externalSignal?.removeEventListener('abort', onExternalAbort);
662
678
  }
663
679
  }
664
680
 
@@ -705,8 +721,8 @@ class WebFetchTool implements Tool {
705
721
  };
706
722
  }
707
723
 
708
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
709
- return executeWebFetch(input);
724
+ async execute(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
725
+ return executeWebFetch(input, { signal: context.signal });
710
726
  }
711
727
  }
712
728
 
@@ -115,6 +115,7 @@ async function executeBraveSearch(
115
115
  offset: number,
116
116
  freshness: string | undefined,
117
117
  apiKey: string,
118
+ signal?: AbortSignal,
118
119
  ): Promise<ToolExecutionResult> {
119
120
  const params = new URLSearchParams({
120
121
  q: query,
@@ -136,6 +137,7 @@ async function executeBraveSearch(
136
137
  'Accept-Encoding': 'gzip',
137
138
  'X-Subscription-Token': apiKey,
138
139
  },
140
+ signal,
139
141
  });
140
142
 
141
143
  if (response.ok) {
@@ -170,6 +172,7 @@ async function executeBraveSearch(
170
172
  async function executePerplexitySearch(
171
173
  query: string,
172
174
  apiKey: string,
175
+ signal?: AbortSignal,
173
176
  ): Promise<ToolExecutionResult> {
174
177
  for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
175
178
  const response = await fetch(PERPLEXITY_API_URL, {
@@ -184,6 +187,7 @@ async function executePerplexitySearch(
184
187
  { role: 'user', content: query },
185
188
  ],
186
189
  }),
190
+ signal,
187
191
  });
188
192
 
189
193
  if (response.ok) {
@@ -249,7 +253,7 @@ class WebSearchTool implements Tool {
249
253
  };
250
254
  }
251
255
 
252
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
256
+ async execute(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
253
257
  const query = input.query;
254
258
  if (!query || typeof query !== 'string') {
255
259
  return { content: 'Error: query is required and must be a string', isError: true };
@@ -268,7 +272,7 @@ class WebSearchTool implements Tool {
268
272
  apiKey = fallbackKey;
269
273
  } else {
270
274
  return {
271
- content: 'Error: No web search API key configured. Set PERPLEXITY_API_KEY or BRAVE_API_KEY environment variable, or configure a key in settings.',
275
+ content: 'Error: No web search API key configured. Set a PERPLEXITY_API_KEY or BRAVE_API_KEY environment variable, or configure it from the Settings page under API Keys.',
272
276
  isError: true,
273
277
  };
274
278
  }
@@ -281,10 +285,10 @@ class WebSearchTool implements Tool {
281
285
  const count = typeof input.count === 'number' ? Math.min(20, Math.max(1, Math.round(input.count))) : 10;
282
286
  const offset = typeof input.offset === 'number' ? Math.min(9, Math.max(0, Math.round(input.offset))) : 0;
283
287
  const freshness = typeof input.freshness === 'string' ? input.freshness : undefined;
284
- return await executeBraveSearch(query, count, offset, freshness, apiKey);
288
+ return await executeBraveSearch(query, count, offset, freshness, apiKey, context.signal);
285
289
  }
286
290
 
287
- return await executePerplexitySearch(query, apiKey);
291
+ return await executePerplexitySearch(query, apiKey, context.signal);
288
292
  } catch (err) {
289
293
  const msg = err instanceof Error ? err.message : String(err);
290
294
  log.error({ err }, 'Web search failed');
@@ -2,6 +2,7 @@ import { and, asc, eq, lte } from 'drizzle-orm';
2
2
  import { v4 as uuid } from 'uuid';
3
3
  import { getDb } from '../../memory/db.js';
4
4
  import { reminders } from '../../memory/schema.js';
5
+ import { createRowMapper, cast } from '../../util/row-mapper.js';
5
6
 
6
7
  export interface ReminderRow {
7
8
  id: string;
@@ -16,6 +17,19 @@ export interface ReminderRow {
16
17
  updatedAt: number;
17
18
  }
18
19
 
20
+ const parseRow = createRowMapper<typeof reminders.$inferSelect, ReminderRow>({
21
+ id: 'id',
22
+ label: 'label',
23
+ message: 'message',
24
+ fireAt: 'fireAt',
25
+ mode: { from: 'mode', transform: cast<ReminderRow['mode']>() },
26
+ status: { from: 'status', transform: cast<ReminderRow['status']>() },
27
+ firedAt: 'firedAt',
28
+ conversationId: 'conversationId',
29
+ createdAt: 'createdAt',
30
+ updatedAt: 'updatedAt',
31
+ });
32
+
19
33
  export function insertReminder(params: {
20
34
  label: string;
21
35
  message: string;
@@ -131,18 +145,3 @@ export function setReminderConversationId(id: string, conversationId: string): v
131
145
  .where(eq(reminders.id, id))
132
146
  .run();
133
147
  }
134
-
135
- function parseRow(row: typeof reminders.$inferSelect): ReminderRow {
136
- return {
137
- id: row.id,
138
- label: row.label,
139
- message: row.message,
140
- fireAt: row.fireAt,
141
- mode: row.mode as ReminderRow['mode'],
142
- status: row.status as ReminderRow['status'],
143
- firedAt: row.firedAt,
144
- conversationId: row.conversationId,
145
- createdAt: row.createdAt,
146
- updatedAt: row.updatedAt,
147
- };
148
- }
@@ -66,6 +66,7 @@ export async function executeScheduleCreate(
66
66
  return {
67
67
  content: [
68
68
  `Schedule created successfully.`,
69
+ ` ID: ${job.id}`,
69
70
  ` Name: ${job.name}`,
70
71
  ` Syntax: ${job.syntax}`,
71
72
  ` Schedule: ${scheduleDescription}${job.timezone ? ` (${job.timezone})` : ''}`,
@@ -27,6 +27,7 @@ export async function executeScheduleList(
27
27
  const runs = getScheduleRuns(jobId, 5);
28
28
  const lines = [
29
29
  `Schedule: ${job.name}`,
30
+ ` ID: ${job.id}`,
30
31
  ` Syntax: ${job.syntax}`,
31
32
  ` Expression: ${job.expression}`,
32
33
  ` Schedule: ${describeSchedule(job)}${job.timezone ? ` (${job.timezone})` : ''}`,
@@ -62,7 +63,7 @@ export async function executeScheduleList(
62
63
  for (const job of jobs) {
63
64
  const status = job.enabled ? 'enabled' : 'disabled';
64
65
  const next = job.enabled ? formatLocalDate(job.nextRunAt) : 'n/a';
65
- lines.push(` - [${status}] ${job.name} ([${job.syntax}] ${describeSchedule(job)}) — next: ${next}`);
66
+ lines.push(` - [${status}] ${job.name} (id: ${job.id}) ([${job.syntax}] ${describeSchedule(job)}) — next: ${next}`);
66
67
  }
67
68
 
68
69
  return { content: lines.join('\n'), isError: false };
@@ -1,6 +1,7 @@
1
- import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
1
+ import { readFileSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { dirname } from 'node:path';
3
3
 
4
+ import { pathExists, ensureDir } from '../../../util/fs.js';
4
5
  import type { PathResult, PathFailureReason } from './path-policy.js';
5
6
  import { checkFileSizeOnDisk, checkContentSize } from './size-guard.js';
6
7
  import { applyEdit } from './edit-engine.js';
@@ -55,7 +56,7 @@ export class FileSystemOps {
55
56
  }
56
57
  const filePath = pathCheck.resolved;
57
58
 
58
- if (!existsSync(filePath)) {
59
+ if (!pathExists(filePath)) {
59
60
  return { ok: false, error: Err.notFound(filePath) };
60
61
  }
61
62
 
@@ -108,13 +109,10 @@ export class FileSystemOps {
108
109
  }
109
110
 
110
111
  try {
111
- const dir = dirname(filePath);
112
- if (!existsSync(dir)) {
113
- mkdirSync(dir, { recursive: true });
114
- }
112
+ ensureDir(dirname(filePath));
115
113
 
116
114
  let oldContent = '';
117
- const isNewFile = !existsSync(filePath);
115
+ const isNewFile = !pathExists(filePath);
118
116
  if (!isNewFile) {
119
117
  try {
120
118
  oldContent = readFileSync(filePath, 'utf-8');
@@ -1,8 +1,9 @@
1
1
  import type { ToolExecutionResult, ToolContext, ExecutionTarget } from '../types.js';
2
2
  import type { SkillToolScript } from './script-contract.js';
3
- import { join, resolve } from 'node:path';
3
+ import { basename, join, resolve } from 'node:path';
4
4
  import { runSkillToolScriptSandbox } from './sandbox-runner.js';
5
5
  import { computeSkillVersionHash } from '../../skills/version-hash.js';
6
+ import { bundledToolRegistry } from '../../config/bundled-tool-registry.js';
6
7
 
7
8
  export interface RunSkillToolScriptOptions {
8
9
  /** Where to execute: 'host' runs in-process, 'sandbox' runs in an isolated subprocess. */
@@ -17,6 +18,10 @@ export interface RunSkillToolScriptOptions {
17
18
  * to `computeSkillVersionHash` from the version-hash module. Provided as an
18
19
  * option to support testing and custom resolution strategies. */
19
20
  skillDirHashResolver?: (skillDir: string) => string;
21
+ /** Whether this is a bundled (first-party) skill. When true and running inside
22
+ * a compiled binary, the runner uses the pre-imported bundled tool registry
23
+ * instead of a dynamic filesystem import. */
24
+ bundled?: boolean;
20
25
  }
21
26
 
22
27
  /**
@@ -65,20 +70,30 @@ export async function runSkillToolScript(
65
70
  }
66
71
  }
67
72
 
68
- let module: SkillToolScript;
69
- try {
70
- module = await import(scriptPath);
71
- } catch (err) {
72
- const message = err instanceof Error ? err.message : String(err);
73
- return { content: `Failed to load skill tool script "${executorPath}": ${message}`, isError: true };
73
+ // For bundled skills, use the pre-imported registry instead of dynamic import.
74
+ // In compiled binaries the scripts' relative imports (e.g. ../../../../tools/...)
75
+ // can't resolve because those modules are inside the virtual /$bunfs/ filesystem.
76
+ let module: SkillToolScript | undefined;
77
+ if (options?.bundled) {
78
+ const registryKey = `${basename(skillDir)}:${executorPath}`;
79
+ module = bundledToolRegistry.get(registryKey);
80
+ }
81
+
82
+ if (!module) {
83
+ try {
84
+ module = await import(scriptPath);
85
+ } catch (err) {
86
+ const message = err instanceof Error ? err.message : String(err);
87
+ return { content: `Failed to load skill tool script "${executorPath}": ${message}`, isError: true };
88
+ }
74
89
  }
75
90
 
76
- if (typeof module.run !== 'function') {
91
+ if (typeof module!.run !== 'function') {
77
92
  return { content: `Skill tool script "${executorPath}" does not export a "run" function`, isError: true };
78
93
  }
79
94
 
80
95
  try {
81
- return await module.run(input, context);
96
+ return await module!.run(input, context);
82
97
  } catch (err) {
83
98
  const message = err instanceof Error ? err.message : String(err);
84
99
  return { content: `Skill tool script "${executorPath}" threw an error: ${message}`, isError: true };
@@ -45,6 +45,7 @@ export function createSkillTool(
45
45
  return runSkillToolScript(skillDir, entry.executor, input, context, {
46
46
  target: entry.execution_target,
47
47
  expectedSkillVersionHash: versionHash,
48
+ bundled,
48
49
  });
49
50
  },
50
51
  };
@@ -126,7 +126,7 @@ export async function executeTaskListAdd(
126
126
  if (workItem.notes) {
127
127
  lines.push(` Notes: ${workItem.notes}`);
128
128
  }
129
- if (workItem.sortIndex !== null) {
129
+ if (workItem.sortIndex !== undefined) {
130
130
  lines.push(` Sort index: ${workItem.sortIndex}`);
131
131
  }
132
132
 
@@ -221,7 +221,7 @@ export async function executeTaskListAdd(
221
221
  if (workItem.notes) {
222
222
  lines.push(` Notes: ${workItem.notes}`);
223
223
  }
224
- if (workItem.sortIndex !== null) {
224
+ if (workItem.sortIndex !== undefined) {
225
225
  lines.push(` Sort index: ${workItem.sortIndex}`);
226
226
  }
227
227
 
@@ -8,6 +8,7 @@ import type { ToolDefinition } from '../../providers/types.js';
8
8
  import { registerTool } from '../registry.js';
9
9
  import { getConfig } from '../../config/loader.js';
10
10
  import { getLogger } from '../../util/logger.js';
11
+ import { parseJsonSafe } from '../../util/json.js';
11
12
  import { wrapCommand } from './sandbox.js';
12
13
  import { buildSanitizedEnv } from './safe-env.js';
13
14
 
@@ -126,9 +127,7 @@ export class EvaluateTypescriptTool implements Tool {
126
127
  if (Buffer.byteLength(mockInputJson) > MAX_MOCK_INPUT_BYTES) {
127
128
  return { content: `Error: mock_input_json exceeds maximum size of ${MAX_MOCK_INPUT_BYTES} bytes`, isError: true };
128
129
  }
129
- try {
130
- JSON.parse(mockInputJson);
131
- } catch {
130
+ if (parseJsonSafe(mockInputJson) === undefined && mockInputJson.trim() !== 'null') {
132
131
  return { content: 'Error: mock_input_json must be valid JSON', isError: true };
133
132
  }
134
133
 
@@ -170,7 +169,7 @@ export class EvaluateTypescriptTool implements Tool {
170
169
  timeoutSec: number,
171
170
  timeoutMs: number,
172
171
  maxOutputChars: number,
173
- _context: ToolContext,
172
+ context: ToolContext,
174
173
  ): Promise<EvalResult> {
175
174
  return new Promise<EvalResult>((resolve) => {
176
175
  const startTime = Date.now();
@@ -198,11 +197,24 @@ export class EvaluateTypescriptTool implements Tool {
198
197
  child.kill('SIGKILL');
199
198
  }, timeoutMs);
200
199
 
200
+ // Cooperative cancellation via AbortSignal
201
+ const onAbort = () => {
202
+ child.kill('SIGKILL');
203
+ };
204
+ if (context.signal) {
205
+ if (context.signal.aborted) {
206
+ child.kill('SIGKILL');
207
+ } else {
208
+ context.signal.addEventListener('abort', onAbort, { once: true });
209
+ }
210
+ }
211
+
201
212
  child.stdout.on('data', (data: Buffer) => stdoutChunks.push(data));
202
213
  child.stderr.on('data', (data: Buffer) => stderrChunks.push(data));
203
214
 
204
215
  child.on('close', (code) => {
205
216
  clearTimeout(timer);
217
+ context.signal?.removeEventListener('abort', onAbort);
206
218
  const durationMs = Date.now() - startTime;
207
219
 
208
220
  let stdout = Buffer.concat(stdoutChunks).toString();
@@ -222,14 +234,10 @@ export class EvaluateTypescriptTool implements Tool {
222
234
  const lineStart = stdout.lastIndexOf('\n', markerIdx) + 1;
223
235
  const lineEnd = stdout.indexOf('\n', markerIdx);
224
236
  const line = stdout.slice(lineStart, lineEnd === -1 ? undefined : lineEnd);
225
- try {
226
- const parsed = JSON.parse(line);
227
- if (parsed && typeof parsed === 'object' && '__eval_result' in parsed) {
228
- result = parsed.__eval_result;
229
- break;
230
- }
231
- } catch {
232
- // malformed line — continue scanning earlier occurrences
237
+ const parsed = parseJsonSafe<Record<string, unknown>>(line);
238
+ if (parsed && typeof parsed === 'object' && '__eval_result' in parsed) {
239
+ result = parsed.__eval_result;
240
+ break;
233
241
  }
234
242
  searchFrom = lineStart;
235
243
  }
@@ -256,6 +264,7 @@ export class EvaluateTypescriptTool implements Tool {
256
264
 
257
265
  child.on('error', (err) => {
258
266
  clearTimeout(timer);
267
+ context.signal?.removeEventListener('abort', onAbort);
259
268
  const durationMs = Date.now() - startTime;
260
269
  resolve({
261
270
  ok: false,