@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
@@ -1,8 +1,10 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
1
  import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test';
3
2
  import { mkdtempSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
4
3
  import { tmpdir } from 'node:os';
5
4
  import { join } from 'node:path';
5
+ import type { Tool } from '../tools/types.js';
6
+ import type { SandboxBackend } from '../tools/terminal/backends/types.js';
7
+ import type { ShellOutputResult } from '../tools/shared/shell-output.js';
6
8
 
7
9
  // ── Mock modules ────────────────────────────────────────────────────────────
8
10
 
@@ -502,7 +504,7 @@ describe('wrapCommand', () => {
502
504
  describe('Native sandbox backend', () => {
503
505
  // We test NativeBackend directly rather than through wrapCommand to avoid
504
506
  // platform-dependent sandbox-exec/bwrap availability.
505
- let NativeBackend: any;
507
+ let NativeBackend: new () => SandboxBackend;
506
508
 
507
509
  beforeEach(async () => {
508
510
  const mod = await import('../tools/terminal/backends/native.js');
@@ -546,8 +548,8 @@ describe('Native sandbox backend', () => {
546
548
  // ═══════════════════════════════════════════════════════════════════════════
547
549
 
548
550
  describe('Docker sandbox backend', () => {
549
- let DockerBackend: any;
550
- let _resetDockerChecks: any;
551
+ let DockerBackend: new (sandboxRoot: string, config?: Record<string, unknown>, uid?: number, gid?: number) => SandboxBackend;
552
+ let _resetDockerChecks: () => void;
551
553
 
552
554
  const sandboxDir = join(testTmpDir, 'docker-sandbox');
553
555
 
@@ -628,7 +630,7 @@ describe('Docker sandbox backend', () => {
628
630
  // ═══════════════════════════════════════════════════════════════════════════
629
631
 
630
632
  describe('Shell tool input validation', () => {
631
- let shellTool: any;
633
+ let shellTool: Tool;
632
634
 
633
635
  beforeEach(async () => {
634
636
  const mod = await import('../tools/terminal/shell.js');
@@ -637,6 +639,7 @@ describe('Shell tool input validation', () => {
637
639
 
638
640
  const baseContext = {
639
641
  workingDir: testTmpDir,
642
+ sessionId: 'test-session-1',
640
643
  conversationId: 'test-conv-1',
641
644
  onOutput: () => {},
642
645
  };
@@ -700,13 +703,14 @@ describe('Shell tool input validation', () => {
700
703
 
701
704
  test('tool definition includes required schema fields', () => {
702
705
  const def = shellTool.getDefinition();
706
+ const schema = def.input_schema as { required: string[]; properties: Record<string, unknown> };
703
707
  expect(def.name).toBe('bash');
704
- expect(def.input_schema.required).toContain('command');
705
- expect(def.input_schema.required).toContain('reason');
706
- expect(def.input_schema.properties.command).toBeDefined();
707
- expect(def.input_schema.properties.timeout_seconds).toBeDefined();
708
- expect(def.input_schema.properties.network_mode).toBeDefined();
709
- expect(def.input_schema.properties.credential_ids).toBeDefined();
708
+ expect(schema.required).toContain('command');
709
+ expect(schema.required).toContain('reason');
710
+ expect(schema.properties.command).toBeDefined();
711
+ expect(schema.properties.timeout_seconds).toBeDefined();
712
+ expect(schema.properties.network_mode).toBeDefined();
713
+ expect(schema.properties.credential_ids).toBeDefined();
710
714
  });
711
715
  });
712
716
 
@@ -715,7 +719,7 @@ describe('Shell tool input validation', () => {
715
719
  // ═══════════════════════════════════════════════════════════════════════════
716
720
 
717
721
  describe('formatShellOutput', () => {
718
- let formatShellOutput: any;
722
+ let formatShellOutput: (stdout: string, stderr: string, code: number | null, timedOut: boolean, timeoutSec: number) => ShellOutputResult;
719
723
 
720
724
  beforeEach(async () => {
721
725
  const mod = await import('../tools/shared/shell-output.js');
@@ -775,7 +779,7 @@ describe('formatShellOutput', () => {
775
779
  // ═══════════════════════════════════════════════════════════════════════════
776
780
 
777
781
  describe('EvaluateTypescriptTool input validation', () => {
778
- let evalTool: any;
782
+ let evalTool: Tool;
779
783
 
780
784
  beforeEach(async () => {
781
785
  const mod = await import('../tools/terminal/evaluate-typescript.js');
@@ -784,6 +788,7 @@ describe('EvaluateTypescriptTool input validation', () => {
784
788
 
785
789
  const baseContext = {
786
790
  workingDir: testTmpDir,
791
+ sessionId: 'test-session-1',
787
792
  conversationId: 'test-conv-1',
788
793
  onOutput: () => {},
789
794
  };
@@ -829,12 +834,13 @@ describe('EvaluateTypescriptTool input validation', () => {
829
834
 
830
835
  test('tool definition has correct name and schema', () => {
831
836
  const def = evalTool.getDefinition();
837
+ const schema = def.input_schema as { required: string[]; properties: Record<string, unknown> };
832
838
  expect(def.name).toBe('evaluate_typescript_code');
833
- expect(def.input_schema.required).toContain('code');
834
- expect(def.input_schema.properties.code).toBeDefined();
835
- expect(def.input_schema.properties.mock_input_json).toBeDefined();
836
- expect(def.input_schema.properties.timeout_seconds).toBeDefined();
837
- expect(def.input_schema.properties.filename).toBeDefined();
838
- expect(def.input_schema.properties.entrypoint).toBeDefined();
839
+ expect(schema.required).toContain('code');
840
+ expect(schema.properties.code).toBeDefined();
841
+ expect(schema.properties.mock_input_json).toBeDefined();
842
+ expect(schema.properties.timeout_seconds).toBeDefined();
843
+ expect(schema.properties.filename).toBeDefined();
844
+ expect(schema.properties.entrypoint).toBeDefined();
839
845
  });
840
846
  });
@@ -0,0 +1,545 @@
1
+ /**
2
+ * Tests verifying that resources are properly cleaned up when AbortSignal fires
3
+ * during tool execution: shell processes are killed, file operations respect
4
+ * pre-abort signals, and git operations abort via the signal.
5
+ */
6
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
7
+ import {
8
+ mkdirSync,
9
+ mkdtempSync,
10
+ rmSync,
11
+ existsSync,
12
+ writeFileSync,
13
+ realpathSync,
14
+ } from 'node:fs';
15
+ import { join } from 'node:path';
16
+ import { tmpdir } from 'node:os';
17
+
18
+ // ── Shared mock setup ────────────────────────────────────────────────────────
19
+ // Config mock must be declared before importing tool modules so that the
20
+ // mock.module calls are hoisted above the dynamic imports.
21
+
22
+ import { mock } from 'bun:test';
23
+
24
+ mock.module('../config/loader.js', () => ({
25
+ getConfig: () => ({
26
+ provider: 'anthropic',
27
+ model: 'test',
28
+ apiKeys: {},
29
+ maxTokens: 4096,
30
+ dataDir: '/tmp',
31
+ timeouts: {
32
+ shellDefaultTimeoutSec: 120,
33
+ shellMaxTimeoutSec: 600,
34
+ permissionTimeoutSec: 300,
35
+ },
36
+ sandbox: { enabled: false, backend: 'native' as const },
37
+ rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
38
+ secretDetection: { enabled: false, action: 'warn' as const, entropyThreshold: 4.0 },
39
+ }),
40
+ loadConfig: () => ({}),
41
+ invalidateConfigCache: () => {},
42
+ saveConfig: () => {},
43
+ loadRawConfig: () => ({}),
44
+ saveRawConfig: () => {},
45
+ }));
46
+
47
+ mock.module('../util/logger.js', () => ({
48
+ getLogger: () => new Proxy({} as Record<string, unknown>, {
49
+ get: () => () => {},
50
+ }),
51
+ }));
52
+
53
+ // shell.ts requires the sandbox wrapper — provide a pass-through stub.
54
+ mock.module('../tools/terminal/sandbox.js', () => ({
55
+ wrapCommand: (command: string, _cwd: string, _cfg: unknown, _opts?: unknown) => ({
56
+ command: 'bash',
57
+ args: ['-c', '--', command],
58
+ sandboxed: false,
59
+ }),
60
+ }));
61
+
62
+ // shell.ts uses the script proxy — stub it to avoid network side-effects.
63
+ mock.module('../tools/network/script-proxy/index.js', () => ({
64
+ getOrStartSession: async () => ({
65
+ session: { id: 'mock-session' },
66
+ }),
67
+ getSessionEnv: () => ({}),
68
+ }));
69
+
70
+ mock.module('../util/platform.js', () => ({
71
+ getDataDir: () => '/tmp',
72
+ getSocketPath: () => '/tmp/vellum.sock',
73
+ }));
74
+
75
+ mock.module('../tools/credentials/resolve.js', () => ({
76
+ resolveCredentialRef: () => null,
77
+ }));
78
+
79
+ mock.module('../tools/network/script-proxy/logging.js', () => ({
80
+ buildCredentialRefTrace: () => ({}),
81
+ }));
82
+
83
+ mock.module('../security/secret-scanner.js', () => ({
84
+ redactSecrets: (s: string) => s,
85
+ scanText: () => [],
86
+ }));
87
+
88
+ // ── Helpers ──────────────────────────────────────────────────────────────────
89
+
90
+ const testDirs: string[] = [];
91
+
92
+ function makeTempDir(): string {
93
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), 'abort-cleanup-test-')));
94
+ testDirs.push(dir);
95
+ return dir;
96
+ }
97
+
98
+ function makeToolContext(workingDir: string, signal?: AbortSignal) {
99
+ return {
100
+ workingDir,
101
+ sessionId: 'test-session',
102
+ conversationId: 'test-conv',
103
+ signal,
104
+ };
105
+ }
106
+
107
+ // Restore all module mocks before each test so that this suite is not
108
+ // order-dependent. Without this, stubs installed by one test file can bleed
109
+ // into subsequent files and produce false passes or failures.
110
+ beforeEach(() => {
111
+ mock.restore();
112
+ });
113
+
114
+ afterEach(() => {
115
+ for (const dir of testDirs.splice(0)) {
116
+ rmSync(dir, { recursive: true, force: true });
117
+ }
118
+ });
119
+
120
+ // ── 1. Shell process cleanup on AbortSignal ─────────────────────────────────
121
+
122
+ describe('shell tool — process cleanup on AbortSignal', () => {
123
+ test('kills the child process when AbortSignal fires mid-execution', async () => {
124
+ const { hostShellTool } = await import('../tools/host-terminal/host-shell.js');
125
+ const dir = makeTempDir();
126
+ const ac = new AbortController();
127
+
128
+ const promise = hostShellTool.execute(
129
+ { command: 'sleep 30', reason: 'test' },
130
+ makeToolContext(dir, ac.signal),
131
+ );
132
+
133
+ // Allow the child process to start before aborting.
134
+ await new Promise((r) => setTimeout(r, 100));
135
+ ac.abort();
136
+
137
+ const result = await promise;
138
+
139
+ // The process was killed by SIGKILL, so it exits non-zero.
140
+ expect(result.isError).toBe(true);
141
+ });
142
+
143
+ test('kills the child process immediately when signal is already aborted', async () => {
144
+ const { hostShellTool } = await import('../tools/host-terminal/host-shell.js');
145
+ const dir = makeTempDir();
146
+ const ac = new AbortController();
147
+ ac.abort(); // pre-aborted
148
+
149
+ const result = await hostShellTool.execute(
150
+ { command: 'sleep 30', reason: 'test' },
151
+ makeToolContext(dir, ac.signal),
152
+ );
153
+
154
+ expect(result.isError).toBe(true);
155
+ });
156
+
157
+ test('completes normally when abort signal never fires', async () => {
158
+ const { hostShellTool } = await import('../tools/host-terminal/host-shell.js');
159
+ const dir = makeTempDir();
160
+ const ac = new AbortController();
161
+
162
+ const result = await hostShellTool.execute(
163
+ { command: 'echo completed', reason: 'test' },
164
+ makeToolContext(dir, ac.signal),
165
+ );
166
+
167
+ expect(result.isError).toBe(false);
168
+ expect(result.content).toContain('completed');
169
+ // Ensure aborting after completion doesn't cause errors.
170
+ ac.abort();
171
+ });
172
+
173
+ test('removes the abort listener after process exits normally', async () => {
174
+ const { hostShellTool } = await import('../tools/host-terminal/host-shell.js');
175
+ const dir = makeTempDir();
176
+ const ac = new AbortController();
177
+
178
+ await hostShellTool.execute(
179
+ { command: 'echo done', reason: 'test' },
180
+ makeToolContext(dir, ac.signal),
181
+ );
182
+
183
+ // After the process exits the abort listener should be removed.
184
+ // AbortSignal.eventCounts is not universally available, but we can
185
+ // verify the listener doesn't hold a reference by aborting and
186
+ // confirming no error is thrown from a dangling handler.
187
+ expect(() => ac.abort()).not.toThrow();
188
+ });
189
+
190
+ test('abort during long-running output does not leave orphaned listeners', async () => {
191
+ const { hostShellTool } = await import('../tools/host-terminal/host-shell.js');
192
+ const dir = makeTempDir();
193
+ const ac = new AbortController();
194
+ const chunks: string[] = [];
195
+
196
+ const promise = hostShellTool.execute(
197
+ { command: 'for i in 1 2 3 4 5; do echo $i; sleep 2; done', reason: 'test' },
198
+ {
199
+ ...makeToolContext(dir, ac.signal),
200
+ onOutput: (c: string) => chunks.push(c),
201
+ },
202
+ );
203
+
204
+ // Wait for the first chunk to arrive before aborting.
205
+ await new Promise<void>((resolve) => {
206
+ const interval = setInterval(() => {
207
+ if (chunks.length > 0) {
208
+ clearInterval(interval);
209
+ resolve();
210
+ }
211
+ }, 50);
212
+ });
213
+
214
+ ac.abort();
215
+ const result = await promise;
216
+
217
+ // Process was killed.
218
+ expect(result.isError).toBe(true);
219
+ });
220
+ });
221
+
222
+ // ── 2. File operation abort handling ─────────────────────────────────────────
223
+ //
224
+ // File operations in this codebase use synchronous Node.js APIs
225
+ // (readFileSync / writeFileSync), so there are no long-lived file handles
226
+ // to close. Abort cancellation is handled at the executor level:
227
+ // ToolExecutor.execute() checks signal.aborted before dispatching to the
228
+ // tool and returns a 'Cancelled' error result immediately.
229
+ // The tests below verify that contract at the tool level.
230
+
231
+ describe('file tools — abort signal pre-check', () => {
232
+ test('file_write tool: execute still succeeds when context has no signal', async () => {
233
+ const { getTool } = await import('../tools/registry.js');
234
+ await import('../tools/filesystem/write.js');
235
+ const fileWriteTool = getTool('file_write')!;
236
+ const dir = makeTempDir();
237
+
238
+ const result = await fileWriteTool.execute(
239
+ { path: 'out.txt', content: 'hello', reason: 'test' },
240
+ makeToolContext(dir),
241
+ );
242
+
243
+ expect(result.isError).toBe(false);
244
+ expect(existsSync(join(dir, 'out.txt'))).toBe(true);
245
+ });
246
+
247
+ test('file_write tool: execute succeeds with a non-aborted signal', async () => {
248
+ const { getTool } = await import('../tools/registry.js');
249
+ await import('../tools/filesystem/write.js');
250
+ const fileWriteTool = getTool('file_write')!;
251
+ const dir = makeTempDir();
252
+ const ac = new AbortController();
253
+
254
+ const result = await fileWriteTool.execute(
255
+ { path: 'out2.txt', content: 'world', reason: 'test' },
256
+ makeToolContext(dir, ac.signal),
257
+ );
258
+
259
+ expect(result.isError).toBe(false);
260
+ expect(existsSync(join(dir, 'out2.txt'))).toBe(true);
261
+ });
262
+
263
+ test('file_read tool: execute succeeds when context has no signal', async () => {
264
+ const { getTool } = await import('../tools/registry.js');
265
+ await import('../tools/filesystem/read.js');
266
+ const fileReadTool = getTool('file_read')!;
267
+ const dir = makeTempDir();
268
+ writeFileSync(join(dir, 'read-me.txt'), 'content to read');
269
+
270
+ const result = await fileReadTool.execute(
271
+ { path: 'read-me.txt' },
272
+ makeToolContext(dir),
273
+ );
274
+
275
+ expect(result.isError).toBe(false);
276
+ expect(result.content).toContain('content to read');
277
+ });
278
+
279
+ test('file_read tool: execute succeeds with a non-aborted signal', async () => {
280
+ const { getTool } = await import('../tools/registry.js');
281
+ await import('../tools/filesystem/read.js');
282
+ const fileReadTool = getTool('file_read')!;
283
+ const dir = makeTempDir();
284
+ writeFileSync(join(dir, 'read-me2.txt'), 'readable');
285
+ const ac = new AbortController();
286
+
287
+ const result = await fileReadTool.execute(
288
+ { path: 'read-me2.txt' },
289
+ makeToolContext(dir, ac.signal),
290
+ );
291
+
292
+ expect(result.isError).toBe(false);
293
+ expect(result.content).toContain('readable');
294
+ });
295
+
296
+ test('file_write tool: synchronous write completes regardless of pre-aborted signal (abort checked at executor level)', async () => {
297
+ // The tool implementations use synchronous Node.js I/O, so they complete
298
+ // regardless of the signal. The ToolExecutor.execute() wrapper is
299
+ // responsible for checking signal.aborted before dispatching; the tool
300
+ // itself is not expected to check it. This test documents that contract.
301
+ const { getTool } = await import('../tools/registry.js');
302
+ await import('../tools/filesystem/write.js');
303
+
304
+ const dir = makeTempDir();
305
+ const ac = new AbortController();
306
+ ac.abort(); // pre-aborted
307
+
308
+ const fileWriteTool = getTool('file_write')!;
309
+
310
+ // When invoked directly (not through ToolExecutor), the sync write runs.
311
+ const result = await fileWriteTool.execute(
312
+ { path: 'should-write.txt', content: 'test', reason: 'test' },
313
+ makeToolContext(dir, ac.signal),
314
+ );
315
+ // Should succeed — the tool's own execute() doesn't check the signal.
316
+ expect(result.isError).toBe(false);
317
+ });
318
+ });
319
+
320
+ // ── 3. Git operation cleanup on AbortSignal ───────────────────────────────────
321
+
322
+ describe('WorkspaceGitService — abort signal propagation', () => {
323
+ let testDir: string;
324
+
325
+ beforeEach(() => {
326
+ testDir = join(
327
+ tmpdir(),
328
+ `abort-git-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
329
+ );
330
+ mkdirSync(testDir, { recursive: true });
331
+ testDirs.push(testDir);
332
+ });
333
+
334
+ test('writeNote: AbortSignal cancels the git notes operation', async () => {
335
+ const {
336
+ WorkspaceGitService,
337
+ _resetGitServiceRegistry,
338
+ } = await import('../workspace/git-service.js');
339
+
340
+ _resetGitServiceRegistry();
341
+ const service = new WorkspaceGitService(testDir);
342
+ await service.ensureInitialized();
343
+
344
+ const headHash = await service.getHeadHash();
345
+
346
+ // Abort immediately — writeNote passes the signal to execFileAsync.
347
+ const ac = new AbortController();
348
+ ac.abort();
349
+
350
+ // The operation should throw (or resolve with an error) because the signal
351
+ // is already aborted when execFileAsync receives it.
352
+ let threw = false;
353
+ try {
354
+ await service.writeNote(headHash, 'should-be-cancelled', ac.signal);
355
+ } catch {
356
+ threw = true;
357
+ }
358
+
359
+ // With a pre-aborted signal, Node's execFileAsync rejects immediately.
360
+ expect(threw).toBe(true);
361
+ });
362
+
363
+ test('writeNote: completes normally without an abort signal', async () => {
364
+ const {
365
+ WorkspaceGitService,
366
+ _resetGitServiceRegistry,
367
+ } = await import('../workspace/git-service.js');
368
+
369
+ _resetGitServiceRegistry();
370
+ const service = new WorkspaceGitService(testDir);
371
+ await service.ensureInitialized();
372
+
373
+ const headHash = await service.getHeadHash();
374
+
375
+ // Should not throw.
376
+ await service.writeNote(headHash, 'note content');
377
+ });
378
+
379
+ test('commitIfDirty: respects deadlineMs to avoid stale commits', async () => {
380
+ const {
381
+ WorkspaceGitService,
382
+ _resetGitServiceRegistry,
383
+ } = await import('../workspace/git-service.js');
384
+
385
+ _resetGitServiceRegistry();
386
+ const service = new WorkspaceGitService(testDir);
387
+ await service.ensureInitialized();
388
+
389
+ writeFileSync(join(testDir, 'dirty.txt'), 'uncommitted');
390
+
391
+ // Deadline already expired.
392
+ const { committed } = await service.commitIfDirty(
393
+ () => ({ message: 'should not commit' }),
394
+ { deadlineMs: Date.now() - 1000 },
395
+ );
396
+
397
+ expect(committed).toBe(false);
398
+ });
399
+
400
+ test('commitIfDirty: commits when deadline has not expired', async () => {
401
+ const {
402
+ WorkspaceGitService,
403
+ _resetGitServiceRegistry,
404
+ } = await import('../workspace/git-service.js');
405
+
406
+ _resetGitServiceRegistry();
407
+ const service = new WorkspaceGitService(testDir);
408
+ await service.ensureInitialized();
409
+
410
+ writeFileSync(join(testDir, 'commit-me.txt'), 'changes');
411
+
412
+ const { committed } = await service.commitIfDirty(
413
+ () => ({ message: 'test commit' }),
414
+ { deadlineMs: Date.now() + 30_000 },
415
+ );
416
+
417
+ expect(committed).toBe(true);
418
+ });
419
+
420
+ test('ensureInitialized: concurrent calls resolve without duplicate git inits', async () => {
421
+ const {
422
+ WorkspaceGitService,
423
+ _resetGitServiceRegistry,
424
+ } = await import('../workspace/git-service.js');
425
+
426
+ _resetGitServiceRegistry();
427
+ const service = new WorkspaceGitService(testDir);
428
+
429
+ // Fire multiple concurrent init calls — all should resolve without errors.
430
+ await Promise.all([
431
+ service.ensureInitialized(),
432
+ service.ensureInitialized(),
433
+ service.ensureInitialized(),
434
+ ]);
435
+
436
+ expect(service.isInitialized()).toBe(true);
437
+ });
438
+
439
+ test('commitChanges: interleaved with abort-signalled writeNote does not corrupt repo', async () => {
440
+ const {
441
+ WorkspaceGitService,
442
+ _resetGitServiceRegistry,
443
+ } = await import('../workspace/git-service.js');
444
+
445
+ _resetGitServiceRegistry();
446
+ const service = new WorkspaceGitService(testDir);
447
+ await service.ensureInitialized();
448
+
449
+ writeFileSync(join(testDir, 'f.txt'), 'data');
450
+
451
+ // Commit the file.
452
+ await service.commitChanges('add file');
453
+
454
+ const headHash = await service.getHeadHash();
455
+
456
+ // Fire a pre-aborted writeNote alongside another commitChanges.
457
+ const ac = new AbortController();
458
+ ac.abort();
459
+
460
+ const [commitResult] = await Promise.allSettled([
461
+ service.commitChanges('second commit').then(() => 'committed'),
462
+ service.writeNote(headHash, 'note', ac.signal).catch(() => 'note-aborted'),
463
+ ]);
464
+
465
+ // The commit itself should succeed — the aborted note does not block it.
466
+ expect(commitResult.status).toBe('fulfilled');
467
+ if (commitResult.status === 'fulfilled') {
468
+ expect(commitResult.value).toBe('committed');
469
+ }
470
+
471
+ // Repo must still be in a clean, functional state.
472
+ const status = await service.getStatus();
473
+ expect(status.clean).toBe(true);
474
+ });
475
+ });
476
+
477
+ // ── 4. Shell tool (sandboxed) — abort signal ─────────────────────────────────
478
+ //
479
+ // The sandboxed `bash` tool mirrors the host_bash abort handling. Since the
480
+ // sandbox stub is already mocked above, we can drive the sandboxed path too.
481
+
482
+ describe('bash (sandboxed) shell tool — process cleanup on AbortSignal', () => {
483
+ test('kills child process when signal fires mid-execution', async () => {
484
+ // Import shell.ts (registered as 'bash') after mocks are in place.
485
+ const { getTool } = await import('../tools/registry.js');
486
+ await import('../tools/terminal/shell.js');
487
+
488
+ // Assert registration — a missing tool signals a real regression, not a skip.
489
+ expect(getTool('bash')).toBeDefined();
490
+ const bashTool = getTool('bash')!;
491
+
492
+ const dir = makeTempDir();
493
+ const ac = new AbortController();
494
+
495
+ const promise = bashTool.execute(
496
+ { command: 'sleep 30', reason: 'test' },
497
+ makeToolContext(dir, ac.signal),
498
+ );
499
+
500
+ await new Promise((r) => setTimeout(r, 100));
501
+ ac.abort();
502
+
503
+ const result = await promise;
504
+ expect(result.isError).toBe(true);
505
+ });
506
+
507
+ test('kills child process immediately when signal is pre-aborted', async () => {
508
+ const { getTool } = await import('../tools/registry.js');
509
+ await import('../tools/terminal/shell.js');
510
+
511
+ expect(getTool('bash')).toBeDefined();
512
+ const bashTool = getTool('bash')!;
513
+
514
+ const dir = makeTempDir();
515
+ const ac = new AbortController();
516
+ ac.abort();
517
+
518
+ const result = await bashTool.execute(
519
+ { command: 'sleep 30', reason: 'test' },
520
+ makeToolContext(dir, ac.signal),
521
+ );
522
+
523
+ expect(result.isError).toBe(true);
524
+ });
525
+
526
+ test('short command completes normally with a non-aborted signal attached', async () => {
527
+ const { getTool } = await import('../tools/registry.js');
528
+ await import('../tools/terminal/shell.js');
529
+
530
+ expect(getTool('bash')).toBeDefined();
531
+ const bashTool = getTool('bash')!;
532
+
533
+ const dir = makeTempDir();
534
+ const ac = new AbortController();
535
+
536
+ const result = await bashTool.execute(
537
+ { command: 'echo hello-world', reason: 'test' },
538
+ makeToolContext(dir, ac.signal),
539
+ );
540
+
541
+ expect(result.isError).toBe(false);
542
+ expect(result.content).toContain('hello-world');
543
+ ac.abort(); // cleanup after success
544
+ });
545
+ });