svamp-cli 0.2.342 → 0.2.346

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 (67) hide show
  1. package/bin/skills/artifact/SKILL.md +1 -1
  2. package/bin/skills/crew/SKILL.md +21 -3
  3. package/dist/DefaultTransport-vfp78fug.mjs +47 -0
  4. package/dist/GeminiTransport-3OkhtFRO.mjs +130 -0
  5. package/dist/KimiTransport-D8n009Tr.mjs +141 -0
  6. package/dist/acpAgentConfig-CObOPlA8.mjs +48 -0
  7. package/dist/acpBackend-BZ1MJhQV.mjs +922 -0
  8. package/dist/{adminCommands-CRTBDZan.mjs → adminCommands-BvsYnRDA.mjs} +2 -18
  9. package/dist/{agentCommands-BuO988tJ.mjs → agentCommands-B2Nl7Fof.mjs} +6 -23
  10. package/dist/api-BHjyC0Ph.mjs +157 -0
  11. package/dist/{auth--WS0wezs.mjs → auth-CRz8TsEl.mjs} +1 -22
  12. package/dist/bootStatus-DqtNgJch.mjs +143 -0
  13. package/dist/claudeAuth-iAerdaMr.mjs +247 -0
  14. package/dist/cli.mjs +2939 -5
  15. package/dist/codexAppServerBackend-VGsYtbqg.mjs +786 -0
  16. package/dist/codexProvider-BtxYc5Rj.mjs +68 -0
  17. package/dist/{commands-DXM17M6n.mjs → commands-BB1Kcyt-.mjs} +13 -19
  18. package/dist/{commands-DU4B-4Gh.mjs → commands-BOpoVBOM.mjs} +55 -25
  19. package/dist/{commands-C0HQiu-S.mjs → commands-C2aODeVc.mjs} +58 -33
  20. package/dist/{commands-Cwxy-C5N.mjs → commands-CiRCwjkJ.mjs} +10 -32
  21. package/dist/{commands-DDuGBSsz.mjs → commands-D1pZq3d4.mjs} +103 -41
  22. package/dist/{commands-BKb1g8Gq.mjs → commands-D69mnkYu.mjs} +20 -30
  23. package/dist/{commands-BsKgZ5sQ.mjs → commands-DW5RnHZw.mjs} +2 -19
  24. package/dist/{commands-DXLOKJOj.mjs → commands-H4PsTGn8.mjs} +58 -26
  25. package/dist/cron-hHGdb5kU.mjs +132 -0
  26. package/dist/detectIsolation-CzoDCEnh.mjs +319 -0
  27. package/dist/envFlag-COOuJ3AH.mjs +11 -0
  28. package/dist/fileLock-BMPwCrB6.mjs +80 -0
  29. package/dist/{fleet-BHzlAvnu.mjs → fleet-DM_Q7w69.mjs} +85 -24
  30. package/dist/flushExit-C-i6SGjj.mjs +37 -0
  31. package/dist/friendlyName-1UZcyDhu.mjs +586 -0
  32. package/dist/{headlessCli-C2gvOpOt.mjs → headlessCli-C8HzQvis.mjs} +3 -21
  33. package/dist/hookSettings-DYMTUgVA.mjs +10399 -0
  34. package/dist/{httpServer-Be1Hcddr.mjs → httpServer-BzEDbOGn.mjs} +14 -5
  35. package/dist/hyphaClient-ybivOjZo.mjs +49 -0
  36. package/dist/index.mjs +40 -8
  37. package/dist/instance-CeHz5C8v.mjs +203 -0
  38. package/dist/kimiProvider-DbxODebR.mjs +69 -0
  39. package/dist/kimiSetup-NGsoYB5h.mjs +48 -0
  40. package/dist/loopVerify-B47sSjxe.mjs +444 -0
  41. package/dist/machineTools-DygM1jXW.mjs +580 -0
  42. package/dist/model-CUHojegn.mjs +66 -0
  43. package/dist/{notifyCommands-0aZUwKQK.mjs → notifyCommands-D-IKIxiq.mjs} +3 -18
  44. package/dist/package-DtuINqf5.mjs +64 -0
  45. package/dist/pinnedCodex-XW4qvOAe.mjs +11 -0
  46. package/dist/pinnedNono-5ZHb6328.mjs +15 -0
  47. package/dist/{rpc-rAQQ1q8t.mjs → rpc-BB6RF4-F.mjs} +38 -6
  48. package/dist/{rpc-B2Lc2BAB.mjs → rpc-Cf0grcru.mjs} +28 -20
  49. package/dist/{run-C7QVfyw4.mjs → run-ChdsgaSD.mjs} +17 -9
  50. package/dist/run-DkjwF-zA.mjs +9695 -0
  51. package/dist/sandboxDetect-Cdzsj5TC.mjs +19 -0
  52. package/dist/{scheduler-CzF4lOe_.mjs → scheduler-BY30ZrB9.mjs} +38 -6
  53. package/dist/securityContext-Cm5UTCuD.mjs +218 -0
  54. package/dist/serveAuth-DbVLRCRR.mjs +418 -0
  55. package/dist/{serveCommands-EIEqdaVu.mjs → serveCommands-CUHUiYnZ.mjs} +13 -34
  56. package/dist/sharingNotificationSync-qGIQ-3yf.mjs +359 -0
  57. package/dist/{sideband-BoutiRJi.mjs → sideband-68HhGT0i.mjs} +1 -21
  58. package/dist/store-Bn0ByYev.mjs +347 -0
  59. package/dist/store-fi0f-ff1.mjs +184 -0
  60. package/dist/types-57J3Re0n.mjs +17 -0
  61. package/dist/wantsHelp-6bMOeNUz.mjs +5 -0
  62. package/dist/wrapCommand-CdNawSaa.mjs +117 -0
  63. package/package.json +3 -3
  64. package/dist/cli-B24CmIli.mjs +0 -2780
  65. package/dist/package-CTbGYaJG.mjs +0 -64
  66. package/dist/run-CF4kAVPW.mjs +0 -26508
  67. package/dist/sandboxDetect-DNTcbgWD.mjs +0 -12
@@ -0,0 +1,132 @@
1
+ const FIELD_RANGES = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 7]];
2
+ const MONTH_NAMES = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
3
+ const DOW_NAMES = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
4
+ function substituteNames(token, fieldIndex) {
5
+ const names = fieldIndex === 3 ? MONTH_NAMES : fieldIndex === 4 ? DOW_NAMES : null;
6
+ if (!names) return token;
7
+ return token.replace(/[A-Za-z]+/g, (word) => {
8
+ const i = names.indexOf(word.toUpperCase());
9
+ if (i < 0) return word;
10
+ return String(fieldIndex === 3 ? i + 1 : i);
11
+ });
12
+ }
13
+ function normalizeDow(set) {
14
+ if (!set.has(7)) return set;
15
+ const out = new Set(set);
16
+ out.delete(7);
17
+ out.add(0);
18
+ return out;
19
+ }
20
+ function parseField(token, [min, max]) {
21
+ const set = /* @__PURE__ */ new Set();
22
+ for (const part of token.split(",")) {
23
+ let m;
24
+ if (part === "*") {
25
+ for (let i = min; i <= max; i++) set.add(i);
26
+ } else if (m = part.match(/^\*\/(\d+)$/)) {
27
+ const s = +m[1];
28
+ if (s <= 0) throw new Error(`invalid cron step (must be >= 1): "${token}"`);
29
+ for (let i = min; i <= max; i += s) set.add(i);
30
+ } else if (m = part.match(/^(\d+)-(\d+)\/(\d+)$/)) {
31
+ const s = +m[3];
32
+ if (s <= 0) throw new Error(`invalid cron step (must be >= 1): "${token}"`);
33
+ if (+m[1] > +m[2]) throw new Error(`invalid cron range (start > end): "${token}"`);
34
+ for (let i = +m[1]; i <= +m[2]; i += s) set.add(i);
35
+ } else if (m = part.match(/^(\d+)-(\d+)$/)) {
36
+ if (+m[1] > +m[2]) throw new Error(`invalid cron range (start > end): "${token}"`);
37
+ for (let i = +m[1]; i <= +m[2]; i++) set.add(i);
38
+ } else if (m = part.match(/^(\d+)$/)) {
39
+ set.add(+m[1]);
40
+ } else throw new Error(`invalid cron field: "${token}"`);
41
+ }
42
+ for (const v of set) if (v < min || v > max) throw new Error(`cron value ${v} out of range [${min},${max}]`);
43
+ return set;
44
+ }
45
+ function parseCron(expr) {
46
+ const fields = String(expr).trim().split(/\s+/);
47
+ if (fields.length !== 5) throw new Error(`cron must have 5 fields, got ${fields.length}: "${expr}"`);
48
+ const [minute, hour, dom, month, dow] = fields.map((f, i) => parseField(substituteNames(f, i), FIELD_RANGES[i]));
49
+ return { minute, hour, dom, month, dow: normalizeDow(dow), domRestricted: fields[2] !== "*", dowRestricted: fields[4] !== "*" };
50
+ }
51
+ function validateCronExpr(expr) {
52
+ parseCron(expr);
53
+ }
54
+ function cronMatches(expr, date) {
55
+ const c = typeof expr === "string" ? parseCron(expr) : expr;
56
+ if (!c.minute.has(date.getMinutes())) return false;
57
+ if (!c.hour.has(date.getHours())) return false;
58
+ if (!c.month.has(date.getMonth() + 1)) return false;
59
+ const domOk = c.dom.has(date.getDate());
60
+ const dowOk = c.dow.has(date.getDay());
61
+ if (c.domRestricted && c.dowRestricted) return domOk || dowOk;
62
+ return domOk && dowOk;
63
+ }
64
+ const _zoneFormatters = /* @__PURE__ */ new Map();
65
+ function zoneFormatter(tz) {
66
+ let f = _zoneFormatters.get(tz);
67
+ if (!f) {
68
+ f = new Intl.DateTimeFormat("en-US", {
69
+ timeZone: tz,
70
+ hour12: false,
71
+ year: "numeric",
72
+ month: "2-digit",
73
+ day: "2-digit",
74
+ hour: "2-digit",
75
+ minute: "2-digit"
76
+ });
77
+ _zoneFormatters.set(tz, f);
78
+ }
79
+ return f;
80
+ }
81
+ function inZone(date, tz) {
82
+ if (!tz) return date;
83
+ try {
84
+ const p = zoneFormatter(tz).formatToParts(date).reduce((o, x) => {
85
+ o[x.type] = x.value;
86
+ return o;
87
+ }, {});
88
+ const midnight24 = p.hour === "24";
89
+ return new Date(+p.year, +p.month - 1, +p.day + (midnight24 ? 1 : 0), midnight24 ? 0 : +p.hour, +p.minute);
90
+ } catch {
91
+ return date;
92
+ }
93
+ }
94
+ function nextFire(expr, from, tz) {
95
+ const c = parseCron(expr);
96
+ if (tz && scanForMatch(c, from, void 0) === null) return null;
97
+ return scanForMatch(c, from, tz);
98
+ }
99
+ function scanForMatch(c, from, tz) {
100
+ const d = new Date(from.getTime());
101
+ d.setSeconds(0, 0);
102
+ d.setMinutes(d.getMinutes() + 1);
103
+ for (let i = 0; i < 366 * 24 * 60; i++) {
104
+ if (cronMatches(c, tz ? inZone(d, tz) : d)) return new Date(d.getTime());
105
+ d.setMinutes(d.getMinutes() + 1);
106
+ }
107
+ return null;
108
+ }
109
+ function describeNextFire(expr, tz, from = /* @__PURE__ */ new Date()) {
110
+ let next;
111
+ try {
112
+ next = nextFire(expr, from, tz);
113
+ } catch {
114
+ return null;
115
+ }
116
+ if (!next) return "never fires";
117
+ const shown = tz ? inZone(next, tz) : next;
118
+ const pad = (n) => String(n).padStart(2, "0");
119
+ const stamp = `${shown.getFullYear()}-${pad(shown.getMonth() + 1)}-${pad(shown.getDate())} ${pad(shown.getHours())}:${pad(shown.getMinutes())}`;
120
+ return `next ${stamp} ${tz || "local"}`;
121
+ }
122
+ function resolvePath(ctx, path) {
123
+ return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], ctx);
124
+ }
125
+ function renderTemplate(template, ctx) {
126
+ return String(template).replace(/\$\{([\w.$]+)\}/g, (_m, p) => {
127
+ const v = resolvePath(ctx, p);
128
+ return v == null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
129
+ });
130
+ }
131
+
132
+ export { cronMatches as c, describeNextFire as d, inZone as i, renderTemplate as r, validateCronExpr as v };
@@ -0,0 +1,319 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { mkdir, rm, readFile, chmod, access, mkdtemp } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { createHash } from 'node:crypto';
7
+ import { P as PINNED_NONO_VERSION, n as nonoAssetUrl, N as NONO_CHECKSUM_ASSET, e as expectedSha256 } from './pinnedNono-5ZHb6328.mjs';
8
+ import { I as ISOLATION_PREFERENCE } from './types-57J3Re0n.mjs';
9
+ import { e as envFlagEnabled } from './envFlag-COOuJ3AH.mjs';
10
+
11
+ const execFileAsync = promisify(execFile);
12
+ const VERIFY_HARD_TIMEOUT_MS = 2e4;
13
+ async function verifyWithHardTimeout(method, binaryPath, timeoutMs = VERIFY_HARD_TIMEOUT_MS) {
14
+ let timer;
15
+ try {
16
+ return await Promise.race([
17
+ verifyIsolation(method, binaryPath),
18
+ new Promise((resolve) => {
19
+ timer = setTimeout(
20
+ () => resolve({
21
+ passed: false,
22
+ error: `verification timed out after ${timeoutMs}ms (runtime unresponsive)`
23
+ }),
24
+ timeoutMs
25
+ );
26
+ })
27
+ ]);
28
+ } finally {
29
+ if (timer) clearTimeout(timer);
30
+ }
31
+ }
32
+ const SVAMP_TOOLS_DIR = join(homedir(), ".svamp", "tools");
33
+ const SVAMP_BIN_DIR = join(SVAMP_TOOLS_DIR, "bin");
34
+ async function checkCommand(command, versionArgs) {
35
+ try {
36
+ const { stdout } = await execFileAsync(command, versionArgs, {
37
+ timeout: 5e3
38
+ });
39
+ const version = stdout.trim().split("\n")[0];
40
+ return { found: true, version, path: command };
41
+ } catch {
42
+ }
43
+ const localPath = join(SVAMP_BIN_DIR, command);
44
+ try {
45
+ const { stdout } = await execFileAsync(localPath, versionArgs, {
46
+ timeout: 5e3
47
+ });
48
+ const version = stdout.trim().split("\n")[0];
49
+ return { found: true, version, path: localPath };
50
+ } catch {
51
+ }
52
+ return { found: false };
53
+ }
54
+ function getNonoAssetName(version) {
55
+ const platform = process.platform;
56
+ const arch = process.arch;
57
+ if (platform === "darwin" && arch === "arm64") {
58
+ return `nono-v${version}-aarch64-apple-darwin.tar.gz`;
59
+ }
60
+ if (platform === "darwin" && arch === "x64") {
61
+ return `nono-v${version}-x86_64-apple-darwin.tar.gz`;
62
+ }
63
+ if (platform === "linux" && arch === "x64") {
64
+ return `nono-v${version}-x86_64-unknown-linux-gnu.tar.gz`;
65
+ }
66
+ return null;
67
+ }
68
+ async function installNono() {
69
+ const platform = process.platform;
70
+ if (platform !== "darwin" && platform !== "linux") {
71
+ return false;
72
+ }
73
+ console.log(`[isolation] Installing pinned nono ${PINNED_NONO_VERSION}...`);
74
+ try {
75
+ const version = PINNED_NONO_VERSION;
76
+ const assetName = getNonoAssetName(version);
77
+ if (!assetName) {
78
+ console.warn(`[isolation] No nono binary for ${platform}/${process.arch}`);
79
+ return false;
80
+ }
81
+ const downloadUrl = nonoAssetUrl(version, assetName);
82
+ console.log(`[isolation] Downloading nono ${version} (pinned) from ${downloadUrl}...`);
83
+ await mkdir(SVAMP_BIN_DIR, { recursive: true });
84
+ const tarball = join(SVAMP_BIN_DIR, assetName);
85
+ await execFileAsync("curl", [
86
+ "-fsSL",
87
+ "-o",
88
+ tarball,
89
+ "--connect-timeout",
90
+ "10",
91
+ downloadUrl
92
+ ], { timeout: 6e4 });
93
+ let expected = null;
94
+ try {
95
+ const { stdout: sums } = await execFileAsync("curl", [
96
+ "-fsSL",
97
+ "--connect-timeout",
98
+ "10",
99
+ nonoAssetUrl(version, NONO_CHECKSUM_ASSET)
100
+ ], { timeout: 2e4, maxBuffer: 4 * 1024 * 1024 });
101
+ expected = expectedSha256(sums, assetName);
102
+ } catch {
103
+ expected = null;
104
+ }
105
+ if (!expected) {
106
+ console.error(`[isolation] Could not obtain a published SHA-256 for ${assetName} \u2014 refusing to install nono.`);
107
+ await rm(tarball, { force: true }).catch(() => {
108
+ });
109
+ return false;
110
+ }
111
+ const actual = createHash("sha256").update(await readFile(tarball)).digest("hex");
112
+ if (actual !== expected) {
113
+ console.error(`[isolation] nono checksum MISMATCH for ${assetName}: expected ${expected}, got ${actual}. Refusing to install.`);
114
+ await rm(tarball, { force: true }).catch(() => {
115
+ });
116
+ return false;
117
+ }
118
+ 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
+ const nonoPath = join(SVAMP_BIN_DIR, "nono");
128
+ await chmod(nonoPath, 493);
129
+ try {
130
+ await access(nonoPath);
131
+ console.log(`[isolation] nono ${version} installed at ${nonoPath}`);
132
+ return true;
133
+ } catch {
134
+ console.warn("[isolation] Download completed but nono binary not found");
135
+ return false;
136
+ }
137
+ } catch (e) {
138
+ console.warn(`[isolation] Failed to install nono: ${e.message}`);
139
+ return false;
140
+ }
141
+ }
142
+ async function parseIsolationTestOutput(stdout, probeFile) {
143
+ const output = stdout.trim();
144
+ const wMatch = output.match(/W=(\d+)/);
145
+ const pMatch = output.match(/P=(\d+)/);
146
+ if (!wMatch || !pMatch) {
147
+ return { passed: false, error: `unexpected test output: ${output}` };
148
+ }
149
+ const workspaceWriteOk = wMatch[1] === "0";
150
+ const probeExitCode = parseInt(pMatch[1], 10);
151
+ if (!workspaceWriteOk) {
152
+ return { passed: false, error: "sandbox blocked writes to allowed workspace path" };
153
+ }
154
+ if (probeExitCode === 0) {
155
+ try {
156
+ await access(probeFile);
157
+ return {
158
+ passed: false,
159
+ error: "writes outside workspace were NOT blocked \u2014 file leaked to host filesystem"
160
+ };
161
+ } catch {
162
+ return { passed: true };
163
+ }
164
+ }
165
+ return { passed: true };
166
+ }
167
+ async function verifyNonoIsolation(binaryPath) {
168
+ const testBase = "/tmp";
169
+ const workDir = await mkdtemp(join(testBase, "svamp-iso-work-"));
170
+ const probeFile = join(homedir(), `.svamp-iso-probe-${process.pid}`);
171
+ try {
172
+ const testScript = [
173
+ `echo ok > "${workDir}/test" 2>/dev/null; W=$?`,
174
+ `echo leak > "${probeFile}" 2>/dev/null; P=$?`,
175
+ `echo "W=$W P=$P"`
176
+ ].join("; ");
177
+ const { stdout } = await execFileAsync(binaryPath, [
178
+ "run",
179
+ "-s",
180
+ "--allow",
181
+ workDir,
182
+ // NOTE: Do NOT add --allow-cwd here. If the daemon's CWD happens to be
183
+ // $HOME (common when started interactively), --allow-cwd would grant
184
+ // access to $HOME, allowing the probe file write to succeed and making
185
+ // verification incorrectly fail ("file leaked to host filesystem").
186
+ // We already grant --allow workDir explicitly, so --allow-cwd is redundant.
187
+ "--trust-override",
188
+ "--",
189
+ "sh",
190
+ "-c",
191
+ testScript
192
+ ], { timeout: 15e3, cwd: workDir });
193
+ return parseIsolationTestOutput(stdout, probeFile);
194
+ } catch (e) {
195
+ return { passed: false, error: e.message };
196
+ } finally {
197
+ await rm(workDir, { recursive: true, force: true }).catch(() => {
198
+ });
199
+ await rm(probeFile, { force: true }).catch(() => {
200
+ });
201
+ }
202
+ }
203
+ async function verifyContainerIsolation(method, binaryPath) {
204
+ try {
205
+ await execFileAsync(binaryPath, ["info"], { timeout: 15e3, killSignal: "SIGKILL" });
206
+ } catch (e) {
207
+ return { passed: false, error: `${method} daemon not accessible: ${e.message}` };
208
+ }
209
+ try {
210
+ const { stdout } = await execFileAsync(
211
+ binaryPath,
212
+ ["run", "--rm", "--network=none", "alpine", "echo", "isolation-ok"],
213
+ { timeout: 3e4, killSignal: "SIGKILL" }
214
+ );
215
+ if (!stdout.includes("isolation-ok")) {
216
+ return { passed: false, error: `container test returned unexpected output: ${stdout.trim()}` };
217
+ }
218
+ return { passed: true };
219
+ } catch (e) {
220
+ if (e.message?.includes("pull access denied") || e.message?.includes("not found")) {
221
+ return { passed: true };
222
+ }
223
+ return { passed: false, error: `container test failed: ${e.message}` };
224
+ }
225
+ }
226
+ async function verifyIsolation(method, binaryPath) {
227
+ switch (method) {
228
+ case "nono":
229
+ return verifyNonoIsolation(binaryPath);
230
+ case "docker":
231
+ case "podman":
232
+ return verifyContainerIsolation(method, binaryPath);
233
+ default:
234
+ return { passed: false, error: `${method} is not supported for external isolation` };
235
+ }
236
+ }
237
+ function isolationDisabledByEnv(env = process.env) {
238
+ const v = (env.SVAMP_ISOLATION || "").trim().toLowerCase();
239
+ if (v && ["none", "off", "disabled", "0", "false", "no"].includes(v)) return true;
240
+ return envFlagEnabled(env.SVAMP_NO_ISOLATION) || envFlagEnabled(env.SVAMP_SKIP_NONO);
241
+ }
242
+ async function detectIsolationCapabilities() {
243
+ if (isolationDisabledByEnv()) {
244
+ console.log(
245
+ "[isolation] Disabled via env (SVAMP_ISOLATION=none) \u2014 skipping nono auto-install + runtime detection. Sessions run WITHOUT per-session sandboxing; session sharing is allowed (rely on the container/host boundary)."
246
+ );
247
+ const notFound = () => ({ found: false });
248
+ return {
249
+ available: [],
250
+ preferred: null,
251
+ disabled: true,
252
+ details: {
253
+ nono: notFound(),
254
+ docker: notFound(),
255
+ podman: notFound(),
256
+ srt: notFound(),
257
+ bwrap: notFound()
258
+ }
259
+ };
260
+ }
261
+ const checks = {
262
+ nono: checkCommand("nono", ["--version"]),
263
+ docker: checkCommand("docker", ["--version"]),
264
+ podman: checkCommand("podman", ["--version"])
265
+ };
266
+ const checkResults = await Promise.all(
267
+ Object.entries(checks).map(async ([method, promise]) => {
268
+ const detail = await promise;
269
+ return [method, detail];
270
+ })
271
+ );
272
+ const details = {
273
+ ...Object.fromEntries(checkResults),
274
+ // Include legacy types in details for backward compat
275
+ srt: { found: false },
276
+ bwrap: { found: false }
277
+ };
278
+ if (!details.nono.found) {
279
+ const installed = await installNono();
280
+ if (installed) {
281
+ details.nono = await checkCommand("nono", ["--version"]);
282
+ }
283
+ }
284
+ const foundMethods = ISOLATION_PREFERENCE.filter((m) => details[m].found);
285
+ if (foundMethods.length > 0) {
286
+ console.log(`[isolation] Found runtimes: ${foundMethods.join(", ")}. Verifying isolation...`);
287
+ await Promise.all(
288
+ foundMethods.map(async (method) => {
289
+ const binaryPath = details[method].path || method;
290
+ const result = await verifyWithHardTimeout(method, binaryPath);
291
+ details[method].verified = result.passed;
292
+ if (!result.passed) {
293
+ details[method].verificationError = result.error;
294
+ console.warn(
295
+ `[isolation] WARNING: ${method} found but verification FAILED: ${result.error}. This runtime will NOT be used for isolation.`
296
+ );
297
+ } else {
298
+ console.log(`[isolation] ${method}: verified OK`);
299
+ }
300
+ })
301
+ );
302
+ }
303
+ const available = ISOLATION_PREFERENCE.filter(
304
+ (m) => details[m].found && details[m].verified === true
305
+ );
306
+ const preferred = available.length > 0 ? available[0] : null;
307
+ if (available.length === 0 && foundMethods.length > 0) {
308
+ console.warn(
309
+ `[isolation] No isolation runtime passed verification (found: ${foundMethods.join(", ")}). Session sharing will be DISABLED.`
310
+ );
311
+ } else if (available.length === 0) {
312
+ console.log("[isolation] No isolation runtimes found. Session sharing will be unavailable.");
313
+ } else {
314
+ console.log(`[isolation] Preferred isolation method: ${preferred}`);
315
+ }
316
+ return { available, preferred, details };
317
+ }
318
+
319
+ export { detectIsolationCapabilities, installNono, isolationDisabledByEnv, verifyWithHardTimeout };
@@ -0,0 +1,11 @@
1
+ const NEGATIVE = /* @__PURE__ */ new Set(["", "0", "false", "no", "off"]);
2
+ function envFlagEnabled(value) {
3
+ if (value == null) return false;
4
+ return !NEGATIVE.has(value.trim().toLowerCase());
5
+ }
6
+ function envFlagDisabled(value) {
7
+ if (value == null) return false;
8
+ return NEGATIVE.has(value.trim().toLowerCase());
9
+ }
10
+
11
+ export { envFlagDisabled as a, envFlagEnabled as e };
@@ -0,0 +1,80 @@
1
+ import { mkdirSync, writeFileSync, readFileSync, rmSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ const _sleepSab = new Int32Array(new SharedArrayBuffer(4));
5
+ function sleepSync(ms) {
6
+ try {
7
+ Atomics.wait(_sleepSab, 0, 0, ms);
8
+ } catch {
9
+ }
10
+ }
11
+ function isProcessDead(pid) {
12
+ try {
13
+ process.kill(pid, 0);
14
+ return false;
15
+ } catch (e) {
16
+ return e?.code === "ESRCH";
17
+ }
18
+ }
19
+ function withFileLock(lockPath, fn, opts) {
20
+ const deadlineMs = opts?.deadlineMs ?? 50;
21
+ const staleMs = opts?.staleMs ?? 5e3;
22
+ const trackOwner = opts?.breakOnDeadOwner ?? false;
23
+ const pidPath = join(lockPath, "owner.pid");
24
+ const deadline = Date.now() + deadlineMs;
25
+ let held = false;
26
+ while (Date.now() < deadline) {
27
+ try {
28
+ mkdirSync(lockPath);
29
+ held = true;
30
+ if (trackOwner) {
31
+ try {
32
+ writeFileSync(pidPath, String(process.pid));
33
+ } catch {
34
+ }
35
+ }
36
+ break;
37
+ } catch {
38
+ let broke = false;
39
+ if (trackOwner) {
40
+ try {
41
+ const owner = Number(readFileSync(pidPath, "utf8"));
42
+ if (Number.isInteger(owner) && owner > 0 && owner !== process.pid && isProcessDead(owner)) {
43
+ rmSync(lockPath, { recursive: true, force: true });
44
+ broke = true;
45
+ }
46
+ } catch {
47
+ }
48
+ }
49
+ if (!broke) {
50
+ try {
51
+ if (Date.now() - statSync(lockPath).mtimeMs > staleMs) {
52
+ rmSync(lockPath, { recursive: true, force: true });
53
+ broke = true;
54
+ }
55
+ } catch {
56
+ }
57
+ }
58
+ if (broke) continue;
59
+ sleepSync(5);
60
+ }
61
+ }
62
+ if (!held) {
63
+ try {
64
+ opts?.onFailOpen?.(lockPath);
65
+ } catch {
66
+ }
67
+ }
68
+ try {
69
+ return fn();
70
+ } finally {
71
+ if (held) {
72
+ try {
73
+ rmSync(lockPath, { recursive: true, force: true });
74
+ } catch {
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ export { sleepSync as s, withFileLock as w };