@vellumai/assistant 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (487) hide show
  1. package/README.md +51 -0
  2. package/eslint.config.mjs +31 -0
  3. package/package.json +1 -1
  4. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  5. package/scripts/ipc/generate-swift.ts +18 -2
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +338 -1
  7. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  8. package/src/__tests__/browser-manager.test.ts +1 -0
  9. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  10. package/src/__tests__/call-orchestrator.test.ts +752 -271
  11. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  12. package/src/__tests__/call-recovery.test.ts +3 -0
  13. package/src/__tests__/call-routes-http.test.ts +5 -0
  14. package/src/__tests__/call-store.test.ts +3 -0
  15. package/src/__tests__/channel-approval-routes.test.ts +1260 -85
  16. package/src/__tests__/channel-approval.test.ts +37 -0
  17. package/src/__tests__/channel-approvals.test.ts +4 -65
  18. package/src/__tests__/channel-guardian.test.ts +556 -0
  19. package/src/__tests__/channel-readiness-service.test.ts +74 -7
  20. package/src/__tests__/checker.test.ts +14 -7
  21. package/src/__tests__/clarification-resolver.test.ts +44 -24
  22. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  23. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  24. package/src/__tests__/config-schema.test.ts +12 -7
  25. package/src/__tests__/context-window-manager.test.ts +30 -2
  26. package/src/__tests__/contradiction-checker.test.ts +20 -5
  27. package/src/__tests__/credential-security-invariants.test.ts +6 -2
  28. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  29. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  30. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  31. package/src/__tests__/guardian-action-store.test.ts +123 -0
  32. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  33. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  34. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  35. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  36. package/src/__tests__/handlers-twilio-config.test.ts +126 -0
  37. package/src/__tests__/intent-routing.test.ts +2 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +228 -1
  39. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  40. package/src/__tests__/model-intents.test.ts +96 -0
  41. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  42. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  43. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  44. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  45. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  46. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  47. package/src/__tests__/qdrant-manager.test.ts +27 -20
  48. package/src/__tests__/relay-server.test.ts +779 -40
  49. package/src/__tests__/run-orchestrator-assistant-events.test.ts +2 -0
  50. package/src/__tests__/run-orchestrator.test.ts +20 -4
  51. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  52. package/src/__tests__/runtime-runs.test.ts +16 -0
  53. package/src/__tests__/schedule-store.test.ts +18 -4
  54. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  55. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  56. package/src/__tests__/session-agent-loop.test.ts +857 -0
  57. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  58. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  59. package/src/__tests__/session-profile-injection.test.ts +6 -0
  60. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  61. package/src/__tests__/session-queue.test.ts +6 -0
  62. package/src/__tests__/session-runtime-assembly.test.ts +237 -13
  63. package/src/__tests__/session-slash-known.test.ts +6 -0
  64. package/src/__tests__/session-slash-queue.test.ts +6 -0
  65. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  66. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  67. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  68. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  69. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  70. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  71. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  72. package/src/__tests__/skills.test.ts +2 -0
  73. package/src/__tests__/sms-messaging-provider.test.ts +2 -1
  74. package/src/__tests__/starter-task-flow.test.ts +2 -0
  75. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  76. package/src/__tests__/system-prompt.test.ts +2 -0
  77. package/src/__tests__/task-management-tools.test.ts +2 -2
  78. package/src/__tests__/task-runner.test.ts +14 -4
  79. package/src/__tests__/terminal-tools.test.ts +25 -19
  80. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  81. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  82. package/src/__tests__/tool-executor.test.ts +23 -24
  83. package/src/__tests__/trust-store.test.ts +3 -3
  84. package/src/__tests__/twilio-rest.test.ts +29 -0
  85. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  86. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  87. package/src/__tests__/twilio-routes.test.ts +141 -21
  88. package/src/__tests__/user-reference.test.ts +2 -0
  89. package/src/__tests__/voice-quality.test.ts +222 -0
  90. package/src/__tests__/web-search.test.ts +45 -29
  91. package/src/agent/loop.ts +1 -1
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  93. package/src/amazon/client.ts +1418 -0
  94. package/src/amazon/request-extractor.ts +135 -0
  95. package/src/amazon/session.ts +109 -0
  96. package/src/autonomy/autonomy-store.ts +5 -5
  97. package/src/browser-extension-relay/client.ts +124 -0
  98. package/src/browser-extension-relay/protocol.ts +63 -0
  99. package/src/browser-extension-relay/server.ts +177 -0
  100. package/src/bundler/app-bundler.ts +3 -3
  101. package/src/bundler/bundle-signer.ts +1 -1
  102. package/src/bundler/signature-verifier.ts +1 -1
  103. package/src/calls/call-conversation-messages.ts +33 -0
  104. package/src/calls/call-domain.ts +106 -5
  105. package/src/calls/call-orchestrator.ts +252 -54
  106. package/src/calls/call-pointer-messages.ts +53 -0
  107. package/src/calls/call-recovery.ts +3 -8
  108. package/src/calls/call-store.ts +69 -87
  109. package/src/calls/elevenlabs-config.ts +3 -2
  110. package/src/calls/guardian-action-sweep.ts +105 -0
  111. package/src/calls/guardian-dispatch.ts +203 -0
  112. package/src/calls/guardian-question-copy.ts +133 -0
  113. package/src/calls/relay-server.ts +466 -8
  114. package/src/calls/speaker-identification.ts +1 -1
  115. package/src/calls/twilio-config.ts +7 -5
  116. package/src/calls/twilio-provider.ts +6 -4
  117. package/src/calls/twilio-rest.ts +40 -15
  118. package/src/calls/twilio-routes.ts +60 -45
  119. package/src/calls/types.ts +3 -1
  120. package/src/channels/types.ts +25 -0
  121. package/src/cli/amazon.ts +815 -0
  122. package/src/cli/config-commands.ts +2 -2
  123. package/src/cli/core-commands.ts +4 -3
  124. package/src/cli/influencer.ts +244 -0
  125. package/src/cli/map.ts +89 -6
  126. package/src/cli.ts +1 -1
  127. package/src/config/agent-schema.ts +171 -0
  128. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  129. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  130. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  131. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  132. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  133. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  134. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  135. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  136. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  137. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  138. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  139. package/src/config/bundled-skills/media-processing/SKILL.md +72 -95
  140. package/src/config/bundled-skills/media-processing/TOOLS.json +57 -147
  141. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  142. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  143. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  144. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  145. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  146. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  147. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  148. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +7 -9
  149. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  150. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +88 -253
  151. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +22 -153
  152. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  153. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +28 -51
  154. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +35 -270
  155. package/src/config/bundled-skills/messaging/SKILL.md +12 -2
  156. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  157. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  158. package/src/config/bundled-skills/phone-calls/SKILL.md +86 -21
  159. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  160. package/src/config/bundled-tool-registry.ts +310 -0
  161. package/src/config/calls-schema.ts +181 -0
  162. package/src/config/core-schema.ts +309 -0
  163. package/src/config/defaults.ts +27 -3
  164. package/src/config/env-registry.ts +169 -0
  165. package/src/config/env.ts +175 -0
  166. package/src/config/loader.ts +6 -6
  167. package/src/config/memory-schema.ts +528 -0
  168. package/src/config/sandbox-schema.ts +55 -0
  169. package/src/config/schema.ts +157 -1138
  170. package/src/config/skill-state.ts +1 -1
  171. package/src/config/skills-schema.ts +32 -0
  172. package/src/config/skills.ts +35 -24
  173. package/src/config/system-prompt.ts +107 -56
  174. package/src/config/templates/SOUL.md +1 -1
  175. package/src/config/types.ts +1 -0
  176. package/src/config/user-reference.ts +4 -9
  177. package/src/config/vellum-skills/catalog.json +0 -7
  178. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  179. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +1 -0
  180. package/src/config/vellum-skills/sms-setup/SKILL.md +112 -14
  181. package/src/context/window-manager.ts +27 -7
  182. package/src/daemon/approval-generators.ts +186 -0
  183. package/src/daemon/approved-devices-store.ts +140 -0
  184. package/src/daemon/assistant-attachments.ts +1 -1
  185. package/src/daemon/classifier.ts +35 -32
  186. package/src/daemon/config-watcher.ts +1 -1
  187. package/src/daemon/daemon-control.ts +254 -0
  188. package/src/daemon/handlers/apps.ts +2 -3
  189. package/src/daemon/handlers/config-channels.ts +158 -0
  190. package/src/daemon/handlers/config-inbox.ts +540 -0
  191. package/src/daemon/handlers/config-ingress.ts +231 -0
  192. package/src/daemon/handlers/config-integrations.ts +258 -0
  193. package/src/daemon/handlers/config-model.ts +143 -0
  194. package/src/daemon/handlers/config-parental.ts +163 -0
  195. package/src/daemon/handlers/config-scheduling.ts +172 -0
  196. package/src/daemon/handlers/config-slack.ts +92 -0
  197. package/src/daemon/handlers/config-telegram.ts +301 -0
  198. package/src/daemon/handlers/config-tools.ts +177 -0
  199. package/src/daemon/handlers/config-trust.ts +104 -0
  200. package/src/daemon/handlers/config-twilio.ts +1080 -0
  201. package/src/daemon/handlers/config.ts +53 -2463
  202. package/src/daemon/handlers/diagnostics.ts +1 -1
  203. package/src/daemon/handlers/dictation.ts +4 -6
  204. package/src/daemon/handlers/documents.ts +18 -32
  205. package/src/daemon/handlers/index.ts +9 -0
  206. package/src/daemon/handlers/misc.ts +3 -5
  207. package/src/daemon/handlers/pairing.ts +98 -0
  208. package/src/daemon/handlers/sessions.ts +74 -5
  209. package/src/daemon/handlers/shared.ts +3 -1
  210. package/src/daemon/handlers/skills.ts +1 -1
  211. package/src/daemon/handlers/twitter-auth.ts +2 -0
  212. package/src/daemon/handlers/work-items.ts +2 -2
  213. package/src/daemon/handlers/workspace-files.ts +4 -3
  214. package/src/daemon/install-cli-launchers.ts +113 -0
  215. package/src/daemon/ipc-contract/apps.ts +356 -0
  216. package/src/daemon/ipc-contract/browser.ts +74 -0
  217. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  218. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  219. package/src/daemon/ipc-contract/documents.ts +74 -0
  220. package/src/daemon/ipc-contract/inbox.ts +209 -0
  221. package/src/daemon/ipc-contract/integrations.ts +284 -0
  222. package/src/daemon/ipc-contract/memory.ts +48 -0
  223. package/src/daemon/ipc-contract/messages.ts +211 -0
  224. package/src/daemon/ipc-contract/pairing.ts +45 -0
  225. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  226. package/src/daemon/ipc-contract/schedules.ts +97 -0
  227. package/src/daemon/ipc-contract/sessions.ts +321 -0
  228. package/src/daemon/ipc-contract/shared.ts +42 -0
  229. package/src/daemon/ipc-contract/skills.ts +120 -0
  230. package/src/daemon/ipc-contract/subagents.ts +58 -0
  231. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  232. package/src/daemon/ipc-contract/trust.ts +60 -0
  233. package/src/daemon/ipc-contract/work-items.ts +225 -0
  234. package/src/daemon/ipc-contract/workspace.ts +113 -0
  235. package/src/daemon/ipc-contract-inventory.json +62 -0
  236. package/src/daemon/ipc-contract-inventory.ts +55 -29
  237. package/src/daemon/ipc-contract.ts +227 -2527
  238. package/src/daemon/ipc-protocol.ts +1 -1
  239. package/src/daemon/ipc-validate.ts +7 -0
  240. package/src/daemon/lifecycle.ts +97 -379
  241. package/src/daemon/pairing-store.ts +177 -0
  242. package/src/daemon/providers-setup.ts +43 -0
  243. package/src/daemon/ride-shotgun-handler.ts +67 -2
  244. package/src/daemon/server.ts +60 -44
  245. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  246. package/src/daemon/session-agent-loop.ts +113 -275
  247. package/src/daemon/session-dynamic-profile.ts +1 -1
  248. package/src/daemon/session-history.ts +1 -1
  249. package/src/daemon/session-media-retry.ts +1 -1
  250. package/src/daemon/session-messaging.ts +37 -2
  251. package/src/daemon/session-notifiers.ts +5 -25
  252. package/src/daemon/session-process.ts +99 -59
  253. package/src/daemon/session-queue-manager.ts +98 -4
  254. package/src/daemon/session-runtime-assembly.ts +149 -15
  255. package/src/daemon/session-surfaces.ts +26 -4
  256. package/src/daemon/session-tool-setup.ts +28 -30
  257. package/src/daemon/session-workspace.ts +1 -1
  258. package/src/daemon/session.ts +24 -1
  259. package/src/daemon/shutdown-handlers.ts +122 -0
  260. package/src/daemon/trace-emitter.ts +1 -1
  261. package/src/daemon/watch-handler.ts +36 -33
  262. package/src/doordash/cart-queries.ts +787 -0
  263. package/src/doordash/client.ts +144 -127
  264. package/src/doordash/order-queries.ts +85 -0
  265. package/src/doordash/queries.ts +10 -1308
  266. package/src/doordash/search-queries.ts +203 -0
  267. package/src/doordash/session.ts +3 -2
  268. package/src/doordash/store-queries.ts +246 -0
  269. package/src/doordash/types.ts +367 -0
  270. package/src/email/providers/agentmail.ts +2 -1
  271. package/src/email/providers/index.ts +3 -2
  272. package/src/email/service.ts +3 -2
  273. package/src/errors.ts +43 -0
  274. package/src/home-base/prebuilt/seed.ts +1 -1
  275. package/src/hooks/cli.ts +6 -5
  276. package/src/hooks/config.ts +6 -8
  277. package/src/hooks/discovery.ts +6 -5
  278. package/src/hooks/manager.ts +4 -3
  279. package/src/hooks/runner.ts +2 -2
  280. package/src/hooks/templates.ts +5 -5
  281. package/src/inbound/public-ingress-urls.ts +3 -1
  282. package/src/index.ts +4 -2
  283. package/src/influencer/client.ts +1104 -0
  284. package/src/instrument.ts +4 -3
  285. package/src/logfire.ts +4 -3
  286. package/src/memory/admin.ts +25 -35
  287. package/src/memory/attachments-store.ts +4 -7
  288. package/src/memory/channel-delivery-store.ts +30 -1
  289. package/src/memory/channel-guardian-store.ts +200 -1
  290. package/src/memory/clarification-resolver.ts +37 -33
  291. package/src/memory/conflict-store.ts +67 -61
  292. package/src/memory/contradiction-checker.ts +141 -117
  293. package/src/memory/conversation-store.ts +335 -51
  294. package/src/memory/db-connection.ts +27 -4
  295. package/src/memory/db-init.ts +121 -4
  296. package/src/memory/db.ts +14 -1
  297. package/src/memory/embedding-backend.ts +27 -5
  298. package/src/memory/embedding-ollama.ts +2 -1
  299. package/src/memory/entity-extractor.ts +38 -35
  300. package/src/memory/guardian-action-store.ts +430 -0
  301. package/src/memory/inbox-escalation-projection.ts +59 -0
  302. package/src/memory/inbox-thread-store.ts +218 -0
  303. package/src/memory/ingress-invite-store.ts +338 -0
  304. package/src/memory/ingress-member-store.ts +350 -0
  305. package/src/memory/items-extractor.ts +91 -97
  306. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  307. package/src/memory/job-handlers/media-processing.ts +11 -42
  308. package/src/memory/job-handlers/summarization.ts +32 -26
  309. package/src/memory/job-utils.ts +3 -10
  310. package/src/memory/jobs-store.ts +6 -9
  311. package/src/memory/jobs-worker.ts +51 -36
  312. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  313. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  314. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  315. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  316. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  317. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  318. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  319. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  320. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  321. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  322. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  323. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  324. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  325. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  326. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  327. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  328. package/src/memory/migrations/017-memory-items-indexes.ts +12 -0
  329. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  330. package/src/memory/migrations/index.ts +24 -0
  331. package/src/memory/migrations/registry.ts +79 -0
  332. package/src/memory/migrations/validate-migration-state.ts +69 -0
  333. package/src/memory/qdrant-manager.ts +49 -8
  334. package/src/memory/query-builder.ts +1 -1
  335. package/src/memory/raw-query.ts +119 -0
  336. package/src/memory/recall-cache.ts +4 -1
  337. package/src/memory/retriever.ts +163 -47
  338. package/src/memory/schema-migration.ts +25 -984
  339. package/src/memory/schema.ts +130 -7
  340. package/src/memory/search/entity.ts +10 -19
  341. package/src/memory/search/lexical.ts +81 -52
  342. package/src/memory/search/ranking.ts +21 -22
  343. package/src/memory/search/semantic.ts +157 -19
  344. package/src/memory/shared-app-links-store.ts +4 -5
  345. package/src/memory/validation.ts +19 -0
  346. package/src/messaging/draft-store.ts +5 -6
  347. package/src/messaging/providers/sms/adapter.ts +3 -6
  348. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  349. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  350. package/src/messaging/providers/whatsapp/client.ts +67 -0
  351. package/src/messaging/style-analyzer.ts +5 -4
  352. package/src/messaging/thread-summarizer.ts +61 -69
  353. package/src/messaging/triage-engine.ts +62 -71
  354. package/src/migrations/config-merge.ts +53 -0
  355. package/src/migrations/data-layout.ts +68 -0
  356. package/src/migrations/data-merge.ts +33 -0
  357. package/src/migrations/hooks-merge.ts +90 -0
  358. package/src/migrations/index.ts +6 -0
  359. package/src/migrations/log.ts +23 -0
  360. package/src/migrations/skills-merge.ts +33 -0
  361. package/src/migrations/workspace-layout.ts +79 -0
  362. package/src/permissions/checker.ts +126 -11
  363. package/src/permissions/prompter.ts +14 -0
  364. package/src/permissions/shell-identity.ts +31 -1
  365. package/src/permissions/trust-store.ts +21 -1
  366. package/src/providers/anthropic/client.ts +4 -4
  367. package/src/providers/failover.ts +2 -2
  368. package/src/providers/model-intents.ts +70 -0
  369. package/src/providers/ollama/client.ts +2 -1
  370. package/src/providers/provider-send-message.ts +176 -0
  371. package/src/providers/registry.ts +71 -30
  372. package/src/providers/retry.ts +35 -1
  373. package/src/providers/types.ts +12 -1
  374. package/src/runtime/approval-conversation-turn.ts +97 -0
  375. package/src/runtime/approval-message-composer.ts +115 -5
  376. package/src/runtime/assistant-event-hub.ts +3 -1
  377. package/src/runtime/channel-approval-parser.ts +36 -2
  378. package/src/runtime/channel-approvals.ts +0 -21
  379. package/src/runtime/channel-guardian-service.ts +48 -7
  380. package/src/runtime/channel-readiness-service.ts +160 -34
  381. package/src/runtime/channel-readiness-types.ts +10 -4
  382. package/src/runtime/channel-retry-sweep.ts +184 -0
  383. package/src/runtime/guardian-context-resolver.ts +108 -0
  384. package/src/runtime/http-server.ts +289 -745
  385. package/src/runtime/http-types.ts +56 -3
  386. package/src/runtime/middleware/auth.ts +116 -0
  387. package/src/runtime/middleware/error-handler.ts +33 -0
  388. package/src/runtime/middleware/twilio-validation.ts +127 -0
  389. package/src/runtime/routes/app-routes.ts +1 -1
  390. package/src/runtime/routes/call-routes.ts +49 -6
  391. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  392. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  393. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  394. package/src/runtime/routes/channel-route-shared.ts +144 -0
  395. package/src/runtime/routes/channel-routes.ts +32 -1634
  396. package/src/runtime/routes/conversation-routes.ts +50 -7
  397. package/src/runtime/routes/events-routes.ts +2 -2
  398. package/src/runtime/routes/identity-routes.ts +126 -0
  399. package/src/runtime/routes/pairing-routes.ts +144 -0
  400. package/src/runtime/routes/run-routes.ts +15 -1
  401. package/src/runtime/run-orchestrator.ts +52 -34
  402. package/src/schedule/schedule-store.ts +36 -32
  403. package/src/schedule/scheduler.ts +3 -3
  404. package/src/security/encrypted-store.ts +5 -7
  405. package/src/security/oauth2.ts +45 -15
  406. package/src/security/parental-control-store.ts +183 -0
  407. package/src/security/secret-allowlist.ts +4 -3
  408. package/src/security/secret-scanner.ts +5 -5
  409. package/src/security/secure-keys.ts +1 -1
  410. package/src/security/token-manager.ts +3 -2
  411. package/src/services/vercel-deploy.ts +6 -2
  412. package/src/skills/tool-manifest.ts +3 -3
  413. package/src/skills/vellum-catalog-remote.ts +75 -16
  414. package/src/slack/slack-webhook.ts +2 -1
  415. package/src/swarm/orchestrator.ts +92 -1
  416. package/src/swarm/router-planner.ts +6 -9
  417. package/src/swarm/worker-prompts.ts +9 -12
  418. package/src/tasks/task-compiler.ts +19 -28
  419. package/src/tasks/task-runner.ts +1 -1
  420. package/src/tools/assets/search.ts +15 -14
  421. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  422. package/src/tools/browser/auto-navigate.ts +1 -0
  423. package/src/tools/browser/browser-execution.ts +13 -1
  424. package/src/tools/browser/browser-manager.ts +119 -4
  425. package/src/tools/browser/network-recorder.ts +5 -0
  426. package/src/tools/credentials/broker.ts +11 -2
  427. package/src/tools/credentials/metadata-store.ts +18 -14
  428. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  429. package/src/tools/credentials/vault.ts +49 -23
  430. package/src/tools/executor.ts +80 -18
  431. package/src/tools/host-terminal/cli-discover.ts +1 -1
  432. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  433. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  434. package/src/tools/network/script-proxy/server.ts +1 -1
  435. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  436. package/src/tools/network/web-fetch.ts +18 -2
  437. package/src/tools/network/web-search.ts +7 -3
  438. package/src/tools/reminder/reminder-store.ts +14 -15
  439. package/src/tools/schedule/create.ts +1 -0
  440. package/src/tools/schedule/list.ts +2 -1
  441. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  442. package/src/tools/skills/skill-script-runner.ts +24 -9
  443. package/src/tools/skills/skill-tool-factory.ts +1 -0
  444. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  445. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  446. package/src/tools/terminal/parser.ts +50 -0
  447. package/src/tools/watcher/delete.ts +6 -0
  448. package/src/tools/weather/service.ts +1 -1
  449. package/src/twitter/client.ts +190 -24
  450. package/src/twitter/session.ts +4 -3
  451. package/src/util/clipboard.ts +1 -1
  452. package/src/util/errors.ts +65 -8
  453. package/src/util/fs.ts +40 -0
  454. package/src/util/json.ts +10 -0
  455. package/src/util/log-redact.ts +189 -0
  456. package/src/util/logger.ts +25 -18
  457. package/src/util/object.ts +3 -0
  458. package/src/util/platform.ts +72 -365
  459. package/src/util/pricing.ts +1 -1
  460. package/src/util/promise-guard.ts +1 -1
  461. package/src/util/retry.ts +19 -0
  462. package/src/util/row-mapper.ts +79 -0
  463. package/src/util/silently.ts +21 -0
  464. package/src/watcher/engine.ts +5 -1
  465. package/src/watcher/provider-types.ts +20 -0
  466. package/src/watcher/providers/github.ts +156 -0
  467. package/src/watcher/providers/gmail.ts +1 -0
  468. package/src/watcher/providers/google-calendar.ts +1 -0
  469. package/src/watcher/providers/linear.ts +460 -0
  470. package/src/watcher/providers/slack.ts +1 -0
  471. package/src/work-items/work-item-runner.ts +1 -1
  472. package/src/workspace/git-service.ts +1 -1
  473. package/src/workspace/provider-commit-message-generator.ts +51 -22
  474. package/src/__tests__/call-bridge.test.ts +0 -517
  475. package/src/__tests__/session-process-bridge.test.ts +0 -244
  476. package/src/calls/call-bridge.ts +0 -168
  477. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +0 -137
  478. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
  479. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
  480. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
  481. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
  482. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
  483. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
  484. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
  485. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
  486. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
  487. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -9,7 +9,7 @@ const VALID_EXECUTION_TARGETS = new Set(['host', 'sandbox']);
9
9
  * Throws descriptive errors for any validation failure.
10
10
  */
11
11
  export function parseToolManifest(raw: unknown): SkillToolManifest {
12
- if (raw === null || raw === undefined || typeof raw !== 'object' || Array.isArray(raw)) {
12
+ if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) {
13
13
  throw new Error('TOOLS.json must be a JSON object');
14
14
  }
15
15
 
@@ -54,7 +54,7 @@ export function parseToolManifest(raw: unknown): SkillToolManifest {
54
54
  }
55
55
 
56
56
  function parseToolEntry(raw: unknown, prefix: string): SkillToolEntry {
57
- if (raw === null || raw === undefined || typeof raw !== 'object' || Array.isArray(raw)) {
57
+ if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) {
58
58
  throw new Error(`${prefix}: each tool entry must be a JSON object`);
59
59
  }
60
60
 
@@ -97,7 +97,7 @@ function parseToolEntry(raw: unknown, prefix: string): SkillToolEntry {
97
97
  const risk = entry.risk as SkillToolEntry['risk'];
98
98
 
99
99
  // input_schema
100
- if (!('input_schema' in entry) || entry.input_schema === null || typeof entry.input_schema !== 'object' || Array.isArray(entry.input_schema)) {
100
+ if (!('input_schema' in entry) || entry.input_schema == null || typeof entry.input_schema !== 'object' || Array.isArray(entry.input_schema)) {
101
101
  throw new Error(`${prefix}: missing or non-object "input_schema"`);
102
102
  }
103
103
  const input_schema = entry.input_schema as Record<string, unknown>;
@@ -1,13 +1,14 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import { gunzipSync } from 'node:zlib';
3
4
 
4
5
  import type { CatalogEntry } from '../tools/skills/vellum-catalog.js';
5
6
  import { getLogger } from '../util/logger.js';
7
+ import { readPlatformToken } from '../util/platform.js';
6
8
 
7
9
  const log = getLogger('vellum-catalog-remote');
8
10
 
9
- const GITHUB_RAW_BASE =
10
- 'https://raw.githubusercontent.com/vellum-ai/vellum-assistant/main/assistant/src/config/vellum-skills';
11
+ const PLATFORM_URL = process.env.VELLUM_ASSISTANT_PLATFORM_URL ?? 'https://assistant.vellum.ai';
11
12
 
12
13
  const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
13
14
 
@@ -43,7 +44,20 @@ function getBundledSkillContent(skillId: string): string | null {
43
44
  }
44
45
  }
45
46
 
46
- /** Fetch catalog entries (cached, async). Falls back to bundled copy. */
47
+ /** Build request headers, including platform token when available. */
48
+ function buildPlatformHeaders(): Record<string, string> {
49
+ const headers: Record<string, string> = {};
50
+ const token = readPlatformToken();
51
+ if (token) {
52
+ headers['X-Session-Token'] = token;
53
+ }
54
+ return headers;
55
+ }
56
+
57
+ /**
58
+ * Fetch catalog entries from the platform API. Falls back to bundled copy.
59
+ * Reads the platform token from ~/.vellum/platform-token automatically.
60
+ */
47
61
  export async function fetchCatalogEntries(): Promise<CatalogEntry[]> {
48
62
  const now = Date.now();
49
63
  if (cachedEntries && now - cacheTimestamp < CACHE_TTL_MS) {
@@ -51,8 +65,9 @@ export async function fetchCatalogEntries(): Promise<CatalogEntry[]> {
51
65
  }
52
66
 
53
67
  try {
54
- const url = `${GITHUB_RAW_BASE}/catalog.json`;
68
+ const url = `${PLATFORM_URL}/v1/skills/`;
55
69
  const response = await fetch(url, {
70
+ headers: buildPlatformHeaders(),
56
71
  signal: AbortSignal.timeout(5000),
57
72
  });
58
73
 
@@ -63,14 +78,14 @@ export async function fetchCatalogEntries(): Promise<CatalogEntry[]> {
63
78
  const manifest: CatalogManifest = await response.json();
64
79
  const skills = manifest.skills;
65
80
  if (!Array.isArray(skills) || skills.length === 0) {
66
- throw new Error('Remote catalog has invalid or empty skills array');
81
+ throw new Error('Platform catalog has invalid or empty skills array');
67
82
  }
68
83
  cachedEntries = skills;
69
84
  cacheTimestamp = now;
70
- log.info({ count: cachedEntries.length }, 'Fetched remote vellum-skills catalog');
85
+ log.info({ count: cachedEntries.length }, 'Fetched vellum-skills catalog from platform API');
71
86
  return cachedEntries;
72
87
  } catch (err) {
73
- log.warn({ err }, 'Failed to fetch remote catalog, falling back to bundled copy');
88
+ log.warn({ err }, 'Failed to fetch catalog from platform API, falling back to bundled copy');
74
89
  const bundled = loadBundledCatalog();
75
90
  // Cache the bundled result too so we don't re-fetch on every call during outage
76
91
  cachedEntries = bundled;
@@ -79,28 +94,72 @@ export async function fetchCatalogEntries(): Promise<CatalogEntry[]> {
79
94
  }
80
95
  }
81
96
 
82
- /** Fetch a skill's SKILL.md content from GitHub. Falls back to bundled copy. */
97
+ /**
98
+ * Extract SKILL.md content from a tar archive (uncompressed).
99
+ * Tar format: 512-byte header blocks followed by file data blocks.
100
+ */
101
+ function extractSkillMdFromTar(tarBuffer: Buffer): string | null {
102
+ let offset = 0;
103
+ while (offset + 512 <= tarBuffer.length) {
104
+ const header = tarBuffer.subarray(offset, offset + 512);
105
+
106
+ // Check for end-of-archive (two consecutive zero blocks)
107
+ if (header.every((b) => b === 0)) break;
108
+
109
+ // Extract filename (bytes 0-99, null-terminated)
110
+ const nameEnd = header.indexOf(0, 0);
111
+ const name = header.subarray(0, Math.min(nameEnd >= 0 ? nameEnd : 100, 100)).toString('utf-8');
112
+
113
+ // Extract file size (bytes 124-135, octal string)
114
+ const sizeStr = header.subarray(124, 136).toString('utf-8').trim();
115
+ const size = parseInt(sizeStr, 8) || 0;
116
+
117
+ offset += 512; // move past header
118
+
119
+ if (name.endsWith('SKILL.md') || name === 'SKILL.md') {
120
+ return tarBuffer.subarray(offset, offset + size).toString('utf-8');
121
+ }
122
+
123
+ // Skip to next header (data blocks are padded to 512 bytes)
124
+ offset += Math.ceil(size / 512) * 512;
125
+ }
126
+ return null;
127
+ }
128
+
129
+ /**
130
+ * Fetch a skill's SKILL.md content from the platform tar API.
131
+ * GET /v1/skills/{skill_id}/ returns a tar.gz archive containing all skill files.
132
+ * Falls back to bundled copy on failure.
133
+ */
83
134
  export async function fetchSkillContent(skillId: string): Promise<string | null> {
84
135
  try {
85
- const url = `${GITHUB_RAW_BASE}/${encodeURIComponent(skillId)}/SKILL.md`;
136
+ const url = `${PLATFORM_URL}/v1/skills/${encodeURIComponent(skillId)}/`;
86
137
  const response = await fetch(url, {
87
- signal: AbortSignal.timeout(10000),
138
+ headers: buildPlatformHeaders(),
139
+ signal: AbortSignal.timeout(15000),
88
140
  });
89
141
 
90
142
  if (!response.ok) {
91
143
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
92
144
  }
93
145
 
94
- const content = await response.text();
95
- log.info({ skillId }, 'Fetched remote SKILL.md');
96
- return content;
146
+ const gzipBuffer = Buffer.from(await response.arrayBuffer());
147
+ const tarBuffer = gunzipSync(gzipBuffer);
148
+ const skillMd = extractSkillMdFromTar(tarBuffer);
149
+
150
+ if (skillMd) {
151
+ return skillMd;
152
+ }
153
+
154
+ log.warn({ skillId }, 'SKILL.md not found in platform tar archive, falling back to bundled');
97
155
  } catch (err) {
98
- log.warn({ err, skillId }, 'Failed to fetch remote SKILL.md, falling back to bundled copy');
99
- return getBundledSkillContent(skillId);
156
+ log.warn({ err, skillId }, 'Failed to fetch skill content from platform API, falling back to bundled');
100
157
  }
158
+
159
+ return getBundledSkillContent(skillId);
101
160
  }
102
161
 
103
- /** Check if a skill ID exists in the remote catalog. */
162
+ /** Check if a skill ID exists in the catalog. */
104
163
  export async function checkVellumSkill(skillId: string): Promise<boolean> {
105
164
  const entries = await fetchCatalogEntries();
106
165
  return entries.some((e) => e.id === skillId);
@@ -1,4 +1,5 @@
1
1
  import { getLogger } from '../util/logger.js';
2
+ import { ProviderError } from '../util/errors.js';
2
3
 
3
4
  const log = getLogger('slack-webhook');
4
5
 
@@ -56,6 +57,6 @@ export async function postToSlackWebhook(
56
57
 
57
58
  if (!response.ok) {
58
59
  const body = await response.text();
59
- throw new Error(`Slack webhook returned ${response.status}: ${body}`);
60
+ throw new ProviderError(`Slack webhook returned ${response.status}: ${body}`, 'slack', response.status);
60
61
  }
61
62
  }
@@ -43,6 +43,12 @@ export async function executeSwarm(opts: ExecuteSwarmOptions): Promise<SwarmExec
43
43
  const { plan, limits, backend, workingDir, model, onStatus, signal } = opts;
44
44
  const startTime = Date.now();
45
45
 
46
+ // Safety net: reject cyclic plans even if the caller skipped validation
47
+ const cyclePath = detectCycle(plan.tasks);
48
+ if (cyclePath) {
49
+ throw new Error(`Swarm plan contains a dependency cycle: ${cyclePath.join(' -> ')}`);
50
+ }
51
+
46
52
  onStatus?.({ kind: 'plan_created', message: `Plan with ${plan.tasks.length} tasks` });
47
53
 
48
54
  const results = new Map<string, SwarmTaskResult>();
@@ -128,7 +134,7 @@ export async function executeSwarm(opts: ExecuteSwarmOptions): Promise<SwarmExec
128
134
  const r = results.get(depId);
129
135
  return r ? { taskId: depId, summary: r.summary } : null;
130
136
  })
131
- .filter((d): d is { taskId: string; summary: string } => d !== null);
137
+ .filter((d): d is { taskId: string; summary: string } => d != null);
132
138
 
133
139
  let result = await runWorkerTask({
134
140
  task,
@@ -144,6 +150,11 @@ export async function executeSwarm(opts: ExecuteSwarmOptions): Promise<SwarmExec
144
150
  let retries = 0;
145
151
  while (result.status === 'failed' && retries < limits.maxRetriesPerTask && !signal?.aborted) {
146
152
  retries++;
153
+ // Exponential backoff with ±25% jitter to prevent thundering herd
154
+ const baseDelayMs = 1000 * Math.pow(2, retries - 1);
155
+ const jitter = baseDelayMs * 0.25 * (2 * Math.random() - 1);
156
+ await abortableSleep(baseDelayMs + jitter, signal);
157
+ if (signal?.aborted) break;
147
158
  result = await runWorkerTask({
148
159
  task,
149
160
  upstreamContext: plan.objective,
@@ -253,6 +264,86 @@ function blockDependents(
253
264
  }
254
265
  }
255
266
 
267
+ /**
268
+ * DFS-based cycle detection. Returns the cycle path (e.g. ['a', 'b', 'c', 'a'])
269
+ * if a cycle exists, or null if the graph is a valid DAG.
270
+ */
271
+ function detectCycle(tasks: SwarmTaskNode[]): string[] | null {
272
+ const adj = new Map<string, string[]>();
273
+ for (const t of tasks) {
274
+ adj.set(t.id, []);
275
+ }
276
+ for (const t of tasks) {
277
+ for (const dep of t.dependencies) {
278
+ // Edge from dep -> t.id (dep must finish before t)
279
+ adj.get(dep)?.push(t.id);
280
+ }
281
+ }
282
+
283
+ const WHITE = 0, GRAY = 1, BLACK = 2;
284
+ const color = new Map<string, number>();
285
+ for (const t of tasks) color.set(t.id, WHITE);
286
+
287
+ const parent = new Map<string, string>();
288
+
289
+ // Iterative DFS to avoid stack overflow on deep acyclic chains
290
+ for (const t of tasks) {
291
+ if (color.get(t.id) !== WHITE) continue;
292
+
293
+ const stack: Array<{ node: string; neighborIdx: number }> = [
294
+ { node: t.id, neighborIdx: 0 },
295
+ ];
296
+ color.set(t.id, GRAY);
297
+
298
+ while (stack.length > 0) {
299
+ const frame = stack[stack.length - 1];
300
+ const neighbors = adj.get(frame.node) ?? [];
301
+
302
+ if (frame.neighborIdx >= neighbors.length) {
303
+ // All neighbors visited — mark node as fully processed
304
+ color.set(frame.node, BLACK);
305
+ stack.pop();
306
+ continue;
307
+ }
308
+
309
+ const neighbor = neighbors[frame.neighborIdx];
310
+ frame.neighborIdx++;
311
+
312
+ if (color.get(neighbor) === GRAY) {
313
+ // Back edge found — reconstruct cycle path
314
+ const cycle = [neighbor];
315
+ for (let i = stack.length - 1; i >= 0; i--) {
316
+ cycle.push(stack[i].node);
317
+ if (stack[i].node === neighbor) break;
318
+ }
319
+ cycle.reverse();
320
+ return cycle;
321
+ }
322
+
323
+ if (color.get(neighbor) === WHITE) {
324
+ parent.set(neighbor, frame.node);
325
+ color.set(neighbor, GRAY);
326
+ stack.push({ node: neighbor, neighborIdx: 0 });
327
+ }
328
+ }
329
+ }
330
+ return null;
331
+ }
332
+
333
+ /** Resolves after `ms` milliseconds, or immediately if the signal fires first. */
334
+ function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
335
+ if (signal?.aborted) return Promise.resolve();
336
+ return new Promise<void>((resolve) => {
337
+ const timer = setTimeout(done, ms);
338
+ signal?.addEventListener('abort', done, { once: true });
339
+ function done() {
340
+ clearTimeout(timer);
341
+ signal?.removeEventListener('abort', done);
342
+ resolve();
343
+ }
344
+ });
345
+ }
346
+
256
347
  function buildMarkdownFallback(objective: string, results: SwarmTaskResult[]): string {
257
348
  const lines: string[] = [`## Swarm Results: ${objective}`, ''];
258
349
 
@@ -1,4 +1,5 @@
1
1
  import type { Provider, Message } from '../providers/types.js';
2
+ import { parseJsonSafe } from '../util/json.js';
2
3
  import type { SwarmPlan, SwarmTaskNode } from './types.js';
3
4
  import type { SwarmLimits } from './limits.js';
4
5
  import { validateAndNormalizePlan } from './plan-validator.js';
@@ -61,7 +62,7 @@ export function parsePlanJSON(raw: string): { tasks: Array<{ id: string; role: s
61
62
  // Try all fenced code blocks — LLMs sometimes emit non-JSON blocks before the plan
62
63
  const fencedRegex = /```(?:json)?\s*\n?([\s\S]*?)\n?```/g;
63
64
  let match;
64
- while ((match = fencedRegex.exec(raw)) !== null) {
65
+ while ((match = fencedRegex.exec(raw)) != null) {
65
66
  const result = tryParsePlan(match[1]);
66
67
  if (result) return result;
67
68
  }
@@ -71,15 +72,11 @@ export function parsePlanJSON(raw: string): { tasks: Array<{ id: string; role: s
71
72
  }
72
73
 
73
74
  function tryParsePlan(jsonStr: string): { tasks: Array<{ id: string; role: string; objective: string; dependencies: string[] }> } | null {
74
- try {
75
- const parsed = JSON.parse(jsonStr.trim());
76
- if (parsed && Array.isArray(parsed.tasks)) {
77
- return parsed;
78
- }
79
- return null;
80
- } catch {
81
- return null;
75
+ const parsed = parseJsonSafe<{ tasks?: unknown }>(jsonStr.trim());
76
+ if (parsed && Array.isArray(parsed.tasks)) {
77
+ return parsed as { tasks: Array<{ id: string; role: string; objective: string; dependencies: string[] }> };
82
78
  }
79
+ return null;
83
80
  }
84
81
 
85
82
  /**
@@ -1,5 +1,6 @@
1
1
  import type { SwarmRole, SwarmTaskResult } from './types.js';
2
2
  import { truncate } from '../util/truncate.js';
3
+ import { parseJsonSafe } from '../util/json.js';
3
4
 
4
5
  /**
5
6
  * Build a role-specific worker prompt for a swarm task.
@@ -56,18 +57,14 @@ export function parseWorkerOutput(raw: string): Pick<SwarmTaskResult, 'summary'
56
57
 
57
58
  // Walk backwards to prefer the final valid contract block.
58
59
  for (let i = jsonBlocks.length - 1; i >= 0; i--) {
59
- try {
60
- const parsed = JSON.parse(jsonBlocks[i][1]);
61
- if (typeof parsed.summary !== 'string') continue;
62
- return {
63
- summary: parsed.summary,
64
- artifacts: Array.isArray(parsed.artifacts) ? parsed.artifacts : [],
65
- issues: Array.isArray(parsed.issues) ? parsed.issues : [],
66
- nextSteps: Array.isArray(parsed.nextSteps) ? parsed.nextSteps : [],
67
- };
68
- } catch {
69
- // Malformed JSON — try the next block up.
70
- }
60
+ const parsed = parseJsonSafe<Record<string, unknown>>(jsonBlocks[i][1]);
61
+ if (!parsed || typeof parsed.summary !== 'string') continue;
62
+ return {
63
+ summary: parsed.summary,
64
+ artifacts: Array.isArray(parsed.artifacts) ? parsed.artifacts : [],
65
+ issues: Array.isArray(parsed.issues) ? parsed.issues : [],
66
+ nextSteps: Array.isArray(parsed.nextSteps) ? parsed.nextSteps : [],
67
+ };
71
68
  }
72
69
 
73
70
  return {
@@ -4,6 +4,7 @@ import { messages as messagesTable } from '../memory/schema.js';
4
4
  import { createTask } from './task-store.js';
5
5
  import type { Task } from './task-store.js';
6
6
  import { truncate } from '../util/truncate.js';
7
+ import { parseJsonSafe } from '../util/json.js';
7
8
  import { sanitizeToolList } from './tool-sanitizer.js';
8
9
 
9
10
  /** Output schema for the task compiler. */
@@ -91,20 +92,14 @@ export function saveCompiledTask(compiled: CompiledTask, conversationId: string)
91
92
  * string or a JSON array of Anthropic content blocks.
92
93
  */
93
94
  function extractTextContent(content: string): string {
94
- try {
95
- const parsed = JSON.parse(content);
96
- if (Array.isArray(parsed)) {
97
- return parsed
98
- .filter((block: Record<string, unknown>) => block.type === 'text')
99
- .map((block: Record<string, unknown>) => block.text as string)
100
- .join('\n');
101
- }
102
- // If it parsed but isn't an array, treat as plain text
103
- return content;
104
- } catch {
105
- // Not JSON — it's a plain text message
106
- return content;
95
+ const parsed = parseJsonSafe(content);
96
+ if (Array.isArray(parsed)) {
97
+ return parsed
98
+ .filter((block: Record<string, unknown>) => block.type === 'text')
99
+ .map((block: Record<string, unknown>) => block.text as string)
100
+ .join('\n');
107
101
  }
102
+ return content;
108
103
  }
109
104
 
110
105
  /**
@@ -118,22 +113,18 @@ function extractToolNames(
118
113
  for (const msg of msgs) {
119
114
  if (msg.role !== 'assistant') continue;
120
115
 
121
- try {
122
- const parsed = JSON.parse(msg.content);
123
- if (!Array.isArray(parsed)) continue;
124
-
125
- for (const block of parsed) {
126
- if (
127
- block &&
128
- typeof block === 'object' &&
129
- block.type === 'tool_use' &&
130
- typeof block.name === 'string'
131
- ) {
132
- tools.add(block.name);
133
- }
116
+ const parsed = parseJsonSafe(msg.content);
117
+ if (!Array.isArray(parsed)) continue;
118
+
119
+ for (const block of parsed) {
120
+ if (
121
+ block &&
122
+ typeof block === 'object' &&
123
+ block.type === 'tool_use' &&
124
+ typeof block.name === 'string'
125
+ ) {
126
+ tools.add(block.name);
134
127
  }
135
- } catch {
136
- // Not JSON content — skip
137
128
  }
138
129
  }
139
130
 
@@ -25,7 +25,7 @@ export interface TaskRunResult {
25
25
  export function renderTemplate(template: string, inputs: Record<string, string>): string {
26
26
  return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
27
27
  if (key in inputs) return inputs[key];
28
- return match;
28
+ return `<MISSING: ${key}>`;
29
29
  });
30
30
  }
31
31
 
@@ -12,7 +12,7 @@ import { RiskLevel } from '../../permissions/types.js';
12
12
  import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
13
13
  import type { ToolDefinition } from '../../providers/types.js';
14
14
  import { registerTool } from '../registry.js';
15
- import { getDb } from '../../memory/db.js';
15
+ import { getDb, rawAll } from '../../memory/db.js';
16
16
  import { attachments, messageAttachments, messages, conversations } from '../../memory/schema.js';
17
17
  import type { StoredAttachment } from '../../memory/attachments-store.js';
18
18
  import { isAttachmentVisible, type AttachmentContext } from '../../daemon/media-visibility-policy.js';
@@ -151,17 +151,16 @@ export function searchAttachments(params: AssetSearchParams): StoredAttachment[]
151
151
  .where(eq(messages.conversationId, params.conversation_id))
152
152
  .all()
153
153
  .map((r) => r.attachmentId)
154
- .filter((id): id is string => id !== null);
154
+ .filter((id): id is string => id !== undefined);
155
155
 
156
156
  if (linkedIds.length === 0) {
157
157
  return [];
158
158
  }
159
159
 
160
- // Use raw SQL IN clause for the attachment ID set
161
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
162
160
  const placeholders = linkedIds.map(() => '?').join(', ');
163
161
 
164
- // Build WHERE clauses for raw query
162
+ // Build WHERE clauses for raw query (FTS5 virtual table not involved,
163
+ // but dynamic IN-list with optional filters is simpler in raw SQL)
165
164
  const whereParts: string[] = [`a.id IN (${placeholders})`];
166
165
  const bindValues: (string | number)[] = [...linkedIds];
167
166
 
@@ -182,15 +181,8 @@ export function searchAttachments(params: AssetSearchParams): StoredAttachment[]
182
181
  }
183
182
  }
184
183
  const limit = Math.min(params.limit ?? DEFAULT_LIMIT, MAX_RESULTS);
185
- const stmt = raw.prepare(
186
- `SELECT a.id, a.original_filename, a.mime_type, a.size_bytes, a.kind, a.thumbnail_base64, a.created_at
187
- FROM attachments a
188
- WHERE ${whereParts.join(' AND ')}
189
- ORDER BY a.created_at DESC
190
- LIMIT ?`,
191
- );
192
184
 
193
- const rows = stmt.all(...bindValues, limit) as Array<{
185
+ interface AttachmentRow {
194
186
  id: string;
195
187
  original_filename: string;
196
188
  mime_type: string;
@@ -198,7 +190,16 @@ export function searchAttachments(params: AssetSearchParams): StoredAttachment[]
198
190
  kind: string;
199
191
  thumbnail_base64: string | null;
200
192
  created_at: number;
201
- }>;
193
+ }
194
+
195
+ const rows = rawAll<AttachmentRow>(
196
+ `SELECT a.id, a.original_filename, a.mime_type, a.size_bytes, a.kind, a.thumbnail_base64, a.created_at
197
+ FROM attachments a
198
+ WHERE ${whereParts.join(' AND ')}
199
+ ORDER BY a.created_at DESC
200
+ LIMIT ?`,
201
+ ...bindValues, limit,
202
+ );
202
203
 
203
204
  return rows.map((r) => ({
204
205
  id: r.id,
@@ -37,6 +37,7 @@ function mockPage(url: string, evaluateResult: unknown = null): Page {
37
37
  keyboard: { press: async () => {} },
38
38
  mouse: { click: async () => {}, move: async () => {}, wheel: async () => {} },
39
39
  bringToFront: async () => {},
40
+ on: () => {},
40
41
  };
41
42
  }
42
43
 
@@ -113,6 +113,7 @@ export async function autoNavigate(
113
113
  return [];
114
114
  }
115
115
 
116
+ await cdp.send('Page.bringToFront').catch(() => {});
116
117
  await cdp.send('Page.enable').catch(() => {});
117
118
 
118
119
  const rootUrl = `https://${domain}/`;
@@ -93,6 +93,10 @@ export async function executeBrowserNavigate(
93
93
  input: Record<string, unknown>,
94
94
  context: ToolContext,
95
95
  ): Promise<ToolExecutionResult> {
96
+ if (context.signal?.aborted) {
97
+ return { content: 'Error: operation was cancelled', isError: true };
98
+ }
99
+
96
100
  const parsedUrl = parseUrl(input.url);
97
101
  if (!parsedUrl) {
98
102
  return { content: 'Error: url is required and must be a valid HTTP(S) URL', isError: true };
@@ -316,6 +320,10 @@ export async function executeBrowserNavigate(
316
320
  if (challenge?.type === 'captcha') {
317
321
  log.info('CAPTCHA detected, waiting up to 5s for auto-resolve');
318
322
  for (let i = 0; i < 5; i++) {
323
+ if (context.signal?.aborted) {
324
+ if (sender) updateBrowserStatus(context.sessionId, sender, 'idle');
325
+ return { content: 'Navigation cancelled.', isError: true };
326
+ }
319
327
  await new Promise((r) => setTimeout(r, 1000));
320
328
  const still = await detectCaptchaChallenge(page);
321
329
  if (!still) {
@@ -797,11 +805,15 @@ export async function executeBrowserWaitFor(
797
805
  input: Record<string, unknown>,
798
806
  context: ToolContext,
799
807
  ): Promise<ToolExecutionResult> {
808
+ if (context.signal?.aborted) {
809
+ return { content: 'Error: operation was cancelled', isError: true };
810
+ }
811
+
800
812
  const selector = typeof input.selector === 'string' && input.selector ? input.selector : null;
801
813
  const text = typeof input.text === 'string' && input.text ? input.text : null;
802
814
  const duration = typeof input.duration === 'number' ? input.duration : null;
803
815
 
804
- const modeCount = [selector, text, duration].filter((v) => v !== null).length;
816
+ const modeCount = [selector, text, duration].filter((v) => v != null).length;
805
817
  if (modeCount === 0) {
806
818
  return { content: 'Error: Exactly one of selector, text, or duration is required.', isError: true };
807
819
  }