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
@@ -1,165 +1,92 @@
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 { randomUUID } from 'node:crypto';
5
3
 
6
- // ── document_create ──────────────────────────────────────────────────
7
-
8
- export class DocumentCreateTool implements Tool {
9
- name = 'document_create';
10
- description =
11
- 'Create a new long-form document with a rich text editor. Use this when the user asks to write a blog post, article, or any long-form content. The editor opens in workspace mode with chat docked to the side.';
12
- category = 'document';
13
- defaultRiskLevel = RiskLevel.Low;
14
- executionMode = 'local' as const;
15
-
16
- getDefinition(): ToolDefinition {
17
- return {
18
- name: this.name,
19
- description: this.description,
20
- input_schema: {
21
- type: 'object',
22
- properties: {
23
- title: {
24
- type: 'string',
25
- description: 'Initial title for the document (optional, can be updated later)',
26
- },
27
- initial_content: {
28
- type: 'string',
29
- description: 'Initial Markdown content to populate the editor (optional)',
30
- },
31
- },
32
- },
33
- };
34
- }
35
-
36
- async execute(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
37
- const title = (input.title as string | undefined) || 'Untitled Document';
38
- const initialContent = (input.initial_content as string | undefined) || '';
39
- const surfaceId = `doc-${randomUUID()}`;
40
-
41
- // Send document_editor_show IPC message to open the built-in RTE
42
- if (context.sendToClient) {
43
- context.sendToClient({
44
- type: 'document_editor_show',
45
- sessionId: context.sessionId,
46
- surfaceId,
47
- title,
48
- initialContent,
49
- });
50
-
51
- context.sendToClient({
52
- type: 'ui_surface_show',
53
- sessionId: context.sessionId,
54
- surfaceId: `preview-${surfaceId}`,
55
- surfaceType: 'document_preview',
56
- display: 'inline',
4
+ // ── Exported execute functions ──────────────────────────────────────
5
+
6
+ export function executeDocumentCreate(input: Record<string, unknown>, context: ToolContext): ToolExecutionResult {
7
+ const title = (input.title as string | undefined) || 'Untitled Document';
8
+ const initialContent = (input.initial_content as string | undefined) || '';
9
+ const surfaceId = `doc-${randomUUID()}`;
10
+
11
+ // Send document_editor_show IPC message to open the built-in RTE
12
+ if (context.sendToClient) {
13
+ context.sendToClient({
14
+ type: 'document_editor_show',
15
+ sessionId: context.sessionId,
16
+ surfaceId,
17
+ title,
18
+ initialContent,
19
+ });
20
+
21
+ context.sendToClient({
22
+ type: 'ui_surface_show',
23
+ sessionId: context.sessionId,
24
+ surfaceId: `preview-${surfaceId}`,
25
+ surfaceType: 'document_preview',
26
+ display: 'inline',
27
+ title,
28
+ data: {
57
29
  title,
58
- data: {
59
- title,
60
- surfaceId,
61
- subtitle: 'Document',
62
- },
63
- });
64
-
65
- return {
66
- content: JSON.stringify({
67
- surface_id: surfaceId,
68
- title,
69
- opened: true,
70
- message: 'Document editor opened in Directory panel',
71
- }),
72
- isError: false,
73
- };
74
- }
30
+ surfaceId,
31
+ subtitle: 'Document',
32
+ },
33
+ });
75
34
 
76
- // Fallback if no IPC client is connected
77
35
  return {
78
36
  content: JSON.stringify({
79
37
  surface_id: surfaceId,
80
38
  title,
81
- opened: false,
82
- error: 'No IPC client connected to open document editor',
39
+ opened: true,
40
+ message: 'Document editor opened in Directory panel',
83
41
  }),
84
42
  isError: false,
85
43
  };
86
44
  }
87
- }
88
-
89
- // ── document_update ──────────────────────────────────────────────────
90
-
91
- export class DocumentUpdateTool implements Tool {
92
- name = 'document_update';
93
- description =
94
- 'Update content in an open document editor. Use this to stream generated content or apply edits.';
95
- category = 'document';
96
- defaultRiskLevel = RiskLevel.Low;
97
- executionMode = 'local' as const;
98
-
99
- getDefinition(): ToolDefinition {
100
- return {
101
- name: this.name,
102
- description: this.description,
103
- input_schema: {
104
- type: 'object',
105
- properties: {
106
- surface_id: {
107
- type: 'string',
108
- description: 'The ID of the document surface to update',
109
- },
110
- content: {
111
- type: 'string',
112
- description: 'Markdown content to set or append',
113
- },
114
- mode: {
115
- type: 'string',
116
- enum: ['replace', 'append'],
117
- description: 'Whether to replace all content or append to the end. Defaults to append.',
118
- },
119
- },
120
- required: ['surface_id', 'content'],
121
- },
122
- };
123
- }
124
45
 
125
- async execute(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
126
- const surfaceId = input.surface_id as string;
127
- const content = input.content as string;
128
- const mode = (input.mode as string | undefined) || 'append';
129
-
130
- // Send document_editor_update IPC message to update the built-in RTE
131
- if (context.sendToClient) {
132
- context.sendToClient({
133
- type: 'document_editor_update',
134
- sessionId: context.sessionId,
135
- surfaceId,
136
- markdown: content,
137
- mode,
138
- });
46
+ // Fallback if no IPC client is connected
47
+ return {
48
+ content: JSON.stringify({
49
+ surface_id: surfaceId,
50
+ title,
51
+ opened: false,
52
+ error: 'No IPC client connected to open document editor',
53
+ }),
54
+ isError: false,
55
+ };
56
+ }
139
57
 
140
- return {
141
- content: JSON.stringify({
142
- success: true,
143
- surface_id: surfaceId,
144
- mode,
145
- message: 'Document content updated',
146
- }),
147
- isError: false,
148
- };
149
- }
58
+ export function executeDocumentUpdate(input: Record<string, unknown>, context: ToolContext): ToolExecutionResult {
59
+ const surfaceId = input.surface_id as string;
60
+ const content = input.content as string;
61
+ const mode = (input.mode as string | undefined) || 'append';
62
+
63
+ // Send document_editor_update IPC message to update the built-in RTE
64
+ if (context.sendToClient) {
65
+ context.sendToClient({
66
+ type: 'document_editor_update',
67
+ sessionId: context.sessionId,
68
+ surfaceId,
69
+ markdown: content,
70
+ mode,
71
+ });
150
72
 
151
- // Fallback if no IPC client is connected
152
73
  return {
153
74
  content: JSON.stringify({
154
- success: false,
155
- error: 'No IPC client connected to update document',
75
+ success: true,
76
+ surface_id: surfaceId,
77
+ mode,
78
+ message: 'Document content updated',
156
79
  }),
157
- isError: true,
80
+ isError: false,
158
81
  };
159
82
  }
160
- }
161
83
 
162
- // ── Exported tool instances ──────────────────────────────────────────
163
-
164
- export const documentCreateTool = new DocumentCreateTool();
165
- export const documentUpdateTool = new DocumentUpdateTool();
84
+ // Fallback if no IPC client is connected
85
+ return {
86
+ content: JSON.stringify({
87
+ success: false,
88
+ error: 'No IPC client connected to update document',
89
+ }),
90
+ isError: true,
91
+ };
92
+ }
@@ -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();