@sublang/playbook 9.0.0 → 11.0.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/README.md +1 -1
- package/docs/cli.md +109 -21
- package/docs/configuration.md +89 -40
- package/docs/embedding.md +126 -12
- package/package.json +14 -3
- package/reference/sdlc/captain.md +14 -10
- package/reference/sdlc/captain.playbook/captain.fsm.d.ts +33 -13
- package/reference/sdlc/captain.playbook/captain.fsm.js +80 -9
- package/reference/sdlc/captain.playbook/captain.fsm.ts +137 -18
- package/reference/sdlc/captain.playbook/captain.gears.md +10 -6
- package/reference/sdlc/captain.playbook/captain.playbook.d.ts +5 -1
- package/reference/sdlc/captain.playbook/captain.playbook.js +140 -10
- package/reference/sdlc/captain.playbook/captain.playbook.ts +188 -16
- package/reference/sdlc/code.md +35 -16
- package/reference/sdlc/code.playbook/bin/interactive-session.js +228 -23
- package/reference/sdlc/code.playbook/bin/launch-config.js +611 -221
- package/reference/sdlc/code.playbook/bin/playbook.js +304 -178
- package/reference/sdlc/code.playbook/bin/replay-observer.js +221 -0
- package/reference/sdlc/code.playbook/bin/repository-effects.js +2930 -0
- package/reference/sdlc/code.playbook/bin/run.js +669 -215
- package/reference/sdlc/code.playbook/bin/session-store.js +4546 -502
- package/reference/sdlc/code.playbook/code.fsm.d.ts +7 -0
- package/reference/sdlc/code.playbook/code.fsm.js +74 -25
- package/reference/sdlc/code.playbook/code.fsm.ts +83 -29
- package/reference/sdlc/code.playbook/code.gears.md +0 -2
- package/reference/sdlc/code.playbook/code.playbook.d.ts +5 -2
- package/reference/sdlc/code.playbook/code.playbook.js +54 -2
- package/reference/sdlc/code.playbook/code.playbook.ts +75 -6
- package/reference/sdlc/code.playbook/code.registry.d.ts +10 -3
- package/reference/sdlc/code.playbook/code.registry.js +10 -3
- package/reference/sdlc/code.playbook/code.registry.ts +23 -5
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +103 -8
- package/reference/sdlc/code.playbook/playbook-captain.js +1871 -75
- package/reference/sdlc/code.playbook/playbook-captain.ts +2801 -102
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +14 -10
- package/reference/sdlc/code.playbook/session-store.d.ts +82 -0
- package/reference/sdlc/code.playbook/session-store.js +113 -0
- package/reference/sdlc/decide.md +24 -16
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +7 -0
- package/reference/sdlc/decide.playbook/decide.fsm.js +80 -29
- package/reference/sdlc/decide.playbook/decide.fsm.ts +89 -31
- package/reference/sdlc/decide.playbook/decide.gears.md +0 -1
- package/reference/sdlc/decide.playbook/decide.playbook.d.ts +13 -5
- package/reference/sdlc/decide.playbook/decide.playbook.js +1712 -91
- package/reference/sdlc/decide.playbook/decide.playbook.ts +2677 -136
- package/reference/sdlc/decide.playbook/decide.registry.d.ts +7 -3
- package/reference/sdlc/decide.playbook/decide.registry.js +10 -3
- package/reference/sdlc/decide.playbook/decide.registry.ts +20 -5
- package/reference/sdlc/review.md +36 -18
- package/reference/sdlc/review.playbook/review.fsm.d.ts +7 -0
- package/reference/sdlc/review.playbook/review.fsm.js +133 -12
- package/reference/sdlc/review.playbook/review.fsm.ts +140 -12
- package/reference/sdlc/review.playbook/review.playbook.d.ts +5 -2
- package/reference/sdlc/review.playbook/review.playbook.js +65 -2
- package/reference/sdlc/review.playbook/review.playbook.ts +83 -6
- package/reference/sdlc/review.playbook/review.registry.d.ts +10 -3
- package/reference/sdlc/review.playbook/review.registry.js +10 -3
- package/reference/sdlc/review.playbook/review.registry.ts +23 -5
- package/slc/gears2fsm.md +6 -5
- package/slc/link.md +544 -41
- package/src/accepted-outcome.d.ts +18 -0
- package/src/accepted-outcome.js +94 -0
- package/src/accepted-outcome.ts +140 -0
- package/src/runtime.d.ts +164 -3
- package/src/runtime.ts +213 -2
- package/src/xstate-playbook-runtime.d.ts +149 -10
- package/src/xstate-playbook-runtime.js +2569 -270
- package/src/xstate-playbook-runtime.ts +4133 -490
- package/src/xstate-runtime.d.ts +59 -1
- package/src/xstate-runtime.js +866 -7
- package/src/xstate-runtime.ts +1397 -7
|
@@ -2,49 +2,47 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
4
4
|
|
|
5
|
-
import { spawn } from
|
|
6
|
-
import { randomUUID } from
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from
|
|
13
|
-
import { homedir, tmpdir } from 'node:os';
|
|
14
|
-
import { dirname, join, resolve } from 'node:path';
|
|
15
|
-
import { fileURLToPath } from 'node:url';
|
|
16
|
-
import { launchManagedTmuxPlay } from '@sublang/cligent/tmux-play';
|
|
17
|
-
import { stringify as stringifyYaml } from 'yaml';
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { homedir, tmpdir } from "node:os";
|
|
9
|
+
import { dirname, join, resolve } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { launchManagedTmuxPlay } from "@sublang/cligent/tmux-play";
|
|
12
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
18
13
|
import {
|
|
19
14
|
adapterSdkFailureLines,
|
|
20
15
|
checkAdapterSdks,
|
|
21
16
|
mappedSdksFor,
|
|
22
17
|
probeAdapterSdk,
|
|
23
|
-
} from
|
|
18
|
+
} from "./adapter-sdk.js";
|
|
24
19
|
import {
|
|
25
20
|
adaptersFromLaunchPlan,
|
|
26
21
|
extractWithFlags,
|
|
27
22
|
loadLaunchPlan,
|
|
28
23
|
loadSelectedLaunchPlanDataOnly,
|
|
29
24
|
projectTmuxConfig,
|
|
25
|
+
resolveLaunchSessionsDir,
|
|
26
|
+
relocateLegacyUserConfig,
|
|
27
|
+
resolveLegacyUserConfigPath,
|
|
30
28
|
resolveUserConfigPath,
|
|
31
29
|
checkReadiness,
|
|
32
|
-
} from
|
|
30
|
+
} from "./launch-config.js";
|
|
33
31
|
import {
|
|
34
32
|
createManagedInteractiveSessionCommand,
|
|
35
33
|
MANAGED_INTERACTIVE_PAYLOAD_KIND,
|
|
36
34
|
MANAGED_INTERACTIVE_PAYLOAD_SCHEMA_VERSION,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
} from './run.js';
|
|
35
|
+
publishManagedInteractiveReadinessWitness,
|
|
36
|
+
} from "./interactive-session.js";
|
|
37
|
+
import { prepareConfiguredRegistries } from "./provision.js";
|
|
38
|
+
import { executionConfigFromPlan } from "./run.js";
|
|
42
39
|
import {
|
|
43
40
|
assertCaptainSessionExecutionCompatible,
|
|
41
|
+
assertCaptainSessionsDirectoryUsable,
|
|
44
42
|
createCaptainSessionStore,
|
|
45
43
|
SESSION_ID_PATTERN,
|
|
46
44
|
validateCaptainSessionRecord,
|
|
47
|
-
} from
|
|
45
|
+
} from "./session-store.js";
|
|
48
46
|
|
|
49
47
|
// Preserve the established import surface while the CLI itself delegates to
|
|
50
48
|
// the host-neutral launch-config module.
|
|
@@ -64,9 +62,8 @@ export {
|
|
|
64
62
|
normalizeLaunchPlan,
|
|
65
63
|
projectTmuxConfig,
|
|
66
64
|
resolveAgent,
|
|
67
|
-
resolveConfigHome,
|
|
68
65
|
resolveUserConfigPath,
|
|
69
|
-
} from
|
|
66
|
+
} from "./launch-config.js";
|
|
70
67
|
|
|
71
68
|
const READINESS_FAILURE_EXIT_CODE = 2;
|
|
72
69
|
const COMPOSITION_FAILURE_EXIT_CODE = 1;
|
|
@@ -77,22 +74,26 @@ export async function runPlaybookCli(options = {}) {
|
|
|
77
74
|
const stdout = options.stdout ?? process.stdout;
|
|
78
75
|
const stderr = options.stderr ?? process.stderr;
|
|
79
76
|
const loadModule = options.loadModule ?? ((specifier) => import(specifier));
|
|
80
|
-
const home =
|
|
77
|
+
const home =
|
|
78
|
+
options.homeDir ??
|
|
79
|
+
(typeof env.HOME === "string" && env.HOME.trim().length > 0
|
|
80
|
+
? env.HOME
|
|
81
|
+
: homedir());
|
|
81
82
|
const userConfigPath =
|
|
82
83
|
options.userConfigPath ?? resolveUserConfigPath(env, home);
|
|
83
84
|
|
|
84
85
|
// PBCLI-18: `playbook run ...` is the non-interactive presentation of the
|
|
85
86
|
// same generic-config Captain session. It never resolves or launches the
|
|
86
87
|
// tmux presenter, but it receives the launch inputs shared with this host.
|
|
87
|
-
if (argv[0] ===
|
|
88
|
-
const { runPlaybookRun } = await import(
|
|
88
|
+
if (argv[0] === "run") {
|
|
89
|
+
const { runPlaybookRun } = await import("./run.js");
|
|
89
90
|
return await runPlaybookRun({
|
|
90
91
|
argv: argv.slice(1),
|
|
91
92
|
stdout,
|
|
92
93
|
stderr,
|
|
93
94
|
env,
|
|
94
95
|
homeDir: home,
|
|
95
|
-
userConfigPath,
|
|
96
|
+
...(options.userConfigPath === undefined ? {} : { userConfigPath }),
|
|
96
97
|
...(options.cwd ? { cwd: options.cwd } : {}),
|
|
97
98
|
...(options.loadModule ? { loadModule: options.loadModule } : {}),
|
|
98
99
|
...(options.readStdin ? { readStdin: options.readStdin } : {}),
|
|
@@ -115,9 +116,12 @@ export async function runPlaybookCli(options = {}) {
|
|
|
115
116
|
...(options.createHostRuntime
|
|
116
117
|
? { createHostRuntime: options.createHostRuntime }
|
|
117
118
|
: {}),
|
|
118
|
-
...(options.
|
|
119
|
-
? {
|
|
119
|
+
...(options.createEffectLedgerWriteAhead
|
|
120
|
+
? {
|
|
121
|
+
createEffectLedgerWriteAhead: options.createEffectLedgerWriteAhead,
|
|
122
|
+
}
|
|
120
123
|
: {}),
|
|
124
|
+
...(options.sessionStore ? { sessionStore: options.sessionStore } : {}),
|
|
121
125
|
...(options.sessionsDir ? { sessionsDir: options.sessionsDir } : {}),
|
|
122
126
|
...(options.now ? { now: options.now } : {}),
|
|
123
127
|
...(options.createSessionTempId
|
|
@@ -126,6 +130,12 @@ export async function runPlaybookCli(options = {}) {
|
|
|
126
130
|
...(options.createAttemptId
|
|
127
131
|
? { createAttemptId: options.createAttemptId }
|
|
128
132
|
: {}),
|
|
133
|
+
...(options.installRetainedGenerationsForLaunch
|
|
134
|
+
? {
|
|
135
|
+
installRetainedGenerationsForLaunch:
|
|
136
|
+
options.installRetainedGenerationsForLaunch,
|
|
137
|
+
}
|
|
138
|
+
: {}),
|
|
129
139
|
...(options.signal ? { signal: options.signal } : {}),
|
|
130
140
|
// PBCLI-39: the run path gates on SDK availability too.
|
|
131
141
|
...(options.probeAdapterSdk
|
|
@@ -145,7 +155,7 @@ export async function runPlaybookCli(options = {}) {
|
|
|
145
155
|
|
|
146
156
|
// PBCLI-6: `--help` / `-h` print help and exit 0 without seeding,
|
|
147
157
|
// composing, or launching.
|
|
148
|
-
if (argv.includes(
|
|
158
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
149
159
|
stdout.write(helpText({ userConfigPath }));
|
|
150
160
|
return { code: 0 };
|
|
151
161
|
}
|
|
@@ -163,17 +173,17 @@ export async function runPlaybookCli(options = {}) {
|
|
|
163
173
|
}
|
|
164
174
|
if (withPaths.length > 0 && hasExplicitConfig(argv)) {
|
|
165
175
|
stderr.write(
|
|
166
|
-
|
|
167
|
-
|
|
176
|
+
"playbook: --with overlays the top-level config and cannot combine " +
|
|
177
|
+
"with a raw --config launch\n",
|
|
168
178
|
);
|
|
169
179
|
return { code: COMPOSITION_FAILURE_EXIT_CODE };
|
|
170
180
|
}
|
|
171
|
-
const noProvision = forwardArgv.includes(
|
|
172
|
-
forwardArgv = forwardArgv.filter((arg) => arg !==
|
|
181
|
+
const noProvision = forwardArgv.includes("--no-provision");
|
|
182
|
+
forwardArgv = forwardArgv.filter((arg) => arg !== "--no-provision");
|
|
173
183
|
if (noProvision && hasExplicitConfig(argv)) {
|
|
174
184
|
stderr.write(
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
"playbook: --no-provision applies to configured registry preparation " +
|
|
186
|
+
"and cannot combine with a raw --config launch\n",
|
|
177
187
|
);
|
|
178
188
|
return { code: COMPOSITION_FAILURE_EXIT_CODE };
|
|
179
189
|
}
|
|
@@ -198,10 +208,49 @@ export async function runPlaybookCli(options = {}) {
|
|
|
198
208
|
return { code: COMPOSITION_FAILURE_EXIT_CODE };
|
|
199
209
|
}
|
|
200
210
|
|
|
211
|
+
// DR-043: only a validated managed launch may relocate the legacy config.
|
|
212
|
+
// Help and raw --config return above without changing either config path;
|
|
213
|
+
// the delegated `run` front end applies the same boundary independently.
|
|
214
|
+
if (options.userConfigPath === undefined) {
|
|
215
|
+
try {
|
|
216
|
+
relocateLegacyUserConfig(
|
|
217
|
+
userConfigPath,
|
|
218
|
+
resolveLegacyUserConfigPath(env, home),
|
|
219
|
+
(line) => stderr.write(line),
|
|
220
|
+
);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
stderr.write(`playbook: ${errorMessage(error)}\n`);
|
|
223
|
+
return { code: COMPOSITION_FAILURE_EXIT_CODE };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
201
227
|
const launchCwd = resolve(
|
|
202
228
|
options.cwd ?? process.cwd(),
|
|
203
|
-
interactiveArgs.cwd ??
|
|
229
|
+
interactiveArgs.cwd ?? ".",
|
|
204
230
|
);
|
|
231
|
+
let resolvedSessionsDir;
|
|
232
|
+
if (options.sessionStore === undefined) {
|
|
233
|
+
try {
|
|
234
|
+
resolvedSessionsDir = resolveLaunchSessionsDir({
|
|
235
|
+
userConfigPath,
|
|
236
|
+
overlayPaths: withPaths,
|
|
237
|
+
env,
|
|
238
|
+
homeDir: home,
|
|
239
|
+
...(options.sessionsDir !== undefined
|
|
240
|
+
? { sessionsDir: options.sessionsDir }
|
|
241
|
+
: {}),
|
|
242
|
+
preparePrimary: interactiveArgs.sessionId === undefined,
|
|
243
|
+
onNotice: (line) => stderr.write(line),
|
|
244
|
+
});
|
|
245
|
+
// PBCLI-78: listing validates the locator but never consumes the store.
|
|
246
|
+
if (!interactiveArgs.list) {
|
|
247
|
+
await assertCaptainSessionsDirectoryUsable(resolvedSessionsDir);
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
stderr.write(`playbook: ${errorMessage(error)}\n`);
|
|
251
|
+
return { code: COMPOSITION_FAILURE_EXIT_CODE };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
205
254
|
// PBCLI-49: selected planning is deliberately provisional. It narrows the
|
|
206
255
|
// current config before preparation; the pane child later acquires the
|
|
207
256
|
// lease and repeats the authoritative read before any host/import work.
|
|
@@ -209,15 +258,49 @@ export async function runPlaybookCli(options = {}) {
|
|
|
209
258
|
let selectedRecord;
|
|
210
259
|
if (interactiveArgs.sessionId !== undefined) {
|
|
211
260
|
try {
|
|
212
|
-
store = createInteractiveStore(options, env, home);
|
|
261
|
+
store = createInteractiveStore(options, env, home, resolvedSessionsDir);
|
|
213
262
|
selectedRecord = validateCaptainSessionRecord(
|
|
214
263
|
await store.read(interactiveArgs.sessionId),
|
|
215
264
|
);
|
|
216
|
-
|
|
265
|
+
const needsAbandonmentRecovery =
|
|
266
|
+
(selectedRecord.state === "uncertain" &&
|
|
267
|
+
Object.hasOwn(selectedRecord.uncertain, "abandonment")) ||
|
|
268
|
+
(selectedRecord.state === "settled" &&
|
|
269
|
+
Object.hasOwn(selectedRecord, "settledAbandonment"));
|
|
270
|
+
if (selectedRecord.state !== "settled" && !needsAbandonmentRecovery) {
|
|
217
271
|
throw new Error(
|
|
218
272
|
`Captain session ${JSON.stringify(interactiveArgs.sessionId)} has an uncertain turn; recover it with playbook run before reopening interactively`,
|
|
219
273
|
);
|
|
220
274
|
}
|
|
275
|
+
if (needsAbandonmentRecovery) {
|
|
276
|
+
const recoveryLease = await store.acquire(interactiveArgs.sessionId);
|
|
277
|
+
let recoveryError;
|
|
278
|
+
try {
|
|
279
|
+
selectedRecord = validateCaptainSessionRecord(
|
|
280
|
+
await recoveryLease.recoverUnresolvedEffectAbandonment(),
|
|
281
|
+
);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
recoveryError = error;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
await recoveryLease.release();
|
|
287
|
+
} catch (error) {
|
|
288
|
+
if (recoveryError !== undefined) {
|
|
289
|
+
throw aggregateOperationalFailures(
|
|
290
|
+
recoveryError,
|
|
291
|
+
error,
|
|
292
|
+
"Captain session abandonment recovery failed and its lease could not be released",
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
if (recoveryError !== undefined) throw recoveryError;
|
|
298
|
+
if (selectedRecord.state !== "settled") {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`Captain session ${JSON.stringify(interactiveArgs.sessionId)} abandonment recovery did not settle its turn`,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
221
304
|
} catch (error) {
|
|
222
305
|
stderr.write(`playbook: ${errorMessage(error)}\n`);
|
|
223
306
|
return { code: COMPOSITION_FAILURE_EXIT_CODE };
|
|
@@ -243,7 +326,7 @@ export async function runPlaybookCli(options = {}) {
|
|
|
243
326
|
enabled: !noProvision,
|
|
244
327
|
stderr,
|
|
245
328
|
hostRoots: options.hostRoots,
|
|
246
|
-
commandName:
|
|
329
|
+
commandName: "playbook",
|
|
247
330
|
}),
|
|
248
331
|
onNotice: (line) => stderr.write(line),
|
|
249
332
|
});
|
|
@@ -310,7 +393,7 @@ export async function runPlaybookCli(options = {}) {
|
|
|
310
393
|
spawnFn,
|
|
311
394
|
[
|
|
312
395
|
tmuxPlayBin,
|
|
313
|
-
|
|
396
|
+
"--config",
|
|
314
397
|
composedPath,
|
|
315
398
|
...interactiveArgs.diagnosticArgv,
|
|
316
399
|
],
|
|
@@ -322,7 +405,7 @@ export async function runPlaybookCli(options = {}) {
|
|
|
322
405
|
}
|
|
323
406
|
|
|
324
407
|
try {
|
|
325
|
-
store ??= createInteractiveStore(options, env, home);
|
|
408
|
+
store ??= createInteractiveStore(options, env, home, resolvedSessionsDir);
|
|
326
409
|
} catch (error) {
|
|
327
410
|
stderr.write(`playbook: ${errorMessage(error)}\n`);
|
|
328
411
|
return { code: COMPOSITION_FAILURE_EXIT_CODE };
|
|
@@ -341,7 +424,10 @@ export async function runPlaybookCli(options = {}) {
|
|
|
341
424
|
);
|
|
342
425
|
} else {
|
|
343
426
|
sessionId = (options.createLogicalSessionId ?? randomUUID)();
|
|
344
|
-
if (
|
|
427
|
+
if (
|
|
428
|
+
typeof sessionId !== "string" ||
|
|
429
|
+
!SESSION_ID_PATTERN.test(sessionId)
|
|
430
|
+
) {
|
|
345
431
|
throw new Error(
|
|
346
432
|
`logical session id generator returned a non-UUID value: ${JSON.stringify(sessionId)}`,
|
|
347
433
|
);
|
|
@@ -358,6 +444,7 @@ export async function runPlaybookCli(options = {}) {
|
|
|
358
444
|
projectTmuxConfig(plan),
|
|
359
445
|
);
|
|
360
446
|
let prepared;
|
|
447
|
+
let managedChildWorkDir;
|
|
361
448
|
try {
|
|
362
449
|
throwIfSignalAborted(options.signal);
|
|
363
450
|
prepared = await awaitManagedPreparation(
|
|
@@ -372,13 +459,19 @@ export async function runPlaybookCli(options = {}) {
|
|
|
372
459
|
...(options.adapterImports
|
|
373
460
|
? { adapterImports: options.adapterImports }
|
|
374
461
|
: {}),
|
|
375
|
-
createSessionCommand: (context) =>
|
|
376
|
-
|
|
462
|
+
createSessionCommand: (context) => {
|
|
463
|
+
if (managedChildWorkDir !== undefined) {
|
|
464
|
+
throw new Error(
|
|
465
|
+
"managed tmux-play requested more than one session command",
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
managedChildWorkDir = context.workDir;
|
|
469
|
+
return createManagedInteractiveSessionCommand(
|
|
377
470
|
context,
|
|
378
471
|
{
|
|
379
472
|
schemaVersion: MANAGED_INTERACTIVE_PAYLOAD_SCHEMA_VERSION,
|
|
380
473
|
kind: MANAGED_INTERACTIVE_PAYLOAD_KIND,
|
|
381
|
-
mode: selectedRecord ?
|
|
474
|
+
mode: selectedRecord ? "selected" : "fresh",
|
|
382
475
|
sessionId,
|
|
383
476
|
cwd,
|
|
384
477
|
sessionsDir: store.sessionsDir,
|
|
@@ -389,20 +482,43 @@ export async function runPlaybookCli(options = {}) {
|
|
|
389
482
|
selfBin:
|
|
390
483
|
options.managedSessionBin ??
|
|
391
484
|
fileURLToPath(
|
|
392
|
-
new URL(
|
|
485
|
+
new URL("./interactive-session.js", import.meta.url),
|
|
393
486
|
),
|
|
394
487
|
...(options.execPath ? { execPath: options.execPath } : {}),
|
|
395
488
|
},
|
|
396
|
-
)
|
|
489
|
+
);
|
|
490
|
+
},
|
|
397
491
|
}),
|
|
398
492
|
options.signal,
|
|
399
493
|
);
|
|
400
494
|
if (prepared?.sessionId !== sessionId) {
|
|
401
495
|
await cancelPreparedAfterFailure(
|
|
402
496
|
prepared,
|
|
403
|
-
new Error(
|
|
497
|
+
new Error("managed tmux-play prepared a mismatched session id"),
|
|
404
498
|
);
|
|
405
499
|
}
|
|
500
|
+
if (managedChildWorkDir === undefined) {
|
|
501
|
+
await cancelPreparedAfterFailure(
|
|
502
|
+
prepared,
|
|
503
|
+
new Error("managed tmux-play did not request a session command"),
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
if (prepared?.workDir !== managedChildWorkDir) {
|
|
507
|
+
await cancelPreparedAfterFailure(
|
|
508
|
+
prepared,
|
|
509
|
+
new Error("managed tmux-play prepared a mismatched work directory"),
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
if (!selectedRecord) {
|
|
513
|
+
try {
|
|
514
|
+
await (
|
|
515
|
+
options.publishManagedReadinessWitness ??
|
|
516
|
+
publishManagedInteractiveReadinessWitness
|
|
517
|
+
)(managedChildWorkDir, sessionId);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
await cancelPreparedAfterFailure(prepared, error);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
406
522
|
try {
|
|
407
523
|
await writeStream(
|
|
408
524
|
stderr,
|
|
@@ -421,7 +537,9 @@ export async function runPlaybookCli(options = {}) {
|
|
|
421
537
|
});
|
|
422
538
|
return { code: 0 };
|
|
423
539
|
} catch (error) {
|
|
424
|
-
stderr.write(
|
|
540
|
+
stderr.write(
|
|
541
|
+
`playbook: failed to launch managed session: ${errorMessage(error)}\n`,
|
|
542
|
+
);
|
|
425
543
|
return { code: COMPOSITION_FAILURE_EXIT_CODE };
|
|
426
544
|
} finally {
|
|
427
545
|
rmSync(tempDir, { recursive: true, force: true });
|
|
@@ -429,21 +547,23 @@ export async function runPlaybookCli(options = {}) {
|
|
|
429
547
|
}
|
|
430
548
|
|
|
431
549
|
export function parseInteractiveArgs(argv) {
|
|
432
|
-
if (argv.includes(
|
|
433
|
-
if (argv.filter((arg) => arg ===
|
|
434
|
-
throw new Error(
|
|
550
|
+
if (argv.includes("--theme-diagnostics")) {
|
|
551
|
+
if (argv.filter((arg) => arg === "--theme-diagnostics").length > 1) {
|
|
552
|
+
throw new Error("--theme-diagnostics was repeated");
|
|
435
553
|
}
|
|
436
|
-
if (
|
|
437
|
-
|
|
554
|
+
if (
|
|
555
|
+
argv.some((arg) => arg === "--session" || arg.startsWith("--session="))
|
|
556
|
+
) {
|
|
557
|
+
throw new Error("--session cannot combine with --theme-diagnostics");
|
|
438
558
|
}
|
|
439
|
-
if (argv.includes(
|
|
440
|
-
throw new Error(
|
|
559
|
+
if (argv.includes("--list")) {
|
|
560
|
+
throw new Error("--list cannot combine with --theme-diagnostics");
|
|
441
561
|
}
|
|
442
562
|
const recoveryArg = argv.find(
|
|
443
563
|
(arg) =>
|
|
444
|
-
arg ===
|
|
445
|
-
arg ===
|
|
446
|
-
arg ===
|
|
564
|
+
arg === "--continue" ||
|
|
565
|
+
arg === "--retry-uncertain" ||
|
|
566
|
+
arg === "--discard-uncertain",
|
|
447
567
|
);
|
|
448
568
|
if (recoveryArg !== undefined) {
|
|
449
569
|
throw new Error(
|
|
@@ -464,32 +584,34 @@ export function parseInteractiveArgs(argv) {
|
|
|
464
584
|
let list = false;
|
|
465
585
|
for (let index = 0; index < argv.length; index += 1) {
|
|
466
586
|
const arg = argv[index];
|
|
467
|
-
if (arg ===
|
|
468
|
-
if (list) throw new Error(
|
|
587
|
+
if (arg === "--list") {
|
|
588
|
+
if (list) throw new Error("--list was repeated");
|
|
469
589
|
list = true;
|
|
470
590
|
continue;
|
|
471
591
|
}
|
|
472
|
-
if (arg ===
|
|
592
|
+
if (arg === "--session" || arg.startsWith("--session=")) {
|
|
473
593
|
if (sessionId !== undefined) {
|
|
474
|
-
throw new Error(
|
|
594
|
+
throw new Error(
|
|
595
|
+
"interactive --session selector was repeated or combined",
|
|
596
|
+
);
|
|
475
597
|
}
|
|
476
|
-
sessionId = optionValue(argv, index,
|
|
477
|
-
if (arg ===
|
|
598
|
+
sessionId = optionValue(argv, index, "--session");
|
|
599
|
+
if (arg === "--session") index += 1;
|
|
478
600
|
if (!SESSION_ID_PATTERN.test(sessionId)) {
|
|
479
|
-
throw new Error(
|
|
601
|
+
throw new Error("--session requires a canonical lowercase UUID");
|
|
480
602
|
}
|
|
481
603
|
continue;
|
|
482
604
|
}
|
|
483
|
-
if (arg ===
|
|
484
|
-
if (cwd !== undefined) throw new Error(
|
|
485
|
-
cwd = optionValue(argv, index,
|
|
486
|
-
if (arg ===
|
|
605
|
+
if (arg === "--cwd" || arg.startsWith("--cwd=")) {
|
|
606
|
+
if (cwd !== undefined) throw new Error("interactive --cwd was repeated");
|
|
607
|
+
cwd = optionValue(argv, index, "--cwd");
|
|
608
|
+
if (arg === "--cwd") index += 1;
|
|
487
609
|
continue;
|
|
488
610
|
}
|
|
489
611
|
if (
|
|
490
|
-
arg ===
|
|
491
|
-
arg ===
|
|
492
|
-
arg ===
|
|
612
|
+
arg === "--continue" ||
|
|
613
|
+
arg === "--retry-uncertain" ||
|
|
614
|
+
arg === "--discard-uncertain"
|
|
493
615
|
) {
|
|
494
616
|
throw new Error(
|
|
495
617
|
`${arg} is headless recovery syntax; use playbook run with an explicit session`,
|
|
@@ -501,14 +623,16 @@ export function parseInteractiveArgs(argv) {
|
|
|
501
623
|
}
|
|
502
624
|
if (sessionId !== undefined && cwd !== undefined) {
|
|
503
625
|
throw new Error(
|
|
504
|
-
|
|
626
|
+
"interactive --cwd cannot combine with --session; the stored working directory is authoritative",
|
|
505
627
|
);
|
|
506
628
|
}
|
|
507
629
|
if (sessionId !== undefined && list) {
|
|
508
|
-
throw new Error(
|
|
630
|
+
throw new Error("--session cannot combine with --list");
|
|
509
631
|
}
|
|
510
632
|
if (list && cwd !== undefined) {
|
|
511
|
-
throw new Error(
|
|
633
|
+
throw new Error(
|
|
634
|
+
"--cwd applies to a fresh launch and cannot combine with --list",
|
|
635
|
+
);
|
|
512
636
|
}
|
|
513
637
|
return Object.freeze({
|
|
514
638
|
sessionId,
|
|
@@ -529,7 +653,7 @@ export function parseInteractiveArgs(argv) {
|
|
|
529
653
|
export async function runPlaybookCliEntry(options = {}) {
|
|
530
654
|
const processLike = options.processLike ?? process;
|
|
531
655
|
const entryArgv = options.argv ?? processLike.argv?.slice(2) ?? [];
|
|
532
|
-
if (entryArgv[0] !==
|
|
656
|
+
if (entryArgv[0] !== "run" && !isManagedInteractiveInvocation(entryArgv)) {
|
|
533
657
|
return runPlaybookCli(options);
|
|
534
658
|
}
|
|
535
659
|
const controller = new AbortController();
|
|
@@ -541,7 +665,7 @@ export async function runPlaybookCliEntry(options = {}) {
|
|
|
541
665
|
processLike.off(signal, handler);
|
|
542
666
|
}
|
|
543
667
|
};
|
|
544
|
-
for (const signal of [
|
|
668
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
545
669
|
handlers[signal] = () => {
|
|
546
670
|
if (receivedSignal !== undefined) {
|
|
547
671
|
removeHandlers();
|
|
@@ -575,33 +699,33 @@ export async function runPlaybookCliEntry(options = {}) {
|
|
|
575
699
|
|
|
576
700
|
function isManagedInteractiveInvocation(argv) {
|
|
577
701
|
return (
|
|
578
|
-
!argv.includes(
|
|
579
|
-
!argv.includes(
|
|
580
|
-
!argv.includes(
|
|
581
|
-
!argv.includes(
|
|
702
|
+
!argv.includes("--help") &&
|
|
703
|
+
!argv.includes("-h") &&
|
|
704
|
+
!argv.includes("--list") &&
|
|
705
|
+
!argv.includes("--theme-diagnostics") &&
|
|
582
706
|
!hasExplicitConfig(argv)
|
|
583
707
|
);
|
|
584
708
|
}
|
|
585
709
|
|
|
586
710
|
function writeComposedConfig(composed) {
|
|
587
|
-
const dir = mkdtempSync(join(tmpdir(),
|
|
588
|
-
const path = join(dir,
|
|
711
|
+
const dir = mkdtempSync(join(tmpdir(), "playbook-"));
|
|
712
|
+
const path = join(dir, "tmux-play.config.yaml");
|
|
589
713
|
writeFileSync(path, stringifyYaml(composed));
|
|
590
714
|
return { dir, path };
|
|
591
715
|
}
|
|
592
716
|
|
|
593
717
|
function hasExplicitConfig(argv) {
|
|
594
|
-
return argv.some((arg) => arg ===
|
|
718
|
+
return argv.some((arg) => arg === "--config" || arg.startsWith("--config="));
|
|
595
719
|
}
|
|
596
720
|
|
|
597
721
|
function assertRawConfigHasNoManagedSelector(argv) {
|
|
598
722
|
const managed = argv.find(
|
|
599
723
|
(arg) =>
|
|
600
|
-
arg ===
|
|
601
|
-
arg.startsWith(
|
|
602
|
-
arg ===
|
|
603
|
-
arg ===
|
|
604
|
-
arg ===
|
|
724
|
+
arg === "--session" ||
|
|
725
|
+
arg.startsWith("--session=") ||
|
|
726
|
+
arg === "--continue" ||
|
|
727
|
+
arg === "--retry-uncertain" ||
|
|
728
|
+
arg === "--discard-uncertain",
|
|
605
729
|
);
|
|
606
730
|
if (managed !== undefined) {
|
|
607
731
|
throw new Error(
|
|
@@ -612,21 +736,20 @@ function assertRawConfigHasNoManagedSelector(argv) {
|
|
|
612
736
|
|
|
613
737
|
function optionValue(argv, index, name) {
|
|
614
738
|
const arg = argv[index];
|
|
615
|
-
const value =
|
|
616
|
-
|
|
617
|
-
if (typeof value !== 'string' || value.length === 0) {
|
|
739
|
+
const value = arg === name ? argv[index + 1] : arg.slice(`${name}=`.length);
|
|
740
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
618
741
|
throw new Error(`${name} requires a value`);
|
|
619
742
|
}
|
|
620
743
|
return value;
|
|
621
744
|
}
|
|
622
745
|
|
|
623
|
-
function createInteractiveStore(options, env, home) {
|
|
746
|
+
function createInteractiveStore(options, env, home, sessionsDir) {
|
|
624
747
|
return (
|
|
625
748
|
options.sessionStore ??
|
|
626
749
|
createCaptainSessionStore({
|
|
627
750
|
env,
|
|
628
751
|
homeDir: home,
|
|
629
|
-
|
|
752
|
+
sessionsDir,
|
|
630
753
|
...(options.now ? { now: options.now } : {}),
|
|
631
754
|
...(options.createSessionTempId
|
|
632
755
|
? { createTempId: options.createSessionTempId }
|
|
@@ -638,12 +761,12 @@ function createInteractiveStore(options, env, home) {
|
|
|
638
761
|
async function writeStream(stream, text, signal) {
|
|
639
762
|
throwIfSignalAborted(signal);
|
|
640
763
|
const ready = stream.write(text);
|
|
641
|
-
if (ready !== false || typeof stream.once !==
|
|
764
|
+
if (ready !== false || typeof stream.once !== "function") return;
|
|
642
765
|
await new Promise((resolvePromise, rejectPromise) => {
|
|
643
766
|
const cleanup = () => {
|
|
644
|
-
stream.off?.(
|
|
645
|
-
stream.off?.(
|
|
646
|
-
signal?.removeEventListener(
|
|
767
|
+
stream.off?.("drain", onDrain);
|
|
768
|
+
stream.off?.("error", onError);
|
|
769
|
+
signal?.removeEventListener("abort", onAbort);
|
|
647
770
|
};
|
|
648
771
|
const onDrain = () => {
|
|
649
772
|
cleanup();
|
|
@@ -655,11 +778,11 @@ async function writeStream(stream, text, signal) {
|
|
|
655
778
|
};
|
|
656
779
|
const onAbort = () => {
|
|
657
780
|
cleanup();
|
|
658
|
-
rejectPromise(signal.reason ?? new Error(
|
|
781
|
+
rejectPromise(signal.reason ?? new Error("operation aborted"));
|
|
659
782
|
};
|
|
660
|
-
stream.once(
|
|
661
|
-
stream.once(
|
|
662
|
-
signal?.addEventListener(
|
|
783
|
+
stream.once("drain", onDrain);
|
|
784
|
+
stream.once("error", onError);
|
|
785
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
663
786
|
if (signal?.aborted) onAbort();
|
|
664
787
|
});
|
|
665
788
|
}
|
|
@@ -671,22 +794,22 @@ async function awaitManagedPreparation(start, signal) {
|
|
|
671
794
|
|
|
672
795
|
let onAbort;
|
|
673
796
|
const aborted = new Promise((resolvePromise) => {
|
|
674
|
-
onAbort = () => resolvePromise({ type:
|
|
675
|
-
signal.addEventListener(
|
|
797
|
+
onAbort = () => resolvePromise({ type: "aborted" });
|
|
798
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
676
799
|
if (signal.aborted) onAbort();
|
|
677
800
|
});
|
|
678
801
|
const outcome = await Promise.race([
|
|
679
802
|
launch.then(
|
|
680
|
-
(value) => ({ type:
|
|
681
|
-
(error) => ({ type:
|
|
803
|
+
(value) => ({ type: "prepared", value }),
|
|
804
|
+
(error) => ({ type: "failed", error }),
|
|
682
805
|
),
|
|
683
806
|
aborted,
|
|
684
807
|
]);
|
|
685
|
-
signal.removeEventListener(
|
|
686
|
-
if (outcome.type ===
|
|
687
|
-
if (outcome.type ===
|
|
808
|
+
signal.removeEventListener("abort", onAbort);
|
|
809
|
+
if (outcome.type === "prepared") return outcome.value;
|
|
810
|
+
if (outcome.type === "failed") throw outcome.error;
|
|
688
811
|
|
|
689
|
-
const abortError = signal.reason ?? new Error(
|
|
812
|
+
const abortError = signal.reason ?? new Error("operation aborted");
|
|
690
813
|
let latePrepared;
|
|
691
814
|
try {
|
|
692
815
|
latePrepared = await launch;
|
|
@@ -694,7 +817,7 @@ async function awaitManagedPreparation(start, signal) {
|
|
|
694
817
|
throw aggregateOperationalFailures(
|
|
695
818
|
abortError,
|
|
696
819
|
launchError,
|
|
697
|
-
|
|
820
|
+
"managed tmux-play preparation failed while retiring an aborted launch",
|
|
698
821
|
);
|
|
699
822
|
}
|
|
700
823
|
await cancelPreparedAfterFailure(latePrepared, abortError);
|
|
@@ -704,14 +827,16 @@ async function cancelPreparedIfAborted(prepared, signal) {
|
|
|
704
827
|
if (!signal?.aborted) return;
|
|
705
828
|
await cancelPreparedAfterFailure(
|
|
706
829
|
prepared,
|
|
707
|
-
signal.reason ?? new Error(
|
|
830
|
+
signal.reason ?? new Error("operation aborted"),
|
|
708
831
|
);
|
|
709
832
|
}
|
|
710
833
|
|
|
711
834
|
async function cancelPreparedAfterFailure(prepared, primary) {
|
|
712
835
|
try {
|
|
713
|
-
if (typeof prepared?.cancel !==
|
|
714
|
-
throw new Error(
|
|
836
|
+
if (typeof prepared?.cancel !== "function") {
|
|
837
|
+
throw new Error(
|
|
838
|
+
"managed tmux-play preparation has no cancellation boundary",
|
|
839
|
+
);
|
|
715
840
|
}
|
|
716
841
|
await prepared.cancel();
|
|
717
842
|
} catch (cancelError) {
|
|
@@ -733,7 +858,7 @@ function aggregateOperationalFailures(primary, secondary, summary) {
|
|
|
733
858
|
|
|
734
859
|
function throwIfSignalAborted(signal) {
|
|
735
860
|
if (signal?.aborted) {
|
|
736
|
-
throw signal.reason ?? new Error(
|
|
861
|
+
throw signal.reason ?? new Error("operation aborted");
|
|
737
862
|
}
|
|
738
863
|
}
|
|
739
864
|
|
|
@@ -744,76 +869,77 @@ function helpText({
|
|
|
744
869
|
}) {
|
|
745
870
|
const failures =
|
|
746
871
|
failingAdapters.length > 0
|
|
747
|
-
? [`Adapters not ready: ${failingAdapters.join(
|
|
872
|
+
? [`Adapters not ready: ${failingAdapters.join(", ")}`, ""]
|
|
748
873
|
: [];
|
|
749
874
|
return [
|
|
750
875
|
// PBCLI-40: the SDK remedy leads, because an unusable adapter cannot be
|
|
751
876
|
// fixed by the credential advice further down.
|
|
752
877
|
...sdkFailureLines,
|
|
753
878
|
...failures,
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
879
|
+
"Usage:",
|
|
880
|
+
" playbook [--with <path>]... [--no-provision] [--cwd <path>]",
|
|
881
|
+
" playbook --session <id> [--with <path>]... [--no-provision]",
|
|
882
|
+
" playbook --list [--with <path>]... [--no-provision]",
|
|
883
|
+
" playbook --theme-diagnostics [--with <path>]... [--cwd <path>]",
|
|
884
|
+
" playbook --config <path> [tmux-play arguments...]",
|
|
885
|
+
" playbook run [--with <path>]... [--no-provision] [--json]",
|
|
886
|
+
" [--verbose] [--] [input]",
|
|
887
|
+
" playbook run (--continue | --session <id>) [reply]",
|
|
888
|
+
" playbook run --session <id> --retry-uncertain",
|
|
889
|
+
" playbook run --session <id> --discard-uncertain",
|
|
890
|
+
" playbook --help",
|
|
891
|
+
"",
|
|
767
892
|
`Default config: ${userConfigPath}`,
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
893
|
+
"",
|
|
894
|
+
" Only a fresh managed launch accepts --cwd. It creates a durable logical",
|
|
895
|
+
" Captain session and reports `playbook: session <id>` before attach.",
|
|
896
|
+
" Reopen that same session with `playbook --session <id>` or submit one",
|
|
897
|
+
" headless turn with `playbook run --session <id> [reply]`; selected",
|
|
898
|
+
" sessions always retain their stored working directory.",
|
|
899
|
+
" --with <path> overlays a top-level config fragment (same format as",
|
|
900
|
+
" the default config) for a fresh launch or compatible ordinary reopen —",
|
|
901
|
+
" maps merge recursively, other values replace, later files win, and the",
|
|
902
|
+
" default config file is never modified.",
|
|
903
|
+
" --no-provision keeps configured filesystem registries read-only;",
|
|
904
|
+
" any missing engine links remain a launch error.",
|
|
905
|
+
" `playbook run --verbose` prints Captain telemetry topics to stderr.",
|
|
906
|
+
" `playbook run --help` prints complete continuation and recovery usage.",
|
|
907
|
+
" Raw --config and --theme-diagnostics use cligent's stock tmux-play",
|
|
908
|
+
" process boundary and do not create or select a durable Captain session.",
|
|
909
|
+
"",
|
|
910
|
+
"Adapter setup:",
|
|
911
|
+
" claude: npm install -g @anthropic-ai/claude-agent-sdk, then run",
|
|
912
|
+
" Claude Code once or set ANTHROPIC_API_KEY.",
|
|
913
|
+
" codex: npm install -g @openai/codex-sdk, then run Codex CLI once",
|
|
914
|
+
" or set OPENAI_API_KEY.",
|
|
915
|
+
" Each SDK is an optional peer dependency, so you install only the",
|
|
916
|
+
" vendors your config actually names.",
|
|
917
|
+
"",
|
|
918
|
+
"Agent swap recipe:",
|
|
919
|
+
" - set the top-level captain and each stable players.<id> to an",
|
|
920
|
+
" adapter shorthand (claude, codex) or an inline agent block",
|
|
921
|
+
" - bind every playbooks.<id>.roles.<role> explicitly to a player id;",
|
|
922
|
+
" a scalar names the id, while { player, model?, effort?, fastMode? }",
|
|
923
|
+
" may retune one role; false selects provider-default model/effort,",
|
|
924
|
+
" while fastMode false is a literal disabled request",
|
|
925
|
+
" - reusing one id deliberately shares that provider conversation;",
|
|
926
|
+
" distinct ids stay isolated even when their agent blocks are equal",
|
|
927
|
+
" - the launcher injects captain.from and retains referenced player ids",
|
|
928
|
+
" verbatim",
|
|
929
|
+
"",
|
|
930
|
+
"Migration warning:",
|
|
931
|
+
" playbooks.<id>.players is removed and is not auto-migrated. Move each",
|
|
932
|
+
" agent to top-level players, choose ids for sharing or isolation, and",
|
|
933
|
+
" bind every local role under playbooks.<id>.roles.",
|
|
934
|
+
"",
|
|
935
|
+
].join("\n");
|
|
810
936
|
}
|
|
811
937
|
|
|
812
938
|
async function launchTmuxPlay(spawnFn, childArgs, stderr) {
|
|
813
939
|
return await new Promise((resolveResult) => {
|
|
814
940
|
let child;
|
|
815
941
|
try {
|
|
816
|
-
child = spawnFn(process.execPath, childArgs, { stdio:
|
|
942
|
+
child = spawnFn(process.execPath, childArgs, { stdio: "inherit" });
|
|
817
943
|
} catch (error) {
|
|
818
944
|
stderr.write(
|
|
819
945
|
`playbook: failed to launch tmux-play: ${errorMessage(error)}\n`,
|
|
@@ -827,13 +953,13 @@ async function launchTmuxPlay(spawnFn, childArgs, stderr) {
|
|
|
827
953
|
settled = true;
|
|
828
954
|
resolveResult(result);
|
|
829
955
|
};
|
|
830
|
-
child.on(
|
|
956
|
+
child.on("error", (err) => {
|
|
831
957
|
stderr.write(
|
|
832
958
|
`playbook: failed to launch tmux-play: ${errorMessage(err)}\n`,
|
|
833
959
|
);
|
|
834
960
|
settle({ code: 127 });
|
|
835
961
|
});
|
|
836
|
-
child.on(
|
|
962
|
+
child.on("exit", (code, signal) => {
|
|
837
963
|
if (signal) settle({ signal });
|
|
838
964
|
else settle({ code: code ?? 0 });
|
|
839
965
|
});
|
|
@@ -841,8 +967,8 @@ async function launchTmuxPlay(spawnFn, childArgs, stderr) {
|
|
|
841
967
|
}
|
|
842
968
|
|
|
843
969
|
function resolveTmuxPlayBin() {
|
|
844
|
-
const tmuxPlayIndexUrl = import.meta.resolve(
|
|
845
|
-
return join(dirname(fileURLToPath(tmuxPlayIndexUrl)),
|
|
970
|
+
const tmuxPlayIndexUrl = import.meta.resolve("@sublang/cligent/tmux-play");
|
|
971
|
+
return join(dirname(fileURLToPath(tmuxPlayIndexUrl)), "cli.js");
|
|
846
972
|
}
|
|
847
973
|
|
|
848
974
|
function errorMessage(error) {
|