vellum 0.2.0 → 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 (361) 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 +161 -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__/app-bundler.test.ts +12 -33
  11. package/src/__tests__/asset-materialize-tool.test.ts +16 -15
  12. package/src/__tests__/asset-search-tool.test.ts +23 -22
  13. package/src/__tests__/attachments-store.test.ts +56 -127
  14. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
  15. package/src/__tests__/browser-skill-endstate.test.ts +5 -8
  16. package/src/__tests__/call-bridge.test.ts +385 -0
  17. package/src/__tests__/call-constants.test.ts +40 -0
  18. package/src/__tests__/call-orchestrator.test.ts +454 -0
  19. package/src/__tests__/call-recovery.test.ts +518 -0
  20. package/src/__tests__/call-routes-http.test.ts +459 -0
  21. package/src/__tests__/call-state-machine.test.ts +143 -0
  22. package/src/__tests__/call-state.test.ts +133 -0
  23. package/src/__tests__/call-store.test.ts +691 -0
  24. package/src/__tests__/cli-discover.test.ts +1 -1
  25. package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
  26. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  27. package/src/__tests__/computer-use-tools.test.ts +250 -0
  28. package/src/__tests__/config-schema.test.ts +348 -3
  29. package/src/__tests__/conflict-store.test.ts +2 -1
  30. package/src/__tests__/contacts-tools.test.ts +331 -0
  31. package/src/__tests__/conversation-store.test.ts +30 -32
  32. package/src/__tests__/credential-security-invariants.test.ts +4 -0
  33. package/src/__tests__/date-context.test.ts +373 -0
  34. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  35. package/src/__tests__/doordash-session.test.ts +9 -0
  36. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
  37. package/src/__tests__/followup-tools.test.ts +303 -0
  38. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  39. package/src/__tests__/intent-routing.test.ts +64 -57
  40. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  41. package/src/__tests__/ipc-snapshot.test.ts +96 -28
  42. package/src/__tests__/llm-usage-store.test.ts +3 -8
  43. package/src/__tests__/media-generate-image.test.ts +1 -1
  44. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  45. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  46. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  47. package/src/__tests__/playbook-tools.test.ts +342 -0
  48. package/src/__tests__/profile-compiler.test.ts +2 -1
  49. package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
  50. package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
  51. package/src/__tests__/recurrence-engine.test.ts +69 -0
  52. package/src/__tests__/recurrence-types.test.ts +71 -0
  53. package/src/__tests__/registry.test.ts +17 -10
  54. package/src/__tests__/relay-server.test.ts +633 -0
  55. package/src/__tests__/reminder-store.test.ts +6 -3
  56. package/src/__tests__/reminder.test.ts +43 -77
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +222 -0
  58. package/src/__tests__/run-orchestrator.test.ts +7 -7
  59. package/src/__tests__/runtime-attachment-metadata.test.ts +19 -20
  60. package/src/__tests__/runtime-runs-http.test.ts +5 -23
  61. package/src/__tests__/runtime-runs.test.ts +11 -11
  62. package/src/__tests__/schedule-store.test.ts +482 -0
  63. package/src/__tests__/schedule-tools.test.ts +700 -0
  64. package/src/__tests__/scheduler-recurrence.test.ts +329 -0
  65. package/src/__tests__/server-history-render.test.ts +14 -13
  66. package/src/__tests__/session-error.test.ts +28 -0
  67. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  68. package/src/__tests__/session-queue.test.ts +89 -16
  69. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  70. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  71. package/src/__tests__/signup-e2e.test.ts +2 -1
  72. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  73. package/src/__tests__/skill-script-runner.test.ts +159 -0
  74. package/src/__tests__/speaker-identification.test.ts +52 -0
  75. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  76. package/src/__tests__/subagent-tools.test.ts +141 -41
  77. package/src/__tests__/task-compiler.test.ts +2 -1
  78. package/src/__tests__/task-runner.test.ts +2 -1
  79. package/src/__tests__/task-scheduler.test.ts +2 -1
  80. package/src/__tests__/task-tools.test.ts +49 -56
  81. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  82. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  83. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  84. package/src/__tests__/tool-executor.test.ts +13 -17
  85. package/src/__tests__/turn-commit.test.ts +273 -2
  86. package/src/__tests__/twilio-provider.test.ts +143 -0
  87. package/src/__tests__/twilio-routes.test.ts +789 -0
  88. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  89. package/src/__tests__/view-image-tool.test.ts +217 -0
  90. package/src/__tests__/workspace-git-service.test.ts +403 -0
  91. package/src/__tests__/workspace-heartbeat-service.test.ts +141 -2
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  93. package/src/bundler/app-bundler.ts +35 -14
  94. package/src/calls/call-bridge.ts +95 -0
  95. package/src/calls/call-constants.ts +48 -0
  96. package/src/calls/call-domain.ts +276 -0
  97. package/src/calls/call-orchestrator.ts +390 -0
  98. package/src/calls/call-recovery.ts +207 -0
  99. package/src/calls/call-state-machine.ts +68 -0
  100. package/src/calls/call-state.ts +64 -0
  101. package/src/calls/call-store.ts +416 -0
  102. package/src/calls/relay-server.ts +335 -0
  103. package/src/calls/speaker-identification.ts +213 -0
  104. package/src/calls/twilio-config.ts +34 -0
  105. package/src/calls/twilio-provider.ts +173 -0
  106. package/src/calls/twilio-routes.ts +250 -0
  107. package/src/calls/types.ts +37 -0
  108. package/src/calls/voice-provider.ts +14 -0
  109. package/src/cli/config-commands.ts +334 -0
  110. package/src/cli/core-commands.ts +776 -0
  111. package/src/cli/doordash.ts +256 -25
  112. package/src/cli/ipc-client.ts +82 -0
  113. package/src/cli/map.ts +246 -0
  114. package/src/cli/twitter.ts +575 -0
  115. package/src/cli.ts +7 -5
  116. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  117. package/src/commands/cc-command-registry.ts +209 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  119. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  120. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  121. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  122. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  123. package/src/config/bundled-skills/document/SKILL.md +18 -0
  124. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  125. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  126. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  127. package/src/config/bundled-skills/doordash/SKILL.md +163 -0
  128. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  129. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  130. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  131. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  132. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  133. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  134. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -24
  135. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
  136. package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
  137. package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
  138. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
  139. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
  140. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
  141. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
  142. package/src/config/bundled-skills/reminder/SKILL.md +20 -0
  143. package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
  144. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
  145. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
  146. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
  147. package/src/config/bundled-skills/schedule/SKILL.md +74 -0
  148. package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
  149. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
  150. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
  151. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
  152. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
  153. package/src/config/bundled-skills/subagent/SKILL.md +25 -0
  154. package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
  155. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
  156. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
  157. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
  158. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
  159. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
  160. package/src/config/bundled-skills/tasks/SKILL.md +28 -0
  161. package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
  162. package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
  163. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
  164. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
  165. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
  166. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
  167. package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
  168. package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
  169. package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
  170. package/src/config/bundled-skills/twitter/SKILL.md +134 -0
  171. package/src/config/bundled-skills/watcher/SKILL.md +27 -0
  172. package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
  173. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
  174. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
  175. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
  176. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
  177. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
  178. package/src/config/defaults.ts +44 -0
  179. package/src/config/loader.ts +4 -1
  180. package/src/config/schema.ts +218 -1
  181. package/src/config/system-prompt.ts +100 -6
  182. package/src/config/templates/IDENTITY.md +7 -0
  183. package/src/config/types.ts +5 -0
  184. package/src/contacts/contact-store.ts +4 -4
  185. package/src/daemon/assistant-attachments.ts +10 -0
  186. package/src/daemon/classifier.ts +3 -1
  187. package/src/daemon/computer-use-session.ts +3 -1
  188. package/src/daemon/date-context.ts +136 -0
  189. package/src/daemon/handlers/apps.ts +16 -1
  190. package/src/daemon/handlers/browser.ts +54 -0
  191. package/src/daemon/handlers/computer-use.ts +7 -1
  192. package/src/daemon/handlers/config.ts +192 -4
  193. package/src/daemon/handlers/diagnostics.ts +5 -1
  194. package/src/daemon/handlers/documents.ts +18 -29
  195. package/src/daemon/handlers/home-base.ts +5 -1
  196. package/src/daemon/handlers/index.ts +40 -271
  197. package/src/daemon/handlers/misc.ts +9 -1
  198. package/src/daemon/handlers/publish.ts +6 -1
  199. package/src/daemon/handlers/sessions.ts +65 -12
  200. package/src/daemon/handlers/shared.ts +36 -1
  201. package/src/daemon/handlers/signing.ts +37 -0
  202. package/src/daemon/handlers/skills.ts +20 -6
  203. package/src/daemon/handlers/subagents.ts +8 -3
  204. package/src/daemon/handlers/twitter-auth.ts +169 -0
  205. package/src/daemon/handlers/work-items.ts +495 -39
  206. package/src/daemon/ipc-contract-inventory.json +40 -4
  207. package/src/daemon/ipc-contract.ts +185 -37
  208. package/src/daemon/ipc-protocol.ts +7 -2
  209. package/src/daemon/lifecycle.ts +48 -5
  210. package/src/daemon/main.ts +10 -4
  211. package/src/daemon/ride-shotgun-handler.ts +74 -10
  212. package/src/daemon/server.ts +144 -29
  213. package/src/daemon/session-agent-loop.ts +887 -0
  214. package/src/daemon/session-attachments.ts +28 -5
  215. package/src/daemon/session-error.ts +24 -3
  216. package/src/daemon/session-lifecycle.ts +147 -0
  217. package/src/daemon/session-media-retry.ts +147 -0
  218. package/src/daemon/session-messaging.ts +145 -0
  219. package/src/daemon/session-notifiers.ts +164 -0
  220. package/src/daemon/session-process.ts +2 -2
  221. package/src/daemon/session-queue-manager.ts +1 -0
  222. package/src/daemon/session-runtime-assembly.ts +52 -0
  223. package/src/daemon/session-skill-tools.ts +124 -5
  224. package/src/daemon/session-slash.ts +3 -0
  225. package/src/daemon/session-surfaces.ts +77 -2
  226. package/src/daemon/session-tool-setup.ts +222 -2
  227. package/src/daemon/session-usage.ts +0 -2
  228. package/src/daemon/session.ts +114 -1365
  229. package/src/daemon/video-thumbnail.ts +60 -0
  230. package/src/doordash/client.ts +121 -27
  231. package/src/doordash/queries.ts +1 -2
  232. package/src/export/formatter.ts +3 -1
  233. package/src/followups/followup-store.ts +4 -2
  234. package/src/followups/types.ts +6 -0
  235. package/src/hooks/templates.ts +1 -1
  236. package/src/index.ts +32 -1151
  237. package/src/media/gemini-image-service.ts +1 -1
  238. package/src/memory/attachments-store.ts +28 -83
  239. package/src/memory/channel-delivery-store.ts +7 -21
  240. package/src/memory/clarification-resolver.ts +6 -5
  241. package/src/memory/contradiction-checker.ts +3 -2
  242. package/src/memory/conversation-key-store.ts +10 -29
  243. package/src/memory/conversation-store.ts +2 -1
  244. package/src/memory/db.ts +362 -2
  245. package/src/memory/entity-extractor.ts +6 -3
  246. package/src/memory/items-extractor.ts +5 -4
  247. package/src/memory/jobs-store.ts +3 -2
  248. package/src/memory/llm-usage-store.ts +1 -2
  249. package/src/memory/runs-store.ts +1 -2
  250. package/src/memory/schema.ts +65 -2
  251. package/src/messaging/style-analyzer.ts +3 -2
  252. package/src/messaging/thread-summarizer.ts +8 -12
  253. package/src/messaging/triage-engine.ts +4 -2
  254. package/src/providers/openrouter/client.ts +20 -0
  255. package/src/providers/registry.ts +8 -0
  256. package/src/runtime/http-server.ts +277 -25
  257. package/src/runtime/http-types.ts +0 -2
  258. package/src/runtime/routes/attachment-routes.ts +5 -6
  259. package/src/runtime/routes/call-routes.ts +140 -0
  260. package/src/runtime/routes/channel-routes.ts +12 -19
  261. package/src/runtime/routes/conversation-routes.ts +5 -9
  262. package/src/runtime/routes/run-routes.ts +4 -8
  263. package/src/runtime/run-orchestrator.ts +39 -6
  264. package/src/schedule/recurrence-engine.ts +138 -0
  265. package/src/schedule/recurrence-types.ts +67 -0
  266. package/src/schedule/schedule-store.ts +102 -57
  267. package/src/schedule/scheduler.ts +9 -6
  268. package/src/security/oauth2.ts +29 -4
  269. package/src/security/secret-allowlist.ts +46 -0
  270. package/src/skills/clawhub.ts +1 -1
  271. package/src/subagent/manager.ts +40 -8
  272. package/src/swarm/backend-claude-code.ts +64 -9
  273. package/src/swarm/worker-prompts.ts +2 -1
  274. package/src/tasks/SPEC.md +34 -28
  275. package/src/tasks/ephemeral-permissions.ts +16 -7
  276. package/src/tasks/task-compiler.ts +5 -4
  277. package/src/tasks/task-runner.ts +10 -5
  278. package/src/tasks/task-scheduler.ts +1 -1
  279. package/src/tasks/tool-sanitizer.ts +36 -0
  280. package/src/tools/assets/search.ts +4 -4
  281. package/src/tools/browser/api-map.ts +220 -0
  282. package/src/tools/browser/auto-navigate.ts +270 -0
  283. package/src/tools/browser/browser-execution.ts +2 -1
  284. package/src/tools/browser/browser-manager.ts +2 -2
  285. package/src/tools/browser/network-recorder.ts +5 -4
  286. package/src/tools/browser/x-auto-navigate.ts +207 -0
  287. package/src/tools/calls/call-end.ts +67 -0
  288. package/src/tools/calls/call-start.ts +73 -0
  289. package/src/tools/calls/call-status.ts +81 -0
  290. package/src/tools/claude-code/claude-code.ts +77 -11
  291. package/src/tools/contacts/contact-merge.ts +46 -78
  292. package/src/tools/contacts/contact-search.ts +35 -79
  293. package/src/tools/contacts/contact-upsert.ts +35 -108
  294. package/src/tools/credentials/vault.ts +21 -5
  295. package/src/tools/document/document-tool.ts +71 -144
  296. package/src/tools/executor.ts +129 -10
  297. package/src/tools/followups/followup_create.ts +46 -88
  298. package/src/tools/followups/followup_list.ts +34 -74
  299. package/src/tools/followups/followup_resolve.ts +31 -66
  300. package/src/tools/host-terminal/cli-discover.ts +2 -1
  301. package/src/tools/host-terminal/host-shell.ts +10 -0
  302. package/src/tools/memory/handlers.ts +5 -4
  303. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  304. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  305. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  306. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  307. package/src/tools/network/web-fetch.ts +18 -6
  308. package/src/tools/playbooks/index.ts +4 -5
  309. package/src/tools/playbooks/playbook-create.ts +3 -47
  310. package/src/tools/playbooks/playbook-delete.ts +1 -25
  311. package/src/tools/playbooks/playbook-list.ts +1 -28
  312. package/src/tools/playbooks/playbook-update.ts +3 -51
  313. package/src/tools/registry.ts +2 -4
  314. package/src/tools/reminder/reminder.ts +5 -78
  315. package/src/tools/schedule/create.ts +69 -74
  316. package/src/tools/schedule/delete.ts +21 -47
  317. package/src/tools/schedule/list.ts +55 -74
  318. package/src/tools/schedule/update.ts +77 -84
  319. package/src/tools/subagent/abort.ts +29 -58
  320. package/src/tools/subagent/message.ts +30 -63
  321. package/src/tools/subagent/read.ts +53 -84
  322. package/src/tools/subagent/spawn.ts +43 -82
  323. package/src/tools/subagent/status.ts +42 -71
  324. package/src/tools/swarm/delegate.ts +2 -1
  325. package/src/tools/tasks/index.ts +8 -6
  326. package/src/tools/tasks/task-delete.ts +69 -56
  327. package/src/tools/tasks/task-list.ts +31 -52
  328. package/src/tools/tasks/task-run.ts +74 -102
  329. package/src/tools/tasks/task-save.ts +33 -65
  330. package/src/tools/tasks/work-item-enqueue.ts +192 -134
  331. package/src/tools/tasks/work-item-list.ts +33 -78
  332. package/src/tools/tasks/work-item-remove.ts +60 -0
  333. package/src/tools/tasks/work-item-update.ts +114 -0
  334. package/src/tools/terminal/backends/native.ts +3 -1
  335. package/src/tools/tool-manifest.ts +20 -74
  336. package/src/tools/types.ts +6 -0
  337. package/src/tools/ui-surface/definitions.ts +6 -1
  338. package/src/tools/watch/screen-watch.ts +3 -1
  339. package/src/tools/watcher/create.ts +52 -98
  340. package/src/tools/watcher/delete.ts +20 -46
  341. package/src/tools/watcher/digest.ts +36 -70
  342. package/src/tools/watcher/list.ts +49 -79
  343. package/src/tools/watcher/update.ts +45 -91
  344. package/src/twitter/client.ts +690 -0
  345. package/src/twitter/session.ts +91 -0
  346. package/src/usage/types.ts +0 -1
  347. package/src/util/truncate.ts +6 -0
  348. package/src/watcher/providers/slack.ts +2 -1
  349. package/src/watcher/watcher-store.ts +3 -2
  350. package/src/work-items/work-item-store.ts +236 -2
  351. package/src/workspace/commit-message-enrichment-service.ts +284 -0
  352. package/src/workspace/commit-message-provider.ts +95 -0
  353. package/src/workspace/git-service.ts +272 -52
  354. package/src/workspace/heartbeat-service.ts +70 -13
  355. package/src/workspace/provider-commit-message-generator.ts +242 -0
  356. package/src/workspace/turn-commit.ts +100 -51
  357. package/src/tools/contacts/index.ts +0 -4
  358. package/src/tools/document/index.ts +0 -5
  359. package/src/tools/followups/index.ts +0 -3
  360. package/src/tools/subagent/index.ts +0 -5
  361. /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
@@ -1,87 +1,55 @@
1
- import { RiskLevel } from '../../permissions/types.js';
2
- import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
3
- import type { ToolDefinition } from '../../providers/types.js';
4
- import { registerTool } from '../registry.js';
1
+ import type { ToolContext, ToolExecutionResult } from '../types.js';
5
2
  import { mergeContacts, getContact } from '../../contacts/contact-store.js';
6
3
 
7
- const definition: ToolDefinition = {
8
- name: 'contact_merge',
9
- description: 'Merge two contacts when you discover they are the same person (e.g. same person on email and Slack). Combines channels, keeps the higher importance, and deletes the donor contact.',
10
- input_schema: {
11
- type: 'object',
12
- properties: {
13
- keep_id: {
14
- type: 'string',
15
- description: 'ID of the contact to keep (the surviving contact)',
16
- },
17
- merge_id: {
18
- type: 'string',
19
- description: 'ID of the contact to merge into the kept contact (will be deleted)',
20
- },
21
- },
22
- required: ['keep_id', 'merge_id'],
23
- },
24
- };
4
+ export async function executeContactMerge(
5
+ input: Record<string, unknown>,
6
+ _context: ToolContext,
7
+ ): Promise<ToolExecutionResult> {
8
+ const keepId = input.keep_id as string | undefined;
9
+ const mergeId = input.merge_id as string | undefined;
25
10
 
26
- class ContactMergeTool implements Tool {
27
- name = 'contact_merge';
28
- description = definition.description;
29
- category = 'contacts';
30
- defaultRiskLevel = RiskLevel.Medium;
31
-
32
- getDefinition(): ToolDefinition {
33
- return definition;
11
+ if (!keepId || typeof keepId !== 'string') {
12
+ return { content: 'Error: keep_id is required', isError: true };
13
+ }
14
+ if (!mergeId || typeof mergeId !== 'string') {
15
+ return { content: 'Error: merge_id is required', isError: true };
34
16
  }
35
17
 
36
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
37
- const keepId = input.keep_id as string | undefined;
38
- const mergeId = input.merge_id as string | undefined;
39
-
40
- if (!keepId || typeof keepId !== 'string') {
41
- return { content: 'Error: keep_id is required', isError: true };
42
- }
43
- if (!mergeId || typeof mergeId !== 'string') {
44
- return { content: 'Error: merge_id is required', isError: true };
45
- }
46
-
47
- // Show what will be merged for clarity
48
- const keepContact = getContact(keepId);
49
- const mergeContact = getContact(mergeId);
50
-
51
- if (!keepContact) {
52
- return { content: `Error: Contact "${keepId}" not found`, isError: true };
53
- }
54
- if (!mergeContact) {
55
- return { content: `Error: Contact "${mergeId}" not found`, isError: true };
56
- }
57
-
58
- try {
59
- const merged = mergeContacts(keepId, mergeId);
18
+ // Show what will be merged for clarity
19
+ const keepContact = getContact(keepId);
20
+ const mergeContact = getContact(mergeId);
60
21
 
61
- const channelList = merged.channels
62
- .map((ch) => ` - ${ch.type}: ${ch.address}${ch.isPrimary ? ' (primary)' : ''}`)
63
- .join('\n');
22
+ if (!keepContact) {
23
+ return { content: `Error: Contact "${keepId}" not found`, isError: true };
24
+ }
25
+ if (!mergeContact) {
26
+ return { content: `Error: Contact "${mergeId}" not found`, isError: true };
27
+ }
64
28
 
65
- return {
66
- content: [
67
- `Merged "${mergeContact.displayName}" into "${keepContact.displayName}".`,
68
- ``,
69
- `Surviving contact (${merged.id}):`,
70
- ` Name: ${merged.displayName}`,
71
- ` Importance: ${merged.importance.toFixed(2)}`,
72
- ` Interactions: ${merged.interactionCount}`,
73
- merged.relationship ? ` Relationship: ${merged.relationship}` : null,
74
- merged.channels.length > 0 ? ` Channels:\n${channelList}` : null,
75
- ``,
76
- `Deleted contact: ${mergeContact.displayName} (${mergeId})`,
77
- ].filter(Boolean).join('\n'),
78
- isError: false,
79
- };
80
- } catch (err) {
81
- const msg = err instanceof Error ? err.message : String(err);
82
- return { content: `Error: ${msg}`, isError: true };
83
- }
29
+ try {
30
+ const merged = mergeContacts(keepId, mergeId);
31
+
32
+ const channelList = merged.channels
33
+ .map((ch) => ` - ${ch.type}: ${ch.address}${ch.isPrimary ? ' (primary)' : ''}`)
34
+ .join('\n');
35
+
36
+ return {
37
+ content: [
38
+ `Merged "${mergeContact.displayName}" into "${keepContact.displayName}".`,
39
+ ``,
40
+ `Surviving contact (${merged.id}):`,
41
+ ` Name: ${merged.displayName}`,
42
+ ` Importance: ${merged.importance.toFixed(2)}`,
43
+ ` Interactions: ${merged.interactionCount}`,
44
+ merged.relationship ? ` Relationship: ${merged.relationship}` : null,
45
+ merged.channels.length > 0 ? ` Channels:\n${channelList}` : null,
46
+ ``,
47
+ `Deleted contact: ${mergeContact.displayName} (${mergeId})`,
48
+ ].filter(Boolean).join('\n'),
49
+ isError: false,
50
+ };
51
+ } catch (err) {
52
+ const msg = err instanceof Error ? err.message : String(err);
53
+ return { content: `Error: ${msg}`, isError: true };
84
54
  }
85
55
  }
86
-
87
- registerTool(new ContactMergeTool());
@@ -1,7 +1,4 @@
1
- import { RiskLevel } from '../../permissions/types.js';
2
- import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
3
- import type { ToolDefinition } from '../../providers/types.js';
4
- import { registerTool } from '../registry.js';
1
+ import type { ToolContext, ToolExecutionResult } from '../types.js';
5
2
  import { searchContacts } from '../../contacts/contact-store.js';
6
3
  import type { ContactWithChannels } from '../../contacts/types.js';
7
4
 
@@ -18,85 +15,44 @@ function formatContactSummary(c: ContactWithChannels): string {
18
15
  return parts.join('\n');
19
16
  }
20
17
 
21
- const definition: ToolDefinition = {
22
- name: 'contact_search',
23
- description: 'Search for contacts by name, channel address, relationship type, or other criteria. Returns matching contacts with their channel information.',
24
- input_schema: {
25
- type: 'object',
26
- properties: {
27
- query: {
28
- type: 'string',
29
- description: 'Search by display name (partial match)',
30
- },
31
- channel_address: {
32
- type: 'string',
33
- description: 'Search by channel address (email, phone, handle — partial match)',
34
- },
35
- channel_type: {
36
- type: 'string',
37
- description: 'Filter by channel type when searching by address (email, slack, whatsapp, phone, etc.)',
38
- },
39
- relationship: {
40
- type: 'string',
41
- description: 'Filter by relationship type (exact match)',
42
- },
43
- limit: {
44
- type: 'number',
45
- description: 'Maximum results to return (default 20, max 100)',
46
- },
47
- },
48
- required: [],
49
- },
50
- };
51
-
52
- class ContactSearchTool implements Tool {
53
- name = 'contact_search';
54
- description = definition.description;
55
- category = 'contacts';
56
- defaultRiskLevel = RiskLevel.Low;
57
-
58
- getDefinition(): ToolDefinition {
59
- return definition;
18
+ export async function executeContactSearch(
19
+ input: Record<string, unknown>,
20
+ _context: ToolContext,
21
+ ): Promise<ToolExecutionResult> {
22
+ const query = input.query as string | undefined;
23
+ const channelAddress = input.channel_address as string | undefined;
24
+ const channelType = input.channel_type as string | undefined;
25
+ const relationship = input.relationship as string | undefined;
26
+ const limit = input.limit as number | undefined;
27
+
28
+ if (!query && !channelAddress && !relationship) {
29
+ return {
30
+ content: 'Error: At least one search criterion is required (query, channel_address, or relationship)',
31
+ isError: true,
32
+ };
60
33
  }
61
34
 
62
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
63
- const query = input.query as string | undefined;
64
- const channelAddress = input.channel_address as string | undefined;
65
- const channelType = input.channel_type as string | undefined;
66
- const relationship = input.relationship as string | undefined;
67
- const limit = input.limit as number | undefined;
68
-
69
- if (!query && !channelAddress && !relationship) {
70
- return {
71
- content: 'Error: At least one search criterion is required (query, channel_address, or relationship)',
72
- isError: true,
73
- };
35
+ try {
36
+ const results = searchContacts({
37
+ query,
38
+ channelAddress,
39
+ channelType,
40
+ relationship,
41
+ limit,
42
+ });
43
+
44
+ if (results.length === 0) {
45
+ return { content: 'No contacts found matching the search criteria.', isError: false };
74
46
  }
75
47
 
76
- try {
77
- const results = searchContacts({
78
- query,
79
- channelAddress,
80
- channelType,
81
- relationship,
82
- limit,
83
- });
84
-
85
- if (results.length === 0) {
86
- return { content: 'No contacts found matching the search criteria.', isError: false };
87
- }
88
-
89
- const lines = [`Found ${results.length} contact(s):\n`];
90
- for (const contact of results) {
91
- lines.push(formatContactSummary(contact));
92
- }
93
-
94
- return { content: lines.join('\n'), isError: false };
95
- } catch (err) {
96
- const msg = err instanceof Error ? err.message : String(err);
97
- return { content: `Error: ${msg}`, isError: true };
48
+ const lines = [`Found ${results.length} contact(s):\n`];
49
+ for (const contact of results) {
50
+ lines.push(formatContactSummary(contact));
98
51
  }
52
+
53
+ return { content: lines.join('\n'), isError: false };
54
+ } catch (err) {
55
+ const msg = err instanceof Error ? err.message : String(err);
56
+ return { content: `Error: ${msg}`, isError: true };
99
57
  }
100
58
  }
101
-
102
- registerTool(new ContactSearchTool());
@@ -1,9 +1,5 @@
1
- import { RiskLevel } from '../../permissions/types.js';
2
- import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
3
- import type { ToolDefinition } from '../../providers/types.js';
4
- import { registerTool } from '../registry.js';
1
+ import type { ToolContext, ToolExecutionResult } from '../types.js';
5
2
  import { upsertContact } from '../../contacts/contact-store.js';
6
- import { CHANNEL_TYPES } from '../../contacts/types.js';
7
3
 
8
4
  function formatContact(c: ReturnType<typeof upsertContact>): string {
9
5
  const lines = [
@@ -25,113 +21,44 @@ function formatContact(c: ReturnType<typeof upsertContact>): string {
25
21
  return lines.join('\n');
26
22
  }
27
23
 
28
- const definition: ToolDefinition = {
29
- name: 'contact_upsert',
30
- description: 'Create or update a contact in the relationship graph. Use this to track people the user interacts with across channels (email, Slack, etc.).',
31
- input_schema: {
32
- type: 'object',
33
- properties: {
34
- id: {
35
- type: 'string',
36
- description: 'Contact ID to update. Omit to create a new contact (or auto-match by channel address).',
37
- },
38
- display_name: {
39
- type: 'string',
40
- description: 'Display name for the contact',
41
- },
42
- relationship: {
43
- type: 'string',
44
- description: 'Relationship type (e.g. colleague, friend, manager, client, family)',
45
- },
46
- importance: {
47
- type: 'number',
48
- description: 'Importance score 0-1 (default 0.5). Higher = more important.',
49
- },
50
- response_expectation: {
51
- type: 'string',
52
- description: 'Expected response speed (e.g. immediate, within_hours, within_day, casual)',
53
- },
54
- preferred_tone: {
55
- type: 'string',
56
- description: 'Preferred communication tone (e.g. formal, casual, friendly, professional)',
57
- },
58
- channels: {
59
- type: 'array',
60
- description: 'Communication channels for this contact',
61
- items: {
62
- type: 'object',
63
- properties: {
64
- type: {
65
- type: 'string',
66
- enum: [...CHANNEL_TYPES],
67
- description: 'Channel type',
68
- },
69
- address: {
70
- type: 'string',
71
- description: 'Channel address (email address, Slack handle, phone number, etc.)',
72
- },
73
- is_primary: {
74
- type: 'boolean',
75
- description: 'Whether this is the primary channel for this type',
76
- },
77
- },
78
- required: ['type', 'address'],
79
- },
80
- },
81
- },
82
- required: ['display_name'],
83
- },
84
- };
85
-
86
- class ContactUpsertTool implements Tool {
87
- name = 'contact_upsert';
88
- description = definition.description;
89
- category = 'contacts';
90
- defaultRiskLevel = RiskLevel.Low;
91
-
92
- getDefinition(): ToolDefinition {
93
- return definition;
24
+ export async function executeContactUpsert(
25
+ input: Record<string, unknown>,
26
+ _context: ToolContext,
27
+ ): Promise<ToolExecutionResult> {
28
+ const displayName = input.display_name as string | undefined;
29
+ if (!displayName || typeof displayName !== 'string' || displayName.trim().length === 0) {
30
+ return { content: 'Error: display_name is required and must be a non-empty string', isError: true };
94
31
  }
95
32
 
96
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
97
- const displayName = input.display_name as string | undefined;
98
- if (!displayName || typeof displayName !== 'string' || displayName.trim().length === 0) {
99
- return { content: 'Error: display_name is required and must be a non-empty string', isError: true };
100
- }
101
-
102
- const importance = input.importance as number | undefined;
103
- if (importance !== undefined && (typeof importance !== 'number' || importance < 0 || importance > 1)) {
104
- return { content: 'Error: importance must be a number between 0 and 1', isError: true };
105
- }
33
+ const importance = input.importance as number | undefined;
34
+ if (importance !== undefined && (typeof importance !== 'number' || importance < 0 || importance > 1)) {
35
+ return { content: 'Error: importance must be a number between 0 and 1', isError: true };
36
+ }
106
37
 
107
- const rawChannels = input.channels as Array<{ type: string; address: string; is_primary?: boolean }> | undefined;
108
- const channels = rawChannels?.map((ch) => ({
109
- type: ch.type,
110
- address: ch.address,
111
- isPrimary: ch.is_primary,
112
- }));
38
+ const rawChannels = input.channels as Array<{ type: string; address: string; is_primary?: boolean }> | undefined;
39
+ const channels = rawChannels?.map((ch) => ({
40
+ type: ch.type,
41
+ address: ch.address,
42
+ isPrimary: ch.is_primary,
43
+ }));
113
44
 
114
- try {
115
- const contact = upsertContact({
116
- id: input.id as string | undefined,
117
- displayName: displayName.trim(),
118
- relationship: input.relationship as string | undefined,
119
- importance,
120
- responseExpectation: input.response_expectation as string | undefined,
121
- preferredTone: input.preferred_tone as string | undefined,
122
- channels,
123
- });
45
+ try {
46
+ const contact = upsertContact({
47
+ id: input.id as string | undefined,
48
+ displayName: displayName.trim(),
49
+ relationship: input.relationship as string | undefined,
50
+ importance,
51
+ responseExpectation: input.response_expectation as string | undefined,
52
+ preferredTone: input.preferred_tone as string | undefined,
53
+ channels,
54
+ });
124
55
 
125
- const isNew = contact.createdAt === contact.updatedAt;
126
- return {
127
- content: `${isNew ? 'Created' : 'Updated'} contact:\n${formatContact(contact)}`,
128
- isError: false,
129
- };
130
- } catch (err) {
131
- const msg = err instanceof Error ? err.message : String(err);
132
- return { content: `Error: ${msg}`, isError: true };
133
- }
56
+ return {
57
+ content: `${contact.created ? 'Created' : 'Updated'} contact:\n${formatContact(contact)}`,
58
+ isError: false,
59
+ };
60
+ } catch (err) {
61
+ const msg = err instanceof Error ? err.message : String(err);
62
+ return { content: `Error: ${msg}`, isError: true };
134
63
  }
135
64
  }
136
-
137
- registerTool(new ContactUpsertTool());
@@ -95,15 +95,19 @@ function findStoredOAuthField(service: string, fieldNames: string[]): string | u
95
95
  }
96
96
  }
97
97
 
98
- // Fallback: check credential metadata on the access_token record, where
99
- // oauth2_connect persists the client config after a successful flow.
98
+ // Legacy fallback: check credential metadata on the access_token record.
99
+ // Older OAuth2 flows stored client_id/client_secret only in metadata JSON.
100
+ // New flows persist them in the keychain (checked above) for defense in depth.
100
101
  const metadataKey = fieldNames.some((f) => f.includes('client_id'))
101
102
  ? 'oauth2ClientId' as const
102
103
  : 'oauth2ClientSecret' as const;
103
104
  for (const svc of servicesToCheck) {
104
105
  const meta = getCredentialMetadata(svc, 'access_token');
105
106
  const value = meta?.[metadataKey];
106
- if (value) return value;
107
+ if (value) {
108
+ log.debug({ service: svc, field: metadataKey }, 'OAuth client credential resolved from metadata (legacy fallback)');
109
+ return value;
110
+ }
107
111
  }
108
112
 
109
113
  return undefined;
@@ -113,7 +117,7 @@ class CredentialStoreTool implements Tool {
113
117
  name = 'credential_store';
114
118
  description = 'Store, list, delete, or prompt for credentials in the secure vault';
115
119
  category = 'credentials';
116
- defaultRiskLevel = RiskLevel.Medium;
120
+ defaultRiskLevel = RiskLevel.Low;
117
121
 
118
122
  getDefinition(): ToolDefinition {
119
123
  return {
@@ -569,7 +573,7 @@ class CredentialStoreTool implements Tool {
569
573
  return { content: 'Error: failed to store access token in secure storage', isError: true };
570
574
  }
571
575
 
572
- const expiresAt = tokens.expiresIn ? Date.now() + tokens.expiresIn * 1000 : undefined;
576
+ const expiresAt = tokens.expiresIn ? Date.now() + tokens.expiresIn * 1000 : null;
573
577
 
574
578
  let accountInfo: string | undefined;
575
579
  if (userinfoUrl && grantedScopes.some((s) => s.includes('userinfo'))) {
@@ -586,6 +590,18 @@ class CredentialStoreTool implements Tool {
586
590
  }
587
591
  }
588
592
 
593
+ // Persist client credentials in keychain for defense in depth
594
+ const clientIdStored = setSecureKey(`credential:${service}:client_id`, clientId);
595
+ if (!clientIdStored) {
596
+ return { content: 'Error: failed to store client_id in secure storage', isError: true };
597
+ }
598
+ if (clientSecret) {
599
+ const clientSecretStored = setSecureKey(`credential:${service}:client_secret`, clientSecret);
600
+ if (!clientSecretStored) {
601
+ return { content: 'Error: failed to store client_secret in secure storage', isError: true };
602
+ }
603
+ }
604
+
589
605
  upsertCredentialMetadata(service, 'access_token', {
590
606
  allowedTools: allowedTools ?? [],
591
607
  expiresAt,