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,4 +1,4 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { Client, WorkflowHandle } from '@temporalio/client';
3
2
  import { Config } from '../config';
4
- export declare function registerReleaseTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string, handle: WorkflowHandle): void;
3
+ import { type TempoToolDescriptor } from './descriptor';
4
+ export declare function buildReleaseTool(client: Client, config: Config, getPlayerId: () => string, handle: WorkflowHandle): TempoToolDescriptor;
@@ -1,98 +1,103 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerReleaseTool = registerReleaseTool;
3
+ exports.buildReleaseTool = buildReleaseTool;
4
4
  const zod_1 = require("zod");
5
5
  const resolve_1 = require("../activities/resolve");
6
6
  const signals_1 = require("../workflows/signals");
7
- const helpers_1 = require("./helpers");
7
+ const descriptor_1 = require("./descriptor");
8
8
  const validation_1 = require("../utils/validation");
9
- function registerReleaseTool(server, client, config, getPlayerId, handle) {
10
- (0, helpers_1.defineTool)(server, 'release', 'Release held player sessions — unlocks their outboxes and delivers deferred task messages. Without a player name, releases all held sessions.', {
11
- player: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional()
12
- .describe('Name of a specific held player to release. Omit to release all held players.'),
13
- }, async (args) => {
14
- const { player } = args;
15
- if (player) {
16
- const nameError = (0, validation_1.validatePlayerName)(player);
17
- if (nameError)
18
- return (0, helpers_1.fail)(nameError);
19
- }
20
- try {
9
+ function buildReleaseTool(client, config, getPlayerId, handle) {
10
+ return {
11
+ name: 'release',
12
+ description: 'Release held player sessions unlocks their outboxes and delivers deferred task messages. Without a player name, releases all held sessions.',
13
+ params: {
14
+ player: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional()
15
+ .describe('Name of a specific held player to release. Omit to release all held players.'),
16
+ },
17
+ handler: async (args) => {
18
+ const { player } = args;
21
19
  if (player) {
22
- // Release a specific player
23
- const sessions = await (0, resolve_1.scanEnsembleSessions)(client, config.ensemble);
24
- const target = sessions.find((s) => s.playerId === player);
25
- if (!target) {
26
- return (0, helpers_1.fail)(`No session found with name "${player}".`);
27
- }
28
- // Check if the session's outbox is actually locked
29
- const targetHandle = client.workflow.getHandle(target.workflowId);
30
- let isLocked = false;
31
- try {
32
- isLocked = await targetHandle.query(signals_1.outboxLockedQuery);
33
- }
34
- catch {
35
- // Query may fail for old workflows without the handler — not held
36
- }
37
- if (!isLocked) {
38
- return (0, helpers_1.fail)(`Session "${player}" is not held (outbox not locked). Only held sessions can be released.`);
39
- }
40
- const entry = {
41
- type: 'release',
42
- targetPlayerId: player,
43
- };
44
- const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
45
- return (0, helpers_1.ok)(`Release request submitted for **${player}**. Task assignment will be delivered shortly. (outbox: ${entryId})`);
20
+ const nameError = (0, validation_1.validatePlayerName)(player);
21
+ if (nameError)
22
+ return (0, descriptor_1.fail)(nameError);
46
23
  }
47
- else {
48
- // Release all held players — scan ensemble and check outboxLocked on each
49
- const sessions = await (0, resolve_1.scanEnsembleSessions)(client, config.ensemble);
50
- const heldSessions = [];
51
- for (const session of sessions) {
52
- // Skip self and conductors (conductor outbox is never locked)
53
- if (session.playerId === getPlayerId())
54
- continue;
24
+ try {
25
+ if (player) {
26
+ // Release a specific player
27
+ const sessions = await (0, resolve_1.scanEnsembleSessions)(client, config.ensemble);
28
+ const target = sessions.find((s) => s.playerId === player);
29
+ if (!target) {
30
+ return (0, descriptor_1.fail)(`No session found with name "${player}".`);
31
+ }
32
+ // Check if the session's outbox is actually locked
33
+ const targetHandle = client.workflow.getHandle(target.workflowId);
34
+ let isLocked = false;
55
35
  try {
56
- const sessionHandle = client.workflow.getHandle(session.workflowId);
57
- const isLocked = await sessionHandle.query(signals_1.outboxLockedQuery);
58
- if (isLocked) {
59
- heldSessions.push(session);
60
- }
36
+ isLocked = await targetHandle.query(signals_1.outboxLockedQuery);
61
37
  }
62
38
  catch {
63
- // Skip sessions where query fails (old workflows, terminated, etc.)
39
+ // Query may fail for old workflows without the handler — not held
64
40
  }
41
+ if (!isLocked) {
42
+ return (0, descriptor_1.fail)(`Session "${player}" is not held (outbox not locked). Only held sessions can be released.`);
43
+ }
44
+ const entry = {
45
+ type: 'release',
46
+ targetPlayerId: player,
47
+ };
48
+ const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
49
+ return (0, descriptor_1.ok)(`Release request submitted for **${player}**. Task assignment will be delivered shortly. (outbox: ${entryId})`);
65
50
  }
66
- if (heldSessions.length === 0) {
67
- return (0, helpers_1.ok)('No held sessions found. Nothing to release.');
68
- }
69
- const released = [];
70
- const errors = [];
71
- for (const session of heldSessions) {
72
- try {
73
- const entry = {
74
- type: 'release',
75
- targetPlayerId: session.playerId,
76
- };
77
- await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
78
- released.push(session.playerId);
51
+ else {
52
+ // Release all held players scan ensemble and check outboxLocked on each
53
+ const sessions = await (0, resolve_1.scanEnsembleSessions)(client, config.ensemble);
54
+ const heldSessions = [];
55
+ for (const session of sessions) {
56
+ // Skip self and conductors (conductor outbox is never locked)
57
+ if (session.playerId === getPlayerId())
58
+ continue;
59
+ try {
60
+ const sessionHandle = client.workflow.getHandle(session.workflowId);
61
+ const isLocked = await sessionHandle.query(signals_1.outboxLockedQuery);
62
+ if (isLocked) {
63
+ heldSessions.push(session);
64
+ }
65
+ }
66
+ catch {
67
+ // Skip sessions where query fails (old workflows, terminated, etc.)
68
+ }
79
69
  }
80
- catch (err) {
81
- errors.push(`${session.playerId}: ${(0, helpers_1.formatError)(err)}`);
70
+ if (heldSessions.length === 0) {
71
+ return (0, descriptor_1.ok)('No held sessions found. Nothing to release.');
82
72
  }
73
+ const released = [];
74
+ const errors = [];
75
+ for (const session of heldSessions) {
76
+ try {
77
+ const entry = {
78
+ type: 'release',
79
+ targetPlayerId: session.playerId,
80
+ };
81
+ await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
82
+ released.push(session.playerId);
83
+ }
84
+ catch (err) {
85
+ errors.push(`${session.playerId}: ${(0, descriptor_1.formatError)(err)}`);
86
+ }
87
+ }
88
+ const lines = [];
89
+ if (released.length > 0) {
90
+ lines.push(`Released ${released.length} player(s): ${released.join(', ')}`);
91
+ }
92
+ if (errors.length > 0) {
93
+ lines.push(`Errors:\n${errors.map((e) => ` - ${e}`).join('\n')}`);
94
+ }
95
+ return (0, descriptor_1.ok)(lines.join('\n'));
83
96
  }
84
- const lines = [];
85
- if (released.length > 0) {
86
- lines.push(`Released ${released.length} player(s): ${released.join(', ')}`);
87
- }
88
- if (errors.length > 0) {
89
- lines.push(`Errors:\n${errors.map((e) => ` - ${e}`).join('\n')}`);
90
- }
91
- return (0, helpers_1.ok)(lines.join('\n'));
92
97
  }
93
- }
94
- catch (err) {
95
- return (0, helpers_1.fail)(`Failed to release: ${(0, helpers_1.formatError)(err)}`);
96
- }
97
- });
98
+ catch (err) {
99
+ return (0, descriptor_1.fail)(`Failed to release: ${(0, descriptor_1.formatError)(err)}`);
100
+ }
101
+ },
102
+ };
98
103
  }
@@ -1,3 +1,3 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { WorkflowHandle } from '@temporalio/client';
3
- export declare function registerReportTool(server: McpServer, handle: WorkflowHandle): void;
2
+ import { type TempoToolDescriptor } from './descriptor';
3
+ export declare function buildReportTool(handle: WorkflowHandle): TempoToolDescriptor;
@@ -1,29 +1,34 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerReportTool = registerReportTool;
3
+ exports.buildReportTool = buildReportTool;
4
4
  const zod_1 = require("zod");
5
5
  const signals_1 = require("../workflows/signals");
6
- const helpers_1 = require("./helpers");
6
+ const descriptor_1 = require("./descriptor");
7
7
  const validation_1 = require("../utils/validation");
8
- function registerReportTool(server, handle) {
9
- (0, helpers_1.defineTool)(server, 'report', 'Send an update to the conductor. Use this to report task completion, blockers, or questions. No-op if no conductor is running.', {
10
- text: zod_1.z.string().max(validation_1.MESSAGE_MAX).describe('The report content'),
11
- type: zod_1.z.enum(['result', 'blocker', 'question', 'update']).optional()
12
- .describe('Type of report: "result" (default, task done), "blocker" (stuck), "question" (need input), "update" (progress update, still working)'),
13
- }, async (args) => {
14
- const text = args.text;
15
- const type = (args.type ?? 'result');
16
- try {
17
- const entry = {
18
- type: 'report',
19
- text,
20
- reportType: type,
21
- };
22
- const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
23
- return (0, helpers_1.ok)(`Report sent to conductor: [${type}] ${text} (outbox: ${entryId})`);
24
- }
25
- catch (err) {
26
- return (0, helpers_1.fail)(`Failed to send report: ${(0, helpers_1.formatError)(err)}`);
27
- }
28
- });
8
+ function buildReportTool(handle) {
9
+ return {
10
+ name: 'report',
11
+ description: 'Send an update to the conductor. Use this to report task completion, blockers, or questions. No-op if no conductor is running.',
12
+ params: {
13
+ text: zod_1.z.string().max(validation_1.MESSAGE_MAX).describe('The report content'),
14
+ type: zod_1.z.enum(['result', 'blocker', 'question', 'update']).optional()
15
+ .describe('Type of report: "result" (default, task done), "blocker" (stuck), "question" (need input), "update" (progress update, still working)'),
16
+ },
17
+ handler: async (args) => {
18
+ const text = args.text;
19
+ const type = (args.type ?? 'result');
20
+ try {
21
+ const entry = {
22
+ type: 'report',
23
+ text,
24
+ reportType: type,
25
+ };
26
+ const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
27
+ return (0, descriptor_1.ok)(`Report sent to conductor: [${type}] ${text} (outbox: ${entryId})`);
28
+ }
29
+ catch (err) {
30
+ return (0, descriptor_1.fail)(`Failed to send report: ${(0, descriptor_1.formatError)(err)}`);
31
+ }
32
+ },
33
+ };
29
34
  }
@@ -0,0 +1,3 @@
1
+ import { WorkflowHandle } from '@temporalio/client';
2
+ import { type TempoToolDescriptor } from './descriptor';
3
+ export declare function buildResetTool(handle: WorkflowHandle, getPlayerId: () => string): TempoToolDescriptor;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildResetTool = buildResetTool;
4
+ /**
5
+ * `reset` — clean-wipe a player's conversation context (D14). Enqueues a
6
+ * `ResetOutboxEntry` on the caller's outbox; the dispatch loop runs
7
+ * `deliverReset` on the target, setting a `pendingReset` flag the target's Pi
8
+ * extension polls + acts on (Pi `newSession()` — fresh context, no replay).
9
+ *
10
+ * D14 ruling: reset = CLEAN-WIPE only (always `fresh: true`). For a context
11
+ * wipe that RE-SEEDS from saved state, use `restart` with `loadFromState`
12
+ * instead — that is a different path. Reset is an operator/conductor control op
13
+ * and does NOT route through the MD-G tool gate.
14
+ */
15
+ const zod_1 = require("zod");
16
+ const signals_1 = require("../workflows/signals");
17
+ const descriptor_1 = require("./descriptor");
18
+ const validation_1 = require("../utils/validation");
19
+ function buildResetTool(handle, getPlayerId) {
20
+ return {
21
+ name: 'reset',
22
+ description: "Clean-wipe a player's conversation context — the target starts a FRESH " +
23
+ "session (no transcript replay). Use this to clear a player that's stuck " +
24
+ 'or off-track. For a context wipe that RE-SEEDS from saved state, use ' +
25
+ '`restart` with `loadFromState` instead.',
26
+ params: {
27
+ playerId: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).describe('The player whose context to clean-wipe'),
28
+ reason: zod_1.z.string().max(500).optional().describe('Optional reason — surfaced to the wiped session and recorded for audit'),
29
+ },
30
+ handler: async (args) => {
31
+ const { playerId, reason } = args;
32
+ const nameError = (0, validation_1.validatePlayerName)(playerId);
33
+ if (nameError)
34
+ return (0, descriptor_1.fail)(nameError);
35
+ try {
36
+ const entry = {
37
+ type: 'reset',
38
+ targetPlayerId: playerId,
39
+ invokerPlayerId: getPlayerId(),
40
+ fresh: true, // D14: reset = clean-wipe only.
41
+ ...(reason !== undefined ? { reason } : {}),
42
+ };
43
+ const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
44
+ return (0, descriptor_1.ok)(`Reset (clean-wipe) queued for **${playerId}**. (outbox: ${entryId})`);
45
+ }
46
+ catch (err) {
47
+ return (0, descriptor_1.fail)(`Failed to reset: ${(0, descriptor_1.formatError)(err)}`);
48
+ }
49
+ },
50
+ };
51
+ }
@@ -1,6 +1,6 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { Client, WorkflowHandle } from '@temporalio/client';
3
2
  import { Config } from '../config';
3
+ import { type TempoToolDescriptor } from './descriptor';
4
4
  export interface RestartToolArgs {
5
5
  playerId: string;
6
6
  host?: string;
@@ -32,4 +32,4 @@ export declare function enforceYesStealGuard(client: Client, ensemble: string, p
32
32
  force?: boolean;
33
33
  confirmStealFromHost?: string;
34
34
  }, localHostname?: string): Promise<string | null>;
35
- export declare function registerRestartTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string, handle: WorkflowHandle): void;
35
+ export declare function buildRestartTool(client: Client, config: Config, getPlayerId: () => string, handle: WorkflowHandle): TempoToolDescriptor;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enforceYesStealGuard = enforceYesStealGuard;
4
- exports.registerRestartTool = registerRestartTool;
4
+ exports.buildRestartTool = buildRestartTool;
5
5
  /**
6
6
  * `restart` — reap the current attachment, claim a fresh one, spawn a new adapter.
7
7
  *
@@ -35,7 +35,7 @@ const zod_1 = require("zod");
35
35
  const os_1 = require("os");
36
36
  const signals_1 = require("../workflows/signals");
37
37
  const resolve_1 = require("./resolve");
38
- const helpers_1 = require("./helpers");
38
+ const descriptor_1 = require("./descriptor");
39
39
  const validation_1 = require("../utils/validation");
40
40
  const DEFAULT_CONTEXT_MESSAGES = 10;
41
41
  /**
@@ -84,48 +84,53 @@ async function enforceYesStealGuard(client, ensemble, playerId, args, localHostn
84
84
  }
85
85
  return null;
86
86
  }
87
- function registerRestartTool(server, client, config, getPlayerId, handle) {
88
- (0, helpers_1.defineTool)(server, 'restart', 'Restart a session — reap the current attachment (gracefully, or with force=true), claim a fresh attachment, spawn a new adapter, and optionally replay recent context. Replaces `encore`, `recruit --force`, and `stop`-then-`recruit`. Pass `host` to restart on a different machine; when `force=true` AND the target is currently on a different host, pass `confirmStealFromHost` matching that hostname (design §16.5). Pass `loadFromState` to seed the restarted session from a saved-state slot (#334) instead of (or alongside) transcript replay.', {
89
- playerId: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).describe('The player name to restart'),
90
- host: zod_1.z.string().optional().describe('Target host for the new attachment (defaults to the session\'s preferredHost or last-known hostname). When set, the spawn is routed to the per-host task queue `agent-tempo-{host}`.'),
91
- fresh: zod_1.z.boolean().optional().describe('Skip context replay — spawn a clean slate (default false)'),
92
- force: zod_1.z.boolean().optional().describe('Steal a live attachment via forceDetach (default false; graceful detach is tried first regardless)'),
93
- contextMessages: zod_1.z.number().min(0).max(validation_1.RESTART_CONTEXT_MESSAGES_MAX).optional().describe(`Number of recent messages to include in context (default ${DEFAULT_CONTEXT_MESSAGES}, max ${validation_1.RESTART_CONTEXT_MESSAGES_MAX})`),
94
- confirmStealFromHost: zod_1.z.string().optional().describe('Required when `force=true` and the target\'s current attachment is on a different host. Must match the hostname currently holding the attachment (design §16.5 Option B).'),
95
- // #334 PR-2 saved-state seed integration.
96
- loadFromState: zod_1.z.union([
97
- zod_1.z.boolean(),
98
- zod_1.z.string().regex(validation_1.PLAYER_STATE_KEY_REGEX).max(validation_1.PLAYER_STATE_KEY_MAX),
99
- ]).optional().describe(`Seed the restarted session with a saved-state slot. \`true\` resolves to the default key "${validation_1.PLAYER_STATE_DEFAULT_KEY}"; a string names a specific slot. By default, transcript replay is suppressed when this is set — pass \`transcript: 'replay'\` to stack both. Falls back to transcript replay if the slot is empty.`),
100
- transcript: zod_1.z.enum(['suppress', 'replay']).optional().describe('When `loadFromState` is set, controls transcript-replay interaction: `suppress` (default) seeds only the saved state; `replay` seeds the saved state and then replays the recent transcript on top of it. Ignored when `loadFromState` is absent.'),
101
- }, async (args) => {
102
- const input = args;
103
- const nameError = (0, validation_1.validatePlayerName)(input.playerId);
104
- if (nameError)
105
- return (0, helpers_1.fail)(nameError);
106
- // Client-side --yes-steal guard (§16.5). Cross-host force-restart
107
- // requires an explicit confirmStealFromHost matching the current holder.
108
- const guardError = await enforceYesStealGuard(client, config.ensemble, input.playerId, input);
109
- if (guardError)
110
- return (0, helpers_1.fail)(guardError);
111
- try {
112
- const entry = {
113
- type: 'restart',
114
- targetPlayerId: input.playerId,
115
- invokerPlayerId: getPlayerId(),
116
- ...(input.host !== undefined ? { host: input.host } : {}),
117
- ...(input.fresh !== undefined ? { fresh: input.fresh } : {}),
118
- ...(input.force !== undefined ? { force: input.force } : {}),
119
- ...(input.contextMessages !== undefined ? { contextMessages: input.contextMessages } : {}),
120
- ...(input.loadFromState !== undefined ? { loadFromState: input.loadFromState } : {}),
121
- ...(input.transcript !== undefined ? { transcript: input.transcript } : {}),
122
- };
123
- const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
124
- return (0, helpers_1.ok)(`Restart queued for **${input.playerId}**${input.host ? ` on ${input.host}` : ''}` +
125
- `${input.fresh ? ' (fresh)' : ''}${input.force ? ' (force)' : ''}. (outbox: ${entryId})`);
126
- }
127
- catch (err) {
128
- return (0, helpers_1.fail)(`Failed to restart: ${(0, helpers_1.formatError)(err)}`);
129
- }
130
- });
87
+ function buildRestartTool(client, config, getPlayerId, handle) {
88
+ return {
89
+ name: 'restart',
90
+ description: 'Restart a session — reap the current attachment (gracefully, or with force=true), claim a fresh attachment, spawn a new adapter, and optionally replay recent context. Replaces `encore`, `recruit --force`, and `stop`-then-`recruit`. Pass `host` to restart on a different machine; when `force=true` AND the target is currently on a different host, pass `confirmStealFromHost` matching that hostname (design §16.5). Pass `loadFromState` to seed the restarted session from a saved-state slot (#334) instead of (or alongside) transcript replay.',
91
+ params: {
92
+ playerId: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).describe('The player name to restart'),
93
+ host: zod_1.z.string().optional().describe('Target host for the new attachment (defaults to the session\'s preferredHost or last-known hostname). When set, the spawn is routed to the per-host task queue `agent-tempo-{host}`.'),
94
+ fresh: zod_1.z.boolean().optional().describe('Skip context replay spawn a clean slate (default false)'),
95
+ force: zod_1.z.boolean().optional().describe('Steal a live attachment via forceDetach (default false; graceful detach is tried first regardless)'),
96
+ contextMessages: zod_1.z.number().min(0).max(validation_1.RESTART_CONTEXT_MESSAGES_MAX).optional().describe(`Number of recent messages to include in context (default ${DEFAULT_CONTEXT_MESSAGES}, max ${validation_1.RESTART_CONTEXT_MESSAGES_MAX})`),
97
+ confirmStealFromHost: zod_1.z.string().optional().describe('Required when `force=true` and the target\'s current attachment is on a different host. Must match the hostname currently holding the attachment (design §16.5 Option B).'),
98
+ // #334 PR-2 — saved-state seed integration.
99
+ loadFromState: zod_1.z.union([
100
+ zod_1.z.boolean(),
101
+ zod_1.z.string().regex(validation_1.PLAYER_STATE_KEY_REGEX).max(validation_1.PLAYER_STATE_KEY_MAX),
102
+ ]).optional().describe(`Seed the restarted session with a saved-state slot. \`true\` resolves to the default key "${validation_1.PLAYER_STATE_DEFAULT_KEY}"; a string names a specific slot. By default, transcript replay is suppressed when this is set — pass \`transcript: 'replay'\` to stack both. Falls back to transcript replay if the slot is empty.`),
103
+ transcript: zod_1.z.enum(['suppress', 'replay']).optional().describe('When `loadFromState` is set, controls transcript-replay interaction: `suppress` (default) seeds only the saved state; `replay` seeds the saved state and then replays the recent transcript on top of it. Ignored when `loadFromState` is absent.'),
104
+ },
105
+ handler: async (args) => {
106
+ const input = args;
107
+ const nameError = (0, validation_1.validatePlayerName)(input.playerId);
108
+ if (nameError)
109
+ return (0, descriptor_1.fail)(nameError);
110
+ // Client-side --yes-steal guard (§16.5). Cross-host force-restart
111
+ // requires an explicit confirmStealFromHost matching the current holder.
112
+ const guardError = await enforceYesStealGuard(client, config.ensemble, input.playerId, input);
113
+ if (guardError)
114
+ return (0, descriptor_1.fail)(guardError);
115
+ try {
116
+ const entry = {
117
+ type: 'restart',
118
+ targetPlayerId: input.playerId,
119
+ invokerPlayerId: getPlayerId(),
120
+ ...(input.host !== undefined ? { host: input.host } : {}),
121
+ ...(input.fresh !== undefined ? { fresh: input.fresh } : {}),
122
+ ...(input.force !== undefined ? { force: input.force } : {}),
123
+ ...(input.contextMessages !== undefined ? { contextMessages: input.contextMessages } : {}),
124
+ ...(input.loadFromState !== undefined ? { loadFromState: input.loadFromState } : {}),
125
+ ...(input.transcript !== undefined ? { transcript: input.transcript } : {}),
126
+ };
127
+ const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
128
+ return (0, descriptor_1.ok)(`Restart queued for **${input.playerId}**${input.host ? ` on ${input.host}` : ''}` +
129
+ `${input.fresh ? ' (fresh)' : ''}${input.force ? ' (force)' : ''}. (outbox: ${entryId})`);
130
+ }
131
+ catch (err) {
132
+ return (0, descriptor_1.fail)(`Failed to restart: ${(0, descriptor_1.formatError)(err)}`);
133
+ }
134
+ },
135
+ };
131
136
  }
@@ -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 registerRestoreTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string): void;
3
+ import { type TempoToolDescriptor } from './descriptor';
4
+ export declare function buildRestoreTool(client: Client, config: Config, getPlayerId: () => string): TempoToolDescriptor;