vellum 0.2.1 → 0.2.2

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 (349) hide show
  1. package/README.md +15 -2
  2. package/bun.lock +5 -2
  3. package/package.json +4 -2
  4. package/scripts/capture-x-graphql.ts +562 -0
  5. package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
  6. package/scripts/test.sh +5 -0
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +133 -34
  8. package/src/__tests__/account-registry.test.ts +2 -1
  9. package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
  10. package/src/__tests__/asset-materialize-tool.test.ts +16 -15
  11. package/src/__tests__/asset-search-tool.test.ts +23 -22
  12. package/src/__tests__/attachments-store.test.ts +56 -127
  13. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
  14. package/src/__tests__/browser-skill-endstate.test.ts +4 -3
  15. package/src/__tests__/call-bridge.test.ts +385 -0
  16. package/src/__tests__/call-constants.test.ts +40 -0
  17. package/src/__tests__/call-orchestrator.test.ts +130 -4
  18. package/src/__tests__/call-recovery.test.ts +518 -0
  19. package/src/__tests__/call-routes-http.test.ts +459 -0
  20. package/src/__tests__/call-state-machine.test.ts +143 -0
  21. package/src/__tests__/call-store.test.ts +216 -1
  22. package/src/__tests__/cli-discover.test.ts +1 -1
  23. package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
  24. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  25. package/src/__tests__/computer-use-tools.test.ts +250 -0
  26. package/src/__tests__/config-schema.test.ts +299 -3
  27. package/src/__tests__/conflict-store.test.ts +2 -1
  28. package/src/__tests__/contacts-tools.test.ts +331 -0
  29. package/src/__tests__/conversation-store.test.ts +30 -32
  30. package/src/__tests__/credential-security-invariants.test.ts +4 -0
  31. package/src/__tests__/date-context.test.ts +373 -0
  32. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  33. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
  34. package/src/__tests__/followup-tools.test.ts +303 -0
  35. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  36. package/src/__tests__/intent-routing.test.ts +64 -57
  37. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +62 -28
  39. package/src/__tests__/llm-usage-store.test.ts +3 -8
  40. package/src/__tests__/media-generate-image.test.ts +1 -1
  41. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  42. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  43. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  44. package/src/__tests__/playbook-tools.test.ts +342 -0
  45. package/src/__tests__/profile-compiler.test.ts +2 -1
  46. package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
  47. package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
  48. package/src/__tests__/recurrence-engine.test.ts +69 -0
  49. package/src/__tests__/recurrence-types.test.ts +71 -0
  50. package/src/__tests__/registry.test.ts +5 -3
  51. package/src/__tests__/relay-server.test.ts +633 -0
  52. package/src/__tests__/reminder-store.test.ts +6 -3
  53. package/src/__tests__/reminder.test.ts +43 -77
  54. package/src/__tests__/run-orchestrator-assistant-events.test.ts +8 -4
  55. package/src/__tests__/run-orchestrator.test.ts +4 -4
  56. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
  57. package/src/__tests__/runtime-runs-http.test.ts +4 -4
  58. package/src/__tests__/runtime-runs.test.ts +4 -4
  59. package/src/__tests__/schedule-store.test.ts +482 -0
  60. package/src/__tests__/schedule-tools.test.ts +700 -0
  61. package/src/__tests__/scheduler-recurrence.test.ts +329 -0
  62. package/src/__tests__/server-history-render.test.ts +14 -13
  63. package/src/__tests__/session-error.test.ts +28 -0
  64. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  65. package/src/__tests__/session-queue.test.ts +71 -48
  66. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  67. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  68. package/src/__tests__/signup-e2e.test.ts +2 -1
  69. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  70. package/src/__tests__/skill-script-runner.test.ts +159 -0
  71. package/src/__tests__/speaker-identification.test.ts +52 -0
  72. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  73. package/src/__tests__/subagent-tools.test.ts +141 -41
  74. package/src/__tests__/task-compiler.test.ts +2 -1
  75. package/src/__tests__/task-runner.test.ts +2 -1
  76. package/src/__tests__/task-scheduler.test.ts +2 -1
  77. package/src/__tests__/task-tools.test.ts +49 -56
  78. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  79. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  80. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  81. package/src/__tests__/tool-executor.test.ts +13 -17
  82. package/src/__tests__/turn-commit.test.ts +218 -3
  83. package/src/__tests__/twilio-provider.test.ts +143 -0
  84. package/src/__tests__/twilio-routes.test.ts +789 -0
  85. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  86. package/src/__tests__/view-image-tool.test.ts +217 -0
  87. package/src/__tests__/workspace-git-service.test.ts +186 -0
  88. package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
  89. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  90. package/src/bundler/app-bundler.ts +12 -8
  91. package/src/calls/call-bridge.ts +95 -0
  92. package/src/calls/call-constants.ts +43 -5
  93. package/src/calls/call-domain.ts +276 -0
  94. package/src/calls/call-orchestrator.ts +43 -17
  95. package/src/calls/call-recovery.ts +207 -0
  96. package/src/calls/call-state-machine.ts +68 -0
  97. package/src/calls/call-store.ts +192 -5
  98. package/src/calls/relay-server.ts +41 -4
  99. package/src/calls/speaker-identification.ts +213 -0
  100. package/src/calls/twilio-provider.ts +10 -6
  101. package/src/calls/twilio-routes.ts +90 -76
  102. package/src/calls/types.ts +1 -1
  103. package/src/cli/config-commands.ts +334 -0
  104. package/src/cli/core-commands.ts +776 -0
  105. package/src/cli/doordash.ts +251 -1
  106. package/src/cli/ipc-client.ts +82 -0
  107. package/src/cli/map.ts +246 -0
  108. package/src/cli/twitter.ts +575 -0
  109. package/src/cli.ts +7 -5
  110. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  111. package/src/commands/cc-command-registry.ts +209 -0
  112. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  113. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  114. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  115. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  116. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  117. package/src/config/bundled-skills/document/SKILL.md +18 -0
  118. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  119. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  120. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  121. package/src/config/bundled-skills/doordash/SKILL.md +82 -23
  122. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  123. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  124. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  125. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  126. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  127. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
  128. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
  129. package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
  130. package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
  131. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
  132. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
  133. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
  134. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
  135. package/src/config/bundled-skills/reminder/SKILL.md +20 -0
  136. package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
  137. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
  138. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
  139. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +74 -0
  141. package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
  142. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
  143. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
  144. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
  145. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
  146. package/src/config/bundled-skills/subagent/SKILL.md +25 -0
  147. package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
  148. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
  149. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
  150. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
  151. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
  152. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
  153. package/src/config/bundled-skills/tasks/SKILL.md +28 -0
  154. package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
  155. package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
  156. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
  157. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
  158. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
  159. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
  160. package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
  161. package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
  162. package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
  163. package/src/config/bundled-skills/twitter/SKILL.md +134 -0
  164. package/src/config/bundled-skills/watcher/SKILL.md +27 -0
  165. package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
  166. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
  167. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
  168. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
  169. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
  170. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
  171. package/src/config/defaults.ts +33 -0
  172. package/src/config/loader.ts +4 -1
  173. package/src/config/schema.ts +161 -1
  174. package/src/config/system-prompt.ts +61 -16
  175. package/src/config/templates/IDENTITY.md +7 -0
  176. package/src/config/types.ts +4 -0
  177. package/src/contacts/contact-store.ts +4 -4
  178. package/src/daemon/assistant-attachments.ts +10 -0
  179. package/src/daemon/classifier.ts +3 -1
  180. package/src/daemon/computer-use-session.ts +3 -1
  181. package/src/daemon/date-context.ts +136 -0
  182. package/src/daemon/handlers/apps.ts +16 -1
  183. package/src/daemon/handlers/browser.ts +54 -0
  184. package/src/daemon/handlers/computer-use.ts +7 -1
  185. package/src/daemon/handlers/config.ts +163 -5
  186. package/src/daemon/handlers/diagnostics.ts +5 -1
  187. package/src/daemon/handlers/documents.ts +18 -29
  188. package/src/daemon/handlers/home-base.ts +5 -1
  189. package/src/daemon/handlers/index.ts +40 -277
  190. package/src/daemon/handlers/misc.ts +9 -1
  191. package/src/daemon/handlers/publish.ts +6 -1
  192. package/src/daemon/handlers/sessions.ts +65 -12
  193. package/src/daemon/handlers/shared.ts +36 -1
  194. package/src/daemon/handlers/signing.ts +37 -0
  195. package/src/daemon/handlers/skills.ts +20 -6
  196. package/src/daemon/handlers/subagents.ts +8 -3
  197. package/src/daemon/handlers/twitter-auth.ts +169 -0
  198. package/src/daemon/handlers/work-items.ts +384 -68
  199. package/src/daemon/ipc-contract-inventory.json +28 -4
  200. package/src/daemon/ipc-contract.ts +133 -37
  201. package/src/daemon/ipc-protocol.ts +7 -2
  202. package/src/daemon/lifecycle.ts +21 -0
  203. package/src/daemon/main.ts +10 -4
  204. package/src/daemon/ride-shotgun-handler.ts +74 -10
  205. package/src/daemon/server.ts +143 -26
  206. package/src/daemon/session-agent-loop.ts +887 -0
  207. package/src/daemon/session-attachments.ts +28 -5
  208. package/src/daemon/session-error.ts +24 -3
  209. package/src/daemon/session-lifecycle.ts +147 -0
  210. package/src/daemon/session-media-retry.ts +147 -0
  211. package/src/daemon/session-messaging.ts +145 -0
  212. package/src/daemon/session-notifiers.ts +164 -0
  213. package/src/daemon/session-process.ts +2 -2
  214. package/src/daemon/session-queue-manager.ts +1 -0
  215. package/src/daemon/session-runtime-assembly.ts +52 -0
  216. package/src/daemon/session-skill-tools.ts +124 -5
  217. package/src/daemon/session-slash.ts +3 -0
  218. package/src/daemon/session-surfaces.ts +77 -2
  219. package/src/daemon/session-tool-setup.ts +216 -2
  220. package/src/daemon/session-usage.ts +0 -2
  221. package/src/daemon/session.ts +114 -1404
  222. package/src/daemon/video-thumbnail.ts +60 -0
  223. package/src/doordash/client.ts +121 -27
  224. package/src/doordash/queries.ts +1 -2
  225. package/src/export/formatter.ts +3 -1
  226. package/src/followups/followup-store.ts +4 -2
  227. package/src/followups/types.ts +6 -0
  228. package/src/hooks/templates.ts +1 -1
  229. package/src/index.ts +32 -1153
  230. package/src/memory/attachments-store.ts +28 -83
  231. package/src/memory/channel-delivery-store.ts +7 -21
  232. package/src/memory/clarification-resolver.ts +6 -5
  233. package/src/memory/contradiction-checker.ts +3 -2
  234. package/src/memory/conversation-key-store.ts +10 -29
  235. package/src/memory/conversation-store.ts +2 -1
  236. package/src/memory/db.ts +96 -2
  237. package/src/memory/entity-extractor.ts +6 -3
  238. package/src/memory/items-extractor.ts +5 -4
  239. package/src/memory/jobs-store.ts +3 -2
  240. package/src/memory/llm-usage-store.ts +1 -2
  241. package/src/memory/runs-store.ts +1 -2
  242. package/src/memory/schema.ts +23 -2
  243. package/src/messaging/style-analyzer.ts +3 -2
  244. package/src/messaging/thread-summarizer.ts +8 -12
  245. package/src/messaging/triage-engine.ts +4 -2
  246. package/src/providers/openrouter/client.ts +20 -0
  247. package/src/providers/registry.ts +8 -0
  248. package/src/runtime/http-server.ts +108 -20
  249. package/src/runtime/routes/attachment-routes.ts +2 -3
  250. package/src/runtime/routes/call-routes.ts +140 -0
  251. package/src/runtime/routes/channel-routes.ts +5 -10
  252. package/src/runtime/routes/conversation-routes.ts +5 -5
  253. package/src/runtime/routes/run-routes.ts +2 -2
  254. package/src/runtime/run-orchestrator.ts +9 -3
  255. package/src/schedule/recurrence-engine.ts +138 -0
  256. package/src/schedule/recurrence-types.ts +67 -0
  257. package/src/schedule/schedule-store.ts +102 -57
  258. package/src/schedule/scheduler.ts +9 -6
  259. package/src/security/oauth2.ts +29 -4
  260. package/src/security/secret-allowlist.ts +46 -0
  261. package/src/skills/clawhub.ts +1 -1
  262. package/src/subagent/manager.ts +40 -8
  263. package/src/swarm/backend-claude-code.ts +64 -9
  264. package/src/swarm/worker-prompts.ts +2 -1
  265. package/src/tasks/SPEC.md +34 -28
  266. package/src/tasks/ephemeral-permissions.ts +16 -7
  267. package/src/tasks/task-compiler.ts +5 -4
  268. package/src/tasks/task-runner.ts +10 -5
  269. package/src/tasks/task-scheduler.ts +1 -1
  270. package/src/tasks/tool-sanitizer.ts +36 -0
  271. package/src/tools/assets/search.ts +4 -4
  272. package/src/tools/browser/api-map.ts +220 -0
  273. package/src/tools/browser/auto-navigate.ts +270 -0
  274. package/src/tools/browser/browser-execution.ts +2 -1
  275. package/src/tools/browser/browser-manager.ts +2 -2
  276. package/src/tools/browser/network-recorder.ts +5 -4
  277. package/src/tools/browser/x-auto-navigate.ts +207 -0
  278. package/src/tools/calls/call-end.ts +17 -67
  279. package/src/tools/calls/call-start.ts +24 -85
  280. package/src/tools/calls/call-status.ts +35 -51
  281. package/src/tools/claude-code/claude-code.ts +77 -11
  282. package/src/tools/contacts/contact-merge.ts +46 -78
  283. package/src/tools/contacts/contact-search.ts +35 -79
  284. package/src/tools/contacts/contact-upsert.ts +35 -108
  285. package/src/tools/credentials/vault.ts +20 -4
  286. package/src/tools/document/document-tool.ts +71 -144
  287. package/src/tools/executor.ts +129 -10
  288. package/src/tools/followups/followup_create.ts +46 -88
  289. package/src/tools/followups/followup_list.ts +34 -74
  290. package/src/tools/followups/followup_resolve.ts +31 -66
  291. package/src/tools/host-terminal/cli-discover.ts +2 -1
  292. package/src/tools/host-terminal/host-shell.ts +10 -0
  293. package/src/tools/memory/handlers.ts +5 -4
  294. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  295. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  296. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  297. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  298. package/src/tools/network/web-fetch.ts +18 -6
  299. package/src/tools/playbooks/index.ts +4 -5
  300. package/src/tools/playbooks/playbook-create.ts +3 -47
  301. package/src/tools/playbooks/playbook-delete.ts +1 -25
  302. package/src/tools/playbooks/playbook-list.ts +1 -28
  303. package/src/tools/playbooks/playbook-update.ts +3 -51
  304. package/src/tools/reminder/reminder.ts +5 -78
  305. package/src/tools/schedule/create.ts +69 -74
  306. package/src/tools/schedule/delete.ts +21 -47
  307. package/src/tools/schedule/list.ts +55 -74
  308. package/src/tools/schedule/update.ts +77 -84
  309. package/src/tools/subagent/abort.ts +29 -58
  310. package/src/tools/subagent/message.ts +30 -63
  311. package/src/tools/subagent/read.ts +53 -84
  312. package/src/tools/subagent/spawn.ts +43 -82
  313. package/src/tools/subagent/status.ts +42 -71
  314. package/src/tools/swarm/delegate.ts +2 -1
  315. package/src/tools/tasks/index.ts +8 -8
  316. package/src/tools/tasks/task-delete.ts +60 -88
  317. package/src/tools/tasks/task-list.ts +31 -52
  318. package/src/tools/tasks/task-run.ts +72 -108
  319. package/src/tools/tasks/task-save.ts +33 -65
  320. package/src/tools/tasks/work-item-enqueue.ts +183 -215
  321. package/src/tools/tasks/work-item-list.ts +33 -63
  322. package/src/tools/tasks/work-item-remove.ts +45 -97
  323. package/src/tools/tasks/work-item-update.ts +91 -163
  324. package/src/tools/terminal/backends/native.ts +3 -1
  325. package/src/tools/tool-manifest.ts +0 -62
  326. package/src/tools/types.ts +6 -0
  327. package/src/tools/ui-surface/definitions.ts +3 -1
  328. package/src/tools/watch/screen-watch.ts +3 -1
  329. package/src/tools/watcher/create.ts +52 -98
  330. package/src/tools/watcher/delete.ts +20 -46
  331. package/src/tools/watcher/digest.ts +36 -70
  332. package/src/tools/watcher/list.ts +49 -79
  333. package/src/tools/watcher/update.ts +45 -91
  334. package/src/twitter/client.ts +690 -0
  335. package/src/twitter/session.ts +91 -0
  336. package/src/usage/types.ts +0 -1
  337. package/src/util/truncate.ts +6 -0
  338. package/src/watcher/providers/slack.ts +2 -1
  339. package/src/watcher/watcher-store.ts +3 -2
  340. package/src/work-items/work-item-store.ts +27 -2
  341. package/src/workspace/commit-message-enrichment-service.ts +31 -7
  342. package/src/workspace/git-service.ts +87 -22
  343. package/src/workspace/provider-commit-message-generator.ts +242 -0
  344. package/src/workspace/turn-commit.ts +62 -3
  345. package/src/tools/contacts/index.ts +0 -4
  346. package/src/tools/document/index.ts +0 -5
  347. package/src/tools/followups/index.ts +0 -3
  348. package/src/tools/subagent/index.ts +0 -5
  349. /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
@@ -0,0 +1,207 @@
1
+ /**
2
+ * CDP-based auto-navigation for X.com.
3
+ *
4
+ * Drives Chrome through key X.com pages to trigger GraphQL API calls,
5
+ * so the NetworkRecorder captures the full API surface without manual browsing.
6
+ */
7
+
8
+ import { getLogger } from '../../util/logger.js';
9
+
10
+ const log = getLogger('x-auto-navigate');
11
+
12
+ const CDP_BASE = 'http://localhost:9222';
13
+
14
+ interface NavStep {
15
+ label: string;
16
+ url?: string;
17
+ clickSelector?: string;
18
+ }
19
+
20
+ /** Minimal CDP client — connects to one page tab. */
21
+ class MiniCDP {
22
+ private ws: WebSocket | null = null;
23
+ private nextId = 1;
24
+ private callbacks = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
25
+
26
+ async connect(wsUrl: string): Promise<void> {
27
+ return new Promise((resolve, reject) => {
28
+ const ws = new WebSocket(wsUrl);
29
+ ws.onopen = () => { this.ws = ws; resolve(); };
30
+ ws.onerror = (e) => reject(new Error(`CDP error: ${e}`));
31
+ ws.onclose = () => { this.ws = null; };
32
+ ws.onmessage = (event) => {
33
+ const msg = JSON.parse(String(event.data));
34
+ if (msg.id != null) {
35
+ const cb = this.callbacks.get(msg.id);
36
+ if (cb) {
37
+ this.callbacks.delete(msg.id);
38
+ msg.error ? cb.reject(new Error(msg.error.message)) : cb.resolve(msg.result);
39
+ }
40
+ }
41
+ };
42
+ });
43
+ }
44
+
45
+ async send(method: string, params?: Record<string, unknown>): Promise<unknown> {
46
+ if (!this.ws) throw new Error('Not connected');
47
+ const id = this.nextId++;
48
+ return new Promise((resolve, reject) => {
49
+ this.callbacks.set(id, { resolve, reject });
50
+ this.ws!.send(JSON.stringify({ id, method, params }));
51
+ });
52
+ }
53
+
54
+ close() { this.ws?.close(); }
55
+ }
56
+
57
+ /**
58
+ * Navigate Chrome through X.com pages to trigger GraphQL calls.
59
+ * The NetworkRecorder should already be attached and capturing.
60
+ *
61
+ * @param abortSignal Optional signal to stop navigation early.
62
+ * @returns List of step labels that completed successfully.
63
+ */
64
+ export async function navigateXPages(abortSignal?: { aborted: boolean }): Promise<string[]> {
65
+ let wsUrl: string | null = null;
66
+ try {
67
+ const res = await fetch(`${CDP_BASE}/json/list`);
68
+ if (!res.ok) {
69
+ log.warn('CDP not available for auto-navigation');
70
+ return [];
71
+ }
72
+ const targets = (await res.json()) as Array<{ type: string; url: string; webSocketDebuggerUrl: string }>;
73
+ const xTab = targets.find(
74
+ t => t.type === 'page' && (t.url.includes('x.com') || t.url.includes('twitter.com')),
75
+ );
76
+ wsUrl = xTab?.webSocketDebuggerUrl ?? targets.find(t => t.type === 'page')?.webSocketDebuggerUrl ?? null;
77
+ } catch (err) {
78
+ log.warn({ err }, 'Failed to discover Chrome tabs');
79
+ return [];
80
+ }
81
+
82
+ if (!wsUrl) {
83
+ log.warn('No Chrome tab found for auto-navigation');
84
+ return [];
85
+ }
86
+
87
+ const cdp = new MiniCDP();
88
+ try {
89
+ await cdp.connect(wsUrl);
90
+ } catch (err) {
91
+ log.warn({ err }, 'Failed to connect CDP for auto-navigation');
92
+ return [];
93
+ }
94
+
95
+ await cdp.send('Page.enable').catch(() => {});
96
+ const completed: string[] = [];
97
+
98
+ // Navigate to home first to discover the screen name
99
+ try {
100
+ await cdp.send('Page.navigate', { url: 'https://x.com/home' });
101
+ await sleep(3000);
102
+ } catch (err) {
103
+ log.warn({ err }, 'Failed to navigate to home');
104
+ cdp.close();
105
+ return [];
106
+ }
107
+
108
+ // Resolve screen name for profile-based URLs
109
+ let screenName: string | null = null;
110
+ try {
111
+ const result = await cdp.send('Runtime.evaluate', {
112
+ expression: `
113
+ (function() {
114
+ const link = document.querySelector('a[data-testid="AppTabBar_Profile_Link"]');
115
+ if (link) return link.getAttribute('href')?.replace('/', '') ?? null;
116
+ return null;
117
+ })()
118
+ `,
119
+ awaitPromise: false,
120
+ returnByValue: true,
121
+ }) as { result?: { value?: string | null } };
122
+ screenName = result?.result?.value ?? null;
123
+ } catch { /* ignore */ }
124
+
125
+ log.info({ screenName }, 'Detected screen name');
126
+
127
+ // Build steps with resolved URLs
128
+ const steps: NavStep[] = [
129
+ { label: 'Home timeline', url: 'https://x.com/home' },
130
+ { label: 'Profile', clickSelector: 'a[data-testid="AppTabBar_Profile_Link"]' },
131
+ { label: 'Tweet detail', clickSelector: 'article[data-testid="tweet"] a[href*="/status/"]' },
132
+ { label: 'Search', url: 'https://x.com/search?q=hello&src=typed_query' },
133
+ { label: 'Bookmarks', url: 'https://x.com/i/bookmarks' },
134
+ { label: 'Notifications', url: 'https://x.com/notifications' },
135
+ ];
136
+
137
+ // Add profile-based URLs if we have the screen name
138
+ if (screenName) {
139
+ steps.push(
140
+ { label: 'Likes', url: `https://x.com/${screenName}/likes` },
141
+ { label: 'Followers', url: `https://x.com/${screenName}/followers` },
142
+ { label: 'Following', url: `https://x.com/${screenName}/following` },
143
+ { label: 'Media', url: `https://x.com/${screenName}/media` },
144
+ );
145
+ }
146
+
147
+ for (const step of steps) {
148
+ if (abortSignal?.aborted) break;
149
+
150
+ log.info({ step: step.label }, 'Auto-navigate step starting');
151
+
152
+ try {
153
+ if (step.url) {
154
+ await cdp.send('Page.navigate', { url: step.url });
155
+ await sleep(3000);
156
+ }
157
+
158
+ if (step.clickSelector) {
159
+ await sleep(1500);
160
+ await clickInPage(cdp, step.clickSelector);
161
+ await sleep(2000);
162
+ }
163
+
164
+ // Scroll to trigger lazy-loaded content
165
+ await cdp.send('Runtime.evaluate', {
166
+ expression: 'window.scrollBy(0, 800)',
167
+ awaitPromise: false,
168
+ }).catch(() => {});
169
+
170
+ await sleep(2000);
171
+
172
+ completed.push(step.label);
173
+ log.info({ step: step.label }, 'Auto-navigate step completed');
174
+ } catch (err) {
175
+ log.warn({ err, step: step.label }, 'Auto-navigate step failed');
176
+ }
177
+ }
178
+
179
+ cdp.close();
180
+ log.info({ completed: completed.length, total: steps.length }, 'Auto-navigation finished');
181
+ return completed;
182
+ }
183
+
184
+ async function clickInPage(cdp: MiniCDP, selector: string): Promise<boolean> {
185
+ try {
186
+ const result = await cdp.send('Runtime.evaluate', {
187
+ expression: `
188
+ (function() {
189
+ const el = document.querySelector(${JSON.stringify(selector)});
190
+ if (!el) return false;
191
+ el.scrollIntoView({ block: 'center' });
192
+ el.click();
193
+ return true;
194
+ })()
195
+ `,
196
+ awaitPromise: false,
197
+ returnByValue: true,
198
+ }) as { result?: { value?: boolean } };
199
+ return result?.result?.value === true;
200
+ } catch {
201
+ return false;
202
+ }
203
+ }
204
+
205
+ function sleep(ms: number): Promise<void> {
206
+ return new Promise(r => setTimeout(r, ms));
207
+ }
@@ -2,13 +2,7 @@ import { RiskLevel } from '../../permissions/types.js';
2
2
  import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
3
3
  import type { ToolDefinition } from '../../providers/types.js';
4
4
  import { registerTool } from '../registry.js';
5
- import { getCallSession, updateCallSession } from '../../calls/call-store.js';
6
- import { getCallOrchestrator, unregisterCallOrchestrator } from '../../calls/call-state.js';
7
- import { activeRelayConnections } from '../../calls/relay-server.js';
8
- import { TwilioConversationRelayProvider } from '../../calls/twilio-provider.js';
9
- import { getLogger } from '../../util/logger.js';
10
-
11
- const log = getLogger('call-end');
5
+ import { cancelCall } from '../../calls/call-domain.js';
12
6
 
13
7
  const definition: ToolDefinition = {
14
8
  name: 'call_end',
@@ -47,70 +41,26 @@ class CallEndTool implements Tool {
47
41
 
48
42
  const reason = input.reason as string | undefined;
49
43
 
50
- try {
51
- const session = getCallSession(callSessionId);
52
- if (!session) {
53
- return { content: `Error: no call session found with ID ${callSessionId}`, isError: true };
54
- }
55
-
56
- if (session.status === 'completed' || session.status === 'failed') {
57
- return {
58
- content: `Call session ${callSessionId} has already ended with status: ${session.status}`,
59
- isError: false,
60
- };
61
- }
62
-
63
- log.info({ callSessionId, reason }, 'Ending call');
64
-
65
- // Terminate the call via the provider API so Twilio hangs up,
66
- // even if the relay WebSocket is not connected.
67
- if (session.providerCallSid) {
68
- try {
69
- const provider = new TwilioConversationRelayProvider();
70
- await provider.endCall(session.providerCallSid);
71
- } catch (endErr) {
72
- log.warn({ err: endErr, callSessionId, callSid: session.providerCallSid }, 'Failed to terminate call via provider API — proceeding with cleanup');
73
- }
74
- }
44
+ const result = await cancelCall({ callSessionId, reason });
75
45
 
76
- // End the relay connection if active
77
- const relayConnection = activeRelayConnections.get(callSessionId);
78
- if (relayConnection) {
79
- relayConnection.endSession(reason);
80
- relayConnection.destroy();
81
- activeRelayConnections.delete(callSessionId);
82
- }
83
-
84
- // Clean up orchestrator
85
- const orchestrator = getCallOrchestrator(callSessionId);
86
- if (orchestrator) {
87
- orchestrator.destroy();
88
- unregisterCallOrchestrator(callSessionId);
89
- }
90
-
91
- // Update session status
92
- updateCallSession(callSessionId, {
93
- status: 'completed',
94
- endedAt: Date.now(),
95
- });
96
-
97
- log.info({ callSessionId }, 'Call ended successfully');
98
-
99
- const lines = [
100
- 'Call ended successfully.',
101
- ` Call Session ID: ${callSessionId}`,
102
- ` Status: completed`,
103
- ];
104
- if (reason) {
105
- lines.push(` Reason: ${reason}`);
46
+ if (!result.ok) {
47
+ // If the call already ended, report it as a non-error for the tool
48
+ if (result.status === 409) {
49
+ return { content: result.error, isError: false };
106
50
  }
51
+ return { content: `Error: ${result.error}`, isError: true };
52
+ }
107
53
 
108
- return { content: lines.join('\n'), isError: false };
109
- } catch (err) {
110
- const msg = err instanceof Error ? err.message : String(err);
111
- log.error({ err, callSessionId }, 'Failed to end call');
112
- return { content: `Error ending call: ${msg}`, isError: true };
54
+ const lines = [
55
+ 'Call ended successfully.',
56
+ ` Call Session ID: ${callSessionId}`,
57
+ ` Status: cancelled`,
58
+ ];
59
+ if (reason) {
60
+ lines.push(` Reason: ${reason}`);
113
61
  }
62
+
63
+ return { content: lines.join('\n'), isError: false };
114
64
  }
115
65
  }
116
66
 
@@ -2,15 +2,8 @@ import { RiskLevel } from '../../permissions/types.js';
2
2
  import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
3
3
  import type { ToolDefinition } from '../../providers/types.js';
4
4
  import { registerTool } from '../registry.js';
5
- import { DENIED_NUMBERS } from '../../calls/call-constants.js';
6
- import { createCallSession, updateCallSession } from '../../calls/call-store.js';
7
- import { TwilioConversationRelayProvider } from '../../calls/twilio-provider.js';
8
- import { getTwilioConfig } from '../../calls/twilio-config.js';
9
- import { getLogger } from '../../util/logger.js';
10
-
11
- const log = getLogger('call-start');
12
-
13
- const E164_REGEX = /^\+\d+$/;
5
+ import { startCall } from '../../calls/call-domain.js';
6
+ import { getConfig } from '../../config/loader.js';
14
7
 
15
8
  const definition: ToolDefinition = {
16
9
  name: 'call_start',
@@ -47,87 +40,33 @@ class CallStartTool implements Tool {
47
40
  }
48
41
 
49
42
  async execute(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
50
- const phoneNumber = input.phone_number as string | undefined;
51
- if (!phoneNumber || typeof phoneNumber !== 'string') {
52
- return { content: 'Error: phone_number is required and must be a string', isError: true };
53
- }
54
-
55
- if (!E164_REGEX.test(phoneNumber)) {
56
- return {
57
- content: 'Error: phone_number must be in E.164 format (starts with + followed by digits, e.g. +14155551234)',
58
- isError: true,
59
- };
43
+ if (!getConfig().calls.enabled) {
44
+ return { content: 'Error: Calls feature is disabled via configuration. Set calls.enabled to true to use this feature.', isError: true };
60
45
  }
61
46
 
62
- const task = input.task as string | undefined;
63
- if (!task || typeof task !== 'string' || task.trim().length === 0) {
64
- return { content: 'Error: task is required and must be a non-empty string', isError: true };
65
- }
47
+ const result = await startCall({
48
+ phoneNumber: input.phone_number as string,
49
+ task: input.task as string,
50
+ context: input.context as string | undefined,
51
+ conversationId: context.conversationId,
52
+ });
66
53
 
67
- if (DENIED_NUMBERS.has(phoneNumber)) {
68
- return { content: 'Error: this phone number is not allowed to be called', isError: true };
54
+ if (!result.ok) {
55
+ return { content: `Error: ${result.error}`, isError: true };
69
56
  }
70
57
 
71
- const callContext = input.context as string | undefined;
72
-
73
- // Create session outside the try block so it's available in the catch block
74
- // for marking as failed if the provider call fails.
75
- let sessionId: string | null = null;
76
-
77
- try {
78
- const config = getTwilioConfig();
79
- const provider = new TwilioConversationRelayProvider();
80
-
81
- const session = createCallSession({
82
- conversationId: context.conversationId,
83
- provider: 'twilio',
84
- fromNumber: config.phoneNumber,
85
- toNumber: phoneNumber,
86
- task: callContext ? `${task}\n\nContext: ${callContext}` : task,
87
- });
88
- sessionId = session.id;
89
-
90
- log.info({ callSessionId: session.id, to: phoneNumber, task }, 'Initiating outbound call');
91
-
92
- const baseUrl = config.webhookBaseUrl.replace(/\/$/, '');
93
- const { callSid } = await provider.initiateCall({
94
- from: config.phoneNumber,
95
- to: phoneNumber,
96
- webhookUrl: `${baseUrl}/v1/calls/twilio/voice-webhook?callSessionId=${session.id}`,
97
- statusCallbackUrl: `${baseUrl}/v1/calls/twilio/status`,
98
- });
99
-
100
- updateCallSession(session.id, { providerCallSid: callSid });
101
-
102
- log.info({ callSessionId: session.id, callSid }, 'Call initiated successfully');
103
-
104
- return {
105
- content: [
106
- 'Call initiated successfully.',
107
- ` Call Session ID: ${session.id}`,
108
- ` Call SID: ${callSid}`,
109
- ` To: ${phoneNumber}`,
110
- ` Status: initiated`,
111
- '',
112
- 'The AI voice assistant is now placing the call. Use call_status to check progress.',
113
- ].join('\n'),
114
- isError: false,
115
- };
116
- } catch (err) {
117
- const msg = err instanceof Error ? err.message : String(err);
118
- log.error({ err, phoneNumber }, 'Failed to initiate call');
119
-
120
- // Mark the session as failed so it doesn't stay in 'initiated' state
121
- if (sessionId) {
122
- updateCallSession(sessionId, {
123
- status: 'failed',
124
- endedAt: Date.now(),
125
- lastError: msg,
126
- });
127
- }
128
-
129
- return { content: `Error initiating call: ${msg}`, isError: true };
130
- }
58
+ return {
59
+ content: [
60
+ 'Call initiated successfully.',
61
+ ` Call Session ID: ${result.session.id}`,
62
+ ` Call SID: ${result.callSid}`,
63
+ ` To: ${result.session.toNumber}`,
64
+ ` Status: initiated`,
65
+ '',
66
+ 'The AI voice assistant is now placing the call. Use call_status to check progress.',
67
+ ].join('\n'),
68
+ isError: false,
69
+ };
131
70
  }
132
71
  }
133
72
 
@@ -2,10 +2,7 @@ import { RiskLevel } from '../../permissions/types.js';
2
2
  import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
3
3
  import type { ToolDefinition } from '../../providers/types.js';
4
4
  import { registerTool } from '../registry.js';
5
- import { getCallSession, getActiveCallSessionForConversation, getPendingQuestion } from '../../calls/call-store.js';
6
- import { getLogger } from '../../util/logger.js';
7
-
8
- const log = getLogger('call-status');
5
+ import { getCallStatus } from '../../calls/call-domain.js';
9
6
 
10
7
  const definition: ToolDefinition = {
11
8
  name: 'call_status',
@@ -35,62 +32,49 @@ class CallStatusTool implements Tool {
35
32
  async execute(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
36
33
  const callSessionId = input.call_session_id as string | undefined;
37
34
 
38
- try {
39
- let session;
35
+ const result = getCallStatus(callSessionId, context.conversationId);
40
36
 
41
- if (callSessionId) {
42
- session = getCallSession(callSessionId);
43
- if (!session) {
44
- return { content: `Error: no call session found with ID ${callSessionId}`, isError: true };
45
- }
46
- } else {
47
- session = getActiveCallSessionForConversation(context.conversationId);
48
- if (!session) {
49
- return { content: 'No active call found in the current conversation.', isError: false };
50
- }
37
+ if (!result.ok) {
38
+ // When no active call is found and no specific ID was requested, it's not an error
39
+ if (!callSessionId && result.error === 'No active call found in the current conversation') {
40
+ return { content: result.error, isError: false };
51
41
  }
42
+ return { content: `Error: ${result.error}`, isError: true };
43
+ }
52
44
 
53
- log.info({ callSessionId: session.id, status: session.status }, 'Checking call status');
54
-
55
- const lines = [
56
- `Call Session: ${session.id}`,
57
- ` Status: ${session.status}`,
58
- ` To: ${session.toNumber}`,
59
- ` From: ${session.fromNumber}`,
60
- ];
45
+ const { session } = result;
46
+ const lines = [
47
+ `Call Session: ${session.id}`,
48
+ ` Status: ${session.status}`,
49
+ ` To: ${session.toNumber}`,
50
+ ` From: ${session.fromNumber}`,
51
+ ];
61
52
 
62
- if (session.providerCallSid) {
63
- lines.push(` Call SID: ${session.providerCallSid}`);
64
- }
65
-
66
- if (session.task) {
67
- lines.push(` Task: ${session.task}`);
68
- }
53
+ if (session.providerCallSid) {
54
+ lines.push(` Call SID: ${session.providerCallSid}`);
55
+ }
69
56
 
70
- if (session.startedAt) {
71
- const durationMs = (session.endedAt ?? Date.now()) - session.startedAt;
72
- const durationSec = Math.round(durationMs / 1000);
73
- lines.push(` Duration: ${durationSec}s`);
74
- }
57
+ if (session.task) {
58
+ lines.push(` Task: ${session.task}`);
59
+ }
75
60
 
76
- if (session.lastError) {
77
- lines.push(` Last Error: ${session.lastError}`);
78
- }
61
+ if (session.startedAt) {
62
+ const durationMs = (session.endedAt ?? Date.now()) - session.startedAt;
63
+ const durationSec = Math.round(durationMs / 1000);
64
+ lines.push(` Duration: ${durationSec}s`);
65
+ }
79
66
 
80
- // Check for pending questions from the call
81
- const pendingQuestion = getPendingQuestion(session.id);
82
- if (pendingQuestion) {
83
- lines.push('');
84
- lines.push(` Pending Question: ${pendingQuestion.questionText}`);
85
- lines.push(` Question ID: ${pendingQuestion.id}`);
86
- }
67
+ if (session.lastError) {
68
+ lines.push(` Last Error: ${session.lastError}`);
69
+ }
87
70
 
88
- return { content: lines.join('\n'), isError: false };
89
- } catch (err) {
90
- const msg = err instanceof Error ? err.message : String(err);
91
- log.error({ err, callSessionId }, 'Failed to check call status');
92
- return { content: `Error checking call status: ${msg}`, isError: true };
71
+ if (result.pendingQuestion) {
72
+ lines.push('');
73
+ lines.push(` Pending Question: ${result.pendingQuestion.questionText}`);
74
+ lines.push(` Question ID: ${result.pendingQuestion.id}`);
93
75
  }
76
+
77
+ return { content: lines.join('\n'), isError: false };
94
78
  }
95
79
  }
96
80