svamp-cli 0.2.319 → 0.2.321
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/{adminCommands-DBADX_Uo.mjs → adminCommands-BRiz5afS.mjs} +1 -1
- package/dist/{agentCommands-BJeGwkeX.mjs → agentCommands-Cve_0_8K.mjs} +32 -6
- package/dist/{auth-0NUGwOc0.mjs → auth-DrLE8Yp-.mjs} +1 -1
- package/dist/{cli-CWElpiIq.mjs → cli-xMKB7O8k.mjs} +89 -85
- package/dist/cli.mjs +2 -2
- package/dist/{commands-BsaTwKnd.mjs → commands-BxHc_Exm.mjs} +3 -3
- package/dist/{commands-C0lwV0U3.mjs → commands-C-QNVo3l.mjs} +5 -3
- package/dist/{commands-DubEbPAY.mjs → commands-CXgDZgqh.mjs} +4 -2
- package/dist/{commands-AdVHZXK6.mjs → commands-D1d9snmv.mjs} +1 -1
- package/dist/{commands-CJWgDDl5.mjs → commands-D9-YqAI3.mjs} +5 -3
- package/dist/{commands-CLgdrTSU.mjs → commands-DfRahEsO.mjs} +5 -3
- package/dist/{commands-DMSkWb0h.mjs → commands-Fvgfzyo3.mjs} +34 -9
- package/dist/{commands-BY1lmXaM.mjs → commands-slA1eX13.mjs} +1 -1
- package/dist/{fleet-OKnU0cYK.mjs → fleet-umFHXPoE.mjs} +2 -2
- package/dist/{headlessCli-CCO0IFYl.mjs → headlessCli-DgJ15daM.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{notifyCommands-4GUy4Mcl.mjs → notifyCommands-Cip4lIYS.mjs} +1 -1
- package/dist/package-DJRwvqcx.mjs +64 -0
- package/dist/{pinnedClaudeCode-dbXgsGNK.mjs → pinnedClaudeCode-BkZfX5pW.mjs} +1 -1
- package/dist/{rpc-FGp88Yxp.mjs → rpc-Bz9w7JV9.mjs} +1 -1
- package/dist/{rpc-TXF-di9u.mjs → rpc-CLoO6Z5Z.mjs} +1 -1
- package/dist/{run-09AkgeGM.mjs → run-CKLatoxa.mjs} +268 -94
- package/dist/{run-7TVWXnOM.mjs → run-CxuKTe0N.mjs} +1 -1
- package/dist/{scheduler-b-ysyKIj.mjs → scheduler-Bs0nAlhs.mjs} +1 -1
- package/dist/{serveCommands-D3W7w0G6.mjs → serveCommands-Bi7DJs-3.mjs} +33 -8
- package/dist/{sideband-C1db5sMF.mjs → sideband-CFbMRvzr.mjs} +1 -1
- package/package.json +3 -3
- package/dist/package-BEEqsQhN.mjs +0 -64
|
@@ -1261,11 +1261,11 @@ async function killDescendant(rootPid, targetPid, signal = "SIGTERM") {
|
|
|
1261
1261
|
}
|
|
1262
1262
|
}
|
|
1263
1263
|
|
|
1264
|
-
function svampHome() {
|
|
1264
|
+
function svampHome$1() {
|
|
1265
1265
|
return process.env.SVAMP_HOME || join(homedir(), ".svamp");
|
|
1266
1266
|
}
|
|
1267
1267
|
function cacheFile() {
|
|
1268
|
-
return join(svampHome(), "instance-config.json");
|
|
1268
|
+
return join(svampHome$1(), "instance-config.json");
|
|
1269
1269
|
}
|
|
1270
1270
|
const CONFIG_FILENAME = "svamp.json";
|
|
1271
1271
|
let _config = null;
|
|
@@ -1308,7 +1308,7 @@ function readCache() {
|
|
|
1308
1308
|
}
|
|
1309
1309
|
function writeCache(cfg) {
|
|
1310
1310
|
try {
|
|
1311
|
-
mkdirSync(svampHome(), { recursive: true });
|
|
1311
|
+
mkdirSync(svampHome$1(), { recursive: true });
|
|
1312
1312
|
writeFileSync(cacheFile(), JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
1313
1313
|
} catch {
|
|
1314
1314
|
}
|
|
@@ -1444,7 +1444,7 @@ function getSkillsCollection() {
|
|
|
1444
1444
|
}
|
|
1445
1445
|
|
|
1446
1446
|
function getMachineFingerprint() {
|
|
1447
|
-
const idFile = join(
|
|
1447
|
+
const idFile = join(svampHome(), "machine-id");
|
|
1448
1448
|
try {
|
|
1449
1449
|
const existing = readFileSync$1(idFile, "utf-8").trim();
|
|
1450
1450
|
if (existing) return existing;
|
|
@@ -1452,15 +1452,18 @@ function getMachineFingerprint() {
|
|
|
1452
1452
|
}
|
|
1453
1453
|
const id = randomUUID();
|
|
1454
1454
|
try {
|
|
1455
|
-
mkdirSync(
|
|
1455
|
+
mkdirSync(svampHome(), { recursive: true });
|
|
1456
1456
|
writeFileSync(idFile, id + "\n");
|
|
1457
1457
|
} catch {
|
|
1458
1458
|
}
|
|
1459
1459
|
return id;
|
|
1460
1460
|
}
|
|
1461
|
+
function svampHome() {
|
|
1462
|
+
return process.env.SVAMP_HOME || join(homedir(), ".svamp");
|
|
1463
|
+
}
|
|
1461
1464
|
const FRP_VERSION = "0.68.0";
|
|
1462
|
-
const
|
|
1463
|
-
const
|
|
1465
|
+
const binDir = () => join(svampHome(), "bin");
|
|
1466
|
+
const frpcBin = () => join(binDir(), platform() === "win32" ? "frpc.exe" : "frpc");
|
|
1464
1467
|
const _livingFrpcPids = /* @__PURE__ */ new Set();
|
|
1465
1468
|
function isFrpcSubdomainConflict(errorLine) {
|
|
1466
1469
|
return /router config conflict|already exists/i.test(errorLine);
|
|
@@ -1469,15 +1472,17 @@ function shouldGiveUpOnConflict(args) {
|
|
|
1469
1472
|
if (args.everStartedProxy) return false;
|
|
1470
1473
|
return args.conflictCount >= args.minAttempts && args.elapsedMs >= args.minElapsedMs;
|
|
1471
1474
|
}
|
|
1472
|
-
function selectOrphanedFrpcPids(psOutput, selfPid, livingPids) {
|
|
1475
|
+
function selectOrphanedFrpcPids(psOutput, selfPid, livingPids, managedBinPath = frpcBin()) {
|
|
1473
1476
|
const pids = [];
|
|
1474
1477
|
for (const line of psOutput.split("\n")) {
|
|
1475
|
-
const m = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
1478
|
+
const m = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
|
|
1476
1479
|
if (!m) continue;
|
|
1477
1480
|
const pid = Number(m[1]);
|
|
1478
|
-
const
|
|
1479
|
-
|
|
1481
|
+
const ppid = Number(m[2]);
|
|
1482
|
+
const cmd = m[3];
|
|
1483
|
+
if (!cmd.includes(".svamp/bin/frpc") && !cmd.includes(managedBinPath)) continue;
|
|
1480
1484
|
if (pid === selfPid || livingPids.has(pid)) continue;
|
|
1485
|
+
if (ppid !== 1) continue;
|
|
1481
1486
|
pids.push(pid);
|
|
1482
1487
|
}
|
|
1483
1488
|
return pids;
|
|
@@ -1485,7 +1490,7 @@ function selectOrphanedFrpcPids(psOutput, selfPid, livingPids) {
|
|
|
1485
1490
|
function killOrphanedFrpc(log) {
|
|
1486
1491
|
if (process.platform === "win32") return;
|
|
1487
1492
|
try {
|
|
1488
|
-
const out = execSync("ps -axo pid=,command=", { encoding: "utf-8" });
|
|
1493
|
+
const out = execSync("ps -axo pid=,ppid=,command=", { encoding: "utf-8" });
|
|
1489
1494
|
const pids = selectOrphanedFrpcPids(out, process.pid, _livingFrpcPids);
|
|
1490
1495
|
for (const pid of pids) {
|
|
1491
1496
|
try {
|
|
@@ -1494,7 +1499,7 @@ function killOrphanedFrpc(log) {
|
|
|
1494
1499
|
}
|
|
1495
1500
|
}
|
|
1496
1501
|
if (pids.length) {
|
|
1497
|
-
log(`Reaped ${pids.length} orphaned frpc process(es) from a prior daemon generation \u2014 they were retrying stale subdomains and causing frps router-config-conflict churn.`);
|
|
1502
|
+
log(`Reaped ${pids.length} orphaned frpc process(es) (reparented to init) from a prior daemon generation \u2014 they were retrying stale subdomains and causing frps router-config-conflict churn.`);
|
|
1498
1503
|
}
|
|
1499
1504
|
} catch {
|
|
1500
1505
|
}
|
|
@@ -1542,10 +1547,10 @@ function getFrpcDownloadUrl() {
|
|
|
1542
1547
|
}
|
|
1543
1548
|
let _frpcDownloadInFlight = null;
|
|
1544
1549
|
async function ensureFrpc(log) {
|
|
1545
|
-
if (existsSync$1(
|
|
1550
|
+
if (existsSync$1(frpcBin())) {
|
|
1546
1551
|
try {
|
|
1547
|
-
const out = execSync(`"${
|
|
1548
|
-
if (out === FRP_VERSION) return
|
|
1552
|
+
const out = execSync(`"${frpcBin()}" --version`, { stdio: "pipe", timeout: 5e3 }).toString().trim();
|
|
1553
|
+
if (out === FRP_VERSION) return frpcBin();
|
|
1549
1554
|
(log || console.log)(`frpc version mismatch: ${out} \u2260 ${FRP_VERSION}, re-downloading...`);
|
|
1550
1555
|
} catch {
|
|
1551
1556
|
(log || console.log)("frpc binary broken or invalid signature, re-downloading...");
|
|
@@ -1559,11 +1564,11 @@ async function ensureFrpc(log) {
|
|
|
1559
1564
|
}
|
|
1560
1565
|
async function downloadFrpc(log) {
|
|
1561
1566
|
try {
|
|
1562
|
-
if (existsSync$1(
|
|
1567
|
+
if (existsSync$1(frpcBin())) unlinkSync(frpcBin());
|
|
1563
1568
|
} catch {
|
|
1564
1569
|
}
|
|
1565
1570
|
const logger = log || console.log;
|
|
1566
|
-
mkdirSync(
|
|
1571
|
+
mkdirSync(binDir(), { recursive: true });
|
|
1567
1572
|
const url = getFrpcDownloadUrl();
|
|
1568
1573
|
logger(`Downloading frpc ${FRP_VERSION} from ${url}...`);
|
|
1569
1574
|
const ctrl = new AbortController();
|
|
@@ -1584,23 +1589,23 @@ async function downloadFrpc(log) {
|
|
|
1584
1589
|
if (platform() === "win32") {
|
|
1585
1590
|
throw new Error("Windows ZIP extraction not implemented \u2014 please install frpc manually");
|
|
1586
1591
|
}
|
|
1587
|
-
const tmpTar = join(
|
|
1592
|
+
const tmpTar = join(binDir(), `frpc-${FRP_VERSION}-${process.pid}-${Math.random().toString(36).slice(2, 8)}.tar.gz`);
|
|
1588
1593
|
writeFileSync(tmpTar, buffer);
|
|
1589
1594
|
try {
|
|
1590
1595
|
const dirName = `frp_${FRP_VERSION}_${platform() === "darwin" ? "darwin" : "linux"}_${arch() === "x64" ? "amd64" : arch()}`;
|
|
1591
1596
|
execSync(
|
|
1592
|
-
`tar -xzf "${tmpTar}" -C "${
|
|
1597
|
+
`tar -xzf "${tmpTar}" -C "${binDir()}" --strip-components=1 "${dirName}/frpc"`,
|
|
1593
1598
|
{ stdio: "pipe" }
|
|
1594
1599
|
);
|
|
1595
|
-
chmodSync(
|
|
1596
|
-
logger(`frpc installed at ${
|
|
1600
|
+
chmodSync(frpcBin(), 493);
|
|
1601
|
+
logger(`frpc installed at ${frpcBin()}`);
|
|
1597
1602
|
} finally {
|
|
1598
1603
|
try {
|
|
1599
1604
|
unlinkSync(tmpTar);
|
|
1600
1605
|
} catch {
|
|
1601
1606
|
}
|
|
1602
1607
|
}
|
|
1603
|
-
return
|
|
1608
|
+
return frpcBin();
|
|
1604
1609
|
}
|
|
1605
1610
|
function tomlStr(v) {
|
|
1606
1611
|
return String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
@@ -1799,7 +1804,7 @@ class FrpcTunnel {
|
|
|
1799
1804
|
healthCheckInterval: options.healthCheckInterval
|
|
1800
1805
|
};
|
|
1801
1806
|
});
|
|
1802
|
-
const configDir = join(
|
|
1807
|
+
const configDir = join(svampHome(), "frpc");
|
|
1803
1808
|
mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
1804
1809
|
try {
|
|
1805
1810
|
chmodSync(configDir, 448);
|
|
@@ -2967,9 +2972,9 @@ const NON_ADMIN_SECRET_DIR_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
2967
2972
|
".azure",
|
|
2968
2973
|
".npmrc"
|
|
2969
2974
|
]);
|
|
2970
|
-
function assertNonAdminMountDirSafe(directory, homeDir) {
|
|
2971
|
-
const resolved = path.resolve(directory);
|
|
2972
|
-
const home = path.resolve(homeDir);
|
|
2975
|
+
function assertNonAdminMountDirSafe(directory, homeDir, resolveReal = defaultResolveReal) {
|
|
2976
|
+
const resolved = resolveReal(path.resolve(directory));
|
|
2977
|
+
const home = resolveReal(path.resolve(homeDir));
|
|
2973
2978
|
if (resolved === home) {
|
|
2974
2979
|
throw new Error("Access denied: shared users may not serve the home directory root (exposes all dotfiles)");
|
|
2975
2980
|
}
|
|
@@ -2978,12 +2983,24 @@ function assertNonAdminMountDirSafe(directory, homeDir) {
|
|
|
2978
2983
|
if (!insideHome) {
|
|
2979
2984
|
throw new Error("Access denied: shared users may only serve directories within the home directory");
|
|
2980
2985
|
}
|
|
2981
|
-
for (const seg of
|
|
2982
|
-
if (
|
|
2983
|
-
|
|
2986
|
+
for (const seg of rel.split(path.sep)) {
|
|
2987
|
+
if (seg.startsWith(".")) {
|
|
2988
|
+
const known = NON_ADMIN_SECRET_DIR_SEGMENTS.has(seg);
|
|
2989
|
+
throw new Error(known ? `Access denied: refusing to serve a sensitive directory (${seg})` : `Access denied: shared users may not serve a hidden directory (${seg}) \u2014 it may hold credentials`);
|
|
2984
2990
|
}
|
|
2985
2991
|
}
|
|
2986
2992
|
}
|
|
2993
|
+
function defaultResolveReal(p) {
|
|
2994
|
+
try {
|
|
2995
|
+
return fs.realpathSync(p);
|
|
2996
|
+
} catch {
|
|
2997
|
+
return p;
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
function resolveMountOwnerEmail(suppliedOwnerEmail, callerEmail, machineOwnerEmail, callerIsTrusted, envOwnerEmail) {
|
|
3001
|
+
const supplied = callerIsTrusted ? suppliedOwnerEmail : void 0;
|
|
3002
|
+
return supplied || callerEmail || machineOwnerEmail || envOwnerEmail || void 0;
|
|
3003
|
+
}
|
|
2987
3004
|
function sanitizeMountForRole(mount, isAdmin) {
|
|
2988
3005
|
if (isAdmin) return mount;
|
|
2989
3006
|
const { linkToken: _lt, directory: _dir, process: proc, url, ...rest } = mount;
|
|
@@ -3132,6 +3149,16 @@ class ServeManager {
|
|
|
3132
3149
|
hyphaServerUrl;
|
|
3133
3150
|
/** #126: daemon-provided lookup of sessionId → { cwd, sharing, ownerEmail } for the files gateway. */
|
|
3134
3151
|
sessionResolver = null;
|
|
3152
|
+
/**
|
|
3153
|
+
* #0944/#0945: daemon-provided "this mount no longer backs that session's Launch Pad entry"
|
|
3154
|
+
* hook. Fired with the OWNING session id, which is the only correct target — the CLI-side
|
|
3155
|
+
* removeSessionLinkByService() defaults to the AMBIENT $SVAMP_SESSION_ID and resolves
|
|
3156
|
+
* `.svamp/<id>/config.json` relative to its own CWD, so it silently no-ops when the teardown
|
|
3157
|
+
* is run from a plain shell, from another session, or against a mount in another project dir.
|
|
3158
|
+
* Living here means EVERY path — the serveRemove RPC, the CLI, the app's Launch Pad "Remove &
|
|
3159
|
+
* stop", and a cross-session replace — drops the right link.
|
|
3160
|
+
*/
|
|
3161
|
+
onMountUnbound = null;
|
|
3135
3162
|
constructor(svampHome, logger, hyphaServerUrl) {
|
|
3136
3163
|
this.persistFile = path.join(svampHome, "serve-mounts.json");
|
|
3137
3164
|
this.managedPidDir = path.join(svampHome, "serve-managed");
|
|
@@ -3151,6 +3178,23 @@ class ServeManager {
|
|
|
3151
3178
|
setSessionResolver(fn) {
|
|
3152
3179
|
this.sessionResolver = fn;
|
|
3153
3180
|
}
|
|
3181
|
+
/**
|
|
3182
|
+
* #0944/#0945: register the daemon's "drop this session's Launch Pad link for this mount"
|
|
3183
|
+
* callback. Fired when a mount stops backing a session's Pad entry — on a real removal, and
|
|
3184
|
+
* on a replace whose new owner is a DIFFERENT session. Best-effort by contract: the callback
|
|
3185
|
+
* must never throw, because Launch Pad bookkeeping may not fail a teardown the user asked for.
|
|
3186
|
+
*/
|
|
3187
|
+
setMountUnboundHook(fn) {
|
|
3188
|
+
this.onMountUnbound = fn;
|
|
3189
|
+
}
|
|
3190
|
+
/** Fire the unbind hook, swallowing anything it throws (bookkeeping is never load-bearing). */
|
|
3191
|
+
fireMountUnbound(sessionId, mountName) {
|
|
3192
|
+
if (!sessionId || !this.onMountUnbound) return;
|
|
3193
|
+
try {
|
|
3194
|
+
this.onMountUnbound(sessionId, mountName);
|
|
3195
|
+
} catch {
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3154
3198
|
/**
|
|
3155
3199
|
* #126: bring up the always-on, per-machine session-files gateway. Idempotent. `stableToken`
|
|
3156
3200
|
* must be a stable per-machine hex/alnum string (DNS-label-safe) so the gateway's subdomain is
|
|
@@ -3235,9 +3279,14 @@ class ServeManager {
|
|
|
3235
3279
|
if (resolvedDir && !fs.existsSync(resolvedDir)) {
|
|
3236
3280
|
throw new Error(`Path does not exist: ${resolvedDir}`);
|
|
3237
3281
|
}
|
|
3238
|
-
|
|
3282
|
+
const replaced = this.mounts.get(spec.name);
|
|
3283
|
+
if (replaced) {
|
|
3239
3284
|
await this.removeMount(spec.name, { replacing: true });
|
|
3240
3285
|
}
|
|
3286
|
+
if (replaced?.sessionId && replaced.sessionId !== spec.sessionId) {
|
|
3287
|
+
this.log(`Mount '${spec.name}': taken over by session ${spec.sessionId || "(none)"} from ${replaced.sessionId} \u2014 the previous owner's Launch Pad link is being dropped (its URL no longer resolves).`);
|
|
3288
|
+
this.fireMountUnbound(replaced.sessionId, spec.name);
|
|
3289
|
+
}
|
|
3241
3290
|
const access = spec.access ?? "link";
|
|
3242
3291
|
if (access === "owner" && !spec.ownerEmail) {
|
|
3243
3292
|
this.log(`\u26A0 Mount '${spec.name}': access='owner' but no owner email could be resolved \u2014 NO ONE will be able to sign in. Use an explicit email allowlist instead, e.g. access: ["you@example.com"].`);
|
|
@@ -3276,10 +3325,12 @@ class ServeManager {
|
|
|
3276
3325
|
* Remove a mount. If no mounts remain, stop Caddy + auth proxy.
|
|
3277
3326
|
*/
|
|
3278
3327
|
async removeMount(name, opts) {
|
|
3279
|
-
|
|
3328
|
+
const removed = this.mounts.get(name);
|
|
3329
|
+
if (!removed) {
|
|
3280
3330
|
throw new Error(`Mount '${name}' not found`);
|
|
3281
3331
|
}
|
|
3282
3332
|
this.mounts.delete(name);
|
|
3333
|
+
if (!opts?.replacing) this.fireMountUnbound(removed.sessionId, name);
|
|
3283
3334
|
await this.stopManagedProcess(name).catch(() => {
|
|
3284
3335
|
});
|
|
3285
3336
|
const tunnel = this.mountTunnels.get(name);
|
|
@@ -3744,9 +3795,10 @@ a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:und
|
|
|
3744
3795
|
}
|
|
3745
3796
|
}
|
|
3746
3797
|
const rootDir = target.cwd;
|
|
3798
|
+
const normRel = this.toSessionRelative(rootDir, m[2] || "");
|
|
3747
3799
|
if (method === "GET" || method === "HEAD") {
|
|
3748
3800
|
if (url.searchParams.get("list") === "1") {
|
|
3749
|
-
const lex = containedPath(rootDir,
|
|
3801
|
+
const lex = containedPath(rootDir, normRel);
|
|
3750
3802
|
if (!lex) {
|
|
3751
3803
|
deny(403, "Forbidden");
|
|
3752
3804
|
return;
|
|
@@ -3773,11 +3825,11 @@ a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:und
|
|
|
3773
3825
|
res.end(method === "HEAD" ? void 0 : JSON.stringify({ path: relPath, entries }));
|
|
3774
3826
|
return;
|
|
3775
3827
|
}
|
|
3776
|
-
serveStaticMount(req, res, { rootDir, relPath, mountUrlPrefix: "", browse: false});
|
|
3828
|
+
serveStaticMount(req, res, { rootDir, relPath: normRel ? `/${normRel}` : "/", mountUrlPrefix: "", browse: false});
|
|
3777
3829
|
return;
|
|
3778
3830
|
}
|
|
3779
3831
|
if (method === "PUT" || method === "DELETE") {
|
|
3780
|
-
const filePath = this.resolveContainedPath(rootDir,
|
|
3832
|
+
const filePath = this.resolveContainedPath(rootDir, normRel);
|
|
3781
3833
|
if (!filePath) {
|
|
3782
3834
|
deny(403, "Forbidden: path escapes session root");
|
|
3783
3835
|
return;
|
|
@@ -3839,6 +3891,27 @@ a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:und
|
|
|
3839
3891
|
}
|
|
3840
3892
|
deny(405, "Method not allowed");
|
|
3841
3893
|
}
|
|
3894
|
+
/**
|
|
3895
|
+
* #126 root-cause fix: the frontend builds gateway URLs from the file's ABSOLUTE path (the
|
|
3896
|
+
* session cwd + name), e.g. `/s/<id>/Users/me/proj/big.bin` for a file at
|
|
3897
|
+
* `/Users/me/proj/big.bin` in a session whose cwd is `/Users/me/proj`. A URL path can't carry
|
|
3898
|
+
* the leading slash, so a naive join against cwd DOUBLE-NESTS it
|
|
3899
|
+
* (`/Users/me/proj/Users/me/proj/big.bin`) — the file uploads with a 201 but lands in the wrong
|
|
3900
|
+
* place (and downloads 404). The RPC path never hit this because Node's `resolve(cwd, absPath)`
|
|
3901
|
+
* returns the absolute path as-is. Mirror that semantics here: reconstruct the remainder as an
|
|
3902
|
+
* absolute path; if it IS the cwd or lives under it, the caller meant an absolute path — return
|
|
3903
|
+
* it relative to cwd. Otherwise it's already cwd-relative. Returns a cwd-relative path with no
|
|
3904
|
+
* leading slash (containment is still enforced downstream by `containedPath`/`resolveContainedPath`).
|
|
3905
|
+
*/
|
|
3906
|
+
toSessionRelative(rootDir, remainder) {
|
|
3907
|
+
const rel = (remainder || "").replace(/^\/+/, "");
|
|
3908
|
+
if (!rel) return "";
|
|
3909
|
+
const base = path.resolve(rootDir);
|
|
3910
|
+
const asAbsolute = path.resolve("/", rel);
|
|
3911
|
+
if (asAbsolute === base) return "";
|
|
3912
|
+
if (asAbsolute.startsWith(base + path.sep)) return asAbsolute.slice(base.length + 1);
|
|
3913
|
+
return rel;
|
|
3914
|
+
}
|
|
3842
3915
|
/**
|
|
3843
3916
|
* #126: resolve a session-relative path against `rootDir` with lexical + symlink containment
|
|
3844
3917
|
* (mirrors the static PUT handler's resolveContained). Allows a not-yet-existing target (for
|
|
@@ -4264,6 +4337,10 @@ Connection: close\r
|
|
|
4264
4337
|
async startMountTunnel(mountName) {
|
|
4265
4338
|
if (this.mountTunnels.has(mountName)) return;
|
|
4266
4339
|
if (!this.port) throw new Error("Auth proxy not running \u2014 call ensureRunning() first");
|
|
4340
|
+
if (process.env.SVAMP_SERVE_NO_TUNNEL === "1") {
|
|
4341
|
+
this.log(`Mount '${mountName}': tunnel skipped (SVAMP_SERVE_NO_TUNNEL=1) \u2014 local only at http://127.0.0.1:${this.port}/${mountName}/`);
|
|
4342
|
+
return;
|
|
4343
|
+
}
|
|
4267
4344
|
const subdomainSafe = mountName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
4268
4345
|
const tunnelName = `static-${subdomainSafe}`;
|
|
4269
4346
|
const mount = this.mounts.get(mountName);
|
|
@@ -4319,6 +4396,7 @@ var serveManager = /*#__PURE__*/Object.freeze({
|
|
|
4319
4396
|
ServeManager: ServeManager,
|
|
4320
4397
|
assertNonAdminMountDirSafe: assertNonAdminMountDirSafe,
|
|
4321
4398
|
buildLinkSubdomain: buildLinkSubdomain,
|
|
4399
|
+
resolveMountOwnerEmail: resolveMountOwnerEmail,
|
|
4322
4400
|
sanitizeMountForRole: sanitizeMountForRole
|
|
4323
4401
|
});
|
|
4324
4402
|
|
|
@@ -4469,6 +4547,10 @@ function setClaudeAuthCustom(baseUrl, apiKey) {
|
|
|
4469
4547
|
ANTHROPIC_API_KEY: apiKey
|
|
4470
4548
|
});
|
|
4471
4549
|
}
|
|
4550
|
+
function applyPromptCacheTtl(spawnEnv) {
|
|
4551
|
+
const cacheOverride = spawnEnv.ENABLE_PROMPT_CACHING_1H ?? process.env.ENABLE_PROMPT_CACHING_1H ?? spawnEnv.FORCE_PROMPT_CACHING_5M ?? process.env.FORCE_PROMPT_CACHING_5M;
|
|
4552
|
+
if (cacheOverride === void 0) spawnEnv.ENABLE_PROMPT_CACHING_1H = "1";
|
|
4553
|
+
}
|
|
4472
4554
|
function applyClaudeProxyEnv(spawnEnv) {
|
|
4473
4555
|
const mode = currentMode();
|
|
4474
4556
|
if (mode === "hypha") {
|
|
@@ -4486,8 +4568,7 @@ function applyClaudeProxyEnv(spawnEnv) {
|
|
|
4486
4568
|
}
|
|
4487
4569
|
spawnEnv.ANTHROPIC_BASE_URL = proxyUrl;
|
|
4488
4570
|
spawnEnv.ANTHROPIC_API_KEY = token;
|
|
4489
|
-
|
|
4490
|
-
if (cacheOverride === void 0) spawnEnv.ENABLE_PROMPT_CACHING_1H = "1";
|
|
4571
|
+
applyPromptCacheTtl(spawnEnv);
|
|
4491
4572
|
return `hypha proxy (${proxyUrl}, 1h cache TTL)`;
|
|
4492
4573
|
}
|
|
4493
4574
|
if (mode === "custom") {
|
|
@@ -4500,7 +4581,8 @@ function applyClaudeProxyEnv(spawnEnv) {
|
|
|
4500
4581
|
}
|
|
4501
4582
|
spawnEnv.ANTHROPIC_BASE_URL = url;
|
|
4502
4583
|
spawnEnv.ANTHROPIC_API_KEY = key;
|
|
4503
|
-
|
|
4584
|
+
applyPromptCacheTtl(spawnEnv);
|
|
4585
|
+
return `custom proxy (${url}, 1h cache TTL)`;
|
|
4504
4586
|
}
|
|
4505
4587
|
delete spawnEnv.ANTHROPIC_BASE_URL;
|
|
4506
4588
|
delete spawnEnv.ANTHROPIC_API_KEY;
|
|
@@ -6360,6 +6442,13 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
6360
6442
|
} catch {
|
|
6361
6443
|
}
|
|
6362
6444
|
const serveCallerTrusted = (context) => !context?.user || isSameOwnerWorkspace(context) || roleAtLeast(getEffectiveRole(context, currentMetadata.sharing), "admin");
|
|
6445
|
+
const resolveMountOwnerEmailFor = (supplied, context) => resolveMountOwnerEmail(
|
|
6446
|
+
supplied,
|
|
6447
|
+
context?.user?.email,
|
|
6448
|
+
currentMetadata.sharing?.owner,
|
|
6449
|
+
serveCallerTrusted(context),
|
|
6450
|
+
process.env.SVAMP_OWNER_EMAIL
|
|
6451
|
+
);
|
|
6363
6452
|
let lastInboundRpcAt = Date.now();
|
|
6364
6453
|
const trackInbound = () => {
|
|
6365
6454
|
lastInboundRpcAt = Date.now();
|
|
@@ -7526,7 +7615,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
7526
7615
|
const { homedir } = await import('os');
|
|
7527
7616
|
assertNonAdminMountDirSafe(params.directory, homedir());
|
|
7528
7617
|
}
|
|
7529
|
-
|
|
7618
|
+
if (params.sessionId && !serveCallerTrusted(context)) {
|
|
7619
|
+
await authorizeSessionAccess(params.sessionId, "admin", context);
|
|
7620
|
+
}
|
|
7621
|
+
const ownerEmail2 = resolveMountOwnerEmailFor(params.ownerEmail, context);
|
|
7530
7622
|
const access = params.access || "owner";
|
|
7531
7623
|
return sm.addMount(params.name, params.directory, params.sessionId, access, ownerEmail2);
|
|
7532
7624
|
},
|
|
@@ -7543,6 +7635,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
7543
7635
|
const { homedir } = await import('os');
|
|
7544
7636
|
assertNonAdminMountDirSafe(params.directory, homedir());
|
|
7545
7637
|
}
|
|
7638
|
+
if (params.sessionId && !serveCallerTrusted(context)) {
|
|
7639
|
+
await authorizeSessionAccess(params.sessionId, "admin", context);
|
|
7640
|
+
}
|
|
7546
7641
|
const existingMount = sm.getMount(params.name);
|
|
7547
7642
|
if (existingMount && !serveCallerTrusted(context)) {
|
|
7548
7643
|
const callerEmail = (context?.user?.email || "").toLowerCase();
|
|
@@ -7551,7 +7646,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
7551
7646
|
throw new Error(`Not authorized to replace mount '${params.name}' (owned by another user)`);
|
|
7552
7647
|
}
|
|
7553
7648
|
}
|
|
7554
|
-
const ownerEmail2 = params.ownerEmail
|
|
7649
|
+
const ownerEmail2 = resolveMountOwnerEmailFor(params.ownerEmail, context);
|
|
7555
7650
|
const access = params.access ?? "owner";
|
|
7556
7651
|
return sm.applyMount({
|
|
7557
7652
|
name: params.name,
|
|
@@ -7921,7 +8016,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
7921
8016
|
}
|
|
7922
8017
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
7923
8018
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
7924
|
-
const { toolsForRole } = await import('./sideband-
|
|
8019
|
+
const { toolsForRole } = await import('./sideband-CFbMRvzr.mjs');
|
|
7925
8020
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
7926
8021
|
return fmt(r2);
|
|
7927
8022
|
}
|
|
@@ -8026,7 +8121,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
8026
8121
|
return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
|
|
8027
8122
|
}
|
|
8028
8123
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
8029
|
-
const { queryCore } = await import('./commands-
|
|
8124
|
+
const { queryCore } = await import('./commands-slA1eX13.mjs');
|
|
8030
8125
|
const timeout = c.reply?.timeout_sec || 120;
|
|
8031
8126
|
let result;
|
|
8032
8127
|
try {
|
|
@@ -8238,6 +8333,9 @@ ${d?.error || "not found"}`;
|
|
|
8238
8333
|
});
|
|
8239
8334
|
},
|
|
8240
8335
|
getLastInboundRpcAt: () => lastInboundRpcAt,
|
|
8336
|
+
resetInboundClock: () => {
|
|
8337
|
+
lastInboundRpcAt = Date.now();
|
|
8338
|
+
},
|
|
8241
8339
|
getCurrentMetadata: () => currentMetadata,
|
|
8242
8340
|
setHandlers: (newHandlers) => {
|
|
8243
8341
|
handlers = newHandlers;
|
|
@@ -16370,8 +16468,12 @@ function isVisibleTo(issue, session) {
|
|
|
16370
16468
|
if (!issue.owner && !issue.session) return true;
|
|
16371
16469
|
return false;
|
|
16372
16470
|
}
|
|
16471
|
+
function normalizeIssueRef(id) {
|
|
16472
|
+
const s = String(id ?? "").trim().replace(/^#+/, "");
|
|
16473
|
+
return /^\d+$/.test(s) ? s.padStart(4, "0") : s;
|
|
16474
|
+
}
|
|
16373
16475
|
function getIssue(projectRoot, id) {
|
|
16374
|
-
const padded =
|
|
16476
|
+
const padded = normalizeIssueRef(id);
|
|
16375
16477
|
let best = null;
|
|
16376
16478
|
for (const p of [issuePath(projectRoot, padded), issuePath(projectRoot, padded, true)]) {
|
|
16377
16479
|
if (!existsSync(p)) continue;
|
|
@@ -16383,7 +16485,14 @@ function getIssue(projectRoot, id) {
|
|
|
16383
16485
|
} catch {
|
|
16384
16486
|
}
|
|
16385
16487
|
}
|
|
16386
|
-
|
|
16488
|
+
if (best) return best.issue;
|
|
16489
|
+
try {
|
|
16490
|
+
for (const issue of listIssues(projectRoot, { includeArchived: true })) {
|
|
16491
|
+
if (normalizeIssueRef(issue.id) === padded) return issue;
|
|
16492
|
+
}
|
|
16493
|
+
} catch {
|
|
16494
|
+
}
|
|
16495
|
+
return null;
|
|
16387
16496
|
}
|
|
16388
16497
|
function atomicWrite(path, content) {
|
|
16389
16498
|
mkdirSync$1(dirname(path), { recursive: true });
|
|
@@ -16432,8 +16541,7 @@ function addIssue(projectRoot, fields) {
|
|
|
16432
16541
|
throw new Error("addIssue: could not allocate a free issue id");
|
|
16433
16542
|
}
|
|
16434
16543
|
function issueLockPath(projectRoot, id) {
|
|
16435
|
-
|
|
16436
|
-
return join$1(issuesDir(projectRoot), `${padded}.lock`);
|
|
16544
|
+
return join$1(issuesDir(projectRoot), `${normalizeIssueRef(id)}.lock`);
|
|
16437
16545
|
}
|
|
16438
16546
|
function withIssueLock(projectRoot, id, fn) {
|
|
16439
16547
|
try {
|
|
@@ -16446,16 +16554,20 @@ function _updateIssueUnlocked(projectRoot, id, patch) {
|
|
|
16446
16554
|
const cur = getIssue(projectRoot, id);
|
|
16447
16555
|
if (!cur) return null;
|
|
16448
16556
|
const wasArchived = cur.status === "archived";
|
|
16449
|
-
const
|
|
16557
|
+
const canonicalId = normalizeIssueRef(cur.id);
|
|
16558
|
+
const next = { ...cur, ...patch, id: canonicalId };
|
|
16450
16559
|
if (patch.status) next.status = normalizeStatus(patch.status);
|
|
16451
16560
|
const nowArchived = next.status === "archived";
|
|
16452
16561
|
if (nowArchived && !next.closed) next.closed = (/* @__PURE__ */ new Date()).toISOString();
|
|
16453
16562
|
if (!nowArchived) next.closed = null;
|
|
16454
16563
|
atomicWrite(issuePath(projectRoot, next.id, nowArchived), serializeIssue(next));
|
|
16455
|
-
|
|
16456
|
-
|
|
16564
|
+
for (const stale of [
|
|
16565
|
+
issuePath(projectRoot, cur.id, wasArchived),
|
|
16566
|
+
issuePath(projectRoot, cur.id, nowArchived)
|
|
16567
|
+
]) {
|
|
16568
|
+
if (stale === issuePath(projectRoot, next.id, nowArchived)) continue;
|
|
16457
16569
|
try {
|
|
16458
|
-
if (existsSync(
|
|
16570
|
+
if (existsSync(stale)) unlinkSync$1(stale);
|
|
16459
16571
|
} catch {
|
|
16460
16572
|
}
|
|
16461
16573
|
}
|
|
@@ -17439,6 +17551,19 @@ function parseEvaluatorVerdict(text) {
|
|
|
17439
17551
|
}
|
|
17440
17552
|
return null;
|
|
17441
17553
|
}
|
|
17554
|
+
function buildStaleSafeOracleGuidance(oracleOutput, now = /* @__PURE__ */ new Date()) {
|
|
17555
|
+
const reading = (oracleOutput || "").trim().slice(0, 500);
|
|
17556
|
+
return [
|
|
17557
|
+
`The loop oracle reported unfinished work as of ${now.toISOString()}:`,
|
|
17558
|
+
reading || "(no detail)",
|
|
17559
|
+
"",
|
|
17560
|
+
"That is a POINT-IN-TIME reading, not a fact about right now \u2014 the backlog may have changed",
|
|
17561
|
+
"since (an item can be closed or paused out-of-band while this message was queued). RE-RUN",
|
|
17562
|
+
"the oracle yourself and treat ITS CURRENT OUTPUT as the truth. If it is now empty, the goal",
|
|
17563
|
+
"is complete: say so rather than chasing the items listed above. Otherwise keep working until",
|
|
17564
|
+
"it is genuinely empty."
|
|
17565
|
+
].join("\n");
|
|
17566
|
+
}
|
|
17442
17567
|
async function runLoopVerification(cfg, deps) {
|
|
17443
17568
|
const log = deps.log || (() => {
|
|
17444
17569
|
});
|
|
@@ -17453,9 +17578,11 @@ async function runLoopVerification(cfg, deps) {
|
|
|
17453
17578
|
return { action: "gave_up", reason: `oracle still reports pending work after ${holds} verification hold(s): ${oracleOutput.trim().slice(0, 200)}` };
|
|
17454
17579
|
}
|
|
17455
17580
|
log(`[loopVerify] oracle reports pending work \u2014 re-kicking instead of marking done`);
|
|
17456
|
-
return {
|
|
17457
|
-
|
|
17458
|
-
|
|
17581
|
+
return {
|
|
17582
|
+
action: "rekick",
|
|
17583
|
+
reason: "oracle reports pending work",
|
|
17584
|
+
guidance: buildStaleSafeOracleGuidance(oracleOutput)
|
|
17585
|
+
};
|
|
17459
17586
|
}
|
|
17460
17587
|
} catch (e) {
|
|
17461
17588
|
log(`[loopVerify] oracle runner error (continuing to evaluator): ${e?.message || e}`);
|
|
@@ -17684,6 +17811,17 @@ function compactUnsupportedWarning(version) {
|
|
|
17684
17811
|
return `\u26A0\uFE0F /compact did not run${v} \u2014 Claude Code forwarded it as a normal message, so nothing was compacted. In-session /compact needs a newer Claude Code (>= 2.1.205). Ask the machine owner to upgrade (e.g. \`svamp fleet upgrade-claude\`), then try again.`;
|
|
17685
17812
|
}
|
|
17686
17813
|
|
|
17814
|
+
function shouldRunZombieProbe(args) {
|
|
17815
|
+
return args.hasActiveSessions && !args.inGrace && args.consecutiveHeartbeatFailures === 0 && args.inboundSilenceMs > args.thresholdMs;
|
|
17816
|
+
}
|
|
17817
|
+
function nextFailuresAfterZombieProbeFailure(consecutiveHeartbeatFailures) {
|
|
17818
|
+
return consecutiveHeartbeatFailures + 1;
|
|
17819
|
+
}
|
|
17820
|
+
function shouldForceReconnect(consecutiveHeartbeatFailures) {
|
|
17821
|
+
if (consecutiveHeartbeatFailures < 2) return false;
|
|
17822
|
+
return consecutiveHeartbeatFailures === 2 || consecutiveHeartbeatFailures % 3 === 0;
|
|
17823
|
+
}
|
|
17824
|
+
|
|
17687
17825
|
const SVAMP_HOME$1 = process.env.SVAMP_HOME || join$1(os.homedir(), ".svamp");
|
|
17688
17826
|
function generateHookSettings(portOrOptions = {}) {
|
|
17689
17827
|
const opts = typeof portOrOptions === "number" ? { sessionStartPort: portOrOptions } : portOrOptions;
|
|
@@ -18555,9 +18693,11 @@ function atomicWriteLoopState(path, obj) {
|
|
|
18555
18693
|
writeFileSync(tmp, JSON.stringify(obj, null, 2));
|
|
18556
18694
|
renameSync$1(tmp, path);
|
|
18557
18695
|
}
|
|
18558
|
-
function safeBacklogPendingCount(
|
|
18696
|
+
function safeBacklogPendingCount(directory, selfSessionId, oracle) {
|
|
18559
18697
|
try {
|
|
18560
|
-
|
|
18698
|
+
const root = resolveProjectRoot(directory);
|
|
18699
|
+
const scope = oracleScopeSessionId(typeof oracle === "string" ? oracle : void 0, selfSessionId);
|
|
18700
|
+
return backlogOraclePending(root, scope).length;
|
|
18561
18701
|
} catch {
|
|
18562
18702
|
return 0;
|
|
18563
18703
|
}
|
|
@@ -19314,6 +19454,7 @@ async function startDaemon(options) {
|
|
|
19314
19454
|
};
|
|
19315
19455
|
});
|
|
19316
19456
|
let consecutiveHeartbeatFailures = 0;
|
|
19457
|
+
let lastReconnectAt = 0;
|
|
19317
19458
|
process.on("SIGINT", () => requestShutdown("os-signal"));
|
|
19318
19459
|
process.on("SIGTERM", () => requestShutdown("os-signal"));
|
|
19319
19460
|
process.on("SIGUSR1", () => requestShutdown("os-signal-cleanup"));
|
|
@@ -19510,7 +19651,7 @@ async function startDaemon(options) {
|
|
|
19510
19651
|
try {
|
|
19511
19652
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
19512
19653
|
if (!dir) return;
|
|
19513
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
19654
|
+
const { reconcileServiceLinks } = await import('./agentCommands-Cve_0_8K.mjs');
|
|
19514
19655
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
19515
19656
|
const config = readSvampConfig(configPath);
|
|
19516
19657
|
const entries = Array.from(urls.entries());
|
|
@@ -19532,7 +19673,7 @@ async function startDaemon(options) {
|
|
|
19532
19673
|
try {
|
|
19533
19674
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
19534
19675
|
if (!dir) return;
|
|
19535
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
19676
|
+
const { reconcileServiceLinks } = await import('./agentCommands-Cve_0_8K.mjs');
|
|
19536
19677
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
19537
19678
|
const config = readSvampConfig(configPath);
|
|
19538
19679
|
const incoming = [{
|
|
@@ -19548,6 +19689,22 @@ async function startDaemon(options) {
|
|
|
19548
19689
|
logger.log(`[serve] Link reconcile failed for mount ${mountName}: ${err?.message || err}`);
|
|
19549
19690
|
}
|
|
19550
19691
|
}
|
|
19692
|
+
async function dropMountSessionLink(sessionId, mountName) {
|
|
19693
|
+
if (!sessionId) return;
|
|
19694
|
+
try {
|
|
19695
|
+
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
19696
|
+
if (!dir) return;
|
|
19697
|
+
const { dropServiceLinks } = await import('./agentCommands-Cve_0_8K.mjs');
|
|
19698
|
+
const configPath = getSvampConfigPath(dir, sessionId);
|
|
19699
|
+
const config = readSvampConfig(configPath);
|
|
19700
|
+
if (dropServiceLinks(config, "serve", mountName)) {
|
|
19701
|
+
writeSvampConfig(configPath, config);
|
|
19702
|
+
logger.log(`[serve] Dropped Launch Pad link for mount ${mountName} \u2190 session ${sessionId}`);
|
|
19703
|
+
}
|
|
19704
|
+
} catch (err) {
|
|
19705
|
+
logger.log(`[serve] Link drop failed for mount ${mountName}: ${err?.message || err}`);
|
|
19706
|
+
}
|
|
19707
|
+
}
|
|
19551
19708
|
async function createExposedTunnel(spec) {
|
|
19552
19709
|
const { FrpcTunnel } = await Promise.resolve().then(function () { return frpc; });
|
|
19553
19710
|
const tunnel = new FrpcTunnel({
|
|
@@ -19572,12 +19729,15 @@ async function startDaemon(options) {
|
|
|
19572
19729
|
const tunnelRecreateInFlight = /* @__PURE__ */ new Set();
|
|
19573
19730
|
const { ServeManager } = await Promise.resolve().then(function () { return serveManager; });
|
|
19574
19731
|
const serveManager$1 = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
19732
|
+
serveManager$1.setMountUnboundHook((sessionId, mountName) => {
|
|
19733
|
+
void dropMountSessionLink(sessionId, mountName);
|
|
19734
|
+
});
|
|
19575
19735
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
19576
19736
|
});
|
|
19577
19737
|
ensureAutoInstalledCommands(logger);
|
|
19578
19738
|
(async () => {
|
|
19579
19739
|
try {
|
|
19580
|
-
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-
|
|
19740
|
+
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-BkZfX5pW.mjs');
|
|
19581
19741
|
beginClaudeVersionReconcile((msg) => logger.log(msg));
|
|
19582
19742
|
} catch (e) {
|
|
19583
19743
|
logger.log(`[claude-version] check failed: ${e?.message || e}`);
|
|
@@ -19690,10 +19850,14 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
|
|
|
19690
19850
|
}
|
|
19691
19851
|
};
|
|
19692
19852
|
server.on("services_registered", () => {
|
|
19853
|
+
lastReconnectAt = Date.now();
|
|
19854
|
+
try {
|
|
19855
|
+
machineService.resetInboundClock?.();
|
|
19856
|
+
} catch {
|
|
19857
|
+
}
|
|
19693
19858
|
if (consecutiveHeartbeatFailures > 0) {
|
|
19694
19859
|
logger.log(`Hypha reconnection successful \u2014 services re-registered (resetting ${consecutiveHeartbeatFailures} failures)`);
|
|
19695
19860
|
consecutiveHeartbeatFailures = 0;
|
|
19696
|
-
lastReconnectAt = Date.now();
|
|
19697
19861
|
}
|
|
19698
19862
|
const reEmitLiveness = (phase) => {
|
|
19699
19863
|
try {
|
|
@@ -20259,26 +20423,33 @@ ${parts.join("\n")}`);
|
|
|
20259
20423
|
const oracleCmd = typeof ls.oracle === "string" && ls.oracle.trim() ? ls.oracle.trim() : void 0;
|
|
20260
20424
|
try {
|
|
20261
20425
|
sessionService.pushMessage({ type: "message", message: "\u{1F50D} Verifying loop completion (independent review)\u2026" }, "event");
|
|
20262
|
-
const
|
|
20263
|
-
|
|
20264
|
-
|
|
20265
|
-
|
|
20266
|
-
|
|
20267
|
-
|
|
20268
|
-
|
|
20269
|
-
|
|
20270
|
-
if (err) resolve2({ ok: false, output: (String(stdout || "") + String(stderr || "")).slice(-500) });
|
|
20271
|
-
else resolve2({ ok: true, output: "" });
|
|
20272
|
-
});
|
|
20273
|
-
});
|
|
20274
|
-
}
|
|
20275
|
-
const pending = backlogOraclePending(projectRoot, sessionId);
|
|
20276
|
-
return { ok: pending.length === 0, output: pending.length ? `${pending.length} pending: ${pending.map((i) => "#" + i.id).join(" ")}` : "No pending issues." };
|
|
20277
|
-
},
|
|
20278
|
-
runEvaluator: (prompt) => runHeadlessEvaluator(prompt),
|
|
20279
|
-
log: (m) => logger.log(`[Session ${sessionId}] ${m}`)
|
|
20426
|
+
const runOracle = async (cmd) => {
|
|
20427
|
+
if (cmd) {
|
|
20428
|
+
return await new Promise((resolve2) => {
|
|
20429
|
+
exec$1(cmd, { cwd: directory, timeout: 12e4, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
20430
|
+
if (err) resolve2({ ok: false, output: (String(stdout || "") + String(stderr || "")).slice(-500) });
|
|
20431
|
+
else resolve2({ ok: true, output: "" });
|
|
20432
|
+
});
|
|
20433
|
+
});
|
|
20280
20434
|
}
|
|
20281
|
-
|
|
20435
|
+
const pending = backlogOraclePending(projectRoot, sessionId);
|
|
20436
|
+
return { ok: pending.length === 0, output: pending.length ? `${pending.length} pending: ${pending.map((i) => "#" + i.id).join(" ")}` : "No pending issues." };
|
|
20437
|
+
};
|
|
20438
|
+
const verifyDeps = {
|
|
20439
|
+
projectRoot,
|
|
20440
|
+
runOracle,
|
|
20441
|
+
runEvaluator: (prompt) => runHeadlessEvaluator(prompt),
|
|
20442
|
+
log: (m) => logger.log(`[Session ${sessionId}] ${m}`)
|
|
20443
|
+
};
|
|
20444
|
+
const verifyCfg = { task: ls.goal_task || ls.task, until: ls.until, oracle: oracleCmd, startedAt, sessionId, holds, maxHolds: 3 };
|
|
20445
|
+
let result = await runLoopVerification(verifyCfg, verifyDeps);
|
|
20446
|
+
if (result.action === "rekick" && result.reason === "oracle reports pending work") {
|
|
20447
|
+
const fresh = await runOracle(oracleCmd);
|
|
20448
|
+
if (fresh.ok) {
|
|
20449
|
+
logger.log(`[Session ${sessionId}] [#0194] oracle went CLEAN during verification \u2014 re-running the gate instead of re-kicking on the stale reading`);
|
|
20450
|
+
result = await runLoopVerification(verifyCfg, { ...verifyDeps, runOracle: async () => fresh });
|
|
20451
|
+
}
|
|
20452
|
+
}
|
|
20282
20453
|
if (loopCancelledDuringVerify(readLoopState(directory, sessionId))) {
|
|
20283
20454
|
logger.log(`[Session ${sessionId}] loop was cancelled during verification \u2014 not re-kicking (#0308)`);
|
|
20284
20455
|
return;
|
|
@@ -20958,7 +21129,7 @@ ${parts.join("\n")}`);
|
|
|
20958
21129
|
const maxExt = resolveMaxExtensions(process.env.SVAMP_LOOP_MAX_EXTENSIONS);
|
|
20959
21130
|
const effMaxIters = effectiveSoftCap(ls.max_iterations, ls.extensions, maxExt);
|
|
20960
21131
|
const budgetCheck = process.env.SVAMP_LOOP_BUDGET === "0" ? { exceeded: false, kind: void 0 } : checkLoopBudget(ledger, { max_iterations: effMaxIters, ...ls.budget || {} }, now, startedAt);
|
|
20961
|
-
const pending = safeBacklogPendingCount(
|
|
21132
|
+
const pending = safeBacklogPendingCount(directory, sessionId, ls.oracle);
|
|
20962
21133
|
const prog = updateLoopProgress(ls, pending, now);
|
|
20963
21134
|
ls.progress_history = prog.progress_history;
|
|
20964
21135
|
ls.auto_resumes = prog.auto_resumes;
|
|
@@ -21894,11 +22065,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21894
22065
|
});
|
|
21895
22066
|
},
|
|
21896
22067
|
onIssue: async (params) => {
|
|
21897
|
-
const { issueRpc } = await import('./rpc-
|
|
22068
|
+
const { issueRpc } = await import('./rpc-CLoO6Z5Z.mjs');
|
|
21898
22069
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
21899
22070
|
},
|
|
21900
22071
|
onWorkflow: async (params) => {
|
|
21901
|
-
const { workflowRpc } = await import('./rpc-
|
|
22072
|
+
const { workflowRpc } = await import('./rpc-Bz9w7JV9.mjs');
|
|
21902
22073
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
21903
22074
|
},
|
|
21904
22075
|
onRipgrep: async (args, cwd) => {
|
|
@@ -22605,11 +22776,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
22605
22776
|
});
|
|
22606
22777
|
},
|
|
22607
22778
|
onIssue: async (params) => {
|
|
22608
|
-
const { issueRpc } = await import('./rpc-
|
|
22779
|
+
const { issueRpc } = await import('./rpc-CLoO6Z5Z.mjs');
|
|
22609
22780
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
22610
22781
|
},
|
|
22611
22782
|
onWorkflow: async (params) => {
|
|
22612
|
-
const { workflowRpc } = await import('./rpc-
|
|
22783
|
+
const { workflowRpc } = await import('./rpc-Bz9w7JV9.mjs');
|
|
22613
22784
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
22614
22785
|
},
|
|
22615
22786
|
onRipgrep: async (args, cwd) => {
|
|
@@ -22958,7 +23129,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
22958
23129
|
const acpMaxExt = resolveMaxExtensions(process.env.SVAMP_LOOP_MAX_EXTENSIONS);
|
|
22959
23130
|
const acpEffMax = effectiveSoftCap(ls.max_iterations, ls.extensions, acpMaxExt);
|
|
22960
23131
|
const budgetCheck = process.env.SVAMP_LOOP_BUDGET === "0" ? { exceeded: false, kind: void 0 } : checkLoopBudget(ledger, { max_iterations: acpEffMax, ...ls.budget || {} }, now, startedAt);
|
|
22961
|
-
const acpPending = safeBacklogPendingCount(projectRoot, sessionId);
|
|
23132
|
+
const acpPending = safeBacklogPendingCount(projectRoot, sessionId, ls.oracle);
|
|
22962
23133
|
const acpProg = updateLoopProgress(ls, acpPending, now);
|
|
22963
23134
|
ls.progress_history = acpProg.progress_history;
|
|
22964
23135
|
ls.auto_resumes = acpProg.auto_resumes;
|
|
@@ -23660,7 +23831,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23660
23831
|
}
|
|
23661
23832
|
if (persistedSessions.length > 0) {
|
|
23662
23833
|
try {
|
|
23663
|
-
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-
|
|
23834
|
+
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-BkZfX5pW.mjs');
|
|
23664
23835
|
await awaitClaudeVersionReady();
|
|
23665
23836
|
} catch {
|
|
23666
23837
|
}
|
|
@@ -23700,7 +23871,11 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23700
23871
|
isOrphaned: !!isOrphaned,
|
|
23701
23872
|
loopActive: isLoopActiveForSession(persisted.directory, persisted.sessionId),
|
|
23702
23873
|
loopArmed: _loopArmed,
|
|
23703
|
-
armedHasPendingWork: _loopArmed ? safeBacklogPendingCount(
|
|
23874
|
+
armedHasPendingWork: _loopArmed ? safeBacklogPendingCount(
|
|
23875
|
+
persisted.directory,
|
|
23876
|
+
persisted.sessionId,
|
|
23877
|
+
readLoopState(persisted.directory, persisted.sessionId)?.oracle
|
|
23878
|
+
) > 0 : false
|
|
23704
23879
|
});
|
|
23705
23880
|
if (_loopArmed) {
|
|
23706
23881
|
logger.log(`[loop] Restored DORMANT loop for ${persisted.sessionId} \u2192 ${_restoreAction === "loop-resume" ? "re-arming (pending work found)" : "staying dormant/watching (backlog empty)"}`);
|
|
@@ -23905,7 +24080,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23905
24080
|
const PING_TIMEOUT_MS = 15e3;
|
|
23906
24081
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
23907
24082
|
const RECONNECT_JITTER_MS = 2500;
|
|
23908
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
24083
|
+
const { WorkflowScheduler } = await import('./scheduler-Bs0nAlhs.mjs');
|
|
23909
24084
|
const workflowProjectRoots = () => {
|
|
23910
24085
|
const dirs = /* @__PURE__ */ new Set();
|
|
23911
24086
|
for (const s of pidToTrackedSession.values()) {
|
|
@@ -23945,7 +24120,6 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23945
24120
|
runOAuthAccountRefresh();
|
|
23946
24121
|
const backendAccountRefreshInterval = setInterval(runOAuthAccountRefresh, 5 * 6e4);
|
|
23947
24122
|
let heartbeatRunning = false;
|
|
23948
|
-
let lastReconnectAt = 0;
|
|
23949
24123
|
let heartbeatCycle = 0;
|
|
23950
24124
|
const heartbeatInterval = setInterval(async () => {
|
|
23951
24125
|
if (heartbeatRunning) return;
|
|
@@ -23981,10 +24155,11 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23981
24155
|
}
|
|
23982
24156
|
}
|
|
23983
24157
|
}
|
|
24158
|
+
const inGrace = lastReconnectAt > 0 && Date.now() - lastReconnectAt < POST_RECONNECT_GRACE_MS;
|
|
23984
24159
|
const INBOUND_SILENCE_THRESHOLD_MS = 12e4;
|
|
23985
24160
|
const inboundSilenceMs = Date.now() - machineService.getLastInboundRpcAt();
|
|
23986
24161
|
const hasActiveSessions = pidToTrackedSession.size > 0;
|
|
23987
|
-
if (hasActiveSessions
|
|
24162
|
+
if (shouldRunZombieProbe({ hasActiveSessions, inboundSilenceMs, consecutiveHeartbeatFailures, inGrace, thresholdMs: INBOUND_SILENCE_THRESHOLD_MS })) {
|
|
23988
24163
|
logger.log(`No inbound RPC for ${Math.round(inboundSilenceMs / 1e3)}s with ${pidToTrackedSession.size} active session(s) \u2014 zombie probe`);
|
|
23989
24164
|
try {
|
|
23990
24165
|
const machineServiceId = `${server.config.client_id}:default`;
|
|
@@ -23993,11 +24168,10 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23993
24168
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Zombie probe timed out")), PING_TIMEOUT_MS))
|
|
23994
24169
|
]);
|
|
23995
24170
|
} catch (err) {
|
|
23996
|
-
logger.log(`Zombie detection probe failed: ${err.message} \u2014
|
|
23997
|
-
consecutiveHeartbeatFailures =
|
|
24171
|
+
logger.log(`Zombie detection probe failed: ${err.message} \u2014 raising suspicion (two-strike)`);
|
|
24172
|
+
consecutiveHeartbeatFailures = nextFailuresAfterZombieProbeFailure(consecutiveHeartbeatFailures);
|
|
23998
24173
|
}
|
|
23999
24174
|
}
|
|
24000
|
-
const inGrace = lastReconnectAt > 0 && Date.now() - lastReconnectAt < POST_RECONNECT_GRACE_MS;
|
|
24001
24175
|
if (!inGrace) {
|
|
24002
24176
|
try {
|
|
24003
24177
|
const pingStart = Date.now();
|
|
@@ -24022,7 +24196,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
24022
24196
|
} else if (consecutiveHeartbeatFailures % 6 === 0) {
|
|
24023
24197
|
logger.log(`Connection down for ${consecutiveHeartbeatFailures * HEARTBEAT_INTERVAL_MS / 1e3}s (${consecutiveHeartbeatFailures} failures, retrying indefinitely)`);
|
|
24024
24198
|
}
|
|
24025
|
-
if (consecutiveHeartbeatFailures
|
|
24199
|
+
if (shouldForceReconnect(consecutiveHeartbeatFailures)) {
|
|
24026
24200
|
const jitterMs = Math.floor(Math.random() * RECONNECT_JITTER_MS);
|
|
24027
24201
|
if (jitterMs > 0) await new Promise((r) => setTimeout(r, jitterMs));
|
|
24028
24202
|
const conn = server.rpc?._connection;
|
|
@@ -24539,4 +24713,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
24539
24713
|
writeStopMarker: writeStopMarker
|
|
24540
24714
|
});
|
|
24541
24715
|
|
|
24542
|
-
export { listSkillFiles as $, saveWorkflow as A, rawWorkflow as B, listWorkflows as C, isWorkflowEnabled as D, workflowSchedules as E, inZone as F, cronMatches as G, summarize as H, workflowSteps as I, parseJwtEmail as J, computeCollectionConfigUpdate as K, SYSTEM_COLLECTION_CONFIG as L, loadMachineContext as M, buildMachineInstructions as N, machineToolsForRole as O, buildMachineTools as P, parseFrontmatter as Q, READ_ONLY_TOOLS as R, SharingNotificationSync as S, getSkillsServer as T, getSkillsWorkspaceName as U, getSkillsCollectionName as V, fetchWithTimeout as W, searchSkills as X, SKILLS_DIR as Y, getSkillInfo as Z, downloadSkillFile as _, createSessionStore as a, resolveModel as a0,
|
|
24716
|
+
export { listSkillFiles as $, saveWorkflow as A, rawWorkflow as B, listWorkflows as C, isWorkflowEnabled as D, workflowSchedules as E, inZone as F, cronMatches as G, summarize as H, workflowSteps as I, parseJwtEmail as J, computeCollectionConfigUpdate as K, SYSTEM_COLLECTION_CONFIG as L, loadMachineContext as M, buildMachineInstructions as N, machineToolsForRole as O, buildMachineTools as P, parseFrontmatter as Q, READ_ONLY_TOOLS as R, SharingNotificationSync as S, getSkillsServer as T, getSkillsWorkspaceName as U, getSkillsCollectionName as V, fetchWithTimeout as W, searchSkills as X, SKILLS_DIR as Y, getSkillInfo as Z, downloadSkillFile as _, createSessionStore as a, resolveModel as a0, formatHandle as a1, normalizeAllowedUser as a2, loadSecurityContextConfig as a3, resolveSecurityContext as a4, buildSecurityContextFromFlags as a5, mergeSecurityContexts as a6, buildSessionShareUrl as a7, computeOutboundHop as a8, registerAwaitingReply as a9, kimiSetup as aA, api as aB, run as aC, buildMachineShareUrl as aa, parseHandle as ab, handleMatchesMetadata as ac, PINNED_CODEX_VERSION as ad, clearStopMarker as ae, stopMarkerExists as af, withFileLock as ag, describeMisconfiguration as ah, buildMachineDeps as ai, applyClaudeProxyEnv as aj, composeSessionId as ak, generateFriendlyName as al, generateHookSettings as am, instanceConfig as an, frpc as ao, staticFileServer as ap, claudeAuth as aq, codexProvider as ar, projectInfo as as, DefaultTransport$1 as at, acpBackend as au, acpAgentConfig as av, codexAppServerBackend as aw, GeminiTransport$1 as ax, KimiTransport$1 as ay, kimiProvider as az, stopDaemon as b, connectToHypha as c, daemonStatus as d, shortId as e, resolveProjectRoot as f, getHyphaServerUrl$1 as g, getIssue as h, resumeIssue as i, addComment as j, addIssue as k, listIssues as l, isPendingIssue as m, searchIssues as n, isVisibleTo as o, pauseIssue as p, getRun as q, registerMachineService as r, startDaemon as s, listRuns as t, updateIssue as u, getWorkflow as v, runWorkflow as w, setWorkflowEnabled as x, removeWorkflow as y, validateWorkflowName as z };
|