agent-tempo 1.2.0 → 1.4.0

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 (281) hide show
  1. package/CLAUDE.md +253 -219
  2. package/LICENSE +21 -21
  3. package/README.md +293 -289
  4. package/assets/icon-dark.svg +9 -9
  5. package/assets/icon.svg +9 -9
  6. package/assets/logo-dark.svg +11 -11
  7. package/assets/logo-light.svg +11 -11
  8. package/dashboard/README.md +91 -91
  9. package/dashboard/dist/assets/{index-D6Xyje_n.js → index-jmYe6rmS.js} +2 -2
  10. package/dashboard/dist/assets/index-jmYe6rmS.js.map +1 -0
  11. package/dashboard/dist/index.html +20 -20
  12. package/dashboard/package.json +47 -47
  13. package/dist/activities/outbox.d.ts +30 -1
  14. package/dist/activities/outbox.js +96 -3
  15. package/dist/adapters/base.js +5 -0
  16. package/dist/adapters/copilot/adapter.js +12 -1
  17. package/dist/adapters/index.d.ts +1 -1
  18. package/dist/adapters/index.js +7 -0
  19. package/dist/adapters/pi/adapter.d.ts +2 -0
  20. package/dist/adapters/pi/adapter.js +43 -0
  21. package/dist/adapters/pi/index.d.ts +16 -0
  22. package/dist/adapters/pi/index.js +10 -0
  23. package/dist/cli/global-wrapper.d.ts +19 -0
  24. package/dist/cli/global-wrapper.js +169 -0
  25. package/dist/cli/help-text.js +97 -97
  26. package/dist/cli/startup.js +11 -0
  27. package/dist/cli/upgrade-command.js +81 -81
  28. package/dist/cli.js +12 -0
  29. package/dist/client/core.js +9 -2
  30. package/dist/client/interface.d.ts +6 -0
  31. package/dist/config.d.ts +79 -0
  32. package/dist/config.js +74 -0
  33. package/dist/daemon.js +37 -1
  34. package/dist/http/aggregate.d.ts +22 -1
  35. package/dist/http/aggregate.js +41 -0
  36. package/dist/http/auth.d.ts +94 -8
  37. package/dist/http/auth.js +93 -9
  38. package/dist/http/body.d.ts +4 -1
  39. package/dist/http/body.js +6 -3
  40. package/dist/http/event-bus.js +1 -0
  41. package/dist/http/event-types.d.ts +34 -2
  42. package/dist/http/event-types.js +1 -0
  43. package/dist/http/gate-audit.d.ts +12 -0
  44. package/dist/http/gate-audit.js +95 -0
  45. package/dist/http/gate-registry.d.ts +167 -0
  46. package/dist/http/gate-registry.js +163 -0
  47. package/dist/http/gate-routes.d.ts +48 -0
  48. package/dist/http/gate-routes.js +102 -0
  49. package/dist/http/ingest-registry.d.ts +30 -0
  50. package/dist/http/ingest-registry.js +108 -0
  51. package/dist/http/inner-loop-routes.d.ts +66 -0
  52. package/dist/http/inner-loop-routes.js +182 -0
  53. package/dist/http/inner-loop.d.ts +92 -0
  54. package/dist/http/inner-loop.js +155 -0
  55. package/dist/http/server.d.ts +38 -3
  56. package/dist/http/server.js +211 -6
  57. package/dist/http/snapshot.d.ts +6 -0
  58. package/dist/http/snapshot.js +6 -0
  59. package/dist/pi/cue-pump.d.ts +61 -0
  60. package/dist/pi/cue-pump.js +95 -0
  61. package/dist/pi/extension.d.ts +45 -0
  62. package/dist/pi/extension.js +407 -0
  63. package/dist/pi/gate-client.d.ts +54 -0
  64. package/dist/pi/gate-client.js +136 -0
  65. package/dist/pi/headless.d.ts +85 -0
  66. package/dist/pi/headless.js +224 -0
  67. package/dist/pi/index.d.ts +28 -0
  68. package/dist/pi/index.js +43 -0
  69. package/dist/pi/inner-loop-client.d.ts +67 -0
  70. package/dist/pi/inner-loop-client.js +164 -0
  71. package/dist/pi/inner-loop-publisher.d.ts +187 -0
  72. package/dist/pi/inner-loop-publisher.js +236 -0
  73. package/dist/pi/lazy-proxy.d.ts +37 -0
  74. package/dist/pi/lazy-proxy.js +55 -0
  75. package/dist/pi/mission-control/actions.d.ts +48 -0
  76. package/dist/pi/mission-control/actions.js +98 -0
  77. package/dist/pi/mission-control/board.d.ts +53 -0
  78. package/dist/pi/mission-control/board.js +104 -0
  79. package/dist/pi/mission-control/extension.d.ts +44 -0
  80. package/dist/pi/mission-control/extension.js +251 -0
  81. package/dist/pi/mission-control/index.d.ts +15 -0
  82. package/dist/pi/mission-control/index.js +32 -0
  83. package/dist/pi/mission-control/inner-tail.d.ts +48 -0
  84. package/dist/pi/mission-control/inner-tail.js +76 -0
  85. package/dist/pi/mission-control/pi-ui.d.ts +43 -0
  86. package/dist/pi/mission-control/pi-ui.js +10 -0
  87. package/dist/pi/mission-control/render.d.ts +6 -0
  88. package/dist/pi/mission-control/render.js +95 -0
  89. package/dist/pi/phase-driver.d.ts +74 -0
  90. package/dist/pi/phase-driver.js +122 -0
  91. package/dist/pi/pi-types.d.ts +208 -0
  92. package/dist/pi/pi-types.js +21 -0
  93. package/dist/pi/probe.d.ts +80 -0
  94. package/dist/pi/probe.js +154 -0
  95. package/dist/pi/render-tools.d.ts +17 -0
  96. package/dist/pi/render-tools.js +51 -0
  97. package/dist/pi/reset-pump.d.ts +47 -0
  98. package/dist/pi/reset-pump.js +85 -0
  99. package/dist/pi/tool-capability.d.ts +60 -0
  100. package/dist/pi/tool-capability.js +156 -0
  101. package/dist/pi/workflow-client.d.ts +158 -0
  102. package/dist/pi/workflow-client.js +289 -0
  103. package/dist/pi/zod-to-typebox.d.ts +74 -0
  104. package/dist/pi/zod-to-typebox.js +191 -0
  105. package/dist/scripts/verify-daemon-isolation-guard.js +24 -24
  106. package/dist/server-tools.d.ts +2 -0
  107. package/dist/server-tools.js +50 -46
  108. package/dist/server.js +4 -0
  109. package/dist/spawn.d.ts +55 -0
  110. package/dist/spawn.js +84 -12
  111. package/dist/tools/agent-types.d.ts +2 -2
  112. package/dist/tools/agent-types.js +22 -17
  113. package/dist/tools/attachment-info.d.ts +2 -2
  114. package/dist/tools/attachment-info.js +38 -33
  115. package/dist/tools/broadcast.d.ts +2 -2
  116. package/dist/tools/broadcast.js +69 -64
  117. package/dist/tools/cancel-stage.d.ts +2 -2
  118. package/dist/tools/cancel-stage.js +20 -15
  119. package/dist/tools/clear-state.d.ts +2 -2
  120. package/dist/tools/clear-state.js +25 -20
  121. package/dist/tools/coat-check-evict.d.ts +2 -2
  122. package/dist/tools/coat-check-evict.js +30 -25
  123. package/dist/tools/coat-check-get.d.ts +2 -2
  124. package/dist/tools/coat-check-get.js +39 -34
  125. package/dist/tools/coat-check-list.d.ts +2 -2
  126. package/dist/tools/coat-check-list.js +48 -43
  127. package/dist/tools/coat-check-put.d.ts +2 -2
  128. package/dist/tools/coat-check-put.js +41 -36
  129. package/dist/tools/cue.d.ts +2 -2
  130. package/dist/tools/cue.js +57 -52
  131. package/dist/tools/descriptor.d.ts +72 -0
  132. package/dist/tools/descriptor.js +39 -0
  133. package/dist/tools/destroy.d.ts +2 -2
  134. package/dist/tools/destroy.js +153 -148
  135. package/dist/tools/ensemble.d.ts +2 -2
  136. package/dist/tools/ensemble.js +71 -66
  137. package/dist/tools/evaluate-gate.d.ts +2 -2
  138. package/dist/tools/evaluate-gate.js +33 -27
  139. package/dist/tools/fetch-state.d.ts +2 -2
  140. package/dist/tools/fetch-state.js +43 -38
  141. package/dist/tools/gates.d.ts +2 -2
  142. package/dist/tools/gates.js +39 -34
  143. package/dist/tools/hosts.d.ts +2 -2
  144. package/dist/tools/hosts.js +25 -20
  145. package/dist/tools/listen.d.ts +2 -2
  146. package/dist/tools/listen.js +23 -18
  147. package/dist/tools/load-lineup.d.ts +2 -2
  148. package/dist/tools/load-lineup.js +324 -319
  149. package/dist/tools/migrate.d.ts +2 -2
  150. package/dist/tools/migrate.js +45 -40
  151. package/dist/tools/pause.d.ts +2 -2
  152. package/dist/tools/pause.js +34 -29
  153. package/dist/tools/play.d.ts +2 -2
  154. package/dist/tools/play.js +53 -48
  155. package/dist/tools/quality-gate.d.ts +2 -2
  156. package/dist/tools/quality-gate.js +26 -21
  157. package/dist/tools/recall.d.ts +2 -2
  158. package/dist/tools/recall.js +32 -27
  159. package/dist/tools/recruit.d.ts +2 -2
  160. package/dist/tools/recruit.js +325 -256
  161. package/dist/tools/release.d.ts +2 -2
  162. package/dist/tools/release.js +85 -80
  163. package/dist/tools/report.d.ts +2 -2
  164. package/dist/tools/report.js +28 -23
  165. package/dist/tools/reset.d.ts +3 -0
  166. package/dist/tools/reset.js +51 -0
  167. package/dist/tools/restart.d.ts +2 -2
  168. package/dist/tools/restart.js +51 -46
  169. package/dist/tools/restore.d.ts +2 -2
  170. package/dist/tools/restore.js +76 -71
  171. package/dist/tools/save-lineup.d.ts +2 -2
  172. package/dist/tools/save-lineup.js +32 -27
  173. package/dist/tools/save-state.d.ts +2 -2
  174. package/dist/tools/save-state.js +43 -38
  175. package/dist/tools/schedule.d.ts +2 -2
  176. package/dist/tools/schedule.js +133 -128
  177. package/dist/tools/schedules.d.ts +2 -2
  178. package/dist/tools/schedules.js +41 -36
  179. package/dist/tools/set-ensemble-description.d.ts +2 -2
  180. package/dist/tools/set-ensemble-description.js +26 -21
  181. package/dist/tools/set-name.d.ts +2 -2
  182. package/dist/tools/set-name.js +38 -33
  183. package/dist/tools/set-part.d.ts +2 -2
  184. package/dist/tools/set-part.js +20 -15
  185. package/dist/tools/shutdown.d.ts +2 -2
  186. package/dist/tools/shutdown.js +39 -34
  187. package/dist/tools/stage.d.ts +2 -2
  188. package/dist/tools/stage.js +28 -23
  189. package/dist/tools/stages.d.ts +2 -2
  190. package/dist/tools/stages.js +36 -31
  191. package/dist/tools/unschedule.d.ts +2 -2
  192. package/dist/tools/unschedule.js +30 -25
  193. package/dist/tools/who-am-i.d.ts +2 -2
  194. package/dist/tools/who-am-i.js +36 -31
  195. package/dist/tools/worktree.d.ts +2 -2
  196. package/dist/tools/worktree.js +134 -129
  197. package/dist/tui/index.js +6 -6
  198. package/dist/types.d.ts +47 -2
  199. package/dist/types.js +1 -1
  200. package/dist/utils/default-part.js +1 -0
  201. package/dist/utils/grpc-shutdown-guard.d.ts +52 -0
  202. package/dist/utils/grpc-shutdown-guard.js +88 -0
  203. package/dist/utils/sdk-probe.d.ts +23 -0
  204. package/dist/utils/sdk-probe.js +46 -7
  205. package/dist/worker.d.ts +3 -1
  206. package/dist/worker.js +6 -2
  207. package/dist/workflows/session.js +70 -2
  208. package/dist/workflows/signals.d.ts +32 -2
  209. package/dist/workflows/signals.js +25 -2
  210. package/examples/agents/tempo-composer.md +56 -56
  211. package/examples/agents/tempo-conductor.md +117 -117
  212. package/examples/agents/tempo-critic.md +73 -73
  213. package/examples/agents/tempo-improv.md +74 -74
  214. package/examples/agents/tempo-liner.md +75 -75
  215. package/examples/agents/tempo-roadie.md +61 -61
  216. package/examples/agents/tempo-soloist.md +71 -71
  217. package/examples/agents/tempo-tuner.md +94 -94
  218. package/examples/ensembles/tempo-big-band.yaml +146 -146
  219. package/examples/ensembles/tempo-dev-team.yaml +58 -58
  220. package/examples/ensembles/tempo-headless-jam.yaml +77 -77
  221. package/examples/ensembles/tempo-jam-session.yaml +41 -41
  222. package/examples/ensembles/tempo-mock-jam.yaml +79 -79
  223. package/examples/ensembles/tempo-review-squad.yaml +32 -32
  224. package/package.json +176 -173
  225. package/packaging/launchd/com.agent.tempo.plist +46 -46
  226. package/packaging/systemd/agent-tempo.service +32 -32
  227. package/packaging/windows/install-task.ps1 +71 -71
  228. package/scenarios/conductor-recruit-mock.yaml +33 -33
  229. package/scenarios/echo-roundtrip.yaml +15 -15
  230. package/scenarios/multi-player-handoff.yaml +38 -38
  231. package/scenarios/recruit-cascade.yaml +38 -38
  232. package/scenarios/two-player-conversation.yaml +33 -33
  233. package/workflow-bundle.js +97 -6
  234. package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
  235. package/dist/activities/claude-stop.d.ts +0 -21
  236. package/dist/activities/claude-stop.js +0 -94
  237. package/dist/channel.d.ts +0 -3
  238. package/dist/channel.js +0 -48
  239. package/dist/copilot-bridge.d.ts +0 -22
  240. package/dist/copilot-bridge.js +0 -565
  241. package/dist/scripts/258-spotcheck.js +0 -303
  242. package/dist/tools/detach.d.ts +0 -4
  243. package/dist/tools/detach.js +0 -45
  244. package/dist/tools/encore.d.ts +0 -4
  245. package/dist/tools/encore.js +0 -31
  246. package/dist/tools/helpers.d.ts +0 -21
  247. package/dist/tools/helpers.js +0 -25
  248. package/dist/tools/pause-ensemble.d.ts +0 -4
  249. package/dist/tools/pause-ensemble.js +0 -58
  250. package/dist/tools/resume-ensemble.d.ts +0 -4
  251. package/dist/tools/resume-ensemble.js +0 -79
  252. package/dist/tools/stop.d.ts +0 -4
  253. package/dist/tools/stop.js +0 -29
  254. package/dist/tui/client.d.ts +0 -6
  255. package/dist/tui/client.js +0 -9
  256. package/dist/tui/components/ActivityLog.d.ts +0 -16
  257. package/dist/tui/components/ActivityLog.js +0 -36
  258. package/dist/tui/components/CommandOverlay.d.ts +0 -15
  259. package/dist/tui/components/CommandOverlay.js +0 -34
  260. package/dist/tui/components/ConductorChat.d.ts +0 -16
  261. package/dist/tui/components/ConductorChat.js +0 -32
  262. package/dist/tui/components/EnsembleListView.d.ts +0 -14
  263. package/dist/tui/components/EnsembleListView.js +0 -32
  264. package/dist/tui/components/EnsemblePanel.d.ts +0 -12
  265. package/dist/tui/components/EnsemblePanel.js +0 -40
  266. package/dist/tui/components/InputBar.d.ts +0 -13
  267. package/dist/tui/components/InputBar.js +0 -58
  268. package/dist/tui/components/ScheduleOverlay.d.ts +0 -13
  269. package/dist/tui/components/ScheduleOverlay.js +0 -113
  270. package/dist/tui/components/TopBar.d.ts +0 -12
  271. package/dist/tui/components/TopBar.js +0 -15
  272. package/dist/tui/core-api.d.ts +0 -26
  273. package/dist/tui/core-api.js +0 -67
  274. package/dist/tui/hooks/useEnsembleDiscovery.d.ts +0 -3
  275. package/dist/tui/hooks/useEnsembleDiscovery.js +0 -30
  276. package/dist/tui/hooks/useMaestroPoller.d.ts +0 -3
  277. package/dist/tui/hooks/useMaestroPoller.js +0 -36
  278. package/dist/tui/hooks/useSendCommand.d.ts +0 -7
  279. package/dist/tui/hooks/useSendCommand.js +0 -29
  280. package/dist/utils/bg-preflight.d.ts +0 -25
  281. package/dist/utils/bg-preflight.js +0 -154
@@ -1,152 +1,157 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerScheduleTool = registerScheduleTool;
3
+ exports.buildScheduleTool = buildScheduleTool;
4
4
  const zod_1 = require("zod");
5
5
  const croner_1 = require("croner");
6
6
  const client_1 = require("@temporalio/client");
7
7
  const config_1 = require("../config");
8
8
  const duration_1 = require("../utils/duration");
9
9
  const resolve_1 = require("./resolve");
10
- const helpers_1 = require("./helpers");
10
+ const descriptor_1 = require("./descriptor");
11
11
  const validation_1 = require("../utils/validation");
12
12
  const log = (...args) => console.error('[agent-tempo:schedule]', ...args);
13
- function registerScheduleTool(server, client, config, getPlayerId) {
14
- (0, helpers_1.defineTool)(server, 'schedule', 'Schedule a message to be sent to a player at a specific time, after a delay, on a recurring interval, or via cron expression.', {
15
- name: zod_1.z.string().max(validation_1.SCHEDULE_NAME_MAX).describe('Unique name for this schedule'),
16
- message: zod_1.z.string().max(validation_1.SCHEDULE_MESSAGE_MAX).describe('The message to deliver'),
17
- target: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).describe('Player name to deliver to ("self" = this session)'),
18
- at: zod_1.z.string().optional().describe('ISO datetime for one-shot delivery (e.g. "2026-04-03T20:00:00Z")'),
19
- delay: zod_1.z.string().optional().describe('Duration until first delivery (e.g. "10m", "2h", "1d")'),
20
- every: zod_1.z.string().optional().describe('Recurring interval (e.g. "5m", "1h")'),
21
- cron: zod_1.z.string().max(validation_1.CRON_EXPRESSION_MAX).optional().describe('Cron expression for recurring delivery (e.g. "0 9 * * 1-5" = weekdays at 9am). Mutually exclusive with at/delay/every.'),
22
- timezone: zod_1.z.string().optional().describe('IANA timezone for cron evaluation (e.g. "America/New_York"). Defaults to UTC. Only used with cron.'),
23
- until: zod_1.z.string().optional().describe('ISO datetime stop recurring after this time'),
24
- count: zod_1.z.number().optional().describe('Max number of deliveries for recurring schedules'),
25
- }, async (args) => {
26
- const { name, message, at, delay, every, cron, timezone, until, count } = args;
27
- let target = args.target;
28
- // Resolve "self" to the current player name
29
- if (target === 'self') {
30
- target = getPlayerId();
31
- }
32
- // Validate target player exists (warn, don't block)
33
- let targetWarning;
34
- if (target !== 'all' && target !== 'conductor') {
35
- try {
36
- const targetHandle = await (0, resolve_1.resolveSession)(client, config.ensemble, target);
37
- if (!targetHandle) {
38
- targetWarning = `Warning: player "${target}" is not currently active. The schedule will be created but may fail to deliver until the player joins.`;
13
+ function buildScheduleTool(client, config, getPlayerId) {
14
+ return {
15
+ name: 'schedule',
16
+ description: 'Schedule a message to be sent to a player at a specific time, after a delay, on a recurring interval, or via cron expression.',
17
+ params: {
18
+ name: zod_1.z.string().max(validation_1.SCHEDULE_NAME_MAX).describe('Unique name for this schedule'),
19
+ message: zod_1.z.string().max(validation_1.SCHEDULE_MESSAGE_MAX).describe('The message to deliver'),
20
+ target: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).describe('Player name to deliver to ("self" = this session)'),
21
+ at: zod_1.z.string().optional().describe('ISO datetime for one-shot delivery (e.g. "2026-04-03T20:00:00Z")'),
22
+ delay: zod_1.z.string().optional().describe('Duration until first delivery (e.g. "10m", "2h", "1d")'),
23
+ every: zod_1.z.string().optional().describe('Recurring interval (e.g. "5m", "1h")'),
24
+ cron: zod_1.z.string().max(validation_1.CRON_EXPRESSION_MAX).optional().describe('Cron expression for recurring delivery (e.g. "0 9 * * 1-5" = weekdays at 9am). Mutually exclusive with at/delay/every.'),
25
+ timezone: zod_1.z.string().optional().describe('IANA timezone for cron evaluation (e.g. "America/New_York"). Defaults to UTC. Only used with cron.'),
26
+ until: zod_1.z.string().optional().describe('ISO datetime stop recurring after this time'),
27
+ count: zod_1.z.number().optional().describe('Max number of deliveries for recurring schedules'),
28
+ },
29
+ handler: async (args) => {
30
+ const { name, message, at, delay, every, cron, timezone, until, count } = args;
31
+ let target = args.target;
32
+ // Resolve "self" to the current player name
33
+ if (target === 'self') {
34
+ target = getPlayerId();
35
+ }
36
+ // Validate target player exists (warn, don't block)
37
+ let targetWarning;
38
+ if (target !== 'all' && target !== 'conductor') {
39
+ try {
40
+ const targetHandle = await (0, resolve_1.resolveSession)(client, config.ensemble, target);
41
+ if (!targetHandle) {
42
+ targetWarning = `Warning: player "${target}" is not currently active. The schedule will be created but may fail to deliver until the player joins.`;
43
+ }
44
+ }
45
+ catch {
46
+ // Resolution failed — don't block schedule creation
39
47
  }
40
48
  }
41
- catch {
42
- // Resolution failed don't block schedule creation
49
+ // Validate exactly one timing option
50
+ const timingCount = [at, delay, every, cron].filter(Boolean).length;
51
+ if (timingCount !== 1) {
52
+ return (0, descriptor_1.fail)('Provide exactly one timing option: `at`, `delay`, `every`, or `cron`.');
43
53
  }
44
- }
45
- // Validate exactly one timing option
46
- const timingCount = [at, delay, every, cron].filter(Boolean).length;
47
- if (timingCount !== 1) {
48
- return (0, helpers_1.fail)('Provide exactly one timing option: `at`, `delay`, `every`, or `cron`.');
49
- }
50
- // timezone only valid with cron
51
- if (timezone && !cron) {
52
- return (0, helpers_1.fail)('`timezone` can only be used with `cron`.');
53
- }
54
- const now = Date.now();
55
- let nextFireAt;
56
- let interval;
57
- if (at) {
58
- const ts = Date.parse(at);
59
- if (isNaN(ts)) {
60
- return (0, helpers_1.fail)(`Invalid ISO datetime for "at": ${at}`);
54
+ // timezone only valid with cron
55
+ if (timezone && !cron) {
56
+ return (0, descriptor_1.fail)('`timezone` can only be used with `cron`.');
61
57
  }
62
- nextFireAt = ts;
63
- }
64
- else if (delay) {
65
- const ms = (0, duration_1.parseDuration)(delay);
66
- if (ms === null) {
67
- return (0, helpers_1.fail)(`Invalid duration for "delay": ${delay}. Use e.g. "30s", "10m", "2h", "1d".`);
58
+ const now = Date.now();
59
+ let nextFireAt;
60
+ let interval;
61
+ if (at) {
62
+ const ts = Date.parse(at);
63
+ if (isNaN(ts)) {
64
+ return (0, descriptor_1.fail)(`Invalid ISO datetime for "at": ${at}`);
65
+ }
66
+ nextFireAt = ts;
68
67
  }
69
- nextFireAt = now + ms;
70
- }
71
- else if (every) {
72
- // every (recurring interval)
73
- const ms = (0, duration_1.parseDuration)(every);
74
- if (ms === null || ms < 10_000) {
75
- return (0, helpers_1.fail)(`Invalid or too-short interval for "every": ${every}. Minimum is 10s.`);
68
+ else if (delay) {
69
+ const ms = (0, duration_1.parseDuration)(delay);
70
+ if (ms === null) {
71
+ return (0, descriptor_1.fail)(`Invalid duration for "delay": ${delay}. Use e.g. "30s", "10m", "2h", "1d".`);
72
+ }
73
+ nextFireAt = now + ms;
76
74
  }
77
- nextFireAt = now + ms;
78
- interval = ms;
79
- }
80
- else {
81
- // cron (recurring via cron expression)
82
- try {
83
- const job = new croner_1.Cron(cron, { timezone: timezone || 'UTC' });
84
- const next = job.nextRun();
85
- if (!next) {
86
- return (0, helpers_1.fail)(`Cron expression "${cron}" has no upcoming fire time.`);
75
+ else if (every) {
76
+ // every (recurring interval)
77
+ const ms = (0, duration_1.parseDuration)(every);
78
+ if (ms === null || ms < 10_000) {
79
+ return (0, descriptor_1.fail)(`Invalid or too-short interval for "every": ${every}. Minimum is 10s.`);
87
80
  }
88
- nextFireAt = next.getTime();
81
+ nextFireAt = now + ms;
82
+ interval = ms;
89
83
  }
90
- catch (err) {
91
- return (0, helpers_1.fail)(`Invalid cron expression "${cron}": ${(0, helpers_1.formatError)(err)}`);
84
+ else {
85
+ // cron (recurring via cron expression)
86
+ try {
87
+ const job = new croner_1.Cron(cron, { timezone: timezone || 'UTC' });
88
+ const next = job.nextRun();
89
+ if (!next) {
90
+ return (0, descriptor_1.fail)(`Cron expression "${cron}" has no upcoming fire time.`);
91
+ }
92
+ nextFireAt = next.getTime();
93
+ }
94
+ catch (err) {
95
+ return (0, descriptor_1.fail)(`Invalid cron expression "${cron}": ${(0, descriptor_1.formatError)(err)}`);
96
+ }
92
97
  }
93
- }
94
- // Parse optional until
95
- let untilMs;
96
- if (until) {
97
- const ts = Date.parse(until);
98
- if (isNaN(ts)) {
99
- return (0, helpers_1.fail)(`Invalid ISO datetime for "until": ${until}`);
98
+ // Parse optional until
99
+ let untilMs;
100
+ if (until) {
101
+ const ts = Date.parse(until);
102
+ if (isNaN(ts)) {
103
+ return (0, descriptor_1.fail)(`Invalid ISO datetime for "until": ${until}`);
104
+ }
105
+ untilMs = ts;
100
106
  }
101
- untilMs = ts;
102
- }
103
- const type = cron ? 'cron' : every ? 'interval' : 'once';
104
- const scheduleEntry = {
105
- name,
106
- message,
107
- target,
108
- type,
109
- nextFireAt: new Date(nextFireAt).toISOString(),
110
- interval,
111
- cronExpression: cron,
112
- timezone: cron ? (timezone || 'UTC') : undefined,
113
- until: untilMs ? new Date(untilMs).toISOString() : undefined,
114
- remainingCount: count,
115
- firedCount: 0,
116
- createdBy: getPlayerId(),
117
- };
118
- try {
119
- const wfId = (0, config_1.schedulerWorkflowId)(config.ensemble);
120
- // Try to signal the existing scheduler workflow
107
+ const type = cron ? 'cron' : every ? 'interval' : 'once';
108
+ const scheduleEntry = {
109
+ name,
110
+ message,
111
+ target,
112
+ type,
113
+ nextFireAt: new Date(nextFireAt).toISOString(),
114
+ interval,
115
+ cronExpression: cron,
116
+ timezone: cron ? (timezone || 'UTC') : undefined,
117
+ until: untilMs ? new Date(untilMs).toISOString() : undefined,
118
+ remainingCount: count,
119
+ firedCount: 0,
120
+ createdBy: getPlayerId(),
121
+ };
121
122
  try {
122
- const handle = client.workflow.getHandle(wfId);
123
- await handle.describe(); // throws if not running
124
- await handle.signal('addSchedule', scheduleEntry);
123
+ const wfId = (0, config_1.schedulerWorkflowId)(config.ensemble);
124
+ // Try to signal the existing scheduler workflow
125
+ try {
126
+ const handle = client.workflow.getHandle(wfId);
127
+ await handle.describe(); // throws if not running
128
+ await handle.signal('addSchedule', scheduleEntry);
129
+ }
130
+ catch {
131
+ // Scheduler not running — start it with this schedule as seed
132
+ await client.workflow.start('agentSchedulerWorkflow', {
133
+ workflowId: wfId,
134
+ taskQueue: config.taskQueue,
135
+ args: [{ ensemble: config.ensemble, entries: [scheduleEntry] }],
136
+ workflowIdConflictPolicy: client_1.WorkflowIdConflictPolicy.USE_EXISTING,
137
+ searchAttributes: {
138
+ AgentTempoEnsemble: [config.ensemble],
139
+ },
140
+ });
141
+ log(`Started scheduler workflow ${wfId}`);
142
+ }
143
+ const fireDate = new Date(nextFireAt).toISOString();
144
+ const recur = cron
145
+ ? ` (cron: ${cron}, tz: ${timezone || 'UTC'})`
146
+ : interval
147
+ ? ` (repeating every ${every})`
148
+ : ' (one-shot)';
149
+ const msg = `Schedule **${name}** created. Next fire: ${fireDate}${recur}. Target: ${target}.`;
150
+ return (0, descriptor_1.ok)(targetWarning ? `${msg}\n\n⚠ ${targetWarning}` : msg);
125
151
  }
126
- catch {
127
- // Scheduler not running start it with this schedule as seed
128
- await client.workflow.start('agentSchedulerWorkflow', {
129
- workflowId: wfId,
130
- taskQueue: config.taskQueue,
131
- args: [{ ensemble: config.ensemble, entries: [scheduleEntry] }],
132
- workflowIdConflictPolicy: client_1.WorkflowIdConflictPolicy.USE_EXISTING,
133
- searchAttributes: {
134
- AgentTempoEnsemble: [config.ensemble],
135
- },
136
- });
137
- log(`Started scheduler workflow ${wfId}`);
152
+ catch (err) {
153
+ return (0, descriptor_1.fail)(`Failed to create schedule: ${(0, descriptor_1.formatError)(err)}`);
138
154
  }
139
- const fireDate = new Date(nextFireAt).toISOString();
140
- const recur = cron
141
- ? ` (cron: ${cron}, tz: ${timezone || 'UTC'})`
142
- : interval
143
- ? ` (repeating every ${every})`
144
- : ' (one-shot)';
145
- const msg = `Schedule **${name}** created. Next fire: ${fireDate}${recur}. Target: ${target}.`;
146
- return (0, helpers_1.ok)(targetWarning ? `${msg}\n\n⚠ ${targetWarning}` : msg);
147
- }
148
- catch (err) {
149
- return (0, helpers_1.fail)(`Failed to create schedule: ${(0, helpers_1.formatError)(err)}`);
150
- }
151
- });
155
+ },
156
+ };
152
157
  }
@@ -1,4 +1,4 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { Client } from '@temporalio/client';
3
2
  import { Config } from '../config';
4
- export declare function registerSchedulesTool(server: McpServer, client: Client, config: Config): void;
3
+ import { type TempoToolDescriptor } from './descriptor';
4
+ export declare function buildSchedulesTool(client: Client, config: Config): TempoToolDescriptor;
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerSchedulesTool = registerSchedulesTool;
3
+ exports.buildSchedulesTool = buildSchedulesTool;
4
4
  const client_1 = require("@temporalio/client");
5
5
  const config_1 = require("../config");
6
- const helpers_1 = require("./helpers");
6
+ const descriptor_1 = require("./descriptor");
7
7
  function formatDuration(ms) {
8
8
  if (ms >= 86_400_000)
9
9
  return `${ms / 86_400_000}d`;
@@ -13,42 +13,47 @@ function formatDuration(ms) {
13
13
  return `${ms / 60_000}m`;
14
14
  return `${ms / 1000}s`;
15
15
  }
16
- function registerSchedulesTool(server, client, config) {
17
- (0, helpers_1.defineTool)(server, 'schedules', 'List all active schedules in this ensemble.', {}, async () => {
18
- try {
19
- const wfId = (0, config_1.schedulerWorkflowId)(config.ensemble);
20
- const handle = client.workflow.getHandle(wfId);
21
- let schedules;
16
+ function buildSchedulesTool(client, config) {
17
+ return {
18
+ name: 'schedules',
19
+ description: 'List all active schedules in this ensemble.',
20
+ params: {},
21
+ handler: async () => {
22
22
  try {
23
- schedules = await handle.query('getSchedules');
24
- }
25
- catch (err) {
26
- if (err instanceof client_1.WorkflowNotFoundError) {
27
- return (0, helpers_1.ok)('No scheduler running — no schedules exist yet.');
23
+ const wfId = (0, config_1.schedulerWorkflowId)(config.ensemble);
24
+ const handle = client.workflow.getHandle(wfId);
25
+ let schedules;
26
+ try {
27
+ schedules = await handle.query('getSchedules');
28
+ }
29
+ catch (err) {
30
+ if (err instanceof client_1.WorkflowNotFoundError) {
31
+ return (0, descriptor_1.ok)('No scheduler running — no schedules exist yet.');
32
+ }
33
+ throw err;
28
34
  }
29
- throw err;
35
+ if (schedules.length === 0) {
36
+ return (0, descriptor_1.ok)('No active schedules.');
37
+ }
38
+ const lines = schedules.map((s) => {
39
+ const next = s.nextFireAt; // already ISO string
40
+ const recur = s.cronExpression
41
+ ? `cron: ${s.cronExpression} (${s.timezone || 'UTC'})`
42
+ : s.interval ? `every ${formatDuration(s.interval)}` : 'one-shot';
43
+ const bounds = [];
44
+ if (s.until)
45
+ bounds.push(`until ${s.until}`);
46
+ if (s.remainingCount != null)
47
+ bounds.push(`${s.firedCount}/${s.firedCount + s.remainingCount} fired`);
48
+ const boundsStr = bounds.length ? ` (${bounds.join(', ')})` : '';
49
+ const msgPreview = s.message.length > 60 ? s.message.slice(0, 57) + '...' : s.message;
50
+ return `• **${s.name}** → ${s.target} | ${recur}${boundsStr} | next: ${next}\n msg: ${msgPreview}`;
51
+ });
52
+ return (0, descriptor_1.ok)(lines.join('\n'));
30
53
  }
31
- if (schedules.length === 0) {
32
- return (0, helpers_1.ok)('No active schedules.');
54
+ catch (err) {
55
+ return (0, descriptor_1.fail)(`Failed to query schedules: ${(0, descriptor_1.formatError)(err)}`);
33
56
  }
34
- const lines = schedules.map((s) => {
35
- const next = s.nextFireAt; // already ISO string
36
- const recur = s.cronExpression
37
- ? `cron: ${s.cronExpression} (${s.timezone || 'UTC'})`
38
- : s.interval ? `every ${formatDuration(s.interval)}` : 'one-shot';
39
- const bounds = [];
40
- if (s.until)
41
- bounds.push(`until ${s.until}`);
42
- if (s.remainingCount != null)
43
- bounds.push(`${s.firedCount}/${s.firedCount + s.remainingCount} fired`);
44
- const boundsStr = bounds.length ? ` (${bounds.join(', ')})` : '';
45
- const msgPreview = s.message.length > 60 ? s.message.slice(0, 57) + '...' : s.message;
46
- return `• **${s.name}** → ${s.target} | ${recur}${boundsStr} | next: ${next}\n msg: ${msgPreview}`;
47
- });
48
- return (0, helpers_1.ok)(lines.join('\n'));
49
- }
50
- catch (err) {
51
- return (0, helpers_1.fail)(`Failed to query schedules: ${(0, helpers_1.formatError)(err)}`);
52
- }
53
- });
57
+ },
58
+ };
54
59
  }
@@ -1,4 +1,4 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { Client } from '@temporalio/client';
3
2
  import { Config } from '../config';
4
- export declare function registerSetEnsembleDescriptionTool(server: McpServer, client: Client, config: Config): void;
3
+ import { type TempoToolDescriptor } from './descriptor';
4
+ export declare function buildSetEnsembleDescriptionTool(client: Client, config: Config): TempoToolDescriptor;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerSetEnsembleDescriptionTool = registerSetEnsembleDescriptionTool;
3
+ exports.buildSetEnsembleDescriptionTool = buildSetEnsembleDescriptionTool;
4
4
  /**
5
5
  * `set_ensemble_description` MCP tool — #399 W1 (Q5.1).
6
6
  *
@@ -12,26 +12,31 @@ exports.registerSetEnsembleDescriptionTool = registerSetEnsembleDescriptionTool;
12
12
  const zod_1 = require("zod");
13
13
  const config_1 = require("../config");
14
14
  const maestro_signals_1 = require("../workflows/maestro-signals");
15
- const helpers_1 = require("./helpers");
15
+ const descriptor_1 = require("./descriptor");
16
16
  const validation_1 = require("../utils/validation");
17
- function registerSetEnsembleDescriptionTool(server, client, config) {
18
- (0, helpers_1.defineTool)(server, 'set_ensemble_description', "Update the ensemble's mission-flavor description (≤100 chars). Surfaces on the dashboard EnsembleCard. Empty string clears it. Refresh at milestone boundaries — don't update for trivial changes.", {
19
- description: zod_1.z
20
- .string()
21
- .max(validation_1.ENSEMBLE_DESCRIPTION_MAX)
22
- .describe('Short summary of what the ensemble is currently working on (≤100 chars).'),
23
- }, async (args) => {
24
- const { description } = args;
25
- try {
26
- const handle = client.workflow.getHandle((0, config_1.maestroWorkflowId)(config.ensemble));
27
- await handle.signal(maestro_signals_1.setEnsembleDescriptionSignal.name, description);
28
- if (description.trim().length === 0) {
29
- return (0, helpers_1.ok)(`Ensemble **${config.ensemble}** description cleared.`);
17
+ function buildSetEnsembleDescriptionTool(client, config) {
18
+ return {
19
+ name: 'set_ensemble_description',
20
+ description: "Update the ensemble's mission-flavor description (≤100 chars). Surfaces on the dashboard EnsembleCard. Empty string clears it. Refresh at milestone boundaries — don't update for trivial changes.",
21
+ params: {
22
+ description: zod_1.z
23
+ .string()
24
+ .max(validation_1.ENSEMBLE_DESCRIPTION_MAX)
25
+ .describe('Short summary of what the ensemble is currently working on (≤100 chars).'),
26
+ },
27
+ handler: async (args) => {
28
+ const { description } = args;
29
+ try {
30
+ const handle = client.workflow.getHandle((0, config_1.maestroWorkflowId)(config.ensemble));
31
+ await handle.signal(maestro_signals_1.setEnsembleDescriptionSignal.name, description);
32
+ if (description.trim().length === 0) {
33
+ return (0, descriptor_1.ok)(`Ensemble **${config.ensemble}** description cleared.`);
34
+ }
35
+ return (0, descriptor_1.ok)(`Ensemble **${config.ensemble}** description updated: "${description}"`);
30
36
  }
31
- return (0, helpers_1.ok)(`Ensemble **${config.ensemble}** description updated: "${description}"`);
32
- }
33
- catch (err) {
34
- return (0, helpers_1.fail)(`Failed to set ensemble description: ${(0, helpers_1.formatError)(err)}`);
35
- }
36
- });
37
+ catch (err) {
38
+ return (0, descriptor_1.fail)(`Failed to set ensemble description: ${(0, descriptor_1.formatError)(err)}`);
39
+ }
40
+ },
41
+ };
37
42
  }
@@ -1,4 +1,4 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { WorkflowHandle, Client } from '@temporalio/client';
3
2
  import { Config } from '../config';
4
- export declare function registerSetNameTool(server: McpServer, client: Client, config: Config, handle: WorkflowHandle, getPlayerId: () => string, setPlayerId: (id: string) => void): void;
3
+ import { type TempoToolDescriptor } from './descriptor';
4
+ export declare function buildSetNameTool(client: Client, config: Config, handle: WorkflowHandle, getPlayerId: () => string, setPlayerId: (id: string) => void): TempoToolDescriptor;
@@ -1,45 +1,50 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerSetNameTool = registerSetNameTool;
3
+ exports.buildSetNameTool = buildSetNameTool;
4
4
  const zod_1 = require("zod");
5
5
  const config_1 = require("../config");
6
6
  const scheduler_signals_1 = require("../workflows/scheduler-signals");
7
7
  const resolve_1 = require("./resolve");
8
- const helpers_1 = require("./helpers");
8
+ const descriptor_1 = require("./descriptor");
9
9
  const validation_1 = require("../utils/validation");
10
10
  const log = (...args) => console.error('[agent-tempo:set-name]', ...args);
11
- function registerSetNameTool(server, client, config, handle, getPlayerId, setPlayerId) {
12
- (0, helpers_1.defineTool)(server, 'set_name', 'Set a human-readable name for this session. Visible to other players in the ensemble.', {
13
- name: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).describe('The name for this session (e.g., "UX", "API", "test-runner")'),
14
- }, async (args) => {
15
- const { name } = args;
16
- // Validate name to prevent search attribute query injection
17
- const nameError = (0, validation_1.validatePlayerName)(name);
18
- if (nameError) {
19
- return (0, helpers_1.fail)(nameError);
20
- }
21
- // Check if the name is already taken
22
- const existing = await (0, resolve_1.resolveSession)(client, config.ensemble, name);
23
- if (existing && existing.workflowId !== handle.workflowId) {
24
- return (0, helpers_1.fail)(`Name **${name}** is already taken by another session. Choose a different name.`);
25
- }
26
- try {
27
- const oldName = getPlayerId();
28
- await handle.signal('setName', name);
29
- setPlayerId(name);
30
- // Notify the scheduler to rewrite schedule targets from old name to new name
11
+ function buildSetNameTool(client, config, handle, getPlayerId, setPlayerId) {
12
+ return {
13
+ name: 'set_name',
14
+ description: 'Set a human-readable name for this session. Visible to other players in the ensemble.',
15
+ params: {
16
+ name: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).describe('The name for this session (e.g., "UX", "API", "test-runner")'),
17
+ },
18
+ handler: async (args) => {
19
+ const { name } = args;
20
+ // Validate name to prevent search attribute query injection
21
+ const nameError = (0, validation_1.validatePlayerName)(name);
22
+ if (nameError) {
23
+ return (0, descriptor_1.fail)(nameError);
24
+ }
25
+ // Check if the name is already taken
26
+ const existing = await (0, resolve_1.resolveSession)(client, config.ensemble, name);
27
+ if (existing && existing.workflowId !== handle.workflowId) {
28
+ return (0, descriptor_1.fail)(`Name **${name}** is already taken by another session. Choose a different name.`);
29
+ }
31
30
  try {
32
- const schedulerHandle = client.workflow.getHandle((0, config_1.schedulerWorkflowId)(config.ensemble));
33
- await schedulerHandle.signal(scheduler_signals_1.updateScheduleTargetSignal, oldName, name);
31
+ const oldName = getPlayerId();
32
+ await handle.signal('setName', name);
33
+ setPlayerId(name);
34
+ // Notify the scheduler to rewrite schedule targets from old name to new name
35
+ try {
36
+ const schedulerHandle = client.workflow.getHandle((0, config_1.schedulerWorkflowId)(config.ensemble));
37
+ await schedulerHandle.signal(scheduler_signals_1.updateScheduleTargetSignal, oldName, name);
38
+ }
39
+ catch {
40
+ // Scheduler may not be running — that's fine, no schedules to update
41
+ log(`No scheduler to notify about rename ${oldName} → ${name}`);
42
+ }
43
+ return (0, descriptor_1.ok)(`Session name set to **${name}**. Run \`/rename ${name}\` to match your Claude Code session name.`);
34
44
  }
35
- catch {
36
- // Scheduler may not be running — that's fine, no schedules to update
37
- log(`No scheduler to notify about rename ${oldName} → ${name}`);
45
+ catch (err) {
46
+ return (0, descriptor_1.fail)(`Failed to set name: ${(0, descriptor_1.formatError)(err)}`);
38
47
  }
39
- return (0, helpers_1.ok)(`Session name set to **${name}**. Run \`/rename ${name}\` to match your Claude Code session name.`);
40
- }
41
- catch (err) {
42
- return (0, helpers_1.fail)(`Failed to set name: ${(0, helpers_1.formatError)(err)}`);
43
- }
44
- });
48
+ },
49
+ };
45
50
  }
@@ -1,3 +1,3 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { WorkflowHandle } from '@temporalio/client';
3
- export declare function registerSetPartTool(server: McpServer, handle: WorkflowHandle): void;
2
+ import { type TempoToolDescriptor } from './descriptor';
3
+ export declare function buildSetPartTool(handle: WorkflowHandle): TempoToolDescriptor;
@@ -1,20 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerSetPartTool = registerSetPartTool;
3
+ exports.buildSetPartTool = buildSetPartTool;
4
4
  const zod_1 = require("zod");
5
- const helpers_1 = require("./helpers");
5
+ const descriptor_1 = require("./descriptor");
6
6
  const validation_1 = require("../utils/validation");
7
- function registerSetPartTool(server, handle) {
8
- (0, helpers_1.defineTool)(server, 'set_part', 'Update your description of what you are currently working on. Visible to other sessions via ensemble.', {
9
- part: zod_1.z.string().max(validation_1.PART_MAX).describe('A short description of your current work'),
10
- }, async (args) => {
11
- const { part } = args;
12
- try {
13
- await handle.signal('setPart', part);
14
- return (0, helpers_1.ok)(`Part updated: "${part}"`);
15
- }
16
- catch (err) {
17
- return (0, helpers_1.fail)(`Failed to update part: ${(0, helpers_1.formatError)(err)}`);
18
- }
19
- });
7
+ function buildSetPartTool(handle) {
8
+ return {
9
+ name: 'set_part',
10
+ description: 'Update your description of what you are currently working on. Visible to other sessions via ensemble.',
11
+ params: {
12
+ part: zod_1.z.string().max(validation_1.PART_MAX).describe('A short description of your current work'),
13
+ },
14
+ handler: async (args) => {
15
+ const { part } = args;
16
+ try {
17
+ await handle.signal('setPart', part);
18
+ return (0, descriptor_1.ok)(`Part updated: "${part}"`);
19
+ }
20
+ catch (err) {
21
+ return (0, descriptor_1.fail)(`Failed to update part: ${(0, descriptor_1.formatError)(err)}`);
22
+ }
23
+ },
24
+ };
20
25
  }
@@ -1,4 +1,4 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { Client } from '@temporalio/client';
3
2
  import { Config } from '../config';
4
- export declare function registerShutdownTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string): void;
3
+ import { type TempoToolDescriptor } from './descriptor';
4
+ export declare function buildShutdownTool(client: Client, config: Config, getPlayerId: () => string): TempoToolDescriptor;