agent-tempo 1.3.1 → 1.4.1

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 (199) hide show
  1. package/CLAUDE.md +39 -5
  2. package/README.md +6 -2
  3. package/dashboard/dist/assets/{index-D6Xyje_n.js → index-jmYe6rmS.js} +2 -2
  4. package/dashboard/dist/assets/index-jmYe6rmS.js.map +1 -0
  5. package/dashboard/dist/index.html +1 -1
  6. package/dashboard/package.json +1 -1
  7. package/dist/activities/outbox.d.ts +30 -1
  8. package/dist/activities/outbox.js +96 -3
  9. package/dist/adapters/base.js +5 -0
  10. package/dist/adapters/index.d.ts +1 -1
  11. package/dist/adapters/index.js +7 -0
  12. package/dist/adapters/pi/adapter.d.ts +2 -0
  13. package/dist/adapters/pi/adapter.js +43 -0
  14. package/dist/adapters/pi/index.d.ts +16 -0
  15. package/dist/adapters/pi/index.js +10 -0
  16. package/dist/client/core.js +9 -2
  17. package/dist/client/interface.d.ts +6 -0
  18. package/dist/config.d.ts +79 -0
  19. package/dist/config.js +74 -0
  20. package/dist/daemon.js +32 -1
  21. package/dist/http/aggregate.d.ts +22 -1
  22. package/dist/http/aggregate.js +41 -0
  23. package/dist/http/auth.d.ts +94 -8
  24. package/dist/http/auth.js +93 -9
  25. package/dist/http/body.d.ts +4 -1
  26. package/dist/http/body.js +6 -3
  27. package/dist/http/event-bus.js +1 -0
  28. package/dist/http/event-types.d.ts +34 -2
  29. package/dist/http/event-types.js +1 -0
  30. package/dist/http/gate-audit.d.ts +12 -0
  31. package/dist/http/gate-audit.js +95 -0
  32. package/dist/http/gate-registry.d.ts +167 -0
  33. package/dist/http/gate-registry.js +163 -0
  34. package/dist/http/gate-routes.d.ts +48 -0
  35. package/dist/http/gate-routes.js +102 -0
  36. package/dist/http/ingest-registry.d.ts +30 -0
  37. package/dist/http/ingest-registry.js +108 -0
  38. package/dist/http/inner-loop-routes.d.ts +66 -0
  39. package/dist/http/inner-loop-routes.js +182 -0
  40. package/dist/http/inner-loop.d.ts +92 -0
  41. package/dist/http/inner-loop.js +155 -0
  42. package/dist/http/server.d.ts +38 -3
  43. package/dist/http/server.js +211 -6
  44. package/dist/http/snapshot.d.ts +6 -0
  45. package/dist/http/snapshot.js +6 -0
  46. package/dist/pi/cue-pump.d.ts +61 -0
  47. package/dist/pi/cue-pump.js +95 -0
  48. package/dist/pi/extension.d.ts +45 -0
  49. package/dist/pi/extension.js +407 -0
  50. package/dist/pi/gate-client.d.ts +54 -0
  51. package/dist/pi/gate-client.js +136 -0
  52. package/dist/pi/headless.d.ts +85 -0
  53. package/dist/pi/headless.js +250 -0
  54. package/dist/pi/index.d.ts +28 -0
  55. package/dist/pi/index.js +43 -0
  56. package/dist/pi/inner-loop-client.d.ts +67 -0
  57. package/dist/pi/inner-loop-client.js +164 -0
  58. package/dist/pi/inner-loop-publisher.d.ts +187 -0
  59. package/dist/pi/inner-loop-publisher.js +236 -0
  60. package/dist/pi/lazy-proxy.d.ts +37 -0
  61. package/dist/pi/lazy-proxy.js +55 -0
  62. package/dist/pi/mission-control/actions.d.ts +48 -0
  63. package/dist/pi/mission-control/actions.js +98 -0
  64. package/dist/pi/mission-control/board.d.ts +88 -0
  65. package/dist/pi/mission-control/board.js +141 -0
  66. package/dist/pi/mission-control/extension.d.ts +51 -0
  67. package/dist/pi/mission-control/extension.js +330 -0
  68. package/dist/pi/mission-control/index.d.ts +15 -0
  69. package/dist/pi/mission-control/index.js +32 -0
  70. package/dist/pi/mission-control/inner-tail.d.ts +48 -0
  71. package/dist/pi/mission-control/inner-tail.js +76 -0
  72. package/dist/pi/mission-control/pi-ui.d.ts +43 -0
  73. package/dist/pi/mission-control/pi-ui.js +10 -0
  74. package/dist/pi/mission-control/render.d.ts +6 -0
  75. package/dist/pi/mission-control/render.js +98 -0
  76. package/dist/pi/phase-driver.d.ts +74 -0
  77. package/dist/pi/phase-driver.js +122 -0
  78. package/dist/pi/pi-types.d.ts +222 -0
  79. package/dist/pi/pi-types.js +21 -0
  80. package/dist/pi/probe.d.ts +99 -0
  81. package/dist/pi/probe.js +179 -0
  82. package/dist/pi/render-tools.d.ts +17 -0
  83. package/dist/pi/render-tools.js +56 -0
  84. package/dist/pi/reset-pump.d.ts +47 -0
  85. package/dist/pi/reset-pump.js +85 -0
  86. package/dist/pi/session-seed.d.ts +74 -0
  87. package/dist/pi/session-seed.js +103 -0
  88. package/dist/pi/tool-capability.d.ts +60 -0
  89. package/dist/pi/tool-capability.js +156 -0
  90. package/dist/pi/workflow-client.d.ts +158 -0
  91. package/dist/pi/workflow-client.js +289 -0
  92. package/dist/pi/zod-to-typebox.d.ts +74 -0
  93. package/dist/pi/zod-to-typebox.js +191 -0
  94. package/dist/server-tools.d.ts +2 -0
  95. package/dist/server-tools.js +50 -46
  96. package/dist/spawn.d.ts +55 -0
  97. package/dist/spawn.js +72 -0
  98. package/dist/tools/agent-types.d.ts +2 -2
  99. package/dist/tools/agent-types.js +22 -17
  100. package/dist/tools/attachment-info.d.ts +2 -2
  101. package/dist/tools/attachment-info.js +38 -33
  102. package/dist/tools/broadcast.d.ts +2 -2
  103. package/dist/tools/broadcast.js +69 -64
  104. package/dist/tools/cancel-stage.d.ts +2 -2
  105. package/dist/tools/cancel-stage.js +20 -15
  106. package/dist/tools/clear-state.d.ts +2 -2
  107. package/dist/tools/clear-state.js +25 -20
  108. package/dist/tools/coat-check-evict.d.ts +2 -2
  109. package/dist/tools/coat-check-evict.js +29 -24
  110. package/dist/tools/coat-check-get.d.ts +2 -2
  111. package/dist/tools/coat-check-get.js +38 -33
  112. package/dist/tools/coat-check-list.d.ts +2 -2
  113. package/dist/tools/coat-check-list.js +48 -43
  114. package/dist/tools/coat-check-put.d.ts +2 -2
  115. package/dist/tools/coat-check-put.js +38 -33
  116. package/dist/tools/cue.d.ts +2 -2
  117. package/dist/tools/cue.js +57 -52
  118. package/dist/tools/descriptor.d.ts +72 -0
  119. package/dist/tools/descriptor.js +39 -0
  120. package/dist/tools/destroy.d.ts +2 -2
  121. package/dist/tools/destroy.js +153 -148
  122. package/dist/tools/ensemble.d.ts +2 -2
  123. package/dist/tools/ensemble.js +71 -66
  124. package/dist/tools/evaluate-gate.d.ts +2 -2
  125. package/dist/tools/evaluate-gate.js +33 -27
  126. package/dist/tools/fetch-state.d.ts +2 -2
  127. package/dist/tools/fetch-state.js +42 -37
  128. package/dist/tools/gates.d.ts +2 -2
  129. package/dist/tools/gates.js +39 -34
  130. package/dist/tools/hosts.d.ts +2 -2
  131. package/dist/tools/hosts.js +25 -20
  132. package/dist/tools/listen.d.ts +2 -2
  133. package/dist/tools/listen.js +23 -18
  134. package/dist/tools/load-lineup.d.ts +2 -2
  135. package/dist/tools/load-lineup.js +324 -319
  136. package/dist/tools/migrate.d.ts +2 -2
  137. package/dist/tools/migrate.js +45 -40
  138. package/dist/tools/pause.d.ts +2 -2
  139. package/dist/tools/pause.js +34 -29
  140. package/dist/tools/play.d.ts +2 -2
  141. package/dist/tools/play.js +53 -48
  142. package/dist/tools/quality-gate.d.ts +2 -2
  143. package/dist/tools/quality-gate.js +26 -21
  144. package/dist/tools/recall.d.ts +2 -2
  145. package/dist/tools/recall.js +32 -27
  146. package/dist/tools/recruit.d.ts +2 -2
  147. package/dist/tools/recruit.js +340 -256
  148. package/dist/tools/release.d.ts +2 -2
  149. package/dist/tools/release.js +85 -80
  150. package/dist/tools/report.d.ts +2 -2
  151. package/dist/tools/report.js +28 -23
  152. package/dist/tools/reset.d.ts +3 -0
  153. package/dist/tools/reset.js +51 -0
  154. package/dist/tools/restart.d.ts +2 -2
  155. package/dist/tools/restart.js +51 -46
  156. package/dist/tools/restore.d.ts +2 -2
  157. package/dist/tools/restore.js +76 -71
  158. package/dist/tools/save-lineup.d.ts +2 -2
  159. package/dist/tools/save-lineup.js +32 -27
  160. package/dist/tools/save-state.d.ts +2 -2
  161. package/dist/tools/save-state.js +31 -26
  162. package/dist/tools/schedule.d.ts +2 -2
  163. package/dist/tools/schedule.js +133 -128
  164. package/dist/tools/schedules.d.ts +2 -2
  165. package/dist/tools/schedules.js +41 -36
  166. package/dist/tools/set-ensemble-description.d.ts +2 -2
  167. package/dist/tools/set-ensemble-description.js +26 -21
  168. package/dist/tools/set-name.d.ts +2 -2
  169. package/dist/tools/set-name.js +38 -33
  170. package/dist/tools/set-part.d.ts +2 -2
  171. package/dist/tools/set-part.js +20 -15
  172. package/dist/tools/shutdown.d.ts +2 -2
  173. package/dist/tools/shutdown.js +39 -34
  174. package/dist/tools/stage.d.ts +2 -2
  175. package/dist/tools/stage.js +28 -23
  176. package/dist/tools/stages.d.ts +2 -2
  177. package/dist/tools/stages.js +36 -31
  178. package/dist/tools/unschedule.d.ts +2 -2
  179. package/dist/tools/unschedule.js +30 -25
  180. package/dist/tools/who-am-i.d.ts +2 -2
  181. package/dist/tools/who-am-i.js +36 -31
  182. package/dist/tools/worktree.d.ts +2 -2
  183. package/dist/tools/worktree.js +134 -129
  184. package/dist/tui/index.js +6 -6
  185. package/dist/types.d.ts +47 -2
  186. package/dist/types.js +1 -1
  187. package/dist/utils/default-part.js +1 -0
  188. package/dist/utils/sdk-probe.d.ts +23 -0
  189. package/dist/utils/sdk-probe.js +46 -7
  190. package/dist/worker.d.ts +3 -1
  191. package/dist/worker.js +6 -2
  192. package/dist/workflows/session.js +70 -2
  193. package/dist/workflows/signals.d.ts +32 -2
  194. package/dist/workflows/signals.js +25 -2
  195. package/package.json +4 -1
  196. package/workflow-bundle.js +97 -6
  197. package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
  198. package/dist/tools/helpers.d.ts +0 -21
  199. package/dist/tools/helpers.js +0 -25
@@ -35,11 +35,11 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.DORMANT_THRESHOLD_MS = void 0;
37
37
  exports.classifyDormancy = classifyDormancy;
38
- exports.registerEnsembleTool = registerEnsembleTool;
38
+ exports.buildEnsembleTool = buildEnsembleTool;
39
39
  const zod_1 = require("zod");
40
40
  const os = __importStar(require("os"));
41
41
  const resolve_1 = require("../activities/resolve");
42
- const helpers_1 = require("./helpers");
42
+ const descriptor_1 = require("./descriptor");
43
43
  const duration_1 = require("../utils/duration");
44
44
  /**
45
45
  * Default dormancy threshold (1 hour). Per #563: a `detached` player whose
@@ -79,75 +79,80 @@ function classifyDormancy(session, now, thresholdMs = exports.DORMANT_THRESHOLD_
79
79
  }
80
80
  return 'active';
81
81
  }
82
- function registerEnsembleTool(server, client, config, getPlayerId, ownWorkflowId) {
83
- (0, helpers_1.defineTool)(server, 'ensemble', `Discover active Claude Code sessions in the "${config.ensemble}" ensemble. Returns player IDs, descriptions, and metadata. NOTE: returns tempo-registered players only — does NOT include Claude Code Agent-tool sub-agents (spawned via the Agent tool / subagent_type). Those are ephemeral and process-local; call TaskList separately to enumerate them. Tempo players are addressable via cue; Agent-tool sub-agents are not.`, {
84
- scope: zod_1.z.string().optional().describe('Filter scope: "machine" (same hostname), "repo" (same git root), "all" (default). All scopes are within the current ensemble.'),
85
- // #563: dormancy filter. Default `show` preserves the pre-#563 listing
86
- // (everything visible) but groups gone/long-detached players into a
87
- // separate "Dormant" section. `hide` suppresses dormant entries
88
- // entirely; `show-only` is the inverse, useful for cleanup workflows.
89
- dormant: zod_1.z.enum(['show', 'hide', 'show-only']).optional().describe('Dormancy filter: "show" (default — group dormant in a separate section), "hide" (suppress dormant entries), "show-only" (only show dormant). A player is dormant when phase=gone, or phase=detached with no activity in the last hour.'),
90
- }, async (args) => {
91
- const scope = (args.scope ?? 'all');
92
- const dormantFilter = (args.dormant ?? 'show');
93
- let sessions;
94
- try {
95
- sessions = await (0, resolve_1.scanEnsembleSessions)(client, config.ensemble);
96
- }
97
- catch (err) {
98
- return (0, helpers_1.fail)(`Error listing workflows: ${(0, helpers_1.formatError)(err)}`);
99
- }
100
- // Apply scope filters
101
- let ownGitRoot;
102
- if (scope === 'repo') {
82
+ function buildEnsembleTool(client, config, getPlayerId, ownWorkflowId) {
83
+ return {
84
+ name: 'ensemble',
85
+ description: `Discover active Claude Code sessions in the "${config.ensemble}" ensemble. Returns player IDs, descriptions, and metadata. NOTE: returns tempo-registered players only — does NOT include Claude Code Agent-tool sub-agents (spawned via the Agent tool / subagent_type). Those are ephemeral and process-local; call TaskList separately to enumerate them. Tempo players are addressable via cue; Agent-tool sub-agents are not.`,
86
+ params: {
87
+ scope: zod_1.z.string().optional().describe('Filter scope: "machine" (same hostname), "repo" (same git root), "all" (default). All scopes are within the current ensemble.'),
88
+ // #563: dormancy filter. Default `show` preserves the pre-#563 listing
89
+ // (everything visible) but groups gone/long-detached players into a
90
+ // separate "Dormant" section. `hide` suppresses dormant entries
91
+ // entirely; `show-only` is the inverse, useful for cleanup workflows.
92
+ dormant: zod_1.z.enum(['show', 'hide', 'show-only']).optional().describe('Dormancy filter: "show" (default — group dormant in a separate section), "hide" (suppress dormant entries), "show-only" (only show dormant). A player is dormant when phase=gone, or phase=detached with no activity in the last hour.'),
93
+ },
94
+ handler: async (args) => {
95
+ const scope = (args.scope ?? 'all');
96
+ const dormantFilter = (args.dormant ?? 'show');
97
+ let sessions;
103
98
  try {
104
- const ownHandle = client.workflow.getHandle(ownWorkflowId);
105
- const ownMeta = await ownHandle.query('getMetadata');
106
- ownGitRoot = ownMeta.gitRoot;
99
+ sessions = await (0, resolve_1.scanEnsembleSessions)(client, config.ensemble);
107
100
  }
108
- catch {
109
- // Can't determine own git root — skip repo filtering
101
+ catch (err) {
102
+ return (0, descriptor_1.fail)(`Error listing workflows: ${(0, descriptor_1.formatError)(err)}`);
110
103
  }
111
- }
112
- const scoped = sessions.filter((s) => {
113
- if (scope === 'machine' && s.hostname !== os.hostname())
114
- return false;
115
- if (scope === 'repo' && ownGitRoot && s.gitRoot !== ownGitRoot)
116
- return false;
117
- return true;
118
- });
119
- const now = Date.now();
120
- const enriched = scoped.map((s) => ({
121
- ...s,
122
- isYou: s.playerId === getPlayerId(),
123
- dormancy: classifyDormancy(s, now),
124
- }));
125
- const active = enriched.filter((p) => p.dormancy === 'active');
126
- const dormant = enriched.filter((p) => p.dormancy === 'dormant');
127
- if (active.length === 0 && dormant.length === 0) {
128
- return (0, helpers_1.ok)('No active sessions found.');
129
- }
130
- // #563 summary line — surface both counts so operators can see what's
131
- // being hidden behind the dormant filter without re-running.
132
- const summary = `**${config.ensemble}**: ${active.length} active, ${dormant.length} dormant`;
133
- const sections = [summary];
134
- const showActive = dormantFilter !== 'show-only';
135
- const showDormant = dormantFilter !== 'hide';
136
- if (showActive) {
137
- if (active.length > 0) {
138
- sections.push(`\n=== Active (${active.length}) ===\n`);
139
- sections.push(active.map((p) => renderPlayerLine(p, now, false)).join('\n\n'));
104
+ // Apply scope filters
105
+ let ownGitRoot;
106
+ if (scope === 'repo') {
107
+ try {
108
+ const ownHandle = client.workflow.getHandle(ownWorkflowId);
109
+ const ownMeta = await ownHandle.query('getMetadata');
110
+ ownGitRoot = ownMeta.gitRoot;
111
+ }
112
+ catch {
113
+ // Can't determine own git root — skip repo filtering
114
+ }
140
115
  }
141
- else {
142
- sections.push('\n=== Active (0) ===\n(none)');
116
+ const scoped = sessions.filter((s) => {
117
+ if (scope === 'machine' && s.hostname !== os.hostname())
118
+ return false;
119
+ if (scope === 'repo' && ownGitRoot && s.gitRoot !== ownGitRoot)
120
+ return false;
121
+ return true;
122
+ });
123
+ const now = Date.now();
124
+ const enriched = scoped.map((s) => ({
125
+ ...s,
126
+ isYou: s.playerId === getPlayerId(),
127
+ dormancy: classifyDormancy(s, now),
128
+ }));
129
+ const active = enriched.filter((p) => p.dormancy === 'active');
130
+ const dormant = enriched.filter((p) => p.dormancy === 'dormant');
131
+ if (active.length === 0 && dormant.length === 0) {
132
+ return (0, descriptor_1.ok)('No active sessions found.');
143
133
  }
144
- }
145
- if (showDormant && dormant.length > 0) {
146
- sections.push(`\n=== Dormant (${dormant.length}) — last seen >1h ago or gone ===\n`);
147
- sections.push(dormant.map((p) => renderPlayerLine(p, now, true)).join('\n\n'));
148
- }
149
- return (0, helpers_1.ok)(sections.join('\n'));
150
- });
134
+ // #563 summary line — surface both counts so operators can see what's
135
+ // being hidden behind the dormant filter without re-running.
136
+ const summary = `**${config.ensemble}**: ${active.length} active, ${dormant.length} dormant`;
137
+ const sections = [summary];
138
+ const showActive = dormantFilter !== 'show-only';
139
+ const showDormant = dormantFilter !== 'hide';
140
+ if (showActive) {
141
+ if (active.length > 0) {
142
+ sections.push(`\n=== Active (${active.length}) ===\n`);
143
+ sections.push(active.map((p) => renderPlayerLine(p, now, false)).join('\n\n'));
144
+ }
145
+ else {
146
+ sections.push('\n=== Active (0) ===\n(none)');
147
+ }
148
+ }
149
+ if (showDormant && dormant.length > 0) {
150
+ sections.push(`\n=== Dormant (${dormant.length}) — last seen >1h ago or gone ===\n`);
151
+ sections.push(dormant.map((p) => renderPlayerLine(p, now, true)).join('\n\n'));
152
+ }
153
+ return (0, descriptor_1.ok)(sections.join('\n'));
154
+ },
155
+ };
151
156
  }
152
157
  /**
153
158
  * Render one player as the multi-line block historically emitted by
@@ -1,3 +1,3 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { WorkflowHandle } from '@temporalio/client';
3
- export declare function registerEvaluateGateTool(server: McpServer, handle: WorkflowHandle, getPlayerId: () => string): void;
2
+ import { type TempoToolDescriptor } from './descriptor';
3
+ export declare function buildEvaluateGateTool(handle: WorkflowHandle, getPlayerId: () => string): TempoToolDescriptor;
@@ -1,32 +1,38 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerEvaluateGateTool = registerEvaluateGateTool;
3
+ exports.buildEvaluateGateTool = buildEvaluateGateTool;
4
4
  const zod_1 = require("zod");
5
- const helpers_1 = require("./helpers");
5
+ const descriptor_1 = require("./descriptor");
6
+ const signals_1 = require("../workflows/signals");
6
7
  const validation_1 = require("../utils/validation");
7
- function registerEvaluateGateTool(server, handle, getPlayerId) {
8
- (0, helpers_1.defineTool)(server, 'evaluate_gate', 'Mark one or more criteria on a quality gate as passed or failed. Conductor only.', {
9
- task: zod_1.z.string().max(validation_1.GATE_TASK_MAX).describe('The task name of the gate to evaluate'),
10
- evaluations: zod_1.z.array(zod_1.z.object({
11
- index: zod_1.z.number().int().min(0).describe('Zero-based index of the criterion'),
12
- status: zod_1.z.enum(['passed', 'failed']).describe('Whether this criterion passed or failed'),
13
- notes: zod_1.z.string().max(validation_1.GATE_NOTES_MAX).optional().describe('Optional notes explaining the evaluation'),
14
- })).min(1).describe('List of criterion evaluations'),
15
- }, async (args) => {
16
- const { task, evaluations } = args;
17
- try {
18
- await handle.signal('evaluateGateCriteria', {
19
- task,
20
- evaluations,
21
- evaluatedBy: getPlayerId(),
22
- });
23
- const summary = evaluations
24
- .map((ev) => ` ${ev.index}: ${ev.status === 'passed' ? '\u2705' : '\u274c'} ${ev.status}${ev.notes ? ` — ${ev.notes}` : ''}`)
25
- .join('\n');
26
- return (0, helpers_1.ok)(`Evaluated ${evaluations.length} criteria on gate **${task}**:\n${summary}`);
27
- }
28
- catch (err) {
29
- return (0, helpers_1.fail)(`Failed to evaluate gate: ${(0, helpers_1.formatError)(err)}`);
30
- }
31
- });
8
+ function buildEvaluateGateTool(handle, getPlayerId) {
9
+ return {
10
+ name: 'evaluate_gate',
11
+ description: 'Mark one or more criteria on a quality gate as passed or failed. Conductor only.',
12
+ params: {
13
+ task: zod_1.z.string().max(validation_1.GATE_TASK_MAX).describe('The task name of the gate to evaluate'),
14
+ evaluations: zod_1.z.array(zod_1.z.object({
15
+ index: zod_1.z.number().int().min(0).describe('Zero-based index of the criterion'),
16
+ status: zod_1.z.enum(['passed', 'failed']).describe('Whether this criterion passed or failed'),
17
+ notes: zod_1.z.string().max(validation_1.GATE_NOTES_MAX).optional().describe('Optional notes explaining the evaluation'),
18
+ })).min(1).describe('List of criterion evaluations'),
19
+ },
20
+ handler: async (args) => {
21
+ const { task, evaluations } = args;
22
+ try {
23
+ await handle.signal(signals_1.evaluateGateCriteriaSignal, {
24
+ task,
25
+ evaluations,
26
+ evaluatedBy: getPlayerId(),
27
+ });
28
+ const summary = evaluations
29
+ .map((ev) => ` ${ev.index}: ${ev.status === 'passed' ? '\u2705' : '\u274c'} ${ev.status}${ev.notes ? ` — ${ev.notes}` : ''}`)
30
+ .join('\n');
31
+ return (0, descriptor_1.ok)(`Evaluated ${evaluations.length} criteria on gate **${task}**:\n${summary}`);
32
+ }
33
+ catch (err) {
34
+ return (0, descriptor_1.fail)(`Failed to evaluate gate: ${(0, descriptor_1.formatError)(err)}`);
35
+ }
36
+ },
37
+ };
32
38
  }
@@ -1,6 +1,6 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { WorkflowHandle, Client } from '@temporalio/client';
3
2
  import { Config } from '../config';
3
+ import { type TempoToolDescriptor } from './descriptor';
4
4
  /**
5
5
  * Return type is monomorphic by design: `{ content, savedAt, savedBy } | null`.
6
6
  * ADR 0011 §Alternatives explicitly rejected a `list: boolean` flag because
@@ -10,4 +10,4 @@ import { Config } from '../config';
10
10
  * playerStateKeys`; v2 can graduate a dedicated `list_state` MCP tool if
11
11
  * telemetry shows real demand.
12
12
  */
13
- export declare function registerFetchStateTool(server: McpServer, client: Client, config: Config, handle: WorkflowHandle, getPlayerId: () => string): void;
13
+ export declare function buildFetchStateTool(client: Client, config: Config, handle: WorkflowHandle, getPlayerId: () => string): TempoToolDescriptor;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerFetchStateTool = registerFetchStateTool;
3
+ exports.buildFetchStateTool = buildFetchStateTool;
4
4
  /**
5
5
  * `fetch_state` — read a saved state slot for self or any peer in the
6
6
  * ensemble (#334 PR-1, ADR 0011).
@@ -26,7 +26,7 @@ exports.registerFetchStateTool = registerFetchStateTool;
26
26
  const zod_1 = require("zod");
27
27
  const resolve_1 = require("./resolve");
28
28
  const signals_1 = require("../workflows/signals");
29
- const helpers_1 = require("./helpers");
29
+ const descriptor_1 = require("./descriptor");
30
30
  const validation_1 = require("../utils/validation");
31
31
  /**
32
32
  * Return type is monomorphic by design: `{ content, savedAt, savedBy } | null`.
@@ -37,42 +37,47 @@ const validation_1 = require("../utils/validation");
37
37
  * playerStateKeys`; v2 can graduate a dedicated `list_state` MCP tool if
38
38
  * telemetry shows real demand.
39
39
  */
40
- function registerFetchStateTool(server, client, config, handle, getPlayerId) {
41
- (0, helpers_1.defineTool)(server, 'fetch_state', `Read a saved-state slot for yourself or a peer. Defaults to your own "${validation_1.PLAYER_STATE_DEFAULT_KEY}" slot.
40
+ function buildFetchStateTool(client, config, handle, getPlayerId) {
41
+ return {
42
+ name: 'fetch_state',
43
+ description: `Read a saved-state slot for yourself or a peer. Defaults to your own "${validation_1.PLAYER_STATE_DEFAULT_KEY}" slot.
42
44
 
43
- Pass \`playerId\` to read a peer's slot (any player in the ensemble can read any other player's state — audit identity is recorded on each slot via \`savedBy\`). Returns a "(no state saved …)" message when the slot is empty.`, {
44
- 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}").`),
45
- playerId: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional().describe('Target player name (default: self).'),
46
- }, async (args) => {
47
- const { key, playerId } = args;
48
- const targetId = playerId ?? getPlayerId();
49
- // Validate any explicit playerId `getPlayerId()` is trusted (set by
50
- // the MCP server) and skips this check.
51
- if (playerId !== undefined) {
52
- const nameError = (0, validation_1.validatePlayerName)(playerId);
53
- if (nameError)
54
- return (0, helpers_1.fail)(nameError);
55
- }
56
- try {
57
- // Self-targeted reads use the cached own-session handle directly;
58
- // peer reads go through `resolveSession` (the codebase convention,
59
- // not a raw `client.workflow.getHandle`) so workflow-id format
60
- // changes only need to be tracked in one place.
61
- const targetHandle = targetId === getPlayerId()
62
- ? handle
63
- : await (0, resolve_1.resolveSession)(client, config.ensemble, targetId);
64
- if (!targetHandle) {
65
- return (0, helpers_1.fail)(`No session found with name "${targetId}".`);
45
+ Pass \`playerId\` to read a peer's slot (any player in the ensemble can read any other player's state — audit identity is recorded on each slot via \`savedBy\`). Returns a "(no state saved …)" message when the slot is empty.`,
46
+ params: {
47
+ 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}").`),
48
+ playerId: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional().describe('Target player name (default: self).'),
49
+ },
50
+ handler: async (args) => {
51
+ const { key, playerId } = args;
52
+ const targetId = playerId ?? getPlayerId();
53
+ // Validate any explicit playerId `getPlayerId()` is trusted (set by
54
+ // the MCP server) and skips this check.
55
+ if (playerId !== undefined) {
56
+ const nameError = (0, validation_1.validatePlayerName)(playerId);
57
+ if (nameError)
58
+ return (0, descriptor_1.fail)(nameError);
66
59
  }
67
- const slotKey = key ?? validation_1.PLAYER_STATE_DEFAULT_KEY;
68
- const result = await targetHandle.query(signals_1.playerStateQuery, { key: slotKey });
69
- if (!result) {
70
- return (0, helpers_1.ok)(`(no state saved at slot "${slotKey}" for ${targetId})`);
60
+ try {
61
+ // Self-targeted reads use the cached own-session handle directly;
62
+ // peer reads go through `resolveSession` (the codebase convention,
63
+ // not a raw `client.workflow.getHandle`) so workflow-id format
64
+ // changes only need to be tracked in one place.
65
+ const targetHandle = targetId === getPlayerId()
66
+ ? handle
67
+ : await (0, resolve_1.resolveSession)(client, config.ensemble, targetId);
68
+ if (!targetHandle) {
69
+ return (0, descriptor_1.fail)(`No session found with name "${targetId}".`);
70
+ }
71
+ const slotKey = key ?? validation_1.PLAYER_STATE_DEFAULT_KEY;
72
+ const result = await targetHandle.query(signals_1.playerStateQuery, { key: slotKey });
73
+ if (!result) {
74
+ return (0, descriptor_1.ok)(`(no state saved at slot "${slotKey}" for ${targetId})`);
75
+ }
76
+ return (0, descriptor_1.ok)(`Slot **"${slotKey}"** — saved by **${result.savedBy}** at ${result.savedAt}\n\n${result.content}`);
71
77
  }
72
- return (0, helpers_1.ok)(`Slot **"${slotKey}"** — saved by **${result.savedBy}** at ${result.savedAt}\n\n${result.content}`);
73
- }
74
- catch (err) {
75
- return (0, helpers_1.fail)(`Failed to fetch state: ${(0, helpers_1.formatError)(err)}`);
76
- }
77
- });
78
+ catch (err) {
79
+ return (0, descriptor_1.fail)(`Failed to fetch state: ${(0, descriptor_1.formatError)(err)}`);
80
+ }
81
+ },
82
+ };
78
83
  }
@@ -1,3 +1,3 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { WorkflowHandle } from '@temporalio/client';
3
- export declare function registerGatesTool(server: McpServer, handle: WorkflowHandle): void;
2
+ import { type TempoToolDescriptor } from './descriptor';
3
+ export declare function buildGatesTool(handle: WorkflowHandle): TempoToolDescriptor;
@@ -1,41 +1,46 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerGatesTool = registerGatesTool;
3
+ exports.buildGatesTool = buildGatesTool;
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 registerGatesTool(server, handle) {
8
- (0, helpers_1.defineTool)(server, 'gates', 'List quality gates and their status. Optionally filter by task name or status. Conductor only.', {
9
- task: zod_1.z.string().max(validation_1.GATE_TASK_MAX).optional().describe('Filter by specific task name'),
10
- status: zod_1.z.enum(['open', 'passed', 'failed']).optional().describe('Filter by gate status'),
11
- }, async (args) => {
12
- const { task, status } = args;
13
- try {
14
- const gates = await handle.query('qualityGates');
15
- let filtered = gates;
16
- if (task) {
17
- filtered = filtered.filter((g) => g.task === task);
18
- }
19
- if (status) {
20
- filtered = filtered.filter((g) => g.status === status);
7
+ function buildGatesTool(handle) {
8
+ return {
9
+ name: 'gates',
10
+ description: 'List quality gates and their status. Optionally filter by task name or status. Conductor only.',
11
+ params: {
12
+ task: zod_1.z.string().max(validation_1.GATE_TASK_MAX).optional().describe('Filter by specific task name'),
13
+ status: zod_1.z.enum(['open', 'passed', 'failed']).optional().describe('Filter by gate status'),
14
+ },
15
+ handler: async (args) => {
16
+ const { task, status } = args;
17
+ try {
18
+ const gates = await handle.query('qualityGates');
19
+ let filtered = gates;
20
+ if (task) {
21
+ filtered = filtered.filter((g) => g.task === task);
22
+ }
23
+ if (status) {
24
+ filtered = filtered.filter((g) => g.status === status);
25
+ }
26
+ if (filtered.length === 0) {
27
+ return (0, descriptor_1.ok)('No quality gates found matching the filter.');
28
+ }
29
+ const lines = filtered.map((g) => {
30
+ const icon = g.status === 'passed' ? '\u2705' : g.status === 'failed' ? '\u274c' : '\u23f3';
31
+ const criteriaLines = g.criteria.map((c, i) => {
32
+ const cIcon = c.status === 'passed' ? '\u2705' : c.status === 'failed' ? '\u274c' : '\u2b1c';
33
+ const evaluator = c.evaluatedBy ? ` (by ${c.evaluatedBy})` : '';
34
+ const notes = c.notes ? ` — ${c.notes}` : '';
35
+ return ` ${i}. ${cIcon} ${c.text}${evaluator}${notes}`;
36
+ });
37
+ return `${icon} **${g.task}** [${g.status}] (by ${g.createdBy}, ${g.createdAt})\n${criteriaLines.join('\n')}`;
38
+ });
39
+ return (0, descriptor_1.ok)(`${filtered.length} quality gate${filtered.length === 1 ? '' : 's'}:\n\n${lines.join('\n\n')}`);
21
40
  }
22
- if (filtered.length === 0) {
23
- return (0, helpers_1.ok)('No quality gates found matching the filter.');
41
+ catch (err) {
42
+ return (0, descriptor_1.fail)(`Failed to query gates: ${(0, descriptor_1.formatError)(err)}`);
24
43
  }
25
- const lines = filtered.map((g) => {
26
- const icon = g.status === 'passed' ? '\u2705' : g.status === 'failed' ? '\u274c' : '\u23f3';
27
- const criteriaLines = g.criteria.map((c, i) => {
28
- const cIcon = c.status === 'passed' ? '\u2705' : c.status === 'failed' ? '\u274c' : '\u2b1c';
29
- const evaluator = c.evaluatedBy ? ` (by ${c.evaluatedBy})` : '';
30
- const notes = c.notes ? ` — ${c.notes}` : '';
31
- return ` ${i}. ${cIcon} ${c.text}${evaluator}${notes}`;
32
- });
33
- return `${icon} **${g.task}** [${g.status}] (by ${g.createdBy}, ${g.createdAt})\n${criteriaLines.join('\n')}`;
34
- });
35
- return (0, helpers_1.ok)(`${filtered.length} quality gate${filtered.length === 1 ? '' : 's'}:\n\n${lines.join('\n\n')}`);
36
- }
37
- catch (err) {
38
- return (0, helpers_1.fail)(`Failed to query gates: ${(0, helpers_1.formatError)(err)}`);
39
- }
40
- });
44
+ },
45
+ };
41
46
  }
@@ -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 registerHostsTool(server: McpServer, client: Client, config: Config): void;
3
+ import { type TempoToolDescriptor } from './descriptor';
4
+ export declare function buildHostsTool(client: Client, config: Config): TempoToolDescriptor;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerHostsTool = registerHostsTool;
3
+ exports.buildHostsTool = buildHostsTool;
4
4
  /**
5
5
  * `hosts` — MCP tool for surfacing daemons polling the Temporal namespace (#274).
6
6
  *
@@ -16,25 +16,30 @@ exports.registerHostsTool = registerHostsTool;
16
16
  * Thin wrapper — all the logic is in `listHosts`.
17
17
  */
18
18
  const zod_1 = require("zod");
19
- const helpers_1 = require("./helpers");
19
+ const descriptor_1 = require("./descriptor");
20
20
  const hosts_1 = require("../utils/hosts");
21
21
  const format_hosts_1 = require("../utils/format-hosts");
22
- function registerHostsTool(server, client, config) {
23
- (0, helpers_1.defineTool)(server, 'hosts', 'Show all daemons polling this Temporal namespace, with their advertised capabilities. Returns liveness (live/stale), recruit-readiness, and each daemon\'s profile (default agent, available player types, platform) when it signaled one at boot. Read-only diagnostic.', {
24
- includeStale: zod_1.z.boolean().optional().describe('Include hosts not seen in the last minute (default: false).'),
25
- force: zod_1.z.boolean().optional().describe('Bypass the 3-second result cache (default: false).'),
26
- }, async (args) => {
27
- const { includeStale, force } = args;
28
- try {
29
- const hosts = await (0, hosts_1.listHosts)(client, {
30
- force: Boolean(force),
31
- namespace: config.temporalNamespace,
32
- taskQueue: config.taskQueue,
33
- });
34
- return (0, helpers_1.ok)((0, format_hosts_1.formatHostList)(hosts, { includeStale: Boolean(includeStale) }));
35
- }
36
- catch (err) {
37
- return (0, helpers_1.fail)(`Failed to list hosts: ${(0, helpers_1.formatError)(err)}`);
38
- }
39
- });
22
+ function buildHostsTool(client, config) {
23
+ return {
24
+ name: 'hosts',
25
+ description: 'Show all daemons polling this Temporal namespace, with their advertised capabilities. Returns liveness (live/stale), recruit-readiness, and each daemon\'s profile (default agent, available player types, platform) when it signaled one at boot. Read-only diagnostic.',
26
+ params: {
27
+ includeStale: zod_1.z.boolean().optional().describe('Include hosts not seen in the last minute (default: false).'),
28
+ force: zod_1.z.boolean().optional().describe('Bypass the 3-second result cache (default: false).'),
29
+ },
30
+ handler: async (args) => {
31
+ const { includeStale, force } = args;
32
+ try {
33
+ const hosts = await (0, hosts_1.listHosts)(client, {
34
+ force: Boolean(force),
35
+ namespace: config.temporalNamespace,
36
+ taskQueue: config.taskQueue,
37
+ });
38
+ return (0, descriptor_1.ok)((0, format_hosts_1.formatHostList)(hosts, { includeStale: Boolean(includeStale) }));
39
+ }
40
+ catch (err) {
41
+ return (0, descriptor_1.fail)(`Failed to list hosts: ${(0, descriptor_1.formatError)(err)}`);
42
+ }
43
+ },
44
+ };
40
45
  }
@@ -1,3 +1,3 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { WorkflowHandle } from '@temporalio/client';
3
- export declare function registerListenTool(server: McpServer, handle: WorkflowHandle): void;
2
+ import { type TempoToolDescriptor } from './descriptor';
3
+ export declare function buildListenTool(handle: WorkflowHandle): TempoToolDescriptor;
@@ -1,22 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerListenTool = registerListenTool;
4
- const helpers_1 = require("./helpers");
5
- function registerListenTool(server, handle) {
6
- (0, helpers_1.defineTool)(server, 'listen', 'Check for pending messages from other sessions. Use this if you want to manually check for new messages.', {}, async () => {
7
- try {
8
- const messages = await handle.query('pendingMessages');
9
- if (messages.length === 0) {
10
- return (0, helpers_1.ok)('No pending messages.');
3
+ exports.buildListenTool = buildListenTool;
4
+ const descriptor_1 = require("./descriptor");
5
+ function buildListenTool(handle) {
6
+ return {
7
+ name: 'listen',
8
+ description: 'Check for pending messages from other sessions. Use this if you want to manually check for new messages.',
9
+ params: {},
10
+ handler: async () => {
11
+ try {
12
+ const messages = await handle.query('pendingMessages');
13
+ if (messages.length === 0) {
14
+ return (0, descriptor_1.ok)('No pending messages.');
15
+ }
16
+ // Mark messages as delivered
17
+ const ids = messages.map((m) => m.id);
18
+ await handle.signal('markDelivered', ids);
19
+ const lines = messages.map((m) => `**${m.from}** (${m.timestamp}):\n${m.text}`);
20
+ return (0, descriptor_1.ok)(lines.join('\n\n'));
11
21
  }
12
- // Mark messages as delivered
13
- const ids = messages.map((m) => m.id);
14
- await handle.signal('markDelivered', ids);
15
- const lines = messages.map((m) => `**${m.from}** (${m.timestamp}):\n${m.text}`);
16
- return (0, helpers_1.ok)(lines.join('\n\n'));
17
- }
18
- catch (err) {
19
- return (0, helpers_1.fail)(`Failed to check messages: ${(0, helpers_1.formatError)(err)}`);
20
- }
21
- });
22
+ catch (err) {
23
+ return (0, descriptor_1.fail)(`Failed to check messages: ${(0, descriptor_1.formatError)(err)}`);
24
+ }
25
+ },
26
+ };
22
27
  }
@@ -1,5 +1,5 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { Client, WorkflowHandle } from '@temporalio/client';
3
2
  import { Config } from '../config';
4
3
  import { AgentType } from '../types';
5
- export declare function registerLoadLineupTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string, ownAgentType?: AgentType, handle?: WorkflowHandle, setPlayerId?: (id: string) => void, isConductor?: boolean): void;
4
+ import { type TempoToolDescriptor } from './descriptor';
5
+ export declare function buildLoadLineupTool(client: Client, config: Config, getPlayerId: () => string, ownAgentType?: AgentType, handle?: WorkflowHandle, setPlayerId?: (id: string) => void, isConductor?: boolean): TempoToolDescriptor;