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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.buildLoadLineupTool = buildLoadLineupTool;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const croner_1 = require("croner");
|
|
6
6
|
const client_1 = require("@temporalio/client");
|
|
@@ -12,370 +12,375 @@ const resolve_2 = require("../activities/resolve");
|
|
|
12
12
|
const signals_1 = require("../workflows/signals");
|
|
13
13
|
const duration_1 = require("../utils/duration");
|
|
14
14
|
const safe_path_1 = require("../utils/safe-path");
|
|
15
|
-
const
|
|
15
|
+
const descriptor_1 = require("./descriptor");
|
|
16
16
|
const validation_1 = require("../utils/validation");
|
|
17
17
|
const constants_1 = require("../constants");
|
|
18
18
|
const log = (...args) => console.error('[agent-tempo:load-lineup]', ...args);
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
name:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
// Resolve the file path: saved → shipped examples → direct file path
|
|
40
|
-
let filePath;
|
|
41
|
-
if (lineupPath) {
|
|
42
|
-
// User-provided path — validate against allowed roots
|
|
43
|
-
filePath = (0, safe_path_1.safeLineupPath)(lineupPath, process.cwd());
|
|
19
|
+
function buildLoadLineupTool(client, config, getPlayerId, ownAgentType = 'claude', handle, setPlayerId, isConductor) {
|
|
20
|
+
return {
|
|
21
|
+
name: 'load_lineup',
|
|
22
|
+
description: 'Load an ensemble lineup — recruits players and creates schedules.',
|
|
23
|
+
params: {
|
|
24
|
+
name: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional().describe('Name of a lineup — resolves saved lineups, then shipped examples (e.g. "tempo-dev-team")'),
|
|
25
|
+
path: zod_1.z.string().max(validation_1.PATH_MAX).optional().describe('Explicit file path to a lineup YAML file'),
|
|
26
|
+
hold: zod_1.z.boolean().optional().describe('When true, spawn players in "warm hold": processes start and attach but their outbox is locked, so they receive a standby message and defer their initial task until `release` is called.'),
|
|
27
|
+
initialStartup: zod_1.z.boolean().optional().describe('Issue #172: when true, the lineup was loaded as part of initial ensemble startup (`up --lineup` / `conduct --lineup`). Conductor instructions are stored as pending context and combined with the user\'s first message instead of firing immediately. Also recruits players with `hold: true`. Defaults to false — conductor-invoked mid-work `load_lineup` keeps the legacy behavior.'),
|
|
28
|
+
},
|
|
29
|
+
handler: async (args) => {
|
|
30
|
+
const lineupName = args.name;
|
|
31
|
+
const lineupPath = args.path;
|
|
32
|
+
const initialStartup = args.initialStartup === true;
|
|
33
|
+
// Initial-startup always implies warm-hold — players should wait for
|
|
34
|
+
// the conductor to decompose the user's first message before starting.
|
|
35
|
+
const hold = args.hold === true || initialStartup;
|
|
36
|
+
if (!lineupName && !lineupPath) {
|
|
37
|
+
return (0, descriptor_1.fail)('Provide either `name` (saved lineup) or `path` (file path). Exactly one is required.');
|
|
44
38
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
filePath = resolution.path;
|
|
48
|
-
// Only validate user-facing paths (saved lineups, file paths).
|
|
49
|
-
// Shipped examples are package-controlled and may live outside
|
|
50
|
-
// allowed roots when globally installed.
|
|
51
|
-
if (resolution.source !== 'shipped') {
|
|
52
|
-
filePath = (0, safe_path_1.safeLineupPath)(filePath, process.cwd());
|
|
53
|
-
}
|
|
39
|
+
if (lineupName && lineupPath) {
|
|
40
|
+
return (0, descriptor_1.fail)('Provide either `name` or `path`, not both.');
|
|
54
41
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// more heavily with the LLM — putting the "call resume_ensemble +
|
|
62
|
-
// release FIRST" framing at the top of the conductor's inbox reduces
|
|
63
|
-
// the chance the model skims past it and goes straight to broadcast.
|
|
64
|
-
// Runs independently of whether the lineup has a `conductor:` section
|
|
65
|
-
// (e.g. players-only lineups still need the banner + directive).
|
|
66
|
-
if (initialStartup && isConductor && handle) {
|
|
67
|
-
try {
|
|
68
|
-
const playerCount = lineup.players.length;
|
|
69
|
-
await handle.signal('receiveMessage', {
|
|
70
|
-
from: 'system',
|
|
71
|
-
text: (0, constants_1.ensembleReadyDirective)(lineup.name, playerCount),
|
|
72
|
-
responseRequested: false,
|
|
73
|
-
});
|
|
74
|
-
conductorActions.push('startup banner + directive delivered');
|
|
42
|
+
try {
|
|
43
|
+
// Resolve the file path: saved → shipped examples → direct file path
|
|
44
|
+
let filePath;
|
|
45
|
+
if (lineupPath) {
|
|
46
|
+
// User-provided path — validate against allowed roots
|
|
47
|
+
filePath = (0, safe_path_1.safeLineupPath)(lineupPath, process.cwd());
|
|
75
48
|
}
|
|
76
|
-
|
|
77
|
-
|
|
49
|
+
else {
|
|
50
|
+
const resolution = (0, loader_1.resolveLineupPath)(lineupName);
|
|
51
|
+
filePath = resolution.path;
|
|
52
|
+
// Only validate user-facing paths (saved lineups, file paths).
|
|
53
|
+
// Shipped examples are package-controlled and may live outside
|
|
54
|
+
// allowed roots when globally installed.
|
|
55
|
+
if (resolution.source !== 'shipped') {
|
|
56
|
+
filePath = (0, safe_path_1.safeLineupPath)(filePath, process.cwd());
|
|
57
|
+
}
|
|
78
58
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
59
|
+
const lineup = (0, agent_types_1.loadAndResolveLineup)(filePath);
|
|
60
|
+
const recruited = [];
|
|
61
|
+
const failed = [];
|
|
62
|
+
const conductorActions = [];
|
|
63
|
+
// Issue #172 follow-up: on `initialStartup`, fire the `from: 'system'`
|
|
64
|
+
// directive BEFORE any lineup instructions. Earlier messages weigh
|
|
65
|
+
// more heavily with the LLM — putting the "call resume_ensemble +
|
|
66
|
+
// release FIRST" framing at the top of the conductor's inbox reduces
|
|
67
|
+
// the chance the model skims past it and goes straight to broadcast.
|
|
68
|
+
// Runs independently of whether the lineup has a `conductor:` section
|
|
69
|
+
// (e.g. players-only lineups still need the banner + directive).
|
|
70
|
+
if (initialStartup && isConductor && handle) {
|
|
84
71
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (setPlayerId)
|
|
93
|
-
setPlayerId(lineup.conductor.name);
|
|
94
|
-
conductorActions.push(`name → ${lineup.conductor.name}`);
|
|
95
|
-
log(`Conductor name set to "${lineup.conductor.name}"`);
|
|
96
|
-
}
|
|
72
|
+
const playerCount = lineup.players.length;
|
|
73
|
+
await handle.signal('receiveMessage', {
|
|
74
|
+
from: 'system',
|
|
75
|
+
text: (0, constants_1.ensembleReadyDirective)(lineup.name, playerCount),
|
|
76
|
+
responseRequested: false,
|
|
77
|
+
});
|
|
78
|
+
conductorActions.push('startup banner + directive delivered');
|
|
97
79
|
}
|
|
98
80
|
catch (err) {
|
|
99
|
-
failed.push(`conductor
|
|
81
|
+
failed.push(`conductor startup banner: ${err}`);
|
|
100
82
|
}
|
|
101
83
|
}
|
|
102
|
-
// Apply conductor
|
|
103
|
-
if (lineup.conductor
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
84
|
+
// Apply conductor section if present and this session is the conductor
|
|
85
|
+
if (lineup.conductor && isConductor && handle) {
|
|
86
|
+
// Apply conductor name
|
|
87
|
+
if (lineup.conductor.name && lineup.conductor.name !== getPlayerId()) {
|
|
88
|
+
try {
|
|
89
|
+
// Check if the name is already taken
|
|
90
|
+
const existing = await (0, resolve_1.resolveSession)(client, config.ensemble, lineup.conductor.name);
|
|
91
|
+
if (existing && existing.workflowId !== handle.workflowId) {
|
|
92
|
+
failed.push(`conductor name "${lineup.conductor.name}": already taken by another session`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
await handle.signal('setName', lineup.conductor.name);
|
|
96
|
+
if (setPlayerId)
|
|
97
|
+
setPlayerId(lineup.conductor.name);
|
|
98
|
+
conductorActions.push(`name → ${lineup.conductor.name}`);
|
|
99
|
+
log(`Conductor name set to "${lineup.conductor.name}"`);
|
|
100
|
+
}
|
|
113
101
|
}
|
|
114
|
-
|
|
115
|
-
failed.push(`conductor
|
|
102
|
+
catch (err) {
|
|
103
|
+
failed.push(`conductor name: ${err}`);
|
|
116
104
|
}
|
|
117
105
|
}
|
|
118
|
-
|
|
119
|
-
|
|
106
|
+
// Apply conductor type (update metadata)
|
|
107
|
+
if (lineup.conductor.type) {
|
|
108
|
+
try {
|
|
109
|
+
const typeInfo = (0, agent_types_1.resolveAgentType)(lineup.conductor.type);
|
|
110
|
+
if (typeInfo) {
|
|
111
|
+
await handle.signal('updateMetadata', {
|
|
112
|
+
playerType: typeInfo.name,
|
|
113
|
+
playerTypeDescription: typeInfo.description || '',
|
|
114
|
+
});
|
|
115
|
+
conductorActions.push(`type → ${typeInfo.name}`);
|
|
116
|
+
log(`Conductor type set to "${typeInfo.name}"`);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
failed.push(`conductor type "${lineup.conductor.type}": agent type not found`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
failed.push(`conductor type: ${err}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Send conductor instructions.
|
|
127
|
+
// Issue #172 (v0.26 simplification): on the initial-startup path,
|
|
128
|
+
// signal the lineup instructions as a `receiveMessage` immediately
|
|
129
|
+
// — the ensemble-wide pause (below) stops any downstream dispatch,
|
|
130
|
+
// and the banner+directive signal (also below) tells the LLM to
|
|
131
|
+
// wait silently until the user speaks and then call
|
|
132
|
+
// `resume_ensemble` first. Legacy mid-work path also signals
|
|
133
|
+
// immediately — no branching required.
|
|
134
|
+
if (lineup.conductor.instructions) {
|
|
135
|
+
try {
|
|
136
|
+
await handle.signal('receiveMessage', {
|
|
137
|
+
from: 'lineup',
|
|
138
|
+
text: lineup.conductor.instructions,
|
|
139
|
+
responseRequested: false,
|
|
140
|
+
});
|
|
141
|
+
conductorActions.push(initialStartup ? 'instructions seeded (initial startup)' : 'instructions delivered');
|
|
142
|
+
log('Conductor instructions delivered');
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
failed.push(`conductor instructions: ${err}`);
|
|
146
|
+
}
|
|
120
147
|
}
|
|
121
148
|
}
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
// and the banner+directive signal (also below) tells the LLM to
|
|
127
|
-
// wait silently until the user speaks and then call
|
|
128
|
-
// `resume_ensemble` first. Legacy mid-work path also signals
|
|
129
|
-
// immediately — no branching required.
|
|
130
|
-
if (lineup.conductor.instructions) {
|
|
149
|
+
// Legacy hold-mode standby (conductor-invoked mid-work with hold: true).
|
|
150
|
+
// Runs ONLY on the non-initial-startup path — initialStartup handling
|
|
151
|
+
// happens earlier above, ordered before the lineup instructions.
|
|
152
|
+
if (!initialStartup && hold && isConductor && handle) {
|
|
131
153
|
try {
|
|
132
154
|
await handle.signal('receiveMessage', {
|
|
133
|
-
from: '
|
|
134
|
-
text:
|
|
155
|
+
from: 'system',
|
|
156
|
+
text: 'Ensemble is loading in hold mode — players are connecting but on standby. Wait for instructions from the user or maestro before directing the ensemble. When ready, use the `release` tool to deliver task assignments to all held players.',
|
|
135
157
|
responseRequested: false,
|
|
136
158
|
});
|
|
137
|
-
conductorActions.push(
|
|
138
|
-
log('Conductor instructions delivered');
|
|
159
|
+
conductorActions.push('hold mode standby');
|
|
139
160
|
}
|
|
140
161
|
catch (err) {
|
|
141
|
-
failed.push(`conductor
|
|
162
|
+
failed.push(`conductor hold message: ${err}`);
|
|
142
163
|
}
|
|
143
164
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
// Skip if already active — send instructions via cue instead
|
|
171
|
-
const existing = await (0, resolve_1.resolveSession)(client, config.ensemble, playerName);
|
|
172
|
-
if (existing) {
|
|
173
|
-
log(`Player "${playerName}" already active — skipping recruit`);
|
|
174
|
-
recruited.push(`${playerName} (already active)`);
|
|
175
|
-
if (player.instructions && handle) {
|
|
176
|
-
try {
|
|
177
|
-
const cueEntry = {
|
|
178
|
-
type: 'cue',
|
|
179
|
-
targetPlayerId: playerName,
|
|
180
|
-
message: player.instructions,
|
|
181
|
-
};
|
|
182
|
-
await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [cueEntry] });
|
|
183
|
-
}
|
|
184
|
-
catch (err) {
|
|
185
|
-
log(`Failed to send instructions to already-active player "${playerName}":`, err);
|
|
165
|
+
// Recruit players via outbox — no polling needed
|
|
166
|
+
for (const player of lineup.players) {
|
|
167
|
+
const playerName = player.name;
|
|
168
|
+
const workDir = player.workDir || process.cwd();
|
|
169
|
+
const agentType = player.agent === 'copilot' ? 'copilot' : 'claude';
|
|
170
|
+
const isCustomAgent = player.agent && player.agent !== 'default' && player.agent !== 'copilot';
|
|
171
|
+
const systemPrompt = player._agentDefinition ? undefined : (isCustomAgent ? player.agent : undefined);
|
|
172
|
+
const agentDefinition = player._agentDefinition;
|
|
173
|
+
const agentDefinitionPath = player._agentDefinitionPath;
|
|
174
|
+
// Skip if already active — send instructions via cue instead
|
|
175
|
+
const existing = await (0, resolve_1.resolveSession)(client, config.ensemble, playerName);
|
|
176
|
+
if (existing) {
|
|
177
|
+
log(`Player "${playerName}" already active — skipping recruit`);
|
|
178
|
+
recruited.push(`${playerName} (already active)`);
|
|
179
|
+
if (player.instructions && handle) {
|
|
180
|
+
try {
|
|
181
|
+
const cueEntry = {
|
|
182
|
+
type: 'cue',
|
|
183
|
+
targetPlayerId: playerName,
|
|
184
|
+
message: player.instructions,
|
|
185
|
+
};
|
|
186
|
+
await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [cueEntry] });
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
log(`Failed to send instructions to already-active player "${playerName}":`, err);
|
|
190
|
+
}
|
|
186
191
|
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
// Submit recruit via outbox — pre-creates workflow with pending status
|
|
195
|
+
if (!handle) {
|
|
196
|
+
failed.push(`${playerName}: load_lineup requires a workflow handle to recruit`);
|
|
197
|
+
continue;
|
|
187
198
|
}
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
// Submit recruit via outbox — pre-creates workflow with pending status
|
|
191
|
-
if (!handle) {
|
|
192
|
-
failed.push(`${playerName}: load_lineup requires a workflow handle to recruit`);
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
// Resolve full agent type info (description, nativeResolvable) if available
|
|
197
|
-
const resolvedType = agentDefinition ? (0, agent_types_1.resolveAgentType)(agentDefinition) : null;
|
|
198
|
-
const entry = {
|
|
199
|
-
type: 'recruit',
|
|
200
|
-
targetName: playerName,
|
|
201
|
-
workDir,
|
|
202
|
-
isConductor: false,
|
|
203
|
-
initialMessage: player.instructions,
|
|
204
|
-
agent: agentType,
|
|
205
|
-
systemPrompt: agentDefinition ? undefined : systemPrompt,
|
|
206
|
-
agentDefinition: resolvedType?.name || agentDefinition,
|
|
207
|
-
agentDefinitionPath: resolvedType?.path || agentDefinitionPath,
|
|
208
|
-
agentDefinitionDescription: resolvedType?.description,
|
|
209
|
-
nativeResolvable: resolvedType?.nativeResolvable,
|
|
210
|
-
allowedTools: player.allowedTools,
|
|
211
|
-
claudeBin: config.claudeBin,
|
|
212
|
-
...(hold ? { held: true } : {}),
|
|
213
|
-
};
|
|
214
|
-
await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
|
|
215
|
-
recruited.push(playerName);
|
|
216
|
-
log(`Recruit request submitted for "${playerName}" in ${workDir}`);
|
|
217
|
-
}
|
|
218
|
-
catch (err) {
|
|
219
|
-
failed.push(`${playerName}: recruit failed — ${err}`);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
// Create schedules
|
|
223
|
-
const schedulesCreated = [];
|
|
224
|
-
const scheduleWarnings = [];
|
|
225
|
-
if (lineup.schedules && lineup.schedules.length > 0) {
|
|
226
|
-
// Build valid target set from lineup player names + special values
|
|
227
|
-
const validTargets = new Set(lineup.players.map((p) => p.name));
|
|
228
|
-
validTargets.add('conductor');
|
|
229
|
-
validTargets.add('all');
|
|
230
|
-
for (const sched of lineup.schedules) {
|
|
231
199
|
try {
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
200
|
+
// Resolve full agent type info (description, nativeResolvable) if available
|
|
201
|
+
const resolvedType = agentDefinition ? (0, agent_types_1.resolveAgentType)(agentDefinition) : null;
|
|
202
|
+
const entry = {
|
|
203
|
+
type: 'recruit',
|
|
204
|
+
targetName: playerName,
|
|
205
|
+
workDir,
|
|
206
|
+
isConductor: false,
|
|
207
|
+
initialMessage: player.instructions,
|
|
208
|
+
agent: agentType,
|
|
209
|
+
systemPrompt: agentDefinition ? undefined : systemPrompt,
|
|
210
|
+
agentDefinition: resolvedType?.name || agentDefinition,
|
|
211
|
+
agentDefinitionPath: resolvedType?.path || agentDefinitionPath,
|
|
212
|
+
agentDefinitionDescription: resolvedType?.description,
|
|
213
|
+
nativeResolvable: resolvedType?.nativeResolvable,
|
|
214
|
+
allowedTools: player.allowedTools,
|
|
215
|
+
claudeBin: config.claudeBin,
|
|
216
|
+
...(hold ? { held: true } : {}),
|
|
217
|
+
};
|
|
218
|
+
await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
|
|
219
|
+
recruited.push(playerName);
|
|
220
|
+
log(`Recruit request submitted for "${playerName}" in ${workDir}`);
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
failed.push(`${playerName}: recruit failed — ${err}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Create schedules
|
|
227
|
+
const schedulesCreated = [];
|
|
228
|
+
const scheduleWarnings = [];
|
|
229
|
+
if (lineup.schedules && lineup.schedules.length > 0) {
|
|
230
|
+
// Build valid target set from lineup player names + special values
|
|
231
|
+
const validTargets = new Set(lineup.players.map((p) => p.name));
|
|
232
|
+
validTargets.add('conductor');
|
|
233
|
+
validTargets.add('all');
|
|
234
|
+
for (const sched of lineup.schedules) {
|
|
235
|
+
try {
|
|
236
|
+
// Validate schedule target against known player names
|
|
237
|
+
if (!validTargets.has(sched.target)) {
|
|
238
|
+
scheduleWarnings.push(`schedule "${sched.name}": target "${sched.target}" does not match any player in this lineup (known: ${[...validTargets].join(', ')})`);
|
|
239
|
+
}
|
|
240
|
+
const now = Date.now();
|
|
241
|
+
let nextFireAt;
|
|
242
|
+
let interval;
|
|
243
|
+
let cronExpression;
|
|
244
|
+
let timezone;
|
|
245
|
+
if (sched.at) {
|
|
246
|
+
nextFireAt = Date.parse(sched.at);
|
|
247
|
+
// Support at + every: use `at` as the initial fire time, `every` as the interval
|
|
248
|
+
if (sched.every) {
|
|
249
|
+
const ms = (0, duration_1.parseDuration)(sched.every);
|
|
250
|
+
if (!ms)
|
|
251
|
+
throw new Error(`Invalid interval: ${sched.every}`);
|
|
252
|
+
interval = ms;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else if (sched.delay) {
|
|
256
|
+
const ms = (0, duration_1.parseDuration)(sched.delay);
|
|
257
|
+
if (!ms)
|
|
258
|
+
throw new Error(`Invalid delay: ${sched.delay}`);
|
|
259
|
+
nextFireAt = now + ms;
|
|
260
|
+
}
|
|
261
|
+
else if (sched.every) {
|
|
245
262
|
const ms = (0, duration_1.parseDuration)(sched.every);
|
|
246
263
|
if (!ms)
|
|
247
264
|
throw new Error(`Invalid interval: ${sched.every}`);
|
|
265
|
+
nextFireAt = now + ms;
|
|
248
266
|
interval = ms;
|
|
249
267
|
}
|
|
268
|
+
else if (sched.cron) {
|
|
269
|
+
cronExpression = sched.cron;
|
|
270
|
+
timezone = sched.timezone || 'UTC';
|
|
271
|
+
const job = new croner_1.Cron(cronExpression, { timezone });
|
|
272
|
+
const next = job.nextRun();
|
|
273
|
+
if (!next)
|
|
274
|
+
throw new Error(`Cron expression "${sched.cron}" has no upcoming fire time`);
|
|
275
|
+
nextFireAt = next.getTime();
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
throw new Error('No timing specified');
|
|
279
|
+
}
|
|
280
|
+
const type = sched.cron ? 'cron' : (sched.every || interval) ? 'interval' : 'once';
|
|
281
|
+
const scheduleEntry = {
|
|
282
|
+
name: sched.name,
|
|
283
|
+
message: sched.message,
|
|
284
|
+
target: sched.target,
|
|
285
|
+
type,
|
|
286
|
+
nextFireAt: new Date(nextFireAt).toISOString(),
|
|
287
|
+
interval,
|
|
288
|
+
cronExpression,
|
|
289
|
+
timezone,
|
|
290
|
+
until: sched.until,
|
|
291
|
+
remainingCount: sched.count,
|
|
292
|
+
firedCount: 0,
|
|
293
|
+
createdBy: getPlayerId(),
|
|
294
|
+
};
|
|
295
|
+
const wfId = (0, config_1.schedulerWorkflowId)(config.ensemble);
|
|
296
|
+
try {
|
|
297
|
+
const handle = client.workflow.getHandle(wfId);
|
|
298
|
+
await handle.describe();
|
|
299
|
+
await handle.signal('addSchedule', scheduleEntry);
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
await client.workflow.start('agentSchedulerWorkflow', {
|
|
303
|
+
workflowId: wfId,
|
|
304
|
+
taskQueue: config.taskQueue,
|
|
305
|
+
args: [{ ensemble: config.ensemble, entries: [scheduleEntry] }],
|
|
306
|
+
workflowIdConflictPolicy: client_1.WorkflowIdConflictPolicy.USE_EXISTING,
|
|
307
|
+
searchAttributes: {
|
|
308
|
+
AgentTempoEnsemble: [config.ensemble],
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
schedulesCreated.push(sched.name);
|
|
250
313
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (!ms)
|
|
254
|
-
throw new Error(`Invalid delay: ${sched.delay}`);
|
|
255
|
-
nextFireAt = now + ms;
|
|
256
|
-
}
|
|
257
|
-
else if (sched.every) {
|
|
258
|
-
const ms = (0, duration_1.parseDuration)(sched.every);
|
|
259
|
-
if (!ms)
|
|
260
|
-
throw new Error(`Invalid interval: ${sched.every}`);
|
|
261
|
-
nextFireAt = now + ms;
|
|
262
|
-
interval = ms;
|
|
263
|
-
}
|
|
264
|
-
else if (sched.cron) {
|
|
265
|
-
cronExpression = sched.cron;
|
|
266
|
-
timezone = sched.timezone || 'UTC';
|
|
267
|
-
const job = new croner_1.Cron(cronExpression, { timezone });
|
|
268
|
-
const next = job.nextRun();
|
|
269
|
-
if (!next)
|
|
270
|
-
throw new Error(`Cron expression "${sched.cron}" has no upcoming fire time`);
|
|
271
|
-
nextFireAt = next.getTime();
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
throw new Error('No timing specified');
|
|
275
|
-
}
|
|
276
|
-
const type = sched.cron ? 'cron' : (sched.every || interval) ? 'interval' : 'once';
|
|
277
|
-
const scheduleEntry = {
|
|
278
|
-
name: sched.name,
|
|
279
|
-
message: sched.message,
|
|
280
|
-
target: sched.target,
|
|
281
|
-
type,
|
|
282
|
-
nextFireAt: new Date(nextFireAt).toISOString(),
|
|
283
|
-
interval,
|
|
284
|
-
cronExpression,
|
|
285
|
-
timezone,
|
|
286
|
-
until: sched.until,
|
|
287
|
-
remainingCount: sched.count,
|
|
288
|
-
firedCount: 0,
|
|
289
|
-
createdBy: getPlayerId(),
|
|
290
|
-
};
|
|
291
|
-
const wfId = (0, config_1.schedulerWorkflowId)(config.ensemble);
|
|
292
|
-
try {
|
|
293
|
-
const handle = client.workflow.getHandle(wfId);
|
|
294
|
-
await handle.describe();
|
|
295
|
-
await handle.signal('addSchedule', scheduleEntry);
|
|
314
|
+
catch (err) {
|
|
315
|
+
failed.push(`schedule "${sched.name}": ${err}`);
|
|
296
316
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Issue #172: on the initial-startup path, pause the whole ensemble
|
|
320
|
+
// so scheduler fires, maestro nudges, and per-session outbox dispatch
|
|
321
|
+
// are all halted while we wait for the user's first message. The
|
|
322
|
+
// conductor's `receiveMessageSignal` handler combines lineup context +
|
|
323
|
+
// user text and the combined prompt instructs Claude Code to call
|
|
324
|
+
// `resume_ensemble` BEFORE any other action. Inlines the three signals
|
|
325
|
+
// that `pause_ensemble` fires so we don't depend on the tool impl.
|
|
326
|
+
if (initialStartup && isConductor && handle) {
|
|
327
|
+
try {
|
|
328
|
+
const maestroId = (0, config_1.maestroWorkflowId)(config.ensemble);
|
|
329
|
+
await client.workflow.getHandle(maestroId).signal('maestroSetPaused', true);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// Maestro may not be running yet — fine, it will start paused once spawned.
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const schedulerId = (0, config_1.schedulerWorkflowId)(config.ensemble);
|
|
336
|
+
await client.workflow.getHandle(schedulerId).signal('setSchedulerPaused', true);
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// Scheduler may not exist yet if no schedules are defined — ignore.
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
const sessions = await (0, resolve_2.scanEnsembleSessions)(client, config.ensemble);
|
|
343
|
+
for (const session of sessions) {
|
|
344
|
+
try {
|
|
345
|
+
await client.workflow.getHandle(session.workflowId).signal('setPaused', true);
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Individual session may have just terminated — skip.
|
|
349
|
+
}
|
|
307
350
|
}
|
|
308
|
-
|
|
351
|
+
conductorActions.push('ensemble paused (awaiting first user message)');
|
|
309
352
|
}
|
|
310
353
|
catch (err) {
|
|
311
|
-
failed.push(`
|
|
354
|
+
failed.push(`pause ensemble: ${(0, descriptor_1.formatError)(err)}`);
|
|
312
355
|
}
|
|
313
356
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
// conductor's `receiveMessageSignal` handler combines lineup context +
|
|
319
|
-
// user text and the combined prompt instructs Claude Code to call
|
|
320
|
-
// `resume_ensemble` BEFORE any other action. Inlines the three signals
|
|
321
|
-
// that `pause_ensemble` fires so we don't depend on the tool impl.
|
|
322
|
-
if (initialStartup && isConductor && handle) {
|
|
323
|
-
try {
|
|
324
|
-
const maestroId = (0, config_1.maestroWorkflowId)(config.ensemble);
|
|
325
|
-
await client.workflow.getHandle(maestroId).signal('maestroSetPaused', true);
|
|
326
|
-
}
|
|
327
|
-
catch {
|
|
328
|
-
// Maestro may not be running yet — fine, it will start paused once spawned.
|
|
357
|
+
// Build summary
|
|
358
|
+
const lines = [`Loaded lineup **${lineup.name}**.`];
|
|
359
|
+
if (conductorActions.length > 0) {
|
|
360
|
+
lines.push(`Conductor: ${conductorActions.join(', ')}`);
|
|
329
361
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
try {
|
|
338
|
-
const sessions = await (0, resolve_2.scanEnsembleSessions)(client, config.ensemble);
|
|
339
|
-
for (const session of sessions) {
|
|
340
|
-
try {
|
|
341
|
-
await client.workflow.getHandle(session.workflowId).signal('setPaused', true);
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
// Individual session may have just terminated — skip.
|
|
345
|
-
}
|
|
362
|
+
if (recruited.length > 0) {
|
|
363
|
+
if (hold) {
|
|
364
|
+
lines.push(`Held: ${recruited.join(', ')} — ${recruited.length} player(s) held. Use \`release\` to start them.`);
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
lines.push(`Recruited: ${recruited.join(', ')}`);
|
|
346
368
|
}
|
|
347
|
-
conductorActions.push('ensemble paused (awaiting first user message)');
|
|
348
369
|
}
|
|
349
|
-
|
|
350
|
-
|
|
370
|
+
if (schedulesCreated.length > 0) {
|
|
371
|
+
lines.push(`Schedules created: ${schedulesCreated.join(', ')}`);
|
|
351
372
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const lines = [`Loaded lineup **${lineup.name}**.`];
|
|
355
|
-
if (conductorActions.length > 0) {
|
|
356
|
-
lines.push(`Conductor: ${conductorActions.join(', ')}`);
|
|
357
|
-
}
|
|
358
|
-
if (recruited.length > 0) {
|
|
359
|
-
if (hold) {
|
|
360
|
-
lines.push(`Held: ${recruited.join(', ')} — ${recruited.length} player(s) held. Use \`release\` to start them.`);
|
|
373
|
+
if (scheduleWarnings.length > 0) {
|
|
374
|
+
lines.push(`⚠ Schedule target warnings:\n${scheduleWarnings.map(w => ` - ${w}`).join('\n')}`);
|
|
361
375
|
}
|
|
362
|
-
|
|
363
|
-
lines.push(`
|
|
376
|
+
if (failed.length > 0) {
|
|
377
|
+
lines.push(`Failures:\n${failed.map(f => ` - ${f}`).join('\n')}`);
|
|
364
378
|
}
|
|
379
|
+
return (0, descriptor_1.ok)(lines.join('\n'));
|
|
365
380
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
if (scheduleWarnings.length > 0) {
|
|
370
|
-
lines.push(`⚠ Schedule target warnings:\n${scheduleWarnings.map(w => ` - ${w}`).join('\n')}`);
|
|
371
|
-
}
|
|
372
|
-
if (failed.length > 0) {
|
|
373
|
-
lines.push(`Failures:\n${failed.map(f => ` - ${f}`).join('\n')}`);
|
|
381
|
+
catch (err) {
|
|
382
|
+
return (0, descriptor_1.fail)(`Failed to load lineup: ${(0, descriptor_1.formatError)(err)}`);
|
|
374
383
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
catch (err) {
|
|
378
|
-
return (0, helpers_1.fail)(`Failed to load lineup: ${(0, helpers_1.formatError)(err)}`);
|
|
379
|
-
}
|
|
380
|
-
});
|
|
384
|
+
},
|
|
385
|
+
};
|
|
381
386
|
}
|