svamp-cli 0.2.274 → 0.2.276
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/dist/{agentCommands-iom4Qnpt.mjs → agentCommands-DYduijxc.mjs} +5 -5
- package/dist/{auth-C5eN8Hte.mjs → auth-DF4dUys9.mjs} +1 -1
- package/dist/{cli-P3Tx1nrm.mjs → cli-BrZuHfQa.mjs} +65 -65
- package/dist/cli.mjs +2 -2
- package/dist/{commands-CKhDT3cQ.mjs → commands-7WZLaYv2.mjs} +2 -2
- package/dist/{commands-CQHlltkU.mjs → commands-BjkRYbLw.mjs} +2 -2
- package/dist/{commands-B6LByF-g.mjs → commands-Cxn4Cmts.mjs} +1 -1
- package/dist/{commands-D606feNR.mjs → commands-DINv6746.mjs} +1 -1
- package/dist/{commands-CgrbLgCc.mjs → commands-DLlgY23R.mjs} +1 -1
- package/dist/{commands-C7J1hEg3.mjs → commands-DvD1mdMU.mjs} +2 -2
- package/dist/{commands-VvErX3uk.mjs → commands-clA49kHW.mjs} +7 -7
- package/dist/{fleet-B1PDO1l8.mjs → fleet-xyz1ik_N.mjs} +2 -2
- package/dist/{frpc-Dzhtizmd.mjs → frpc-BkGcc69x.mjs} +7 -5
- package/dist/{headlessCli-1WzEZVMV.mjs → headlessCli-Je1q_pDw.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/package-D3rksT8O.mjs +64 -0
- package/dist/{pinnedClaudeCode-CgEtGkX2.mjs → pinnedClaudeCode-D8Tbz32G.mjs} +1 -1
- package/dist/{rpc-BMoQLUTg.mjs → rpc-CBa5WaBD.mjs} +1 -1
- package/dist/{rpc-CIs3wZAu.mjs → rpc-HiJYPcOU.mjs} +1 -1
- package/dist/{run-DxoIx34n.mjs → run-BRHMebDp.mjs} +4 -3
- package/dist/{run-l_8UB-9h.mjs → run-DjThStDG.mjs} +272 -68
- package/dist/{scheduler-uZ__o3N7.mjs → scheduler-D1JpZ5m8.mjs} +1 -1
- package/dist/{serveCommands-twuwB_gw.mjs → serveCommands-D-3LtTUy.mjs} +5 -5
- package/dist/{sideband-C8myzP2c.mjs → sideband-DdcXgJHf.mjs} +1 -1
- package/package.json +2 -2
- package/dist/package-BYElq4R_.mjs +0 -64
|
@@ -3,7 +3,7 @@ import fs$1, { mkdir as mkdir$1, readdir as readdir$1, readFile, writeFile as wr
|
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import fs__default, { existsSync as existsSync$1, rmSync, readFileSync as readFileSync$1, mkdirSync, writeFileSync, renameSync as renameSync$1, realpathSync as realpathSync$1, copyFileSync, unlinkSync as unlinkSync$1, readdirSync as readdirSync$1, watch, rmdirSync } from 'fs';
|
|
5
5
|
import * as path from 'path';
|
|
6
|
-
import path__default, { join, resolve as resolve$1, dirname as dirname$1, basename as basename$1 } from 'path';
|
|
6
|
+
import path__default, { join, delimiter, resolve as resolve$1, dirname as dirname$1, basename as basename$1 } from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { execFile, spawn, execSync, exec as exec$1 } from 'child_process';
|
|
9
9
|
import * as crypto from 'crypto';
|
|
@@ -1158,8 +1158,14 @@ async function readProcessTable() {
|
|
|
1158
1158
|
let stdout;
|
|
1159
1159
|
try {
|
|
1160
1160
|
const result = await execFileAsync$1("ps", ["-A", "-o", "pid=,ppid=,etime=,%cpu=,rss=,command="], {
|
|
1161
|
-
maxBuffer: 8 * 1024 * 1024
|
|
1161
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
1162
1162
|
// 8MB — plenty for any realistic process table
|
|
1163
|
+
// Hard wall-clock deadline: on a wedged host (D-state / stuck FS) `ps` can block
|
|
1164
|
+
// forever; maxBuffer bounds size, not time. Without this the awaited promise never
|
|
1165
|
+
// settles and callers (killDescendant admin RPC, Tasks-panel detectDescendants) hang.
|
|
1166
|
+
// On timeout execFile rejects → the catch below returns [] (best-effort, no crash).
|
|
1167
|
+
timeout: 5e3,
|
|
1168
|
+
killSignal: "SIGKILL"
|
|
1163
1169
|
});
|
|
1164
1170
|
stdout = result.stdout;
|
|
1165
1171
|
} catch {
|
|
@@ -1500,6 +1506,9 @@ const COOKIE_NAME = "svamp_serve_token";
|
|
|
1500
1506
|
const SVAMP_SERVE_TOKEN_PARAM = "__svamp_serve_token";
|
|
1501
1507
|
const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1502
1508
|
const DEFAULT_CACHE_MAX_SIZE = 1e3;
|
|
1509
|
+
function jsStringLiteral(value) {
|
|
1510
|
+
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
1511
|
+
}
|
|
1503
1512
|
class TokenCache {
|
|
1504
1513
|
cache = /* @__PURE__ */ new Map();
|
|
1505
1514
|
ttlMs;
|
|
@@ -1602,16 +1611,19 @@ function isStructurallyLiveJwt(token) {
|
|
|
1602
1611
|
async function verifyTokenViaHypha(token, hyphaServerUrl) {
|
|
1603
1612
|
try {
|
|
1604
1613
|
const baseUrl = hyphaServerUrl.replace(/\/$/, "");
|
|
1605
|
-
const url = `${baseUrl}/public/services/ws/
|
|
1614
|
+
const url = `${baseUrl}/public/services/ws/parse_token`;
|
|
1606
1615
|
const resp = await fetch(url, {
|
|
1607
|
-
method: "
|
|
1616
|
+
method: "POST",
|
|
1608
1617
|
headers: {
|
|
1609
|
-
Authorization: `Bearer ${token}
|
|
1618
|
+
Authorization: `Bearer ${token}`,
|
|
1619
|
+
"Content-Type": "application/json"
|
|
1610
1620
|
},
|
|
1621
|
+
body: JSON.stringify({ token }),
|
|
1611
1622
|
signal: AbortSignal.timeout(1e4)
|
|
1612
1623
|
});
|
|
1613
1624
|
if (!resp.ok) return null;
|
|
1614
1625
|
const data = await resp.json();
|
|
1626
|
+
if (data?.is_anonymous === true) return null;
|
|
1615
1627
|
const email = data?.email;
|
|
1616
1628
|
if (typeof email === "string" && email) return email.toLowerCase();
|
|
1617
1629
|
return null;
|
|
@@ -1701,10 +1713,10 @@ a:hover{text-decoration:underline}
|
|
|
1701
1713
|
<div class="status" id="status"></div>
|
|
1702
1714
|
</div>
|
|
1703
1715
|
<script>
|
|
1704
|
-
const hyphaServer = ${
|
|
1705
|
-
const redirectUrl = ${
|
|
1706
|
-
const cookieName = ${
|
|
1707
|
-
const tokenParam = ${
|
|
1716
|
+
const hyphaServer = ${jsStringLiteral(this.hyphaServerUrl)};
|
|
1717
|
+
const redirectUrl = ${jsStringLiteral(redirectUrl)};
|
|
1718
|
+
const cookieName = ${jsStringLiteral(COOKIE_NAME)};
|
|
1719
|
+
const tokenParam = ${jsStringLiteral(SVAMP_SERVE_TOKEN_PARAM)};
|
|
1708
1720
|
|
|
1709
1721
|
const btn = document.getElementById('login-btn');
|
|
1710
1722
|
const statusEl = document.getElementById('status');
|
|
@@ -2201,7 +2213,7 @@ class ServeManager {
|
|
|
2201
2213
|
throw new Error(`Path does not exist: ${resolvedDir}`);
|
|
2202
2214
|
}
|
|
2203
2215
|
if (this.mounts.has(spec.name)) {
|
|
2204
|
-
await this.removeMount(spec.name);
|
|
2216
|
+
await this.removeMount(spec.name, { replacing: true });
|
|
2205
2217
|
}
|
|
2206
2218
|
const access = spec.access ?? "link";
|
|
2207
2219
|
if (access === "owner" && !spec.ownerEmail) {
|
|
@@ -2240,7 +2252,7 @@ class ServeManager {
|
|
|
2240
2252
|
/**
|
|
2241
2253
|
* Remove a mount. If no mounts remain, stop Caddy + auth proxy.
|
|
2242
2254
|
*/
|
|
2243
|
-
async removeMount(name) {
|
|
2255
|
+
async removeMount(name, opts) {
|
|
2244
2256
|
if (!this.mounts.has(name)) {
|
|
2245
2257
|
throw new Error(`Mount '${name}' not found`);
|
|
2246
2258
|
}
|
|
@@ -2260,7 +2272,7 @@ class ServeManager {
|
|
|
2260
2272
|
}
|
|
2261
2273
|
this.persist();
|
|
2262
2274
|
this.log(`Mount removed: ${name}`);
|
|
2263
|
-
if (this.mounts.size === 0) {
|
|
2275
|
+
if (this.mounts.size === 0 && !opts?.replacing) {
|
|
2264
2276
|
await this.shutdown();
|
|
2265
2277
|
}
|
|
2266
2278
|
}
|
|
@@ -2403,7 +2415,12 @@ class ServeManager {
|
|
|
2403
2415
|
cwd: cfg.workdir ?? process.cwd(),
|
|
2404
2416
|
env: { ...process.env, ...cfg.env ?? {} },
|
|
2405
2417
|
stdio: ["ignore", "pipe", "pipe"],
|
|
2406
|
-
|
|
2418
|
+
// #0483: run in its OWN process group so we can kill the whole tree. Managed mounts
|
|
2419
|
+
// are typically wrappers (npm start / npm run dev / python -m …) whose real listener
|
|
2420
|
+
// is a GRANDCHILD; killing only the direct pid leaves the grandchild holding the port
|
|
2421
|
+
// → next warmup hits EADDRINUSE → mount permanently broken + orphan accumulation.
|
|
2422
|
+
// killManagedTree() below signals -pid (the group).
|
|
2423
|
+
detached: true
|
|
2407
2424
|
});
|
|
2408
2425
|
child.stdout?.on("data", (d) => {
|
|
2409
2426
|
const line = d.toString().trimEnd();
|
|
@@ -2429,10 +2446,7 @@ class ServeManager {
|
|
|
2429
2446
|
this.log(`Managed process '${name}' ready on 127.0.0.1:${cfg.port}`);
|
|
2430
2447
|
}).catch((err) => {
|
|
2431
2448
|
this.log(`Managed process '${name}' warmup failed: ${err.message}`);
|
|
2432
|
-
|
|
2433
|
-
child.kill("SIGTERM");
|
|
2434
|
-
} catch {
|
|
2435
|
-
}
|
|
2449
|
+
this.killManagedTree(child, "SIGTERM");
|
|
2436
2450
|
this.managedProcs.delete(name);
|
|
2437
2451
|
throw err;
|
|
2438
2452
|
});
|
|
@@ -2440,12 +2454,33 @@ class ServeManager {
|
|
|
2440
2454
|
child,
|
|
2441
2455
|
pid: child.pid ?? 0,
|
|
2442
2456
|
lastRequestAt: Date.now(),
|
|
2457
|
+
activeConns: 0,
|
|
2443
2458
|
warmupPromise
|
|
2444
2459
|
};
|
|
2445
2460
|
this.managedProcs.set(name, newHandle);
|
|
2446
2461
|
this.log(`Managed process '${name}' starting: ${cfg.command} ${(cfg.args ?? []).join(" ")} (port ${cfg.port})`);
|
|
2447
2462
|
return warmupPromise;
|
|
2448
2463
|
}
|
|
2464
|
+
/**
|
|
2465
|
+
* #0483: kill the managed child's whole PROCESS GROUP (it was spawned detached, so it leads
|
|
2466
|
+
* its own group). This reaps grandchildren (the real listener behind an npm/python wrapper)
|
|
2467
|
+
* that a direct child.kill() would orphan, leaving the port bound. Falls back to a direct
|
|
2468
|
+
* child.kill() if the group signal fails (e.g. pid already gone).
|
|
2469
|
+
*/
|
|
2470
|
+
killManagedTree(child, signal) {
|
|
2471
|
+
const pid = child.pid;
|
|
2472
|
+
if (pid && pid > 0) {
|
|
2473
|
+
try {
|
|
2474
|
+
process.kill(-pid, signal);
|
|
2475
|
+
return;
|
|
2476
|
+
} catch {
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
try {
|
|
2480
|
+
child.kill(signal);
|
|
2481
|
+
} catch {
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2449
2484
|
/** Poll a URL until it returns <500 or the deadline passes. */
|
|
2450
2485
|
async warmupProbe(url, timeoutMs) {
|
|
2451
2486
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -2478,17 +2513,11 @@ class ServeManager {
|
|
|
2478
2513
|
const h = this.managedProcs.get(name);
|
|
2479
2514
|
if (!h) return;
|
|
2480
2515
|
this.managedProcs.delete(name);
|
|
2481
|
-
try {
|
|
2482
|
-
h.child.kill("SIGTERM");
|
|
2483
|
-
} catch {
|
|
2484
|
-
}
|
|
2485
2516
|
const child = h.child;
|
|
2517
|
+
this.killManagedTree(child, "SIGTERM");
|
|
2486
2518
|
await new Promise((resolve) => {
|
|
2487
2519
|
const t = setTimeout(() => {
|
|
2488
|
-
|
|
2489
|
-
child.kill("SIGKILL");
|
|
2490
|
-
} catch {
|
|
2491
|
-
}
|
|
2520
|
+
this.killManagedTree(child, "SIGKILL");
|
|
2492
2521
|
resolve();
|
|
2493
2522
|
}, 5e3);
|
|
2494
2523
|
child.once("exit", () => {
|
|
@@ -2508,6 +2537,10 @@ class ServeManager {
|
|
|
2508
2537
|
if (!cfg || !cfg.idleTimeoutSec || cfg.idleTimeoutSec <= 0) continue;
|
|
2509
2538
|
const h = this.managedProcs.get(name);
|
|
2510
2539
|
if (!h || h.warmupPromise) continue;
|
|
2540
|
+
if (h.activeConns > 0) {
|
|
2541
|
+
h.lastRequestAt = now;
|
|
2542
|
+
continue;
|
|
2543
|
+
}
|
|
2511
2544
|
if (now - h.lastRequestAt >= cfg.idleTimeoutSec * 1e3) {
|
|
2512
2545
|
this.log(`Idle eviction: stopping '${name}' (idle ${Math.round((now - h.lastRequestAt) / 1e3)}s \u2265 ${cfg.idleTimeoutSec}s)`);
|
|
2513
2546
|
this.stopManagedProcess(name).catch(() => {
|
|
@@ -2538,7 +2571,9 @@ class ServeManager {
|
|
|
2538
2571
|
...this.port > 0 ? { port: this.port } : {}
|
|
2539
2572
|
};
|
|
2540
2573
|
try {
|
|
2541
|
-
|
|
2574
|
+
const tmp = `${this.persistFile}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
2575
|
+
fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
|
|
2576
|
+
fs.renameSync(tmp, this.persistFile);
|
|
2542
2577
|
} catch (err) {
|
|
2543
2578
|
this.log(`Error persisting serve state: ${err.message}`);
|
|
2544
2579
|
}
|
|
@@ -2676,12 +2711,27 @@ a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:und
|
|
|
2676
2711
|
if (req.method === "PUT") {
|
|
2677
2712
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
2678
2713
|
const ws = fs.createWriteStream(filePath);
|
|
2714
|
+
let done = false;
|
|
2715
|
+
const cleanupPartial = () => {
|
|
2716
|
+
if (done) return;
|
|
2717
|
+
done = true;
|
|
2718
|
+
ws.destroy();
|
|
2719
|
+
fs.unlink(filePath, () => {
|
|
2720
|
+
});
|
|
2721
|
+
};
|
|
2722
|
+
req.on("aborted", cleanupPartial);
|
|
2723
|
+
req.on("close", () => {
|
|
2724
|
+
if (!ws.writableFinished) cleanupPartial();
|
|
2725
|
+
});
|
|
2679
2726
|
req.pipe(ws);
|
|
2680
2727
|
ws.on("finish", () => {
|
|
2728
|
+
done = true;
|
|
2681
2729
|
res.writeHead(201);
|
|
2682
2730
|
res.end();
|
|
2683
2731
|
});
|
|
2684
2732
|
ws.on("error", (err) => {
|
|
2733
|
+
if (done) return;
|
|
2734
|
+
done = true;
|
|
2685
2735
|
res.writeHead(500);
|
|
2686
2736
|
res.end(err.message);
|
|
2687
2737
|
});
|
|
@@ -2709,7 +2759,18 @@ a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:und
|
|
|
2709
2759
|
}
|
|
2710
2760
|
}
|
|
2711
2761
|
const handle = this.managedProcs.get(mount.name);
|
|
2712
|
-
if (handle)
|
|
2762
|
+
if (handle) {
|
|
2763
|
+
handle.lastRequestAt = Date.now();
|
|
2764
|
+
handle.activeConns++;
|
|
2765
|
+
let released = false;
|
|
2766
|
+
const release = () => {
|
|
2767
|
+
if (released) return;
|
|
2768
|
+
released = true;
|
|
2769
|
+
handle.activeConns = Math.max(0, handle.activeConns - 1);
|
|
2770
|
+
handle.lastRequestAt = Date.now();
|
|
2771
|
+
};
|
|
2772
|
+
res.on("close", release);
|
|
2773
|
+
}
|
|
2713
2774
|
const targetPath = mountResolvedByHost ? req.url || "/" : (basePath || "/") + (url.search || "");
|
|
2714
2775
|
const proxyReq = http.request({
|
|
2715
2776
|
hostname: "127.0.0.1",
|
|
@@ -2810,7 +2871,17 @@ Connection: close\r
|
|
|
2810
2871
|
}
|
|
2811
2872
|
}
|
|
2812
2873
|
const handle = this.managedProcs.get(mount.name);
|
|
2813
|
-
if (handle)
|
|
2874
|
+
if (handle) {
|
|
2875
|
+
handle.lastRequestAt = Date.now();
|
|
2876
|
+
handle.activeConns++;
|
|
2877
|
+
let released = false;
|
|
2878
|
+
clientSocket.on("close", () => {
|
|
2879
|
+
if (released) return;
|
|
2880
|
+
released = true;
|
|
2881
|
+
handle.activeConns = Math.max(0, handle.activeConns - 1);
|
|
2882
|
+
handle.lastRequestAt = Date.now();
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2814
2885
|
const targetPath = mountResolvedByHost ? req.url || "/" : (url.pathname.slice(`/${mountName}`.length) || "/") + (url.search || "");
|
|
2815
2886
|
const upstream = net.connect(cfg.port, "127.0.0.1", () => {
|
|
2816
2887
|
const lines = [`${req.method} ${targetPath} HTTP/1.1`];
|
|
@@ -2902,7 +2973,7 @@ Connection: close\r
|
|
|
2902
2973
|
const mount = this.mounts.get(mountName);
|
|
2903
2974
|
const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
|
|
2904
2975
|
try {
|
|
2905
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
2976
|
+
const { FrpcTunnel } = await import('./frpc-BkGcc69x.mjs');
|
|
2906
2977
|
let tunnel;
|
|
2907
2978
|
tunnel = new FrpcTunnel({
|
|
2908
2979
|
name: tunnelName,
|
|
@@ -4297,6 +4368,14 @@ class ChannelOutbox {
|
|
|
4297
4368
|
file;
|
|
4298
4369
|
byChannel = /* @__PURE__ */ new Map();
|
|
4299
4370
|
seqByChannel = /* @__PURE__ */ new Map();
|
|
4371
|
+
// #0500: per-channel MONOTONIC high-water seq that survives TTL eviction/compaction. seqByChannel
|
|
4372
|
+
// is rebuilt from RETAINED records on every reload, so once a channel's replies all age out (>1h
|
|
4373
|
+
// idle) its seq would reset to 1 — a long-poll receiver holding a pre-eviction cursor N would then
|
|
4374
|
+
// silently drop the new seq=1..N replies (since() filters seq > cursor). highWater is never lowered
|
|
4375
|
+
// and is persisted to a small sidecar (_outbox_seq.json) so seq stays monotonic across eviction AND
|
|
4376
|
+
// daemon restarts. Cross-process safe: re-read + max before each bump (via reload()).
|
|
4377
|
+
highWater = /* @__PURE__ */ new Map();
|
|
4378
|
+
seqFile;
|
|
4300
4379
|
emitter = new EventEmitter();
|
|
4301
4380
|
appendsSinceCompact = 0;
|
|
4302
4381
|
// #0362: change-detection for reload() — a long-poll receiver reuses ONE instance and only
|
|
@@ -4307,12 +4386,14 @@ class ChannelOutbox {
|
|
|
4307
4386
|
constructor(projectDir) {
|
|
4308
4387
|
const dir = join$1(projectDir, ".svamp", "channels");
|
|
4309
4388
|
this.file = join$1(dir, "_outbox.jsonl");
|
|
4389
|
+
this.seqFile = join$1(dir, "_outbox_seq.json");
|
|
4310
4390
|
this.emitter.setMaxListeners(0);
|
|
4311
4391
|
try {
|
|
4312
4392
|
mkdirSync$1(dir, { recursive: true });
|
|
4313
4393
|
} catch {
|
|
4314
4394
|
}
|
|
4315
4395
|
this._load();
|
|
4396
|
+
this._loadHighWater();
|
|
4316
4397
|
}
|
|
4317
4398
|
/** Current on-disk (mtimeMs, size), or (0, -1) if the file does not exist. */
|
|
4318
4399
|
_stat() {
|
|
@@ -4335,6 +4416,7 @@ class ChannelOutbox {
|
|
|
4335
4416
|
this.byChannel.clear();
|
|
4336
4417
|
this.seqByChannel.clear();
|
|
4337
4418
|
this._load();
|
|
4419
|
+
this._loadHighWater();
|
|
4338
4420
|
return true;
|
|
4339
4421
|
}
|
|
4340
4422
|
_load() {
|
|
@@ -4357,6 +4439,7 @@ class ChannelOutbox {
|
|
|
4357
4439
|
arr.push(reply);
|
|
4358
4440
|
this.byChannel.set(channelId, arr);
|
|
4359
4441
|
this.seqByChannel.set(channelId, Math.max(this.seqByChannel.get(channelId) || 0, reply.seq));
|
|
4442
|
+
this.highWater.set(channelId, Math.max(this.highWater.get(channelId) || 0, reply.seq));
|
|
4360
4443
|
}
|
|
4361
4444
|
for (const [ch, arr] of this.byChannel) if (arr.length > MAX_PER_CHANNEL) this.byChannel.set(ch, arr.slice(-MAX_PER_CHANNEL));
|
|
4362
4445
|
} catch {
|
|
@@ -4384,6 +4467,30 @@ class ChannelOutbox {
|
|
|
4384
4467
|
} catch {
|
|
4385
4468
|
}
|
|
4386
4469
|
}
|
|
4470
|
+
/** #0500: merge the durable per-channel high-water sidecar into memory (never lowers a value). */
|
|
4471
|
+
_loadHighWater() {
|
|
4472
|
+
try {
|
|
4473
|
+
if (!existsSync(this.seqFile)) return;
|
|
4474
|
+
const obj = JSON.parse(readFileSync(this.seqFile, "utf8"));
|
|
4475
|
+
for (const [ch, seq] of Object.entries(obj)) {
|
|
4476
|
+
if (typeof seq === "number" && Number.isFinite(seq)) {
|
|
4477
|
+
this.highWater.set(ch, Math.max(this.highWater.get(ch) || 0, seq));
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
} catch {
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
/** #0500: atomically persist the high-water map so seq survives eviction + restart. Best-effort. */
|
|
4484
|
+
_persistHighWater() {
|
|
4485
|
+
try {
|
|
4486
|
+
const obj = {};
|
|
4487
|
+
for (const [ch, seq] of this.highWater) obj[ch] = seq;
|
|
4488
|
+
const tmp = `${this.seqFile}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
4489
|
+
writeFileSync$1(tmp, JSON.stringify(obj));
|
|
4490
|
+
renameSync(tmp, this.seqFile);
|
|
4491
|
+
} catch {
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4387
4494
|
_evict(channelId) {
|
|
4388
4495
|
const cutoff = Date.now() - TTL_MS;
|
|
4389
4496
|
let arr = this.byChannel.get(channelId);
|
|
@@ -4394,8 +4501,11 @@ class ChannelOutbox {
|
|
|
4394
4501
|
}
|
|
4395
4502
|
/** Append a reply addressed to `to`. Assigns seq + ts, persists, and wakes waiters. */
|
|
4396
4503
|
append(channelId, r) {
|
|
4397
|
-
|
|
4504
|
+
this.reload();
|
|
4505
|
+
const seq = Math.max(this.highWater.get(channelId) || 0, this.seqByChannel.get(channelId) || 0) + 1;
|
|
4398
4506
|
this.seqByChannel.set(channelId, seq);
|
|
4507
|
+
this.highWater.set(channelId, seq);
|
|
4508
|
+
this._persistHighWater();
|
|
4399
4509
|
const reply = { seq, ts: Date.now(), to: r.to, body: r.body, ...r.correlationId ? { correlationId: r.correlationId } : {} };
|
|
4400
4510
|
const arr = this.byChannel.get(channelId) || [];
|
|
4401
4511
|
arr.push(reply);
|
|
@@ -4412,6 +4522,7 @@ class ChannelOutbox {
|
|
|
4412
4522
|
}
|
|
4413
4523
|
if (++this.appendsSinceCompact >= COMPACT_EVERY) {
|
|
4414
4524
|
this.appendsSinceCompact = 0;
|
|
4525
|
+
this.reload();
|
|
4415
4526
|
this._compact();
|
|
4416
4527
|
}
|
|
4417
4528
|
this.emitter.emit(channelId, reply);
|
|
@@ -4469,6 +4580,7 @@ class ChannelOutbox {
|
|
|
4469
4580
|
}
|
|
4470
4581
|
/** Drop a channel's outbox (on channel delete) — best-effort rewrite of the log. */
|
|
4471
4582
|
purge(channelId) {
|
|
4583
|
+
if (existsSync(this.file)) this.reload();
|
|
4472
4584
|
this.byChannel.delete(channelId);
|
|
4473
4585
|
this.seqByChannel.delete(channelId);
|
|
4474
4586
|
if (!existsSync(this.file)) return;
|
|
@@ -5704,7 +5816,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5704
5816
|
const tunnels = handlers.tunnels;
|
|
5705
5817
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
5706
5818
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
5707
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
5819
|
+
const { FrpcTunnel } = await import('./frpc-BkGcc69x.mjs');
|
|
5708
5820
|
const tunnel = new FrpcTunnel({
|
|
5709
5821
|
name: params.name,
|
|
5710
5822
|
ports: params.ports,
|
|
@@ -5852,7 +5964,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5852
5964
|
authorizeRequest(context, currentMetadata.sharing, "view");
|
|
5853
5965
|
const sm = handlers.serveManager;
|
|
5854
5966
|
if (!sm) throw new Error("Serve manager not available");
|
|
5855
|
-
|
|
5967
|
+
const info = sm.getInfo();
|
|
5968
|
+
const isAdmin = serveCallerTrusted(context);
|
|
5969
|
+
return { ...info, mounts: (info.mounts || []).map((m) => sanitizeMountForRole(m, isAdmin)) };
|
|
5856
5970
|
},
|
|
5857
5971
|
/**
|
|
5858
5972
|
* Aggregate frpc tunnel health for all serve mounts.
|
|
@@ -6178,7 +6292,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6178
6292
|
}
|
|
6179
6293
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
6180
6294
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
6181
|
-
const { toolsForRole } = await import('./sideband-
|
|
6295
|
+
const { toolsForRole } = await import('./sideband-DdcXgJHf.mjs');
|
|
6182
6296
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
6183
6297
|
return fmt(r2);
|
|
6184
6298
|
}
|
|
@@ -6277,7 +6391,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6277
6391
|
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
6278
6392
|
const callId = "call_" + Math.random().toString(16).slice(2, 12);
|
|
6279
6393
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
6280
|
-
const { queryCore } = await import('./commands-
|
|
6394
|
+
const { queryCore } = await import('./commands-DINv6746.mjs');
|
|
6281
6395
|
const timeout = c.reply?.timeout_sec || 120;
|
|
6282
6396
|
let result;
|
|
6283
6397
|
try {
|
|
@@ -6372,7 +6486,9 @@ ${d?.error || "not found"}`;
|
|
|
6372
6486
|
});
|
|
6373
6487
|
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
6374
6488
|
const cursor = Math.max(0, Number(kwargs.cursor) || 0);
|
|
6375
|
-
const
|
|
6489
|
+
const waitRaw = Number(kwargs.wait ?? 25);
|
|
6490
|
+
const waitSec = Number.isFinite(waitRaw) ? waitRaw : 25;
|
|
6491
|
+
const waitMs = Math.min(Math.max(0, waitSec * 1e3), 6e4);
|
|
6376
6492
|
const outbox = new ChannelOutbox(dir);
|
|
6377
6493
|
const deadline = Date.now() + waitMs;
|
|
6378
6494
|
for (; ; ) {
|
|
@@ -6483,7 +6599,7 @@ function writeInboundContext(sessionId, ctx) {
|
|
|
6483
6599
|
try {
|
|
6484
6600
|
const p = ctxPath(sessionId);
|
|
6485
6601
|
mkdirSync$1(join$1(SVAMP_HOME$2, "inbound"), { recursive: true });
|
|
6486
|
-
const tmp = p
|
|
6602
|
+
const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;
|
|
6487
6603
|
writeFileSync$1(tmp, JSON.stringify({ ...ctx, deliveredAt: Date.now() }));
|
|
6488
6604
|
renameSync(tmp, p);
|
|
6489
6605
|
} catch {
|
|
@@ -7084,7 +7200,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
7084
7200
|
const skillCtxFor = (c) => ({
|
|
7085
7201
|
channelsServiceId,
|
|
7086
7202
|
baseUrl: channelsBaseUrl,
|
|
7087
|
-
|
|
7203
|
+
// #0512: embed ONLY the channel-wide shared_key. The previous `|| callers?.[0]?.key`
|
|
7204
|
+
// fallback leaked one specific caller's PRIVATE per-key credential into the skill body,
|
|
7205
|
+
// which is served by the UNAUTHENTICATED channelDescribe (and view-tier getChannelSkill) —
|
|
7206
|
+
// an anonymous reader could then impersonate that caller (send as them + read their outbox).
|
|
7207
|
+
// For a per-key channel (no shared_key) key is undefined and generateSkillBody renders the
|
|
7208
|
+
// <your-key> placeholder, which is the correct instruction for a per-key caller.
|
|
7209
|
+
key: c.identity?.shared_key,
|
|
7088
7210
|
// The session this skill is being copied FROM — baked into `dynamic` channel
|
|
7089
7211
|
// instructions as the routing target so the copied link lands in THIS session.
|
|
7090
7212
|
session: sessionId
|
|
@@ -7467,7 +7589,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
7467
7589
|
name: c.name,
|
|
7468
7590
|
skillUrl,
|
|
7469
7591
|
sendUrl,
|
|
7470
|
-
|
|
7592
|
+
// #0512: only the channel-wide shared_key — never an individual caller's private key
|
|
7593
|
+
// (this getChannelSkill result is reachable at view tier).
|
|
7594
|
+
key: c.identity?.shared_key,
|
|
7471
7595
|
hyphaKeyless: (c.identity?.hypha_allow || []).length > 0,
|
|
7472
7596
|
bind: c.bind,
|
|
7473
7597
|
bindMode: mode,
|
|
@@ -8713,6 +8837,23 @@ function handleClaudeMessage(ctx, msg) {
|
|
|
8713
8837
|
}
|
|
8714
8838
|
}
|
|
8715
8839
|
|
|
8840
|
+
const KNOWN_AGENT_LAUNCHERS = ["claude", "codex", "gemini"];
|
|
8841
|
+
function agentLauncherExists(cmd, env = process.env) {
|
|
8842
|
+
if (!cmd) return false;
|
|
8843
|
+
if (cmd.includes("/") || cmd.includes("\\")) return existsSync$1(cmd);
|
|
8844
|
+
const dirs = (env.PATH || "").split(delimiter).filter(Boolean);
|
|
8845
|
+
const exts = process.platform === "win32" ? ["", ...(env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)] : [""];
|
|
8846
|
+
for (const dir of dirs) {
|
|
8847
|
+
for (const ext of exts) {
|
|
8848
|
+
if (existsSync$1(join(dir, cmd + ext))) return true;
|
|
8849
|
+
}
|
|
8850
|
+
}
|
|
8851
|
+
return false;
|
|
8852
|
+
}
|
|
8853
|
+
function detectAvailableAgents(env = process.env) {
|
|
8854
|
+
return KNOWN_AGENT_LAUNCHERS.filter((c) => agentLauncherExists(c, env));
|
|
8855
|
+
}
|
|
8856
|
+
|
|
8716
8857
|
const INLINE_ARTIFACT_MAX_BYTES = 512 * 1024;
|
|
8717
8858
|
const MAX_STORED_MESSAGE_BYTES = 2 * 1024 * 1024;
|
|
8718
8859
|
const MIN_DATA_URI_SCAN_CHARS = INLINE_ARTIFACT_MAX_BYTES;
|
|
@@ -12077,7 +12218,31 @@ async function verifyIsolation(method, binaryPath) {
|
|
|
12077
12218
|
return { passed: false, error: `${method} is not supported for external isolation` };
|
|
12078
12219
|
}
|
|
12079
12220
|
}
|
|
12221
|
+
function isolationDisabledByEnv(env = process.env) {
|
|
12222
|
+
const v = (env.SVAMP_ISOLATION || "").trim().toLowerCase();
|
|
12223
|
+
if (v && ["none", "off", "disabled", "0", "false", "no"].includes(v)) return true;
|
|
12224
|
+
const truthy = (s) => !!s && !["", "0", "false", "no", "off"].includes(s.trim().toLowerCase());
|
|
12225
|
+
return truthy(env.SVAMP_NO_ISOLATION) || truthy(env.SVAMP_SKIP_NONO);
|
|
12226
|
+
}
|
|
12080
12227
|
async function detectIsolationCapabilities() {
|
|
12228
|
+
if (isolationDisabledByEnv()) {
|
|
12229
|
+
console.log(
|
|
12230
|
+
"[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)."
|
|
12231
|
+
);
|
|
12232
|
+
const notFound = () => ({ found: false });
|
|
12233
|
+
return {
|
|
12234
|
+
available: [],
|
|
12235
|
+
preferred: null,
|
|
12236
|
+
disabled: true,
|
|
12237
|
+
details: {
|
|
12238
|
+
nono: notFound(),
|
|
12239
|
+
docker: notFound(),
|
|
12240
|
+
podman: notFound(),
|
|
12241
|
+
srt: notFound(),
|
|
12242
|
+
bwrap: notFound()
|
|
12243
|
+
}
|
|
12244
|
+
};
|
|
12245
|
+
}
|
|
12081
12246
|
const checks = {
|
|
12082
12247
|
nono: checkCommand("nono", ["--version"]),
|
|
12083
12248
|
docker: checkCommand("docker", ["--version"]),
|
|
@@ -12567,6 +12732,7 @@ class ProcessSupervisor {
|
|
|
12567
12732
|
throw new Error(`Process '${entry.spec.name}' is already running`);
|
|
12568
12733
|
}
|
|
12569
12734
|
entry.stopping = false;
|
|
12735
|
+
this.clearTimers(entry);
|
|
12570
12736
|
if (entry.spec.desiredStopped) {
|
|
12571
12737
|
entry.spec.desiredStopped = false;
|
|
12572
12738
|
await this.persistSpec(entry.spec);
|
|
@@ -12625,6 +12791,8 @@ class ProcessSupervisor {
|
|
|
12625
12791
|
this.clearTimers(entry);
|
|
12626
12792
|
await this.killChild(entry.child);
|
|
12627
12793
|
entry.child = void 0;
|
|
12794
|
+
} else {
|
|
12795
|
+
this.clearTimers(entry);
|
|
12628
12796
|
}
|
|
12629
12797
|
if (entry.deleted) return;
|
|
12630
12798
|
entry.stopping = false;
|
|
@@ -12856,6 +13024,7 @@ class ProcessSupervisor {
|
|
|
12856
13024
|
spawnProcess(entry) {
|
|
12857
13025
|
const { spec, state } = entry;
|
|
12858
13026
|
if (entry.deleted) return;
|
|
13027
|
+
state.consecutiveProbeFailures = 0;
|
|
12859
13028
|
try {
|
|
12860
13029
|
const env = { ...process.env, ...spec.env ?? {} };
|
|
12861
13030
|
const child = spawn(spec.command, spec.args, {
|
|
@@ -12960,6 +13129,7 @@ class ProcessSupervisor {
|
|
|
12960
13129
|
);
|
|
12961
13130
|
entry.restartTimer = setTimeout(() => {
|
|
12962
13131
|
if (entry.deleted || entry.stopping) return;
|
|
13132
|
+
if (entry.child) return;
|
|
12963
13133
|
state.restartCount++;
|
|
12964
13134
|
state.status = "starting";
|
|
12965
13135
|
this.spawnProcess(entry);
|
|
@@ -12988,6 +13158,8 @@ class ProcessSupervisor {
|
|
|
12988
13158
|
const timeoutMs = (probe.timeout ?? DEFAULT_PROBE_TIMEOUT_S) * 1e3;
|
|
12989
13159
|
const threshold = probe.failureThreshold ?? DEFAULT_PROBE_FAILURE_THRESHOLD;
|
|
12990
13160
|
const url = `http://localhost:${probe.port}${urlPath}`;
|
|
13161
|
+
const probedChild = entry.child;
|
|
13162
|
+
const stillSameChild = () => !entry.deleted && !entry.stopping && entry.child === probedChild && entry.state.status === "running";
|
|
12991
13163
|
try {
|
|
12992
13164
|
const controller = new AbortController();
|
|
12993
13165
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -12997,6 +13169,7 @@ class ProcessSupervisor {
|
|
|
12997
13169
|
} finally {
|
|
12998
13170
|
clearTimeout(timer);
|
|
12999
13171
|
}
|
|
13172
|
+
if (!stillSameChild()) return;
|
|
13000
13173
|
const ok = resp.ok;
|
|
13001
13174
|
entry.state.lastProbe = { ok, timestamp: Date.now(), statusCode: resp.status };
|
|
13002
13175
|
if (ok) {
|
|
@@ -13011,6 +13184,7 @@ class ProcessSupervisor {
|
|
|
13011
13184
|
}
|
|
13012
13185
|
}
|
|
13013
13186
|
} catch (err) {
|
|
13187
|
+
if (!stillSameChild()) return;
|
|
13014
13188
|
entry.state.lastProbe = { ok: false, timestamp: Date.now(), error: err.message };
|
|
13015
13189
|
entry.state.consecutiveProbeFailures++;
|
|
13016
13190
|
console.warn(
|
|
@@ -13041,7 +13215,12 @@ class ProcessSupervisor {
|
|
|
13041
13215
|
return;
|
|
13042
13216
|
}
|
|
13043
13217
|
console.log(`[SUPERVISOR] Process '${entry.spec.name}' TTL: expires in ${remainingS.toFixed(0)}s`);
|
|
13044
|
-
|
|
13218
|
+
const MAX_DELAY = 2147483647;
|
|
13219
|
+
const delayMs = remainingS * 1e3;
|
|
13220
|
+
entry.ttlTimer = setTimeout(
|
|
13221
|
+
() => delayMs > MAX_DELAY ? this.setupTTL(entry) : this.expireProcess(entry),
|
|
13222
|
+
Math.min(delayMs, MAX_DELAY)
|
|
13223
|
+
);
|
|
13045
13224
|
}
|
|
13046
13225
|
expireProcess(entry) {
|
|
13047
13226
|
if (entry.deleted) return;
|
|
@@ -13973,7 +14152,7 @@ function summarizeExtensions(extensions, maxExtensions = DEFAULT_LOOP_MAX_EXTENS
|
|
|
13973
14152
|
const n = grantedExtensionCount(extensions, maxExtensions);
|
|
13974
14153
|
if (n === 0) return "";
|
|
13975
14154
|
const turns = extensionTurns(extensions, maxExtensions);
|
|
13976
|
-
const reasons = extensions.slice(0, maxExtensions).map((e) => (e
|
|
14155
|
+
const reasons = extensions.slice(0, maxExtensions).map((e) => (e?.reason || "").trim()).filter(Boolean).join("; ").slice(0, 300);
|
|
13977
14156
|
return `${n} extension${n === 1 ? "" : "s"} granted (+${turns} turns)${reasons ? `: ${reasons}` : ""}`;
|
|
13978
14157
|
}
|
|
13979
14158
|
const DEFAULT_LOOP_STUCK_CHECKPOINTS = 3;
|
|
@@ -13981,7 +14160,7 @@ const DEFAULT_LOOP_MAX_AUTORESUMES = Number.POSITIVE_INFINITY;
|
|
|
13981
14160
|
const PROGRESS_HISTORY_CAP = 12;
|
|
13982
14161
|
function resolveStuckLimit(envValue) {
|
|
13983
14162
|
const n = Number(envValue);
|
|
13984
|
-
if (Number.isFinite(n) && n >= 1) return Math.floor(n);
|
|
14163
|
+
if (Number.isFinite(n) && n >= 1) return Math.min(Math.floor(n), PROGRESS_HISTORY_CAP - 1);
|
|
13985
14164
|
return DEFAULT_LOOP_STUCK_CHECKPOINTS;
|
|
13986
14165
|
}
|
|
13987
14166
|
function resolveMaxAutoResumes(envValue) {
|
|
@@ -14039,7 +14218,8 @@ function resolveEvaluatorModel(envValue) {
|
|
|
14039
14218
|
function parseEvaluatorVerdict(text) {
|
|
14040
14219
|
if (!text) return null;
|
|
14041
14220
|
const candidates = [];
|
|
14042
|
-
|
|
14221
|
+
const openStack = [];
|
|
14222
|
+
let inStr = false, esc = false;
|
|
14043
14223
|
for (let i = 0; i < text.length; i++) {
|
|
14044
14224
|
const c = text[i];
|
|
14045
14225
|
if (inStr) {
|
|
@@ -14055,15 +14235,11 @@ function parseEvaluatorVerdict(text) {
|
|
|
14055
14235
|
if (c === '"') {
|
|
14056
14236
|
inStr = true;
|
|
14057
14237
|
} else if (c === "{") {
|
|
14058
|
-
|
|
14059
|
-
depth++;
|
|
14238
|
+
openStack.push(i);
|
|
14060
14239
|
} else if (c === "}") {
|
|
14061
|
-
|
|
14062
|
-
|
|
14063
|
-
|
|
14064
|
-
candidates.push(text.slice(start, i + 1));
|
|
14065
|
-
start = -1;
|
|
14066
|
-
}
|
|
14240
|
+
const open = openStack.pop();
|
|
14241
|
+
if (open !== void 0) {
|
|
14242
|
+
candidates.push(text.slice(open, i + 1));
|
|
14067
14243
|
}
|
|
14068
14244
|
}
|
|
14069
14245
|
}
|
|
@@ -15329,7 +15505,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
15329
15505
|
const key = s.started_at ?? "\u2205";
|
|
15330
15506
|
if (key === announcedLoopStart) return;
|
|
15331
15507
|
announcedLoopStart = key;
|
|
15332
|
-
const iter = typeof s.iteration === "number" ? s.iteration : 0;
|
|
15508
|
+
const iter = s.ledger && typeof s.ledger.turns === "number" && s.ledger.turns > 0 ? s.ledger.turns : typeof s.iteration === "number" ? s.iteration : 0;
|
|
15333
15509
|
const plural = iter === 1 ? "" : "s";
|
|
15334
15510
|
const gaveUpCfg = readLoopJson(directory, sessionId, "loop.config.json") || {};
|
|
15335
15511
|
const curMax = typeof gaveUpCfg.max_iterations === "number" ? gaveUpCfg.max_iterations : iter;
|
|
@@ -15957,7 +16133,7 @@ async function startDaemon(options) {
|
|
|
15957
16133
|
try {
|
|
15958
16134
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
15959
16135
|
if (!dir) return;
|
|
15960
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
16136
|
+
const { reconcileServiceLinks } = await import('./agentCommands-DYduijxc.mjs');
|
|
15961
16137
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
15962
16138
|
const config = readSvampConfig(configPath);
|
|
15963
16139
|
const entries = Array.from(urls.entries());
|
|
@@ -15975,7 +16151,7 @@ async function startDaemon(options) {
|
|
|
15975
16151
|
}
|
|
15976
16152
|
}
|
|
15977
16153
|
async function createExposedTunnel(spec) {
|
|
15978
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
16154
|
+
const { FrpcTunnel } = await import('./frpc-BkGcc69x.mjs');
|
|
15979
16155
|
const tunnel = new FrpcTunnel({
|
|
15980
16156
|
name: spec.name,
|
|
15981
16157
|
ports: spec.ports,
|
|
@@ -16003,7 +16179,7 @@ async function startDaemon(options) {
|
|
|
16003
16179
|
ensureAutoInstalledCommands(logger);
|
|
16004
16180
|
(async () => {
|
|
16005
16181
|
try {
|
|
16006
|
-
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-
|
|
16182
|
+
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-D8Tbz32G.mjs');
|
|
16007
16183
|
beginClaudeVersionReconcile((msg) => logger.log(msg));
|
|
16008
16184
|
} catch (e) {
|
|
16009
16185
|
logger.log(`[claude-version] check failed: ${e?.message || e}`);
|
|
@@ -16685,10 +16861,11 @@ ${parts.join("\n")}`);
|
|
|
16685
16861
|
logger.log(`[Session ${sessionId}] loop was cancelled during verification \u2014 not re-kicking (#0308)`);
|
|
16686
16862
|
return;
|
|
16687
16863
|
}
|
|
16688
|
-
const
|
|
16864
|
+
const curLs = readLoopState(directory, sessionId) || ls;
|
|
16865
|
+
const ledger = curLs.ledger ?? ls.ledger;
|
|
16689
16866
|
const budget = ls.budget;
|
|
16690
|
-
const progress_history = ls.progress_history;
|
|
16691
|
-
const extensions = ls.extensions;
|
|
16867
|
+
const progress_history = curLs.progress_history ?? ls.progress_history;
|
|
16868
|
+
const extensions = curLs.extensions ?? ls.extensions;
|
|
16692
16869
|
if (result.action === "rekick") {
|
|
16693
16870
|
const cond = ls.task || "";
|
|
16694
16871
|
writeGoalLoopState(directory, sessionId, {
|
|
@@ -16817,6 +16994,7 @@ ${parts.join("\n")}`);
|
|
|
16817
16994
|
let consecutiveOverloadRetries = 0;
|
|
16818
16995
|
let overloadBailedThisTurn = false;
|
|
16819
16996
|
let pendingCompactTurn = false;
|
|
16997
|
+
let noIsolationWarned = false;
|
|
16820
16998
|
const RATELIMIT_CFG = getRateLimitRetryConfig();
|
|
16821
16999
|
let currentTurnMessage;
|
|
16822
17000
|
let rateLimitRetryCount = 0;
|
|
@@ -16924,6 +17102,14 @@ ${parts.join("\n")}`);
|
|
|
16924
17102
|
} else if (shouldIsolateSession()) {
|
|
16925
17103
|
logger.log(`[Session ${sessionId}] WARNING: No isolation runtime (nono/docker/podman) available. Session is NOT sandboxed.`);
|
|
16926
17104
|
sessionMetadata = { ...sessionMetadata, isolationMethod: void 0 };
|
|
17105
|
+
if (!isolationCapabilities.disabled && !noIsolationWarned) {
|
|
17106
|
+
noIsolationWarned = true;
|
|
17107
|
+
sessionService.pushMessage({
|
|
17108
|
+
type: "message",
|
|
17109
|
+
level: "warning",
|
|
17110
|
+
message: "This session requested sandboxing (a security context / --isolate) but no isolation runtime (nono, docker, or podman) is available on this machine \u2014 it is running WITHOUT a sandbox. Install a runtime, or set SVAMP_ISOLATION=none to run unsandboxed intentionally (e.g. when the machine is already a container)."
|
|
17111
|
+
}, "event");
|
|
17112
|
+
}
|
|
16927
17113
|
} else {
|
|
16928
17114
|
sessionMetadata = { ...sessionMetadata, isolationMethod: void 0 };
|
|
16929
17115
|
}
|
|
@@ -16972,6 +17158,22 @@ ${parts.join("\n")}`);
|
|
|
16972
17158
|
spawnEnv.SVAMP_SANDBOXED = "1";
|
|
16973
17159
|
logger.log(`[Session ${sessionId}] Credential staging: HOME=${stagedCredentials.homePath}, SVAMP_SANDBOXED=1`);
|
|
16974
17160
|
}
|
|
17161
|
+
if (!agentLauncherExists("claude")) {
|
|
17162
|
+
const others = detectAvailableAgents().filter((a) => a !== "claude");
|
|
17163
|
+
const hint = others.length ? ` This machine has: ${others.join(", ")} \u2014 start a ${others[0]} session instead.` : " No agent launchers were found on this machine.";
|
|
17164
|
+
logger.log(`[Session ${sessionId}] Claude launcher ('claude') not found on PATH \u2014 aborting spawn (available: ${detectAvailableAgents().join(", ") || "none"})`);
|
|
17165
|
+
sessionService.pushMessage({
|
|
17166
|
+
type: "message",
|
|
17167
|
+
level: "error",
|
|
17168
|
+
message: `Claude Code (\`claude\`) isn't installed on this machine (${os$1.hostname()}), so this Claude session can't run.${hint}`
|
|
17169
|
+
}, "event");
|
|
17170
|
+
sessionMetadata = { ...sessionMetadata, lifecycleState: "idle" };
|
|
17171
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
17172
|
+
sessionWasProcessing = false;
|
|
17173
|
+
signalProcessing(false);
|
|
17174
|
+
sessionService.sendSessionEnd();
|
|
17175
|
+
return void 0;
|
|
17176
|
+
}
|
|
16975
17177
|
const child = spawn(spawnCommand, spawnArgs, {
|
|
16976
17178
|
cwd: directory,
|
|
16977
17179
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -16988,7 +17190,7 @@ ${parts.join("\n")}`);
|
|
|
16988
17190
|
child.on("error", (err) => {
|
|
16989
17191
|
logger.log(`[Session ${sessionId}] Claude process error: ${err.message}`);
|
|
16990
17192
|
sessionService.pushMessage(
|
|
16991
|
-
{ type: "message", message: `Agent process
|
|
17193
|
+
{ type: "message", level: "error", message: `Agent process failed to start: ${err.message}. Please ensure the Claude Code CLI is installed on this machine.` },
|
|
16992
17194
|
"event"
|
|
16993
17195
|
);
|
|
16994
17196
|
sessionWasProcessing = false;
|
|
@@ -18165,18 +18367,19 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
18165
18367
|
});
|
|
18166
18368
|
},
|
|
18167
18369
|
onIssue: async (params) => {
|
|
18168
|
-
const { issueRpc } = await import('./rpc-
|
|
18370
|
+
const { issueRpc } = await import('./rpc-HiJYPcOU.mjs');
|
|
18169
18371
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
18170
18372
|
},
|
|
18171
18373
|
onWorkflow: async (params) => {
|
|
18172
|
-
const { workflowRpc } = await import('./rpc-
|
|
18374
|
+
const { workflowRpc } = await import('./rpc-CBa5WaBD.mjs');
|
|
18173
18375
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
18174
18376
|
},
|
|
18175
18377
|
onRipgrep: async (args, cwd) => {
|
|
18176
|
-
const {
|
|
18378
|
+
const { execFile } = await import('child_process');
|
|
18177
18379
|
const rgCwd = cwd || directory;
|
|
18380
|
+
const argv = Array.isArray(args) ? args : String(args).split(/\s+/).filter(Boolean);
|
|
18178
18381
|
return new Promise((resolve2, reject) => {
|
|
18179
|
-
|
|
18382
|
+
execFile("rg", argv, { cwd: rgCwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
|
|
18180
18383
|
if (err && !stdout) {
|
|
18181
18384
|
reject(new Error(err.message));
|
|
18182
18385
|
} else {
|
|
@@ -18780,18 +18983,19 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
18780
18983
|
});
|
|
18781
18984
|
},
|
|
18782
18985
|
onIssue: async (params) => {
|
|
18783
|
-
const { issueRpc } = await import('./rpc-
|
|
18986
|
+
const { issueRpc } = await import('./rpc-HiJYPcOU.mjs');
|
|
18784
18987
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
18785
18988
|
},
|
|
18786
18989
|
onWorkflow: async (params) => {
|
|
18787
|
-
const { workflowRpc } = await import('./rpc-
|
|
18990
|
+
const { workflowRpc } = await import('./rpc-CBa5WaBD.mjs');
|
|
18788
18991
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
18789
18992
|
},
|
|
18790
18993
|
onRipgrep: async (args, cwd) => {
|
|
18791
|
-
const {
|
|
18994
|
+
const { execFile } = await import('child_process');
|
|
18792
18995
|
const rgCwd = cwd || directory;
|
|
18996
|
+
const argv = Array.isArray(args) ? args : String(args).split(/\s+/).filter(Boolean);
|
|
18793
18997
|
return new Promise((resolve2, reject) => {
|
|
18794
|
-
|
|
18998
|
+
execFile("rg", argv, { cwd: rgCwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
|
|
18795
18999
|
if (err && !stdout) {
|
|
18796
19000
|
reject(new Error(err.message));
|
|
18797
19001
|
} else {
|
|
@@ -19676,7 +19880,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
19676
19880
|
}
|
|
19677
19881
|
if (persistedSessions.length > 0) {
|
|
19678
19882
|
try {
|
|
19679
|
-
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-
|
|
19883
|
+
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-D8Tbz32G.mjs');
|
|
19680
19884
|
await awaitClaudeVersionReady();
|
|
19681
19885
|
} catch {
|
|
19682
19886
|
}
|
|
@@ -19873,7 +20077,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
19873
20077
|
const PING_TIMEOUT_MS = 15e3;
|
|
19874
20078
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
19875
20079
|
const RECONNECT_JITTER_MS = 2500;
|
|
19876
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
20080
|
+
const { WorkflowScheduler } = await import('./scheduler-D1JpZ5m8.mjs');
|
|
19877
20081
|
const workflowScheduler = new WorkflowScheduler({
|
|
19878
20082
|
projectRoots: () => {
|
|
19879
20083
|
const dirs = /* @__PURE__ */ new Set();
|