@vellumai/assistant 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -0,0 +1,222 @@
1
+ import { describe, test, expect, mock } from 'bun:test';
2
+
3
+ let mockConfig: Record<string, unknown> = {};
4
+
5
+ mock.module('../config/loader.js', () => ({
6
+ loadConfig: () => mockConfig,
7
+ }));
8
+
9
+ import { buildElevenLabsVoiceSpec, resolveVoiceQualityProfile, isVoiceProfileValid } from '../calls/voice-quality.js';
10
+
11
+ describe('buildElevenLabsVoiceSpec', () => {
12
+ test('returns bare voiceId when no model is set', () => {
13
+ expect(buildElevenLabsVoiceSpec({ voiceId: 'abc123' })).toBe('abc123');
14
+ });
15
+
16
+ test('returns empty string when voiceId is empty', () => {
17
+ expect(buildElevenLabsVoiceSpec({ voiceId: '' })).toBe('');
18
+ });
19
+
20
+ test('returns empty string when voiceId is whitespace', () => {
21
+ expect(buildElevenLabsVoiceSpec({ voiceId: ' ' })).toBe('');
22
+ });
23
+
24
+ test('returns bare voiceId when voiceModelId is empty', () => {
25
+ expect(buildElevenLabsVoiceSpec({ voiceId: 'abc123', voiceModelId: '' })).toBe('abc123');
26
+ });
27
+
28
+ test('returns bare voiceId when voiceModelId is whitespace', () => {
29
+ expect(buildElevenLabsVoiceSpec({ voiceId: 'abc123', voiceModelId: ' ' })).toBe('abc123');
30
+ });
31
+
32
+ test('appends model and defaults when voiceModelId is provided', () => {
33
+ const result = buildElevenLabsVoiceSpec({ voiceId: 'abc123', voiceModelId: 'eleven_turbo_v2' });
34
+ expect(result).toBe('abc123-eleven_turbo_v2-1_0.5_0.75');
35
+ });
36
+
37
+ test('uses custom speed, stability, and similarity values', () => {
38
+ const result = buildElevenLabsVoiceSpec({
39
+ voiceId: 'voice1',
40
+ voiceModelId: 'model1',
41
+ speed: 1.5,
42
+ stability: 0.8,
43
+ similarityBoost: 0.9,
44
+ });
45
+ expect(result).toBe('voice1-model1-1.5_0.8_0.9');
46
+ });
47
+
48
+ test('trims whitespace from voiceId', () => {
49
+ expect(buildElevenLabsVoiceSpec({ voiceId: ' abc123 ' })).toBe('abc123');
50
+ });
51
+ });
52
+
53
+ describe('resolveVoiceQualityProfile', () => {
54
+ test('returns standard profile for twilio_standard mode', () => {
55
+ mockConfig = {
56
+ calls: {
57
+ voice: {
58
+ mode: 'twilio_standard',
59
+ language: 'en-US',
60
+ transcriptionProvider: 'Google',
61
+ fallbackToStandardOnError: false,
62
+ elevenlabs: {},
63
+ },
64
+ },
65
+ };
66
+ const profile = resolveVoiceQualityProfile();
67
+ expect(profile.mode).toBe('twilio_standard');
68
+ expect(profile.ttsProvider).toBe('Google');
69
+ expect(profile.voice).toBe('Google.en-US-Journey-O');
70
+ expect(profile.validationErrors).toHaveLength(0);
71
+ });
72
+
73
+ test('returns elevenlabs profile for twilio_elevenlabs_tts mode', () => {
74
+ mockConfig = {
75
+ calls: {
76
+ voice: {
77
+ mode: 'twilio_elevenlabs_tts',
78
+ language: 'en-US',
79
+ transcriptionProvider: 'Google',
80
+ fallbackToStandardOnError: false,
81
+ elevenlabs: { voiceId: 'elvoice1' },
82
+ },
83
+ },
84
+ };
85
+ const profile = resolveVoiceQualityProfile();
86
+ expect(profile.mode).toBe('twilio_elevenlabs_tts');
87
+ expect(profile.ttsProvider).toBe('ElevenLabs');
88
+ expect(profile.voice).toBe('elvoice1');
89
+ expect(profile.validationErrors).toHaveLength(0);
90
+ });
91
+
92
+ test('falls back to standard when voiceId missing and fallback enabled', () => {
93
+ mockConfig = {
94
+ calls: {
95
+ voice: {
96
+ mode: 'twilio_elevenlabs_tts',
97
+ language: 'en-US',
98
+ transcriptionProvider: 'Google',
99
+ fallbackToStandardOnError: true,
100
+ elevenlabs: { voiceId: '' },
101
+ },
102
+ },
103
+ };
104
+ const profile = resolveVoiceQualityProfile();
105
+ expect(profile.mode).toBe('twilio_standard');
106
+ expect(profile.validationErrors.length).toBeGreaterThan(0);
107
+ expect(profile.validationErrors[0]).toContain('falling back');
108
+ });
109
+
110
+ test('returns validation error when voiceId missing and fallback disabled', () => {
111
+ mockConfig = {
112
+ calls: {
113
+ voice: {
114
+ mode: 'twilio_elevenlabs_tts',
115
+ language: 'en-US',
116
+ transcriptionProvider: 'Google',
117
+ fallbackToStandardOnError: false,
118
+ elevenlabs: { voiceId: '' },
119
+ },
120
+ },
121
+ };
122
+ const profile = resolveVoiceQualityProfile();
123
+ expect(profile.mode).toBe('twilio_elevenlabs_tts');
124
+ expect(profile.validationErrors.length).toBeGreaterThan(0);
125
+ expect(profile.validationErrors[0]).toContain('voiceId is required');
126
+ });
127
+
128
+ test('returns elevenlabs_agent profile with agentId', () => {
129
+ mockConfig = {
130
+ calls: {
131
+ voice: {
132
+ mode: 'elevenlabs_agent',
133
+ language: 'en-US',
134
+ transcriptionProvider: 'Google',
135
+ fallbackToStandardOnError: false,
136
+ elevenlabs: { voiceId: 'voice1', agentId: 'agent123' },
137
+ },
138
+ },
139
+ };
140
+ const profile = resolveVoiceQualityProfile();
141
+ expect(profile.mode).toBe('elevenlabs_agent');
142
+ expect(profile.agentId).toBe('agent123');
143
+ expect(profile.validationErrors).toHaveLength(0);
144
+ });
145
+
146
+ test('falls back to standard when agentId missing and fallback enabled', () => {
147
+ mockConfig = {
148
+ calls: {
149
+ voice: {
150
+ mode: 'elevenlabs_agent',
151
+ language: 'en-US',
152
+ transcriptionProvider: 'Google',
153
+ fallbackToStandardOnError: true,
154
+ elevenlabs: { voiceId: 'voice1', agentId: '' },
155
+ },
156
+ },
157
+ };
158
+ const profile = resolveVoiceQualityProfile();
159
+ expect(profile.mode).toBe('twilio_standard');
160
+ expect(profile.validationErrors[0]).toContain('falling back');
161
+ });
162
+
163
+ test('returns validation error when agentId missing and fallback disabled', () => {
164
+ mockConfig = {
165
+ calls: {
166
+ voice: {
167
+ mode: 'elevenlabs_agent',
168
+ language: 'en-US',
169
+ transcriptionProvider: 'Google',
170
+ fallbackToStandardOnError: false,
171
+ elevenlabs: { voiceId: 'voice1', agentId: '' },
172
+ },
173
+ },
174
+ };
175
+ const profile = resolveVoiceQualityProfile();
176
+ expect(profile.mode).toBe('elevenlabs_agent');
177
+ expect(profile.validationErrors.length).toBeGreaterThan(0);
178
+ expect(profile.validationErrors[0]).toContain('agentId is required');
179
+ });
180
+
181
+ test('returns standard profile for unknown mode', () => {
182
+ mockConfig = {
183
+ calls: {
184
+ voice: {
185
+ mode: 'unknown_mode',
186
+ language: 'en-US',
187
+ transcriptionProvider: 'Google',
188
+ fallbackToStandardOnError: false,
189
+ elevenlabs: {},
190
+ },
191
+ },
192
+ };
193
+ const profile = resolveVoiceQualityProfile();
194
+ expect(profile.mode).toBe('twilio_standard');
195
+ });
196
+ });
197
+
198
+ describe('isVoiceProfileValid', () => {
199
+ test('returns true for profile with no errors', () => {
200
+ expect(isVoiceProfileValid({
201
+ mode: 'twilio_standard',
202
+ language: 'en-US',
203
+ transcriptionProvider: 'Google',
204
+ ttsProvider: 'Google',
205
+ voice: 'Google.en-US-Journey-O',
206
+ fallbackToStandardOnError: false,
207
+ validationErrors: [],
208
+ })).toBe(true);
209
+ });
210
+
211
+ test('returns false for profile with errors', () => {
212
+ expect(isVoiceProfileValid({
213
+ mode: 'twilio_elevenlabs_tts',
214
+ language: 'en-US',
215
+ transcriptionProvider: 'Google',
216
+ ttsProvider: 'ElevenLabs',
217
+ voice: '',
218
+ fallbackToStandardOnError: false,
219
+ validationErrors: ['voiceId is required'],
220
+ })).toBe(false);
221
+ });
222
+ });
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
1
  import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
3
2
 
4
3
  // No mock.module calls — this test file uses its own inline executeWebSearch
@@ -45,7 +44,7 @@ describe('WebSearchTool', () => {
45
44
  globalThis.fetch = (async () => {
46
45
  fetchCalled = true;
47
46
  return new Response('{}', { status: 200 });
48
- }) as any;
47
+ }) as unknown as typeof fetch;
49
48
 
50
49
  process.env.BRAVE_API_KEY = 'test-key';
51
50
 
@@ -74,7 +73,7 @@ describe('WebSearchTool', () => {
74
73
  status: 200,
75
74
  headers: { 'Content-Type': 'application/json' },
76
75
  });
77
- }) as any;
76
+ }) as unknown as typeof fetch;
78
77
 
79
78
  await executeWebSearch({ query: 'test', count: 50 }, 'test-key', 'brave');
80
79
  expect(capturedUrl).toContain('count=20');
@@ -91,7 +90,7 @@ describe('WebSearchTool', () => {
91
90
  status: 200,
92
91
  headers: { 'Content-Type': 'application/json' },
93
92
  });
94
- }) as any;
93
+ }) as unknown as typeof fetch;
95
94
 
96
95
  await executeWebSearch({ query: 'test', offset: 20 }, 'test-key', 'brave');
97
96
  expect(capturedUrl).toContain('offset=9');
@@ -105,7 +104,7 @@ describe('WebSearchTool', () => {
105
104
  status: 200,
106
105
  headers: { 'Content-Type': 'application/json' },
107
106
  });
108
- }) as any;
107
+ }) as unknown as typeof fetch;
109
108
 
110
109
  await executeWebSearch({ query: 'test', freshness: 'pw' }, 'test-key', 'brave');
111
110
  expect(capturedUrl).toContain('freshness=pw');
@@ -119,7 +118,7 @@ describe('WebSearchTool', () => {
119
118
  status: 200,
120
119
  headers: { 'Content-Type': 'application/json' },
121
120
  });
122
- }) as any;
121
+ }) as unknown as typeof fetch;
123
122
 
124
123
  await executeWebSearch({ query: 'test', freshness: 'invalid' }, 'test-key', 'brave');
125
124
  expect(capturedUrl).not.toContain('freshness');
@@ -142,7 +141,7 @@ describe('WebSearchTool', () => {
142
141
  status: 200,
143
142
  headers: { 'Content-Type': 'application/json' },
144
143
  })
145
- ) as any;
144
+ ) as unknown as typeof fetch;
146
145
 
147
146
  const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
148
147
  expect(result.isError).toBe(false);
@@ -159,7 +158,7 @@ describe('WebSearchTool', () => {
159
158
  status: 200,
160
159
  headers: { 'Content-Type': 'application/json' },
161
160
  })
162
- ) as any;
161
+ ) as unknown as typeof fetch;
163
162
 
164
163
  const result = await executeWebSearch({ query: 'noresults' }, 'test-key', 'brave');
165
164
  expect(result.isError).toBe(false);
@@ -172,7 +171,7 @@ describe('WebSearchTool', () => {
172
171
  status: 200,
173
172
  headers: { 'Content-Type': 'application/json' },
174
173
  })
175
- ) as any;
174
+ ) as unknown as typeof fetch;
176
175
 
177
176
  const result = await executeWebSearch({ query: 'empty' }, 'test-key', 'brave');
178
177
  expect(result.isError).toBe(false);
@@ -182,7 +181,7 @@ describe('WebSearchTool', () => {
182
181
  test('handles 401 unauthorized', async () => {
183
182
  globalThis.fetch = (async () =>
184
183
  new Response('Unauthorized', { status: 401 })
185
- ) as any;
184
+ ) as unknown as typeof fetch;
186
185
 
187
186
  const result = await executeWebSearch({ query: 'test' }, 'bad-key', 'brave');
188
187
  expect(result.isError).toBe(true);
@@ -200,7 +199,7 @@ describe('WebSearchTool', () => {
200
199
  JSON.stringify({ web: { results: [{ title: 'Result', url: 'https://example.com', description: 'Found it' }] } }),
201
200
  { status: 200, headers: { 'Content-Type': 'application/json' } },
202
201
  );
203
- }) as any;
202
+ }) as unknown as typeof fetch;
204
203
 
205
204
  const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
206
205
  expect(result.isError).toBe(false);
@@ -211,7 +210,7 @@ describe('WebSearchTool', () => {
211
210
  test('returns error after exhausting 429 retries', async () => {
212
211
  globalThis.fetch = (async () =>
213
212
  new Response('Too Many Requests', { status: 429 })
214
- ) as any;
213
+ ) as unknown as typeof fetch;
215
214
 
216
215
  const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
217
216
  expect(result.isError).toBe(true);
@@ -235,7 +234,7 @@ describe('WebSearchTool', () => {
235
234
  JSON.stringify({ web: { results: [] } }),
236
235
  { status: 200, headers: { 'Content-Type': 'application/json' } },
237
236
  );
238
- }) as any;
237
+ }) as unknown as typeof fetch;
239
238
 
240
239
  const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
241
240
  expect(result.isError).toBe(false);
@@ -245,7 +244,7 @@ describe('WebSearchTool', () => {
245
244
  test('handles network errors', async () => {
246
245
  globalThis.fetch = (async () => {
247
246
  throw new Error('Network unreachable');
248
- }) as any;
247
+ }) as unknown as typeof fetch;
249
248
 
250
249
  const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
251
250
  expect(result.isError).toBe(true);
@@ -254,7 +253,7 @@ describe('WebSearchTool', () => {
254
253
 
255
254
  test('sends correct headers', async () => {
256
255
  let capturedHeaders: Record<string, string> = {};
257
- globalThis.fetch = (async (_url: string, init: any) => {
256
+ globalThis.fetch = (async (_url: string, init: RequestInit) => {
258
257
  capturedHeaders = Object.fromEntries(
259
258
  Object.entries(init.headers as Record<string, string>),
260
259
  );
@@ -262,7 +261,7 @@ describe('WebSearchTool', () => {
262
261
  status: 200,
263
262
  headers: { 'Content-Type': 'application/json' },
264
263
  });
265
- }) as any;
264
+ }) as unknown as typeof fetch;
266
265
 
267
266
  await executeWebSearch({ query: 'test' }, 'my-api-key', 'brave');
268
267
  expect(capturedHeaders['X-Subscription-Token']).toBe('my-api-key');
@@ -288,7 +287,7 @@ describe('WebSearchTool', () => {
288
287
  status: 200,
289
288
  headers: { 'Content-Type': 'application/json' },
290
289
  })
291
- ) as any;
290
+ ) as unknown as typeof fetch;
292
291
 
293
292
  const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
294
293
  expect(result.isError).toBe(false);
@@ -309,7 +308,7 @@ describe('WebSearchTool', () => {
309
308
  status: 200,
310
309
  headers: { 'Content-Type': 'application/json' },
311
310
  })
312
- ) as any;
311
+ ) as unknown as typeof fetch;
313
312
 
314
313
  const result = await executeWebSearch({ query: 'test' }, 'pplx-key', 'perplexity');
315
314
  expect(result.isError).toBe(false);
@@ -328,7 +327,7 @@ describe('WebSearchTool', () => {
328
327
  status: 200,
329
328
  headers: { 'Content-Type': 'application/json' },
330
329
  })
331
- ) as any;
330
+ ) as unknown as typeof fetch;
332
331
 
333
332
  const result = await executeWebSearch({ query: 'noresults' }, 'pplx-key', 'perplexity');
334
333
  expect(result.isError).toBe(false);
@@ -338,7 +337,7 @@ describe('WebSearchTool', () => {
338
337
  test('handles 401 unauthorized', async () => {
339
338
  globalThis.fetch = (async () =>
340
339
  new Response('Unauthorized', { status: 401 })
341
- ) as any;
340
+ ) as unknown as typeof fetch;
342
341
 
343
342
  const result = await executeWebSearch({ query: 'test' }, 'bad-key', 'perplexity');
344
343
  expect(result.isError).toBe(true);
@@ -347,23 +346,23 @@ describe('WebSearchTool', () => {
347
346
 
348
347
  test('sends correct headers', async () => {
349
348
  let capturedHeaders: Record<string, string> = {};
350
- let capturedBody: any;
351
- globalThis.fetch = (async (_url: string, init: any) => {
349
+ let capturedBody: Record<string, unknown> = {};
350
+ globalThis.fetch = (async (_url: string, init: RequestInit) => {
352
351
  capturedHeaders = Object.fromEntries(
353
352
  Object.entries(init.headers as Record<string, string>),
354
353
  );
355
- capturedBody = JSON.parse(init.body);
354
+ capturedBody = JSON.parse(init.body as string);
356
355
  return new Response(JSON.stringify({ choices: [{ message: { content: 'result' } }] }), {
357
356
  status: 200,
358
357
  headers: { 'Content-Type': 'application/json' },
359
358
  });
360
- }) as any;
359
+ }) as unknown as typeof fetch;
361
360
 
362
361
  await executeWebSearch({ query: 'test query' }, 'pplx-my-key', 'perplexity');
363
362
  expect(capturedHeaders['Authorization']).toBe('Bearer pplx-my-key');
364
363
  expect(capturedHeaders['Content-Type']).toBe('application/json');
365
364
  expect(capturedBody.model).toBe('sonar');
366
- expect(capturedBody.messages[0].content).toBe('test query');
365
+ expect((capturedBody.messages as Array<{ content: string }>)[0].content).toBe('test query');
367
366
  });
368
367
 
369
368
  test('retries on 429 and succeeds', async () => {
@@ -377,7 +376,7 @@ describe('WebSearchTool', () => {
377
376
  JSON.stringify({ choices: [{ message: { content: 'Found it' } }] }),
378
377
  { status: 200, headers: { 'Content-Type': 'application/json' } },
379
378
  );
380
- }) as any;
379
+ }) as unknown as typeof fetch;
381
380
 
382
381
  const result = await executeWebSearch({ query: 'test' }, 'pplx-key', 'perplexity');
383
382
  expect(result.isError).toBe(false);
@@ -388,7 +387,7 @@ describe('WebSearchTool', () => {
388
387
  test('handles network errors', async () => {
389
388
  globalThis.fetch = (async () => {
390
389
  throw new Error('Network unreachable');
391
- }) as any;
390
+ }) as unknown as typeof fetch;
392
391
 
393
392
  const result = await executeWebSearch({ query: 'test' }, 'pplx-key', 'perplexity');
394
393
  expect(result.isError).toBe(true);
@@ -397,6 +396,23 @@ describe('WebSearchTool', () => {
397
396
  });
398
397
  });
399
398
 
399
+ interface BraveSearchResult {
400
+ title: string;
401
+ url: string;
402
+ description?: string;
403
+ age?: string;
404
+ extra_snippets?: string[];
405
+ }
406
+
407
+ interface BraveSearchResponse {
408
+ web?: { results: BraveSearchResult[] };
409
+ }
410
+
411
+ interface PerplexityResponse {
412
+ choices?: Array<{ message: { content: string } }>;
413
+ citations?: string[];
414
+ }
415
+
400
416
  /**
401
417
  * Helper that exercises the web search logic directly, bypassing module
402
418
  * registration concerns. This replicates the core execute path from
@@ -414,7 +430,7 @@ async function executeWebSearch(
414
430
 
415
431
  if (!apiKey) {
416
432
  return {
417
- content: 'Error: No web search API key configured. Set PERPLEXITY_API_KEY or BRAVE_API_KEY environment variable, or configure a key in settings.',
433
+ content: 'Error: No web search API key configured. Provide a PERPLEXITY_API_KEY or BRAVE_API_KEY here in the chat using the secure credential prompt, or set it from the Settings page.',
418
434
  isError: true,
419
435
  };
420
436
  }
@@ -461,7 +477,7 @@ async function executeBraveSearchHelper(
461
477
  });
462
478
 
463
479
  if (response.ok) {
464
- const data = await response.json() as any;
480
+ const data = await response.json() as BraveSearchResponse;
465
481
  const results = data.web?.results ?? [];
466
482
 
467
483
  if (results.length === 0) {
@@ -536,7 +552,7 @@ async function executePerplexitySearchHelper(
536
552
  });
537
553
 
538
554
  if (response.ok) {
539
- const data = await response.json() as any;
555
+ const data = await response.json() as PerplexityResponse;
540
556
  const content = data.choices?.[0]?.message?.content;
541
557
  if (!content) {
542
558
  return { content: `No results found for "${query}".`, isError: false };
@@ -0,0 +1,110 @@
1
+ import { afterAll, beforeEach, describe, expect, mock, test } from 'bun:test';
2
+ import { mkdtempSync, rmSync } from 'node:fs';
3
+ import type { Database } from 'bun:sqlite';
4
+ import * as net from 'node:net';
5
+ import { join } from 'node:path';
6
+ import { tmpdir } from 'node:os';
7
+
8
+ const testDir = mkdtempSync(join(tmpdir(), 'work-item-output-test-'));
9
+
10
+ mock.module('../util/platform.js', () => ({
11
+ getDataDir: () => testDir,
12
+ isMacOS: () => process.platform === 'darwin',
13
+ isLinux: () => process.platform === 'linux',
14
+ isWindows: () => process.platform === 'win32',
15
+ getSocketPath: () => join(testDir, 'test.sock'),
16
+ getPidPath: () => join(testDir, 'test.pid'),
17
+ getDbPath: () => join(testDir, 'test.db'),
18
+ getLogPath: () => join(testDir, 'test.log'),
19
+ ensureDataDir: () => {},
20
+ migrateToDataLayout: () => {},
21
+ migrateToWorkspaceLayout: () => {},
22
+ }));
23
+
24
+ mock.module('../util/logger.js', () => ({
25
+ getLogger: () => new Proxy({} as Record<string, unknown>, {
26
+ get: () => () => {},
27
+ }),
28
+ }));
29
+
30
+ mock.module('../config/loader.js', () => ({
31
+ getConfig: () => ({ memory: {} }),
32
+ }));
33
+
34
+ mock.module('./indexer.js', () => ({
35
+ indexMessageNow: () => {},
36
+ }));
37
+
38
+ import { addMessage, createConversation } from '../memory/conversation-store.js';
39
+ import { getDb, initializeDb, resetDb } from '../memory/db.js';
40
+ import { createTask, createTaskRun, updateTaskRun } from '../tasks/task-store.js';
41
+ import { createWorkItem, updateWorkItem } from '../work-items/work-item-store.js';
42
+ import { handleWorkItemOutput } from '../daemon/handlers/work-items.js';
43
+ import type { HandlerContext } from '../daemon/handlers/shared.js';
44
+
45
+ initializeDb();
46
+
47
+ afterAll(() => {
48
+ resetDb();
49
+ try { rmSync(testDir, { recursive: true, force: true }); } catch { /* best effort */ }
50
+ });
51
+
52
+ function getRawDb(): Database {
53
+ return (getDb() as unknown as { $client: Database }).$client;
54
+ }
55
+
56
+ describe('handleWorkItemOutput', () => {
57
+ beforeEach(() => {
58
+ const raw = getRawDb();
59
+ raw.run('DELETE FROM task_runs');
60
+ raw.run('DELETE FROM work_items');
61
+ raw.run('DELETE FROM tasks');
62
+ raw.run('DELETE FROM messages');
63
+ raw.run('DELETE FROM conversations');
64
+ });
65
+
66
+ test('uses only the latest assistant text block for summary output', () => {
67
+ const task = createTask({
68
+ title: 'Delete weather report',
69
+ template: 'Delete weather_report_task.txt',
70
+ });
71
+ const run = createTaskRun(task.id);
72
+ const item = createWorkItem({
73
+ taskId: task.id,
74
+ title: 'Delete weather_report_task.txt',
75
+ });
76
+ const conversation = createConversation('Task output test');
77
+
78
+ updateTaskRun(run.id, {
79
+ status: 'completed',
80
+ conversationId: conversation.id,
81
+ finishedAt: Date.now(),
82
+ });
83
+ updateWorkItem(item.id, {
84
+ status: 'awaiting_review',
85
+ lastRunId: run.id,
86
+ lastRunConversationId: conversation.id,
87
+ lastRunStatus: 'completed',
88
+ });
89
+
90
+ addMessage(conversation.id, 'assistant', JSON.stringify([
91
+ { type: 'text', text: "I'll need to delete the weather report file from your Documents folder. This will permanently remove it." },
92
+ { type: 'text', text: "Looks like that file has already been deleted — it's no longer there. I'll mark this task as done." },
93
+ { type: 'text', text: 'The file is already deleted, so the task is complete.' },
94
+ ]));
95
+
96
+ const sent: Array<{ type: string; [key: string]: unknown }> = [];
97
+ const socket = {} as net.Socket;
98
+ const ctx = {
99
+ send: (_socket: net.Socket, msg: { type: string; [key: string]: unknown }) => sent.push(msg),
100
+ } as unknown as HandlerContext;
101
+
102
+ handleWorkItemOutput({ type: 'work_item_output', id: item.id }, socket, ctx);
103
+
104
+ expect(sent).toHaveLength(1);
105
+ const response = sent[0];
106
+ expect(response.type).toBe('work_item_output_response');
107
+ expect(response.success).toBe(true);
108
+ expect((response.output as { summary: string }).summary).toBe('The file is already deleted, so the task is complete.');
109
+ });
110
+ });
package/src/agent/loop.ts CHANGED
@@ -35,7 +35,7 @@ export type AgentEvent =
35
35
  | { type: 'usage'; inputTokens: number; outputTokens: number; cacheCreationInputTokens?: number; cacheReadInputTokens?: number; model: string; providerDurationMs: number; rawRequest?: unknown; rawResponse?: unknown };
36
36
 
37
37
  const DEFAULT_CONFIG: AgentLoopConfig = {
38
- maxTokens: 64000,
38
+ maxTokens: 16000,
39
39
  maxToolUseTurns: 30,
40
40
  };
41
41
 
@@ -1,4 +1,4 @@
1
- import { readFileSync, existsSync } from 'node:fs';
1
+ import { readTextFileSync } from '../util/fs.js';
2
2
  import { getLogger } from '../util/logger.js';
3
3
  import { getWorkspacePromptPath } from '../util/platform.js';
4
4
  import { getConfig } from '../config/loader.js';
@@ -115,15 +115,7 @@ export class AgentHeartbeatService {
115
115
  }
116
116
 
117
117
  private readChecklist(): string {
118
- const heartbeatPath = getWorkspacePromptPath('HEARTBEAT.md');
119
- if (existsSync(heartbeatPath)) {
120
- try {
121
- return readFileSync(heartbeatPath, 'utf-8');
122
- } catch (err) {
123
- log.warn({ err, heartbeatPath }, 'Failed to read HEARTBEAT.md, using default checklist');
124
- }
125
- }
126
- return DEFAULT_CHECKLIST;
118
+ return readTextFileSync(getWorkspacePromptPath('HEARTBEAT.md')) ?? DEFAULT_CHECKLIST;
127
119
  }
128
120
 
129
121
  /** @internal Exposed for testing. */