svamp-cli 0.2.318 → 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-_az3eIOd.mjs → adminCommands-JGAVkD1F.mjs} +1 -1
- package/dist/{agentCommands-CI_sYhy6.mjs → agentCommands-CoCcX8K1.mjs} +33 -7
- package/dist/{auth-7Og-RZ50.mjs → auth-CFGN1ArQ.mjs} +1 -1
- package/dist/{cli-D3_a7yin.mjs → cli-C0tO-Dnx.mjs} +89 -85
- package/dist/cli.mjs +2 -2
- package/dist/{commands-CDL2fp9I.mjs → commands-6KY9uV6s.mjs} +5 -3
- package/dist/{commands-C5bo2-Pk.mjs → commands-BHcPDQCC.mjs} +5 -3
- package/dist/{commands-B9niiK4o.mjs → commands-BUZUle6g.mjs} +4 -2
- package/dist/{commands-eS7P4neN.mjs → commands-BwkoaGfZ.mjs} +5 -3
- package/dist/{commands-CwvpWFl7.mjs → commands-CeFjRNKk.mjs} +34 -9
- package/dist/{commands-DRYk0mQr.mjs → commands-CubsoWG-.mjs} +1 -1
- package/dist/{commands-DaYsK7Af.mjs → commands-DPi7PGxP.mjs} +3 -3
- package/dist/{commands-DxJHm9r0.mjs → commands-XN2UtO-W.mjs} +1 -1
- package/dist/{fleet-BXfUlehy.mjs → fleet-BKrbG7G-.mjs} +2 -2
- package/dist/{headlessCli-CR9fW45i.mjs → headlessCli-nywi2aop.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{notifyCommands-DUr9QjLt.mjs → notifyCommands-BFr80nfh.mjs} +1 -1
- package/dist/package-D4zdkORw.mjs +64 -0
- package/dist/{pinnedClaudeCode-CnCpensV.mjs → pinnedClaudeCode-BkZfX5pW.mjs} +1 -1
- package/dist/{rpc-CaKQ3aCj.mjs → rpc-DVtlZahE.mjs} +1 -1
- package/dist/{rpc-DX7_LzZf.mjs → rpc-rpH-yXR7.mjs} +1 -1
- package/dist/{run-BuZDMLWU.mjs → run-C22c9FOe.mjs} +1 -1
- package/dist/{run-CC2ckdDX.mjs → run-CvwPIrfo.mjs} +271 -91
- package/dist/{scheduler-B11LcOId.mjs → scheduler-CYyGe6kn.mjs} +1 -1
- package/dist/{serveCommands-DtPbaNdu.mjs → serveCommands-DRMwdDXS.mjs} +55 -8
- package/dist/{sideband-C0CGbTIK.mjs → sideband-D1dsa-t0.mjs} +1 -1
- package/package.json +3 -3
- package/dist/package-CDjOkJPh.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
|
}
|
|
@@ -18803,7 +18920,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
18803
18920
|
url: String(l.url).trim(),
|
|
18804
18921
|
...l.icon ? { icon: String(l.icon) } : {},
|
|
18805
18922
|
// #0266: carry the service backing ({kind,name}) so the Launch Pad can decommission it.
|
|
18806
|
-
...l.service && typeof l.service.name === "string" && (l.service.kind === "tunnel" || l.service.kind === "process") ? { service: { kind: l.service.kind, name: String(l.service.name) } } : {}
|
|
18923
|
+
...l.service && typeof l.service.name === "string" && (l.service.kind === "tunnel" || l.service.kind === "process" || l.service.kind === "serve") ? { service: { kind: l.service.kind, name: String(l.service.name) } } : {}
|
|
18807
18924
|
}));
|
|
18808
18925
|
const cur = meta.sessionLinks;
|
|
18809
18926
|
if (JSON.stringify(cur || []) !== JSON.stringify(links)) {
|
|
@@ -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());
|
|
@@ -19527,6 +19644,43 @@ async function startDaemon(options) {
|
|
|
19527
19644
|
logger.log(`[exposed-tunnels] Link reconcile failed for ${tunnelName}: ${err?.message || err}`);
|
|
19528
19645
|
}
|
|
19529
19646
|
}
|
|
19647
|
+
async function reconcileMountSessionLink(sessionId, mountName, url) {
|
|
19648
|
+
if (!sessionId || !url) return;
|
|
19649
|
+
try {
|
|
19650
|
+
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
19651
|
+
if (!dir) return;
|
|
19652
|
+
const { reconcileServiceLinks } = await import('./agentCommands-CoCcX8K1.mjs');
|
|
19653
|
+
const configPath = getSvampConfigPath(dir, sessionId);
|
|
19654
|
+
const config = readSvampConfig(configPath);
|
|
19655
|
+
const incoming = [{
|
|
19656
|
+
url: String(url),
|
|
19657
|
+
label: mountName,
|
|
19658
|
+
service: { kind: "serve", name: mountName }
|
|
19659
|
+
}];
|
|
19660
|
+
if (reconcileServiceLinks(config, incoming)) {
|
|
19661
|
+
writeSvampConfig(configPath, config);
|
|
19662
|
+
logger.log(`[serve] Reconciled Launch Pad link for mount ${mountName} \u2192 session ${sessionId}`);
|
|
19663
|
+
}
|
|
19664
|
+
} catch (err) {
|
|
19665
|
+
logger.log(`[serve] Link reconcile failed for mount ${mountName}: ${err?.message || err}`);
|
|
19666
|
+
}
|
|
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
|
+
}
|
|
19530
19684
|
async function createExposedTunnel(spec) {
|
|
19531
19685
|
const { FrpcTunnel } = await Promise.resolve().then(function () { return frpc; });
|
|
19532
19686
|
const tunnel = new FrpcTunnel({
|
|
@@ -19551,12 +19705,15 @@ async function startDaemon(options) {
|
|
|
19551
19705
|
const tunnelRecreateInFlight = /* @__PURE__ */ new Set();
|
|
19552
19706
|
const { ServeManager } = await Promise.resolve().then(function () { return serveManager; });
|
|
19553
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
|
+
});
|
|
19554
19711
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
19555
19712
|
});
|
|
19556
19713
|
ensureAutoInstalledCommands(logger);
|
|
19557
19714
|
(async () => {
|
|
19558
19715
|
try {
|
|
19559
|
-
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-
|
|
19716
|
+
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-BkZfX5pW.mjs');
|
|
19560
19717
|
beginClaudeVersionReconcile((msg) => logger.log(msg));
|
|
19561
19718
|
} catch (e) {
|
|
19562
19719
|
logger.log(`[claude-version] check failed: ${e?.message || e}`);
|
|
@@ -19669,10 +19826,14 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
|
|
|
19669
19826
|
}
|
|
19670
19827
|
};
|
|
19671
19828
|
server.on("services_registered", () => {
|
|
19829
|
+
lastReconnectAt = Date.now();
|
|
19830
|
+
try {
|
|
19831
|
+
machineService.resetInboundClock?.();
|
|
19832
|
+
} catch {
|
|
19833
|
+
}
|
|
19672
19834
|
if (consecutiveHeartbeatFailures > 0) {
|
|
19673
19835
|
logger.log(`Hypha reconnection successful \u2014 services re-registered (resetting ${consecutiveHeartbeatFailures} failures)`);
|
|
19674
19836
|
consecutiveHeartbeatFailures = 0;
|
|
19675
|
-
lastReconnectAt = Date.now();
|
|
19676
19837
|
}
|
|
19677
19838
|
const reEmitLiveness = (phase) => {
|
|
19678
19839
|
try {
|
|
@@ -20238,26 +20399,33 @@ ${parts.join("\n")}`);
|
|
|
20238
20399
|
const oracleCmd = typeof ls.oracle === "string" && ls.oracle.trim() ? ls.oracle.trim() : void 0;
|
|
20239
20400
|
try {
|
|
20240
20401
|
sessionService.pushMessage({ type: "message", message: "\u{1F50D} Verifying loop completion (independent review)\u2026" }, "event");
|
|
20241
|
-
const
|
|
20242
|
-
|
|
20243
|
-
|
|
20244
|
-
|
|
20245
|
-
|
|
20246
|
-
|
|
20247
|
-
|
|
20248
|
-
|
|
20249
|
-
if (err) resolve2({ ok: false, output: (String(stdout || "") + String(stderr || "")).slice(-500) });
|
|
20250
|
-
else resolve2({ ok: true, output: "" });
|
|
20251
|
-
});
|
|
20252
|
-
});
|
|
20253
|
-
}
|
|
20254
|
-
const pending = backlogOraclePending(projectRoot, sessionId);
|
|
20255
|
-
return { ok: pending.length === 0, output: pending.length ? `${pending.length} pending: ${pending.map((i) => "#" + i.id).join(" ")}` : "No pending issues." };
|
|
20256
|
-
},
|
|
20257
|
-
runEvaluator: (prompt) => runHeadlessEvaluator(prompt),
|
|
20258
|
-
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
|
+
});
|
|
20259
20410
|
}
|
|
20260
|
-
|
|
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
|
+
}
|
|
20261
20429
|
if (loopCancelledDuringVerify(readLoopState(directory, sessionId))) {
|
|
20262
20430
|
logger.log(`[Session ${sessionId}] loop was cancelled during verification \u2014 not re-kicking (#0308)`);
|
|
20263
20431
|
return;
|
|
@@ -20937,7 +21105,7 @@ ${parts.join("\n")}`);
|
|
|
20937
21105
|
const maxExt = resolveMaxExtensions(process.env.SVAMP_LOOP_MAX_EXTENSIONS);
|
|
20938
21106
|
const effMaxIters = effectiveSoftCap(ls.max_iterations, ls.extensions, maxExt);
|
|
20939
21107
|
const budgetCheck = process.env.SVAMP_LOOP_BUDGET === "0" ? { exceeded: false, kind: void 0 } : checkLoopBudget(ledger, { max_iterations: effMaxIters, ...ls.budget || {} }, now, startedAt);
|
|
20940
|
-
const pending = safeBacklogPendingCount(
|
|
21108
|
+
const pending = safeBacklogPendingCount(directory, sessionId, ls.oracle);
|
|
20941
21109
|
const prog = updateLoopProgress(ls, pending, now);
|
|
20942
21110
|
ls.progress_history = prog.progress_history;
|
|
20943
21111
|
ls.auto_resumes = prog.auto_resumes;
|
|
@@ -21873,11 +22041,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21873
22041
|
});
|
|
21874
22042
|
},
|
|
21875
22043
|
onIssue: async (params) => {
|
|
21876
|
-
const { issueRpc } = await import('./rpc-
|
|
22044
|
+
const { issueRpc } = await import('./rpc-DVtlZahE.mjs');
|
|
21877
22045
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
21878
22046
|
},
|
|
21879
22047
|
onWorkflow: async (params) => {
|
|
21880
|
-
const { workflowRpc } = await import('./rpc-
|
|
22048
|
+
const { workflowRpc } = await import('./rpc-rpH-yXR7.mjs');
|
|
21881
22049
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
21882
22050
|
},
|
|
21883
22051
|
onRipgrep: async (args, cwd) => {
|
|
@@ -22584,11 +22752,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
22584
22752
|
});
|
|
22585
22753
|
},
|
|
22586
22754
|
onIssue: async (params) => {
|
|
22587
|
-
const { issueRpc } = await import('./rpc-
|
|
22755
|
+
const { issueRpc } = await import('./rpc-DVtlZahE.mjs');
|
|
22588
22756
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
22589
22757
|
},
|
|
22590
22758
|
onWorkflow: async (params) => {
|
|
22591
|
-
const { workflowRpc } = await import('./rpc-
|
|
22759
|
+
const { workflowRpc } = await import('./rpc-rpH-yXR7.mjs');
|
|
22592
22760
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
22593
22761
|
},
|
|
22594
22762
|
onRipgrep: async (args, cwd) => {
|
|
@@ -22937,7 +23105,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
22937
23105
|
const acpMaxExt = resolveMaxExtensions(process.env.SVAMP_LOOP_MAX_EXTENSIONS);
|
|
22938
23106
|
const acpEffMax = effectiveSoftCap(ls.max_iterations, ls.extensions, acpMaxExt);
|
|
22939
23107
|
const budgetCheck = process.env.SVAMP_LOOP_BUDGET === "0" ? { exceeded: false, kind: void 0 } : checkLoopBudget(ledger, { max_iterations: acpEffMax, ...ls.budget || {} }, now, startedAt);
|
|
22940
|
-
const acpPending = safeBacklogPendingCount(projectRoot, sessionId);
|
|
23108
|
+
const acpPending = safeBacklogPendingCount(projectRoot, sessionId, ls.oracle);
|
|
22941
23109
|
const acpProg = updateLoopProgress(ls, acpPending, now);
|
|
22942
23110
|
ls.progress_history = acpProg.progress_history;
|
|
22943
23111
|
ls.auto_resumes = acpProg.auto_resumes;
|
|
@@ -23571,7 +23739,15 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23571
23739
|
};
|
|
23572
23740
|
writeDaemonStateFile(localState);
|
|
23573
23741
|
logger.log("Daemon state file written \u2014 daemon is up; restoring mounts/sessions in background");
|
|
23574
|
-
serveManager$1.restore().
|
|
23742
|
+
serveManager$1.restore().then(async () => {
|
|
23743
|
+
try {
|
|
23744
|
+
for (const m of serveManager$1.listMounts()) {
|
|
23745
|
+
if (m?.sessionId && m?.url) await reconcileMountSessionLink(m.sessionId, m.name, m.url);
|
|
23746
|
+
}
|
|
23747
|
+
} catch (err) {
|
|
23748
|
+
logger.log(`[serve] mount link reconcile failed: ${err?.message || err}`);
|
|
23749
|
+
}
|
|
23750
|
+
}).catch((err) => logger.error(`[serve] mount restore failed: ${err?.message || err}`));
|
|
23575
23751
|
const daemonOwnerEmail = parseJwtEmail(process.env.HYPHA_TOKEN || "") || null;
|
|
23576
23752
|
serveManager$1.setSessionResolver((sessionId) => {
|
|
23577
23753
|
for (const [, session] of pidToTrackedSession) {
|
|
@@ -23631,7 +23807,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23631
23807
|
}
|
|
23632
23808
|
if (persistedSessions.length > 0) {
|
|
23633
23809
|
try {
|
|
23634
|
-
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-
|
|
23810
|
+
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-BkZfX5pW.mjs');
|
|
23635
23811
|
await awaitClaudeVersionReady();
|
|
23636
23812
|
} catch {
|
|
23637
23813
|
}
|
|
@@ -23671,7 +23847,11 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23671
23847
|
isOrphaned: !!isOrphaned,
|
|
23672
23848
|
loopActive: isLoopActiveForSession(persisted.directory, persisted.sessionId),
|
|
23673
23849
|
loopArmed: _loopArmed,
|
|
23674
|
-
armedHasPendingWork: _loopArmed ? safeBacklogPendingCount(
|
|
23850
|
+
armedHasPendingWork: _loopArmed ? safeBacklogPendingCount(
|
|
23851
|
+
persisted.directory,
|
|
23852
|
+
persisted.sessionId,
|
|
23853
|
+
readLoopState(persisted.directory, persisted.sessionId)?.oracle
|
|
23854
|
+
) > 0 : false
|
|
23675
23855
|
});
|
|
23676
23856
|
if (_loopArmed) {
|
|
23677
23857
|
logger.log(`[loop] Restored DORMANT loop for ${persisted.sessionId} \u2192 ${_restoreAction === "loop-resume" ? "re-arming (pending work found)" : "staying dormant/watching (backlog empty)"}`);
|
|
@@ -23876,7 +24056,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23876
24056
|
const PING_TIMEOUT_MS = 15e3;
|
|
23877
24057
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
23878
24058
|
const RECONNECT_JITTER_MS = 2500;
|
|
23879
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
24059
|
+
const { WorkflowScheduler } = await import('./scheduler-CYyGe6kn.mjs');
|
|
23880
24060
|
const workflowProjectRoots = () => {
|
|
23881
24061
|
const dirs = /* @__PURE__ */ new Set();
|
|
23882
24062
|
for (const s of pidToTrackedSession.values()) {
|
|
@@ -23952,10 +24132,11 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23952
24132
|
}
|
|
23953
24133
|
}
|
|
23954
24134
|
}
|
|
24135
|
+
const inGrace = lastReconnectAt > 0 && Date.now() - lastReconnectAt < POST_RECONNECT_GRACE_MS;
|
|
23955
24136
|
const INBOUND_SILENCE_THRESHOLD_MS = 12e4;
|
|
23956
24137
|
const inboundSilenceMs = Date.now() - machineService.getLastInboundRpcAt();
|
|
23957
24138
|
const hasActiveSessions = pidToTrackedSession.size > 0;
|
|
23958
|
-
if (hasActiveSessions
|
|
24139
|
+
if (shouldRunZombieProbe({ hasActiveSessions, inboundSilenceMs, consecutiveHeartbeatFailures, inGrace, thresholdMs: INBOUND_SILENCE_THRESHOLD_MS })) {
|
|
23959
24140
|
logger.log(`No inbound RPC for ${Math.round(inboundSilenceMs / 1e3)}s with ${pidToTrackedSession.size} active session(s) \u2014 zombie probe`);
|
|
23960
24141
|
try {
|
|
23961
24142
|
const machineServiceId = `${server.config.client_id}:default`;
|
|
@@ -23964,11 +24145,10 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23964
24145
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Zombie probe timed out")), PING_TIMEOUT_MS))
|
|
23965
24146
|
]);
|
|
23966
24147
|
} catch (err) {
|
|
23967
|
-
logger.log(`Zombie detection probe failed: ${err.message} \u2014
|
|
23968
|
-
consecutiveHeartbeatFailures =
|
|
24148
|
+
logger.log(`Zombie detection probe failed: ${err.message} \u2014 raising suspicion (two-strike)`);
|
|
24149
|
+
consecutiveHeartbeatFailures = nextFailuresAfterZombieProbeFailure(consecutiveHeartbeatFailures);
|
|
23969
24150
|
}
|
|
23970
24151
|
}
|
|
23971
|
-
const inGrace = lastReconnectAt > 0 && Date.now() - lastReconnectAt < POST_RECONNECT_GRACE_MS;
|
|
23972
24152
|
if (!inGrace) {
|
|
23973
24153
|
try {
|
|
23974
24154
|
const pingStart = Date.now();
|
|
@@ -23993,7 +24173,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23993
24173
|
} else if (consecutiveHeartbeatFailures % 6 === 0) {
|
|
23994
24174
|
logger.log(`Connection down for ${consecutiveHeartbeatFailures * HEARTBEAT_INTERVAL_MS / 1e3}s (${consecutiveHeartbeatFailures} failures, retrying indefinitely)`);
|
|
23995
24175
|
}
|
|
23996
|
-
if (consecutiveHeartbeatFailures
|
|
24176
|
+
if (shouldForceReconnect(consecutiveHeartbeatFailures)) {
|
|
23997
24177
|
const jitterMs = Math.floor(Math.random() * RECONNECT_JITTER_MS);
|
|
23998
24178
|
if (jitterMs > 0) await new Promise((r) => setTimeout(r, jitterMs));
|
|
23999
24179
|
const conn = server.rpc?._connection;
|
|
@@ -24510,4 +24690,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
24510
24690
|
writeStopMarker: writeStopMarker
|
|
24511
24691
|
});
|
|
24512
24692
|
|
|
24513
|
-
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 };
|