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,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 { ToolExecutionResult } from '../types.js';
4
2
  import { formatLocalDate } from '../../schedule/schedule-store.js';
5
3
  import {
6
4
  insertReminder,
@@ -8,28 +6,9 @@ import {
8
6
  cancelReminder,
9
7
  } from './reminder-store.js';
10
8
 
11
- function executeReminder(input: Record<string, unknown>): ToolExecutionResult {
12
- const action = input.action as string | undefined;
13
- if (!action) {
14
- return { content: 'Error: action is required', isError: true };
15
- }
16
-
17
- switch (action) {
18
- case 'create':
19
- return createAction(input);
20
- case 'list':
21
- return listAction();
22
- case 'cancel':
23
- return cancelAction(input);
24
- default:
25
- return {
26
- content: `Error: Unknown action "${action}". Valid actions: create, list, cancel`,
27
- isError: true,
28
- };
29
- }
30
- }
9
+ // ── Exported execute functions ──────────────────────────────────────
31
10
 
32
- function createAction(input: Record<string, unknown>): ToolExecutionResult {
11
+ export function executeReminderCreate(input: Record<string, unknown>): ToolExecutionResult {
33
12
  const fireAtStr = input.fire_at as string | undefined;
34
13
  const label = input.label as string | undefined;
35
14
  const message = input.message as string | undefined;
@@ -68,7 +47,7 @@ function createAction(input: Record<string, unknown>): ToolExecutionResult {
68
47
  };
69
48
  }
70
49
 
71
- function listAction(): ToolExecutionResult {
50
+ export function executeReminderList(): ToolExecutionResult {
72
51
  const all = listReminders();
73
52
  if (all.length === 0) {
74
53
  return { content: 'No reminders found.', isError: false };
@@ -86,7 +65,7 @@ function listAction(): ToolExecutionResult {
86
65
  return { content: `Reminders:\n${lines.join('\n')}`, isError: false };
87
66
  }
88
67
 
89
- function cancelAction(input: Record<string, unknown>): ToolExecutionResult {
68
+ export function executeReminderCancel(input: Record<string, unknown>): ToolExecutionResult {
90
69
  const reminderId = input.reminder_id as string | undefined;
91
70
  if (!reminderId) {
92
71
  return { content: 'Error: reminder_id is required for cancel', isError: true };
@@ -99,55 +78,3 @@ function cancelAction(input: Record<string, unknown>): ToolExecutionResult {
99
78
 
100
79
  return { content: `Reminder "${reminderId}" cancelled.`, isError: false };
101
80
  }
102
-
103
- class ReminderTool implements Tool {
104
- name = 'reminder';
105
- description = 'Create, list, or cancel one-time time-based reminders. Reminders fire at a specific future time and either notify the user or execute a message through the assistant. Use this ONLY when the user wants a time-triggered notification (e.g. "remind me at 3pm", "remind me in 2 hours"). Do NOT use this for "add to my tasks" or "add to my queue" — use task_list_add for those requests.';
106
- category = 'reminder';
107
- defaultRiskLevel = RiskLevel.Low;
108
-
109
- getDefinition(): ToolDefinition {
110
- return {
111
- name: this.name,
112
- description: this.description,
113
- input_schema: {
114
- type: 'object',
115
- properties: {
116
- action: {
117
- type: 'string',
118
- enum: ['create', 'list', 'cancel'],
119
- description: 'Reminder action',
120
- },
121
- fire_at: {
122
- type: 'string',
123
- description: 'ISO 8601 timestamp for when the reminder should fire (required for create)',
124
- },
125
- label: {
126
- type: 'string',
127
- description: 'Human-readable label (required for create)',
128
- },
129
- message: {
130
- type: 'string',
131
- description: 'Content shown in notification (notify) or sent to agent (execute). Required for create.',
132
- },
133
- mode: {
134
- type: 'string',
135
- enum: ['notify', 'execute'],
136
- description: 'How the reminder fires. Defaults to notify.',
137
- },
138
- reminder_id: {
139
- type: 'string',
140
- description: 'Reminder ID (required for cancel)',
141
- },
142
- },
143
- required: ['action'],
144
- },
145
- };
146
- }
147
-
148
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
149
- return executeReminder(input);
150
- }
151
- }
152
-
153
- export const reminderTool = new ReminderTool();
@@ -1,86 +1,81 @@
1
- import { RiskLevel } from '../../permissions/types.js';
2
- import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
3
- import type { ToolDefinition } from '../../providers/types.js';
4
- import { registerTool } from '../registry.js';
1
+ import type { ToolContext, ToolExecutionResult } from '../types.js';
5
2
  import { createSchedule, isValidCronExpression, formatLocalDate, describeCronExpression } from '../../schedule/schedule-store.js';
3
+ import { normalizeScheduleSyntax } from '../../schedule/recurrence-types.js';
4
+ import { validateRruleSetLines } from '../../schedule/recurrence-engine.js';
6
5
 
7
- class ScheduleCreateTool implements Tool {
8
- name = 'schedule_create';
9
- description = 'Create a recurring scheduled automation that sends a message at a cron interval. ONLY use this when the user explicitly wants something to run on a schedule (e.g. "every day at 9am", "weekly on Mondays", "every hour"). Do NOT use this for "add to my tasks" or "add to my queue" — use task_list_add for those requests instead.';
10
- category = 'schedule';
11
- defaultRiskLevel = RiskLevel.Medium;
6
+ export async function executeScheduleCreate(
7
+ input: Record<string, unknown>,
8
+ _context: ToolContext,
9
+ ): Promise<ToolExecutionResult> {
10
+ const name = input.name as string;
11
+ const timezone = (input.timezone as string) ?? null;
12
+ const message = input.message as string;
13
+ const enabled = (input.enabled as boolean) ?? true;
12
14
 
13
- getDefinition(): ToolDefinition {
14
- return {
15
- name: this.name,
16
- description: this.description,
17
- input_schema: {
18
- type: 'object',
19
- properties: {
20
- name: {
21
- type: 'string',
22
- description: 'A human-readable name for the scheduled task',
23
- },
24
- cron_expression: {
25
- type: 'string',
26
- description: 'A cron expression (e.g. "0 9 * * 1-5" for weekdays at 9am). Supports standard 5-field cron syntax.',
27
- },
28
- timezone: {
29
- type: 'string',
30
- description: 'IANA timezone (e.g. "America/Los_Angeles"). Defaults to system timezone if omitted.',
31
- },
32
- message: {
33
- type: 'string',
34
- description: 'The message to send to the assistant when the schedule triggers',
35
- },
36
- enabled: {
37
- type: 'boolean',
38
- description: 'Whether the job is enabled immediately. Defaults to true.',
39
- },
40
- },
41
- required: ['name', 'cron_expression', 'message'],
42
- },
43
- };
15
+ if (!name || typeof name !== 'string') {
16
+ return { content: 'Error: name is required and must be a string', isError: true };
17
+ }
18
+ if (!message || typeof message !== 'string') {
19
+ return { content: 'Error: message is required and must be a string', isError: true };
44
20
  }
45
21
 
46
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
47
- const name = input.name as string;
48
- const cronExpression = input.cron_expression as string;
49
- const timezone = (input.timezone as string) ?? null;
50
- const message = input.message as string;
51
- const enabled = (input.enabled as boolean) ?? true;
22
+ // Resolve syntax and expression from new or legacy fields
23
+ const resolved = normalizeScheduleSyntax({
24
+ syntax: input.syntax as 'cron' | 'rrule' | undefined,
25
+ expression: input.expression as string | undefined,
26
+ legacyCronExpression: input.cron_expression as string | undefined,
27
+ });
52
28
 
53
- if (!name || typeof name !== 'string') {
54
- return { content: 'Error: name is required and must be a string', isError: true };
55
- }
56
- if (!cronExpression || typeof cronExpression !== 'string') {
57
- return { content: 'Error: cron_expression is required and must be a string', isError: true };
58
- }
59
- if (!message || typeof message !== 'string') {
60
- return { content: 'Error: message is required and must be a string', isError: true };
61
- }
62
- if (!isValidCronExpression(cronExpression)) {
63
- return { content: `Error: Invalid cron expression: "${cronExpression}"`, isError: true };
64
- }
29
+ if (!resolved) {
30
+ return { content: 'Error: expression (or cron_expression) is required', isError: true };
31
+ }
65
32
 
66
- try {
67
- const job = createSchedule({ name, cronExpression, timezone, message, enabled });
68
- const nextRunDate = formatLocalDate(job.nextRunAt);
33
+ // Syntax-specific pre-validation for actionable error messages
34
+ if (resolved.syntax === 'cron' && !isValidCronExpression(resolved.expression)) {
35
+ return { content: `Error: Invalid cron expression: "${resolved.expression}"`, isError: true };
36
+ }
37
+ if (resolved.syntax === 'rrule') {
38
+ if (typeof resolved.expression !== 'string') {
39
+ return { content: 'Error: expression must be a string', isError: true };
40
+ }
41
+ const setError = validateRruleSetLines(resolved.expression);
42
+ if (setError) {
69
43
  return {
70
- content: [
71
- `Schedule created successfully.`,
72
- ` Name: ${job.name}`,
73
- ` Schedule: ${describeCronExpression(job.cronExpression)}${job.timezone ? ` (${job.timezone})` : ''}`,
74
- ` Enabled: ${job.enabled}`,
75
- ` Next run: ${nextRunDate}`,
76
- ].join('\n'),
77
- isError: false,
44
+ content: `Error: ${setError}. Supported line types: DTSTART, RRULE, RDATE, EXDATE, EXRULE.`,
45
+ isError: true,
78
46
  };
79
- } catch (err) {
80
- const msg = err instanceof Error ? err.message : String(err);
81
- return { content: `Error creating schedule: ${msg}`, isError: true };
82
47
  }
83
48
  }
84
- }
85
49
 
86
- registerTool(new ScheduleCreateTool());
50
+ try {
51
+ const job = createSchedule({
52
+ name,
53
+ cronExpression: resolved.expression,
54
+ timezone,
55
+ message,
56
+ enabled,
57
+ syntax: resolved.syntax,
58
+ expression: resolved.expression,
59
+ });
60
+
61
+ const scheduleDescription = job.syntax === 'rrule'
62
+ ? job.expression
63
+ : describeCronExpression(job.cronExpression);
64
+
65
+ const nextRunDate = formatLocalDate(job.nextRunAt);
66
+ return {
67
+ content: [
68
+ `Schedule created successfully.`,
69
+ ` Name: ${job.name}`,
70
+ ` Syntax: ${job.syntax}`,
71
+ ` Schedule: ${scheduleDescription}${job.timezone ? ` (${job.timezone})` : ''}`,
72
+ ` Enabled: ${job.enabled}`,
73
+ ` Next run: ${nextRunDate}`,
74
+ ].join('\n'),
75
+ isError: false,
76
+ };
77
+ } catch (err) {
78
+ const msg = err instanceof Error ? err.message : String(err);
79
+ return { content: `Error creating schedule: ${msg}`, isError: true };
80
+ }
81
+ }
@@ -1,54 +1,28 @@
1
- import { RiskLevel } from '../../permissions/types.js';
2
- import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
3
- import type { ToolDefinition } from '../../providers/types.js';
4
- import { registerTool } from '../registry.js';
1
+ import type { ToolContext, ToolExecutionResult } from '../types.js';
5
2
  import { deleteSchedule, getSchedule } from '../../schedule/schedule-store.js';
6
3
 
7
- class ScheduleDeleteTool implements Tool {
8
- name = 'schedule_delete';
9
- description = 'Delete a recurring scheduled automation and all its run history';
10
- category = 'schedule';
11
- defaultRiskLevel = RiskLevel.High;
12
-
13
- getDefinition(): ToolDefinition {
14
- return {
15
- name: this.name,
16
- description: this.description,
17
- input_schema: {
18
- type: 'object',
19
- properties: {
20
- job_id: {
21
- type: 'string',
22
- description: 'The ID of the schedule to delete',
23
- },
24
- },
25
- required: ['job_id'],
26
- },
27
- };
4
+ export async function executeScheduleDelete(
5
+ input: Record<string, unknown>,
6
+ _context: ToolContext,
7
+ ): Promise<ToolExecutionResult> {
8
+ const jobId = input.job_id as string;
9
+ if (!jobId || typeof jobId !== 'string') {
10
+ return { content: 'Error: job_id is required', isError: true };
28
11
  }
29
12
 
30
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
31
- const jobId = input.job_id as string;
32
- if (!jobId || typeof jobId !== 'string') {
33
- return { content: 'Error: job_id is required', isError: true };
34
- }
35
-
36
- // Fetch the job first for the confirmation message
37
- const job = getSchedule(jobId);
38
- if (!job) {
39
- return { content: `Error: Schedule not found: ${jobId}`, isError: true };
40
- }
41
-
42
- const deleted = deleteSchedule(jobId);
43
- if (!deleted) {
44
- return { content: `Error: Failed to delete schedule: ${jobId}`, isError: true };
45
- }
13
+ // Fetch the job first for the confirmation message
14
+ const job = getSchedule(jobId);
15
+ if (!job) {
16
+ return { content: `Error: Schedule not found: ${jobId}`, isError: true };
17
+ }
46
18
 
47
- return {
48
- content: `Schedule deleted: "${job.name}"`,
49
- isError: false,
50
- };
19
+ const deleted = deleteSchedule(jobId);
20
+ if (!deleted) {
21
+ return { content: `Error: Failed to delete schedule: ${jobId}`, isError: true };
51
22
  }
52
- }
53
23
 
54
- registerTool(new ScheduleDeleteTool());
24
+ return {
25
+ content: `Schedule deleted: "${job.name}"`,
26
+ isError: false,
27
+ };
28
+ }
@@ -1,88 +1,69 @@
1
- import { RiskLevel } from '../../permissions/types.js';
2
- import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
3
- import type { ToolDefinition } from '../../providers/types.js';
4
- import { registerTool } from '../registry.js';
1
+ import type { ToolContext, ToolExecutionResult } from '../types.js';
5
2
  import { listSchedules, getSchedule, getScheduleRuns, formatLocalDate, describeCronExpression } from '../../schedule/schedule-store.js';
3
+ import { hasSetConstructs } from '../../schedule/recurrence-engine.js';
6
4
 
7
- class ScheduleListTool implements Tool {
8
- name = 'schedule_list';
9
- description = 'List recurring scheduled automations (cron jobs), or show details and recent runs for a specific one. For the user\'s task queue, use task_list_show instead.';
10
- category = 'schedule';
11
- defaultRiskLevel = RiskLevel.Low;
12
-
13
- getDefinition(): ToolDefinition {
14
- return {
15
- name: this.name,
16
- description: this.description,
17
- input_schema: {
18
- type: 'object',
19
- properties: {
20
- enabled_only: {
21
- type: 'boolean',
22
- description: 'When true, only show enabled jobs. Defaults to false.',
23
- },
24
- job_id: {
25
- type: 'string',
26
- description: 'If provided, show detailed info and recent runs for this specific job.',
27
- },
28
- },
29
- required: [],
30
- },
31
- };
5
+ function describeSchedule(job: { syntax: string; expression: string; cronExpression: string }): string {
6
+ if (job.syntax === 'rrule') {
7
+ const label = hasSetConstructs(job.expression) ? '[RRULE set] ' : '';
8
+ return `${label}${job.expression}`;
32
9
  }
10
+ return describeCronExpression(job.cronExpression);
11
+ }
33
12
 
34
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
35
- const jobId = input.job_id as string | undefined;
36
- const enabledOnly = (input.enabled_only as boolean) ?? false;
13
+ export async function executeScheduleList(
14
+ input: Record<string, unknown>,
15
+ _context: ToolContext,
16
+ ): Promise<ToolExecutionResult> {
17
+ const jobId = input.job_id as string | undefined;
18
+ const enabledOnly = (input.enabled_only as boolean) ?? false;
37
19
 
38
- // Detail mode for a specific job
39
- if (jobId) {
40
- const job = getSchedule(jobId);
41
- if (!job) {
42
- return { content: `Error: Schedule not found: ${jobId}`, isError: true };
43
- }
20
+ // Detail mode for a specific job
21
+ if (jobId) {
22
+ const job = getSchedule(jobId);
23
+ if (!job) {
24
+ return { content: `Error: Schedule not found: ${jobId}`, isError: true };
25
+ }
44
26
 
45
- const runs = getScheduleRuns(jobId, 5);
46
- const lines = [
47
- `Schedule: ${job.name}`,
48
- ` Schedule: ${describeCronExpression(job.cronExpression)}${job.timezone ? ` (${job.timezone})` : ''}`,
49
- ` Enabled: ${job.enabled}`,
50
- ` Message: ${job.message}`,
51
- ` Next run: ${formatLocalDate(job.nextRunAt)}`,
52
- ` Last run: ${job.lastRunAt ? formatLocalDate(job.lastRunAt) : 'never'}`,
53
- ` Last status: ${job.lastStatus ?? 'n/a'}`,
54
- ` Retry count: ${job.retryCount}`,
55
- ` Created: ${formatLocalDate(job.createdAt)}`,
56
- ];
27
+ const runs = getScheduleRuns(jobId, 5);
28
+ const lines = [
29
+ `Schedule: ${job.name}`,
30
+ ` Syntax: ${job.syntax}`,
31
+ ` Expression: ${job.expression}`,
32
+ ` Schedule: ${describeSchedule(job)}${job.timezone ? ` (${job.timezone})` : ''}`,
33
+ ` Enabled: ${job.enabled}`,
34
+ ` Message: ${job.message}`,
35
+ ` Next run: ${formatLocalDate(job.nextRunAt)}`,
36
+ ` Last run: ${job.lastRunAt ? formatLocalDate(job.lastRunAt) : 'never'}`,
37
+ ` Last status: ${job.lastStatus ?? 'n/a'}`,
38
+ ` Retry count: ${job.retryCount}`,
39
+ ` Created: ${formatLocalDate(job.createdAt)}`,
40
+ ];
57
41
 
58
- if (runs.length > 0) {
59
- lines.push('', `Recent runs (${runs.length}):`);
60
- for (const run of runs) {
61
- const dur = run.durationMs != null ? `${run.durationMs}ms` : 'n/a';
62
- lines.push(` - ${run.status} at ${formatLocalDate(run.startedAt)} (${dur})${run.error ? ` error: ${run.error}` : ''}`);
63
- }
64
- } else {
65
- lines.push('', 'No runs yet.');
42
+ if (runs.length > 0) {
43
+ lines.push('', `Recent runs (${runs.length}):`);
44
+ for (const run of runs) {
45
+ const dur = run.durationMs != null ? `${run.durationMs}ms` : 'n/a';
46
+ lines.push(` - ${run.status} at ${formatLocalDate(run.startedAt)} (${dur})${run.error ? ` error: ${run.error}` : ''}`);
66
47
  }
67
-
68
- return { content: lines.join('\n'), isError: false };
48
+ } else {
49
+ lines.push('', 'No runs yet.');
69
50
  }
70
51
 
71
- // List mode
72
- const jobs = listSchedules({ enabledOnly });
73
- if (jobs.length === 0) {
74
- return { content: 'No schedules found.', isError: false };
75
- }
52
+ return { content: lines.join('\n'), isError: false };
53
+ }
76
54
 
77
- const lines = [`Schedules (${jobs.length}):`];
78
- for (const job of jobs) {
79
- const status = job.enabled ? 'enabled' : 'disabled';
80
- const next = job.enabled ? formatLocalDate(job.nextRunAt) : 'n/a';
81
- lines.push(` - [${status}] ${job.name} (${describeCronExpression(job.cronExpression)}) — next: ${next}`);
82
- }
55
+ // List mode
56
+ const jobs = listSchedules({ enabledOnly });
57
+ if (jobs.length === 0) {
58
+ return { content: 'No schedules found.', isError: false };
59
+ }
83
60
 
84
- return { content: lines.join('\n'), isError: false };
61
+ const lines = [`Schedules (${jobs.length}):`];
62
+ for (const job of jobs) {
63
+ const status = job.enabled ? 'enabled' : 'disabled';
64
+ const next = job.enabled ? formatLocalDate(job.nextRunAt) : 'n/a';
65
+ lines.push(` - [${status}] ${job.name} ([${job.syntax}] ${describeSchedule(job)}) — next: ${next}`);
85
66
  }
86
- }
87
67
 
88
- registerTool(new ScheduleListTool());
68
+ return { content: lines.join('\n'), isError: false };
69
+ }
@@ -1,97 +1,90 @@
1
- import { RiskLevel } from '../../permissions/types.js';
2
- import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
3
- import type { ToolDefinition } from '../../providers/types.js';
4
- import { registerTool } from '../registry.js';
1
+ import type { ToolContext, ToolExecutionResult } from '../types.js';
5
2
  import { updateSchedule, formatLocalDate, describeCronExpression } from '../../schedule/schedule-store.js';
3
+ import { normalizeScheduleSyntax, type ScheduleSyntax } from '../../schedule/recurrence-types.js';
4
+ import { validateRruleSetLines } from '../../schedule/recurrence-engine.js';
6
5
 
7
- class ScheduleUpdateTool implements Tool {
8
- name = 'schedule_update';
9
- description = 'Update an existing recurring scheduled automation (cron expression, message, name, or enabled state)';
10
- category = 'schedule';
11
- defaultRiskLevel = RiskLevel.Medium;
12
-
13
- getDefinition(): ToolDefinition {
14
- return {
15
- name: this.name,
16
- description: this.description,
17
- input_schema: {
18
- type: 'object',
19
- properties: {
20
- job_id: {
21
- type: 'string',
22
- description: 'The ID of the schedule to update',
23
- },
24
- name: {
25
- type: 'string',
26
- description: 'New name for the job',
27
- },
28
- cron_expression: {
29
- type: 'string',
30
- description: 'New cron expression',
31
- },
32
- timezone: {
33
- type: 'string',
34
- description: 'New IANA timezone',
35
- },
36
- message: {
37
- type: 'string',
38
- description: 'New message to send when triggered',
39
- },
40
- enabled: {
41
- type: 'boolean',
42
- description: 'Enable or disable the job',
43
- },
44
- },
45
- required: ['job_id'],
46
- },
47
- };
6
+ export async function executeScheduleUpdate(
7
+ input: Record<string, unknown>,
8
+ _context: ToolContext,
9
+ ): Promise<ToolExecutionResult> {
10
+ const jobId = input.job_id as string;
11
+ if (!jobId || typeof jobId !== 'string') {
12
+ return { content: 'Error: job_id is required', isError: true };
48
13
  }
49
14
 
50
- async execute(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
51
- const jobId = input.job_id as string;
52
- if (!jobId || typeof jobId !== 'string') {
53
- return { content: 'Error: job_id is required', isError: true };
54
- }
55
-
56
- const updates: Record<string, unknown> = {};
57
- if (input.name !== undefined) updates.name = input.name;
58
- if (input.cron_expression !== undefined) updates.cronExpression = input.cron_expression;
59
- if (input.timezone !== undefined) updates.timezone = input.timezone;
60
- if (input.message !== undefined) updates.message = input.message;
61
- if (input.enabled !== undefined) updates.enabled = input.enabled;
15
+ const updates: Record<string, unknown> = {};
16
+ if (input.name !== undefined) updates.name = input.name;
17
+ if (input.timezone !== undefined) updates.timezone = input.timezone;
18
+ if (input.message !== undefined) updates.message = input.message;
19
+ if (input.enabled !== undefined) updates.enabled = input.enabled;
62
20
 
63
- if (Object.keys(updates).length === 0) {
64
- return { content: 'Error: No updates provided. Specify at least one field to update.', isError: true };
21
+ // Auto-detect syntax when expression changes without explicit syntax
22
+ const hasExpression = input.expression !== undefined || input.cron_expression !== undefined;
23
+ if (hasExpression || input.syntax !== undefined) {
24
+ const resolved = normalizeScheduleSyntax({
25
+ syntax: input.syntax as 'cron' | 'rrule' | undefined,
26
+ expression: input.expression as string | undefined,
27
+ legacyCronExpression: input.cron_expression as string | undefined,
28
+ });
29
+ if (resolved) {
30
+ updates.syntax = resolved.syntax;
31
+ updates.expression = resolved.expression;
32
+ } else if (input.expression !== undefined) {
33
+ updates.expression = input.expression;
34
+ } else if (input.cron_expression !== undefined) {
35
+ updates.cronExpression = input.cron_expression;
65
36
  }
37
+ }
66
38
 
67
- try {
68
- const job = updateSchedule(jobId, updates as {
69
- name?: string;
70
- cronExpression?: string;
71
- timezone?: string | null;
72
- message?: string;
73
- enabled?: boolean;
74
- });
75
-
76
- if (!job) {
77
- return { content: `Error: Schedule not found: ${jobId}`, isError: true };
78
- }
39
+ if (Object.keys(updates).length === 0) {
40
+ return { content: 'Error: No updates provided. Specify at least one field to update.', isError: true };
41
+ }
79
42
 
43
+ // Set-aware pre-validation for RRULE expressions
44
+ const effectiveSyntax = updates.syntax as string | undefined;
45
+ const effectiveExpr = (updates.expression as string | undefined) ?? (updates.cronExpression as string | undefined);
46
+ if (effectiveExpr && typeof effectiveExpr === 'string' && (effectiveSyntax === 'rrule' || /^(DTSTART|RRULE:)/m.test(effectiveExpr))) {
47
+ const setError = validateRruleSetLines(effectiveExpr);
48
+ if (setError) {
80
49
  return {
81
- content: [
82
- `Schedule updated successfully.`,
83
- ` Name: ${job.name}`,
84
- ` Schedule: ${describeCronExpression(job.cronExpression)}${job.timezone ? ` (${job.timezone})` : ''}`,
85
- ` Enabled: ${job.enabled}`,
86
- ` Next run: ${job.enabled ? formatLocalDate(job.nextRunAt) : 'n/a (disabled)'}`,
87
- ].join('\n'),
88
- isError: false,
50
+ content: `Error: ${setError}. Supported line types: DTSTART, RRULE, RDATE, EXDATE, EXRULE.`,
51
+ isError: true,
89
52
  };
90
- } catch (err) {
91
- const msg = err instanceof Error ? err.message : String(err);
92
- return { content: `Error updating schedule: ${msg}`, isError: true };
93
53
  }
94
54
  }
95
- }
96
55
 
97
- registerTool(new ScheduleUpdateTool());
56
+ try {
57
+ const job = updateSchedule(jobId, updates as {
58
+ name?: string;
59
+ cronExpression?: string;
60
+ timezone?: string | null;
61
+ message?: string;
62
+ enabled?: boolean;
63
+ syntax?: ScheduleSyntax;
64
+ expression?: string;
65
+ });
66
+
67
+ if (!job) {
68
+ return { content: `Error: Schedule not found: ${jobId}`, isError: true };
69
+ }
70
+
71
+ const scheduleDescription = job.syntax === 'rrule'
72
+ ? job.expression
73
+ : describeCronExpression(job.cronExpression);
74
+
75
+ return {
76
+ content: [
77
+ `Schedule updated successfully.`,
78
+ ` Name: ${job.name}`,
79
+ ` Syntax: ${job.syntax}`,
80
+ ` Schedule: ${scheduleDescription}${job.timezone ? ` (${job.timezone})` : ''}`,
81
+ ` Enabled: ${job.enabled}`,
82
+ ` Next run: ${job.enabled ? formatLocalDate(job.nextRunAt) : 'n/a (disabled)'}`,
83
+ ].join('\n'),
84
+ isError: false,
85
+ };
86
+ } catch (err) {
87
+ const msg = err instanceof Error ? err.message : String(err);
88
+ return { content: `Error updating schedule: ${msg}`, isError: true };
89
+ }
90
+ }