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
package/dist/spawn.js CHANGED
@@ -12,6 +12,7 @@ exports.spawnCopilotBridge = spawnCopilotBridge;
12
12
  exports.spawnMockAdapter = spawnMockAdapter;
13
13
  exports.spawnClaudeApiAdapter = spawnClaudeApiAdapter;
14
14
  exports.spawnOpenCodeAdapter = spawnOpenCodeAdapter;
15
+ exports.spawnPiHeadless = spawnPiHeadless;
15
16
  exports.spawnClaudeCodeHeadlessAdapter = spawnClaudeCodeHeadlessAdapter;
16
17
  const child_process_1 = require("child_process");
17
18
  const fs_1 = require("fs");
@@ -262,12 +263,12 @@ function spawnInTerminal(claudeArgs, workDir, envVars, options) {
262
263
  // Append `; exit` so the wrapping shell exits when claude does (clean or killed).
263
264
  // Without it, claude exit returns control to the shell prompt and the tab lingers —
264
265
  // parity with the Windows WT `closeOnExit: 'always'` + parent-walk fix from #166.
265
- const osaScript = `
266
- tell application "Ghostty"
267
- set cfg to new surface configuration
268
- set initial working directory of cfg to ${JSON.stringify(workDir)}
269
- set initial input of cfg to ${JSON.stringify(claudeInvocation + '; exit\n')}
270
- set win to new window with configuration cfg
266
+ const osaScript = `
267
+ tell application "Ghostty"
268
+ set cfg to new surface configuration
269
+ set initial working directory of cfg to ${JSON.stringify(workDir)}
270
+ set initial input of cfg to ${JSON.stringify(claudeInvocation + '; exit\n')}
271
+ set win to new window with configuration cfg
271
272
  end tell`;
272
273
  log('Using Ghostty initial-input path');
273
274
  const child = (0, child_process_1.spawn)('osascript', ['-e', osaScript], {
@@ -285,12 +286,12 @@ function spawnInTerminal(claudeArgs, workDir, envVars, options) {
285
286
  // so any `"` or `\` in paths/args doesn't break the AppleScript parser. Parity with
286
287
  // the Ghostty path above.
287
288
  const shellCmd = `cd ${shellQuote(workDir)} && ${claudeInvocation} ; exit`;
288
- const osaScript = `
289
- tell application "iTerm2"
290
- set newWindow to (create window with default profile)
291
- tell current session of newWindow
292
- write text ${JSON.stringify(shellCmd)}
293
- end tell
289
+ const osaScript = `
290
+ tell application "iTerm2"
291
+ set newWindow to (create window with default profile)
292
+ tell current session of newWindow
293
+ write text ${JSON.stringify(shellCmd)}
294
+ end tell
294
295
  end tell`;
295
296
  log('Using iTerm2 write-text path');
296
297
  const child = (0, child_process_1.spawn)('osascript', ['-e', osaScript], {
@@ -671,6 +672,77 @@ function spawnOpenCodeAdapter(opts) {
671
672
  log(`Spawned opencode adapter (pid ${child.pid}) in ${opts.workDir} as "${opts.name}"${opts.model ? ` (model=${opts.model})` : ''}${opts.attachmentId ? ` (attachmentId=${opts.attachmentId})` : ''}`);
672
673
  return { pid: child.pid, logPath, pidPath };
673
674
  }
675
+ /**
676
+ * Resolve the path to the Pi headless adapter entry point. Mirrors
677
+ * {@link resolveOpenCodePath} — dev (ts-node) + prod (compiled .js) both launch
678
+ * the same code through the same `require.main === module` gate.
679
+ */
680
+ function resolvePiPath() {
681
+ const isDev = __filename.endsWith('.ts');
682
+ if (isDev) {
683
+ return { cmd: 'npx', args: ['ts-node', (0, path_1.resolve)(__dirname, 'adapters', 'pi', 'adapter.ts')] };
684
+ }
685
+ return { cmd: 'node', args: [(0, path_1.resolve)(__dirname, 'adapters', 'pi', 'adapter.js')] };
686
+ }
687
+ /**
688
+ * Spawn the headless Pi runtime as a detached subprocess. Pattern matches
689
+ * {@link spawnOpenCodeAdapter} — no TTY, log + PID files, env carries identity +
690
+ * Temporal settings + attachment handoff + the Pi model / continue-session /
691
+ * tool-access knobs. The entry constructs `createAgentSession` with the `src/pi`
692
+ * extension injected inline; the singleton claims + heartbeats + registers tools.
693
+ */
694
+ function spawnPiHeadless(opts) {
695
+ const { cmd, args } = resolvePiPath();
696
+ const logDirPath = opts.logDir || (0, path_1.join)(opts.workDir, 'logs');
697
+ const logName = opts.name || `pi-${Date.now()}`;
698
+ const logPath = (0, path_1.join)(logDirPath, `${logName}.log`);
699
+ const pidPath = (0, path_1.join)(logDirPath, `${logName}.pid`);
700
+ (0, fs_1.mkdirSync)(logDirPath, { recursive: true });
701
+ const logFd = (0, fs_1.openSync)(logPath, 'a');
702
+ const toolAccess = opts.toolAccess || 'restricted';
703
+ let child;
704
+ try {
705
+ child = (0, child_process_1.spawn)(cmd, args, {
706
+ cwd: opts.workDir,
707
+ detached: true,
708
+ stdio: ['ignore', logFd, logFd],
709
+ env: {
710
+ ...process.env,
711
+ [config_1.ENV.ENSEMBLE]: opts.ensemble,
712
+ [config_1.ENV.PLAYER_NAME]: opts.name,
713
+ [config_1.ENV.CONDUCTOR]: opts.isConductor ? 'true' : '',
714
+ [config_1.ENV.TEMPORAL_ADDRESS]: opts.temporalAddress,
715
+ ...(opts.temporalNamespace ? { [config_1.ENV.TEMPORAL_NAMESPACE]: opts.temporalNamespace } : {}),
716
+ ...(opts.temporalApiKey ? { [config_1.ENV.TEMPORAL_API_KEY]: opts.temporalApiKey } : {}),
717
+ ...(opts.temporalTlsCertPath ? { [config_1.ENV.TEMPORAL_TLS_CERT_PATH]: opts.temporalTlsCertPath } : {}),
718
+ ...(opts.temporalTlsKeyPath ? { [config_1.ENV.TEMPORAL_TLS_KEY_PATH]: opts.temporalTlsKeyPath } : {}),
719
+ // Model selection: recruit-arg → AGENT_TEMPO_PI_MODEL → Pi default.
720
+ ...(opts.model ? { [config_1.ENV.PI_MODEL]: opts.model } : {}),
721
+ // Restart-resume: continue the prior Pi conversation.
722
+ ...(opts.continueSessionId ? { [config_1.ENV.PI_CONTINUE_SESSION]: opts.continueSessionId } : {}),
723
+ // MD-C: tool-class policy. ALWAYS set (default 'restricted' — the safe
724
+ // unsupervised default + an explicit value for the gate + audit trail).
725
+ [config_1.ENV.TOOL_ACCESS]: toolAccess,
726
+ // 3c Tier-2: per-player ingest token (minted by the daemon outbox).
727
+ // Absent → the inner-loop publisher's HTTP client no-ops (no fine tail).
728
+ ...(opts.ingestToken ? { [config_1.ENV.INGEST_TOKEN]: opts.ingestToken } : {}),
729
+ // Attachment handoff — extension renews via claimAttachment(expectedAttachmentId).
730
+ ...(opts.attachmentId ? { [config_1.ENV.ATTACHMENT_ID]: opts.attachmentId } : {}),
731
+ ...(opts.attachmentRunId ? { [config_1.ENV.ATTACHMENT_RUN_ID]: opts.attachmentRunId } : {}),
732
+ ...(opts.adapterId ? { [config_1.ENV.ADAPTER_ID]: opts.adapterId } : {}),
733
+ },
734
+ });
735
+ child.unref();
736
+ }
737
+ finally {
738
+ (0, fs_1.closeSync)(logFd);
739
+ }
740
+ if (child.pid != null) {
741
+ (0, fs_1.writeFileSync)(pidPath, String(child.pid));
742
+ }
743
+ log(`Spawned pi headless adapter (pid ${child.pid}) in ${opts.workDir} as "${opts.name}" (toolAccess=${toolAccess})${opts.model ? ` (model=${opts.model})` : ''}${opts.continueSessionId ? ` (continue=${opts.continueSessionId})` : ''}${opts.attachmentId ? ` (attachmentId=${opts.attachmentId})` : ''}`);
744
+ return { pid: child.pid, logPath, pidPath };
745
+ }
674
746
  /**
675
747
  * Resolve the path to the claude-code-headless adapter entry point.
676
748
  * Mirrors {@link resolveClaudeApiPath} so dev (ts-node) and prod
@@ -1,2 +1,2 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare function registerAgentTypesTool(server: McpServer): void;
1
+ import { type TempoToolDescriptor } from './descriptor';
2
+ export declare function buildAgentTypesTool(): TempoToolDescriptor;
@@ -1,21 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerAgentTypesTool = registerAgentTypesTool;
4
- const helpers_1 = require("./helpers");
3
+ exports.buildAgentTypesTool = buildAgentTypesTool;
4
+ const descriptor_1 = require("./descriptor");
5
5
  const agent_types_1 = require("../ensemble/agent-types");
6
- function registerAgentTypesTool(server) {
7
- (0, helpers_1.defineTool)(server, 'agent_types', 'List available player types (agent definitions) that can be used when recruiting', {}, async () => {
8
- const types = (0, agent_types_1.listAgentTypes)();
9
- if (types.length === 0) {
10
- return (0, helpers_1.ok)('No agent types found.');
11
- }
12
- const lines = types.map(t => {
13
- const src = t.source === 'shipped' ? '(shipped)' : t.source === 'user' ? '(user)' : '(project)';
14
- const tools = t.allowedTools && t.allowedTools.length > 0
15
- ? `\n Allowed tools: ${t.allowedTools.join(', ')}`
16
- : '';
17
- return `**${t.name}** ${src}\n ${t.description || 'No description'}${tools}`;
18
- });
19
- return (0, helpers_1.ok)(lines.join('\n\n'));
20
- });
6
+ function buildAgentTypesTool() {
7
+ return {
8
+ name: 'agent_types',
9
+ description: 'List available player types (agent definitions) that can be used when recruiting',
10
+ params: {},
11
+ handler: async () => {
12
+ const types = (0, agent_types_1.listAgentTypes)();
13
+ if (types.length === 0) {
14
+ return (0, descriptor_1.ok)('No agent types found.');
15
+ }
16
+ const lines = types.map(t => {
17
+ const src = t.source === 'shipped' ? '(shipped)' : t.source === 'user' ? '(user)' : '(project)';
18
+ const tools = t.allowedTools && t.allowedTools.length > 0
19
+ ? `\n Allowed tools: ${t.allowedTools.join(', ')}`
20
+ : '';
21
+ return `**${t.name}** ${src}\n ${t.description || 'No description'}${tools}`;
22
+ });
23
+ return (0, descriptor_1.ok)(lines.join('\n\n'));
24
+ },
25
+ };
21
26
  }
@@ -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 registerAttachmentInfoTool(server: McpServer, client: Client, config: Config): void;
3
+ import { type TempoToolDescriptor } from './descriptor';
4
+ export declare function buildAttachmentInfoTool(client: Client, config: Config): TempoToolDescriptor;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerAttachmentInfoTool = registerAttachmentInfoTool;
3
+ exports.buildAttachmentInfoTool = buildAttachmentInfoTool;
4
4
  /**
5
5
  * `attachment_info` — diagnostic query for the V2 attachment lifecycle (design §§2.2, 2.4).
6
6
  *
@@ -11,38 +11,43 @@ exports.registerAttachmentInfoTool = registerAttachmentInfoTool;
11
11
  const zod_1 = require("zod");
12
12
  const resolve_1 = require("./resolve");
13
13
  const signals_1 = require("../workflows/signals");
14
- const helpers_1 = require("./helpers");
14
+ const descriptor_1 = require("./descriptor");
15
15
  const validation_1 = require("../utils/validation");
16
- function registerAttachmentInfoTool(server, client, config) {
17
- (0, helpers_1.defineTool)(server, 'attachment_info', 'Query the V2 attachment lifecycle state of a session — phase (booting/attached/processing/awaiting/draining/detached/gone), current attachment holder (host + adapter + lease expiry), preferred host, and in-flight message count. Read-only diagnostic.', {
18
- playerId: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).describe('The player name to inspect'),
19
- }, async (args) => {
20
- const { playerId } = args;
21
- const nameError = (0, validation_1.validatePlayerName)(playerId);
22
- if (nameError)
23
- return (0, helpers_1.fail)(nameError);
24
- try {
25
- const resolved = await (0, resolve_1.resolveSession)(client, config.ensemble, playerId);
26
- if (!resolved)
27
- return (0, helpers_1.fail)(`No session found with name "${playerId}".`);
28
- const info = await resolved.query(signals_1.attachmentInfoQuery);
29
- const lines = [
30
- `**${playerId}** — phase: \`${info.phase}\``,
31
- `in-flight messages: ${info.inFlightCount}`,
32
- ];
33
- if (info.currentAttachment) {
34
- const a = info.currentAttachment;
35
- lines.push(`attached on: **${a.hostname}** (adapter: ${a.adapterId}/${a.adapterClass}, attachmentId: \`${a.attachmentId.slice(0, 8)}…\`)`);
36
- lines.push(`lease expires: ${a.expiresAt}`);
16
+ function buildAttachmentInfoTool(client, config) {
17
+ return {
18
+ name: 'attachment_info',
19
+ description: 'Query the V2 attachment lifecycle state of a session — phase (booting/attached/processing/awaiting/draining/detached/gone), current attachment holder (host + adapter + lease expiry), preferred host, and in-flight message count. Read-only diagnostic.',
20
+ params: {
21
+ playerId: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).describe('The player name to inspect'),
22
+ },
23
+ handler: async (args) => {
24
+ const { playerId } = args;
25
+ const nameError = (0, validation_1.validatePlayerName)(playerId);
26
+ if (nameError)
27
+ return (0, descriptor_1.fail)(nameError);
28
+ try {
29
+ const resolved = await (0, resolve_1.resolveSession)(client, config.ensemble, playerId);
30
+ if (!resolved)
31
+ return (0, descriptor_1.fail)(`No session found with name "${playerId}".`);
32
+ const info = await resolved.query(signals_1.attachmentInfoQuery);
33
+ const lines = [
34
+ `**${playerId}** phase: \`${info.phase}\``,
35
+ `in-flight messages: ${info.inFlightCount}`,
36
+ ];
37
+ if (info.currentAttachment) {
38
+ const a = info.currentAttachment;
39
+ lines.push(`attached on: **${a.hostname}** (adapter: ${a.adapterId}/${a.adapterClass}, attachmentId: \`${a.attachmentId.slice(0, 8)}…\`)`);
40
+ lines.push(`lease expires: ${a.expiresAt}`);
41
+ }
42
+ if (info.preferredHost)
43
+ lines.push(`preferred host: ${info.preferredHost}`);
44
+ if (info.processingSince)
45
+ lines.push(`processing since: ${info.processingSince}`);
46
+ return (0, descriptor_1.ok)(lines.join('\n'));
37
47
  }
38
- if (info.preferredHost)
39
- lines.push(`preferred host: ${info.preferredHost}`);
40
- if (info.processingSince)
41
- lines.push(`processing since: ${info.processingSince}`);
42
- return (0, helpers_1.ok)(lines.join('\n'));
43
- }
44
- catch (err) {
45
- return (0, helpers_1.fail)(`Failed to query attachment info: ${(0, helpers_1.formatError)(err)}`);
46
- }
47
- });
48
+ catch (err) {
49
+ return (0, descriptor_1.fail)(`Failed to query attachment info: ${(0, descriptor_1.formatError)(err)}`);
50
+ }
51
+ },
52
+ };
48
53
  }
@@ -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 registerBroadcastTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string, handle: WorkflowHandle): void;
3
+ import { type TempoToolDescriptor } from './descriptor';
4
+ export declare function buildBroadcastTool(client: Client, config: Config, getPlayerId: () => string, handle: WorkflowHandle): TempoToolDescriptor;
@@ -1,76 +1,81 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerBroadcastTool = registerBroadcastTool;
3
+ exports.buildBroadcastTool = buildBroadcastTool;
4
4
  const zod_1 = require("zod");
5
5
  const crypto_1 = require("crypto");
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
9
  const search_attributes_1 = require("../utils/search-attributes");
10
- function registerBroadcastTool(server, client, config, getPlayerId, handle) {
11
- (0, helpers_1.defineTool)(server, 'broadcast', 'Send a message to all active players in the ensemble. Optionally filter by player type.', {
12
- message: zod_1.z.string().max(validation_1.MESSAGE_MAX).describe('The message to broadcast'),
13
- type: zod_1.z.string().optional().describe('Only send to players of this type (e.g., "tempo-soloist")'),
14
- includeStale: zod_1.z.boolean().optional().describe('Include disconnected sessions (draining/detached phases; default: false). Argument name kept for backward compatibility.'),
15
- }, async (args) => {
16
- const { message, type: playerType, includeStale: rawIncludeStale } = args;
17
- const includeDisconnected = rawIncludeStale === true;
18
- try {
19
- const query = `WorkflowType = "agentSessionWorkflow" AND ExecutionStatus = "Running"`;
20
- const targets = [];
21
- for await (const workflow of client.workflow.list({ query })) {
22
- try {
23
- const wfHandle = client.workflow.getHandle(workflow.workflowId);
24
- const metadata = await wfHandle.query('getMetadata');
25
- // Filter by ensemble
26
- if (metadata.ensemble !== config.ensemble)
27
- continue;
28
- // Exclude sender
29
- if (metadata.playerId === getPlayerId())
30
- continue;
31
- // Filter by attachment phase (post-#176). Phase lives on the
32
- // `AgentTempoAttachmentState` search attribute.
33
- const phase = (0, search_attributes_1.getAttachmentPhase)(workflow);
34
- if (!(0, validation_1.shouldIncludeInBroadcast)(phase, includeDisconnected))
35
- continue;
36
- // Filter by player type if specified
37
- if (playerType && metadata.playerType !== playerType)
38
- continue;
39
- targets.push({
40
- playerId: metadata.playerId,
41
- playerType: metadata.playerType,
42
- });
10
+ function buildBroadcastTool(client, config, getPlayerId, handle) {
11
+ return {
12
+ name: 'broadcast',
13
+ description: 'Send a message to all active players in the ensemble. Optionally filter by player type.',
14
+ params: {
15
+ message: zod_1.z.string().max(validation_1.MESSAGE_MAX).describe('The message to broadcast'),
16
+ type: zod_1.z.string().optional().describe('Only send to players of this type (e.g., "tempo-soloist")'),
17
+ includeStale: zod_1.z.boolean().optional().describe('Include disconnected sessions (draining/detached phases; default: false). Argument name kept for backward compatibility.'),
18
+ },
19
+ handler: async (args) => {
20
+ const { message, type: playerType, includeStale: rawIncludeStale } = args;
21
+ const includeDisconnected = rawIncludeStale === true;
22
+ try {
23
+ const query = `WorkflowType = "agentSessionWorkflow" AND ExecutionStatus = "Running"`;
24
+ const targets = [];
25
+ for await (const workflow of client.workflow.list({ query })) {
26
+ try {
27
+ const wfHandle = client.workflow.getHandle(workflow.workflowId);
28
+ const metadata = await wfHandle.query('getMetadata');
29
+ // Filter by ensemble
30
+ if (metadata.ensemble !== config.ensemble)
31
+ continue;
32
+ // Exclude sender
33
+ if (metadata.playerId === getPlayerId())
34
+ continue;
35
+ // Filter by attachment phase (post-#176). Phase lives on the
36
+ // `AgentTempoAttachmentState` search attribute.
37
+ const phase = (0, search_attributes_1.getAttachmentPhase)(workflow);
38
+ if (!(0, validation_1.shouldIncludeInBroadcast)(phase, includeDisconnected))
39
+ continue;
40
+ // Filter by player type if specified
41
+ if (playerType && metadata.playerType !== playerType)
42
+ continue;
43
+ targets.push({
44
+ playerId: metadata.playerId,
45
+ playerType: metadata.playerType,
46
+ });
47
+ }
48
+ catch {
49
+ // Workflow may have just completed — skip it
50
+ }
43
51
  }
44
- catch {
45
- // Workflow may have just completed skip it
52
+ if (targets.length === 0) {
53
+ return (0, descriptor_1.ok)('No active players matched the broadcast filter.');
46
54
  }
55
+ // #357: stamp every fan-out cue with the same `broadcastId` so the
56
+ // TUI can fold the N deliveries into one chat row. Generated ONCE
57
+ // here in the MCP-tool process — not inside the workflow — so
58
+ // workflow determinism is preserved (the workflow only sees the id
59
+ // as an opaque string on `OutboxEntryInput`).
60
+ const broadcastId = (0, crypto_1.randomUUID)();
61
+ // Fan out cue outbox entries for each target.
62
+ const entryIds = [];
63
+ for (const target of targets) {
64
+ const entry = {
65
+ type: 'cue',
66
+ targetPlayerId: target.playerId,
67
+ message,
68
+ broadcastId,
69
+ };
70
+ const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
71
+ entryIds.push(entryId);
72
+ }
73
+ const names = targets.map((t) => t.playerId);
74
+ return (0, descriptor_1.ok)(`Broadcast sent to ${targets.length} player${targets.length === 1 ? '' : 's'}: ${names.join(', ')}`);
47
75
  }
48
- if (targets.length === 0) {
49
- return (0, helpers_1.ok)('No active players matched the broadcast filter.');
50
- }
51
- // #357: stamp every fan-out cue with the same `broadcastId` so the
52
- // TUI can fold the N deliveries into one chat row. Generated ONCE
53
- // here in the MCP-tool process — not inside the workflow — so
54
- // workflow determinism is preserved (the workflow only sees the id
55
- // as an opaque string on `OutboxEntryInput`).
56
- const broadcastId = (0, crypto_1.randomUUID)();
57
- // Fan out cue outbox entries for each target.
58
- const entryIds = [];
59
- for (const target of targets) {
60
- const entry = {
61
- type: 'cue',
62
- targetPlayerId: target.playerId,
63
- message,
64
- broadcastId,
65
- };
66
- const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
67
- entryIds.push(entryId);
76
+ catch (err) {
77
+ return (0, descriptor_1.fail)(`Failed to broadcast: ${(0, descriptor_1.formatError)(err)}`);
68
78
  }
69
- const names = targets.map((t) => t.playerId);
70
- return (0, helpers_1.ok)(`Broadcast sent to ${targets.length} player${targets.length === 1 ? '' : 's'}: ${names.join(', ')}`);
71
- }
72
- catch (err) {
73
- return (0, helpers_1.fail)(`Failed to broadcast: ${(0, helpers_1.formatError)(err)}`);
74
- }
75
- });
79
+ },
80
+ };
76
81
  }
@@ -1,3 +1,3 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { WorkflowHandle } from '@temporalio/client';
3
- export declare function registerCancelStageTool(server: McpServer, handle: WorkflowHandle): void;
2
+ import { type TempoToolDescriptor } from './descriptor';
3
+ export declare function buildCancelStageTool(handle: WorkflowHandle): TempoToolDescriptor;
@@ -1,20 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerCancelStageTool = registerCancelStageTool;
3
+ exports.buildCancelStageTool = buildCancelStageTool;
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 registerCancelStageTool(server, handle) {
8
- (0, helpers_1.defineTool)(server, 'cancel_stage', 'Cancel an active pipeline stage. Players are no longer tracked. Conductor only.', {
9
- name: zod_1.z.string().max(validation_1.STAGE_NAME_MAX).describe('Name of the stage to cancel'),
10
- }, async (args) => {
11
- const { name } = args;
12
- try {
13
- await handle.signal('cancelStage', name);
14
- return (0, helpers_1.ok)(`Stage **${name}** cancelled.`);
15
- }
16
- catch (err) {
17
- return (0, helpers_1.fail)(`Failed to cancel stage: ${(0, helpers_1.formatError)(err)}`);
18
- }
19
- });
7
+ function buildCancelStageTool(handle) {
8
+ return {
9
+ name: 'cancel_stage',
10
+ description: 'Cancel an active pipeline stage. Players are no longer tracked. Conductor only.',
11
+ params: {
12
+ name: zod_1.z.string().max(validation_1.STAGE_NAME_MAX).describe('Name of the stage to cancel'),
13
+ },
14
+ handler: async (args) => {
15
+ const { name } = args;
16
+ try {
17
+ await handle.signal('cancelStage', name);
18
+ return (0, descriptor_1.ok)(`Stage **${name}** cancelled.`);
19
+ }
20
+ catch (err) {
21
+ return (0, descriptor_1.fail)(`Failed to cancel stage: ${(0, descriptor_1.formatError)(err)}`);
22
+ }
23
+ },
24
+ };
20
25
  }
@@ -1,3 +1,3 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { WorkflowHandle } from '@temporalio/client';
3
- export declare function registerClearStateTool(server: McpServer, handle: WorkflowHandle): void;
2
+ import { type TempoToolDescriptor } from './descriptor';
3
+ export declare function buildClearStateTool(handle: WorkflowHandle): TempoToolDescriptor;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerClearStateTool = registerClearStateTool;
3
+ exports.buildClearStateTool = buildClearStateTool;
4
4
  /**
5
5
  * `clear_state` — remove one of the calling player's saved-state slots
6
6
  * (#334 PR-1, ADR 0011).
@@ -14,24 +14,29 @@ exports.registerClearStateTool = registerClearStateTool;
14
14
  */
15
15
  const zod_1 = require("zod");
16
16
  const signals_1 = require("../workflows/signals");
17
- const helpers_1 = require("./helpers");
17
+ const descriptor_1 = require("./descriptor");
18
18
  const validation_1 = require("../utils/validation");
19
- function registerClearStateTool(server, handle) {
20
- (0, helpers_1.defineTool)(server, 'clear_state', `Clear one of your saved-state slots. Owner-only — you can only clear your own state. Idempotent (clearing an empty slot is a no-op). Returns whether the slot was non-empty before the clear.`, {
21
- key: zod_1.z.string().regex(validation_1.PLAYER_STATE_KEY_REGEX).max(validation_1.PLAYER_STATE_KEY_MAX).optional().describe(`Slot name (default "${validation_1.PLAYER_STATE_DEFAULT_KEY}").`),
22
- }, async (args) => {
23
- const { key } = args;
24
- const slotKey = key ?? validation_1.PLAYER_STATE_DEFAULT_KEY;
25
- try {
26
- const result = await handle.executeUpdate(signals_1.clearPlayerStateUpdate, {
27
- args: [{ key: slotKey }],
28
- });
29
- return (0, helpers_1.ok)(result.cleared
30
- ? `Cleared slot **"${slotKey}"**.`
31
- : `Slot **"${slotKey}"** was already empty.`);
32
- }
33
- catch (err) {
34
- return (0, helpers_1.fail)(`Failed to clear state: ${(0, helpers_1.formatError)(err)}`);
35
- }
36
- });
19
+ function buildClearStateTool(handle) {
20
+ return {
21
+ name: 'clear_state',
22
+ description: `Clear one of your saved-state slots. Owner-only — you can only clear your own state. Idempotent (clearing an empty slot is a no-op). Returns whether the slot was non-empty before the clear.`,
23
+ params: {
24
+ key: zod_1.z.string().regex(validation_1.PLAYER_STATE_KEY_REGEX).max(validation_1.PLAYER_STATE_KEY_MAX).optional().describe(`Slot name (default "${validation_1.PLAYER_STATE_DEFAULT_KEY}").`),
25
+ },
26
+ handler: async (args) => {
27
+ const { key } = args;
28
+ const slotKey = key ?? validation_1.PLAYER_STATE_DEFAULT_KEY;
29
+ try {
30
+ const result = await handle.executeUpdate(signals_1.clearPlayerStateUpdate, {
31
+ args: [{ key: slotKey }],
32
+ });
33
+ return (0, descriptor_1.ok)(result.cleared
34
+ ? `Cleared slot **"${slotKey}"**.`
35
+ : `Slot **"${slotKey}"** was already empty.`);
36
+ }
37
+ catch (err) {
38
+ return (0, descriptor_1.fail)(`Failed to clear state: ${(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 { Client } from '@temporalio/client';
3
2
  import { Config } from '../config';
4
- export declare function registerCoatCheckEvictTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string): void;
3
+ import { type TempoToolDescriptor } from './descriptor';
4
+ export declare function buildCoatCheckEvictTool(client: Client, config: Config, getPlayerId: () => string): TempoToolDescriptor;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerCoatCheckEvictTool = registerCoatCheckEvictTool;
3
+ exports.buildCoatCheckEvictTool = buildCoatCheckEvictTool;
4
4
  /**
5
5
  * `coat_check_evict` — remove a coat-check entry (#318, ADR 0008) before
6
6
  * its TTL expires. Owner-or-conductor only: the workflow validator rejects
@@ -14,30 +14,35 @@ exports.registerCoatCheckEvictTool = registerCoatCheckEvictTool;
14
14
  const zod_1 = require("zod");
15
15
  const config_1 = require("../config");
16
16
  const maestro_signals_1 = require("../workflows/maestro-signals");
17
- const helpers_1 = require("./helpers");
17
+ const descriptor_1 = require("./descriptor");
18
18
  const validation_1 = require("../utils/validation");
19
- function registerCoatCheckEvictTool(server, client, config, getPlayerId) {
20
- (0, helpers_1.defineTool)(server, 'coat_check_evict', `Evict a coat-check entry (#318) before its TTL expires. Owner-or-conductor only — non-owners (and non-conductors) get a permission error.
21
-
22
- Use to free a slot when this ensemble is at the 20-entry cap and you want to make room. \`evicted: false\` means the ticket was already gone (TTL-expired or evicted by someone else).`, {
23
- ticket: zod_1.z.string().regex(validation_1.COAT_CHECK_TICKET_REGEX).max(validation_1.COAT_CHECK_TICKET_MAX).describe(`The ticket id returned by an earlier \`coat_check_put\` (≤${validation_1.COAT_CHECK_TICKET_MAX} chars).`),
24
- }, async (args) => {
25
- const { ticket } = args;
26
- const evictedBy = getPlayerId();
27
- try {
28
- const handle = client.workflow.getHandle((0, config_1.maestroWorkflowId)(config.ensemble));
29
- const result = await handle.executeUpdate(maestro_signals_1.coatCheckEvictUpdate, {
30
- args: [{ ticket, evictedBy }],
31
- });
32
- if (!result.evicted) {
33
- return (0, helpers_1.ok)(`Ticket **${ticket}** was already gone (no-op).`);
19
+ function buildCoatCheckEvictTool(client, config, getPlayerId) {
20
+ return {
21
+ name: 'coat_check_evict',
22
+ description: `Evict a coat-check entry (#318) before its TTL expires. Owner-or-conductor only non-owners (and non-conductors) get a permission error.
23
+
24
+ Use to free a slot when this ensemble is at the 20-entry cap and you want to make room. \`evicted: false\` means the ticket was already gone (TTL-expired or evicted by someone else).`,
25
+ params: {
26
+ ticket: zod_1.z.string().regex(validation_1.COAT_CHECK_TICKET_REGEX).max(validation_1.COAT_CHECK_TICKET_MAX).describe(`The ticket id returned by an earlier \`coat_check_put\` (≤${validation_1.COAT_CHECK_TICKET_MAX} chars).`),
27
+ },
28
+ handler: async (args) => {
29
+ const { ticket } = args;
30
+ const evictedBy = getPlayerId();
31
+ try {
32
+ const handle = client.workflow.getHandle((0, config_1.maestroWorkflowId)(config.ensemble));
33
+ const result = await handle.executeUpdate(maestro_signals_1.coatCheckEvictUpdate, {
34
+ args: [{ ticket, evictedBy }],
35
+ });
36
+ if (!result.evicted) {
37
+ return (0, descriptor_1.ok)(`Ticket **${ticket}** was already gone (no-op).`);
38
+ }
39
+ return (0, descriptor_1.ok)(`Evicted ticket **${ticket}**.`);
34
40
  }
35
- return (0, helpers_1.ok)(`Evicted ticket **${ticket}**.`);
36
- }
37
- catch (err) {
38
- // Surfaces `CoatCheckEvictPermissionDenied` ApplicationFailure with
39
- // owner/conductor diagnostic from the workflow validator.
40
- return (0, helpers_1.fail)(`Failed to evict ticket: ${(0, helpers_1.formatError)(err)}`);
41
- }
42
- });
41
+ catch (err) {
42
+ // Surfaces `CoatCheckEvictPermissionDenied` ApplicationFailure with
43
+ // owner/conductor diagnostic from the workflow validator.
44
+ return (0, descriptor_1.fail)(`Failed to evict ticket: ${(0, descriptor_1.formatError)(err)}`);
45
+ }
46
+ },
47
+ };
43
48
  }