vellum 0.2.1 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +15 -2
  2. package/bun.lock +71 -100
  3. package/package.json +5 -3
  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 +305 -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-twilio-config.test.ts +221 -0
  36. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  37. package/src/__tests__/intent-routing.test.ts +64 -57
  38. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  39. package/src/__tests__/ipc-snapshot.test.ts +71 -28
  40. package/src/__tests__/llm-usage-store.test.ts +3 -8
  41. package/src/__tests__/media-generate-image.test.ts +1 -1
  42. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  43. package/src/__tests__/memory-regressions.test.ts +100 -2
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  45. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  46. package/src/__tests__/playbook-tools.test.ts +342 -0
  47. package/src/__tests__/profile-compiler.test.ts +2 -1
  48. package/src/__tests__/provider-commit-message-generator.test.ts +303 -0
  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 +5 -3
  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 +8 -4
  58. package/src/__tests__/run-orchestrator.test.ts +4 -4
  59. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
  60. package/src/__tests__/runtime-runs-http.test.ts +4 -4
  61. package/src/__tests__/runtime-runs.test.ts +4 -4
  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-conflict-gate.test.ts +28 -25
  67. package/src/__tests__/session-error.test.ts +28 -0
  68. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  69. package/src/__tests__/session-queue.test.ts +71 -48
  70. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  71. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  72. package/src/__tests__/signup-e2e.test.ts +2 -1
  73. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  74. package/src/__tests__/skill-script-runner.test.ts +159 -0
  75. package/src/__tests__/speaker-identification.test.ts +52 -0
  76. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  77. package/src/__tests__/subagent-tools.test.ts +141 -41
  78. package/src/__tests__/task-compiler.test.ts +2 -1
  79. package/src/__tests__/task-runner.test.ts +2 -1
  80. package/src/__tests__/task-scheduler.test.ts +2 -1
  81. package/src/__tests__/task-tools.test.ts +49 -56
  82. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  83. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  84. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  85. package/src/__tests__/tool-executor.test.ts +13 -17
  86. package/src/__tests__/turn-commit.test.ts +218 -3
  87. package/src/__tests__/twilio-provider.test.ts +143 -0
  88. package/src/__tests__/twilio-routes.test.ts +789 -0
  89. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  90. package/src/__tests__/view-image-tool.test.ts +217 -0
  91. package/src/__tests__/workspace-git-service.test.ts +186 -0
  92. package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
  93. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  94. package/src/bundler/app-bundler.ts +12 -8
  95. package/src/calls/__tests__/twilio-webhook-urls.test.ts +162 -0
  96. package/src/calls/call-bridge.ts +95 -0
  97. package/src/calls/call-constants.ts +43 -5
  98. package/src/calls/call-domain.ts +276 -0
  99. package/src/calls/call-orchestrator.ts +43 -17
  100. package/src/calls/call-recovery.ts +207 -0
  101. package/src/calls/call-state-machine.ts +68 -0
  102. package/src/calls/call-store.ts +192 -5
  103. package/src/calls/relay-server.ts +41 -4
  104. package/src/calls/speaker-identification.ts +213 -0
  105. package/src/calls/twilio-config.ts +8 -8
  106. package/src/calls/twilio-provider.ts +13 -9
  107. package/src/calls/twilio-routes.ts +90 -76
  108. package/src/calls/twilio-webhook-urls.ts +50 -0
  109. package/src/calls/types.ts +1 -1
  110. package/src/cli/config-commands.ts +334 -0
  111. package/src/cli/core-commands.ts +776 -0
  112. package/src/cli/doordash.ts +251 -1
  113. package/src/cli/ipc-client.ts +82 -0
  114. package/src/cli/map.ts +270 -0
  115. package/src/cli/twitter.ts +575 -0
  116. package/src/cli.ts +7 -5
  117. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  118. package/src/commands/cc-command-registry.ts +209 -0
  119. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  120. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  121. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  122. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  123. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  124. package/src/config/bundled-skills/document/SKILL.md +18 -0
  125. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  126. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  127. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  128. package/src/config/bundled-skills/doordash/SKILL.md +82 -23
  129. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  130. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  131. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  132. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  133. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  134. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
  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 +34 -0
  179. package/src/config/loader.ts +4 -1
  180. package/src/config/schema.ts +165 -1
  181. package/src/config/system-prompt.ts +61 -16
  182. package/src/config/templates/IDENTITY.md +7 -0
  183. package/src/config/types.ts +4 -0
  184. package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -5
  185. package/src/contacts/contact-store.ts +4 -4
  186. package/src/daemon/assistant-attachments.ts +10 -0
  187. package/src/daemon/classifier.ts +3 -1
  188. package/src/daemon/computer-use-session.ts +3 -1
  189. package/src/daemon/date-context.ts +136 -0
  190. package/src/daemon/handlers/apps.ts +16 -1
  191. package/src/daemon/handlers/browser.ts +54 -0
  192. package/src/daemon/handlers/computer-use.ts +7 -1
  193. package/src/daemon/handlers/config.ts +205 -5
  194. package/src/daemon/handlers/diagnostics.ts +5 -1
  195. package/src/daemon/handlers/documents.ts +18 -29
  196. package/src/daemon/handlers/home-base.ts +5 -1
  197. package/src/daemon/handlers/index.ts +40 -277
  198. package/src/daemon/handlers/misc.ts +9 -1
  199. package/src/daemon/handlers/publish.ts +6 -1
  200. package/src/daemon/handlers/sessions.ts +65 -12
  201. package/src/daemon/handlers/shared.ts +36 -1
  202. package/src/daemon/handlers/signing.ts +37 -0
  203. package/src/daemon/handlers/skills.ts +20 -6
  204. package/src/daemon/handlers/subagents.ts +8 -3
  205. package/src/daemon/handlers/twitter-auth.ts +169 -0
  206. package/src/daemon/handlers/work-items.ts +384 -68
  207. package/src/daemon/ipc-contract-inventory.json +32 -4
  208. package/src/daemon/ipc-contract.ts +156 -37
  209. package/src/daemon/ipc-protocol.ts +7 -2
  210. package/src/daemon/lifecycle.ts +21 -0
  211. package/src/daemon/main.ts +10 -4
  212. package/src/daemon/ride-shotgun-handler.ts +75 -10
  213. package/src/daemon/server.ts +143 -26
  214. package/src/daemon/session-agent-loop.ts +922 -0
  215. package/src/daemon/session-attachments.ts +28 -5
  216. package/src/daemon/session-conflict-gate.ts +18 -109
  217. package/src/daemon/session-error.ts +24 -3
  218. package/src/daemon/session-lifecycle.ts +147 -0
  219. package/src/daemon/session-media-retry.ts +147 -0
  220. package/src/daemon/session-messaging.ts +145 -0
  221. package/src/daemon/session-notifiers.ts +164 -0
  222. package/src/daemon/session-process.ts +2 -2
  223. package/src/daemon/session-queue-manager.ts +1 -0
  224. package/src/daemon/session-runtime-assembly.ts +52 -0
  225. package/src/daemon/session-skill-tools.ts +124 -5
  226. package/src/daemon/session-slash.ts +3 -0
  227. package/src/daemon/session-surfaces.ts +77 -2
  228. package/src/daemon/session-tool-setup.ts +216 -2
  229. package/src/daemon/session-usage.ts +0 -2
  230. package/src/daemon/session.ts +114 -1404
  231. package/src/daemon/video-thumbnail.ts +60 -0
  232. package/src/doordash/client.ts +121 -27
  233. package/src/doordash/queries.ts +1 -2
  234. package/src/export/formatter.ts +3 -1
  235. package/src/followups/followup-store.ts +4 -2
  236. package/src/followups/types.ts +6 -0
  237. package/src/hooks/templates.ts +1 -1
  238. package/src/index.ts +32 -1153
  239. package/src/memory/attachments-store.ts +28 -83
  240. package/src/memory/channel-delivery-store.ts +7 -21
  241. package/src/memory/clarification-resolver.ts +6 -5
  242. package/src/memory/conflict-intent.ts +114 -0
  243. package/src/memory/contradiction-checker.ts +3 -2
  244. package/src/memory/conversation-key-store.ts +10 -29
  245. package/src/memory/conversation-store.ts +2 -1
  246. package/src/memory/db.ts +96 -2
  247. package/src/memory/entity-extractor.ts +6 -3
  248. package/src/memory/items-extractor.ts +5 -4
  249. package/src/memory/job-handlers/conflict.ts +23 -1
  250. package/src/memory/jobs-store.ts +3 -2
  251. package/src/memory/llm-usage-store.ts +1 -2
  252. package/src/memory/runs-store.ts +1 -2
  253. package/src/memory/schema.ts +23 -2
  254. package/src/messaging/style-analyzer.ts +3 -2
  255. package/src/messaging/thread-summarizer.ts +8 -12
  256. package/src/messaging/triage-engine.ts +4 -2
  257. package/src/providers/openrouter/client.ts +20 -0
  258. package/src/providers/registry.ts +8 -0
  259. package/src/runtime/gateway-client.ts +36 -0
  260. package/src/runtime/http-server.ts +166 -22
  261. package/src/runtime/routes/attachment-routes.ts +2 -3
  262. package/src/runtime/routes/call-routes.ts +140 -0
  263. package/src/runtime/routes/channel-routes.ts +125 -88
  264. package/src/runtime/routes/conversation-routes.ts +5 -5
  265. package/src/runtime/routes/run-routes.ts +2 -2
  266. package/src/runtime/run-orchestrator.ts +9 -3
  267. package/src/schedule/recurrence-engine.ts +138 -0
  268. package/src/schedule/recurrence-types.ts +67 -0
  269. package/src/schedule/schedule-store.ts +102 -57
  270. package/src/schedule/scheduler.ts +9 -6
  271. package/src/security/oauth2.ts +29 -4
  272. package/src/security/secret-allowlist.ts +46 -0
  273. package/src/skills/clawhub.ts +1 -1
  274. package/src/subagent/manager.ts +40 -8
  275. package/src/swarm/backend-claude-code.ts +64 -9
  276. package/src/swarm/worker-prompts.ts +2 -1
  277. package/src/tasks/SPEC.md +34 -28
  278. package/src/tasks/ephemeral-permissions.ts +16 -7
  279. package/src/tasks/task-compiler.ts +5 -4
  280. package/src/tasks/task-runner.ts +10 -5
  281. package/src/tasks/task-scheduler.ts +1 -1
  282. package/src/tasks/tool-sanitizer.ts +36 -0
  283. package/src/tools/assets/search.ts +4 -4
  284. package/src/tools/browser/api-map.ts +293 -0
  285. package/src/tools/browser/auto-navigate.ts +270 -0
  286. package/src/tools/browser/browser-execution.ts +2 -1
  287. package/src/tools/browser/browser-manager.ts +2 -2
  288. package/src/tools/browser/network-recorder.ts +5 -4
  289. package/src/tools/browser/x-auto-navigate.ts +207 -0
  290. package/src/tools/calls/call-end.ts +17 -67
  291. package/src/tools/calls/call-start.ts +24 -85
  292. package/src/tools/calls/call-status.ts +35 -51
  293. package/src/tools/claude-code/claude-code.ts +207 -11
  294. package/src/tools/contacts/contact-merge.ts +46 -78
  295. package/src/tools/contacts/contact-search.ts +35 -79
  296. package/src/tools/contacts/contact-upsert.ts +35 -108
  297. package/src/tools/credentials/vault.ts +20 -4
  298. package/src/tools/document/document-tool.ts +71 -144
  299. package/src/tools/executor.ts +129 -10
  300. package/src/tools/followups/followup_create.ts +46 -88
  301. package/src/tools/followups/followup_list.ts +34 -74
  302. package/src/tools/followups/followup_resolve.ts +31 -66
  303. package/src/tools/host-terminal/cli-discover.ts +2 -1
  304. package/src/tools/host-terminal/host-shell.ts +10 -0
  305. package/src/tools/memory/handlers.ts +5 -4
  306. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  307. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  308. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  309. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  310. package/src/tools/network/web-fetch.ts +18 -6
  311. package/src/tools/playbooks/index.ts +4 -5
  312. package/src/tools/playbooks/playbook-create.ts +3 -47
  313. package/src/tools/playbooks/playbook-delete.ts +1 -25
  314. package/src/tools/playbooks/playbook-list.ts +1 -28
  315. package/src/tools/playbooks/playbook-update.ts +3 -51
  316. package/src/tools/reminder/reminder.ts +5 -78
  317. package/src/tools/schedule/create.ts +69 -74
  318. package/src/tools/schedule/delete.ts +21 -47
  319. package/src/tools/schedule/list.ts +55 -74
  320. package/src/tools/schedule/update.ts +77 -84
  321. package/src/tools/subagent/abort.ts +29 -58
  322. package/src/tools/subagent/message.ts +30 -63
  323. package/src/tools/subagent/read.ts +53 -84
  324. package/src/tools/subagent/spawn.ts +43 -82
  325. package/src/tools/subagent/status.ts +42 -71
  326. package/src/tools/swarm/delegate.ts +2 -1
  327. package/src/tools/tasks/index.ts +8 -8
  328. package/src/tools/tasks/task-delete.ts +60 -88
  329. package/src/tools/tasks/task-list.ts +31 -52
  330. package/src/tools/tasks/task-run.ts +72 -108
  331. package/src/tools/tasks/task-save.ts +33 -65
  332. package/src/tools/tasks/work-item-enqueue.ts +183 -215
  333. package/src/tools/tasks/work-item-list.ts +33 -63
  334. package/src/tools/tasks/work-item-remove.ts +45 -97
  335. package/src/tools/tasks/work-item-update.ts +91 -163
  336. package/src/tools/terminal/backends/native.ts +3 -1
  337. package/src/tools/tool-manifest.ts +0 -62
  338. package/src/tools/types.ts +6 -0
  339. package/src/tools/ui-surface/definitions.ts +3 -1
  340. package/src/tools/watch/screen-watch.ts +3 -1
  341. package/src/tools/watcher/create.ts +52 -98
  342. package/src/tools/watcher/delete.ts +20 -46
  343. package/src/tools/watcher/digest.ts +36 -70
  344. package/src/tools/watcher/list.ts +49 -79
  345. package/src/tools/watcher/update.ts +45 -91
  346. package/src/twitter/client.ts +690 -0
  347. package/src/twitter/session.ts +91 -0
  348. package/src/usage/types.ts +0 -1
  349. package/src/util/truncate.ts +6 -0
  350. package/src/watcher/providers/slack.ts +2 -1
  351. package/src/watcher/watcher-store.ts +3 -2
  352. package/src/work-items/work-item-store.ts +27 -2
  353. package/src/workspace/commit-message-enrichment-service.ts +31 -7
  354. package/src/workspace/git-service.ts +87 -22
  355. package/src/workspace/provider-commit-message-generator.ts +269 -0
  356. package/src/workspace/turn-commit.ts +62 -3
  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
@@ -17,6 +17,7 @@ import { getConfig } from '../config/loader.js';
17
17
  import { scanText, redactSecrets } from '../security/secret-scanner.js';
18
18
  import { redactSensitiveFields } from '../security/redaction.js';
19
19
  import { getHookManager } from '../hooks/manager.js';
20
+ import { getTaskRunRules } from '../tasks/ephemeral-permissions.js';
20
21
 
21
22
  const log = getLogger('tool-executor');
22
23
 
@@ -67,10 +68,39 @@ export class ToolExecutor {
67
68
  durationMs,
68
69
  errorMessage: msg,
69
70
  isExpected: true,
71
+ errorCategory: 'tool_failure',
70
72
  });
71
73
  return { content: msg, isError: true };
72
74
  }
73
75
 
76
+ // Belt-and-suspenders guard for task runs: only preflight-approved tools
77
+ // may execute. This catches cases where ephemeral rules might not cover
78
+ // a tool, ensuring unapproved calls fail deterministically instead of
79
+ // falling through to the interactive prompter.
80
+ if (context.taskRunId) {
81
+ const taskRules = getTaskRunRules(context.taskRunId);
82
+ const approvedToolNames = new Set(taskRules.map((r) => r.tool));
83
+ if (approvedToolNames.size > 0 && !approvedToolNames.has(name)) {
84
+ const msg = `Tool '${name}' was not approved in the task's preflight. Add it to required tools and re-approve.`;
85
+ const durationMs = Date.now() - startTime;
86
+ emitLifecycleEvent(context, {
87
+ type: 'permission_denied',
88
+ toolName: name,
89
+ executionTarget,
90
+ input,
91
+ workingDir: context.workingDir,
92
+ sessionId: context.sessionId,
93
+ conversationId: context.conversationId,
94
+ requestId: context.requestId,
95
+ riskLevel,
96
+ decision: 'deny',
97
+ reason: msg,
98
+ durationMs,
99
+ });
100
+ return { content: msg, isError: true };
101
+ }
102
+ }
103
+
74
104
  const tool = getTool(name);
75
105
  if (!tool) {
76
106
  const available = getAllTools().filter((t) => t.executionMode !== 'proxy' || context.proxyToolResolver).map((t) => t.name).sort().join(', ');
@@ -90,6 +120,7 @@ export class ToolExecutor {
90
120
  durationMs,
91
121
  errorMessage: msg,
92
122
  isExpected: true,
123
+ errorCategory: 'tool_failure',
93
124
  });
94
125
  return { content: msg, isError: true };
95
126
  }
@@ -100,8 +131,9 @@ export class ToolExecutor {
100
131
  riskLevel = risk;
101
132
 
102
133
  // Build principal context from tool metadata so policy rules can
103
- // distinguish skill-provided tools from core built-ins.
104
- const policyContext = buildPolicyContext(tool);
134
+ // distinguish skill-provided tools from core built-ins. Also includes
135
+ // ephemeral rules when executing within a task run.
136
+ const policyContext = buildPolicyContext(tool, context);
105
137
  const result = await check(name, input, context.workingDir, policyContext);
106
138
 
107
139
  // Private threads force prompting for side-effect tools even when a
@@ -137,6 +169,32 @@ export class ToolExecutor {
137
169
  }
138
170
 
139
171
  if (result.decision === 'prompt') {
172
+ // Non-interactive sessions have no client to respond to prompts —
173
+ // deny immediately instead of blocking for the full permission timeout.
174
+ if (context.isInteractive === false) {
175
+ decision = 'denied';
176
+ const durationMs = Date.now() - startTime;
177
+ log.info({ toolName: name, riskLevel }, 'Auto-denying prompt for non-interactive session');
178
+ emitLifecycleEvent(context, {
179
+ type: 'permission_denied',
180
+ toolName: name,
181
+ executionTarget,
182
+ input,
183
+ workingDir: context.workingDir,
184
+ sessionId: context.sessionId,
185
+ conversationId: context.conversationId,
186
+ requestId: context.requestId,
187
+ riskLevel,
188
+ decision: 'deny',
189
+ reason: 'Non-interactive session: no client to approve prompt',
190
+ durationMs,
191
+ });
192
+ return {
193
+ content: `Permission denied: tool "${name}" requires user approval but no interactive client is connected. The tool was not executed. To allow this tool in non-interactive sessions, add a trust rule via permission settings.`,
194
+ isError: true,
195
+ };
196
+ }
197
+
140
198
  // Need user approval
141
199
  const allowlistOptions = generateAllowlistOptions(name, input);
142
200
  const scopeOptions = generateScopeOptions(context.workingDir, name);
@@ -325,6 +383,7 @@ export class ToolExecutor {
325
383
  durationMs,
326
384
  errorMessage: msg,
327
385
  isExpected: true,
386
+ errorCategory: 'tool_failure',
328
387
  });
329
388
  return { content: msg, isError: true };
330
389
  }
@@ -358,6 +417,7 @@ export class ToolExecutor {
358
417
  durationMs,
359
418
  errorMessage: msg,
360
419
  isExpected: true,
420
+ errorCategory: 'tool_failure',
361
421
  });
362
422
  return { content: msg, isError: true };
363
423
  }
@@ -464,6 +524,39 @@ export class ToolExecutor {
464
524
  } else if (sdConfig.action === 'prompt') {
465
525
  // Ask the user whether to allow tool output containing secrets
466
526
  const types = [...new Set(allMatches.map((m) => m.type))].join(', ');
527
+
528
+ // Non-interactive sessions: auto-block secret output instead of waiting for prompt
529
+ if (context.isInteractive === false) {
530
+ const blockedContent = `Tool output blocked: detected ${allMatches.length} potential secret(s) (${types}). No interactive client available to approve.`;
531
+ const durationMs = Date.now() - startTime;
532
+ log.info({ toolName: name }, 'Auto-blocking secret output for non-interactive session');
533
+ emitLifecycleEvent(context, {
534
+ type: 'permission_denied',
535
+ toolName: name,
536
+ executionTarget,
537
+ input,
538
+ workingDir: context.workingDir,
539
+ sessionId: context.sessionId,
540
+ conversationId: context.conversationId,
541
+ requestId: context.requestId,
542
+ riskLevel: RiskLevel.High,
543
+ decision: 'deny',
544
+ reason: 'Non-interactive session: auto-blocked secret output',
545
+ durationMs,
546
+ });
547
+
548
+ void getHookManager().trigger('post-tool-execute', {
549
+ toolName: name,
550
+ input: sanitizeToolInput(name, input),
551
+ riskLevel,
552
+ isError: true,
553
+ durationMs,
554
+ sessionId: context.sessionId,
555
+ });
556
+
557
+ return { content: blockedContent, isError: true };
558
+ }
559
+
467
560
  const promptInput = {
468
561
  _secretDetection: true,
469
562
  summary: `Tool output contains ${allMatches.length} potential secret(s): ${types}`,
@@ -565,6 +658,14 @@ export class ToolExecutor {
565
658
  const msg = err instanceof Error ? err.message : String(err);
566
659
  const isExpected = err instanceof PermissionDeniedError || err instanceof ToolError || err instanceof TokenExpiredError;
567
660
 
661
+ const errorCategory = err instanceof PermissionDeniedError
662
+ ? 'permission_denied' as const
663
+ : err instanceof TokenExpiredError
664
+ ? 'auth' as const
665
+ : err instanceof ToolError
666
+ ? 'tool_failure' as const
667
+ : 'unexpected' as const;
668
+
568
669
  emitLifecycleEvent(context, {
569
670
  type: 'error',
570
671
  toolName: name,
@@ -579,6 +680,7 @@ export class ToolExecutor {
579
680
  durationMs,
580
681
  errorMessage: msg,
581
682
  isExpected,
683
+ errorCategory,
582
684
  errorName: err instanceof Error ? err.name : undefined,
583
685
  errorStack: err instanceof Error ? err.stack : undefined,
584
686
  });
@@ -622,6 +724,8 @@ const SIDE_EFFECT_TOOLS: ReadonlySet<string> = new Set([
622
724
  'browser_fill_credential',
623
725
  'document_create',
624
726
  'document_update',
727
+ 'reminder_create',
728
+ 'reminder_cancel',
625
729
  'schedule_create',
626
730
  'schedule_update',
627
731
  'schedule_delete',
@@ -644,10 +748,6 @@ export function isSideEffectTool(toolName: string, input?: Record<string, unknow
644
748
  const action = input?.action;
645
749
  return action === 'create' || action === 'update';
646
750
  }
647
- if (toolName === 'reminder') {
648
- const action = input?.action;
649
- return action === 'create' || action === 'cancel';
650
- }
651
751
  if (toolName === 'credential_store') {
652
752
  const action = input?.action;
653
753
  return action === 'store' || action === 'delete' || action === 'prompt' || action === 'oauth2_connect';
@@ -705,11 +805,16 @@ async function executeWithTimeout(
705
805
  }
706
806
 
707
807
  /**
708
- * Build a PolicyContext from tool metadata. Skill-origin tools carry a
709
- * principal identifying the owning skill; core tools yield an undefined
710
- * context so the checker applies default (user) policy.
808
+ * Build a PolicyContext from tool metadata and execution context. Skill-origin
809
+ * tools carry a principal identifying the owning skill. When executing within
810
+ * a task run, ephemeral permission rules are included so pre-approved tools
811
+ * are auto-allowed without prompting.
711
812
  */
712
- function buildPolicyContext(tool: Tool): PolicyContext | undefined {
813
+ function buildPolicyContext(tool: Tool, context?: ToolContext): PolicyContext | undefined {
814
+ const ephemeralRules = context?.taskRunId
815
+ ? getTaskRunRules(context.taskRunId)
816
+ : undefined;
817
+
713
818
  if (tool.origin === 'skill') {
714
819
  return {
715
820
  principal: {
@@ -718,8 +823,22 @@ function buildPolicyContext(tool: Tool): PolicyContext | undefined {
718
823
  version: tool.ownerSkillVersionHash,
719
824
  },
720
825
  executionTarget: tool.executionTarget,
826
+ ephemeralRules: ephemeralRules?.length ? ephemeralRules : undefined,
721
827
  };
722
828
  }
829
+
830
+ // For non-skill tools in a task run, create a context with task principal
831
+ // and ephemeral rules so pre-approved tools are honored.
832
+ if (context?.taskRunId && ephemeralRules?.length) {
833
+ return {
834
+ principal: {
835
+ kind: 'task',
836
+ id: context.taskRunId,
837
+ },
838
+ ephemeralRules,
839
+ };
840
+ }
841
+
723
842
  return undefined;
724
843
  }
725
844
 
@@ -1,6 +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';
1
+ import type { ToolContext, ToolExecutionResult } from '../types.js';
4
2
  import { createFollowUp } from '../../followups/followup-store.js';
5
3
  import { getContact } from '../../contacts/contact-store.js';
6
4
  import type { FollowUp } from '../../followups/types.js';
@@ -17,102 +15,62 @@ function formatFollowUp(f: FollowUp): string {
17
15
  if (f.expectedResponseBy) {
18
16
  lines.push(` Expected response by: ${new Date(f.expectedResponseBy).toISOString()}`);
19
17
  }
20
- if (f.reminderCronId) lines.push(` Reminder cron: ${f.reminderCronId}`);
18
+ if (f.reminderScheduleId) lines.push(` Reminder schedule: ${f.reminderScheduleId}`);
21
19
  return lines.join('\n');
22
20
  }
23
21
 
24
- const definition: ToolDefinition = {
25
- name: 'followup_create',
26
- description: 'Create a follow-up tracker for a sent message. Tracks conversations awaiting a response across channels (email, Slack, WhatsApp, etc.).',
27
- input_schema: {
28
- type: 'object',
29
- properties: {
30
- channel: {
31
- type: 'string',
32
- description: 'Communication channel (e.g. email, slack, whatsapp)',
33
- },
34
- thread_id: {
35
- type: 'string',
36
- description: 'External thread or conversation identifier on that channel',
37
- },
38
- contact_id: {
39
- type: 'string',
40
- description: 'Optional contact ID from the contact graph. Used for grace period context.',
41
- },
42
- expected_response_hours: {
43
- type: 'number',
44
- description: 'Hours to wait for a response before marking as overdue. If omitted, no deadline is set.',
45
- },
46
- reminder_cron_id: {
47
- type: 'string',
48
- description: 'Optional cron job ID to fire a reminder when overdue',
49
- },
50
- },
51
- required: ['channel', 'thread_id'],
52
- },
53
- };
54
-
55
- class FollowUpCreateTool implements Tool {
56
- name = 'followup_create';
57
- description = definition.description;
58
- category = 'followups';
59
- defaultRiskLevel = RiskLevel.Low;
60
-
61
- getDefinition(): ToolDefinition {
62
- return definition;
22
+ export async function executeFollowupCreate(
23
+ input: Record<string, unknown>,
24
+ _context: ToolContext,
25
+ ): Promise<ToolExecutionResult> {
26
+ const channel = input.channel as string | undefined;
27
+ if (!channel || typeof channel !== 'string' || channel.trim().length === 0) {
28
+ return { content: 'Error: channel is required and must be a non-empty string', isError: true };
63
29
  }
64
30
 
65
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
66
- const channel = input.channel as string | undefined;
67
- if (!channel || typeof channel !== 'string' || channel.trim().length === 0) {
68
- return { content: 'Error: channel is required and must be a non-empty string', isError: true };
69
- }
70
-
71
- const threadId = input.thread_id as string | undefined;
72
- if (!threadId || typeof threadId !== 'string' || threadId.trim().length === 0) {
73
- return { content: 'Error: thread_id is required and must be a non-empty string', isError: true };
74
- }
31
+ const threadId = input.thread_id as string | undefined;
32
+ if (!threadId || typeof threadId !== 'string' || threadId.trim().length === 0) {
33
+ return { content: 'Error: thread_id is required and must be a non-empty string', isError: true };
34
+ }
75
35
 
76
- const contactId = input.contact_id as string | undefined;
77
- const expectedResponseHours = input.expected_response_hours as number | undefined;
78
- const reminderCronId = input.reminder_cron_id as string | undefined;
36
+ const contactId = input.contact_id as string | undefined;
37
+ const expectedResponseHours = input.expected_response_hours as number | undefined;
38
+ // Canonical: reminder_schedule_id; deprecated alias: reminder_cron_id
39
+ const reminderScheduleId = (input.reminder_schedule_id ?? input.reminder_cron_id) as string | undefined;
79
40
 
80
- // Validate contact exists if provided
81
- if (contactId) {
82
- const contact = getContact(contactId);
83
- if (!contact) {
84
- return { content: `Error: Contact "${contactId}" not found`, isError: true };
85
- }
41
+ // Validate contact exists if provided
42
+ if (contactId) {
43
+ const contact = getContact(contactId);
44
+ if (!contact) {
45
+ return { content: `Error: Contact "${contactId}" not found`, isError: true };
86
46
  }
47
+ }
87
48
 
88
- if (expectedResponseHours !== undefined && (typeof expectedResponseHours !== 'number' || expectedResponseHours <= 0)) {
89
- return { content: 'Error: expected_response_hours must be a positive number', isError: true };
90
- }
49
+ if (expectedResponseHours !== undefined && (typeof expectedResponseHours !== 'number' || expectedResponseHours <= 0)) {
50
+ return { content: 'Error: expected_response_hours must be a positive number', isError: true };
51
+ }
91
52
 
92
- try {
93
- const now = Date.now();
94
- const expectedResponseBy = expectedResponseHours
95
- ? now + expectedResponseHours * 60 * 60 * 1000
96
- : null;
53
+ try {
54
+ const now = Date.now();
55
+ const expectedResponseBy = expectedResponseHours
56
+ ? now + expectedResponseHours * 60 * 60 * 1000
57
+ : null;
97
58
 
98
- const followUp = createFollowUp({
99
- channel: channel.trim(),
100
- threadId: threadId.trim(),
101
- contactId: contactId ?? null,
102
- sentAt: now,
103
- expectedResponseBy,
104
- reminderCronId: reminderCronId ?? null,
105
- });
59
+ const followUp = createFollowUp({
60
+ channel: channel.trim(),
61
+ threadId: threadId.trim(),
62
+ contactId: contactId ?? null,
63
+ sentAt: now,
64
+ expectedResponseBy,
65
+ reminderScheduleId: reminderScheduleId ?? null,
66
+ });
106
67
 
107
- return {
108
- content: `Created follow-up:\n${formatFollowUp(followUp)}`,
109
- isError: false,
110
- };
111
- } catch (err) {
112
- const msg = err instanceof Error ? err.message : String(err);
113
- return { content: `Error: ${msg}`, isError: true };
114
- }
68
+ return {
69
+ content: `Created follow-up:\n${formatFollowUp(followUp)}`,
70
+ isError: false,
71
+ };
72
+ } catch (err) {
73
+ const msg = err instanceof Error ? err.message : String(err);
74
+ return { content: `Error: ${msg}`, isError: true };
115
75
  }
116
76
  }
117
-
118
- export const followupCreateTool = new FollowUpCreateTool();
@@ -1,6 +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';
1
+ import type { ToolContext, ToolExecutionResult } from '../types.js';
4
2
  import { listFollowUps, getOverdueFollowUps } from '../../followups/followup-store.js';
5
3
  import type { FollowUp, FollowUpStatus } from '../../followups/types.js';
6
4
 
@@ -18,83 +16,45 @@ function formatFollowUpSummary(f: FollowUp): string {
18
16
  return parts.join('\n');
19
17
  }
20
18
 
21
- const definition: ToolDefinition = {
22
- name: 'followup_list',
23
- description: 'List follow-ups with optional filters by status, channel, or contact. Can also show only overdue follow-ups.',
24
- input_schema: {
25
- type: 'object',
26
- properties: {
27
- status: {
28
- type: 'string',
29
- enum: [...VALID_STATUSES],
30
- description: 'Filter by status (pending, resolved, overdue, nudged)',
31
- },
32
- channel: {
33
- type: 'string',
34
- description: 'Filter by communication channel (e.g. email, slack)',
35
- },
36
- contact_id: {
37
- type: 'string',
38
- description: 'Filter by contact ID',
39
- },
40
- overdue_only: {
41
- type: 'boolean',
42
- description: 'When true, return only pending follow-ups past their expected response deadline',
43
- },
44
- },
45
- required: [],
46
- },
47
- };
48
-
49
- class FollowUpListTool implements Tool {
50
- name = 'followup_list';
51
- description = definition.description;
52
- category = 'followups';
53
- defaultRiskLevel = RiskLevel.Low;
54
-
55
- getDefinition(): ToolDefinition {
56
- return definition;
19
+ export async function executeFollowupList(
20
+ input: Record<string, unknown>,
21
+ _context: ToolContext,
22
+ ): Promise<ToolExecutionResult> {
23
+ const status = input.status as FollowUpStatus | undefined;
24
+ const channel = input.channel as string | undefined;
25
+ const contactId = input.contact_id as string | undefined;
26
+ const overdueOnly = input.overdue_only as boolean | undefined;
27
+
28
+ if (status && !VALID_STATUSES.includes(status as typeof VALID_STATUSES[number])) {
29
+ return {
30
+ content: `Error: Invalid status "${status}". Must be one of: ${VALID_STATUSES.join(', ')}`,
31
+ isError: true,
32
+ };
57
33
  }
58
34
 
59
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
60
- const status = input.status as FollowUpStatus | undefined;
61
- const channel = input.channel as string | undefined;
62
- const contactId = input.contact_id as string | undefined;
63
- const overdueOnly = input.overdue_only as boolean | undefined;
35
+ try {
36
+ let results: FollowUp[];
64
37
 
65
- if (status && !VALID_STATUSES.includes(status as typeof VALID_STATUSES[number])) {
66
- return {
67
- content: `Error: Invalid status "${status}". Must be one of: ${VALID_STATUSES.join(', ')}`,
68
- isError: true,
69
- };
38
+ if (overdueOnly || status === 'overdue') {
39
+ results = getOverdueFollowUps();
40
+ if (channel) results = results.filter((f) => f.channel === channel);
41
+ if (contactId) results = results.filter((f) => f.contactId === contactId);
42
+ } else {
43
+ results = listFollowUps({ status, channel, contactId });
70
44
  }
71
45
 
72
- try {
73
- let results: FollowUp[];
74
-
75
- if (overdueOnly || status === 'overdue') {
76
- results = getOverdueFollowUps();
77
- if (channel) results = results.filter((f) => f.channel === channel);
78
- if (contactId) results = results.filter((f) => f.contactId === contactId);
79
- } else {
80
- results = listFollowUps({ status, channel, contactId });
81
- }
82
-
83
- if (results.length === 0) {
84
- return { content: 'No follow-ups found matching the criteria.', isError: false };
85
- }
86
-
87
- const lines = [`Found ${results.length} follow-up(s):\n`];
88
- for (const followUp of results) {
89
- lines.push(formatFollowUpSummary(followUp));
90
- }
46
+ if (results.length === 0) {
47
+ return { content: 'No follow-ups found matching the criteria.', isError: false };
48
+ }
91
49
 
92
- return { content: lines.join('\n'), isError: false };
93
- } catch (err) {
94
- const msg = err instanceof Error ? err.message : String(err);
95
- return { content: `Error: ${msg}`, isError: true };
50
+ const lines = [`Found ${results.length} follow-up(s):\n`];
51
+ for (const followUp of results) {
52
+ lines.push(formatFollowUpSummary(followUp));
96
53
  }
54
+
55
+ return { content: lines.join('\n'), isError: false };
56
+ } catch (err) {
57
+ const msg = err instanceof Error ? err.message : String(err);
58
+ return { content: `Error: ${msg}`, isError: true };
97
59
  }
98
60
  }
99
-
100
- export const followupListTool = new FollowUpListTool();
@@ -1,6 +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';
1
+ import type { ToolContext, ToolExecutionResult } from '../types.js';
4
2
  import { resolveFollowUp, resolveByThread } from '../../followups/followup-store.js';
5
3
  import type { FollowUp } from '../../followups/types.js';
6
4
 
@@ -15,77 +13,44 @@ function formatFollowUp(f: FollowUp): string {
15
13
  return lines.join('\n');
16
14
  }
17
15
 
18
- const definition: ToolDefinition = {
19
- name: 'followup_resolve',
20
- description: 'Manually resolve a follow-up by ID, or auto-resolve by channel + thread ID when a response is received.',
21
- input_schema: {
22
- type: 'object',
23
- properties: {
24
- id: {
25
- type: 'string',
26
- description: 'Follow-up ID to resolve directly',
27
- },
28
- channel: {
29
- type: 'string',
30
- description: 'Channel to match (used with thread_id for auto-resolution)',
31
- },
32
- thread_id: {
33
- type: 'string',
34
- description: 'Thread ID to match (used with channel for auto-resolution)',
35
- },
36
- },
37
- required: [],
38
- },
39
- };
40
-
41
- class FollowUpResolveTool implements Tool {
42
- name = 'followup_resolve';
43
- description = definition.description;
44
- category = 'followups';
45
- defaultRiskLevel = RiskLevel.Low;
46
-
47
- getDefinition(): ToolDefinition {
48
- return definition;
16
+ export async function executeFollowupResolve(
17
+ input: Record<string, unknown>,
18
+ _context: ToolContext,
19
+ ): Promise<ToolExecutionResult> {
20
+ const id = input.id as string | undefined;
21
+ const channel = input.channel as string | undefined;
22
+ const threadId = input.thread_id as string | undefined;
23
+
24
+ if (!id && !(channel && threadId)) {
25
+ return {
26
+ content: 'Error: Either id or both channel and thread_id are required',
27
+ isError: true,
28
+ };
49
29
  }
50
30
 
51
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
52
- const id = input.id as string | undefined;
53
- const channel = input.channel as string | undefined;
54
- const threadId = input.thread_id as string | undefined;
55
-
56
- if (!id && !(channel && threadId)) {
31
+ try {
32
+ if (id) {
33
+ const followUp = resolveFollowUp(id);
57
34
  return {
58
- content: 'Error: Either id or both channel and thread_id are required',
59
- isError: true,
35
+ content: `Resolved follow-up:\n${formatFollowUp(followUp)}`,
36
+ isError: false,
60
37
  };
61
- }
62
-
63
- try {
64
- if (id) {
65
- const followUp = resolveFollowUp(id);
38
+ } else {
39
+ const resolved = resolveByThread(channel!, threadId!);
40
+ if (resolved.length === 0) {
66
41
  return {
67
- content: `Resolved follow-up:\n${formatFollowUp(followUp)}`,
68
- isError: false,
69
- };
70
- } else {
71
- const resolved = resolveByThread(channel!, threadId!);
72
- if (resolved.length === 0) {
73
- return {
74
- content: `No pending follow-up found for channel="${channel}" thread="${threadId}"`,
75
- isError: false,
76
- };
77
- }
78
- const summaries = resolved.map(formatFollowUp).join('\n\n');
79
- return {
80
- content: `Resolved ${resolved.length} follow-up(s):\n${summaries}`,
42
+ content: `No pending follow-up found for channel="${channel}" thread="${threadId}"`,
81
43
  isError: false,
82
44
  };
83
45
  }
84
- } catch (err) {
85
- const msg = err instanceof Error ? err.message : String(err);
86
- return { content: `Error: ${msg}`, isError: true };
46
+ const summaries = resolved.map(formatFollowUp).join('\n\n');
47
+ return {
48
+ content: `Resolved ${resolved.length} follow-up(s):\n${summaries}`,
49
+ isError: false,
50
+ };
87
51
  }
52
+ } catch (err) {
53
+ const msg = err instanceof Error ? err.message : String(err);
54
+ return { content: `Error: ${msg}`, isError: true };
88
55
  }
89
56
  }
90
-
91
- export const followupResolveTool = new FollowUpResolveTool();
@@ -3,6 +3,7 @@ import { RiskLevel } from '../../permissions/types.js';
3
3
  import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
4
4
  import type { ToolDefinition } from '../../providers/types.js';
5
5
  import { getLogger } from '../../util/logger.js';
6
+ import { truncate } from '../../util/truncate.js';
6
7
 
7
8
  const log = getLogger('cli-discover');
8
9
 
@@ -141,7 +142,7 @@ class CliDiscoverTool implements Tool {
141
142
  result.authenticated = authOutput !== null && authOutput.length > 0;
142
143
  if (authOutput) {
143
144
  // Keep auth info brief — first line, max 200 chars
144
- result.authInfo = authOutput.split('\n')[0].slice(0, 200);
145
+ result.authInfo = truncate(authOutput.split('\n')[0], 200, '');
145
146
  }
146
147
  }
147
148