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,8 +1,7 @@
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 { getTask, listTasks, createTask } from '../../tasks/task-store.js';
5
- import { createWorkItem, findActiveWorkItemsByTitle, updateWorkItem, identifyEntityById, buildWorkItemMismatchError } from '../../work-items/work-item-store.js';
3
+ import { createWorkItemWithPermissions, findActiveWorkItemsByTitle, updateWorkItem, identifyEntityById, buildWorkItemMismatchError } from '../../work-items/work-item-store.js';
4
+ import { sanitizeToolList } from '../../tasks/tool-sanitizer.js';
6
5
  import { getLogger } from '../../util/logger.js';
7
6
 
8
7
  const log = getLogger('task-list-add');
@@ -13,237 +12,114 @@ const PRIORITY_LABELS: Record<number, string> = {
13
12
  2: 'low',
14
13
  };
15
14
 
16
- const definition: ToolDefinition = {
17
- name: 'task_list_add',
18
- description:
19
- 'Add a task to the user\'s Task Queue. Use this when the user says "add to my tasks", "add to my queue", "put this on my task list", "track this task", or any variation of adding a one-off item they want to remember or work on. You can provide just a title for ad-hoc items, or reference an existing task definition by name or ID. Do NOT use schedule_create or reminder for simple "add to tasks" requests — those are for timed/recurring automation only.',
20
- input_schema: {
21
- type: 'object',
22
- properties: {
23
- task_id: {
24
- type: 'string',
25
- description: 'ID of an existing task definition to enqueue. Provide this, task_name, or just title.',
26
- },
27
- task_name: {
28
- type: 'string',
29
- description:
30
- 'Title/name of an existing task definition to search for (case-insensitive substring match). Provide this, task_id, or just title.',
31
- },
32
- title: {
33
- type: 'string',
34
- description:
35
- 'Title for the work item. When provided WITHOUT task_id or task_name, creates an ad-hoc work item directly (a lightweight task template is auto-created behind the scenes). When provided WITH task_id or task_name, overrides the task definition title.',
36
- },
37
- notes: {
38
- type: 'string',
39
- description: 'Notes to attach to the work item.',
40
- },
41
- priority_tier: {
42
- type: 'number',
43
- description: '0 = high, 1 = medium (default), 2 = low.',
44
- },
45
- sort_index: {
46
- type: 'number',
47
- description: 'Manual sort order within the priority tier.',
48
- },
49
- if_exists: {
50
- type: 'string',
51
- enum: ['create_duplicate', 'reuse_existing', 'update_existing'],
52
- description:
53
- 'What to do if an active work item with the same title already exists. Defaults to "reuse_existing".',
54
- },
55
- },
56
- },
57
- };
58
-
59
- class TaskListAddTool implements Tool {
60
- name = 'task_list_add';
61
- description = definition.description;
62
- category = 'tasks';
63
- defaultRiskLevel = RiskLevel.Low;
64
-
65
- getDefinition(): ToolDefinition {
66
- return definition;
15
+ function handleDuplicate(
16
+ title: string,
17
+ ifExists: string,
18
+ input: Record<string, unknown>,
19
+ ): ToolExecutionResult | null {
20
+ const existing = findActiveWorkItemsByTitle(title);
21
+ if (existing.length === 0) return null;
22
+
23
+ const match = existing[0];
24
+ log.info({ title, existingId: match.id, ifExists }, 'task_list_add: duplicate detected');
25
+
26
+ if (ifExists === 'reuse_existing') {
27
+ log.info({ title, existingId: match.id }, 'task_list_add: reused existing item');
28
+ return {
29
+ content: `Task "${match.title}" already exists in the queue (ID: ${match.id}, status: ${match.status}). Use task_list_update to modify it.`,
30
+ isError: false,
31
+ };
67
32
  }
68
33
 
69
- /**
70
- * Check for an existing active work item with the same title and handle
71
- * according to the if_exists strategy. Returns a result if a duplicate was
72
- * found and handled, or null if no duplicate exists (caller should proceed).
73
- */
74
- private handleDuplicate(
75
- title: string,
76
- ifExists: string,
77
- input: Record<string, unknown>,
78
- ): ToolExecutionResult | null {
79
- const existing = findActiveWorkItemsByTitle(title);
80
- if (existing.length === 0) return null;
81
-
82
- const match = existing[0];
83
- log.info({ title, existingId: match.id, ifExists }, 'task_list_add: duplicate detected');
84
-
85
- if (ifExists === 'reuse_existing') {
86
- log.info({ title, existingId: match.id }, 'task_list_add: reused existing item');
87
- return {
88
- content: `Task "${match.title}" already exists in the queue (ID: ${match.id}, status: ${match.status}). Use task_list_update to modify it.`,
89
- isError: false,
90
- };
34
+ if (ifExists === 'update_existing') {
35
+ const updates: Partial<{ title: string; notes: string; priorityTier: number; sortIndex: number }> = {};
36
+ if (input.priority_tier !== undefined) updates.priorityTier = input.priority_tier as number;
37
+ if (input.notes !== undefined) updates.notes = input.notes as string;
38
+ if (input.sort_index !== undefined) updates.sortIndex = input.sort_index as number;
39
+ if (Object.keys(updates).length > 0) {
40
+ updateWorkItem(match.id, updates);
91
41
  }
92
-
93
- if (ifExists === 'update_existing') {
94
- const updates: Partial<{ title: string; notes: string; priorityTier: number; sortIndex: number }> = {};
95
- if (input.priority_tier !== undefined) updates.priorityTier = input.priority_tier as number;
96
- if (input.notes !== undefined) updates.notes = input.notes as string;
97
- if (input.sort_index !== undefined) updates.sortIndex = input.sort_index as number;
98
- if (Object.keys(updates).length > 0) {
99
- updateWorkItem(match.id, updates);
100
- }
101
- log.info({ title, existingId: match.id, updates }, 'task_list_add: updated existing item');
102
- return {
103
- content: `Reused existing task "${match.title}" (ID: ${match.id}) instead of creating a duplicate.${
104
- Object.keys(updates).length > 0 ? ` Updated: ${Object.keys(updates).join(', ')}.` : ''
105
- }`,
106
- isError: false,
107
- };
108
- }
109
-
110
- return null;
42
+ log.info({ title, existingId: match.id, updates }, 'task_list_add: updated existing item');
43
+ return {
44
+ content: `Reused existing task "${match.title}" (ID: ${match.id}) instead of creating a duplicate.${
45
+ Object.keys(updates).length > 0 ? ` Updated: ${Object.keys(updates).join(', ')}.` : ''
46
+ }`,
47
+ isError: false,
48
+ };
111
49
  }
112
50
 
113
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
114
- try {
115
- const taskId = input.task_id as string | undefined;
116
- const taskName = input.task_name as string | undefined;
117
- const titleOverride = input.title as string | undefined;
118
- const notes = input.notes as string | undefined;
119
- const priorityTier = input.priority_tier as number | undefined;
120
- const sortIndex = input.sort_index as number | undefined;
121
-
122
- const ifExists = (input.if_exists as string) || 'reuse_existing';
123
-
124
- // Ad-hoc mode: title provided without task_id or task_name
125
- if (!taskId && !taskName) {
126
- if (!titleOverride) {
127
- return {
128
- content: 'Error: You must provide either task_id, task_name, or title to create a work item.',
129
- isError: true,
130
- };
131
- }
132
-
133
- // Duplicate-prevention guard
134
- if (ifExists !== 'create_duplicate') {
135
- const duplicateResult = this.handleDuplicate(titleOverride, ifExists, input);
136
- if (duplicateResult) return duplicateResult;
137
- } else {
138
- log.info({ title: titleOverride }, 'task_list_add: creating duplicate (if_exists=create_duplicate)');
139
- }
140
-
141
- log.debug({ title: titleOverride }, 'task_list_add: creating new item');
142
-
143
- // Auto-create a lightweight task template for the ad-hoc item
144
- const adHocTask = createTask({
145
- title: titleOverride,
146
- template: titleOverride,
147
- });
148
-
149
- const workItem = createWorkItem({
150
- taskId: adHocTask.id,
151
- title: titleOverride,
152
- notes,
153
- priorityTier: priorityTier ?? 1,
154
- sortIndex,
155
- });
156
-
157
- log.info({ selectorType: 'title', workItemId: workItem.id, title: workItem.title }, 'ad-hoc work item created');
158
-
159
- const priority = PRIORITY_LABELS[workItem.priorityTier] ?? `tier ${workItem.priorityTier}`;
160
- const lines = [
161
- `Enqueued work item:`,
162
- ` Title: ${workItem.title}`,
163
- ` ID: ${workItem.id}`,
164
- ` Priority: ${priority}`,
165
- ` Status: ${workItem.status}`,
166
- ];
167
- if (workItem.notes) {
168
- lines.push(` Notes: ${workItem.notes}`);
169
- }
170
- if (workItem.sortIndex !== null) {
171
- lines.push(` Sort index: ${workItem.sortIndex}`);
172
- }
173
-
174
- return { content: lines.join('\n'), isError: false };
175
- }
176
-
177
- let resolvedTask;
178
-
179
- if (taskId) {
180
- resolvedTask = getTask(taskId);
181
- if (!resolvedTask) {
182
- const entity = identifyEntityById(taskId);
183
- if (entity.type === 'work_item') {
184
- return {
185
- content: `Error: ${buildWorkItemMismatchError(taskId, entity.title!, 'task_list_update to modify the existing work item, or task_list_add with just a title for a new ad-hoc item')}`,
186
- isError: true,
187
- };
188
- }
189
- return { content: `Error: No task definition found with ID "${taskId}". Use task_list to see available task templates, or provide just a title to create an ad-hoc work item.`, isError: true };
190
- }
191
- } else {
192
- // Search by name (case-insensitive substring match)
193
- const needle = taskName!.toLowerCase();
194
- const allTasks = listTasks();
195
- const matches = allTasks.filter((t) => t.title.toLowerCase().includes(needle));
196
-
197
- if (matches.length === 0) {
198
- return {
199
- content: `Error: No task definition found matching "${taskName}". Use task_list to see available tasks.`,
200
- isError: true,
201
- };
202
- }
203
-
204
- if (matches.length > 1) {
205
- const lines = [
206
- `Multiple task definitions match "${taskName}". Please specify by ID:`,
207
- '',
208
- ];
209
- for (const m of matches) {
210
- lines.push(`- ${m.title} (ID: ${m.id})`);
211
- }
212
- return { content: lines.join('\n'), isError: true };
213
- }
51
+ return null;
52
+ }
214
53
 
215
- resolvedTask = matches[0];
54
+ export async function executeTaskListAdd(
55
+ input: Record<string, unknown>,
56
+ _context: ToolContext,
57
+ ): Promise<ToolExecutionResult> {
58
+ try {
59
+ const taskId = input.task_id as string | undefined;
60
+ const taskName = input.task_name as string | undefined;
61
+ const titleOverride = input.title as string | undefined;
62
+ const executionPrompt = input.execution_prompt as string | undefined;
63
+ const notes = input.notes as string | undefined;
64
+ const priorityTier = input.priority_tier as number | undefined;
65
+ const sortIndex = input.sort_index as number | undefined;
66
+ const rawRequiredTools = input.required_tools as string[] | undefined;
67
+
68
+ const ifExists = (input.if_exists as string) || 'reuse_existing';
69
+
70
+ // Sanitize explicit required_tools if provided (including empty array)
71
+ const hasExplicitTools = rawRequiredTools !== undefined;
72
+ const sanitizedTools = hasExplicitTools ? sanitizeToolList(rawRequiredTools) : undefined;
73
+
74
+ // Ad-hoc mode: title provided without task_id or task_name
75
+ if (!taskId && !taskName) {
76
+ if (!titleOverride) {
77
+ return {
78
+ content: 'Error: You must provide either task_id, task_name, or title to create a work item.',
79
+ isError: true,
80
+ };
216
81
  }
217
82
 
218
- const finalTitle = titleOverride ?? resolvedTask.title;
219
-
220
83
  // Duplicate-prevention guard
221
84
  if (ifExists !== 'create_duplicate') {
222
- const duplicateResult = this.handleDuplicate(finalTitle, ifExists, input);
85
+ const duplicateResult = handleDuplicate(titleOverride, ifExists, input);
223
86
  if (duplicateResult) return duplicateResult;
224
87
  } else {
225
- log.info({ title: finalTitle }, 'task_list_add: creating duplicate (if_exists=create_duplicate)');
88
+ log.info({ title: titleOverride }, 'task_list_add: creating duplicate (if_exists=create_duplicate)');
226
89
  }
227
90
 
228
- log.debug({ title: finalTitle }, 'task_list_add: creating new item');
91
+ log.debug({ title: titleOverride }, 'task_list_add: creating new item');
92
+
93
+ // Auto-create a lightweight task template for the ad-hoc item.
94
+ // Use execution_prompt as the template (the actual instruction sent to the
95
+ // model at run time), falling back to notes then title. This preserves
96
+ // detailed instructions (e.g. full file paths) that may be shortened in
97
+ // the display title.
98
+ const adHocTemplate = executionPrompt ?? notes ?? titleOverride;
99
+ const adHocTask = createTask({
100
+ title: titleOverride,
101
+ template: adHocTemplate,
102
+ });
229
103
 
230
- const selectorType = taskId ? 'task_id' : 'task_name';
231
- const workItem = createWorkItem({
232
- taskId: resolvedTask.id,
233
- title: finalTitle,
104
+ // For ad-hoc items: explicit tools persist; omitted → null
105
+ const adHocRequiredTools = hasExplicitTools ? JSON.stringify(sanitizedTools) : undefined;
106
+
107
+ const workItem = createWorkItemWithPermissions({
108
+ taskId: adHocTask.id,
109
+ title: titleOverride,
234
110
  notes,
235
111
  priorityTier: priorityTier ?? 1,
236
112
  sortIndex,
113
+ requiredTools: adHocRequiredTools,
237
114
  });
238
115
 
239
- log.info({ selectorType, taskId: resolvedTask.id, workItemId: workItem.id, title: workItem.title }, 'work item created from task definition');
116
+ log.info({ selectorType: 'title', workItemId: workItem.id, title: workItem.title }, 'ad-hoc work item created');
240
117
 
241
118
  const priority = PRIORITY_LABELS[workItem.priorityTier] ?? `tier ${workItem.priorityTier}`;
242
119
  const lines = [
243
120
  `Enqueued work item:`,
244
121
  ` Title: ${workItem.title}`,
245
122
  ` ID: ${workItem.id}`,
246
- ` Task definition: ${resolvedTask.title} (${resolvedTask.id})`,
247
123
  ` Priority: ${priority}`,
248
124
  ` Status: ${workItem.status}`,
249
125
  ];
@@ -255,12 +131,104 @@ class TaskListAddTool implements Tool {
255
131
  }
256
132
 
257
133
  return { content: lines.join('\n'), isError: false };
258
- } catch (err) {
259
- const msg = err instanceof Error ? err.message : String(err);
260
- log.error({ error: msg }, 'enqueue failed');
261
- return { content: `Error: ${msg}`, isError: true };
262
134
  }
135
+
136
+ let resolvedTask;
137
+
138
+ if (taskId) {
139
+ resolvedTask = getTask(taskId);
140
+ if (!resolvedTask) {
141
+ const entity = identifyEntityById(taskId);
142
+ if (entity.type === 'work_item') {
143
+ return {
144
+ content: `Error: ${buildWorkItemMismatchError(taskId, entity.title!, 'task_list_update to modify the existing work item, or task_list_add with just a title for a new ad-hoc item')}`,
145
+ isError: true,
146
+ };
147
+ }
148
+ return { content: `Error: No task definition found with ID "${taskId}". Use task_list to see available task templates, or provide just a title to create an ad-hoc work item.`, isError: true };
149
+ }
150
+ } else {
151
+ // Search by name (case-insensitive substring match)
152
+ const needle = taskName!.toLowerCase();
153
+ const allTasks = listTasks();
154
+ const matches = allTasks.filter((t) => t.title.toLowerCase().includes(needle));
155
+
156
+ if (matches.length === 0) {
157
+ return {
158
+ content: `Error: No task definition found matching "${taskName}". Use task_list to see available tasks.`,
159
+ isError: true,
160
+ };
161
+ }
162
+
163
+ if (matches.length > 1) {
164
+ const lines = [
165
+ `Multiple task definitions match "${taskName}". Please specify by ID:`,
166
+ '',
167
+ ];
168
+ for (const m of matches) {
169
+ lines.push(`- ${m.title} (ID: ${m.id})`);
170
+ }
171
+ return { content: lines.join('\n'), isError: true };
172
+ }
173
+
174
+ resolvedTask = matches[0];
175
+ }
176
+
177
+ const finalTitle = titleOverride ?? resolvedTask.title;
178
+
179
+ // Duplicate-prevention guard
180
+ if (ifExists !== 'create_duplicate') {
181
+ const duplicateResult = handleDuplicate(finalTitle, ifExists, input);
182
+ if (duplicateResult) return duplicateResult;
183
+ } else {
184
+ log.info({ title: finalTitle }, 'task_list_add: creating duplicate (if_exists=create_duplicate)');
185
+ }
186
+
187
+ log.debug({ title: finalTitle }, 'task_list_add: creating new item');
188
+
189
+ const selectorType = taskId ? 'task_id' : 'task_name';
190
+
191
+ // Snapshot precedence: explicit required_tools > template tools > null
192
+ let resolvedRequiredTools: string | undefined;
193
+ if (hasExplicitTools) {
194
+ resolvedRequiredTools = JSON.stringify(sanitizedTools);
195
+ } else if (resolvedTask.requiredTools) {
196
+ // Snapshot template tools at add time
197
+ const templateTools = sanitizeToolList(JSON.parse(resolvedTask.requiredTools));
198
+ resolvedRequiredTools = JSON.stringify(templateTools);
199
+ }
200
+
201
+ const workItem = createWorkItemWithPermissions({
202
+ taskId: resolvedTask.id,
203
+ title: finalTitle,
204
+ notes,
205
+ priorityTier: priorityTier ?? 1,
206
+ sortIndex,
207
+ requiredTools: resolvedRequiredTools,
208
+ });
209
+
210
+ log.info({ selectorType, taskId: resolvedTask.id, workItemId: workItem.id, title: workItem.title }, 'work item created from task definition');
211
+
212
+ const priority = PRIORITY_LABELS[workItem.priorityTier] ?? `tier ${workItem.priorityTier}`;
213
+ const lines = [
214
+ `Enqueued work item:`,
215
+ ` Title: ${workItem.title}`,
216
+ ` ID: ${workItem.id}`,
217
+ ` Task definition: ${resolvedTask.title} (${resolvedTask.id})`,
218
+ ` Priority: ${priority}`,
219
+ ` Status: ${workItem.status}`,
220
+ ];
221
+ if (workItem.notes) {
222
+ lines.push(` Notes: ${workItem.notes}`);
223
+ }
224
+ if (workItem.sortIndex !== null) {
225
+ lines.push(` Sort index: ${workItem.sortIndex}`);
226
+ }
227
+
228
+ return { content: lines.join('\n'), isError: false };
229
+ } catch (err) {
230
+ const msg = err instanceof Error ? err.message : String(err);
231
+ log.error({ error: msg }, 'enqueue failed');
232
+ return { content: `Error: ${msg}`, isError: true };
263
233
  }
264
234
  }
265
-
266
- export const taskListAddTool = new TaskListAddTool();
@@ -1,71 +1,41 @@
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 { listWorkItems, type WorkItemStatus } from '../../work-items/work-item-store.js';
5
3
 
6
- const definition: ToolDefinition = {
7
- name: 'task_list_show',
8
- description: 'List the user\'s Task Queue (work items) with their status, priority, and last run info. Use this when the user says "show my tasks", "what\'s in my queue", "what\'s on my task list", or similar.',
9
- input_schema: {
10
- type: 'object',
11
- properties: {
12
- status: {
13
- oneOf: [
14
- { type: 'string' },
15
- { type: 'array', items: { type: 'string' } },
16
- ],
17
- description:
18
- 'Optional status filter. A single status string (e.g. "queued") or an array of statuses to include.',
19
- },
20
- },
21
- },
22
- };
23
-
24
- class TaskListShowTool implements Tool {
25
- name = 'task_list_show';
26
- description = definition.description;
27
- category = 'tasks';
28
- defaultRiskLevel = RiskLevel.Low;
29
-
30
- getDefinition(): ToolDefinition {
31
- return definition;
32
- }
33
-
34
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
35
- try {
36
- const statusFilter = input.status as string | string[] | undefined;
37
-
38
- let items;
39
- if (typeof statusFilter === 'string') {
40
- items = listWorkItems({ status: statusFilter as WorkItemStatus });
41
- } else if (Array.isArray(statusFilter)) {
42
- // listWorkItems only supports a single status filter, so we fetch all
43
- // and filter client-side when an array is provided
44
- const allItems = listWorkItems();
45
- const allowed = new Set(statusFilter);
46
- items = allItems.filter((item) => allowed.has(item.status));
47
- } else {
48
- items = listWorkItems();
49
- }
4
+ export async function executeTaskListShow(
5
+ input: Record<string, unknown>,
6
+ _context: ToolContext,
7
+ ): Promise<ToolExecutionResult> {
8
+ try {
9
+ const statusFilter = input.status as string | string[] | undefined;
10
+
11
+ let items;
12
+ if (typeof statusFilter === 'string') {
13
+ items = listWorkItems({ status: statusFilter as WorkItemStatus });
14
+ } else if (Array.isArray(statusFilter)) {
15
+ // listWorkItems only supports a single status filter, so we fetch all
16
+ // and filter client-side when an array is provided
17
+ const allItems = listWorkItems();
18
+ const allowed = new Set(statusFilter);
19
+ items = allItems.filter((item) => allowed.has(item.status));
20
+ } else {
21
+ items = listWorkItems();
22
+ }
50
23
 
51
- const count = items.length;
52
- const filtered = statusFilter !== undefined;
24
+ const count = items.length;
25
+ const filtered = statusFilter !== undefined;
53
26
 
54
- if (count === 0) {
55
- const suffix = filtered ? 'no items matching filter.' : 'no tasks queued.';
56
- return { content: `Opened Tasks window \u2014 ${suffix}`, isError: false };
57
- }
27
+ if (count === 0) {
28
+ const suffix = filtered ? 'no items matching filter.' : 'no tasks queued.';
29
+ return { content: `Opened Tasks window \u2014 ${suffix}`, isError: false };
30
+ }
58
31
 
59
- const label = filtered
60
- ? `${count} ${Array.isArray(statusFilter) ? 'matching' : statusFilter} item${count === 1 ? '' : 's'}`
61
- : `${count} item${count === 1 ? '' : 's'}`;
32
+ const label = filtered
33
+ ? `${count} ${Array.isArray(statusFilter) ? 'matching' : statusFilter} item${count === 1 ? '' : 's'}`
34
+ : `${count} item${count === 1 ? '' : 's'}`;
62
35
 
63
- return { content: `Opened Tasks window (${label}).`, isError: false };
64
- } catch (err) {
65
- const msg = err instanceof Error ? err.message : String(err);
66
- return { content: `Error: ${msg}`, isError: true };
67
- }
36
+ return { content: `Opened Tasks window (${label}).`, isError: false };
37
+ } catch (err) {
38
+ const msg = err instanceof Error ? err.message : String(err);
39
+ return { content: `Error: ${msg}`, isError: true };
68
40
  }
69
41
  }
70
-
71
- export const taskListShowTool = new TaskListShowTool();