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.
- package/CLAUDE.md +253 -219
- package/LICENSE +21 -21
- package/README.md +293 -289
- package/assets/icon-dark.svg +9 -9
- package/assets/icon.svg +9 -9
- package/assets/logo-dark.svg +11 -11
- package/assets/logo-light.svg +11 -11
- package/dashboard/README.md +91 -91
- package/dashboard/dist/assets/{index-D6Xyje_n.js → index-jmYe6rmS.js} +2 -2
- package/dashboard/dist/assets/index-jmYe6rmS.js.map +1 -0
- package/dashboard/dist/index.html +20 -20
- package/dashboard/package.json +47 -47
- package/dist/activities/outbox.d.ts +30 -1
- package/dist/activities/outbox.js +96 -3
- package/dist/adapters/base.js +5 -0
- package/dist/adapters/copilot/adapter.js +12 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +7 -0
- package/dist/adapters/pi/adapter.d.ts +2 -0
- package/dist/adapters/pi/adapter.js +43 -0
- package/dist/adapters/pi/index.d.ts +16 -0
- package/dist/adapters/pi/index.js +10 -0
- package/dist/cli/global-wrapper.d.ts +19 -0
- package/dist/cli/global-wrapper.js +169 -0
- package/dist/cli/help-text.js +97 -97
- package/dist/cli/startup.js +11 -0
- package/dist/cli/upgrade-command.js +81 -81
- package/dist/cli.js +12 -0
- package/dist/client/core.js +9 -2
- package/dist/client/interface.d.ts +6 -0
- package/dist/config.d.ts +79 -0
- package/dist/config.js +74 -0
- package/dist/daemon.js +37 -1
- package/dist/http/aggregate.d.ts +22 -1
- package/dist/http/aggregate.js +41 -0
- package/dist/http/auth.d.ts +94 -8
- package/dist/http/auth.js +93 -9
- package/dist/http/body.d.ts +4 -1
- package/dist/http/body.js +6 -3
- package/dist/http/event-bus.js +1 -0
- package/dist/http/event-types.d.ts +34 -2
- package/dist/http/event-types.js +1 -0
- package/dist/http/gate-audit.d.ts +12 -0
- package/dist/http/gate-audit.js +95 -0
- package/dist/http/gate-registry.d.ts +167 -0
- package/dist/http/gate-registry.js +163 -0
- package/dist/http/gate-routes.d.ts +48 -0
- package/dist/http/gate-routes.js +102 -0
- package/dist/http/ingest-registry.d.ts +30 -0
- package/dist/http/ingest-registry.js +108 -0
- package/dist/http/inner-loop-routes.d.ts +66 -0
- package/dist/http/inner-loop-routes.js +182 -0
- package/dist/http/inner-loop.d.ts +92 -0
- package/dist/http/inner-loop.js +155 -0
- package/dist/http/server.d.ts +38 -3
- package/dist/http/server.js +211 -6
- package/dist/http/snapshot.d.ts +6 -0
- package/dist/http/snapshot.js +6 -0
- package/dist/pi/cue-pump.d.ts +61 -0
- package/dist/pi/cue-pump.js +95 -0
- package/dist/pi/extension.d.ts +45 -0
- package/dist/pi/extension.js +407 -0
- package/dist/pi/gate-client.d.ts +54 -0
- package/dist/pi/gate-client.js +136 -0
- package/dist/pi/headless.d.ts +85 -0
- package/dist/pi/headless.js +224 -0
- package/dist/pi/index.d.ts +28 -0
- package/dist/pi/index.js +43 -0
- package/dist/pi/inner-loop-client.d.ts +67 -0
- package/dist/pi/inner-loop-client.js +164 -0
- package/dist/pi/inner-loop-publisher.d.ts +187 -0
- package/dist/pi/inner-loop-publisher.js +236 -0
- package/dist/pi/lazy-proxy.d.ts +37 -0
- package/dist/pi/lazy-proxy.js +55 -0
- package/dist/pi/mission-control/actions.d.ts +48 -0
- package/dist/pi/mission-control/actions.js +98 -0
- package/dist/pi/mission-control/board.d.ts +53 -0
- package/dist/pi/mission-control/board.js +104 -0
- package/dist/pi/mission-control/extension.d.ts +44 -0
- package/dist/pi/mission-control/extension.js +251 -0
- package/dist/pi/mission-control/index.d.ts +15 -0
- package/dist/pi/mission-control/index.js +32 -0
- package/dist/pi/mission-control/inner-tail.d.ts +48 -0
- package/dist/pi/mission-control/inner-tail.js +76 -0
- package/dist/pi/mission-control/pi-ui.d.ts +43 -0
- package/dist/pi/mission-control/pi-ui.js +10 -0
- package/dist/pi/mission-control/render.d.ts +6 -0
- package/dist/pi/mission-control/render.js +95 -0
- package/dist/pi/phase-driver.d.ts +74 -0
- package/dist/pi/phase-driver.js +122 -0
- package/dist/pi/pi-types.d.ts +208 -0
- package/dist/pi/pi-types.js +21 -0
- package/dist/pi/probe.d.ts +80 -0
- package/dist/pi/probe.js +154 -0
- package/dist/pi/render-tools.d.ts +17 -0
- package/dist/pi/render-tools.js +51 -0
- package/dist/pi/reset-pump.d.ts +47 -0
- package/dist/pi/reset-pump.js +85 -0
- package/dist/pi/tool-capability.d.ts +60 -0
- package/dist/pi/tool-capability.js +156 -0
- package/dist/pi/workflow-client.d.ts +158 -0
- package/dist/pi/workflow-client.js +289 -0
- package/dist/pi/zod-to-typebox.d.ts +74 -0
- package/dist/pi/zod-to-typebox.js +191 -0
- package/dist/scripts/verify-daemon-isolation-guard.js +24 -24
- package/dist/server-tools.d.ts +2 -0
- package/dist/server-tools.js +50 -46
- package/dist/server.js +4 -0
- package/dist/spawn.d.ts +55 -0
- package/dist/spawn.js +84 -12
- package/dist/tools/agent-types.d.ts +2 -2
- package/dist/tools/agent-types.js +22 -17
- package/dist/tools/attachment-info.d.ts +2 -2
- package/dist/tools/attachment-info.js +38 -33
- package/dist/tools/broadcast.d.ts +2 -2
- package/dist/tools/broadcast.js +69 -64
- package/dist/tools/cancel-stage.d.ts +2 -2
- package/dist/tools/cancel-stage.js +20 -15
- package/dist/tools/clear-state.d.ts +2 -2
- package/dist/tools/clear-state.js +25 -20
- package/dist/tools/coat-check-evict.d.ts +2 -2
- package/dist/tools/coat-check-evict.js +30 -25
- package/dist/tools/coat-check-get.d.ts +2 -2
- package/dist/tools/coat-check-get.js +39 -34
- package/dist/tools/coat-check-list.d.ts +2 -2
- package/dist/tools/coat-check-list.js +48 -43
- package/dist/tools/coat-check-put.d.ts +2 -2
- package/dist/tools/coat-check-put.js +41 -36
- package/dist/tools/cue.d.ts +2 -2
- package/dist/tools/cue.js +57 -52
- package/dist/tools/descriptor.d.ts +72 -0
- package/dist/tools/descriptor.js +39 -0
- package/dist/tools/destroy.d.ts +2 -2
- package/dist/tools/destroy.js +153 -148
- package/dist/tools/ensemble.d.ts +2 -2
- package/dist/tools/ensemble.js +71 -66
- package/dist/tools/evaluate-gate.d.ts +2 -2
- package/dist/tools/evaluate-gate.js +33 -27
- package/dist/tools/fetch-state.d.ts +2 -2
- package/dist/tools/fetch-state.js +43 -38
- package/dist/tools/gates.d.ts +2 -2
- package/dist/tools/gates.js +39 -34
- package/dist/tools/hosts.d.ts +2 -2
- package/dist/tools/hosts.js +25 -20
- package/dist/tools/listen.d.ts +2 -2
- package/dist/tools/listen.js +23 -18
- package/dist/tools/load-lineup.d.ts +2 -2
- package/dist/tools/load-lineup.js +324 -319
- package/dist/tools/migrate.d.ts +2 -2
- package/dist/tools/migrate.js +45 -40
- package/dist/tools/pause.d.ts +2 -2
- package/dist/tools/pause.js +34 -29
- package/dist/tools/play.d.ts +2 -2
- package/dist/tools/play.js +53 -48
- package/dist/tools/quality-gate.d.ts +2 -2
- package/dist/tools/quality-gate.js +26 -21
- package/dist/tools/recall.d.ts +2 -2
- package/dist/tools/recall.js +32 -27
- package/dist/tools/recruit.d.ts +2 -2
- package/dist/tools/recruit.js +325 -256
- package/dist/tools/release.d.ts +2 -2
- package/dist/tools/release.js +85 -80
- package/dist/tools/report.d.ts +2 -2
- package/dist/tools/report.js +28 -23
- package/dist/tools/reset.d.ts +3 -0
- package/dist/tools/reset.js +51 -0
- package/dist/tools/restart.d.ts +2 -2
- package/dist/tools/restart.js +51 -46
- package/dist/tools/restore.d.ts +2 -2
- package/dist/tools/restore.js +76 -71
- package/dist/tools/save-lineup.d.ts +2 -2
- package/dist/tools/save-lineup.js +32 -27
- package/dist/tools/save-state.d.ts +2 -2
- package/dist/tools/save-state.js +43 -38
- package/dist/tools/schedule.d.ts +2 -2
- package/dist/tools/schedule.js +133 -128
- package/dist/tools/schedules.d.ts +2 -2
- package/dist/tools/schedules.js +41 -36
- package/dist/tools/set-ensemble-description.d.ts +2 -2
- package/dist/tools/set-ensemble-description.js +26 -21
- package/dist/tools/set-name.d.ts +2 -2
- package/dist/tools/set-name.js +38 -33
- package/dist/tools/set-part.d.ts +2 -2
- package/dist/tools/set-part.js +20 -15
- package/dist/tools/shutdown.d.ts +2 -2
- package/dist/tools/shutdown.js +39 -34
- package/dist/tools/stage.d.ts +2 -2
- package/dist/tools/stage.js +28 -23
- package/dist/tools/stages.d.ts +2 -2
- package/dist/tools/stages.js +36 -31
- package/dist/tools/unschedule.d.ts +2 -2
- package/dist/tools/unschedule.js +30 -25
- package/dist/tools/who-am-i.d.ts +2 -2
- package/dist/tools/who-am-i.js +36 -31
- package/dist/tools/worktree.d.ts +2 -2
- package/dist/tools/worktree.js +134 -129
- package/dist/tui/index.js +6 -6
- package/dist/types.d.ts +47 -2
- package/dist/types.js +1 -1
- package/dist/utils/default-part.js +1 -0
- package/dist/utils/grpc-shutdown-guard.d.ts +52 -0
- package/dist/utils/grpc-shutdown-guard.js +88 -0
- package/dist/utils/sdk-probe.d.ts +23 -0
- package/dist/utils/sdk-probe.js +46 -7
- package/dist/worker.d.ts +3 -1
- package/dist/worker.js +6 -2
- package/dist/workflows/session.js +70 -2
- package/dist/workflows/signals.d.ts +32 -2
- package/dist/workflows/signals.js +25 -2
- package/examples/agents/tempo-composer.md +56 -56
- package/examples/agents/tempo-conductor.md +117 -117
- package/examples/agents/tempo-critic.md +73 -73
- package/examples/agents/tempo-improv.md +74 -74
- package/examples/agents/tempo-liner.md +75 -75
- package/examples/agents/tempo-roadie.md +61 -61
- package/examples/agents/tempo-soloist.md +71 -71
- package/examples/agents/tempo-tuner.md +94 -94
- package/examples/ensembles/tempo-big-band.yaml +146 -146
- package/examples/ensembles/tempo-dev-team.yaml +58 -58
- package/examples/ensembles/tempo-headless-jam.yaml +77 -77
- package/examples/ensembles/tempo-jam-session.yaml +41 -41
- package/examples/ensembles/tempo-mock-jam.yaml +79 -79
- package/examples/ensembles/tempo-review-squad.yaml +32 -32
- package/package.json +176 -173
- package/packaging/launchd/com.agent.tempo.plist +46 -46
- package/packaging/systemd/agent-tempo.service +32 -32
- package/packaging/windows/install-task.ps1 +71 -71
- package/scenarios/conductor-recruit-mock.yaml +33 -33
- package/scenarios/echo-roundtrip.yaml +15 -15
- package/scenarios/multi-player-handoff.yaml +38 -38
- package/scenarios/recruit-cascade.yaml +38 -38
- package/scenarios/two-player-conversation.yaml +33 -33
- package/workflow-bundle.js +97 -6
- package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
- package/dist/activities/claude-stop.d.ts +0 -21
- package/dist/activities/claude-stop.js +0 -94
- package/dist/channel.d.ts +0 -3
- package/dist/channel.js +0 -48
- package/dist/copilot-bridge.d.ts +0 -22
- package/dist/copilot-bridge.js +0 -565
- package/dist/scripts/258-spotcheck.js +0 -303
- package/dist/tools/detach.d.ts +0 -4
- package/dist/tools/detach.js +0 -45
- package/dist/tools/encore.d.ts +0 -4
- package/dist/tools/encore.js +0 -31
- package/dist/tools/helpers.d.ts +0 -21
- package/dist/tools/helpers.js +0 -25
- package/dist/tools/pause-ensemble.d.ts +0 -4
- package/dist/tools/pause-ensemble.js +0 -58
- package/dist/tools/resume-ensemble.d.ts +0 -4
- package/dist/tools/resume-ensemble.js +0 -79
- package/dist/tools/stop.d.ts +0 -4
- package/dist/tools/stop.js +0 -29
- package/dist/tui/client.d.ts +0 -6
- package/dist/tui/client.js +0 -9
- package/dist/tui/components/ActivityLog.d.ts +0 -16
- package/dist/tui/components/ActivityLog.js +0 -36
- package/dist/tui/components/CommandOverlay.d.ts +0 -15
- package/dist/tui/components/CommandOverlay.js +0 -34
- package/dist/tui/components/ConductorChat.d.ts +0 -16
- package/dist/tui/components/ConductorChat.js +0 -32
- package/dist/tui/components/EnsembleListView.d.ts +0 -14
- package/dist/tui/components/EnsembleListView.js +0 -32
- package/dist/tui/components/EnsemblePanel.d.ts +0 -12
- package/dist/tui/components/EnsemblePanel.js +0 -40
- package/dist/tui/components/InputBar.d.ts +0 -13
- package/dist/tui/components/InputBar.js +0 -58
- package/dist/tui/components/ScheduleOverlay.d.ts +0 -13
- package/dist/tui/components/ScheduleOverlay.js +0 -113
- package/dist/tui/components/TopBar.d.ts +0 -12
- package/dist/tui/components/TopBar.js +0 -15
- package/dist/tui/core-api.d.ts +0 -26
- package/dist/tui/core-api.js +0 -67
- package/dist/tui/hooks/useEnsembleDiscovery.d.ts +0 -3
- package/dist/tui/hooks/useEnsembleDiscovery.js +0 -30
- package/dist/tui/hooks/useMaestroPoller.d.ts +0 -3
- package/dist/tui/hooks/useMaestroPoller.js +0 -36
- package/dist/tui/hooks/useSendCommand.d.ts +0 -7
- package/dist/tui/hooks/useSendCommand.js +0 -29
- package/dist/utils/bg-preflight.d.ts +0 -25
- package/dist/utils/bg-preflight.js +0 -154
package/dist/tools/destroy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.buildDestroyTool = buildDestroyTool;
|
|
4
4
|
/**
|
|
5
5
|
* `destroy` — terminal end of either a single session or the whole ensemble.
|
|
6
6
|
*
|
|
@@ -24,165 +24,170 @@ const zod_1 = require("zod");
|
|
|
24
24
|
const config_1 = require("../config");
|
|
25
25
|
const signals_1 = require("../workflows/signals");
|
|
26
26
|
const resolve_1 = require("../activities/resolve");
|
|
27
|
-
const
|
|
27
|
+
const descriptor_1 = require("./descriptor");
|
|
28
28
|
const validation_1 = require("../utils/validation");
|
|
29
29
|
const log = (...args) => console.error('[agent-tempo:destroy]', ...args);
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (nameError)
|
|
52
|
-
return (0, helpers_1.fail)(nameError);
|
|
53
|
-
if (playerId === callerId) {
|
|
54
|
-
return (0, helpers_1.fail)('Cannot destroy your own session.');
|
|
30
|
+
function buildDestroyTool(client, config, getPlayerId, handle) {
|
|
31
|
+
return {
|
|
32
|
+
name: 'destroy',
|
|
33
|
+
description: 'Terminally destroy a session workflow (when `playerId` is given) or the entire ensemble (when omitted): every peer session, the scheduler, the maestro, and the conductor. COMPLETEs workflows and cannot be undone. For graceful reap use `shutdown`; for a clean revive use `restart`.',
|
|
34
|
+
params: {
|
|
35
|
+
// #306: `.min(1)` rejects `{playerId: ""}` at the SDK boundary so a
|
|
36
|
+
// buggy MCP caller can't silently fall through to ensemble-wide
|
|
37
|
+
// destroy mode. The handler also guards programmatic callers that
|
|
38
|
+
// bypass Zod (see explicit `playerId === ''` rejection below).
|
|
39
|
+
playerId: zod_1.z.string().min(1).max(validation_1.PLAYER_NAME_MAX).optional().describe('Target player name. Omit to destroy the entire ensemble.'),
|
|
40
|
+
reason: zod_1.z.string().max(500).optional().describe('Optional reason recorded in the workflow\'s audit event'),
|
|
41
|
+
},
|
|
42
|
+
handler: async (args) => {
|
|
43
|
+
const { playerId, reason } = args;
|
|
44
|
+
const callerId = getPlayerId();
|
|
45
|
+
// #306: defense-in-depth for callers that bypass Zod (test harnesses,
|
|
46
|
+
// direct handler invocation). Zod's `.min(1)` already covers normal
|
|
47
|
+
// MCP traffic; this guard ensures empty-string never falls through to
|
|
48
|
+
// ensemble-wide destroy mode regardless of how the handler is reached.
|
|
49
|
+
if (playerId === '') {
|
|
50
|
+
return (0, descriptor_1.fail)('`playerId` cannot be an empty string. Omit it to destroy the entire ensemble.');
|
|
55
51
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
|
|
64
|
-
return (0, helpers_1.ok)(`Destroy queued for **${playerId}**${reason ? ` (reason: ${reason})` : ''}. (outbox: ${entryId})`);
|
|
65
|
-
}
|
|
66
|
-
catch (err) {
|
|
67
|
-
return (0, helpers_1.fail)(`Failed to destroy: ${(0, helpers_1.formatError)(err)}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
// ── Ensemble-scope mode (#287) ──────────────────────────────────────
|
|
71
|
-
// Order: peer sessions (parallel) → scheduler + maestro (parallel) →
|
|
72
|
-
// conductor last. The conductor-last step is the invariant the
|
|
73
|
-
// architect's spec relies on so the conductor sees every peer
|
|
74
|
-
// teardown before its own destroy.
|
|
75
|
-
try {
|
|
76
|
-
const destroyReason = reason ?? `ensemble destroy via ${callerId}`;
|
|
77
|
-
const sessions = await (0, resolve_1.scanEnsembleSessions)(client, config.ensemble);
|
|
78
|
-
const conductorWfId = (0, config_1.conductorWorkflowId)(config.ensemble);
|
|
79
|
-
const peers = [];
|
|
80
|
-
let conductorPresent = false;
|
|
81
|
-
for (const s of sessions) {
|
|
82
|
-
if (s.workflowId === conductorWfId) {
|
|
83
|
-
conductorPresent = true;
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
peers.push(s);
|
|
52
|
+
// ── Single-player mode (existing behaviour) ─────────────────────────
|
|
53
|
+
if (playerId !== undefined) {
|
|
54
|
+
const nameError = (0, validation_1.validatePlayerName)(playerId);
|
|
55
|
+
if (nameError)
|
|
56
|
+
return (0, descriptor_1.fail)(nameError);
|
|
57
|
+
if (playerId === callerId) {
|
|
58
|
+
return (0, descriptor_1.fail)('Cannot destroy your own session.');
|
|
87
59
|
}
|
|
88
|
-
}
|
|
89
|
-
const details = [];
|
|
90
|
-
let destroyed = 0;
|
|
91
|
-
let terminated = 0;
|
|
92
|
-
let failed = 0;
|
|
93
|
-
// Phase 1: destroy every peer in parallel (conductor excluded). Skip
|
|
94
|
-
// the caller's own session — self-destroy is a no-op guard.
|
|
95
|
-
const peerResults = await Promise.allSettled(peers.map(async (s) => {
|
|
96
|
-
if (s.playerId === callerId)
|
|
97
|
-
return { session: s, outcome: 'skipped-self' };
|
|
98
60
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
61
|
+
const entry = {
|
|
62
|
+
type: 'destroy',
|
|
63
|
+
targetPlayerId: playerId,
|
|
64
|
+
...(reason !== undefined ? { reason } : {}),
|
|
65
|
+
notifyConductor: true,
|
|
66
|
+
};
|
|
67
|
+
const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
|
|
68
|
+
return (0, descriptor_1.ok)(`Destroy queued for **${playerId}**${reason ? ` (reason: ${reason})` : ''}. (outbox: ${entryId})`);
|
|
103
69
|
}
|
|
104
70
|
catch (err) {
|
|
105
|
-
return
|
|
71
|
+
return (0, descriptor_1.fail)(`Failed to destroy: ${(0, descriptor_1.formatError)(err)}`);
|
|
106
72
|
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
73
|
+
}
|
|
74
|
+
// ── Ensemble-scope mode (#287) ──────────────────────────────────────
|
|
75
|
+
// Order: peer sessions (parallel) → scheduler + maestro (parallel) →
|
|
76
|
+
// conductor last. The conductor-last step is the invariant the
|
|
77
|
+
// architect's spec relies on so the conductor sees every peer
|
|
78
|
+
// teardown before its own destroy.
|
|
79
|
+
try {
|
|
80
|
+
const destroyReason = reason ?? `ensemble destroy via ${callerId}`;
|
|
81
|
+
const sessions = await (0, resolve_1.scanEnsembleSessions)(client, config.ensemble);
|
|
82
|
+
const conductorWfId = (0, config_1.conductorWorkflowId)(config.ensemble);
|
|
83
|
+
const peers = [];
|
|
84
|
+
let conductorPresent = false;
|
|
85
|
+
for (const s of sessions) {
|
|
86
|
+
if (s.workflowId === conductorWfId) {
|
|
87
|
+
conductorPresent = true;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
peers.push(s);
|
|
91
|
+
}
|
|
115
92
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
93
|
+
const details = [];
|
|
94
|
+
let destroyed = 0;
|
|
95
|
+
let terminated = 0;
|
|
96
|
+
let failed = 0;
|
|
97
|
+
// Phase 1: destroy every peer in parallel (conductor excluded). Skip
|
|
98
|
+
// the caller's own session — self-destroy is a no-op guard.
|
|
99
|
+
const peerResults = await Promise.allSettled(peers.map(async (s) => {
|
|
100
|
+
if (s.playerId === callerId)
|
|
101
|
+
return { session: s, outcome: 'skipped-self' };
|
|
102
|
+
try {
|
|
103
|
+
await client.workflow.getHandle(s.workflowId).executeUpdate(signals_1.destroyUpdate, {
|
|
104
|
+
args: [{ reason: destroyReason, terminatedBy: callerId }],
|
|
105
|
+
});
|
|
106
|
+
return { session: s, outcome: 'destroyed' };
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
return { session: s, outcome: 'failed', error: (0, descriptor_1.formatError)(err) };
|
|
110
|
+
}
|
|
111
|
+
}));
|
|
112
|
+
for (const r of peerResults) {
|
|
113
|
+
if (r.status !== 'fulfilled')
|
|
114
|
+
continue;
|
|
115
|
+
const v = r.value;
|
|
116
|
+
if (v.outcome === 'destroyed') {
|
|
117
|
+
details.push({ target: v.session.playerId, outcome: 'destroyed' });
|
|
118
|
+
destroyed++;
|
|
119
|
+
}
|
|
120
|
+
else if (v.outcome === 'failed') {
|
|
121
|
+
details.push({ target: v.session.playerId, outcome: 'failed', error: v.error });
|
|
122
|
+
failed++;
|
|
123
|
+
}
|
|
124
|
+
// #299: `'skipped-self'` is an internal control-flow tag for the
|
|
125
|
+
// caller's own session — intentionally NOT surfaced in `details`
|
|
126
|
+
// because `EnsembleDestroyDetail` is consumed publicly via
|
|
127
|
+
// `EnsembleDestroySummary` (TempoClient.destroy), which has no
|
|
128
|
+
// caller-self concept. The skip is a bookkeeping no-op here.
|
|
119
129
|
}
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
client.workflow.getHandle((0, config_1.schedulerWorkflowId)(config.ensemble)).terminate(destroyReason),
|
|
131
|
-
client.workflow.getHandle((0, config_1.maestroWorkflowId)(config.ensemble)).terminate(destroyReason),
|
|
132
|
-
]);
|
|
133
|
-
if (schedRes.status === 'fulfilled') {
|
|
134
|
-
details.push({ target: 'scheduler', outcome: 'terminated' });
|
|
135
|
-
terminated++;
|
|
136
|
-
}
|
|
137
|
-
if (maestroRes.status === 'fulfilled') {
|
|
138
|
-
details.push({ target: 'maestro', outcome: 'terminated' });
|
|
139
|
-
terminated++;
|
|
140
|
-
}
|
|
141
|
-
// Phase 3: conductor last, so it observes peer teardown. Skipped if
|
|
142
|
-
// the caller IS the conductor (same self-destroy guard). #299: the
|
|
143
|
-
// skip is a control-flow no-op — no `details` entry, mirroring the
|
|
144
|
-
// peer-self skip. `EnsembleDestroyDetail.outcome` no longer carries
|
|
145
|
-
// a self-skip member.
|
|
146
|
-
if (callerId === 'conductor') {
|
|
147
|
-
// self-skip; no recording
|
|
148
|
-
}
|
|
149
|
-
else if (conductorPresent) {
|
|
150
|
-
try {
|
|
151
|
-
await client.workflow.getHandle(conductorWfId).executeUpdate(signals_1.destroyUpdate, {
|
|
152
|
-
args: [{ reason: destroyReason, terminatedBy: callerId }],
|
|
153
|
-
});
|
|
154
|
-
details.push({ target: 'conductor', outcome: 'destroyed' });
|
|
155
|
-
destroyed++;
|
|
130
|
+
// Phase 2: scheduler + maestro terminate in parallel (non-session
|
|
131
|
+
// workflows — no destroy handler). `terminate` rejects when the
|
|
132
|
+
// workflow isn't running; treat as "not present" instead of failure.
|
|
133
|
+
const [schedRes, maestroRes] = await Promise.allSettled([
|
|
134
|
+
client.workflow.getHandle((0, config_1.schedulerWorkflowId)(config.ensemble)).terminate(destroyReason),
|
|
135
|
+
client.workflow.getHandle((0, config_1.maestroWorkflowId)(config.ensemble)).terminate(destroyReason),
|
|
136
|
+
]);
|
|
137
|
+
if (schedRes.status === 'fulfilled') {
|
|
138
|
+
details.push({ target: 'scheduler', outcome: 'terminated' });
|
|
139
|
+
terminated++;
|
|
156
140
|
}
|
|
157
|
-
|
|
158
|
-
details.push({ target: '
|
|
159
|
-
|
|
141
|
+
if (maestroRes.status === 'fulfilled') {
|
|
142
|
+
details.push({ target: 'maestro', outcome: 'terminated' });
|
|
143
|
+
terminated++;
|
|
160
144
|
}
|
|
145
|
+
// Phase 3: conductor last, so it observes peer teardown. Skipped if
|
|
146
|
+
// the caller IS the conductor (same self-destroy guard). #299: the
|
|
147
|
+
// skip is a control-flow no-op — no `details` entry, mirroring the
|
|
148
|
+
// peer-self skip. `EnsembleDestroyDetail.outcome` no longer carries
|
|
149
|
+
// a self-skip member.
|
|
150
|
+
if (callerId === 'conductor') {
|
|
151
|
+
// self-skip; no recording
|
|
152
|
+
}
|
|
153
|
+
else if (conductorPresent) {
|
|
154
|
+
try {
|
|
155
|
+
await client.workflow.getHandle(conductorWfId).executeUpdate(signals_1.destroyUpdate, {
|
|
156
|
+
args: [{ reason: destroyReason, terminatedBy: callerId }],
|
|
157
|
+
});
|
|
158
|
+
details.push({ target: 'conductor', outcome: 'destroyed' });
|
|
159
|
+
destroyed++;
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
details.push({ target: 'conductor', outcome: 'failed', error: (0, descriptor_1.formatError)(err) });
|
|
163
|
+
failed++;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const summaryLine = `${destroyed} destroyed, ${terminated} terminated, ${failed} failed`;
|
|
167
|
+
const headline = failed > 0
|
|
168
|
+
? `Ensemble **${config.ensemble}** partially destroyed.`
|
|
169
|
+
: `Ensemble **${config.ensemble}** destroyed.`;
|
|
170
|
+
const lines = [headline, summaryLine];
|
|
171
|
+
const failures = details.filter((d) => d.outcome === 'failed');
|
|
172
|
+
if (failures.length > 0) {
|
|
173
|
+
lines.push(`Errors:\n${failures.map((d) => ` - ${d.target}: ${d.error}`).join('\n')}`);
|
|
174
|
+
// #306 follow-up: surface the indeterminate-state hint from my own
|
|
175
|
+
// PR-#306 holistic review (regression risk #3). `Promise.allSettled`
|
|
176
|
+
// returned `failed` outcomes for these peers — the workflows may
|
|
177
|
+
// be in any state from "still running" to "destroyed but RPC
|
|
178
|
+
// timed out". Re-running `destroy` is safe (idempotent on the
|
|
179
|
+
// workflow side: `destroyUpdate` on a `gone` workflow is a no-op
|
|
180
|
+
// via the `isDestroyedQuery` guard) and the cleanest recovery.
|
|
181
|
+
const noun = failed === 1 ? 'peer' : 'peers';
|
|
182
|
+
lines.push(`⚠ ${failed} ${noun} in indeterminate state — ` +
|
|
183
|
+
`run \`/destroy ${config.ensemble}\` again to clean up.`);
|
|
184
|
+
}
|
|
185
|
+
log(`Ensemble destroy by ${callerId}: ${summaryLine}`);
|
|
186
|
+
return (0, descriptor_1.ok)(lines.join('\n'));
|
|
161
187
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
? `Ensemble **${config.ensemble}** partially destroyed.`
|
|
165
|
-
: `Ensemble **${config.ensemble}** destroyed.`;
|
|
166
|
-
const lines = [headline, summaryLine];
|
|
167
|
-
const failures = details.filter((d) => d.outcome === 'failed');
|
|
168
|
-
if (failures.length > 0) {
|
|
169
|
-
lines.push(`Errors:\n${failures.map((d) => ` - ${d.target}: ${d.error}`).join('\n')}`);
|
|
170
|
-
// #306 follow-up: surface the indeterminate-state hint from my own
|
|
171
|
-
// PR-#306 holistic review (regression risk #3). `Promise.allSettled`
|
|
172
|
-
// returned `failed` outcomes for these peers — the workflows may
|
|
173
|
-
// be in any state from "still running" to "destroyed but RPC
|
|
174
|
-
// timed out". Re-running `destroy` is safe (idempotent on the
|
|
175
|
-
// workflow side: `destroyUpdate` on a `gone` workflow is a no-op
|
|
176
|
-
// via the `isDestroyedQuery` guard) and the cleanest recovery.
|
|
177
|
-
const noun = failed === 1 ? 'peer' : 'peers';
|
|
178
|
-
lines.push(`⚠ ${failed} ${noun} in indeterminate state — ` +
|
|
179
|
-
`run \`/destroy ${config.ensemble}\` again to clean up.`);
|
|
188
|
+
catch (err) {
|
|
189
|
+
return (0, descriptor_1.fail)(`Failed to destroy ensemble: ${(0, descriptor_1.formatError)(err)}`);
|
|
180
190
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
catch (err) {
|
|
185
|
-
return (0, helpers_1.fail)(`Failed to destroy ensemble: ${(0, helpers_1.formatError)(err)}`);
|
|
186
|
-
}
|
|
187
|
-
});
|
|
191
|
+
},
|
|
192
|
+
};
|
|
188
193
|
}
|
package/dist/tools/ensemble.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
1
|
import { Client } from '@temporalio/client';
|
|
3
2
|
import { Config } from '../config';
|
|
4
3
|
import { type EnsembleSessionInfo } from '../activities/resolve';
|
|
4
|
+
import { type TempoToolDescriptor } from './descriptor';
|
|
5
5
|
/**
|
|
6
6
|
* Default dormancy threshold (1 hour). Per #563: a `detached` player whose
|
|
7
7
|
* last activity is older than this is considered dormant. `phase === 'gone'`
|
|
@@ -29,4 +29,4 @@ export type DormantFilter = 'show' | 'hide' | 'show-only';
|
|
|
29
29
|
* Exported for unit testing.
|
|
30
30
|
*/
|
|
31
31
|
export declare function classifyDormancy(session: Pick<EnsembleSessionInfo, 'phase' | 'lastActivityAt'>, now: number, thresholdMs?: number): 'active' | 'dormant';
|
|
32
|
-
export declare function
|
|
32
|
+
export declare function buildEnsembleTool(client: Client, config: Config, getPlayerId: () => string, ownWorkflowId: string): TempoToolDescriptor;
|
package/dist/tools/ensemble.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
+
catch (err) {
|
|
102
|
+
return (0, descriptor_1.fail)(`Error listing workflows: ${(0, descriptor_1.formatError)(err)}`);
|
|
110
103
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (scope === '
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
sections
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
exports.buildEvaluateGateTool = buildEvaluateGateTool;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
-
const
|
|
5
|
+
const descriptor_1 = require("./descriptor");
|
|
6
|
+
const signals_1 = require("../workflows/signals");
|
|
6
7
|
const validation_1 = require("../utils/validation");
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
13
|
+
export declare function buildFetchStateTool(client: Client, config: Config, handle: WorkflowHandle, getPlayerId: () => string): TempoToolDescriptor;
|