@scp-dev/cli 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/{chunk-BSX24XMZ.js → chunk-6S3V5ZGU.js} +59 -6
  2. package/dist/{chunk-DEK3TE6T.js → chunk-77NUOWQ4.js} +3 -3
  3. package/dist/{chunk-KN7WEIE4.js → chunk-FPNFRDXG.js} +1 -0
  4. package/dist/{chunk-C7ZUACP7.js → chunk-GTU3EWEY.js} +1 -1
  5. package/dist/{chunk-C5PYVRJE.js → chunk-MHBWDAXO.js} +2 -2
  6. package/dist/{codex-4GTXURTS.js → chunk-S5KEVIGO.js} +6 -10
  7. package/dist/{chunk-GG33NSBW.js → chunk-UUFTEXDU.js} +19 -3
  8. package/dist/{chunk-CVW3INZO.js → chunk-ZEWZBRVM.js} +33 -8
  9. package/dist/{claude-NCPNZLIX.js → claude-7BA4PGK3.js} +16 -6
  10. package/dist/cli.js +1 -1
  11. package/dist/codex-KQNXVGTE.js +26 -0
  12. package/dist/{hooks-SWVIDONV.js → hooks-DRIUG546.js} +6 -2
  13. package/dist/{install-YE7LCGPC.js → install-MZC4PAIC.js} +3 -3
  14. package/dist/{join-64BU6SEY.js → join-JW7C2IDC.js} +1 -1
  15. package/dist/{login-4IF26HH5.js → login-22BLC7KI.js} +2 -2
  16. package/dist/{main-RA3MP25N.js → main-6BPNG53C.js} +99 -26
  17. package/dist/{mcp-RERRUUIX.js → mcp-4VAPXGF6.js} +8 -7
  18. package/dist/{mesh-XTMBRJLR.js → mesh-P3EQXYLH.js} +1 -1
  19. package/dist/muse-QWCI627T.js +429 -0
  20. package/dist/{opencode-CPNR2G3W.js → opencode-7XYA7E3Q.js} +4 -4
  21. package/dist/{peek-GEYQ7SHV.js → peek-G6RLQGKO.js} +1 -1
  22. package/dist/{pi-XHZAUDP4.js → pi-QUW4MSVZ.js} +1 -1
  23. package/dist/{relay-E4JFR33E.js → relay-7VBBQCFB.js} +1 -1
  24. package/dist/{remote-3R4GRDGI.js → remote-VRVONVET.js} +2 -2
  25. package/dist/remote-spawn-7Z2FLG33.js +309 -0
  26. package/dist/resume-B5BOZO2G.js +308 -0
  27. package/dist/{setup-O45SE5L7.js → setup-NK2HRFZN.js} +4 -4
  28. package/dist/{spawn-CJSHYE7E.js → spawn-3IYGUBG6.js} +14 -6
  29. package/dist/{up-O2CMDGXP.js → up-QL3FLYUK.js} +8 -6
  30. package/package.json +6 -6
@@ -0,0 +1,309 @@
1
+ import {
2
+ STDIN_WAIT_MS,
3
+ agentShell,
4
+ closedStdin,
5
+ harnessArgv,
6
+ harnessOutput,
7
+ harnessStdin,
8
+ programAt,
9
+ runHarness,
10
+ spawnEnvironment,
11
+ spawnTitle,
12
+ stderrOutput,
13
+ workerName
14
+ } from "./chunk-ZEWZBRVM.js";
15
+ import "./chunk-FPNFRDXG.js";
16
+ import "./chunk-77NUOWQ4.js";
17
+ import "./chunk-MHBWDAXO.js";
18
+ import "./chunk-UUFTEXDU.js";
19
+ import "./chunk-YYSLYVNZ.js";
20
+ import {
21
+ RelayUsage,
22
+ labelsFor,
23
+ localClient,
24
+ normalizeHarness,
25
+ open,
26
+ parseLabels,
27
+ startDaemon
28
+ } from "./chunk-6S3V5ZGU.js";
29
+ import {
30
+ note
31
+ } from "./chunk-MNTIEWKF.js";
32
+
33
+ // src/remote-spawn.ts
34
+ import { spawn } from "child_process";
35
+ import { existsSync, readFileSync } from "fs";
36
+ import { basename, dirname, join } from "path";
37
+ import { constants } from "os";
38
+ import { CLI_NAME, findSecret, handbackOutput, LIMITS, validHandle } from "@scp-dev/core";
39
+ import { connectBroker, policyPath, relayDir } from "@scp-dev/broker";
40
+ var RUNNER_NAME = /^[a-z0-9][a-z0-9-]{0,31}$/;
41
+ var RUNNER_TIMEOUT_S = 60;
42
+ var RUNNER_TIMEOUT_MAX_S = 3600;
43
+ var RUNNER_STDERR_BYTES = 2048;
44
+ var RUNNER_STDOUT_BYTES = 4096;
45
+ var RUNNER_REF = /^[\x21-\x7e]{1,256}$/;
46
+ var RUNNER_ATTACH = /^[^\u0000-\u001f\u007f-\u009f]{1,1024}$/;
47
+ var RUNNER_DRAIN_MS = 500;
48
+ var TITLE_CHARS = 64;
49
+ var MACHINE = "[a-z0-9][a-z0-9-]{0,31}";
50
+ var MACHINE_ADDRESS = new RegExp(`^me/(${MACHINE})/([^/]+)$`);
51
+ var STDERR_LABEL = `stderr (last ${LIMITS.stderrBytes / 1024} KB):`;
52
+ var VIA_FLAGS = ["as", "harness", "name", "title", "labels", "bg", "json", "via"];
53
+ var GRANT_FLAGS = ["scope", "rules", "in", "out", "allow", "group", "ttl", "peek", "no-push", "as", "human", "harness"];
54
+ function runnersPath(env = process.env) {
55
+ return env.SCP_RUNNERS_FILE || join(dirname(policyPath()), "runners.json");
56
+ }
57
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
58
+ function parseRunners(text, path) {
59
+ let value;
60
+ try {
61
+ value = JSON.parse(text);
62
+ } catch {
63
+ throw new RelayUsage(`${path} is not JSON`);
64
+ }
65
+ if (!isObject(value)) throw new RelayUsage(`${path} maps runner names to {command, timeout_s?}`);
66
+ const runners = {};
67
+ for (const [name, entry] of Object.entries(value)) {
68
+ if (!RUNNER_NAME.test(name))
69
+ throw new RelayUsage(`${path}: runner ${JSON.stringify(name)} is not a valid name: lowercase letters, digits and -, at most 32`);
70
+ const command = isObject(entry) ? entry.command : void 0;
71
+ if (!Array.isArray(command) || !command.length || !command.every((word) => typeof word === "string" && word.length > 0))
72
+ throw new RelayUsage(`${path}: runner ${name} needs command, a non-empty list of words`);
73
+ const timeout = entry.timeout_s ?? RUNNER_TIMEOUT_S;
74
+ if (typeof timeout !== "number" || !Number.isInteger(timeout) || timeout < 1 || timeout > RUNNER_TIMEOUT_MAX_S)
75
+ throw new RelayUsage(`${path}: runner ${name}'s timeout_s is whole seconds, 1 to ${RUNNER_TIMEOUT_MAX_S}`);
76
+ runners[name] = { command: [...command], timeout_s: timeout };
77
+ }
78
+ return runners;
79
+ }
80
+ function readRunners(path = runnersPath()) {
81
+ return existsSync(path) ? parseRunners(readFileSync(path, "utf8"), path) : {};
82
+ }
83
+ function runnerFor(name, runners, path) {
84
+ const runner = Object.hasOwn(runners, name) ? runners[name] : void 0;
85
+ if (runner) return runner;
86
+ const known = Object.keys(runners);
87
+ throw new RelayUsage(`no runner ${JSON.stringify(name)} in ${path}: ${known.length ? `configured: ${known.join(", ")}` : "none is configured"}`);
88
+ }
89
+ function spawnRequest(o) {
90
+ return {
91
+ scp: 1,
92
+ event: "spawn",
93
+ name: o.name,
94
+ title: o.title,
95
+ cmd: o.command,
96
+ cwd: o.cwd,
97
+ parent: o.parent,
98
+ labels: o.labels,
99
+ join: { under: o.parent, name: o.name, mode: "account" }
100
+ };
101
+ }
102
+ function tail(from, bytes) {
103
+ let kept = Buffer.alloc(0);
104
+ let cut = false;
105
+ from.on("data", (chunk) => {
106
+ kept = Buffer.concat([kept, chunk]);
107
+ if (kept.length > bytes) {
108
+ kept = kept.subarray(kept.length - bytes);
109
+ cut = true;
110
+ }
111
+ });
112
+ return { text: () => kept.toString("utf8"), cut: () => cut };
113
+ }
114
+ function runRunner(runner, request, env) {
115
+ return new Promise((resolve) => {
116
+ const child = spawn(runner.command[0], runner.command.slice(1), { detached: true, stdio: ["pipe", "pipe", "pipe"], env, cwd: request.cwd });
117
+ const out = tail(child.stdout, RUNNER_STDOUT_BYTES);
118
+ const err = tail(child.stderr, RUNNER_STDERR_BYTES);
119
+ const pipes = [child.stdout, child.stderr];
120
+ let settled = false;
121
+ const finish = (code, timedOut = false, message) => {
122
+ if (settled) return;
123
+ settled = true;
124
+ clearTimeout(timer);
125
+ const done = () => {
126
+ for (const pipe of pipes) pipe.destroy();
127
+ child.unref();
128
+ const stderr = message ?? handbackOutput(err.text(), err.cut(), RUNNER_STDERR_BYTES);
129
+ resolve({ code, timedOut, stdout: out.cut() ? "" : out.text(), stderr });
130
+ };
131
+ const open2 = pipes.filter((pipe) => !pipe.readableEnded);
132
+ if (!open2.length || message) return done();
133
+ const drain = setTimeout(done, RUNNER_DRAIN_MS);
134
+ let left = open2.length;
135
+ for (const pipe of open2)
136
+ pipe.once("end", () => {
137
+ if (--left > 0) return;
138
+ clearTimeout(drain);
139
+ done();
140
+ });
141
+ };
142
+ const timer = setTimeout(() => {
143
+ try {
144
+ process.kill(-child.pid, "SIGTERM");
145
+ } catch {
146
+ }
147
+ finish(124, true);
148
+ }, runner.timeout_s * 1e3);
149
+ child.once(
150
+ "error",
151
+ (error) => finish(error.code === "ENOENT" ? 127 : 126, false, `${runner.command[0]}: ${error.code === "ENOENT" ? "command not found" : error.message}`)
152
+ );
153
+ child.once("exit", (code, signal) => finish(code ?? 128 + (signal ? constants.signals[signal] ?? 0 : 0)));
154
+ child.stdin.on("error", () => {
155
+ });
156
+ child.stdin.end(`${JSON.stringify(request)}
157
+ `);
158
+ });
159
+ }
160
+ function runnerReply(stdout, name) {
161
+ let value;
162
+ try {
163
+ value = JSON.parse(stdout.trim());
164
+ } catch {
165
+ return void 0;
166
+ }
167
+ if (!isObject(value) || typeof value.machine !== "string" || typeof value.address !== "string") return void 0;
168
+ const address = MACHINE_ADDRESS.exec(value.address);
169
+ if (!address || address[1] !== value.machine || address[2] !== name) return void 0;
170
+ if (value.ref !== void 0 && (typeof value.ref !== "string" || !RUNNER_REF.test(value.ref))) return void 0;
171
+ if (value.attach !== void 0 && (typeof value.attach !== "string" || !RUNNER_ATTACH.test(value.attach))) return void 0;
172
+ const ref = typeof value.ref === "string" && !findSecret(value.ref) ? value.ref : void 0;
173
+ const attach = typeof value.attach === "string" && !findSecret(value.attach) ? value.attach : void 0;
174
+ return { address: value.address, machine: value.machine, ...ref ? { ref } : {}, ...attach ? { attach } : {} };
175
+ }
176
+ function viaStarted(o) {
177
+ if (o.json) return [JSON.stringify({ name: o.name, runner: o.runner, parent: o.parent, ...o.reply })];
178
+ const where = o.reply ? `on machine ${o.reply.machine} and shows under you as ${o.reply.address}` : `on another machine and shows under you as me/<machine>/${o.name}`;
179
+ return [
180
+ `${o.name} handed to runner ${o.runner}: it starts ${where}; its result arrives as a relay message by itself`,
181
+ ...o.reply?.attach ? [`attach: ${o.reply.attach}`] : []
182
+ ];
183
+ }
184
+ function spawnName(values, command) {
185
+ const name = typeof values.name === "string" ? values.name.replace(/^me\//, "") : workerName(command);
186
+ if (!validHandle(name)) throw new RelayUsage(`--name ${JSON.stringify(name)} is not a valid agent handle: lowercase letters, digits, . _ -`);
187
+ return name;
188
+ }
189
+ async function hubMachine(client, why) {
190
+ const bridge = await client.request("cloud_status");
191
+ if (!bridge.enabled || !bridge.machine)
192
+ throw new RelayUsage(`${why}, but this relay broker is not linked to a hub (${CLI_NAME} login, then ${CLI_NAME} cloud enable)`);
193
+ return bridge.machine;
194
+ }
195
+ async function viaSpawn(values, command, cwd, out = (line) => console.log(line)) {
196
+ const via = values.via;
197
+ const extra = Object.keys(values).find((key) => !VIA_FLAGS.includes(key));
198
+ if (extra)
199
+ throw new RelayUsage(
200
+ `--via starts the worker on another machine under you, without a local grant: it takes --name, --title, --labels and --json, not --${extra}`
201
+ );
202
+ const path = runnersPath();
203
+ const runner = runnerFor(via, readRunners(path), path);
204
+ const name = spawnName(values, command);
205
+ const title = typeof values.title === "string" ? values.title : spawnTitle(command) ?? null;
206
+ if (title !== null && title.length > TITLE_CHARS) throw new RelayUsage(`--title is longer than ${TITLE_CHARS} characters`);
207
+ const opened = await open("spawn", values, cwd, false);
208
+ const client = localClient(opened.client, opened.broker, "spawn");
209
+ let parent;
210
+ try {
211
+ const me = opened.me;
212
+ if (me?.kind !== "agent" || !me.address.startsWith("me/"))
213
+ throw new RelayUsage(`${CLI_NAME} spawn --via starts a worker under you: run it from an agent's session, or name the agent with --as`);
214
+ parent = `me/${await hubMachine(client, `${CLI_NAME} spawn --via shows the worker under you through the hub`)}/${me.address.slice(3)}`;
215
+ } finally {
216
+ client.close();
217
+ }
218
+ const request = spawnRequest({ name, title, command, cwd, parent, labels: parseLabels(values.labels) });
219
+ const result = await runRunner(runner, request, spawnEnvironment(process.env, {}));
220
+ if (result.code === 0) {
221
+ for (const line of viaStarted({ name, runner: via, parent, reply: runnerReply(result.stdout, name), json: values.json === true })) out(line);
222
+ return 0;
223
+ }
224
+ const failed = result.timedOut ? `did not finish within ${runner.timeout_s} s` : `failed (exit ${result.code})`;
225
+ note(`${CLI_NAME} spawn: runner ${via} ${failed}${result.stderr ? `:
226
+ ${result.stderr}` : ""}`);
227
+ return result.code;
228
+ }
229
+ async function linkedPlan(values, command) {
230
+ const under = typeof values.under === "string" ? values.under : void 0;
231
+ const remote = under === void 0 ? null : MACHINE_ADDRESS.exec(under);
232
+ if (!under || !remote) return null;
233
+ const dir = relayDir();
234
+ const client = await connectBroker(dir, () => startDaemon(dir));
235
+ let machine;
236
+ try {
237
+ await client.hello({ observe: true });
238
+ machine = await hubMachine(client, `--under ${under} names an agent on machine ${remote[1]}`);
239
+ } finally {
240
+ client.close();
241
+ }
242
+ if (machine === remote[1]) return null;
243
+ if (values.via !== void 0) throw new RelayUsage("--via and --under an agent on another machine: the runner already starts this spawn there");
244
+ const granted = Object.keys(values).find((key) => GRANT_FLAGS.includes(key));
245
+ if (granted) throw new RelayUsage(`a worker under an agent on another machine runs without a grant: --${granted} does not apply`);
246
+ return {
247
+ parent: under,
248
+ machine,
249
+ name: spawnName(values, command),
250
+ ...typeof values.title === "string" ? { title: values.title } : {},
251
+ ...typeof values.labels === "string" ? { labels: values.labels } : {}
252
+ };
253
+ }
254
+ function linkedHandbackBody(address, code, output, stderr) {
255
+ const tail2 = code !== 0 && stderr ? `${STDERR_LABEL}
256
+ ${stderr}` : "";
257
+ const kept = tail2 && output ? handbackOutput(output, false, LIMITS.handbackBytes - Buffer.byteLength(tail2) - 2) : output;
258
+ const head = kept ? `worker ${address} finished (exit ${code})` : `worker ${address} exited without replying (exit ${code})`;
259
+ return [head, kept, tail2].filter(Boolean).join("\n\n");
260
+ }
261
+ async function linkedSpawn(plan, command, cwd, options) {
262
+ const env = spawnEnvironment(process.env, { SCP_NAME: plan.name, SCP_UNDER: plan.parent }, plan.labels);
263
+ const argv = harnessArgv(command, env);
264
+ const stdin = closedStdin(command) ? await harnessStdin(agentShell(process.env) ? STDIN_WAIT_MS : void 0) : void 0;
265
+ const run = await runHarness(argv, env, {
266
+ group: options.group,
267
+ ...closedStdin(argv) ? { stdin: "ignore" } : {},
268
+ ...options.started ? { started: options.started } : {},
269
+ ...stdin ? { stdin } : {}
270
+ });
271
+ if (run.started) await linkedHandback(plan, command, cwd, env, run, options.bytes);
272
+ return run.code;
273
+ }
274
+ async function linkedHandback(plan, command, cwd, env, run, bytes) {
275
+ const harness = normalizeHarness(basename(command[programAt(command)] ?? ""), "cli");
276
+ const dir = relayDir();
277
+ try {
278
+ const client = await connectBroker(dir, () => startDaemon(dir));
279
+ try {
280
+ const hello = await client.hello({ claim: { handle: plan.name, exact: true, harness, cwd, under: plan.parent, labels: labelsFor(env) } });
281
+ const me = hello.endpoint;
282
+ const address = `me/${plan.machine}/${me.address.slice(3)}`;
283
+ if (plan.title && !me.title) await client.request("title", { text: plan.title });
284
+ const code = Math.min(run.code, 255);
285
+ await client.request("send", { to: plan.parent, kind: "message", body: linkedHandbackBody(address, code, harnessOutput(run, bytes), stderrOutput(run)) });
286
+ await client.request("status", { state: "done", note: `exited ${code}` });
287
+ } finally {
288
+ client.close();
289
+ }
290
+ } catch (error) {
291
+ note(`${CLI_NAME} spawn: could not hand ${plan.name}'s result back to ${plan.parent}: ${error instanceof Error ? error.message : String(error)}`);
292
+ }
293
+ }
294
+ export {
295
+ RUNNER_NAME,
296
+ RUNNER_TIMEOUT_S,
297
+ linkedHandbackBody,
298
+ linkedPlan,
299
+ linkedSpawn,
300
+ parseRunners,
301
+ readRunners,
302
+ runRunner,
303
+ runnerFor,
304
+ runnerReply,
305
+ runnersPath,
306
+ spawnRequest,
307
+ viaSpawn,
308
+ viaStarted
309
+ };
@@ -0,0 +1,308 @@
1
+ import {
2
+ agyNote,
3
+ messagingAllow
4
+ } from "./chunk-ZEWZBRVM.js";
5
+ import "./chunk-FPNFRDXG.js";
6
+ import {
7
+ whichOnPath
8
+ } from "./chunk-77NUOWQ4.js";
9
+ import {
10
+ hooksTarget
11
+ } from "./chunk-MHBWDAXO.js";
12
+ import {
13
+ AGY_CONVERSATION,
14
+ AGY_HOOK_SET
15
+ } from "./chunk-UUFTEXDU.js";
16
+ import "./chunk-YYSLYVNZ.js";
17
+ import {
18
+ RelayUsage,
19
+ relayIdentity,
20
+ startDaemon
21
+ } from "./chunk-6S3V5ZGU.js";
22
+ import "./chunk-MNTIEWKF.js";
23
+
24
+ // src/resume.ts
25
+ import { spawn } from "child_process";
26
+ import { randomBytes, randomUUID } from "crypto";
27
+ import { mkdtempSync, readFileSync, rmSync } from "fs";
28
+ import { tmpdir } from "os";
29
+ import { join } from "path";
30
+ import { PassThrough } from "stream";
31
+ import { connectBroker, relayDir } from "@scp-dev/broker";
32
+ import { CLI_NAME, validHandle } from "@scp-dev/core";
33
+ import { MCP_PROTOCOLS, runRelayMcp } from "@scp-dev/mcp";
34
+ var SPECS = {
35
+ agy: {
36
+ bin: "SCP_AGY_BIN",
37
+ refused: ["-i", "--prompt-interactive", "-c", "--continue", "--input-format", "--remote-control"],
38
+ prompt: ["-p", "--print", "--prompt"],
39
+ ids: ["--conversation"]
40
+ },
41
+ grok: {
42
+ bin: "SCP_GROK_BIN",
43
+ refused: ["-c", "--continue", "--fork-session"],
44
+ prompt: ["-p", "--single", "--prompt-file", "--prompt-json"],
45
+ ids: ["-s", "--session-id", "-r", "--resume"]
46
+ }
47
+ };
48
+ var AGY_HELP = `${CLI_NAME} agy [--name N | --as N] [--] [agy args...]
49
+ runs agy headless, wake on: a -p prompt is its first turn, and each new message resumes the same conversation as a
50
+ turn (agy --conversation <id> -p <message>); without a prompt the first message starts it. Refuses -i, -c,
51
+ --input-format and --remote-control: nothing local starts a turn in an interactive agy, so run plain agy for that
52
+ (its hooks still deliver before each model call). Refuses to start without the ${CLI_NAME} hooks, which report the
53
+ conversation; ${CLI_NAME} setup --write installs them.
54
+ $SCP_AGY_BIN picks the agy binary (default agy on PATH).`;
55
+ var GROK_HELP = `${CLI_NAME} grok [--name N | --as N] [--] [grok args...]
56
+ runs grok headless, wake on: a -p prompt is its first turn, and each new message resumes the same session as a turn
57
+ (grok --resume <id> -p <message>); without a prompt the first message starts it. The session id is yours (-s, -r)
58
+ or a new one. Refuses -c and --fork-session: nothing local starts a turn in an interactive grok, so run plain grok
59
+ for that. Adds the --allow rules for the relay tools unless you pass your own.
60
+ $SCP_GROK_BIN picks the grok binary (default grok on PATH).`;
61
+ function insert(args, extra) {
62
+ const end = args.indexOf("--");
63
+ return end < 0 ? [...args, ...extra] : [...args.slice(0, end), ...extra, ...args.slice(end)];
64
+ }
65
+ function resumeLaunch(harness, args, env, uuid = randomUUID) {
66
+ let name;
67
+ let i = 0;
68
+ for (; i < args.length; i++) {
69
+ const arg = args[i];
70
+ if (arg === "--name" || arg === "--as") {
71
+ name = args[++i];
72
+ if (name === void 0) throw new RelayUsage(`${CLI_NAME} ${harness} ${arg} takes a name`);
73
+ } else if (arg.startsWith("--name=") || arg.startsWith("--as=")) name = arg.slice(arg.indexOf("=") + 1);
74
+ else break;
75
+ }
76
+ if (args[i] === "--") i++;
77
+ const rest = args.slice(i);
78
+ name = name?.replace(/^me\//, "");
79
+ if (name !== void 0 && !validHandle(name)) throw new RelayUsage(`${JSON.stringify(name)} is not a valid agent handle`);
80
+ const spec = SPECS[harness];
81
+ const refusal = (flag) => new RelayUsage(
82
+ `${CLI_NAME} ${harness}: ${flag} is not supported: nothing local starts a turn in an interactive ${harness} session; run plain ${harness} for that`
83
+ );
84
+ if (harness === "agy" && rest[0] === "remote-control") throw refusal("remote-control");
85
+ const base = [];
86
+ const ids = /* @__PURE__ */ new Map();
87
+ let prompt = false;
88
+ for (let j = 0; j < rest.length; j++) {
89
+ const arg = rest[j];
90
+ if (arg === "--") {
91
+ base.push(...rest.slice(j));
92
+ break;
93
+ }
94
+ const refused = spec.refused.find((flag) => arg === flag || arg.startsWith(`${flag}=`));
95
+ if (refused) throw refusal(refused);
96
+ const valued = [...spec.prompt, ...spec.ids].find((flag) => arg === flag || arg.startsWith(`${flag}=`));
97
+ if (!valued) {
98
+ base.push(arg);
99
+ continue;
100
+ }
101
+ const value = arg === valued ? rest[++j] : arg.slice(valued.length + 1);
102
+ if (value === void 0) throw new RelayUsage(`${CLI_NAME} ${harness} ${valued} takes a value`);
103
+ if (spec.prompt.includes(valued)) prompt = true;
104
+ else ids.set(valued, value);
105
+ }
106
+ const { SCP_SESSION: _session, SCP_PARENT: _parent, SCP_HARNESS_PID: _pid, ...clean } = env;
107
+ const launchEnv = { ...clean, SCP_HARNESS: harness, SCP_SESSION: randomBytes(16).toString("hex"), ...name !== void 0 ? { SCP_NAME: name } : {} };
108
+ const named = name !== void 0 ? { name } : {};
109
+ if (harness === "agy") {
110
+ const id2 = ids.get("--conversation");
111
+ return {
112
+ harness,
113
+ ...named,
114
+ ...prompt ? { first: rest } : {},
115
+ base,
116
+ opening: id2 === void 0 ? [] : ["--conversation", id2],
117
+ ...id2 !== void 0 ? { id: id2 } : {},
118
+ extra: [],
119
+ env: launchEnv
120
+ };
121
+ }
122
+ const resumed = ids.get("-r") ?? ids.get("--resume");
123
+ const given = ids.get("-s") ?? ids.get("--session-id");
124
+ const id = resumed ?? given ?? uuid();
125
+ const extra = messagingAllow("grok", rest);
126
+ return {
127
+ harness,
128
+ ...named,
129
+ ...prompt ? { first: insert(rest, [...resumed ?? given ? [] : ["-s", id], ...extra]) } : {},
130
+ base,
131
+ opening: resumed !== void 0 ? ["--resume", id] : ["-s", id],
132
+ id,
133
+ extra,
134
+ env: launchEnv
135
+ };
136
+ }
137
+ function turnArgv(launch, text, id) {
138
+ const conversation = id === void 0 ? launch.opening : launch.harness === "agy" ? ["--conversation", id] : ["--resume", id];
139
+ return insert(launch.base, [...conversation, "-p", text, ...launch.extra]);
140
+ }
141
+ function agyHooksProblem(env, cwd) {
142
+ for (const scope of ["project", "user"]) {
143
+ try {
144
+ const set = JSON.parse(readFileSync(hooksTarget("agy", scope, env, cwd), "utf8"))[AGY_HOOK_SET];
145
+ if (set && set.enabled !== false && Array.isArray(set.SessionStart) && set.SessionStart.length) return void 0;
146
+ } catch {
147
+ }
148
+ }
149
+ return `${CLI_NAME} agy: agy has no ${CLI_NAME} hooks, so it cannot report the conversation to resume; run ${CLI_NAME} setup --write`;
150
+ }
151
+ function readConversation(file) {
152
+ if (!file) return void 0;
153
+ try {
154
+ const id = readFileSync(file, "utf8").trim();
155
+ return AGY_CONVERSATION.test(id) ? id : void 0;
156
+ } catch {
157
+ return void 0;
158
+ }
159
+ }
160
+ async function resumeCommand(harness, args, env = process.env, cwd = process.cwd(), err = (line) => void process.stderr.write(`${line}
161
+ `)) {
162
+ if (args[0] === "--help" || args[0] === "-h") {
163
+ process.stdout.write(`${harness === "agy" ? AGY_HELP : GROK_HELP}
164
+ `);
165
+ return 0;
166
+ }
167
+ const launch = resumeLaunch(harness, args, env);
168
+ const spec = SPECS[harness];
169
+ const bin = env[spec.bin] || whichOnPath(harness, env);
170
+ if (!bin) throw new RelayUsage(`${harness} is not on PATH: install it, or set $${spec.bin} to its binary`);
171
+ if (harness === "agy") {
172
+ const problem = agyHooksProblem(env, cwd);
173
+ if (problem) throw new RelayUsage(problem);
174
+ const note = agyNote(["agy", "-p", "", ...launch.base], env, "agy");
175
+ if (note) err(note);
176
+ }
177
+ const runEnv = { ...launch.env, SCP_HARNESS_PID: String(process.pid) };
178
+ const claim = relayIdentity({ harness, fallbackHarness: harness, cwd, env: runEnv, harnessPid: () => process.pid });
179
+ if (!validHandle(claim.handle)) throw new RelayUsage(`${JSON.stringify(claim.handle)} is not a valid agent handle`);
180
+ const dir = harness === "agy" ? mkdtempSync(join(tmpdir(), "scp-agy-")) : void 0;
181
+ const file = dir === void 0 ? void 0 : join(dir, "conversation");
182
+ if (file) runEnv.SCP_AGY_CONVERSATION_FILE = file;
183
+ const broker = relayDir(runEnv);
184
+ const input = new PassThrough();
185
+ const queue = [];
186
+ let id = launch.id;
187
+ let opened = false;
188
+ let ready = false;
189
+ let stopping = false;
190
+ let running;
191
+ let finish;
192
+ const done = new Promise((resolve) => {
193
+ finish = resolve;
194
+ });
195
+ let joined;
196
+ const whoami = new Promise((resolve) => {
197
+ joined = resolve;
198
+ });
199
+ const turn = (argv) => {
200
+ const child = spawn(bin, argv, { env: runEnv, stdio: ["ignore", "inherit", "inherit"] });
201
+ running = child;
202
+ let settled = false;
203
+ child.once("error", (error) => {
204
+ if (settled) return;
205
+ settled = true;
206
+ running = void 0;
207
+ err(`${CLI_NAME} ${harness}: could not start ${bin}: ${error.message}`);
208
+ finish(1);
209
+ });
210
+ child.once("exit", (code, signal) => {
211
+ if (settled) return;
212
+ settled = true;
213
+ running = void 0;
214
+ if (stopping) return finish(0);
215
+ if (code !== 0) {
216
+ err(`${CLI_NAME} ${harness}: turn failed (exit ${code ?? signal}); undelivered messages stay unread in relay_inbox`);
217
+ return finish(code ?? 1);
218
+ }
219
+ opened = true;
220
+ if (harness === "agy") id = readConversation(file) ?? id;
221
+ if (id === void 0) {
222
+ err(`${CLI_NAME} agy: agy's ${CLI_NAME} hooks did not report the conversation, so it cannot be resumed; run ${CLI_NAME} setup --write`);
223
+ return finish(1);
224
+ }
225
+ next();
226
+ });
227
+ };
228
+ const next = () => {
229
+ if (!ready || running || stopping || !queue.length) return;
230
+ turn(turnArgv(launch, queue.splice(0).join("\n\n"), opened ? id : void 0));
231
+ };
232
+ let lines = "";
233
+ const channel = runRelayMcp({
234
+ connect: () => connectBroker(broker, () => startDaemon(broker)),
235
+ claim,
236
+ channels: true,
237
+ wake: harness === "agy" ? "agy-resume" : "grok-resume",
238
+ input,
239
+ env: runEnv,
240
+ output: (chunk) => {
241
+ lines += chunk;
242
+ for (; ; ) {
243
+ const end = lines.indexOf("\n");
244
+ if (end < 0) break;
245
+ const message = JSON.parse(lines.slice(0, end));
246
+ lines = lines.slice(end + 1);
247
+ if (message.id === 2) joined(message.result ?? {});
248
+ if (message.method === "notifications/claude/channel") {
249
+ const content = message.params.content;
250
+ if (typeof content === "string") {
251
+ queue.push(content);
252
+ next();
253
+ }
254
+ }
255
+ }
256
+ },
257
+ log: err
258
+ });
259
+ void channel.catch((error) => {
260
+ err(`${CLI_NAME} ${harness}: ${String(error)}`);
261
+ finish(1);
262
+ });
263
+ const stop = (signal) => {
264
+ stopping = true;
265
+ if (running) running.kill(signal);
266
+ else finish(0);
267
+ };
268
+ const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
269
+ for (const signal of signals) process.on(signal, stop);
270
+ try {
271
+ input.write(
272
+ `${JSON.stringify({ id: 1, method: "initialize", params: { protocolVersion: MCP_PROTOCOLS[0], capabilities: { experimental: { "claude/channel": {} } }, clientInfo: { name: `scp-${harness}`, version: "0.1.0" } } })}
273
+ `
274
+ );
275
+ input.write(`${JSON.stringify({ method: "notifications/initialized" })}
276
+ `);
277
+ input.write(`${JSON.stringify({ id: 2, method: "tools/call", params: { name: "relay_whoami", arguments: {} } })}
278
+ `);
279
+ const result = await Promise.race([whoami, done.then(() => void 0)]);
280
+ if (result === void 0) return await done;
281
+ const text = (result.content ?? []).map((part) => part.text ?? "").join("\n");
282
+ const address = /^address: (\S+)$/m.exec(text)?.[1];
283
+ if (result.isError || !address) throw new RelayUsage(`${CLI_NAME} ${harness}: could not join the relay: ${text || "no answer"}`);
284
+ runEnv.SCP_NAME = address.slice(address.lastIndexOf("/") + 1);
285
+ ready = true;
286
+ if (launch.first) turn(launch.first);
287
+ else {
288
+ err(`${CLI_NAME} ${harness}: ${address} waiting for messages`);
289
+ next();
290
+ }
291
+ return await done;
292
+ } finally {
293
+ for (const signal of signals) process.off(signal, stop);
294
+ input.end();
295
+ await channel.catch(() => {
296
+ });
297
+ if (running?.exitCode === null && running.signalCode === null) running.kill("SIGTERM");
298
+ if (dir) rmSync(dir, { recursive: true, force: true });
299
+ }
300
+ }
301
+ export {
302
+ AGY_HELP,
303
+ GROK_HELP,
304
+ agyHooksProblem,
305
+ resumeCommand,
306
+ resumeLaunch,
307
+ turnArgv
308
+ };
@@ -15,11 +15,11 @@ import {
15
15
  verifyMount,
16
16
  verifyMusePlugin,
17
17
  whichOnPath
18
- } from "./chunk-DEK3TE6T.js";
19
- import "./chunk-C5PYVRJE.js";
20
- import "./chunk-GG33NSBW.js";
18
+ } from "./chunk-77NUOWQ4.js";
19
+ import "./chunk-MHBWDAXO.js";
20
+ import "./chunk-UUFTEXDU.js";
21
21
  import "./chunk-YYSLYVNZ.js";
22
- import "./chunk-BSX24XMZ.js";
22
+ import "./chunk-6S3V5ZGU.js";
23
23
  import "./chunk-MNTIEWKF.js";
24
24
  export {
25
25
  SETUP_HARNESSES,
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  CODEX_VALUED,
3
3
  MESSAGING,
4
+ MUSE_MODE,
5
+ MUSE_MODES_ALLOWED,
4
6
  SPAWN_RUNNER,
5
7
  STDIN_WAIT_MS,
6
8
  agentShell,
@@ -15,6 +17,8 @@ import {
15
17
  harnessArgv,
16
18
  harnessOutput,
17
19
  harnessStdin,
20
+ messagingAllow,
21
+ museModes,
18
22
  programAt,
19
23
  repliesOutput,
20
24
  resumeArgv,
@@ -31,19 +35,21 @@ import {
31
35
  toml,
32
36
  transcriptReplies,
33
37
  workerName
34
- } from "./chunk-CVW3INZO.js";
38
+ } from "./chunk-ZEWZBRVM.js";
35
39
  import {
36
40
  SPAWN_HELP
37
- } from "./chunk-KN7WEIE4.js";
38
- import "./chunk-DEK3TE6T.js";
39
- import "./chunk-C5PYVRJE.js";
40
- import "./chunk-GG33NSBW.js";
41
+ } from "./chunk-FPNFRDXG.js";
42
+ import "./chunk-77NUOWQ4.js";
43
+ import "./chunk-MHBWDAXO.js";
44
+ import "./chunk-UUFTEXDU.js";
41
45
  import "./chunk-YYSLYVNZ.js";
42
- import "./chunk-BSX24XMZ.js";
46
+ import "./chunk-6S3V5ZGU.js";
43
47
  import "./chunk-MNTIEWKF.js";
44
48
  export {
45
49
  CODEX_VALUED,
46
50
  MESSAGING,
51
+ MUSE_MODE,
52
+ MUSE_MODES_ALLOWED,
47
53
  SPAWN_HELP,
48
54
  SPAWN_RUNNER,
49
55
  STDIN_WAIT_MS,
@@ -59,6 +65,8 @@ export {
59
65
  harnessArgv,
60
66
  harnessOutput,
61
67
  harnessStdin,
68
+ messagingAllow,
69
+ museModes,
62
70
  programAt,
63
71
  repliesOutput,
64
72
  resumeArgv,