svamp-cli 0.2.319 → 0.2.320
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-JGAVkD1F.mjs} +1 -1
- package/dist/{agentCommands-BJeGwkeX.mjs → agentCommands-CoCcX8K1.mjs} +32 -6
- package/dist/{auth-0NUGwOc0.mjs → auth-CFGN1ArQ.mjs} +1 -1
- package/dist/{cli-CWElpiIq.mjs → cli-C0tO-Dnx.mjs} +89 -85
- package/dist/cli.mjs +2 -2
- package/dist/{commands-CLgdrTSU.mjs → commands-6KY9uV6s.mjs} +5 -3
- package/dist/{commands-CJWgDDl5.mjs → commands-BHcPDQCC.mjs} +5 -3
- package/dist/{commands-DubEbPAY.mjs → commands-BUZUle6g.mjs} +4 -2
- package/dist/{commands-C0lwV0U3.mjs → commands-BwkoaGfZ.mjs} +5 -3
- package/dist/{commands-DMSkWb0h.mjs → commands-CeFjRNKk.mjs} +34 -9
- package/dist/{commands-BY1lmXaM.mjs → commands-CubsoWG-.mjs} +1 -1
- package/dist/{commands-BsaTwKnd.mjs → commands-DPi7PGxP.mjs} +3 -3
- package/dist/{commands-AdVHZXK6.mjs → commands-XN2UtO-W.mjs} +1 -1
- package/dist/{fleet-OKnU0cYK.mjs → fleet-BKrbG7G-.mjs} +2 -2
- package/dist/{headlessCli-CCO0IFYl.mjs → headlessCli-nywi2aop.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{notifyCommands-4GUy4Mcl.mjs → notifyCommands-BFr80nfh.mjs} +1 -1
- package/dist/package-D4zdkORw.mjs +64 -0
- package/dist/{pinnedClaudeCode-dbXgsGNK.mjs → pinnedClaudeCode-BkZfX5pW.mjs} +1 -1
- package/dist/{rpc-TXF-di9u.mjs → rpc-DVtlZahE.mjs} +1 -1
- package/dist/{rpc-FGp88Yxp.mjs → rpc-rpH-yXR7.mjs} +1 -1
- package/dist/{run-7TVWXnOM.mjs → run-C22c9FOe.mjs} +1 -1
- package/dist/{run-09AkgeGM.mjs → run-CvwPIrfo.mjs} +241 -90
- package/dist/{scheduler-b-ysyKIj.mjs → scheduler-CYyGe6kn.mjs} +1 -1
- package/dist/{serveCommands-D3W7w0G6.mjs → serveCommands-DRMwdDXS.mjs} +33 -8
- package/dist/{sideband-C1db5sMF.mjs → sideband-D1dsa-t0.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);
|
|
@@ -4264,6 +4315,10 @@ Connection: close\r
|
|
|
4264
4315
|
async startMountTunnel(mountName) {
|
|
4265
4316
|
if (this.mountTunnels.has(mountName)) return;
|
|
4266
4317
|
if (!this.port) throw new Error("Auth proxy not running \u2014 call ensureRunning() first");
|
|
4318
|
+
if (process.env.SVAMP_SERVE_NO_TUNNEL === "1") {
|
|
4319
|
+
this.log(`Mount '${mountName}': tunnel skipped (SVAMP_SERVE_NO_TUNNEL=1) \u2014 local only at http://127.0.0.1:${this.port}/${mountName}/`);
|
|
4320
|
+
return;
|
|
4321
|
+
}
|
|
4267
4322
|
const subdomainSafe = mountName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
4268
4323
|
const tunnelName = `static-${subdomainSafe}`;
|
|
4269
4324
|
const mount = this.mounts.get(mountName);
|
|
@@ -4319,6 +4374,7 @@ var serveManager = /*#__PURE__*/Object.freeze({
|
|
|
4319
4374
|
ServeManager: ServeManager,
|
|
4320
4375
|
assertNonAdminMountDirSafe: assertNonAdminMountDirSafe,
|
|
4321
4376
|
buildLinkSubdomain: buildLinkSubdomain,
|
|
4377
|
+
resolveMountOwnerEmail: resolveMountOwnerEmail,
|
|
4322
4378
|
sanitizeMountForRole: sanitizeMountForRole
|
|
4323
4379
|
});
|
|
4324
4380
|
|
|
@@ -4469,6 +4525,10 @@ function setClaudeAuthCustom(baseUrl, apiKey) {
|
|
|
4469
4525
|
ANTHROPIC_API_KEY: apiKey
|
|
4470
4526
|
});
|
|
4471
4527
|
}
|
|
4528
|
+
function applyPromptCacheTtl(spawnEnv) {
|
|
4529
|
+
const cacheOverride = spawnEnv.ENABLE_PROMPT_CACHING_1H ?? process.env.ENABLE_PROMPT_CACHING_1H ?? spawnEnv.FORCE_PROMPT_CACHING_5M ?? process.env.FORCE_PROMPT_CACHING_5M;
|
|
4530
|
+
if (cacheOverride === void 0) spawnEnv.ENABLE_PROMPT_CACHING_1H = "1";
|
|
4531
|
+
}
|
|
4472
4532
|
function applyClaudeProxyEnv(spawnEnv) {
|
|
4473
4533
|
const mode = currentMode();
|
|
4474
4534
|
if (mode === "hypha") {
|
|
@@ -4486,8 +4546,7 @@ function applyClaudeProxyEnv(spawnEnv) {
|
|
|
4486
4546
|
}
|
|
4487
4547
|
spawnEnv.ANTHROPIC_BASE_URL = proxyUrl;
|
|
4488
4548
|
spawnEnv.ANTHROPIC_API_KEY = token;
|
|
4489
|
-
|
|
4490
|
-
if (cacheOverride === void 0) spawnEnv.ENABLE_PROMPT_CACHING_1H = "1";
|
|
4549
|
+
applyPromptCacheTtl(spawnEnv);
|
|
4491
4550
|
return `hypha proxy (${proxyUrl}, 1h cache TTL)`;
|
|
4492
4551
|
}
|
|
4493
4552
|
if (mode === "custom") {
|
|
@@ -4500,7 +4559,8 @@ function applyClaudeProxyEnv(spawnEnv) {
|
|
|
4500
4559
|
}
|
|
4501
4560
|
spawnEnv.ANTHROPIC_BASE_URL = url;
|
|
4502
4561
|
spawnEnv.ANTHROPIC_API_KEY = key;
|
|
4503
|
-
|
|
4562
|
+
applyPromptCacheTtl(spawnEnv);
|
|
4563
|
+
return `custom proxy (${url}, 1h cache TTL)`;
|
|
4504
4564
|
}
|
|
4505
4565
|
delete spawnEnv.ANTHROPIC_BASE_URL;
|
|
4506
4566
|
delete spawnEnv.ANTHROPIC_API_KEY;
|
|
@@ -6360,6 +6420,13 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
6360
6420
|
} catch {
|
|
6361
6421
|
}
|
|
6362
6422
|
const serveCallerTrusted = (context) => !context?.user || isSameOwnerWorkspace(context) || roleAtLeast(getEffectiveRole(context, currentMetadata.sharing), "admin");
|
|
6423
|
+
const resolveMountOwnerEmailFor = (supplied, context) => resolveMountOwnerEmail(
|
|
6424
|
+
supplied,
|
|
6425
|
+
context?.user?.email,
|
|
6426
|
+
currentMetadata.sharing?.owner,
|
|
6427
|
+
serveCallerTrusted(context),
|
|
6428
|
+
process.env.SVAMP_OWNER_EMAIL
|
|
6429
|
+
);
|
|
6363
6430
|
let lastInboundRpcAt = Date.now();
|
|
6364
6431
|
const trackInbound = () => {
|
|
6365
6432
|
lastInboundRpcAt = Date.now();
|
|
@@ -7526,7 +7593,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
7526
7593
|
const { homedir } = await import('os');
|
|
7527
7594
|
assertNonAdminMountDirSafe(params.directory, homedir());
|
|
7528
7595
|
}
|
|
7529
|
-
|
|
7596
|
+
if (params.sessionId && !serveCallerTrusted(context)) {
|
|
7597
|
+
await authorizeSessionAccess(params.sessionId, "admin", context);
|
|
7598
|
+
}
|
|
7599
|
+
const ownerEmail2 = resolveMountOwnerEmailFor(params.ownerEmail, context);
|
|
7530
7600
|
const access = params.access || "owner";
|
|
7531
7601
|
return sm.addMount(params.name, params.directory, params.sessionId, access, ownerEmail2);
|
|
7532
7602
|
},
|
|
@@ -7543,6 +7613,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
7543
7613
|
const { homedir } = await import('os');
|
|
7544
7614
|
assertNonAdminMountDirSafe(params.directory, homedir());
|
|
7545
7615
|
}
|
|
7616
|
+
if (params.sessionId && !serveCallerTrusted(context)) {
|
|
7617
|
+
await authorizeSessionAccess(params.sessionId, "admin", context);
|
|
7618
|
+
}
|
|
7546
7619
|
const existingMount = sm.getMount(params.name);
|
|
7547
7620
|
if (existingMount && !serveCallerTrusted(context)) {
|
|
7548
7621
|
const callerEmail = (context?.user?.email || "").toLowerCase();
|
|
@@ -7551,7 +7624,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
7551
7624
|
throw new Error(`Not authorized to replace mount '${params.name}' (owned by another user)`);
|
|
7552
7625
|
}
|
|
7553
7626
|
}
|
|
7554
|
-
const ownerEmail2 = params.ownerEmail
|
|
7627
|
+
const ownerEmail2 = resolveMountOwnerEmailFor(params.ownerEmail, context);
|
|
7555
7628
|
const access = params.access ?? "owner";
|
|
7556
7629
|
return sm.applyMount({
|
|
7557
7630
|
name: params.name,
|
|
@@ -7921,7 +7994,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
7921
7994
|
}
|
|
7922
7995
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
7923
7996
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
7924
|
-
const { toolsForRole } = await import('./sideband-
|
|
7997
|
+
const { toolsForRole } = await import('./sideband-D1dsa-t0.mjs');
|
|
7925
7998
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
7926
7999
|
return fmt(r2);
|
|
7927
8000
|
}
|
|
@@ -8026,7 +8099,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
8026
8099
|
return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
|
|
8027
8100
|
}
|
|
8028
8101
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
8029
|
-
const { queryCore } = await import('./commands-
|
|
8102
|
+
const { queryCore } = await import('./commands-CubsoWG-.mjs');
|
|
8030
8103
|
const timeout = c.reply?.timeout_sec || 120;
|
|
8031
8104
|
let result;
|
|
8032
8105
|
try {
|
|
@@ -8238,6 +8311,9 @@ ${d?.error || "not found"}`;
|
|
|
8238
8311
|
});
|
|
8239
8312
|
},
|
|
8240
8313
|
getLastInboundRpcAt: () => lastInboundRpcAt,
|
|
8314
|
+
resetInboundClock: () => {
|
|
8315
|
+
lastInboundRpcAt = Date.now();
|
|
8316
|
+
},
|
|
8241
8317
|
getCurrentMetadata: () => currentMetadata,
|
|
8242
8318
|
setHandlers: (newHandlers) => {
|
|
8243
8319
|
handlers = newHandlers;
|
|
@@ -16370,8 +16446,12 @@ function isVisibleTo(issue, session) {
|
|
|
16370
16446
|
if (!issue.owner && !issue.session) return true;
|
|
16371
16447
|
return false;
|
|
16372
16448
|
}
|
|
16449
|
+
function normalizeIssueRef(id) {
|
|
16450
|
+
const s = String(id ?? "").trim().replace(/^#+/, "");
|
|
16451
|
+
return /^\d+$/.test(s) ? s.padStart(4, "0") : s;
|
|
16452
|
+
}
|
|
16373
16453
|
function getIssue(projectRoot, id) {
|
|
16374
|
-
const padded =
|
|
16454
|
+
const padded = normalizeIssueRef(id);
|
|
16375
16455
|
let best = null;
|
|
16376
16456
|
for (const p of [issuePath(projectRoot, padded), issuePath(projectRoot, padded, true)]) {
|
|
16377
16457
|
if (!existsSync(p)) continue;
|
|
@@ -16383,7 +16463,14 @@ function getIssue(projectRoot, id) {
|
|
|
16383
16463
|
} catch {
|
|
16384
16464
|
}
|
|
16385
16465
|
}
|
|
16386
|
-
|
|
16466
|
+
if (best) return best.issue;
|
|
16467
|
+
try {
|
|
16468
|
+
for (const issue of listIssues(projectRoot, { includeArchived: true })) {
|
|
16469
|
+
if (normalizeIssueRef(issue.id) === padded) return issue;
|
|
16470
|
+
}
|
|
16471
|
+
} catch {
|
|
16472
|
+
}
|
|
16473
|
+
return null;
|
|
16387
16474
|
}
|
|
16388
16475
|
function atomicWrite(path, content) {
|
|
16389
16476
|
mkdirSync$1(dirname(path), { recursive: true });
|
|
@@ -16432,8 +16519,7 @@ function addIssue(projectRoot, fields) {
|
|
|
16432
16519
|
throw new Error("addIssue: could not allocate a free issue id");
|
|
16433
16520
|
}
|
|
16434
16521
|
function issueLockPath(projectRoot, id) {
|
|
16435
|
-
|
|
16436
|
-
return join$1(issuesDir(projectRoot), `${padded}.lock`);
|
|
16522
|
+
return join$1(issuesDir(projectRoot), `${normalizeIssueRef(id)}.lock`);
|
|
16437
16523
|
}
|
|
16438
16524
|
function withIssueLock(projectRoot, id, fn) {
|
|
16439
16525
|
try {
|
|
@@ -16446,16 +16532,20 @@ function _updateIssueUnlocked(projectRoot, id, patch) {
|
|
|
16446
16532
|
const cur = getIssue(projectRoot, id);
|
|
16447
16533
|
if (!cur) return null;
|
|
16448
16534
|
const wasArchived = cur.status === "archived";
|
|
16449
|
-
const
|
|
16535
|
+
const canonicalId = normalizeIssueRef(cur.id);
|
|
16536
|
+
const next = { ...cur, ...patch, id: canonicalId };
|
|
16450
16537
|
if (patch.status) next.status = normalizeStatus(patch.status);
|
|
16451
16538
|
const nowArchived = next.status === "archived";
|
|
16452
16539
|
if (nowArchived && !next.closed) next.closed = (/* @__PURE__ */ new Date()).toISOString();
|
|
16453
16540
|
if (!nowArchived) next.closed = null;
|
|
16454
16541
|
atomicWrite(issuePath(projectRoot, next.id, nowArchived), serializeIssue(next));
|
|
16455
|
-
|
|
16456
|
-
|
|
16542
|
+
for (const stale of [
|
|
16543
|
+
issuePath(projectRoot, cur.id, wasArchived),
|
|
16544
|
+
issuePath(projectRoot, cur.id, nowArchived)
|
|
16545
|
+
]) {
|
|
16546
|
+
if (stale === issuePath(projectRoot, next.id, nowArchived)) continue;
|
|
16457
16547
|
try {
|
|
16458
|
-
if (existsSync(
|
|
16548
|
+
if (existsSync(stale)) unlinkSync$1(stale);
|
|
16459
16549
|
} catch {
|
|
16460
16550
|
}
|
|
16461
16551
|
}
|
|
@@ -17439,6 +17529,19 @@ function parseEvaluatorVerdict(text) {
|
|
|
17439
17529
|
}
|
|
17440
17530
|
return null;
|
|
17441
17531
|
}
|
|
17532
|
+
function buildStaleSafeOracleGuidance(oracleOutput, now = /* @__PURE__ */ new Date()) {
|
|
17533
|
+
const reading = (oracleOutput || "").trim().slice(0, 500);
|
|
17534
|
+
return [
|
|
17535
|
+
`The loop oracle reported unfinished work as of ${now.toISOString()}:`,
|
|
17536
|
+
reading || "(no detail)",
|
|
17537
|
+
"",
|
|
17538
|
+
"That is a POINT-IN-TIME reading, not a fact about right now \u2014 the backlog may have changed",
|
|
17539
|
+
"since (an item can be closed or paused out-of-band while this message was queued). RE-RUN",
|
|
17540
|
+
"the oracle yourself and treat ITS CURRENT OUTPUT as the truth. If it is now empty, the goal",
|
|
17541
|
+
"is complete: say so rather than chasing the items listed above. Otherwise keep working until",
|
|
17542
|
+
"it is genuinely empty."
|
|
17543
|
+
].join("\n");
|
|
17544
|
+
}
|
|
17442
17545
|
async function runLoopVerification(cfg, deps) {
|
|
17443
17546
|
const log = deps.log || (() => {
|
|
17444
17547
|
});
|
|
@@ -17453,9 +17556,11 @@ async function runLoopVerification(cfg, deps) {
|
|
|
17453
17556
|
return { action: "gave_up", reason: `oracle still reports pending work after ${holds} verification hold(s): ${oracleOutput.trim().slice(0, 200)}` };
|
|
17454
17557
|
}
|
|
17455
17558
|
log(`[loopVerify] oracle reports pending work \u2014 re-kicking instead of marking done`);
|
|
17456
|
-
return {
|
|
17457
|
-
|
|
17458
|
-
|
|
17559
|
+
return {
|
|
17560
|
+
action: "rekick",
|
|
17561
|
+
reason: "oracle reports pending work",
|
|
17562
|
+
guidance: buildStaleSafeOracleGuidance(oracleOutput)
|
|
17563
|
+
};
|
|
17459
17564
|
}
|
|
17460
17565
|
} catch (e) {
|
|
17461
17566
|
log(`[loopVerify] oracle runner error (continuing to evaluator): ${e?.message || e}`);
|
|
@@ -17684,6 +17789,16 @@ function compactUnsupportedWarning(version) {
|
|
|
17684
17789
|
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
17790
|
}
|
|
17686
17791
|
|
|
17792
|
+
function shouldRunZombieProbe(args) {
|
|
17793
|
+
return args.hasActiveSessions && !args.inGrace && args.consecutiveHeartbeatFailures === 0 && args.inboundSilenceMs > args.thresholdMs;
|
|
17794
|
+
}
|
|
17795
|
+
function nextFailuresAfterZombieProbeFailure(consecutiveHeartbeatFailures) {
|
|
17796
|
+
return consecutiveHeartbeatFailures + 1;
|
|
17797
|
+
}
|
|
17798
|
+
function shouldForceReconnect(consecutiveHeartbeatFailures) {
|
|
17799
|
+
return consecutiveHeartbeatFailures === 2 || consecutiveHeartbeatFailures % 3 === 0;
|
|
17800
|
+
}
|
|
17801
|
+
|
|
17687
17802
|
const SVAMP_HOME$1 = process.env.SVAMP_HOME || join$1(os.homedir(), ".svamp");
|
|
17688
17803
|
function generateHookSettings(portOrOptions = {}) {
|
|
17689
17804
|
const opts = typeof portOrOptions === "number" ? { sessionStartPort: portOrOptions } : portOrOptions;
|
|
@@ -18555,9 +18670,11 @@ function atomicWriteLoopState(path, obj) {
|
|
|
18555
18670
|
writeFileSync(tmp, JSON.stringify(obj, null, 2));
|
|
18556
18671
|
renameSync$1(tmp, path);
|
|
18557
18672
|
}
|
|
18558
|
-
function safeBacklogPendingCount(
|
|
18673
|
+
function safeBacklogPendingCount(directory, selfSessionId, oracle) {
|
|
18559
18674
|
try {
|
|
18560
|
-
|
|
18675
|
+
const root = resolveProjectRoot(directory);
|
|
18676
|
+
const scope = oracleScopeSessionId(typeof oracle === "string" ? oracle : void 0, selfSessionId);
|
|
18677
|
+
return backlogOraclePending(root, scope).length;
|
|
18561
18678
|
} catch {
|
|
18562
18679
|
return 0;
|
|
18563
18680
|
}
|
|
@@ -19510,7 +19627,7 @@ async function startDaemon(options) {
|
|
|
19510
19627
|
try {
|
|
19511
19628
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
19512
19629
|
if (!dir) return;
|
|
19513
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
19630
|
+
const { reconcileServiceLinks } = await import('./agentCommands-CoCcX8K1.mjs');
|
|
19514
19631
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
19515
19632
|
const config = readSvampConfig(configPath);
|
|
19516
19633
|
const entries = Array.from(urls.entries());
|
|
@@ -19532,7 +19649,7 @@ async function startDaemon(options) {
|
|
|
19532
19649
|
try {
|
|
19533
19650
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
19534
19651
|
if (!dir) return;
|
|
19535
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
19652
|
+
const { reconcileServiceLinks } = await import('./agentCommands-CoCcX8K1.mjs');
|
|
19536
19653
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
19537
19654
|
const config = readSvampConfig(configPath);
|
|
19538
19655
|
const incoming = [{
|
|
@@ -19548,6 +19665,22 @@ async function startDaemon(options) {
|
|
|
19548
19665
|
logger.log(`[serve] Link reconcile failed for mount ${mountName}: ${err?.message || err}`);
|
|
19549
19666
|
}
|
|
19550
19667
|
}
|
|
19668
|
+
async function dropMountSessionLink(sessionId, mountName) {
|
|
19669
|
+
if (!sessionId) return;
|
|
19670
|
+
try {
|
|
19671
|
+
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
19672
|
+
if (!dir) return;
|
|
19673
|
+
const { dropServiceLinks } = await import('./agentCommands-CoCcX8K1.mjs');
|
|
19674
|
+
const configPath = getSvampConfigPath(dir, sessionId);
|
|
19675
|
+
const config = readSvampConfig(configPath);
|
|
19676
|
+
if (dropServiceLinks(config, "serve", mountName)) {
|
|
19677
|
+
writeSvampConfig(configPath, config);
|
|
19678
|
+
logger.log(`[serve] Dropped Launch Pad link for mount ${mountName} \u2190 session ${sessionId}`);
|
|
19679
|
+
}
|
|
19680
|
+
} catch (err) {
|
|
19681
|
+
logger.log(`[serve] Link drop failed for mount ${mountName}: ${err?.message || err}`);
|
|
19682
|
+
}
|
|
19683
|
+
}
|
|
19551
19684
|
async function createExposedTunnel(spec) {
|
|
19552
19685
|
const { FrpcTunnel } = await Promise.resolve().then(function () { return frpc; });
|
|
19553
19686
|
const tunnel = new FrpcTunnel({
|
|
@@ -19572,12 +19705,15 @@ async function startDaemon(options) {
|
|
|
19572
19705
|
const tunnelRecreateInFlight = /* @__PURE__ */ new Set();
|
|
19573
19706
|
const { ServeManager } = await Promise.resolve().then(function () { return serveManager; });
|
|
19574
19707
|
const serveManager$1 = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
19708
|
+
serveManager$1.setMountUnboundHook((sessionId, mountName) => {
|
|
19709
|
+
void dropMountSessionLink(sessionId, mountName);
|
|
19710
|
+
});
|
|
19575
19711
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
19576
19712
|
});
|
|
19577
19713
|
ensureAutoInstalledCommands(logger);
|
|
19578
19714
|
(async () => {
|
|
19579
19715
|
try {
|
|
19580
|
-
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-
|
|
19716
|
+
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-BkZfX5pW.mjs');
|
|
19581
19717
|
beginClaudeVersionReconcile((msg) => logger.log(msg));
|
|
19582
19718
|
} catch (e) {
|
|
19583
19719
|
logger.log(`[claude-version] check failed: ${e?.message || e}`);
|
|
@@ -19690,10 +19826,14 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
|
|
|
19690
19826
|
}
|
|
19691
19827
|
};
|
|
19692
19828
|
server.on("services_registered", () => {
|
|
19829
|
+
lastReconnectAt = Date.now();
|
|
19830
|
+
try {
|
|
19831
|
+
machineService.resetInboundClock?.();
|
|
19832
|
+
} catch {
|
|
19833
|
+
}
|
|
19693
19834
|
if (consecutiveHeartbeatFailures > 0) {
|
|
19694
19835
|
logger.log(`Hypha reconnection successful \u2014 services re-registered (resetting ${consecutiveHeartbeatFailures} failures)`);
|
|
19695
19836
|
consecutiveHeartbeatFailures = 0;
|
|
19696
|
-
lastReconnectAt = Date.now();
|
|
19697
19837
|
}
|
|
19698
19838
|
const reEmitLiveness = (phase) => {
|
|
19699
19839
|
try {
|
|
@@ -20259,26 +20399,33 @@ ${parts.join("\n")}`);
|
|
|
20259
20399
|
const oracleCmd = typeof ls.oracle === "string" && ls.oracle.trim() ? ls.oracle.trim() : void 0;
|
|
20260
20400
|
try {
|
|
20261
20401
|
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}`)
|
|
20402
|
+
const runOracle = async (cmd) => {
|
|
20403
|
+
if (cmd) {
|
|
20404
|
+
return await new Promise((resolve2) => {
|
|
20405
|
+
exec$1(cmd, { cwd: directory, timeout: 12e4, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
20406
|
+
if (err) resolve2({ ok: false, output: (String(stdout || "") + String(stderr || "")).slice(-500) });
|
|
20407
|
+
else resolve2({ ok: true, output: "" });
|
|
20408
|
+
});
|
|
20409
|
+
});
|
|
20280
20410
|
}
|
|
20281
|
-
|
|
20411
|
+
const pending = backlogOraclePending(projectRoot, sessionId);
|
|
20412
|
+
return { ok: pending.length === 0, output: pending.length ? `${pending.length} pending: ${pending.map((i) => "#" + i.id).join(" ")}` : "No pending issues." };
|
|
20413
|
+
};
|
|
20414
|
+
const verifyDeps = {
|
|
20415
|
+
projectRoot,
|
|
20416
|
+
runOracle,
|
|
20417
|
+
runEvaluator: (prompt) => runHeadlessEvaluator(prompt),
|
|
20418
|
+
log: (m) => logger.log(`[Session ${sessionId}] ${m}`)
|
|
20419
|
+
};
|
|
20420
|
+
const verifyCfg = { task: ls.goal_task || ls.task, until: ls.until, oracle: oracleCmd, startedAt, sessionId, holds, maxHolds: 3 };
|
|
20421
|
+
let result = await runLoopVerification(verifyCfg, verifyDeps);
|
|
20422
|
+
if (result.action === "rekick" && result.reason === "oracle reports pending work") {
|
|
20423
|
+
const fresh = await runOracle(oracleCmd);
|
|
20424
|
+
if (fresh.ok) {
|
|
20425
|
+
logger.log(`[Session ${sessionId}] [#0194] oracle went CLEAN during verification \u2014 re-running the gate instead of re-kicking on the stale reading`);
|
|
20426
|
+
result = await runLoopVerification(verifyCfg, { ...verifyDeps, runOracle: async () => fresh });
|
|
20427
|
+
}
|
|
20428
|
+
}
|
|
20282
20429
|
if (loopCancelledDuringVerify(readLoopState(directory, sessionId))) {
|
|
20283
20430
|
logger.log(`[Session ${sessionId}] loop was cancelled during verification \u2014 not re-kicking (#0308)`);
|
|
20284
20431
|
return;
|
|
@@ -20958,7 +21105,7 @@ ${parts.join("\n")}`);
|
|
|
20958
21105
|
const maxExt = resolveMaxExtensions(process.env.SVAMP_LOOP_MAX_EXTENSIONS);
|
|
20959
21106
|
const effMaxIters = effectiveSoftCap(ls.max_iterations, ls.extensions, maxExt);
|
|
20960
21107
|
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(
|
|
21108
|
+
const pending = safeBacklogPendingCount(directory, sessionId, ls.oracle);
|
|
20962
21109
|
const prog = updateLoopProgress(ls, pending, now);
|
|
20963
21110
|
ls.progress_history = prog.progress_history;
|
|
20964
21111
|
ls.auto_resumes = prog.auto_resumes;
|
|
@@ -21894,11 +22041,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21894
22041
|
});
|
|
21895
22042
|
},
|
|
21896
22043
|
onIssue: async (params) => {
|
|
21897
|
-
const { issueRpc } = await import('./rpc-
|
|
22044
|
+
const { issueRpc } = await import('./rpc-DVtlZahE.mjs');
|
|
21898
22045
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
21899
22046
|
},
|
|
21900
22047
|
onWorkflow: async (params) => {
|
|
21901
|
-
const { workflowRpc } = await import('./rpc-
|
|
22048
|
+
const { workflowRpc } = await import('./rpc-rpH-yXR7.mjs');
|
|
21902
22049
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
21903
22050
|
},
|
|
21904
22051
|
onRipgrep: async (args, cwd) => {
|
|
@@ -22605,11 +22752,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
22605
22752
|
});
|
|
22606
22753
|
},
|
|
22607
22754
|
onIssue: async (params) => {
|
|
22608
|
-
const { issueRpc } = await import('./rpc-
|
|
22755
|
+
const { issueRpc } = await import('./rpc-DVtlZahE.mjs');
|
|
22609
22756
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
22610
22757
|
},
|
|
22611
22758
|
onWorkflow: async (params) => {
|
|
22612
|
-
const { workflowRpc } = await import('./rpc-
|
|
22759
|
+
const { workflowRpc } = await import('./rpc-rpH-yXR7.mjs');
|
|
22613
22760
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
22614
22761
|
},
|
|
22615
22762
|
onRipgrep: async (args, cwd) => {
|
|
@@ -22958,7 +23105,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
22958
23105
|
const acpMaxExt = resolveMaxExtensions(process.env.SVAMP_LOOP_MAX_EXTENSIONS);
|
|
22959
23106
|
const acpEffMax = effectiveSoftCap(ls.max_iterations, ls.extensions, acpMaxExt);
|
|
22960
23107
|
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);
|
|
23108
|
+
const acpPending = safeBacklogPendingCount(projectRoot, sessionId, ls.oracle);
|
|
22962
23109
|
const acpProg = updateLoopProgress(ls, acpPending, now);
|
|
22963
23110
|
ls.progress_history = acpProg.progress_history;
|
|
22964
23111
|
ls.auto_resumes = acpProg.auto_resumes;
|
|
@@ -23660,7 +23807,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23660
23807
|
}
|
|
23661
23808
|
if (persistedSessions.length > 0) {
|
|
23662
23809
|
try {
|
|
23663
|
-
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-
|
|
23810
|
+
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-BkZfX5pW.mjs');
|
|
23664
23811
|
await awaitClaudeVersionReady();
|
|
23665
23812
|
} catch {
|
|
23666
23813
|
}
|
|
@@ -23700,7 +23847,11 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23700
23847
|
isOrphaned: !!isOrphaned,
|
|
23701
23848
|
loopActive: isLoopActiveForSession(persisted.directory, persisted.sessionId),
|
|
23702
23849
|
loopArmed: _loopArmed,
|
|
23703
|
-
armedHasPendingWork: _loopArmed ? safeBacklogPendingCount(
|
|
23850
|
+
armedHasPendingWork: _loopArmed ? safeBacklogPendingCount(
|
|
23851
|
+
persisted.directory,
|
|
23852
|
+
persisted.sessionId,
|
|
23853
|
+
readLoopState(persisted.directory, persisted.sessionId)?.oracle
|
|
23854
|
+
) > 0 : false
|
|
23704
23855
|
});
|
|
23705
23856
|
if (_loopArmed) {
|
|
23706
23857
|
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 +24056,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23905
24056
|
const PING_TIMEOUT_MS = 15e3;
|
|
23906
24057
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
23907
24058
|
const RECONNECT_JITTER_MS = 2500;
|
|
23908
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
24059
|
+
const { WorkflowScheduler } = await import('./scheduler-CYyGe6kn.mjs');
|
|
23909
24060
|
const workflowProjectRoots = () => {
|
|
23910
24061
|
const dirs = /* @__PURE__ */ new Set();
|
|
23911
24062
|
for (const s of pidToTrackedSession.values()) {
|
|
@@ -23981,10 +24132,11 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23981
24132
|
}
|
|
23982
24133
|
}
|
|
23983
24134
|
}
|
|
24135
|
+
const inGrace = lastReconnectAt > 0 && Date.now() - lastReconnectAt < POST_RECONNECT_GRACE_MS;
|
|
23984
24136
|
const INBOUND_SILENCE_THRESHOLD_MS = 12e4;
|
|
23985
24137
|
const inboundSilenceMs = Date.now() - machineService.getLastInboundRpcAt();
|
|
23986
24138
|
const hasActiveSessions = pidToTrackedSession.size > 0;
|
|
23987
|
-
if (hasActiveSessions
|
|
24139
|
+
if (shouldRunZombieProbe({ hasActiveSessions, inboundSilenceMs, consecutiveHeartbeatFailures, inGrace, thresholdMs: INBOUND_SILENCE_THRESHOLD_MS })) {
|
|
23988
24140
|
logger.log(`No inbound RPC for ${Math.round(inboundSilenceMs / 1e3)}s with ${pidToTrackedSession.size} active session(s) \u2014 zombie probe`);
|
|
23989
24141
|
try {
|
|
23990
24142
|
const machineServiceId = `${server.config.client_id}:default`;
|
|
@@ -23993,11 +24145,10 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23993
24145
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Zombie probe timed out")), PING_TIMEOUT_MS))
|
|
23994
24146
|
]);
|
|
23995
24147
|
} catch (err) {
|
|
23996
|
-
logger.log(`Zombie detection probe failed: ${err.message} \u2014
|
|
23997
|
-
consecutiveHeartbeatFailures =
|
|
24148
|
+
logger.log(`Zombie detection probe failed: ${err.message} \u2014 raising suspicion (two-strike)`);
|
|
24149
|
+
consecutiveHeartbeatFailures = nextFailuresAfterZombieProbeFailure(consecutiveHeartbeatFailures);
|
|
23998
24150
|
}
|
|
23999
24151
|
}
|
|
24000
|
-
const inGrace = lastReconnectAt > 0 && Date.now() - lastReconnectAt < POST_RECONNECT_GRACE_MS;
|
|
24001
24152
|
if (!inGrace) {
|
|
24002
24153
|
try {
|
|
24003
24154
|
const pingStart = Date.now();
|
|
@@ -24022,7 +24173,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
24022
24173
|
} else if (consecutiveHeartbeatFailures % 6 === 0) {
|
|
24023
24174
|
logger.log(`Connection down for ${consecutiveHeartbeatFailures * HEARTBEAT_INTERVAL_MS / 1e3}s (${consecutiveHeartbeatFailures} failures, retrying indefinitely)`);
|
|
24024
24175
|
}
|
|
24025
|
-
if (consecutiveHeartbeatFailures
|
|
24176
|
+
if (shouldForceReconnect(consecutiveHeartbeatFailures)) {
|
|
24026
24177
|
const jitterMs = Math.floor(Math.random() * RECONNECT_JITTER_MS);
|
|
24027
24178
|
if (jitterMs > 0) await new Promise((r) => setTimeout(r, jitterMs));
|
|
24028
24179
|
const conn = server.rpc?._connection;
|
|
@@ -24539,4 +24690,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
24539
24690
|
writeStopMarker: writeStopMarker
|
|
24540
24691
|
});
|
|
24541
24692
|
|
|
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,
|
|
24693
|
+
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 };
|