svamp-cli 0.2.346 → 0.2.356
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/bin/skills/artifact/SKILL.md +19 -5
- package/bin/skills/crew/SKILL.md +42 -11
- package/dist/{acpBackend-BZ1MJhQV.mjs → acpBackend-UaBD4zL3.mjs} +3 -0
- package/dist/{agentCommands-B2Nl7Fof.mjs → agentCommands-D91RcjqQ.mjs} +4 -4
- package/dist/{claudeAuth-iAerdaMr.mjs → claudeAuth-C0wc1GTM.mjs} +45 -1
- package/dist/cli.mjs +78 -69
- package/dist/{commands-D1pZq3d4.mjs → commands-B-yczgz_.mjs} +14 -10
- package/dist/{commands-BOpoVBOM.mjs → commands-BMeDrpSv.mjs} +3 -3
- package/dist/{commands-H4PsTGn8.mjs → commands-BbmypSgx.mjs} +5 -2
- package/dist/{commands-C2aODeVc.mjs → commands-BkdbelFo.mjs} +26 -11
- package/dist/{commands-BB1Kcyt-.mjs → commands-C13-yE2R.mjs} +32 -3
- package/dist/{commands-D69mnkYu.mjs → commands-CoOc4qiw.mjs} +2 -2
- package/dist/{commands-CiRCwjkJ.mjs → commands-sPvRcG3J.mjs} +9 -9
- package/dist/{detectIsolation-CzoDCEnh.mjs → detectIsolation-CrV8IFDv.mjs} +36 -16
- package/dist/{fleet-DM_Q7w69.mjs → fleet-Ca-UjJNL.mjs} +40 -15
- package/dist/{hookSettings-DYMTUgVA.mjs → hookSettings-FFc1pi8j.mjs} +174 -27
- package/dist/index.mjs +8 -8
- package/dist/{loopVerify-B47sSjxe.mjs → loopVerify-DALtTk2V.mjs} +1 -1
- package/dist/{notifyCommands-D-IKIxiq.mjs → notifyCommands-CCUE18Q-.mjs} +1 -1
- package/dist/package-BrsiNutS.mjs +64 -0
- package/dist/{rpc-BB6RF4-F.mjs → rpc-CBkSkzsI.mjs} +8 -8
- package/dist/{rpc-Cf0grcru.mjs → rpc-ccY7xvlo.mjs} +6 -3
- package/dist/{run-ChdsgaSD.mjs → run-DCq3aEiF.mjs} +3 -3
- package/dist/{run-DkjwF-zA.mjs → run-DDmeV9K3.mjs} +190 -59
- package/dist/{scheduler-BY30ZrB9.mjs → scheduler-BkVJ27Ki.mjs} +8 -8
- package/dist/{serveAuth-DbVLRCRR.mjs → serveAuth-4M0vwMyj.mjs} +53 -11
- package/dist/{serveCommands-CUHUiYnZ.mjs → serveCommands-CLhkccJM.mjs} +8 -8
- package/dist/{store-Bn0ByYev.mjs → store-B4E6NzG6.mjs} +10 -1
- package/package.json +2 -2
- package/dist/package-DtuINqf5.mjs +0 -64
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
|
-
import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-
|
|
3
|
+
import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-B-yczgz_.mjs';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import { w as withFileLock } from './fileLock-BMPwCrB6.mjs';
|
|
6
|
-
import { u as updateIssue,
|
|
6
|
+
import { u as updateIssue, h as addComment, i as addIssue, o as resolveCrewIssueOnMerge } from './store-B4E6NzG6.mjs';
|
|
7
7
|
import { s as shortId } from './friendlyName-1UZcyDhu.mjs';
|
|
8
8
|
import { w as wantsHelp } from './wantsHelp-6bMOeNUz.mjs';
|
|
9
9
|
import 'node:os';
|
|
10
10
|
import './hyphaClient-ybivOjZo.mjs';
|
|
11
11
|
import './types-57J3Re0n.mjs';
|
|
12
12
|
import './securityContext-Cm5UTCuD.mjs';
|
|
13
|
-
import './loopVerify-
|
|
13
|
+
import './loopVerify-DALtTk2V.mjs';
|
|
14
14
|
import './instance-CeHz5C8v.mjs';
|
|
15
15
|
import 'fs';
|
|
16
16
|
import 'path';
|
|
@@ -306,6 +306,7 @@ async function featureStart(brief, opts = {}) {
|
|
|
306
306
|
const standalone = !!opts.noWorktree;
|
|
307
307
|
let childDir;
|
|
308
308
|
let base = "";
|
|
309
|
+
const leadDir = opts.directory || lead.directory;
|
|
309
310
|
if (standalone) {
|
|
310
311
|
const dir = opts.directory;
|
|
311
312
|
if (!dir) {
|
|
@@ -319,15 +320,14 @@ async function featureStart(brief, opts = {}) {
|
|
|
319
320
|
}
|
|
320
321
|
console.log(`Standalone child folder: ${childDir} (no worktree)`);
|
|
321
322
|
} else {
|
|
322
|
-
const leadDir = opts.directory || lead.directory;
|
|
323
323
|
if (!leadDir) {
|
|
324
324
|
console.error("Could not determine the lead project directory.");
|
|
325
325
|
process.exit(1);
|
|
326
326
|
}
|
|
327
|
-
const
|
|
328
|
-
base = opts.baseBranch || currentBranch(
|
|
327
|
+
const worktreeRoot = canonicalProjectRoot(leadDir);
|
|
328
|
+
base = opts.baseBranch || currentBranch(worktreeRoot) || "main";
|
|
329
329
|
try {
|
|
330
|
-
wt = createWorktree(
|
|
330
|
+
wt = createWorktree(worktreeRoot);
|
|
331
331
|
} catch (e) {
|
|
332
332
|
console.error(`Failed to create worktree: ${e.message}`);
|
|
333
333
|
process.exit(1);
|
|
@@ -335,7 +335,7 @@ async function featureStart(brief, opts = {}) {
|
|
|
335
335
|
childDir = wt.path;
|
|
336
336
|
console.log(`Worktree: ${wt.branch} \u2192 ${wt.path}`);
|
|
337
337
|
}
|
|
338
|
-
const projectRoot = canonicalProjectRoot(
|
|
338
|
+
const projectRoot = canonicalProjectRoot(leadDir || childDir);
|
|
339
339
|
const result = await machine.spawnSession({
|
|
340
340
|
directory: childDir,
|
|
341
341
|
agent: "claude",
|
|
@@ -629,7 +629,14 @@ async function featureMerge(childPartial, opts = {}) {
|
|
|
629
629
|
notifyLead,
|
|
630
630
|
childId,
|
|
631
631
|
`<crew-freeze>Your lead is merging branch ${branch} into ${base}. Stop editing files now; do not commit until told.</crew-freeze>`,
|
|
632
|
-
"crew: freeze for merge"
|
|
632
|
+
"crew: freeze for merge",
|
|
633
|
+
// #1619: URGENT. This is a pre-emption notice, and the state it must interrupt —
|
|
634
|
+
// a child mid-work — is by definition NOT idle, so at 'normal' urgency
|
|
635
|
+
// classifyInbound QUEUES it until the child stops on its own. waitForChildIdle
|
|
636
|
+
// below is supposed to observe the child halting BECAUSE of this message; a queued
|
|
637
|
+
// message causes no halt, so the wait could only ever time out, and mergeBack can
|
|
638
|
+
// then reach `git worktree remove --force` on a tree the child is still writing.
|
|
639
|
+
"urgent"
|
|
633
640
|
);
|
|
634
641
|
} catch {
|
|
635
642
|
}
|
|
@@ -649,7 +656,10 @@ async function featureMerge(childPartial, opts = {}) {
|
|
|
649
656
|
Merge was not applied: ${r.detail}
|
|
650
657
|
Resolve and run \`svamp feature done\` again.
|
|
651
658
|
</crew-rework>`,
|
|
652
|
-
"crew: rework needed"
|
|
659
|
+
"crew: rework needed",
|
|
660
|
+
// #1619: URGENT — the comment above says 're-wake the child', which at
|
|
661
|
+
// 'normal' urgency is exactly what did not happen.
|
|
662
|
+
"urgent"
|
|
653
663
|
);
|
|
654
664
|
} catch {
|
|
655
665
|
}
|
|
@@ -665,7 +675,12 @@ Resolve and run \`svamp feature done\` again.
|
|
|
665
675
|
Merge could not be applied (${r.stage}): ${r.detail}
|
|
666
676
|
Stop editing and hold \u2014 do not keep working. Once the blocker is resolved, the lead can retry \`svamp feature merge\`.
|
|
667
677
|
</crew-blocked>`,
|
|
668
|
-
"crew: merge blocked"
|
|
678
|
+
"crew: merge blocked",
|
|
679
|
+
// #1619: URGENT. #0353's whole point is to STOP a child that is 'silently
|
|
680
|
+
// LOOPING against an un-merged branch, burning tokens toward the cost
|
|
681
|
+
// ceiling'. A looping child is never idle, so a queued STOP notice arrived
|
|
682
|
+
// only once it was no longer needed.
|
|
683
|
+
"urgent"
|
|
669
684
|
);
|
|
670
685
|
} catch {
|
|
671
686
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { connectAndResolveSession } from './commands-
|
|
1
|
+
import { connectAndResolveSession } from './commands-B-yczgz_.mjs';
|
|
2
2
|
import { w as wantsHelp } from './wantsHelp-6bMOeNUz.mjs';
|
|
3
3
|
import 'node:fs';
|
|
4
4
|
import 'node:child_process';
|
|
@@ -9,12 +9,12 @@ import './friendlyName-1UZcyDhu.mjs';
|
|
|
9
9
|
import 'node:crypto';
|
|
10
10
|
import './types-57J3Re0n.mjs';
|
|
11
11
|
import './securityContext-Cm5UTCuD.mjs';
|
|
12
|
-
import './loopVerify-
|
|
12
|
+
import './loopVerify-DALtTk2V.mjs';
|
|
13
13
|
import './instance-CeHz5C8v.mjs';
|
|
14
14
|
import 'fs';
|
|
15
15
|
import 'path';
|
|
16
16
|
import 'os';
|
|
17
|
-
import './store-
|
|
17
|
+
import './store-B4E6NzG6.mjs';
|
|
18
18
|
import './fileLock-BMPwCrB6.mjs';
|
|
19
19
|
import './flushExit-C-i6SGjj.mjs';
|
|
20
20
|
|
|
@@ -111,6 +111,27 @@ async function outpostInstall(args) {
|
|
|
111
111
|
if (info.downloadBase) console.log("Direct download: " + info.downloadBase);
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
+
async function outpostRotate(args) {
|
|
115
|
+
const dl = argVal(args, "--download-base");
|
|
116
|
+
if (!args.includes("--yes") && !args.includes("-y")) {
|
|
117
|
+
console.log("This REVOKES this session's outpost token and mints a new one.");
|
|
118
|
+
console.log("Every currently-connected outpost will stop working until it re-runs the new");
|
|
119
|
+
console.log("install command. Re-run with --yes to proceed.");
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
const info = await withSession((m, id) => m.sessionRPC(id, "outpostRotateToken", dl ? { params: { downloadBase: dl } } : {}));
|
|
123
|
+
if (!info || info.error) {
|
|
124
|
+
console.error(`Could not rotate the outpost token: ${info?.error || "unknown error"}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
console.log("\u2713 outpost token rotated \u2014 the previous token no longer works.\n");
|
|
128
|
+
console.log("Any machine that was connected must re-run the install command below:\n");
|
|
129
|
+
const gen = info.install || {};
|
|
130
|
+
if (gen.unix) console.log(" macOS / Linux:\n " + gen.unix + "\n");
|
|
131
|
+
if (gen.windows) console.log(" Windows (PowerShell):\n " + gen.windows + "\n");
|
|
132
|
+
if (info.pairingCode) console.log(` \u2026or the short pairing code: ${info.pairingCode}
|
|
133
|
+
`);
|
|
134
|
+
}
|
|
114
135
|
function usage() {
|
|
115
136
|
console.log(`svamp outpost \u2014 drive a connected remote machine (FDE outpost)
|
|
116
137
|
|
|
@@ -121,6 +142,10 @@ function usage() {
|
|
|
121
142
|
stdout/stderr/exit code synchronously (like a local command).
|
|
122
143
|
With one outpost connected, --machine is optional.
|
|
123
144
|
svamp outpost install [--os <os>] Reprint the install one-liner(s) for this session's outpost
|
|
145
|
+
svamp outpost rotate [--yes] REVOKE this session's outpost token and mint a new one.
|
|
146
|
+
Use after the install one-liner has been shared somewhere it
|
|
147
|
+
should not have been. Every currently-connected outpost stops
|
|
148
|
+
working and must re-run the new install command.
|
|
124
149
|
|
|
125
150
|
With several machines connected, target each by id, e.g.:
|
|
126
151
|
svamp outpost exec --machine build-mac "uname -a"
|
|
@@ -149,6 +174,10 @@ async function outpostCommand(args) {
|
|
|
149
174
|
await outpostInstall(args.slice(1));
|
|
150
175
|
return;
|
|
151
176
|
}
|
|
177
|
+
if (sub === "rotate" || sub === "revoke") {
|
|
178
|
+
await outpostRotate(args.slice(1));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
152
181
|
console.error(`unknown outpost command: ${sub}`);
|
|
153
182
|
usage();
|
|
154
183
|
process.exit(1);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as resolveProjectRoot } from './store-
|
|
1
|
+
import { r as resolveProjectRoot } from './store-B4E6NzG6.mjs';
|
|
2
2
|
import { w as wantsHelp } from './wantsHelp-6bMOeNUz.mjs';
|
|
3
3
|
import { s as setWorkflowEnabled, i as isWorkflowEnabled, r as removeWorkflow, g as getWorkflow, l as listWorkflows, c as workflowSteps, a as saveWorkflow, b as rawWorkflow } from './store-fi0f-ff1.mjs';
|
|
4
4
|
import { v as validateCronExpr, d as describeNextFire } from './cron-hHGdb5kU.mjs';
|
|
@@ -147,7 +147,7 @@ async function workflowCommand(args) {
|
|
|
147
147
|
console.error(`Workflow not found: ${name}`);
|
|
148
148
|
process.exit(1);
|
|
149
149
|
}
|
|
150
|
-
const { runWorkflow } = await import('./run-
|
|
150
|
+
const { runWorkflow } = await import('./run-DDmeV9K3.mjs').then(function (n) { return n.e; });
|
|
151
151
|
const run = await runWorkflow(root, wf, { trigger: "manual", actor: process.env.SVAMP_SESSION_ID || null });
|
|
152
152
|
console.log(`\u25B6 ran workflow "${wf.name}" (${run.steps.length} step${run.steps.length === 1 ? "" : "s"}) \u2192 ${run.status}`);
|
|
153
153
|
for (const st of run.steps) {
|
|
@@ -61,7 +61,7 @@ async function serviceExpose(args) {
|
|
|
61
61
|
process.exit(1);
|
|
62
62
|
}
|
|
63
63
|
if (foreground) {
|
|
64
|
-
const { runFrpcTunnel } = await import('./hookSettings-
|
|
64
|
+
const { runFrpcTunnel } = await import('./hookSettings-FFc1pi8j.mjs').then(function (n) { return n.w; });
|
|
65
65
|
await runFrpcTunnel(name, ports, void 0, {
|
|
66
66
|
group,
|
|
67
67
|
groupKey,
|
|
@@ -71,7 +71,7 @@ async function serviceExpose(args) {
|
|
|
71
71
|
});
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
74
|
+
const { connectAndGetMachine } = await import('./commands-B-yczgz_.mjs');
|
|
75
75
|
const { server, machine } = await connectAndGetMachine();
|
|
76
76
|
try {
|
|
77
77
|
const status = await machine.tunnelStart({
|
|
@@ -93,7 +93,7 @@ async function serviceExpose(args) {
|
|
|
93
93
|
console.log(` port ${port}: ${url}`);
|
|
94
94
|
}
|
|
95
95
|
if (process.env.SVAMP_SESSION_ID) {
|
|
96
|
-
const { autoAddSessionLink } = await import('./agentCommands-
|
|
96
|
+
const { autoAddSessionLink } = await import('./agentCommands-D91RcjqQ.mjs');
|
|
97
97
|
let added = 0;
|
|
98
98
|
for (const [port, url] of urlEntries) {
|
|
99
99
|
const label = urlEntries.length > 1 ? `${name}:${port}` : name;
|
|
@@ -129,8 +129,8 @@ async function serviceServe(args) {
|
|
|
129
129
|
const resolvedDir = path.resolve(directory);
|
|
130
130
|
console.log(`Serving ${resolvedDir}`);
|
|
131
131
|
const http = await import('http');
|
|
132
|
-
const { serveStaticMount } = await import('./hookSettings-
|
|
133
|
-
const { getFreePort } = await import('./hookSettings-
|
|
132
|
+
const { serveStaticMount } = await import('./hookSettings-FFc1pi8j.mjs').then(function (n) { return n.x; });
|
|
133
|
+
const { getFreePort } = await import('./hookSettings-FFc1pi8j.mjs').then(function (n) { return n.w; });
|
|
134
134
|
const server = http.createServer((req, res) => {
|
|
135
135
|
const u = new URL(req.url || "/", "http://127.0.0.1");
|
|
136
136
|
let rel = u.pathname;
|
|
@@ -153,7 +153,7 @@ async function serviceServe(args) {
|
|
|
153
153
|
resolve(18080);
|
|
154
154
|
});
|
|
155
155
|
});
|
|
156
|
-
const { runFrpcTunnel } = await import('./hookSettings-
|
|
156
|
+
const { runFrpcTunnel } = await import('./hookSettings-FFc1pi8j.mjs').then(function (n) { return n.w; });
|
|
157
157
|
void server;
|
|
158
158
|
await runFrpcTunnel(name, [servePort]);
|
|
159
159
|
} catch (err) {
|
|
@@ -163,7 +163,7 @@ async function serviceServe(args) {
|
|
|
163
163
|
}
|
|
164
164
|
async function serviceList(_args) {
|
|
165
165
|
try {
|
|
166
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
166
|
+
const { connectAndGetMachine } = await import('./commands-B-yczgz_.mjs');
|
|
167
167
|
const { server, machine } = await connectAndGetMachine();
|
|
168
168
|
try {
|
|
169
169
|
const tunnels = await machine.tunnelList({});
|
|
@@ -203,11 +203,11 @@ async function serviceDelete(args) {
|
|
|
203
203
|
process.exit(1);
|
|
204
204
|
}
|
|
205
205
|
try {
|
|
206
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
206
|
+
const { connectAndGetMachine } = await import('./commands-B-yczgz_.mjs');
|
|
207
207
|
const { server, machine } = await connectAndGetMachine();
|
|
208
208
|
try {
|
|
209
209
|
await machine.tunnelStop({ name });
|
|
210
|
-
const { removeSessionLinkByService } = await import('./agentCommands-
|
|
210
|
+
const { removeSessionLinkByService } = await import('./agentCommands-D91RcjqQ.mjs');
|
|
211
211
|
removeSessionLinkByService("tunnel", name);
|
|
212
212
|
console.log(`Stopped tunnel '${name}'.`);
|
|
213
213
|
} catch (err) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
-
import { mkdir, rm, readFile,
|
|
2
|
+
import { mkdir, rm, readFile, mkdtemp, access, chmod, rename } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
@@ -65,6 +65,16 @@ function getNonoAssetName(version) {
|
|
|
65
65
|
}
|
|
66
66
|
return null;
|
|
67
67
|
}
|
|
68
|
+
async function probeNonoRuns(binaryPath) {
|
|
69
|
+
try {
|
|
70
|
+
const { stdout } = await execFileAsync(binaryPath, ["--version"], { timeout: 5e3 });
|
|
71
|
+
return { ok: true, version: stdout.trim().split("\n")[0] };
|
|
72
|
+
} catch (e) {
|
|
73
|
+
const stderr = typeof e?.stderr === "string" ? e.stderr.trim() : "";
|
|
74
|
+
const first = stderr.split("\n").find((l) => l.trim()) || e?.message || "unknown error";
|
|
75
|
+
return { ok: false, reason: first.trim() };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
68
78
|
async function installNono() {
|
|
69
79
|
const platform = process.platform;
|
|
70
80
|
if (platform !== "darwin" && platform !== "linux") {
|
|
@@ -116,23 +126,33 @@ async function installNono() {
|
|
|
116
126
|
return false;
|
|
117
127
|
}
|
|
118
128
|
console.log(`[isolation] nono ${version} checksum verified (${actual.slice(0, 12)}\u2026)`);
|
|
119
|
-
await execFileAsync("tar", [
|
|
120
|
-
"xzf",
|
|
121
|
-
tarball,
|
|
122
|
-
"-C",
|
|
123
|
-
SVAMP_BIN_DIR
|
|
124
|
-
], { timeout: 15e3 });
|
|
125
|
-
await rm(tarball, { force: true }).catch(() => {
|
|
126
|
-
});
|
|
127
129
|
const nonoPath = join(SVAMP_BIN_DIR, "nono");
|
|
128
|
-
await
|
|
130
|
+
const stageDir = await mkdtemp(join(SVAMP_BIN_DIR, ".nono-stage-"));
|
|
129
131
|
try {
|
|
130
|
-
await
|
|
131
|
-
|
|
132
|
+
await execFileAsync("tar", ["xzf", tarball, "-C", stageDir], { timeout: 15e3 });
|
|
133
|
+
await rm(tarball, { force: true }).catch(() => {
|
|
134
|
+
});
|
|
135
|
+
const staged = join(stageDir, "nono");
|
|
136
|
+
try {
|
|
137
|
+
await access(staged);
|
|
138
|
+
} catch {
|
|
139
|
+
console.warn("[isolation] Download completed but nono binary not found in the archive");
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
await chmod(staged, 493);
|
|
143
|
+
const probe = await probeNonoRuns(staged);
|
|
144
|
+
if (!probe.ok) {
|
|
145
|
+
console.error(
|
|
146
|
+
`[isolation] nono ${version} downloaded and checksum-verified, but it CANNOT RUN on this host: ${probe.reason}. Refusing to install \u2014 the previous binary (if any) is untouched. This host needs a nono build compatible with its libc; sessions will fall back to docker/podman, where --allow-write and --allow-domain are NOT enforced (#0878).`
|
|
147
|
+
);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
await rename(staged, nonoPath);
|
|
151
|
+
console.log(`[isolation] nono ${version} installed at ${nonoPath} (verified runnable: ${probe.version || "ok"})`);
|
|
132
152
|
return true;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
} finally {
|
|
154
|
+
await rm(stageDir, { recursive: true, force: true }).catch(() => {
|
|
155
|
+
});
|
|
136
156
|
}
|
|
137
157
|
} catch (e) {
|
|
138
158
|
console.warn(`[isolation] Failed to install nono: ${e.message}`);
|
|
@@ -316,4 +336,4 @@ async function detectIsolationCapabilities() {
|
|
|
316
336
|
return { available, preferred, details };
|
|
317
337
|
}
|
|
318
338
|
|
|
319
|
-
export { detectIsolationCapabilities, installNono, isolationDisabledByEnv, verifyWithHardTimeout };
|
|
339
|
+
export { detectIsolationCapabilities, installNono, isolationDisabledByEnv, probeNonoRuns, verifyWithHardTimeout };
|
|
@@ -37,6 +37,26 @@ function readDaemonState() {
|
|
|
37
37
|
function getSelfMachineId() {
|
|
38
38
|
return readDaemonState()?.machineId || null;
|
|
39
39
|
}
|
|
40
|
+
function rowDetail(result) {
|
|
41
|
+
const one = (t) => (t || "").trim().replace(/\s+/g, " ");
|
|
42
|
+
const err = one(result.stderr).slice(-200);
|
|
43
|
+
if (result.exitCode === 124 || /timed out and was killed/i.test(err)) {
|
|
44
|
+
return `TIMED OUT (may have partially completed) \u2014 ${err || "no stderr"}`;
|
|
45
|
+
}
|
|
46
|
+
const last = one((result.stdout || "").trim().split("\n").slice(-1)[0]);
|
|
47
|
+
return last || err || "";
|
|
48
|
+
}
|
|
49
|
+
const INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
50
|
+
const SAFE_SKILL_NAME = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
51
|
+
const SAFE_VERSION = /^[0-9][0-9a-zA-Z.+-]*$/;
|
|
52
|
+
function assertSafeFleetArg(value, kind, pattern) {
|
|
53
|
+
if (typeof value !== "string" || !pattern.test(value)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`refusing to fan out: ${kind} ${JSON.stringify(value)} is not a plain ${kind} (expected ${pattern.source}). This string would be interpolated into a shell command run on every machine.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
40
60
|
function partitionSelf(machines, selfId) {
|
|
41
61
|
if (!selfId) return { targets: machines, skipped: [] };
|
|
42
62
|
return {
|
|
@@ -234,8 +254,9 @@ async function fleetUpgradeClaude(opts) {
|
|
|
234
254
|
`);
|
|
235
255
|
try {
|
|
236
256
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
257
|
+
assertSafeFleetArg(version, "version", SAFE_VERSION);
|
|
237
258
|
const cmd = `claude install ${version} 2>&1 || claude update 2>&1`;
|
|
238
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
259
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
239
260
|
let failures = 0;
|
|
240
261
|
for (const r of rows) {
|
|
241
262
|
if (!r.ok) {
|
|
@@ -244,7 +265,7 @@ async function fleetUpgradeClaude(opts) {
|
|
|
244
265
|
continue;
|
|
245
266
|
}
|
|
246
267
|
const ok = r.result.exitCode === 0;
|
|
247
|
-
const detail = (r.result
|
|
268
|
+
const detail = rowDetail(r.result);
|
|
248
269
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
249
270
|
if (!ok) failures += 1;
|
|
250
271
|
}
|
|
@@ -271,8 +292,9 @@ async function fleetUpgradeCodex(opts) {
|
|
|
271
292
|
`);
|
|
272
293
|
try {
|
|
273
294
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
295
|
+
assertSafeFleetArg(version, "version", SAFE_VERSION);
|
|
274
296
|
const cmd = `bash -lc '${NPM_PREFIX_RESOLVER}; npm install -g $PREFIX_ARG @openai/codex@${version} 2>&1 | tail -5'`;
|
|
275
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
297
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
276
298
|
let failures = 0;
|
|
277
299
|
for (const r of rows) {
|
|
278
300
|
if (!r.ok) {
|
|
@@ -281,7 +303,7 @@ async function fleetUpgradeCodex(opts) {
|
|
|
281
303
|
continue;
|
|
282
304
|
}
|
|
283
305
|
const ok = r.result.exitCode === 0;
|
|
284
|
-
const detail = (r.result
|
|
306
|
+
const detail = rowDetail(r.result);
|
|
285
307
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
286
308
|
if (!ok) failures += 1;
|
|
287
309
|
}
|
|
@@ -318,7 +340,7 @@ async function fleetUpgradeNono(opts) {
|
|
|
318
340
|
try {
|
|
319
341
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
320
342
|
const cmd = `svamp daemon install-nono 2>&1 | tail -5`;
|
|
321
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
343
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
322
344
|
let failures = 0;
|
|
323
345
|
for (const r of rows) {
|
|
324
346
|
if (!r.ok) {
|
|
@@ -327,7 +349,7 @@ async function fleetUpgradeNono(opts) {
|
|
|
327
349
|
continue;
|
|
328
350
|
}
|
|
329
351
|
const ok = r.result.exitCode === 0;
|
|
330
|
-
const detail = (r.result
|
|
352
|
+
const detail = rowDetail(r.result);
|
|
331
353
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
332
354
|
if (!ok) failures += 1;
|
|
333
355
|
}
|
|
@@ -347,6 +369,7 @@ async function fleetUpgradeKimi(opts) {
|
|
|
347
369
|
await server.disconnect();
|
|
348
370
|
return;
|
|
349
371
|
}
|
|
372
|
+
assertSafeFleetArg(version, "version", SAFE_VERSION);
|
|
350
373
|
console.log(`Reconciling Kimi to ${version} on ${machines.length} machine(s)...
|
|
351
374
|
`);
|
|
352
375
|
console.log(`Note: Kimi is a global npm package (${KIMI_NPM_PACKAGE}); machines that never use it can ignore this.`);
|
|
@@ -355,7 +378,7 @@ async function fleetUpgradeKimi(opts) {
|
|
|
355
378
|
try {
|
|
356
379
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
357
380
|
const cmd = `bash -lc '${NPM_PREFIX_RESOLVER}; npm install -g $PREFIX_ARG ${KIMI_NPM_PACKAGE}@${version} 2>&1 | tail -5'`;
|
|
358
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
381
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
359
382
|
let failures = 0;
|
|
360
383
|
for (const r of rows) {
|
|
361
384
|
if (!r.ok) {
|
|
@@ -364,7 +387,7 @@ async function fleetUpgradeKimi(opts) {
|
|
|
364
387
|
continue;
|
|
365
388
|
}
|
|
366
389
|
const ok = r.result.exitCode === 0;
|
|
367
|
-
const detail = (r.result
|
|
390
|
+
const detail = rowDetail(r.result);
|
|
368
391
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
369
392
|
if (!ok) failures += 1;
|
|
370
393
|
}
|
|
@@ -395,8 +418,9 @@ async function fleetUpgradeSvamp(opts) {
|
|
|
395
418
|
try {
|
|
396
419
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
397
420
|
for (const m of skipped) printResultRow(m, "SKIP", 'self \u2014 restart separately with "svamp daemon restart"');
|
|
421
|
+
assertSafeFleetArg(version, "version", SAFE_VERSION);
|
|
398
422
|
const cmd = `bash -lc '${NPM_PREFIX_RESOLVER}; npm install -g $PREFIX_ARG svamp-cli@${version} 2>&1 | tail -5 && ${restartCmd} 2>&1 | tail -5'`;
|
|
399
|
-
const rows = await fanOut(targets, async (m) => await m.rpc.bash(cmd, void 0));
|
|
423
|
+
const rows = await fanOut(targets, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
400
424
|
let failures = 0;
|
|
401
425
|
for (const r of rows) {
|
|
402
426
|
if (!r.ok) {
|
|
@@ -405,7 +429,7 @@ async function fleetUpgradeSvamp(opts) {
|
|
|
405
429
|
continue;
|
|
406
430
|
}
|
|
407
431
|
const ok = r.result.exitCode === 0;
|
|
408
|
-
const detail = (r.result
|
|
432
|
+
const detail = rowDetail(r.result);
|
|
409
433
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
410
434
|
if (!ok) failures += 1;
|
|
411
435
|
}
|
|
@@ -445,7 +469,7 @@ async function fleetDaemonRestart(opts) {
|
|
|
445
469
|
console.log(`Restarting daemons on ${machines.length} machine(s) \u2014 ${mode}...
|
|
446
470
|
`);
|
|
447
471
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
448
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
472
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
449
473
|
let failures = 0;
|
|
450
474
|
for (const r of rows) {
|
|
451
475
|
if (!r.ok) {
|
|
@@ -454,7 +478,7 @@ async function fleetDaemonRestart(opts) {
|
|
|
454
478
|
continue;
|
|
455
479
|
}
|
|
456
480
|
const ok = r.result.exitCode === 0;
|
|
457
|
-
const detail = (r.result
|
|
481
|
+
const detail = rowDetail(r.result);
|
|
458
482
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
459
483
|
if (!ok) failures += 1;
|
|
460
484
|
}
|
|
@@ -480,8 +504,9 @@ async function fleetPushSkill(name) {
|
|
|
480
504
|
`);
|
|
481
505
|
try {
|
|
482
506
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
507
|
+
assertSafeFleetArg(name, "skill name", SAFE_SKILL_NAME);
|
|
483
508
|
const cmd = `svamp skills install ${name} --force 2>&1`;
|
|
484
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
509
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
485
510
|
let failures = 0;
|
|
486
511
|
for (const r of rows) {
|
|
487
512
|
if (!r.ok) {
|
|
@@ -490,7 +515,7 @@ async function fleetPushSkill(name) {
|
|
|
490
515
|
continue;
|
|
491
516
|
}
|
|
492
517
|
const ok = r.result.exitCode === 0;
|
|
493
|
-
const detail = (r.result
|
|
518
|
+
const detail = rowDetail(r.result);
|
|
494
519
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
495
520
|
if (!ok) failures += 1;
|
|
496
521
|
}
|
|
@@ -502,4 +527,4 @@ ${rows.length - failures}/${rows.length} updated.`);
|
|
|
502
527
|
}
|
|
503
528
|
}
|
|
504
529
|
|
|
505
|
-
export { fleetDaemonRestart, fleetDaemonRestartCommand, fleetExec, fleetPushSkill, fleetStatus, fleetUpgradeClaude, fleetUpgradeCodex, fleetUpgradeKimi, fleetUpgradeNono, fleetUpgradeSvamp, parseVersionOutput, partitionSelf };
|
|
530
|
+
export { assertSafeFleetArg, fleetDaemonRestart, fleetDaemonRestartCommand, fleetExec, fleetPushSkill, fleetStatus, fleetUpgradeClaude, fleetUpgradeCodex, fleetUpgradeKimi, fleetUpgradeNono, fleetUpgradeSvamp, parseVersionOutput, partitionSelf, rowDetail };
|