svamp-cli 0.2.317 → 0.2.319
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-fJVu-h9l.mjs → adminCommands-DBADX_Uo.mjs} +1 -1
- package/dist/{agentCommands-DdOSpUEW.mjs → agentCommands-BJeGwkeX.mjs} +6 -6
- package/dist/{auth-B2XK13r-.mjs → auth-0NUGwOc0.mjs} +1 -1
- package/dist/{cli-DGVQaDbd.mjs → cli-CWElpiIq.mjs} +76 -76
- package/dist/cli.mjs +2 -2
- package/dist/{commands-Bx2JkNft.mjs → commands-AdVHZXK6.mjs} +1 -1
- package/dist/{commands-wQYdc5EK.mjs → commands-BY1lmXaM.mjs} +1 -1
- package/dist/{commands-DEsDSuMW.mjs → commands-BsaTwKnd.mjs} +2 -2
- package/dist/{commands-CoBMjrtP.mjs → commands-C0lwV0U3.mjs} +2 -2
- package/dist/{commands-C4p4v9eO.mjs → commands-CJWgDDl5.mjs} +2 -2
- package/dist/{commands-DUdStLKb.mjs → commands-CLgdrTSU.mjs} +2 -2
- package/dist/{commands-ino-Gtcu.mjs → commands-DMSkWb0h.mjs} +8 -8
- package/dist/{commands-I9jHLkrI.mjs → commands-DubEbPAY.mjs} +1 -1
- package/dist/{fleet-NRi2YMso.mjs → fleet-OKnU0cYK.mjs} +2 -2
- package/dist/{headlessCli-DpTQ00fL.mjs → headlessCli-CCO0IFYl.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{notifyCommands-BFRuzxXP.mjs → notifyCommands-4GUy4Mcl.mjs} +1 -1
- package/dist/{package-DN_Oa_OT.mjs → package-BEEqsQhN.mjs} +3 -3
- package/dist/{pinnedClaudeCode-CnCpensV.mjs → pinnedClaudeCode-dbXgsGNK.mjs} +1 -1
- package/dist/{rpc-CclCeDcu.mjs → rpc-FGp88Yxp.mjs} +1 -1
- package/dist/{rpc-Cx6KsBYC.mjs → rpc-TXF-di9u.mjs} +1 -1
- package/dist/{run-RA4Vr6lj.mjs → run-09AkgeGM.mjs} +1134 -264
- package/dist/{run-DKhnAl2f.mjs → run-7TVWXnOM.mjs} +1 -1
- package/dist/{scheduler-Wzzed1CU.mjs → scheduler-b-ysyKIj.mjs} +1 -1
- package/dist/{serveCommands-DQeSesYI.mjs → serveCommands-D3W7w0G6.mjs} +29 -7
- package/dist/{sideband-gQ7oQEsx.mjs → sideband-C1db5sMF.mjs} +1 -1
- package/package.json +3 -3
- package/dist/frpc-CNZJM3qC.mjs +0 -775
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import os$1, { homedir } from 'os';
|
|
1
|
+
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import os$1, { homedir, platform, arch } from 'os';
|
|
2
2
|
import fs$1, { mkdir as mkdir$1, readdir as readdir$1, readFile, writeFile as writeFile$1, rename, unlink } from 'fs/promises';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
|
-
import fs__default, { existsSync as existsSync$1, rmSync, readFileSync as readFileSync$1, mkdirSync, writeFileSync, renameSync as renameSync$1, realpathSync as realpathSync$1, copyFileSync,
|
|
4
|
+
import fs__default, { existsSync as existsSync$1, rmSync, readFileSync as readFileSync$1, mkdirSync, writeFileSync, chmodSync, unlinkSync, renameSync as renameSync$1, realpathSync as realpathSync$1, copyFileSync, readdirSync as readdirSync$1, watch, rmdirSync, statSync as statSync$1 } from 'fs';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import path__default, { join, delimiter, resolve as resolve$1, dirname as dirname$1, basename as basename$1 } from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
|
-
import { execFile,
|
|
8
|
+
import { execFile, execSync, spawn, exec as exec$1 } from 'child_process';
|
|
9
9
|
import * as crypto from 'crypto';
|
|
10
|
-
import { randomUUID
|
|
11
|
-
import { randomUUID, randomBytes, createHash, timingSafeEqual } from 'node:crypto';
|
|
12
|
-
import { existsSync, readFileSync, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1, chmodSync, rmSync as rmSync$1, cpSync, statSync, realpathSync, readdirSync, renameSync, appendFileSync, openSync, readSync, closeSync, unlinkSync } from 'node:fs';
|
|
10
|
+
import { randomUUID, createHash } from 'crypto';
|
|
11
|
+
import { randomUUID as randomUUID$1, randomBytes, createHash as createHash$1, timingSafeEqual } from 'node:crypto';
|
|
12
|
+
import { existsSync, readFileSync, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1, chmodSync as chmodSync$1, rmSync as rmSync$1, cpSync, statSync, realpathSync, readdirSync, renameSync, appendFileSync, openSync, readSync, closeSync, unlinkSync as unlinkSync$1 } from 'node:fs';
|
|
13
13
|
import { exec, spawn as spawn$1, execSync as execSync$1, execFile as execFile$1, execFileSync } from 'node:child_process';
|
|
14
14
|
import { promisify } from 'util';
|
|
15
15
|
import * as http from 'http';
|
|
16
16
|
import * as net from 'net';
|
|
17
|
-
import
|
|
17
|
+
import { createServer } from 'net';
|
|
18
|
+
import os, { homedir as homedir$1, platform as platform$1 } from 'node:os';
|
|
18
19
|
import { join as join$1, extname, resolve, sep, basename, dirname } from 'node:path';
|
|
19
20
|
import { EventEmitter } from 'node:events';
|
|
20
21
|
import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
|
|
@@ -1260,6 +1261,1027 @@ async function killDescendant(rootPid, targetPid, signal = "SIGTERM") {
|
|
|
1260
1261
|
}
|
|
1261
1262
|
}
|
|
1262
1263
|
|
|
1264
|
+
function svampHome() {
|
|
1265
|
+
return process.env.SVAMP_HOME || join(homedir(), ".svamp");
|
|
1266
|
+
}
|
|
1267
|
+
function cacheFile() {
|
|
1268
|
+
return join(svampHome(), "instance-config.json");
|
|
1269
|
+
}
|
|
1270
|
+
const CONFIG_FILENAME = "svamp.json";
|
|
1271
|
+
let _config = null;
|
|
1272
|
+
let _loaded = false;
|
|
1273
|
+
let _cacheChecked = false;
|
|
1274
|
+
function stripSlash$1(u) {
|
|
1275
|
+
return u.replace(/\/+$/, "");
|
|
1276
|
+
}
|
|
1277
|
+
function getInstanceUrl() {
|
|
1278
|
+
const raw = (process.env.SVAMP_INSTANCE_URL || "").trim();
|
|
1279
|
+
return raw ? stripSlash$1(raw) : void 0;
|
|
1280
|
+
}
|
|
1281
|
+
function configUrlsFor(anchor) {
|
|
1282
|
+
const base = stripSlash$1(anchor);
|
|
1283
|
+
return [`${base}/${CONFIG_FILENAME}`, `${base}/.well-known/${CONFIG_FILENAME}`];
|
|
1284
|
+
}
|
|
1285
|
+
async function fetchConfig(anchor) {
|
|
1286
|
+
for (const url of configUrlsFor(anchor)) {
|
|
1287
|
+
try {
|
|
1288
|
+
const resp = await fetch(url, { signal: AbortSignal.timeout(8e3) });
|
|
1289
|
+
if (!resp.ok) continue;
|
|
1290
|
+
const data = await resp.json();
|
|
1291
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
1292
|
+
return data;
|
|
1293
|
+
}
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1299
|
+
function readCache() {
|
|
1300
|
+
try {
|
|
1301
|
+
if (existsSync$1(cacheFile())) {
|
|
1302
|
+
const data = JSON.parse(readFileSync$1(cacheFile(), "utf-8"));
|
|
1303
|
+
if (data && typeof data === "object" && !Array.isArray(data)) return data;
|
|
1304
|
+
}
|
|
1305
|
+
} catch {
|
|
1306
|
+
}
|
|
1307
|
+
return null;
|
|
1308
|
+
}
|
|
1309
|
+
function writeCache(cfg) {
|
|
1310
|
+
try {
|
|
1311
|
+
mkdirSync(svampHome(), { recursive: true });
|
|
1312
|
+
writeFileSync(cacheFile(), JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
1313
|
+
} catch {
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
function applyConfig(cfg) {
|
|
1317
|
+
_config = cfg;
|
|
1318
|
+
if (cfg.hyphaServerUrl && typeof cfg.hyphaServerUrl === "string") {
|
|
1319
|
+
process.env.HYPHA_SERVER_URL = stripSlash$1(cfg.hyphaServerUrl.trim());
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
async function loadInstanceConfig(opts = {}) {
|
|
1323
|
+
if (_loaded && !opts.force) return _config;
|
|
1324
|
+
const anchor = opts.anchor ? stripSlash$1(opts.anchor) : getInstanceUrl();
|
|
1325
|
+
if (anchor) {
|
|
1326
|
+
const fetched = await fetchConfig(anchor);
|
|
1327
|
+
if (fetched) {
|
|
1328
|
+
applyConfig(fetched);
|
|
1329
|
+
writeCache(fetched);
|
|
1330
|
+
_loaded = true;
|
|
1331
|
+
_cacheChecked = true;
|
|
1332
|
+
return _config;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
const cached = readCache();
|
|
1336
|
+
if (cached) applyConfig(cached);
|
|
1337
|
+
_loaded = true;
|
|
1338
|
+
_cacheChecked = true;
|
|
1339
|
+
return _config;
|
|
1340
|
+
}
|
|
1341
|
+
function getLoadedConfig() {
|
|
1342
|
+
if (!_loaded && !_cacheChecked) {
|
|
1343
|
+
const cached = readCache();
|
|
1344
|
+
if (cached) applyConfig(cached);
|
|
1345
|
+
_cacheChecked = true;
|
|
1346
|
+
}
|
|
1347
|
+
return _config;
|
|
1348
|
+
}
|
|
1349
|
+
function clearInstanceConfigCache() {
|
|
1350
|
+
_config = null;
|
|
1351
|
+
_loaded = false;
|
|
1352
|
+
_cacheChecked = false;
|
|
1353
|
+
try {
|
|
1354
|
+
if (existsSync$1(cacheFile())) rmSync(cacheFile());
|
|
1355
|
+
} catch {
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function __setConfigForTest(cfg) {
|
|
1359
|
+
_config = cfg;
|
|
1360
|
+
_loaded = true;
|
|
1361
|
+
_cacheChecked = true;
|
|
1362
|
+
if (cfg) applyConfig(cfg);
|
|
1363
|
+
}
|
|
1364
|
+
function __resetForTest() {
|
|
1365
|
+
_config = null;
|
|
1366
|
+
_loaded = false;
|
|
1367
|
+
_cacheChecked = false;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
var instanceConfig = /*#__PURE__*/Object.freeze({
|
|
1371
|
+
__proto__: null,
|
|
1372
|
+
CONFIG_FILENAME: CONFIG_FILENAME,
|
|
1373
|
+
__resetForTest: __resetForTest,
|
|
1374
|
+
__setConfigForTest: __setConfigForTest,
|
|
1375
|
+
clearInstanceConfigCache: clearInstanceConfigCache,
|
|
1376
|
+
configUrlsFor: configUrlsFor,
|
|
1377
|
+
getInstanceUrl: getInstanceUrl,
|
|
1378
|
+
getLoadedConfig: getLoadedConfig,
|
|
1379
|
+
loadInstanceConfig: loadInstanceConfig
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
function stripSlash(u) {
|
|
1383
|
+
return u.replace(/\/+$/, "");
|
|
1384
|
+
}
|
|
1385
|
+
function getHyphaServerUrl() {
|
|
1386
|
+
const fromCfg = getLoadedConfig()?.hyphaServerUrl;
|
|
1387
|
+
if (typeof fromCfg === "string" && fromCfg.trim()) return stripSlash(fromCfg.trim());
|
|
1388
|
+
const env = (process.env.HYPHA_SERVER_URL || "").trim();
|
|
1389
|
+
return env ? stripSlash(env) : void 0;
|
|
1390
|
+
}
|
|
1391
|
+
function getFrpsSubdomainHost() {
|
|
1392
|
+
const fromCfg = getLoadedConfig()?.frps?.subdomainHost;
|
|
1393
|
+
if (typeof fromCfg === "string" && fromCfg.trim()) return fromCfg.trim();
|
|
1394
|
+
const env = (process.env.FRPS_SUBDOMAIN_HOST || "").trim();
|
|
1395
|
+
if (env) return env;
|
|
1396
|
+
throw new Error(
|
|
1397
|
+
'frps subdomain host is not configured. Serve a svamp.json with "frps.subdomainHost" at your frontend URL (svamp login <frontend-url>), or set FRPS_SUBDOMAIN_HOST (legacy). It is deployment-specific and never guessed.'
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
function getFrpsServerAddr() {
|
|
1401
|
+
const fromCfg = getLoadedConfig()?.frps?.serverAddr;
|
|
1402
|
+
if (typeof fromCfg === "string" && fromCfg.trim()) return fromCfg.trim();
|
|
1403
|
+
const env = (process.env.FRPS_SERVER_ADDR || "").trim();
|
|
1404
|
+
if (env) return env;
|
|
1405
|
+
return `frps-ctrl.${getFrpsSubdomainHost()}`;
|
|
1406
|
+
}
|
|
1407
|
+
function getFrpsServerPort() {
|
|
1408
|
+
const fromCfg = getLoadedConfig()?.frps?.serverPort;
|
|
1409
|
+
if (typeof fromCfg === "number" && Number.isFinite(fromCfg)) return fromCfg;
|
|
1410
|
+
const env = parseInt(process.env.FRPS_SERVER_PORT || "", 10);
|
|
1411
|
+
return Number.isFinite(env) ? env : void 0;
|
|
1412
|
+
}
|
|
1413
|
+
function getSvampWebBaseUrl() {
|
|
1414
|
+
const fromCfg = getLoadedConfig()?.web?.baseUrl;
|
|
1415
|
+
if (typeof fromCfg === "string" && fromCfg.trim()) return stripSlash(fromCfg.trim());
|
|
1416
|
+
const env = (process.env.SVAMP_WEB_URL || "").trim();
|
|
1417
|
+
if (env) return stripSlash(env);
|
|
1418
|
+
const instance = getInstanceUrl();
|
|
1419
|
+
if (instance) return instance;
|
|
1420
|
+
throw new Error(
|
|
1421
|
+
'Svamp web base URL is not configured. Serve a svamp.json with "web.baseUrl", log in with your frontend URL (svamp login <frontend-url>), or set SVAMP_WEB_URL (legacy).'
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
function getSkillsServerUrl() {
|
|
1425
|
+
const fromCfg = getLoadedConfig()?.skills?.server;
|
|
1426
|
+
if (typeof fromCfg === "string" && fromCfg.trim()) return stripSlash(fromCfg.trim());
|
|
1427
|
+
const env = (process.env.HYPHA_SKILLS_SERVER || "").trim();
|
|
1428
|
+
if (env) return stripSlash(env);
|
|
1429
|
+
const server = getHyphaServerUrl();
|
|
1430
|
+
if (server) return server;
|
|
1431
|
+
throw new Error(
|
|
1432
|
+
"Cannot determine skills marketplace server. Serve a svamp.json or set HYPHA_SERVER_URL."
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
function getSkillsWorkspace() {
|
|
1436
|
+
const fromCfg = getLoadedConfig()?.skills?.workspace;
|
|
1437
|
+
if (typeof fromCfg === "string" && fromCfg.trim()) return fromCfg.trim();
|
|
1438
|
+
return (process.env.HYPHA_SKILLS_WORKSPACE || "").trim() || "hypha-cloud";
|
|
1439
|
+
}
|
|
1440
|
+
function getSkillsCollection() {
|
|
1441
|
+
const fromCfg = getLoadedConfig()?.skills?.collection;
|
|
1442
|
+
if (typeof fromCfg === "string" && fromCfg.trim()) return fromCfg.trim();
|
|
1443
|
+
return (process.env.HYPHA_SKILLS_COLLECTION || "").trim() || "marketplace";
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function getMachineFingerprint() {
|
|
1447
|
+
const idFile = join(homedir(), ".svamp", "machine-id");
|
|
1448
|
+
try {
|
|
1449
|
+
const existing = readFileSync$1(idFile, "utf-8").trim();
|
|
1450
|
+
if (existing) return existing;
|
|
1451
|
+
} catch {
|
|
1452
|
+
}
|
|
1453
|
+
const id = randomUUID();
|
|
1454
|
+
try {
|
|
1455
|
+
mkdirSync(join(homedir(), ".svamp"), { recursive: true });
|
|
1456
|
+
writeFileSync(idFile, id + "\n");
|
|
1457
|
+
} catch {
|
|
1458
|
+
}
|
|
1459
|
+
return id;
|
|
1460
|
+
}
|
|
1461
|
+
const FRP_VERSION = "0.68.0";
|
|
1462
|
+
const BIN_DIR = join(homedir(), ".svamp", "bin");
|
|
1463
|
+
const FRPC_BIN = join(BIN_DIR, platform() === "win32" ? "frpc.exe" : "frpc");
|
|
1464
|
+
const _livingFrpcPids = /* @__PURE__ */ new Set();
|
|
1465
|
+
function isFrpcSubdomainConflict(errorLine) {
|
|
1466
|
+
return /router config conflict|already exists/i.test(errorLine);
|
|
1467
|
+
}
|
|
1468
|
+
function shouldGiveUpOnConflict(args) {
|
|
1469
|
+
if (args.everStartedProxy) return false;
|
|
1470
|
+
return args.conflictCount >= args.minAttempts && args.elapsedMs >= args.minElapsedMs;
|
|
1471
|
+
}
|
|
1472
|
+
function selectOrphanedFrpcPids(psOutput, selfPid, livingPids) {
|
|
1473
|
+
const pids = [];
|
|
1474
|
+
for (const line of psOutput.split("\n")) {
|
|
1475
|
+
const m = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
1476
|
+
if (!m) continue;
|
|
1477
|
+
const pid = Number(m[1]);
|
|
1478
|
+
const cmd = m[2];
|
|
1479
|
+
if (!cmd.includes(".svamp/bin/frpc")) continue;
|
|
1480
|
+
if (pid === selfPid || livingPids.has(pid)) continue;
|
|
1481
|
+
pids.push(pid);
|
|
1482
|
+
}
|
|
1483
|
+
return pids;
|
|
1484
|
+
}
|
|
1485
|
+
function killOrphanedFrpc(log) {
|
|
1486
|
+
if (process.platform === "win32") return;
|
|
1487
|
+
try {
|
|
1488
|
+
const out = execSync("ps -axo pid=,command=", { encoding: "utf-8" });
|
|
1489
|
+
const pids = selectOrphanedFrpcPids(out, process.pid, _livingFrpcPids);
|
|
1490
|
+
for (const pid of pids) {
|
|
1491
|
+
try {
|
|
1492
|
+
process.kill(pid, "SIGKILL");
|
|
1493
|
+
} catch {
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
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.`);
|
|
1498
|
+
}
|
|
1499
|
+
} catch {
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
function isInCluster() {
|
|
1503
|
+
return !!(process.env.KUBERNETES_SERVICE_HOST || process.env.SANDBOX_ID);
|
|
1504
|
+
}
|
|
1505
|
+
const FRPS_CLUSTER_ADDR = "frps.hypha.svc.cluster.local";
|
|
1506
|
+
const DEFAULT_FRPS_PORT = isInCluster() ? 7e3 : 443;
|
|
1507
|
+
const FRPS_PUBLIC_TOKEN = "hypha-frps-public";
|
|
1508
|
+
function getFrpcDownloadUrl() {
|
|
1509
|
+
const os = platform();
|
|
1510
|
+
const a = arch();
|
|
1511
|
+
let osStr;
|
|
1512
|
+
let archStr;
|
|
1513
|
+
switch (os) {
|
|
1514
|
+
case "darwin":
|
|
1515
|
+
osStr = "darwin";
|
|
1516
|
+
break;
|
|
1517
|
+
case "linux":
|
|
1518
|
+
osStr = "linux";
|
|
1519
|
+
break;
|
|
1520
|
+
case "win32":
|
|
1521
|
+
osStr = "windows";
|
|
1522
|
+
break;
|
|
1523
|
+
default:
|
|
1524
|
+
throw new Error(`Unsupported platform: ${os}`);
|
|
1525
|
+
}
|
|
1526
|
+
switch (a) {
|
|
1527
|
+
case "x64":
|
|
1528
|
+
archStr = "amd64";
|
|
1529
|
+
break;
|
|
1530
|
+
case "arm64":
|
|
1531
|
+
archStr = "arm64";
|
|
1532
|
+
break;
|
|
1533
|
+
case "arm":
|
|
1534
|
+
archStr = "arm";
|
|
1535
|
+
break;
|
|
1536
|
+
default:
|
|
1537
|
+
throw new Error(`Unsupported architecture: ${a}`);
|
|
1538
|
+
}
|
|
1539
|
+
const ext = os === "win32" ? "zip" : "tar.gz";
|
|
1540
|
+
const filename = `frp_${FRP_VERSION}_${osStr}_${archStr}`;
|
|
1541
|
+
return `https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${filename}.${ext}`;
|
|
1542
|
+
}
|
|
1543
|
+
let _frpcDownloadInFlight = null;
|
|
1544
|
+
async function ensureFrpc(log) {
|
|
1545
|
+
if (existsSync$1(FRPC_BIN)) {
|
|
1546
|
+
try {
|
|
1547
|
+
const out = execSync(`"${FRPC_BIN}" --version`, { stdio: "pipe", timeout: 5e3 }).toString().trim();
|
|
1548
|
+
if (out === FRP_VERSION) return FRPC_BIN;
|
|
1549
|
+
(log || console.log)(`frpc version mismatch: ${out} \u2260 ${FRP_VERSION}, re-downloading...`);
|
|
1550
|
+
} catch {
|
|
1551
|
+
(log || console.log)("frpc binary broken or invalid signature, re-downloading...");
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
if (_frpcDownloadInFlight) return _frpcDownloadInFlight;
|
|
1555
|
+
_frpcDownloadInFlight = downloadFrpc(log).finally(() => {
|
|
1556
|
+
_frpcDownloadInFlight = null;
|
|
1557
|
+
});
|
|
1558
|
+
return _frpcDownloadInFlight;
|
|
1559
|
+
}
|
|
1560
|
+
async function downloadFrpc(log) {
|
|
1561
|
+
try {
|
|
1562
|
+
if (existsSync$1(FRPC_BIN)) unlinkSync(FRPC_BIN);
|
|
1563
|
+
} catch {
|
|
1564
|
+
}
|
|
1565
|
+
const logger = log || console.log;
|
|
1566
|
+
mkdirSync(BIN_DIR, { recursive: true });
|
|
1567
|
+
const url = getFrpcDownloadUrl();
|
|
1568
|
+
logger(`Downloading frpc ${FRP_VERSION} from ${url}...`);
|
|
1569
|
+
const ctrl = new AbortController();
|
|
1570
|
+
const timer = setTimeout(() => ctrl.abort(), 6e4);
|
|
1571
|
+
let response;
|
|
1572
|
+
try {
|
|
1573
|
+
response = await fetch(url, { signal: ctrl.signal });
|
|
1574
|
+
} catch (err) {
|
|
1575
|
+
const why = err?.name === "AbortError" ? "timed out after 60s" : err?.message ?? String(err);
|
|
1576
|
+
throw new Error(`Failed to download frpc from ${url}: ${why}`);
|
|
1577
|
+
} finally {
|
|
1578
|
+
clearTimeout(timer);
|
|
1579
|
+
}
|
|
1580
|
+
if (!response.ok) {
|
|
1581
|
+
throw new Error(`Failed to download frpc: ${response.status} ${response.statusText}`);
|
|
1582
|
+
}
|
|
1583
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1584
|
+
if (platform() === "win32") {
|
|
1585
|
+
throw new Error("Windows ZIP extraction not implemented \u2014 please install frpc manually");
|
|
1586
|
+
}
|
|
1587
|
+
const tmpTar = join(BIN_DIR, `frpc-${FRP_VERSION}-${process.pid}-${Math.random().toString(36).slice(2, 8)}.tar.gz`);
|
|
1588
|
+
writeFileSync(tmpTar, buffer);
|
|
1589
|
+
try {
|
|
1590
|
+
const dirName = `frp_${FRP_VERSION}_${platform() === "darwin" ? "darwin" : "linux"}_${arch() === "x64" ? "amd64" : arch()}`;
|
|
1591
|
+
execSync(
|
|
1592
|
+
`tar -xzf "${tmpTar}" -C "${BIN_DIR}" --strip-components=1 "${dirName}/frpc"`,
|
|
1593
|
+
{ stdio: "pipe" }
|
|
1594
|
+
);
|
|
1595
|
+
chmodSync(FRPC_BIN, 493);
|
|
1596
|
+
logger(`frpc installed at ${FRPC_BIN}`);
|
|
1597
|
+
} finally {
|
|
1598
|
+
try {
|
|
1599
|
+
unlinkSync(tmpTar);
|
|
1600
|
+
} catch {
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return FRPC_BIN;
|
|
1604
|
+
}
|
|
1605
|
+
function tomlStr(v) {
|
|
1606
|
+
return String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
1607
|
+
}
|
|
1608
|
+
function generateFrpcConfig(config, proxies, admin) {
|
|
1609
|
+
const useWSS = config.serverPort === 443;
|
|
1610
|
+
const lines = [
|
|
1611
|
+
"# Auto-generated by svamp \u2014 do not edit",
|
|
1612
|
+
`serverAddr = "${tomlStr(config.serverAddr)}"`,
|
|
1613
|
+
`serverPort = ${config.serverPort}`,
|
|
1614
|
+
"",
|
|
1615
|
+
'auth.method = "token"',
|
|
1616
|
+
`auth.token = "${tomlStr(config.authToken)}"`,
|
|
1617
|
+
"",
|
|
1618
|
+
"# Hypha JWT token for server-side authentication",
|
|
1619
|
+
...config.hyphaToken ? [`metadatas.token = "${tomlStr(config.hyphaToken)}"`] : [],
|
|
1620
|
+
"",
|
|
1621
|
+
"# Transport",
|
|
1622
|
+
...useWSS ? ['transport.protocol = "wss"'] : [],
|
|
1623
|
+
// 15s/45s heartbeat — 2x faster failure detection than the frpc default
|
|
1624
|
+
// (30s/90s). With matching server-side frps 0.68.x, dead-peer detection
|
|
1625
|
+
// settles in well under a minute, so the daemon-side recreate threshold
|
|
1626
|
+
// can be tightened too.
|
|
1627
|
+
"transport.heartbeatInterval = 15",
|
|
1628
|
+
"transport.heartbeatTimeout = 45",
|
|
1629
|
+
"transport.poolCount = 5",
|
|
1630
|
+
"",
|
|
1631
|
+
"# Don't exit on login failure \u2014 let frpc keep retrying",
|
|
1632
|
+
"loginFailExit = false",
|
|
1633
|
+
"",
|
|
1634
|
+
'log.to = "console"',
|
|
1635
|
+
'log.level = "info"',
|
|
1636
|
+
""
|
|
1637
|
+
];
|
|
1638
|
+
if (admin) {
|
|
1639
|
+
lines.push("# Local admin/status API (loopback only \u2014 polled by the svamp daemon)");
|
|
1640
|
+
lines.push('webServer.addr = "127.0.0.1"');
|
|
1641
|
+
lines.push(`webServer.port = ${admin.port}`);
|
|
1642
|
+
lines.push(`webServer.user = "${tomlStr(admin.user)}"`);
|
|
1643
|
+
lines.push(`webServer.password = "${tomlStr(admin.password)}"`);
|
|
1644
|
+
lines.push("");
|
|
1645
|
+
}
|
|
1646
|
+
for (const proxy of proxies) {
|
|
1647
|
+
lines.push(`[[proxies]]`);
|
|
1648
|
+
lines.push(`name = "${tomlStr(proxy.name)}"`);
|
|
1649
|
+
lines.push(`type = "${tomlStr(proxy.type)}"`);
|
|
1650
|
+
lines.push(`localIP = "${tomlStr(proxy.localIP || "127.0.0.1")}"`);
|
|
1651
|
+
lines.push(`localPort = ${proxy.localPort}`);
|
|
1652
|
+
if (proxy.subdomain) {
|
|
1653
|
+
lines.push(`subdomain = "${tomlStr(proxy.subdomain)}"`);
|
|
1654
|
+
}
|
|
1655
|
+
if (proxy.customDomains && proxy.customDomains.length > 0) {
|
|
1656
|
+
lines.push(`customDomains = [${proxy.customDomains.map((d) => `"${tomlStr(d)}"`).join(", ")}]`);
|
|
1657
|
+
}
|
|
1658
|
+
if (proxy.group) {
|
|
1659
|
+
lines.push(`loadBalancer.group = "${tomlStr(proxy.group)}"`);
|
|
1660
|
+
if (proxy.groupKey) {
|
|
1661
|
+
lines.push(`loadBalancer.groupKey = "${tomlStr(proxy.groupKey)}"`);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
if (proxy.healthCheckType) {
|
|
1665
|
+
lines.push(`healthCheck.type = "${tomlStr(proxy.healthCheckType)}"`);
|
|
1666
|
+
if (proxy.healthCheckType === "http" && proxy.healthCheckPath) {
|
|
1667
|
+
lines.push(`healthCheck.path = "${tomlStr(proxy.healthCheckPath)}"`);
|
|
1668
|
+
}
|
|
1669
|
+
lines.push(`healthCheck.intervalSeconds = ${proxy.healthCheckInterval || 10}`);
|
|
1670
|
+
lines.push(`healthCheck.timeoutSeconds = ${proxy.healthCheckTimeout || 3}`);
|
|
1671
|
+
lines.push(`healthCheck.maxFailed = ${proxy.healthCheckMaxFailed || 3}`);
|
|
1672
|
+
}
|
|
1673
|
+
if (proxy.type === "http") {
|
|
1674
|
+
lines.push("transport.useEncryption = false");
|
|
1675
|
+
lines.push("transport.useCompression = false");
|
|
1676
|
+
}
|
|
1677
|
+
lines.push("");
|
|
1678
|
+
}
|
|
1679
|
+
return lines.join("\n");
|
|
1680
|
+
}
|
|
1681
|
+
function getFreePort() {
|
|
1682
|
+
return new Promise((resolve, reject) => {
|
|
1683
|
+
const srv = createServer();
|
|
1684
|
+
srv.unref();
|
|
1685
|
+
srv.on("error", reject);
|
|
1686
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
1687
|
+
const addr = srv.address();
|
|
1688
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
1689
|
+
srv.close(() => port ? resolve(port) : reject(new Error("no port")));
|
|
1690
|
+
});
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
function parseFrpcStatus(payload, proxyNames) {
|
|
1694
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1695
|
+
if (payload && typeof payload === "object") {
|
|
1696
|
+
for (const group of Object.values(payload)) {
|
|
1697
|
+
if (!Array.isArray(group)) continue;
|
|
1698
|
+
for (const p of group) {
|
|
1699
|
+
if (p && typeof p.name === "string") {
|
|
1700
|
+
byName.set(p.name, { status: String(p.status ?? ""), err: p.err ? String(p.err) : void 0 });
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
const missing = [];
|
|
1706
|
+
const failed = [];
|
|
1707
|
+
for (const name of proxyNames) {
|
|
1708
|
+
const entry = byName.get(name);
|
|
1709
|
+
if (!entry) {
|
|
1710
|
+
missing.push(name);
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
if (entry.status !== "running") failed.push({ name, status: entry.status, err: entry.err });
|
|
1714
|
+
}
|
|
1715
|
+
return { allRunning: missing.length === 0 && failed.length === 0, missing, failed };
|
|
1716
|
+
}
|
|
1717
|
+
class FrpcTunnel {
|
|
1718
|
+
process = null;
|
|
1719
|
+
_connected = false;
|
|
1720
|
+
_destroyed = false;
|
|
1721
|
+
// #0460: in-flight guard. `this.process` is only set AFTER the `await ensureFrpc()`
|
|
1722
|
+
// inside connect(), so two connect() calls interleaved across that await (e.g. the
|
|
1723
|
+
// auto-restart timer firing while an external caller also connects) both pass the
|
|
1724
|
+
// `if (this.process) return` null-check and spawn a duplicate frpc child — the first
|
|
1725
|
+
// is overwritten and never killed, leaving a ghost tunnel registration. This flag is
|
|
1726
|
+
// set synchronously before the await and makes connect() idempotent across it.
|
|
1727
|
+
_connecting = false;
|
|
1728
|
+
configPath;
|
|
1729
|
+
options;
|
|
1730
|
+
serverConfig;
|
|
1731
|
+
log;
|
|
1732
|
+
logBuffer = [];
|
|
1733
|
+
maxLogLines = 200;
|
|
1734
|
+
proxies = [];
|
|
1735
|
+
// Health tracking
|
|
1736
|
+
_consecutiveErrors = 0;
|
|
1737
|
+
_lastConnectedAt = 0;
|
|
1738
|
+
_lastErrorAt = 0;
|
|
1739
|
+
_firstErrorAt = 0;
|
|
1740
|
+
_restartAttempts = 0;
|
|
1741
|
+
// Persistent-conflict give-up (kth-k8s nightly review 2026-08-09). A generation that has NEVER
|
|
1742
|
+
// brought a proxy online and only ever gets `router config conflict` / `already exists` has lost
|
|
1743
|
+
// the vhost race to another live generation — it must STOP hammering frps ~2/min forever rather
|
|
1744
|
+
// than retry indefinitely. Reset on any real `start proxy success`; only fires when a proxy was
|
|
1745
|
+
// never started (so a live tunnel riding out an frps failover is never torn down).
|
|
1746
|
+
_everStartedProxy = false;
|
|
1747
|
+
_conflictCount = 0;
|
|
1748
|
+
_conflictSince = 0;
|
|
1749
|
+
_restartTimer = null;
|
|
1750
|
+
// #0583: pending reconnect timer (cancelled on destroy)
|
|
1751
|
+
// End-to-end probe state (set when options.probeUrl is provided).
|
|
1752
|
+
_probeTimer = null;
|
|
1753
|
+
_burstTimer = null;
|
|
1754
|
+
// #0622: probe burst timer (cancelled on stopProbeLoop/destroy)
|
|
1755
|
+
_connectTimeoutTimer = null;
|
|
1756
|
+
// #0622: connect() 30s fallback (unref'd + cleared)
|
|
1757
|
+
_lastProbeOkAt = 0;
|
|
1758
|
+
_lastProbeFailAt = 0;
|
|
1759
|
+
_probeOk = false;
|
|
1760
|
+
// frpc admin/status API state (set when options.adminStatus is true).
|
|
1761
|
+
_adminPort = 0;
|
|
1762
|
+
_adminUser = "svamp";
|
|
1763
|
+
_adminPassword = randomUUID();
|
|
1764
|
+
_statusTimer = null;
|
|
1765
|
+
constructor(options) {
|
|
1766
|
+
this.options = options;
|
|
1767
|
+
this.log = options.log || ((msg) => console.log(`[FRPC] ${msg}`));
|
|
1768
|
+
const hyphaToken = options.serverConfig?.hyphaToken || process.env.HYPHA_TOKEN || "";
|
|
1769
|
+
this.serverConfig = {
|
|
1770
|
+
serverAddr: options.serverConfig?.serverAddr || process.env.FRPS_SERVER_ADDR || (isInCluster() ? FRPS_CLUSTER_ADDR : getFrpsServerAddr()),
|
|
1771
|
+
serverPort: options.serverConfig?.serverPort || getFrpsServerPort() || DEFAULT_FRPS_PORT,
|
|
1772
|
+
authToken: options.serverConfig?.authToken || process.env.FRPS_AUTH_TOKEN || FRPS_PUBLIC_TOKEN,
|
|
1773
|
+
hyphaToken,
|
|
1774
|
+
subDomainHost: options.serverConfig?.subDomainHost || process.env.FRPS_SUBDOMAIN_HOST || getFrpsSubdomainHost()
|
|
1775
|
+
};
|
|
1776
|
+
if (!this.serverConfig.hyphaToken) {
|
|
1777
|
+
throw new Error(
|
|
1778
|
+
'Hypha token required for frpc authentication. Run "svamp login" or set HYPHA_TOKEN.'
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
const machineFingerprint = getMachineFingerprint();
|
|
1782
|
+
const machineHash = createHash("sha256").update(machineFingerprint).digest("hex").slice(0, 6);
|
|
1783
|
+
const multiPort = options.ports.length > 1;
|
|
1784
|
+
this.proxies = options.ports.map((port) => {
|
|
1785
|
+
const standaloneHash = createHash("sha256").update(multiPort ? `${options.name}|${port}|${machineFingerprint}` : `${options.name}|${machineFingerprint}`).digest("hex").slice(0, 8);
|
|
1786
|
+
const groupSubdomainKey = multiPort ? `${options.group}|${port}` : `${options.group}`;
|
|
1787
|
+
const subdomain = options.subdomains?.get(port) || (options.group ? `${options.group}-${createHash("sha256").update(groupSubdomainKey).digest("hex").slice(0, 8)}` : `${options.name}-${standaloneHash}`);
|
|
1788
|
+
const proxyName = `${options.name}-${port}-${machineHash}`;
|
|
1789
|
+
return {
|
|
1790
|
+
name: proxyName,
|
|
1791
|
+
type: "http",
|
|
1792
|
+
localIP: options.localHost || "127.0.0.1",
|
|
1793
|
+
localPort: port,
|
|
1794
|
+
subdomain,
|
|
1795
|
+
group: options.group,
|
|
1796
|
+
groupKey: options.groupKey,
|
|
1797
|
+
healthCheckType: options.healthCheckType,
|
|
1798
|
+
healthCheckPath: options.healthCheckPath,
|
|
1799
|
+
healthCheckInterval: options.healthCheckInterval
|
|
1800
|
+
};
|
|
1801
|
+
});
|
|
1802
|
+
const configDir = join(homedir(), ".svamp", "frpc");
|
|
1803
|
+
mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
1804
|
+
try {
|
|
1805
|
+
chmodSync(configDir, 448);
|
|
1806
|
+
} catch {
|
|
1807
|
+
}
|
|
1808
|
+
this.configPath = join(configDir, `${options.name}.toml`);
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* #0583: schedule a backoff reconnect after the child went down (spawn error OR exit).
|
|
1812
|
+
* Extracted from the exit handler so BOTH the spawn-'error' and 'exit' paths share it, and
|
|
1813
|
+
* so it is unit-testable without a real spawn. No-op once destroyed.
|
|
1814
|
+
*/
|
|
1815
|
+
_reconnectAfterDown(reason) {
|
|
1816
|
+
if (this._destroyed) return;
|
|
1817
|
+
const STABILITY_WINDOW_MS = 3e4;
|
|
1818
|
+
if (this._lastConnectedAt > 0 && Date.now() - this._lastConnectedAt >= STABILITY_WINDOW_MS) {
|
|
1819
|
+
this._restartAttempts = 0;
|
|
1820
|
+
}
|
|
1821
|
+
this._restartAttempts++;
|
|
1822
|
+
const delay = Math.min(3e3 * Math.pow(2, this._restartAttempts - 1), 6e4);
|
|
1823
|
+
this.log(`frpc down (${reason}), restarting in ${Math.round(delay / 1e3)}s (attempt ${this._restartAttempts})...`);
|
|
1824
|
+
this._restartTimer = setTimeout(() => {
|
|
1825
|
+
this._restartTimer = null;
|
|
1826
|
+
if (!this._destroyed) {
|
|
1827
|
+
this.connect().catch((err) => {
|
|
1828
|
+
this.options.onError?.(err);
|
|
1829
|
+
if (!this._destroyed && !this._restartTimer && !this.process) {
|
|
1830
|
+
this._reconnectAfterDown(`reconnect setup failed: ${err?.message ?? err}`);
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
}, delay);
|
|
1835
|
+
}
|
|
1836
|
+
/** Connect to frps by starting the frpc process. */
|
|
1837
|
+
async connect() {
|
|
1838
|
+
if (this._destroyed) return;
|
|
1839
|
+
if (this.process) return;
|
|
1840
|
+
if (this._connecting) return;
|
|
1841
|
+
this._connecting = true;
|
|
1842
|
+
let frpcPath;
|
|
1843
|
+
let admin;
|
|
1844
|
+
try {
|
|
1845
|
+
frpcPath = await ensureFrpc(this.log);
|
|
1846
|
+
if (this.options.adminStatus) {
|
|
1847
|
+
if (!this._adminPort) {
|
|
1848
|
+
try {
|
|
1849
|
+
this._adminPort = await getFreePort();
|
|
1850
|
+
} catch (err) {
|
|
1851
|
+
this.log(`admin port alloc failed: ${err?.message ?? err}`);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
if (this._adminPort) admin = { port: this._adminPort, user: this._adminUser, password: this._adminPassword };
|
|
1855
|
+
}
|
|
1856
|
+
const configContent = generateFrpcConfig(this.serverConfig, this.proxies, admin);
|
|
1857
|
+
writeFileSync(this.configPath, configContent, { mode: 384 });
|
|
1858
|
+
this.log(`Config written to ${this.configPath}`);
|
|
1859
|
+
} catch (err) {
|
|
1860
|
+
this._connecting = false;
|
|
1861
|
+
throw err;
|
|
1862
|
+
}
|
|
1863
|
+
if (this._destroyed) {
|
|
1864
|
+
this._connecting = false;
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
return new Promise((resolve, reject) => {
|
|
1868
|
+
const child = spawn(frpcPath, ["-c", this.configPath], {
|
|
1869
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1870
|
+
env: { ...process.env }
|
|
1871
|
+
});
|
|
1872
|
+
this.process = child;
|
|
1873
|
+
if (child.pid) _livingFrpcPids.add(child.pid);
|
|
1874
|
+
this._connecting = false;
|
|
1875
|
+
let resolved = false;
|
|
1876
|
+
const onOutput = (data) => {
|
|
1877
|
+
const text = data.toString().trim();
|
|
1878
|
+
if (!text) return;
|
|
1879
|
+
for (const line of text.split("\n")) {
|
|
1880
|
+
this.logBuffer.push(line);
|
|
1881
|
+
if (this.logBuffer.length > this.maxLogLines) {
|
|
1882
|
+
this.logBuffer.shift();
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
if (text.includes("start proxy success")) {
|
|
1886
|
+
this._everStartedProxy = true;
|
|
1887
|
+
this._conflictCount = 0;
|
|
1888
|
+
this._conflictSince = 0;
|
|
1889
|
+
}
|
|
1890
|
+
if (text.includes("start proxy success") || text.includes("login to server success")) {
|
|
1891
|
+
if (!this._connected) {
|
|
1892
|
+
this._connected = true;
|
|
1893
|
+
this._lastConnectedAt = Date.now();
|
|
1894
|
+
this._consecutiveErrors = 0;
|
|
1895
|
+
this._firstErrorAt = 0;
|
|
1896
|
+
this.options.onConnect?.();
|
|
1897
|
+
const probeConfigured = !!(this.options.probeUrl || this.options.probePath);
|
|
1898
|
+
if (!resolved && !probeConfigured) {
|
|
1899
|
+
resolved = true;
|
|
1900
|
+
resolve();
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
const errorLine = text.split("\n").find((line) => line.includes("[E]"));
|
|
1905
|
+
if (errorLine) {
|
|
1906
|
+
this._consecutiveErrors++;
|
|
1907
|
+
this._lastErrorAt = Date.now();
|
|
1908
|
+
if (this._firstErrorAt === 0) this._firstErrorAt = Date.now();
|
|
1909
|
+
this.options.onError?.(new Error(errorLine));
|
|
1910
|
+
if (isFrpcSubdomainConflict(errorLine) && !this._everStartedProxy) {
|
|
1911
|
+
if (this._conflictSince === 0) this._conflictSince = Date.now();
|
|
1912
|
+
this._conflictCount++;
|
|
1913
|
+
const CONFLICT_GIVEUP_ATTEMPTS = Number(process.env.SVAMP_FRPC_CONFLICT_GIVEUP_ATTEMPTS || 6);
|
|
1914
|
+
const CONFLICT_GIVEUP_MS = Number(process.env.SVAMP_FRPC_CONFLICT_GIVEUP_MS || 6e4);
|
|
1915
|
+
const elapsed = Date.now() - this._conflictSince;
|
|
1916
|
+
if (shouldGiveUpOnConflict({
|
|
1917
|
+
everStartedProxy: this._everStartedProxy,
|
|
1918
|
+
conflictCount: this._conflictCount,
|
|
1919
|
+
elapsedMs: elapsed,
|
|
1920
|
+
minAttempts: CONFLICT_GIVEUP_ATTEMPTS,
|
|
1921
|
+
minElapsedMs: CONFLICT_GIVEUP_MS
|
|
1922
|
+
})) {
|
|
1923
|
+
this.log(
|
|
1924
|
+
`Giving up: ${this._conflictCount} subdomain conflicts over ${Math.round(elapsed / 1e3)}s with no proxy ever online \u2014 another generation owns this vhost. Stopping to avoid endless frps churn.`
|
|
1925
|
+
);
|
|
1926
|
+
this.options.onError?.(new Error(`frpc persistent subdomain conflict (${this._conflictCount}x) \u2014 giving up`));
|
|
1927
|
+
this.destroy();
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
};
|
|
1932
|
+
child.stdout?.on("data", onOutput);
|
|
1933
|
+
child.stderr?.on("data", onOutput);
|
|
1934
|
+
let downHandled = false;
|
|
1935
|
+
const onChildDown = (rejectErr) => {
|
|
1936
|
+
if (downHandled) return;
|
|
1937
|
+
downHandled = true;
|
|
1938
|
+
this._connected = false;
|
|
1939
|
+
if (child.pid) _livingFrpcPids.delete(child.pid);
|
|
1940
|
+
this.process = null;
|
|
1941
|
+
this.options.onDisconnect?.();
|
|
1942
|
+
if (!resolved) {
|
|
1943
|
+
resolved = true;
|
|
1944
|
+
reject(rejectErr);
|
|
1945
|
+
}
|
|
1946
|
+
this._reconnectAfterDown(rejectErr.message);
|
|
1947
|
+
};
|
|
1948
|
+
child.on("error", (err) => {
|
|
1949
|
+
this.options.onError?.(err);
|
|
1950
|
+
onChildDown(err instanceof Error ? err : new Error(String(err)));
|
|
1951
|
+
});
|
|
1952
|
+
child.on("exit", (code) => {
|
|
1953
|
+
onChildDown(new Error(`frpc exited with code ${code}`));
|
|
1954
|
+
});
|
|
1955
|
+
if (this.options.probeUrl || this.options.probePath) {
|
|
1956
|
+
this.startProbeLoop({
|
|
1957
|
+
onFirstOk: () => {
|
|
1958
|
+
if (!resolved) {
|
|
1959
|
+
resolved = true;
|
|
1960
|
+
resolve();
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
} else if (this.options.adminStatus && this._adminPort) {
|
|
1965
|
+
this.startAdminStatusLoop();
|
|
1966
|
+
}
|
|
1967
|
+
if (this._connectTimeoutTimer) {
|
|
1968
|
+
clearTimeout(this._connectTimeoutTimer);
|
|
1969
|
+
this._connectTimeoutTimer = null;
|
|
1970
|
+
}
|
|
1971
|
+
this._connectTimeoutTimer = setTimeout(() => {
|
|
1972
|
+
this._connectTimeoutTimer = null;
|
|
1973
|
+
if (!resolved) {
|
|
1974
|
+
resolved = true;
|
|
1975
|
+
this.log("frpc connection timeout \u2014 continuing (may still connect)");
|
|
1976
|
+
resolve();
|
|
1977
|
+
}
|
|
1978
|
+
}, 3e4);
|
|
1979
|
+
this._connectTimeoutTimer.unref?.();
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
/** Resolve the effective probe URL, honoring probeUrl > probePath. */
|
|
1983
|
+
resolveProbeUrl() {
|
|
1984
|
+
if (this.options.probeUrl) return this.options.probeUrl;
|
|
1985
|
+
if (this.options.probePath) {
|
|
1986
|
+
const base = this.getUrls().get(this.options.ports[0]);
|
|
1987
|
+
if (!base) return null;
|
|
1988
|
+
const sep = this.options.probePath.startsWith("/") ? "" : "/";
|
|
1989
|
+
return `${base}${sep}${this.options.probePath}`;
|
|
1990
|
+
}
|
|
1991
|
+
return null;
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* Start an end-to-end probe loop against the resolved probe URL.
|
|
1995
|
+
* Updates _probeOk + timestamps and fires onProbeFail on transitions.
|
|
1996
|
+
* Called from connect(); cleaned up in destroy().
|
|
1997
|
+
*/
|
|
1998
|
+
startProbeLoop(opts = {}) {
|
|
1999
|
+
const probeUrl = this.resolveProbeUrl();
|
|
2000
|
+
if (!probeUrl) return;
|
|
2001
|
+
this.stopProbeLoop();
|
|
2002
|
+
const intervalMs = this.options.probeIntervalMs ?? 3e4;
|
|
2003
|
+
let firstOkFired = false;
|
|
2004
|
+
let inFlight = false;
|
|
2005
|
+
const runProbe = async () => {
|
|
2006
|
+
if (this._destroyed || inFlight) return;
|
|
2007
|
+
inFlight = true;
|
|
2008
|
+
try {
|
|
2009
|
+
const ctrl = new AbortController();
|
|
2010
|
+
const timer = setTimeout(() => ctrl.abort(), 1e4);
|
|
2011
|
+
let resp;
|
|
2012
|
+
try {
|
|
2013
|
+
resp = await fetch(probeUrl, {
|
|
2014
|
+
method: "GET",
|
|
2015
|
+
signal: ctrl.signal,
|
|
2016
|
+
// Don't follow redirects — we just want any 2xx/3xx.
|
|
2017
|
+
redirect: "manual",
|
|
2018
|
+
headers: { "User-Agent": "svamp-frpc-probe/1" }
|
|
2019
|
+
});
|
|
2020
|
+
} finally {
|
|
2021
|
+
clearTimeout(timer);
|
|
2022
|
+
}
|
|
2023
|
+
if (resp.status < 400) {
|
|
2024
|
+
const wasFailing = !this._probeOk;
|
|
2025
|
+
this._probeOk = true;
|
|
2026
|
+
this._lastProbeOkAt = Date.now();
|
|
2027
|
+
if (wasFailing) {
|
|
2028
|
+
this.log(`probe ok: ${probeUrl} (${resp.status})`);
|
|
2029
|
+
}
|
|
2030
|
+
if (!firstOkFired && opts.onFirstOk) {
|
|
2031
|
+
firstOkFired = true;
|
|
2032
|
+
opts.onFirstOk();
|
|
2033
|
+
}
|
|
2034
|
+
} else {
|
|
2035
|
+
throw new Error(`probe got ${resp.status}`);
|
|
2036
|
+
}
|
|
2037
|
+
} catch (err) {
|
|
2038
|
+
const wasOk = this._probeOk;
|
|
2039
|
+
this._probeOk = false;
|
|
2040
|
+
this._lastProbeFailAt = Date.now();
|
|
2041
|
+
if (wasOk) {
|
|
2042
|
+
this.log(`probe failed: ${err?.message || err}`);
|
|
2043
|
+
this.options.onProbeFail?.(err instanceof Error ? err : new Error(String(err)));
|
|
2044
|
+
}
|
|
2045
|
+
} finally {
|
|
2046
|
+
inFlight = false;
|
|
2047
|
+
}
|
|
2048
|
+
};
|
|
2049
|
+
const initialDelays = [500, 2e3, 5e3, 1e4];
|
|
2050
|
+
let burstIdx = 0;
|
|
2051
|
+
if (this._burstTimer) clearInterval(this._burstTimer);
|
|
2052
|
+
this._burstTimer = setInterval(() => {
|
|
2053
|
+
if (this._destroyed || firstOkFired || burstIdx >= initialDelays.length) {
|
|
2054
|
+
if (this._burstTimer) {
|
|
2055
|
+
clearInterval(this._burstTimer);
|
|
2056
|
+
this._burstTimer = null;
|
|
2057
|
+
}
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
burstIdx++;
|
|
2061
|
+
void runProbe();
|
|
2062
|
+
}, 1500);
|
|
2063
|
+
if (intervalMs > 0) {
|
|
2064
|
+
this._probeTimer = setInterval(runProbe, intervalMs);
|
|
2065
|
+
}
|
|
2066
|
+
void runProbe();
|
|
2067
|
+
}
|
|
2068
|
+
stopProbeLoop() {
|
|
2069
|
+
if (this._probeTimer) {
|
|
2070
|
+
clearInterval(this._probeTimer);
|
|
2071
|
+
this._probeTimer = null;
|
|
2072
|
+
}
|
|
2073
|
+
if (this._burstTimer) {
|
|
2074
|
+
clearInterval(this._burstTimer);
|
|
2075
|
+
this._burstTimer = null;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Poll frpc's loopback admin API (`/api/status`) to track whether this
|
|
2080
|
+
* tunnel's proxies are registered ("running"). Feeds the same `_probeOk` /
|
|
2081
|
+
* staleness fields the daemon health loop watches, so a ghosted/stuck proxy
|
|
2082
|
+
* is detected and recreated even with no HTTP health endpoint on the backend.
|
|
2083
|
+
*
|
|
2084
|
+
* Conservative semantics (critical infra — must not churn healthy tunnels):
|
|
2085
|
+
* - all proxies "running" → ok (refresh lastProbeOkAt)
|
|
2086
|
+
* - a proxy present but not running, or missing after a grace period
|
|
2087
|
+
* → fail (sets lastProbeFailAt; daemon recreates after staleness)
|
|
2088
|
+
* - admin API unreachable → INCONCLUSIVE: leave _probeOk untouched
|
|
2089
|
+
* (a transient blip can't flip ok→fail)
|
|
2090
|
+
*/
|
|
2091
|
+
startAdminStatusLoop() {
|
|
2092
|
+
this.stopAdminStatusLoop();
|
|
2093
|
+
if (!this._adminPort) return;
|
|
2094
|
+
const proxyNames = this.proxies.map((p) => p.name);
|
|
2095
|
+
const intervalMs = this.options.probeIntervalMs ?? 3e4;
|
|
2096
|
+
const startedAt = Date.now();
|
|
2097
|
+
const auth = "Basic " + Buffer.from(`${this._adminUser}:${this._adminPassword}`).toString("base64");
|
|
2098
|
+
this._probeOk = true;
|
|
2099
|
+
this._lastProbeOkAt = Date.now();
|
|
2100
|
+
let inFlight = false;
|
|
2101
|
+
const poll = async () => {
|
|
2102
|
+
if (this._destroyed || inFlight || !this.process) return;
|
|
2103
|
+
inFlight = true;
|
|
2104
|
+
try {
|
|
2105
|
+
const ctrl = new AbortController();
|
|
2106
|
+
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
2107
|
+
let payload;
|
|
2108
|
+
try {
|
|
2109
|
+
const resp = await fetch(`http://127.0.0.1:${this._adminPort}/api/status`, {
|
|
2110
|
+
headers: { Authorization: auth },
|
|
2111
|
+
signal: ctrl.signal
|
|
2112
|
+
});
|
|
2113
|
+
if (!resp.ok) throw new Error(`admin status ${resp.status}`);
|
|
2114
|
+
payload = await resp.json();
|
|
2115
|
+
} finally {
|
|
2116
|
+
clearTimeout(timer);
|
|
2117
|
+
}
|
|
2118
|
+
const { allRunning, missing, failed } = parseFrpcStatus(payload, proxyNames);
|
|
2119
|
+
const graceElapsed = Date.now() - startedAt > 2e4;
|
|
2120
|
+
if (allRunning || !failed.length && !graceElapsed) {
|
|
2121
|
+
const wasFailing = !this._probeOk;
|
|
2122
|
+
this._probeOk = true;
|
|
2123
|
+
this._lastProbeOkAt = Date.now();
|
|
2124
|
+
if (wasFailing) this.log(`admin status ok: all proxies running`);
|
|
2125
|
+
} else {
|
|
2126
|
+
const wasOk = this._probeOk;
|
|
2127
|
+
this._probeOk = false;
|
|
2128
|
+
this._lastProbeFailAt = Date.now();
|
|
2129
|
+
if (wasOk) {
|
|
2130
|
+
const detail = [
|
|
2131
|
+
...failed.map((f) => `${f.name}=${f.status}${f.err ? ` (${f.err})` : ""}`),
|
|
2132
|
+
...missing.map((m) => `${m}=missing`)
|
|
2133
|
+
].join(", ");
|
|
2134
|
+
this.log(`admin status fail: ${detail}`);
|
|
2135
|
+
this.options.onProbeFail?.(new Error(`frpc proxy not running: ${detail}`));
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
} catch {
|
|
2139
|
+
} finally {
|
|
2140
|
+
inFlight = false;
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
if (intervalMs > 0) this._statusTimer = setInterval(poll, intervalMs);
|
|
2144
|
+
setTimeout(() => void poll(), 3e3);
|
|
2145
|
+
}
|
|
2146
|
+
stopAdminStatusLoop() {
|
|
2147
|
+
if (this._statusTimer) {
|
|
2148
|
+
clearInterval(this._statusTimer);
|
|
2149
|
+
this._statusTimer = null;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
/** Disconnect and stop the frpc process. */
|
|
2153
|
+
destroy() {
|
|
2154
|
+
this._destroyed = true;
|
|
2155
|
+
this._connected = false;
|
|
2156
|
+
this.stopProbeLoop();
|
|
2157
|
+
this.stopAdminStatusLoop();
|
|
2158
|
+
if (this._restartTimer) {
|
|
2159
|
+
clearTimeout(this._restartTimer);
|
|
2160
|
+
this._restartTimer = null;
|
|
2161
|
+
}
|
|
2162
|
+
if (this._connectTimeoutTimer) {
|
|
2163
|
+
clearTimeout(this._connectTimeoutTimer);
|
|
2164
|
+
this._connectTimeoutTimer = null;
|
|
2165
|
+
}
|
|
2166
|
+
if (this.process) {
|
|
2167
|
+
if (this.process.pid) _livingFrpcPids.delete(this.process.pid);
|
|
2168
|
+
this.process.kill("SIGTERM");
|
|
2169
|
+
const p = this.process;
|
|
2170
|
+
const killTimer = setTimeout(() => {
|
|
2171
|
+
try {
|
|
2172
|
+
if (p.exitCode === null && p.signalCode === null) p.kill("SIGKILL");
|
|
2173
|
+
} catch {
|
|
2174
|
+
}
|
|
2175
|
+
}, 5e3);
|
|
2176
|
+
killTimer.unref?.();
|
|
2177
|
+
p.once("exit", () => clearTimeout(killTimer));
|
|
2178
|
+
this.process = null;
|
|
2179
|
+
}
|
|
2180
|
+
try {
|
|
2181
|
+
unlinkSync(this.configPath);
|
|
2182
|
+
} catch {
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
/** Whether the frpc tunnel is connected. */
|
|
2186
|
+
get isConnected() {
|
|
2187
|
+
return this._connected && this.process !== null;
|
|
2188
|
+
}
|
|
2189
|
+
/** Snapshot of tunnel health for monitoring. */
|
|
2190
|
+
get status() {
|
|
2191
|
+
return {
|
|
2192
|
+
connected: this.isConnected,
|
|
2193
|
+
pid: this.process?.pid ?? null,
|
|
2194
|
+
name: this.options.name,
|
|
2195
|
+
ports: this.options.ports,
|
|
2196
|
+
serverAddr: this.serverConfig.serverAddr,
|
|
2197
|
+
recentLogs: this.logBuffer.slice(-10),
|
|
2198
|
+
consecutiveErrors: this._consecutiveErrors,
|
|
2199
|
+
lastConnectedAt: this._lastConnectedAt,
|
|
2200
|
+
lastErrorAt: this._lastErrorAt,
|
|
2201
|
+
firstErrorAt: this._firstErrorAt,
|
|
2202
|
+
restartAttempts: this._restartAttempts,
|
|
2203
|
+
failingDurationMs: this._firstErrorAt > 0 ? Date.now() - this._firstErrorAt : 0,
|
|
2204
|
+
probe: this.resolveProbeUrl() || this.options.adminStatus && this._adminPort ? {
|
|
2205
|
+
url: this.resolveProbeUrl() || `frpc-admin:${this._adminPort}/api/status`,
|
|
2206
|
+
ok: this._probeOk,
|
|
2207
|
+
lastOkAt: this._lastProbeOkAt,
|
|
2208
|
+
lastFailAt: this._lastProbeFailAt,
|
|
2209
|
+
stalenessMs: this._lastProbeOkAt > 0 ? Date.now() - this._lastProbeOkAt : 0
|
|
2210
|
+
} : void 0
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
/** Update the Hypha token. Rewrites config; takes effect on next frpc restart. */
|
|
2214
|
+
updateToken(newToken) {
|
|
2215
|
+
this.serverConfig.hyphaToken = newToken;
|
|
2216
|
+
const admin = this.options.adminStatus && this._adminPort ? { port: this._adminPort, user: this._adminUser, password: this._adminPassword } : void 0;
|
|
2217
|
+
const configContent = generateFrpcConfig(this.serverConfig, this.proxies, admin);
|
|
2218
|
+
writeFileSync(this.configPath, configContent, { mode: 384 });
|
|
2219
|
+
this.log("Config updated with fresh token");
|
|
2220
|
+
}
|
|
2221
|
+
/** Get the public URLs for each port. */
|
|
2222
|
+
getUrls() {
|
|
2223
|
+
const urls = /* @__PURE__ */ new Map();
|
|
2224
|
+
const base = this.serverConfig.subDomainHost || "svc.hypha.amun.ai";
|
|
2225
|
+
for (const proxy of this.proxies) {
|
|
2226
|
+
const subdomain = proxy.subdomain || proxy.name;
|
|
2227
|
+
urls.set(proxy.localPort, `https://${subdomain}.${base}`);
|
|
2228
|
+
}
|
|
2229
|
+
return urls;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
async function runFrpcTunnel(name, ports, serverConfig, tunnelOptions) {
|
|
2233
|
+
const portList = ports.join(", ");
|
|
2234
|
+
const tunnel = new FrpcTunnel({
|
|
2235
|
+
name,
|
|
2236
|
+
ports,
|
|
2237
|
+
serverConfig,
|
|
2238
|
+
...tunnelOptions,
|
|
2239
|
+
onConnect: () => {
|
|
2240
|
+
console.log(`Tunnel connected: ${name} \u2192 localhost:[${portList}]`);
|
|
2241
|
+
const urls = tunnel.getUrls();
|
|
2242
|
+
for (const [port, url] of urls) {
|
|
2243
|
+
console.log(` ${port} \u2192 ${url}`);
|
|
2244
|
+
}
|
|
2245
|
+
},
|
|
2246
|
+
onDisconnect: () => {
|
|
2247
|
+
console.log("Tunnel disconnected, frpc will auto-reconnect...");
|
|
2248
|
+
},
|
|
2249
|
+
onError: (err) => {
|
|
2250
|
+
console.error(`Tunnel error: ${err.message}`);
|
|
2251
|
+
}
|
|
2252
|
+
});
|
|
2253
|
+
const cleanup = () => {
|
|
2254
|
+
console.log("\nTunnel shutting down...");
|
|
2255
|
+
tunnel.destroy();
|
|
2256
|
+
process.exit(0);
|
|
2257
|
+
};
|
|
2258
|
+
process.on("SIGINT", cleanup);
|
|
2259
|
+
process.on("SIGTERM", cleanup);
|
|
2260
|
+
try {
|
|
2261
|
+
await tunnel.connect();
|
|
2262
|
+
console.log(`Tunnel is active. Press Ctrl+C to stop.`);
|
|
2263
|
+
await new Promise(() => {
|
|
2264
|
+
});
|
|
2265
|
+
} catch (err) {
|
|
2266
|
+
console.error(`Failed to establish tunnel: ${err.message}`);
|
|
2267
|
+
process.exit(1);
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
var frpc = /*#__PURE__*/Object.freeze({
|
|
2272
|
+
__proto__: null,
|
|
2273
|
+
FrpcTunnel: FrpcTunnel,
|
|
2274
|
+
ensureFrpc: ensureFrpc,
|
|
2275
|
+
generateFrpcConfig: generateFrpcConfig,
|
|
2276
|
+
getFreePort: getFreePort,
|
|
2277
|
+
isFrpcSubdomainConflict: isFrpcSubdomainConflict,
|
|
2278
|
+
killOrphanedFrpc: killOrphanedFrpc,
|
|
2279
|
+
parseFrpcStatus: parseFrpcStatus,
|
|
2280
|
+
runFrpcTunnel: runFrpcTunnel,
|
|
2281
|
+
selectOrphanedFrpcPids: selectOrphanedFrpcPids,
|
|
2282
|
+
shouldGiveUpOnConflict: shouldGiveUpOnConflict
|
|
2283
|
+
});
|
|
2284
|
+
|
|
1263
2285
|
const MIME = {
|
|
1264
2286
|
".html": "text/html; charset=utf-8",
|
|
1265
2287
|
".htm": "text/html; charset=utf-8",
|
|
@@ -1928,188 +2950,6 @@ function escapeHtml(s) {
|
|
|
1928
2950
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1929
2951
|
}
|
|
1930
2952
|
|
|
1931
|
-
function svampHome() {
|
|
1932
|
-
return process.env.SVAMP_HOME || join(homedir(), ".svamp");
|
|
1933
|
-
}
|
|
1934
|
-
function cacheFile() {
|
|
1935
|
-
return join(svampHome(), "instance-config.json");
|
|
1936
|
-
}
|
|
1937
|
-
const CONFIG_FILENAME = "svamp.json";
|
|
1938
|
-
let _config = null;
|
|
1939
|
-
let _loaded = false;
|
|
1940
|
-
let _cacheChecked = false;
|
|
1941
|
-
function stripSlash$1(u) {
|
|
1942
|
-
return u.replace(/\/+$/, "");
|
|
1943
|
-
}
|
|
1944
|
-
function getInstanceUrl() {
|
|
1945
|
-
const raw = (process.env.SVAMP_INSTANCE_URL || "").trim();
|
|
1946
|
-
return raw ? stripSlash$1(raw) : void 0;
|
|
1947
|
-
}
|
|
1948
|
-
function configUrlsFor(anchor) {
|
|
1949
|
-
const base = stripSlash$1(anchor);
|
|
1950
|
-
return [`${base}/${CONFIG_FILENAME}`, `${base}/.well-known/${CONFIG_FILENAME}`];
|
|
1951
|
-
}
|
|
1952
|
-
async function fetchConfig(anchor) {
|
|
1953
|
-
for (const url of configUrlsFor(anchor)) {
|
|
1954
|
-
try {
|
|
1955
|
-
const resp = await fetch(url, { signal: AbortSignal.timeout(8e3) });
|
|
1956
|
-
if (!resp.ok) continue;
|
|
1957
|
-
const data = await resp.json();
|
|
1958
|
-
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
1959
|
-
return data;
|
|
1960
|
-
}
|
|
1961
|
-
} catch {
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
return null;
|
|
1965
|
-
}
|
|
1966
|
-
function readCache() {
|
|
1967
|
-
try {
|
|
1968
|
-
if (existsSync$1(cacheFile())) {
|
|
1969
|
-
const data = JSON.parse(readFileSync$1(cacheFile(), "utf-8"));
|
|
1970
|
-
if (data && typeof data === "object" && !Array.isArray(data)) return data;
|
|
1971
|
-
}
|
|
1972
|
-
} catch {
|
|
1973
|
-
}
|
|
1974
|
-
return null;
|
|
1975
|
-
}
|
|
1976
|
-
function writeCache(cfg) {
|
|
1977
|
-
try {
|
|
1978
|
-
mkdirSync(svampHome(), { recursive: true });
|
|
1979
|
-
writeFileSync(cacheFile(), JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
1980
|
-
} catch {
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
function applyConfig(cfg) {
|
|
1984
|
-
_config = cfg;
|
|
1985
|
-
if (cfg.hyphaServerUrl && typeof cfg.hyphaServerUrl === "string") {
|
|
1986
|
-
process.env.HYPHA_SERVER_URL = stripSlash$1(cfg.hyphaServerUrl.trim());
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
async function loadInstanceConfig(opts = {}) {
|
|
1990
|
-
if (_loaded && !opts.force) return _config;
|
|
1991
|
-
const anchor = opts.anchor ? stripSlash$1(opts.anchor) : getInstanceUrl();
|
|
1992
|
-
if (anchor) {
|
|
1993
|
-
const fetched = await fetchConfig(anchor);
|
|
1994
|
-
if (fetched) {
|
|
1995
|
-
applyConfig(fetched);
|
|
1996
|
-
writeCache(fetched);
|
|
1997
|
-
_loaded = true;
|
|
1998
|
-
_cacheChecked = true;
|
|
1999
|
-
return _config;
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
const cached = readCache();
|
|
2003
|
-
if (cached) applyConfig(cached);
|
|
2004
|
-
_loaded = true;
|
|
2005
|
-
_cacheChecked = true;
|
|
2006
|
-
return _config;
|
|
2007
|
-
}
|
|
2008
|
-
function getLoadedConfig() {
|
|
2009
|
-
if (!_loaded && !_cacheChecked) {
|
|
2010
|
-
const cached = readCache();
|
|
2011
|
-
if (cached) applyConfig(cached);
|
|
2012
|
-
_cacheChecked = true;
|
|
2013
|
-
}
|
|
2014
|
-
return _config;
|
|
2015
|
-
}
|
|
2016
|
-
function clearInstanceConfigCache() {
|
|
2017
|
-
_config = null;
|
|
2018
|
-
_loaded = false;
|
|
2019
|
-
_cacheChecked = false;
|
|
2020
|
-
try {
|
|
2021
|
-
if (existsSync$1(cacheFile())) rmSync(cacheFile());
|
|
2022
|
-
} catch {
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
function __setConfigForTest(cfg) {
|
|
2026
|
-
_config = cfg;
|
|
2027
|
-
_loaded = true;
|
|
2028
|
-
_cacheChecked = true;
|
|
2029
|
-
if (cfg) applyConfig(cfg);
|
|
2030
|
-
}
|
|
2031
|
-
function __resetForTest() {
|
|
2032
|
-
_config = null;
|
|
2033
|
-
_loaded = false;
|
|
2034
|
-
_cacheChecked = false;
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
|
-
var instanceConfig = /*#__PURE__*/Object.freeze({
|
|
2038
|
-
__proto__: null,
|
|
2039
|
-
CONFIG_FILENAME: CONFIG_FILENAME,
|
|
2040
|
-
__resetForTest: __resetForTest,
|
|
2041
|
-
__setConfigForTest: __setConfigForTest,
|
|
2042
|
-
clearInstanceConfigCache: clearInstanceConfigCache,
|
|
2043
|
-
configUrlsFor: configUrlsFor,
|
|
2044
|
-
getInstanceUrl: getInstanceUrl,
|
|
2045
|
-
getLoadedConfig: getLoadedConfig,
|
|
2046
|
-
loadInstanceConfig: loadInstanceConfig
|
|
2047
|
-
});
|
|
2048
|
-
|
|
2049
|
-
function stripSlash(u) {
|
|
2050
|
-
return u.replace(/\/+$/, "");
|
|
2051
|
-
}
|
|
2052
|
-
function getHyphaServerUrl() {
|
|
2053
|
-
const fromCfg = getLoadedConfig()?.hyphaServerUrl;
|
|
2054
|
-
if (typeof fromCfg === "string" && fromCfg.trim()) return stripSlash(fromCfg.trim());
|
|
2055
|
-
const env = (process.env.HYPHA_SERVER_URL || "").trim();
|
|
2056
|
-
return env ? stripSlash(env) : void 0;
|
|
2057
|
-
}
|
|
2058
|
-
function getFrpsSubdomainHost() {
|
|
2059
|
-
const fromCfg = getLoadedConfig()?.frps?.subdomainHost;
|
|
2060
|
-
if (typeof fromCfg === "string" && fromCfg.trim()) return fromCfg.trim();
|
|
2061
|
-
const env = (process.env.FRPS_SUBDOMAIN_HOST || "").trim();
|
|
2062
|
-
if (env) return env;
|
|
2063
|
-
throw new Error(
|
|
2064
|
-
'frps subdomain host is not configured. Serve a svamp.json with "frps.subdomainHost" at your frontend URL (svamp login <frontend-url>), or set FRPS_SUBDOMAIN_HOST (legacy). It is deployment-specific and never guessed.'
|
|
2065
|
-
);
|
|
2066
|
-
}
|
|
2067
|
-
function getFrpsServerAddr() {
|
|
2068
|
-
const fromCfg = getLoadedConfig()?.frps?.serverAddr;
|
|
2069
|
-
if (typeof fromCfg === "string" && fromCfg.trim()) return fromCfg.trim();
|
|
2070
|
-
const env = (process.env.FRPS_SERVER_ADDR || "").trim();
|
|
2071
|
-
if (env) return env;
|
|
2072
|
-
return `frps-ctrl.${getFrpsSubdomainHost()}`;
|
|
2073
|
-
}
|
|
2074
|
-
function getFrpsServerPort() {
|
|
2075
|
-
const fromCfg = getLoadedConfig()?.frps?.serverPort;
|
|
2076
|
-
if (typeof fromCfg === "number" && Number.isFinite(fromCfg)) return fromCfg;
|
|
2077
|
-
const env = parseInt(process.env.FRPS_SERVER_PORT || "", 10);
|
|
2078
|
-
return Number.isFinite(env) ? env : void 0;
|
|
2079
|
-
}
|
|
2080
|
-
function getSvampWebBaseUrl() {
|
|
2081
|
-
const fromCfg = getLoadedConfig()?.web?.baseUrl;
|
|
2082
|
-
if (typeof fromCfg === "string" && fromCfg.trim()) return stripSlash(fromCfg.trim());
|
|
2083
|
-
const env = (process.env.SVAMP_WEB_URL || "").trim();
|
|
2084
|
-
if (env) return stripSlash(env);
|
|
2085
|
-
const instance = getInstanceUrl();
|
|
2086
|
-
if (instance) return instance;
|
|
2087
|
-
throw new Error(
|
|
2088
|
-
'Svamp web base URL is not configured. Serve a svamp.json with "web.baseUrl", log in with your frontend URL (svamp login <frontend-url>), or set SVAMP_WEB_URL (legacy).'
|
|
2089
|
-
);
|
|
2090
|
-
}
|
|
2091
|
-
function getSkillsServerUrl() {
|
|
2092
|
-
const fromCfg = getLoadedConfig()?.skills?.server;
|
|
2093
|
-
if (typeof fromCfg === "string" && fromCfg.trim()) return stripSlash(fromCfg.trim());
|
|
2094
|
-
const env = (process.env.HYPHA_SKILLS_SERVER || "").trim();
|
|
2095
|
-
if (env) return stripSlash(env);
|
|
2096
|
-
const server = getHyphaServerUrl();
|
|
2097
|
-
if (server) return server;
|
|
2098
|
-
throw new Error(
|
|
2099
|
-
"Cannot determine skills marketplace server. Serve a svamp.json or set HYPHA_SERVER_URL."
|
|
2100
|
-
);
|
|
2101
|
-
}
|
|
2102
|
-
function getSkillsWorkspace() {
|
|
2103
|
-
const fromCfg = getLoadedConfig()?.skills?.workspace;
|
|
2104
|
-
if (typeof fromCfg === "string" && fromCfg.trim()) return fromCfg.trim();
|
|
2105
|
-
return (process.env.HYPHA_SKILLS_WORKSPACE || "").trim() || "hypha-cloud";
|
|
2106
|
-
}
|
|
2107
|
-
function getSkillsCollection() {
|
|
2108
|
-
const fromCfg = getLoadedConfig()?.skills?.collection;
|
|
2109
|
-
if (typeof fromCfg === "string" && fromCfg.trim()) return fromCfg.trim();
|
|
2110
|
-
return (process.env.HYPHA_SKILLS_COLLECTION || "").trim() || "marketplace";
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
2953
|
const SESSION_FILES_MOUNT = "svamp-session-files";
|
|
2114
2954
|
function generateLinkToken() {
|
|
2115
2955
|
return crypto.randomBytes(11).toString("hex");
|
|
@@ -2819,6 +3659,7 @@ class ServeManager {
|
|
|
2819
3659
|
if (this.proxyServer) return;
|
|
2820
3660
|
killOrphanedCaddy((m) => this.log(m));
|
|
2821
3661
|
reapOrphanedManagedProcesses(this.managedPidDir, (m) => this.log(m));
|
|
3662
|
+
killOrphanedFrpc((m) => this.log(m));
|
|
2822
3663
|
this.port = await tryReservePort(this.persistedPort);
|
|
2823
3664
|
if (this.persistedPort && this.port !== this.persistedPort) {
|
|
2824
3665
|
this.log(`\u26A0 Previous serve port ${this.persistedPort} unavailable \u2014 using ${this.port}. Downstream configs referencing the old port will need updating.`);
|
|
@@ -3428,7 +4269,7 @@ Connection: close\r
|
|
|
3428
4269
|
const mount = this.mounts.get(mountName);
|
|
3429
4270
|
const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
|
|
3430
4271
|
try {
|
|
3431
|
-
const { FrpcTunnel } = await
|
|
4272
|
+
const { FrpcTunnel } = await Promise.resolve().then(function () { return frpc; });
|
|
3432
4273
|
let tunnel;
|
|
3433
4274
|
tunnel = new FrpcTunnel({
|
|
3434
4275
|
name: tunnelName,
|
|
@@ -3526,7 +4367,7 @@ function writeEnvLines(lines) {
|
|
|
3526
4367
|
}
|
|
3527
4368
|
writeFileSync$1(file, lines.join("\n") + "\n", { encoding: "utf-8", mode: 384 });
|
|
3528
4369
|
try {
|
|
3529
|
-
chmodSync(file, 384);
|
|
4370
|
+
chmodSync$1(file, 384);
|
|
3530
4371
|
} catch {
|
|
3531
4372
|
}
|
|
3532
4373
|
}
|
|
@@ -3794,7 +4635,7 @@ function saveAccounts(accounts, svampHome) {
|
|
|
3794
4635
|
const payload = { version: 1, accounts };
|
|
3795
4636
|
writeFileSync$1(file, JSON.stringify(payload, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
3796
4637
|
try {
|
|
3797
|
-
chmodSync(file, 384);
|
|
4638
|
+
chmodSync$1(file, 384);
|
|
3798
4639
|
} catch {
|
|
3799
4640
|
}
|
|
3800
4641
|
}
|
|
@@ -3849,7 +4690,7 @@ function upsertAccount(input, svampHome) {
|
|
|
3849
4690
|
const now = Date.now();
|
|
3850
4691
|
const existing = input.id ? accounts.find((a) => a.id === input.id) : void 0;
|
|
3851
4692
|
const account = {
|
|
3852
|
-
id: existing?.id || input.id || randomUUID(),
|
|
4693
|
+
id: existing?.id || input.id || randomUUID$1(),
|
|
3853
4694
|
label: input.label.trim(),
|
|
3854
4695
|
vendor,
|
|
3855
4696
|
method,
|
|
@@ -4108,7 +4949,7 @@ const ANTHROPIC_REDIRECT_URI = "https://console.anthropic.com/oauth/code/callbac
|
|
|
4108
4949
|
const ANTHROPIC_SCOPES = "user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
4109
4950
|
function generatePkce() {
|
|
4110
4951
|
const verifier = randomBytes(32).toString("base64url");
|
|
4111
|
-
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
4952
|
+
const challenge = createHash$1("sha256").update(verifier).digest("base64url");
|
|
4112
4953
|
return { verifier, challenge };
|
|
4113
4954
|
}
|
|
4114
4955
|
function buildAnthropicAuthUrl(challenge, state) {
|
|
@@ -4217,14 +5058,14 @@ function startAnthropicFlow(label) {
|
|
|
4217
5058
|
pruneFlows();
|
|
4218
5059
|
const { verifier, challenge } = generatePkce();
|
|
4219
5060
|
const state = randomBytes(32).toString("base64url");
|
|
4220
|
-
const flowId = randomUUID();
|
|
5061
|
+
const flowId = randomUUID$1();
|
|
4221
5062
|
const authorizeUrl = buildAnthropicAuthUrl(challenge, state);
|
|
4222
5063
|
flows.set(flowId, { id: flowId, vendor: "anthropic", label, createdAt: Date.now(), status: "pending", state, verifier });
|
|
4223
5064
|
return { flowId, authorizeUrl };
|
|
4224
5065
|
}
|
|
4225
5066
|
function startOpenAiDeviceFlow(label, log, svampHome) {
|
|
4226
5067
|
pruneFlows();
|
|
4227
|
-
const flowId = randomUUID();
|
|
5068
|
+
const flowId = randomUUID$1();
|
|
4228
5069
|
const codexHome = join$1(oauthFlowDir(flowId, svampHome), "codex");
|
|
4229
5070
|
mkdirSync$1(codexHome, { recursive: true });
|
|
4230
5071
|
const flow = { id: flowId, vendor: "openai", label, createdAt: Date.now(), status: "pending", codexHome, outputBuffer: "" };
|
|
@@ -6587,7 +7428,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
6587
7428
|
const tunnels = handlers.tunnels;
|
|
6588
7429
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
6589
7430
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
6590
|
-
const { FrpcTunnel } = await
|
|
7431
|
+
const { FrpcTunnel } = await Promise.resolve().then(function () { return frpc; });
|
|
6591
7432
|
const tunnel = new FrpcTunnel({
|
|
6592
7433
|
name: params.name,
|
|
6593
7434
|
ports: params.ports,
|
|
@@ -6846,7 +7687,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
6846
7687
|
const sessionRpc = handlers.getSessionRPCHandlers?.(sid);
|
|
6847
7688
|
if (sessionRpc?.saveChannel) {
|
|
6848
7689
|
try {
|
|
6849
|
-
const sharedKey = createHash("sha256").update(sid + (process.env.INTERNAL_API_SECRET || "")).digest("hex").slice(0, 32);
|
|
7690
|
+
const sharedKey = createHash$1("sha256").update(sid + (process.env.INTERNAL_API_SECRET || "")).digest("hex").slice(0, 32);
|
|
6850
7691
|
const meetingChannel = {
|
|
6851
7692
|
id: `meeting-${sid}`,
|
|
6852
7693
|
name: `Meeting WISE (${sid})`,
|
|
@@ -7080,7 +7921,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
7080
7921
|
}
|
|
7081
7922
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
7082
7923
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
7083
|
-
const { toolsForRole } = await import('./sideband-
|
|
7924
|
+
const { toolsForRole } = await import('./sideband-C1db5sMF.mjs');
|
|
7084
7925
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
7085
7926
|
return fmt(r2);
|
|
7086
7927
|
}
|
|
@@ -7179,13 +8020,13 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
7179
8020
|
hyphaWorkspace: u?.scope?.current_workspace
|
|
7180
8021
|
});
|
|
7181
8022
|
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
7182
|
-
const callId = "call_" + randomUUID().slice(0, 10);
|
|
8023
|
+
const callId = "call_" + randomUUID$1().slice(0, 10);
|
|
7183
8024
|
const senderKey = (r.sender.name || "anonymous").toLowerCase();
|
|
7184
8025
|
if (!statelessLimiter.tryAcquire(senderKey)) {
|
|
7185
8026
|
return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
|
|
7186
8027
|
}
|
|
7187
8028
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
7188
|
-
const { queryCore } = await import('./commands-
|
|
8029
|
+
const { queryCore } = await import('./commands-BY1lmXaM.mjs');
|
|
7189
8030
|
const timeout = c.reply?.timeout_sec || 120;
|
|
7190
8031
|
let result;
|
|
7191
8032
|
try {
|
|
@@ -7325,7 +8166,7 @@ ${d?.error || "not found"}`;
|
|
|
7325
8166
|
try {
|
|
7326
8167
|
const saved = writeUpload(dir, v.target, dec.data);
|
|
7327
8168
|
try {
|
|
7328
|
-
new ChannelStore(dir).recordCall(c.id, { sender: r.sender.name, verified: r.sender.verified, callId: "up_" + randomUUID().slice(0, 10), outcome: "uploaded" });
|
|
8169
|
+
new ChannelStore(dir).recordCall(c.id, { sender: r.sender.name, verified: r.sender.verified, callId: "up_" + randomUUID$1().slice(0, 10), outcome: "uploaded" });
|
|
7329
8170
|
} catch {
|
|
7330
8171
|
}
|
|
7331
8172
|
return { ok: true, name: saved.name, path: saved.relPath, bytes: saved.bytes };
|
|
@@ -7434,7 +8275,7 @@ function writeInboundContext(sessionId, ctx) {
|
|
|
7434
8275
|
try {
|
|
7435
8276
|
const p = ctxPath(sessionId);
|
|
7436
8277
|
mkdirSync$1(join$1(SVAMP_HOME$2, "inbound"), { recursive: true });
|
|
7437
|
-
const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;
|
|
8278
|
+
const tmp = `${p}.${process.pid}.${randomUUID$1()}.tmp`;
|
|
7438
8279
|
writeFileSync$1(tmp, JSON.stringify({ ...ctx, deliveredAt: Date.now() }));
|
|
7439
8280
|
renameSync(tmp, p);
|
|
7440
8281
|
} catch {
|
|
@@ -7519,7 +8360,7 @@ function writeAwaiting(sessionId, map) {
|
|
|
7519
8360
|
try {
|
|
7520
8361
|
mkdirSync$1(join$1(SVAMP_HOME$2, "awaiting"), { recursive: true });
|
|
7521
8362
|
const p = awaitPath(sessionId);
|
|
7522
|
-
const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;
|
|
8363
|
+
const tmp = `${p}.${process.pid}.${randomUUID$1()}.tmp`;
|
|
7523
8364
|
writeFileSync$1(tmp, JSON.stringify(map));
|
|
7524
8365
|
renameSync(tmp, p);
|
|
7525
8366
|
} catch {
|
|
@@ -7781,7 +8622,7 @@ function ensureOutpostToken(projectDir, session) {
|
|
|
7781
8622
|
mkdirSync$1(dirname(file), { recursive: true });
|
|
7782
8623
|
writeFileSync$1(file, JSON.stringify(rec, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
7783
8624
|
try {
|
|
7784
|
-
chmodSync(file, 384);
|
|
8625
|
+
chmodSync$1(file, 384);
|
|
7785
8626
|
} catch {
|
|
7786
8627
|
}
|
|
7787
8628
|
return rec;
|
|
@@ -7859,7 +8700,7 @@ function savePairings(pairings, svampHome) {
|
|
|
7859
8700
|
const payload = { version: 1, pairings };
|
|
7860
8701
|
writeFileSync$1(file, JSON.stringify(payload, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
7861
8702
|
try {
|
|
7862
|
-
chmodSync(file, 384);
|
|
8703
|
+
chmodSync$1(file, 384);
|
|
7863
8704
|
} catch {
|
|
7864
8705
|
}
|
|
7865
8706
|
}
|
|
@@ -8571,11 +9412,11 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8571
9412
|
if (role === "agent") {
|
|
8572
9413
|
const data = { ...content };
|
|
8573
9414
|
if ((data.type === "assistant" || data.type === "user") && !data.uuid) {
|
|
8574
|
-
data.uuid = randomUUID();
|
|
9415
|
+
data.uuid = randomUUID$1();
|
|
8575
9416
|
}
|
|
8576
9417
|
wrappedContent = { role: "agent", content: { type: "output", data } };
|
|
8577
9418
|
} else if (role === "event") {
|
|
8578
|
-
wrappedContent = { role: "agent", content: { type: "event", id: randomUUID(), data: content } };
|
|
9419
|
+
wrappedContent = { role: "agent", content: { type: "event", id: randomUUID$1(), data: content } };
|
|
8579
9420
|
} else if (role === "session") {
|
|
8580
9421
|
wrappedContent = { role: "session", content: { type: "session", data: content } };
|
|
8581
9422
|
} else {
|
|
@@ -8583,7 +9424,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8583
9424
|
wrappedContent = { role: "user", content: { type: "text", text } };
|
|
8584
9425
|
}
|
|
8585
9426
|
const msg = {
|
|
8586
|
-
id: randomUUID(),
|
|
9427
|
+
id: randomUUID$1(),
|
|
8587
9428
|
seq: nextSeq++,
|
|
8588
9429
|
content: wrappedContent,
|
|
8589
9430
|
localId: null,
|
|
@@ -8621,7 +9462,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8621
9462
|
const msg = {
|
|
8622
9463
|
// #0816: CSPRNG — this id routes `svamp session inbox reply`, so a predictable
|
|
8623
9464
|
// one lets a third party target another agent's reply thread.
|
|
8624
|
-
messageId: "op_" + randomUUID().slice(0, 10),
|
|
9465
|
+
messageId: "op_" + randomUUID$1().slice(0, 10),
|
|
8625
9466
|
body: `A remote machine connected as an outpost: ${xmlEscape(who)} (${xmlEscape(plat)}). You can now run shell commands on it \u2014 its output returns synchronously, like a local command. Run one with: svamp outpost exec "uname -a"`,
|
|
8626
9467
|
timestamp: Date.now(),
|
|
8627
9468
|
read: false,
|
|
@@ -8866,10 +9707,10 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8866
9707
|
const wrappedContent = parsed && parsed.role === "user" ? { role: "user", content: parsed.content } : { role: "user", content: { type: "text", text: typeof parsed === "string" ? parsed : JSON.stringify(parsed) } };
|
|
8867
9708
|
if (isBtwEcho) wrappedContent.meta = { btw: true };
|
|
8868
9709
|
const msg = {
|
|
8869
|
-
id: randomUUID(),
|
|
9710
|
+
id: randomUUID$1(),
|
|
8870
9711
|
seq: nextSeq++,
|
|
8871
9712
|
content: wrappedContent,
|
|
8872
|
-
localId: effectiveLocalId || randomUUID(),
|
|
9713
|
+
localId: effectiveLocalId || randomUUID$1(),
|
|
8873
9714
|
createdAt: Date.now(),
|
|
8874
9715
|
updatedAt: Date.now()
|
|
8875
9716
|
};
|
|
@@ -9259,7 +10100,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
9259
10100
|
hyphaWorkspace: u?.scope?.current_workspace
|
|
9260
10101
|
});
|
|
9261
10102
|
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
9262
|
-
const callId = "call_" + randomUUID().slice(0, 10);
|
|
10103
|
+
const callId = "call_" + randomUUID$1().slice(0, 10);
|
|
9263
10104
|
const ownerEmail = metadata.sharing?.owner;
|
|
9264
10105
|
const ownerCtx = ownerEmail ? { user: { email: ownerEmail, id: ownerEmail } } : void 0;
|
|
9265
10106
|
try {
|
|
@@ -9344,7 +10185,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
9344
10185
|
if (dec.error || !dec.data) return { error: dec.error || "invalid content" };
|
|
9345
10186
|
const v = validateUpload({ projectDir: metadata.path, channel: c, filename: params.filename, bytes: dec.data.length, contentType: params.content_type });
|
|
9346
10187
|
if (v.error || !v.target) return { error: v.error || "upload rejected" };
|
|
9347
|
-
const callId = "call_" + randomUUID().slice(0, 10);
|
|
10188
|
+
const callId = "call_" + randomUUID$1().slice(0, 10);
|
|
9348
10189
|
try {
|
|
9349
10190
|
const saved = writeUpload(metadata.path, v.target, dec.data);
|
|
9350
10191
|
channelStore.recordCall(c.id, { sender: r.sender.name, verified: r.sender.verified, callId, outcome: "uploaded" });
|
|
@@ -11067,10 +11908,10 @@ function computeCollectionConfigUpdate(existingConfig, extra) {
|
|
|
11067
11908
|
return { changed, merged: { ...cfg, ...extra } };
|
|
11068
11909
|
}
|
|
11069
11910
|
function emailHash(email) {
|
|
11070
|
-
return createHash("sha256").update(email.toLowerCase()).digest("hex").slice(0, 12);
|
|
11911
|
+
return createHash$1("sha256").update(email.toLowerCase()).digest("hex").slice(0, 12);
|
|
11071
11912
|
}
|
|
11072
11913
|
function sessionIdHash(sessionId) {
|
|
11073
|
-
return createHash("sha256").update(sessionId).digest("hex").slice(0, 12);
|
|
11914
|
+
return createHash$1("sha256").update(sessionId).digest("hex").slice(0, 12);
|
|
11074
11915
|
}
|
|
11075
11916
|
function roleLabel(role) {
|
|
11076
11917
|
switch (role) {
|
|
@@ -11190,7 +12031,7 @@ class SharingNotificationSync {
|
|
|
11190
12031
|
const shareType = params.shareType || "session";
|
|
11191
12032
|
const typeLabel = shareType === "machine" ? "Machine" : "Session";
|
|
11192
12033
|
await this.publishUserEvent({
|
|
11193
|
-
id: randomUUID(),
|
|
12034
|
+
id: randomUUID$1(),
|
|
11194
12035
|
recipientEmail: params.recipientEmail.toLowerCase(),
|
|
11195
12036
|
type: "sharing-invite",
|
|
11196
12037
|
source: {
|
|
@@ -11240,7 +12081,7 @@ class SharingNotificationSync {
|
|
|
11240
12081
|
} catch {
|
|
11241
12082
|
}
|
|
11242
12083
|
await this.publishUserEvent({
|
|
11243
|
-
id: randomUUID(),
|
|
12084
|
+
id: randomUUID$1(),
|
|
11244
12085
|
recipientEmail: recipientEmail.toLowerCase(),
|
|
11245
12086
|
type: "sharing-revoked",
|
|
11246
12087
|
source: {
|
|
@@ -11898,7 +12739,7 @@ class AcpBackend {
|
|
|
11898
12739
|
}
|
|
11899
12740
|
async startSession(initialPrompt) {
|
|
11900
12741
|
if (this.disposed) throw new Error("Backend has been disposed");
|
|
11901
|
-
const sessionId = randomUUID();
|
|
12742
|
+
const sessionId = randomUUID$1();
|
|
11902
12743
|
this.emit({ type: "status", status: "starting" });
|
|
11903
12744
|
let startupStatusErrorEmitted = false;
|
|
11904
12745
|
try {
|
|
@@ -12048,7 +12889,7 @@ class AcpBackend {
|
|
|
12048
12889
|
const extendedParams = params;
|
|
12049
12890
|
const toolCall = extendedParams.toolCall;
|
|
12050
12891
|
let toolName = toolCall?.kind || toolCall?.toolName || extendedParams.kind || "Unknown tool";
|
|
12051
|
-
const toolCallId = toolCall?.id || randomUUID();
|
|
12892
|
+
const toolCallId = toolCall?.id || randomUUID$1();
|
|
12052
12893
|
const permissionId = toolCallId;
|
|
12053
12894
|
let input = {};
|
|
12054
12895
|
if (toolCall) {
|
|
@@ -13289,7 +14130,7 @@ class CodexAppServerBackend {
|
|
|
13289
14130
|
}
|
|
13290
14131
|
listeners = [];
|
|
13291
14132
|
client;
|
|
13292
|
-
sessionId = randomUUID();
|
|
14133
|
+
sessionId = randomUUID$1();
|
|
13293
14134
|
log;
|
|
13294
14135
|
model;
|
|
13295
14136
|
// #0438: codex's own approval policy + sandbox, derived from the svamp permission mode. Mutable
|
|
@@ -13434,7 +14275,7 @@ class CodexAppServerBackend {
|
|
|
13434
14275
|
}
|
|
13435
14276
|
}
|
|
13436
14277
|
handleApproval(req) {
|
|
13437
|
-
const id = req.callId || randomUUID();
|
|
14278
|
+
const id = req.callId || randomUUID$1();
|
|
13438
14279
|
const reason = req.type === "exec" ? `Execute: ${(req.command ?? []).join(" ")}` : req.type === "patch" ? "Apply file changes" : req.toolName ? `Tool: ${req.toolName}` : "Codex requires approval";
|
|
13439
14280
|
const payload = req.type === "exec" ? { toolName: "CodexBash", input: { command: req.command, cwd: req.cwd } } : req.type === "patch" ? { toolName: "CodexPatch", input: { fileChanges: req.fileChanges } } : { toolName: req.toolName ?? "CodexTool", input: req.input };
|
|
13440
14281
|
this.emit({ type: "permission-request", id, reason, payload });
|
|
@@ -13480,7 +14321,7 @@ class CodexAppServerBackend {
|
|
|
13480
14321
|
break;
|
|
13481
14322
|
}
|
|
13482
14323
|
case "exec_command_begin": {
|
|
13483
|
-
const callId = String(event.call_id ?? event.callId ?? randomUUID());
|
|
14324
|
+
const callId = String(event.call_id ?? event.callId ?? randomUUID$1());
|
|
13484
14325
|
const command = Array.isArray(event.command) ? event.command.join(" ") : String(event.command ?? "");
|
|
13485
14326
|
this.emit({ type: "tool-call", toolName: "CodexBash", callId, args: { command, cwd: event.cwd } });
|
|
13486
14327
|
break;
|
|
@@ -13491,7 +14332,7 @@ class CodexAppServerBackend {
|
|
|
13491
14332
|
break;
|
|
13492
14333
|
}
|
|
13493
14334
|
case "patch_apply_begin": {
|
|
13494
|
-
const callId = String(event.call_id ?? event.callId ?? randomUUID());
|
|
14335
|
+
const callId = String(event.call_id ?? event.callId ?? randomUUID$1());
|
|
13495
14336
|
const { filePath, patch, kind } = summarizeCodexChanges(event.changes);
|
|
13496
14337
|
this.emit({ type: "tool-call", toolName: "CodexPatch", callId, args: {
|
|
13497
14338
|
filePath: filePath ?? event.file_path ?? event.filePath ?? null,
|
|
@@ -13511,7 +14352,7 @@ class CodexAppServerBackend {
|
|
|
13511
14352
|
break;
|
|
13512
14353
|
}
|
|
13513
14354
|
case "mcp_tool_call_begin": {
|
|
13514
|
-
const callId = String(event.call_id ?? event.callId ?? randomUUID());
|
|
14355
|
+
const callId = String(event.call_id ?? event.callId ?? randomUUID$1());
|
|
13515
14356
|
this.emit({ type: "tool-call", toolName: String(event.tool ?? "CodexMcpTool"), callId, args: { server: event.server, arguments: event.arguments } });
|
|
13516
14357
|
break;
|
|
13517
14358
|
}
|
|
@@ -14459,7 +15300,7 @@ async function stageCredentialsForSharing(sessionId) {
|
|
|
14459
15300
|
} catch {
|
|
14460
15301
|
}
|
|
14461
15302
|
}
|
|
14462
|
-
if (!credentialsCopied && platform() === "darwin") {
|
|
15303
|
+
if (!credentialsCopied && platform$1() === "darwin") {
|
|
14463
15304
|
const stagedCredFile = join$1(stagedClaudeDir, ".credentials.json");
|
|
14464
15305
|
const hasExistingCredentials = existsSync(stagedCredFile);
|
|
14465
15306
|
if (!hasExistingCredentials) {
|
|
@@ -14851,7 +15692,7 @@ class ProcessSupervisor {
|
|
|
14851
15692
|
}
|
|
14852
15693
|
const fullSpec = {
|
|
14853
15694
|
...spec,
|
|
14854
|
-
id: randomUUID
|
|
15695
|
+
id: randomUUID(),
|
|
14855
15696
|
createdAt: Date.now()
|
|
14856
15697
|
};
|
|
14857
15698
|
const entry = this.makeEntry(fullSpec);
|
|
@@ -15614,7 +16455,7 @@ function _updateIssueUnlocked(projectRoot, id, patch) {
|
|
|
15614
16455
|
if (wasArchived !== nowArchived) {
|
|
15615
16456
|
const oldPath = issuePath(projectRoot, cur.id, wasArchived);
|
|
15616
16457
|
try {
|
|
15617
|
-
if (existsSync(oldPath)) unlinkSync(oldPath);
|
|
16458
|
+
if (existsSync(oldPath)) unlinkSync$1(oldPath);
|
|
15618
16459
|
} catch {
|
|
15619
16460
|
}
|
|
15620
16461
|
}
|
|
@@ -15890,7 +16731,7 @@ function removeWorkflow(projectRoot, name) {
|
|
|
15890
16731
|
const p = workflowPath(projectRoot, name);
|
|
15891
16732
|
if (!existsSync(p)) return false;
|
|
15892
16733
|
try {
|
|
15893
|
-
unlinkSync(p);
|
|
16734
|
+
unlinkSync$1(p);
|
|
15894
16735
|
return true;
|
|
15895
16736
|
} catch {
|
|
15896
16737
|
return false;
|
|
@@ -16906,7 +17747,7 @@ process.stdin.resume();
|
|
|
16906
17747
|
const cleanup = () => {
|
|
16907
17748
|
for (const p of cleanupPaths) {
|
|
16908
17749
|
try {
|
|
16909
|
-
if (existsSync(p)) unlinkSync(p);
|
|
17750
|
+
if (existsSync(p)) unlinkSync$1(p);
|
|
16910
17751
|
} catch {
|
|
16911
17752
|
}
|
|
16912
17753
|
}
|
|
@@ -17552,7 +18393,7 @@ function readPendingRestart() {
|
|
|
17552
18393
|
}
|
|
17553
18394
|
function clearPendingRestart() {
|
|
17554
18395
|
try {
|
|
17555
|
-
if (existsSync$1(PENDING_RESTART_FILE)) unlinkSync
|
|
18396
|
+
if (existsSync$1(PENDING_RESTART_FILE)) unlinkSync(PENDING_RESTART_FILE);
|
|
17556
18397
|
} catch {
|
|
17557
18398
|
}
|
|
17558
18399
|
}
|
|
@@ -17565,7 +18406,7 @@ function writeStopMarker(reason) {
|
|
|
17565
18406
|
}
|
|
17566
18407
|
function clearStopMarker() {
|
|
17567
18408
|
try {
|
|
17568
|
-
if (existsSync$1(DAEMON_STOP_MARKER_FILE)) unlinkSync
|
|
18409
|
+
if (existsSync$1(DAEMON_STOP_MARKER_FILE)) unlinkSync(DAEMON_STOP_MARKER_FILE);
|
|
17569
18410
|
} catch {
|
|
17570
18411
|
}
|
|
17571
18412
|
}
|
|
@@ -17962,7 +18803,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
17962
18803
|
url: String(l.url).trim(),
|
|
17963
18804
|
...l.icon ? { icon: String(l.icon) } : {},
|
|
17964
18805
|
// #0266: carry the service backing ({kind,name}) so the Launch Pad can decommission it.
|
|
17965
|
-
...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) } } : {}
|
|
18806
|
+
...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) } } : {}
|
|
17966
18807
|
}));
|
|
17967
18808
|
const cur = meta.sessionLinks;
|
|
17968
18809
|
if (JSON.stringify(cur || []) !== JSON.stringify(links)) {
|
|
@@ -18099,7 +18940,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
18099
18940
|
}
|
|
18100
18941
|
if (wasStopped) {
|
|
18101
18942
|
const eq = getMetadata().messageQueue || [];
|
|
18102
|
-
setMetadata((m) => ({ ...m, messageQueue: [...eq, { id: randomUUID
|
|
18943
|
+
setMetadata((m) => ({ ...m, messageQueue: [...eq, { id: randomUUID(), text: loopResumeMessage(directory, sessionId), displayText: "\u{1F501} Resuming loop (extended)", createdAt: Date.now() }] }));
|
|
18103
18944
|
onLoopActivated?.();
|
|
18104
18945
|
}
|
|
18105
18946
|
sessionService.pushMessage({ type: "message", message: `\u{1F501} Loop extended (+${grant.extension.grantedTurns} turns, ${grant.used}/${grant.max} used)${wasStopped ? " \u2014 resuming" : ""}. Reason: ${grant.extension.reason}`, level: "warning" }, "event");
|
|
@@ -18155,7 +18996,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
18155
18996
|
} catch {
|
|
18156
18997
|
}
|
|
18157
18998
|
const eq = getMetadata().messageQueue || [];
|
|
18158
|
-
setMetadata((m) => ({ ...m, messageQueue: [...eq, { id: randomUUID
|
|
18999
|
+
setMetadata((m) => ({ ...m, messageQueue: [...eq, { id: randomUUID(), text: loopResumeMessage(directory, sessionId), displayText: "\u{1F501} Resuming loop", createdAt: Date.now() }] }));
|
|
18159
19000
|
onLoopActivated?.();
|
|
18160
19001
|
}
|
|
18161
19002
|
if (!wasStopped && (newMax != null || bumpedBudget)) {
|
|
@@ -18188,14 +19029,14 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
18188
19029
|
}
|
|
18189
19030
|
writeGoalLoopState(directory, sessionId, { active: true, phase: "running", durable, iteration: 0, task: condition, goal_task: task, until, oracle, max_iterations: hardMax, budget, started_at: Date.now(), holds: 0, ledger: { turns: 0, tokens: 0, window: [] }, progress_history: [], auto_resumes: 0, verify_errors: 0 });
|
|
18190
19031
|
const existingQueue = getMetadata().messageQueue || [];
|
|
18191
|
-
setMetadata((m) => ({ ...m, messageQueue: [...existingQueue, { id: randomUUID
|
|
19032
|
+
setMetadata((m) => ({ ...m, messageQueue: [...existingQueue, { id: randomUUID(), text: `/goal ${condition}`, displayText: `\u{1F3AF} Goal: ${(task || until || "").slice(0, 100)}`, createdAt: Date.now() }] }));
|
|
18192
19033
|
onLoopActivated?.();
|
|
18193
19034
|
sessionService.pushMessage({ type: "message", message: `\u{1F3AF} Goal set: ${condition.slice(0, 180)}${condition.length > 180 ? "\u2026" : ""}` }, "event");
|
|
18194
19035
|
logger.log(`[svampConfig] /goal set: "${condition.slice(0, 80)}"`);
|
|
18195
19036
|
} else {
|
|
18196
19037
|
writeGoalLoopState(directory, sessionId, { active: false, phase: "cancelled", completed_at: Date.now() });
|
|
18197
19038
|
const existingQueue = getMetadata().messageQueue || [];
|
|
18198
|
-
setMetadata((m) => ({ ...m, messageQueue: [...existingQueue, { id: randomUUID
|
|
19039
|
+
setMetadata((m) => ({ ...m, messageQueue: [...existingQueue, { id: randomUUID(), text: "/goal clear", displayText: "\u{1F3AF} Goal cleared", createdAt: Date.now() }] }));
|
|
18199
19040
|
onLoopActivated?.();
|
|
18200
19041
|
sessionService.pushMessage({ type: "message", message: "Goal cleared." }, "event");
|
|
18201
19042
|
logger.log(`[svampConfig] /goal cleared`);
|
|
@@ -18268,17 +19109,17 @@ function deletePersistedSession(sessionId) {
|
|
|
18268
19109
|
if (entry) {
|
|
18269
19110
|
const sessionFile = getSessionFilePath(entry.directory, sessionId);
|
|
18270
19111
|
try {
|
|
18271
|
-
if (existsSync$1(sessionFile)) unlinkSync
|
|
19112
|
+
if (existsSync$1(sessionFile)) unlinkSync(sessionFile);
|
|
18272
19113
|
} catch {
|
|
18273
19114
|
}
|
|
18274
19115
|
const messagesFile = getSessionMessagesPath(entry.directory, sessionId);
|
|
18275
19116
|
try {
|
|
18276
|
-
if (existsSync$1(messagesFile)) unlinkSync
|
|
19117
|
+
if (existsSync$1(messagesFile)) unlinkSync(messagesFile);
|
|
18277
19118
|
} catch {
|
|
18278
19119
|
}
|
|
18279
19120
|
const configFile = getSvampConfigPath(entry.directory, sessionId);
|
|
18280
19121
|
try {
|
|
18281
|
-
if (existsSync$1(configFile)) unlinkSync
|
|
19122
|
+
if (existsSync$1(configFile)) unlinkSync(configFile);
|
|
18282
19123
|
} catch {
|
|
18283
19124
|
}
|
|
18284
19125
|
const sessionDir = getSessionDir(entry.directory, sessionId);
|
|
@@ -18372,7 +19213,7 @@ function pruneDaemonLogs() {
|
|
|
18372
19213
|
const plan = planLogPrune(entries, policy);
|
|
18373
19214
|
for (const name of plan.deleteFiles) {
|
|
18374
19215
|
try {
|
|
18375
|
-
unlinkSync
|
|
19216
|
+
unlinkSync(join(LOGS_DIR, name));
|
|
18376
19217
|
} catch {
|
|
18377
19218
|
}
|
|
18378
19219
|
}
|
|
@@ -18621,7 +19462,7 @@ async function startDaemon(options) {
|
|
|
18621
19462
|
machineId = readFileSync$1(machineIdFile, "utf-8").trim();
|
|
18622
19463
|
}
|
|
18623
19464
|
if (!machineId) {
|
|
18624
|
-
machineId = `machine-${os$1.hostname()}-${randomUUID
|
|
19465
|
+
machineId = `machine-${os$1.hostname()}-${randomUUID().slice(0, 8)}`;
|
|
18625
19466
|
try {
|
|
18626
19467
|
writeFileSync(machineIdFile, machineId, "utf-8");
|
|
18627
19468
|
} catch {
|
|
@@ -18669,7 +19510,7 @@ async function startDaemon(options) {
|
|
|
18669
19510
|
try {
|
|
18670
19511
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
18671
19512
|
if (!dir) return;
|
|
18672
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
19513
|
+
const { reconcileServiceLinks } = await import('./agentCommands-BJeGwkeX.mjs');
|
|
18673
19514
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
18674
19515
|
const config = readSvampConfig(configPath);
|
|
18675
19516
|
const entries = Array.from(urls.entries());
|
|
@@ -18686,8 +19527,29 @@ async function startDaemon(options) {
|
|
|
18686
19527
|
logger.log(`[exposed-tunnels] Link reconcile failed for ${tunnelName}: ${err?.message || err}`);
|
|
18687
19528
|
}
|
|
18688
19529
|
}
|
|
19530
|
+
async function reconcileMountSessionLink(sessionId, mountName, url) {
|
|
19531
|
+
if (!sessionId || !url) return;
|
|
19532
|
+
try {
|
|
19533
|
+
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
19534
|
+
if (!dir) return;
|
|
19535
|
+
const { reconcileServiceLinks } = await import('./agentCommands-BJeGwkeX.mjs');
|
|
19536
|
+
const configPath = getSvampConfigPath(dir, sessionId);
|
|
19537
|
+
const config = readSvampConfig(configPath);
|
|
19538
|
+
const incoming = [{
|
|
19539
|
+
url: String(url),
|
|
19540
|
+
label: mountName,
|
|
19541
|
+
service: { kind: "serve", name: mountName }
|
|
19542
|
+
}];
|
|
19543
|
+
if (reconcileServiceLinks(config, incoming)) {
|
|
19544
|
+
writeSvampConfig(configPath, config);
|
|
19545
|
+
logger.log(`[serve] Reconciled Launch Pad link for mount ${mountName} \u2192 session ${sessionId}`);
|
|
19546
|
+
}
|
|
19547
|
+
} catch (err) {
|
|
19548
|
+
logger.log(`[serve] Link reconcile failed for mount ${mountName}: ${err?.message || err}`);
|
|
19549
|
+
}
|
|
19550
|
+
}
|
|
18689
19551
|
async function createExposedTunnel(spec) {
|
|
18690
|
-
const { FrpcTunnel } = await
|
|
19552
|
+
const { FrpcTunnel } = await Promise.resolve().then(function () { return frpc; });
|
|
18691
19553
|
const tunnel = new FrpcTunnel({
|
|
18692
19554
|
name: spec.name,
|
|
18693
19555
|
ports: spec.ports,
|
|
@@ -18715,7 +19577,7 @@ async function startDaemon(options) {
|
|
|
18715
19577
|
ensureAutoInstalledCommands(logger);
|
|
18716
19578
|
(async () => {
|
|
18717
19579
|
try {
|
|
18718
|
-
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-
|
|
19580
|
+
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-dbXgsGNK.mjs');
|
|
18719
19581
|
beginClaudeVersionReconcile((msg) => logger.log(msg));
|
|
18720
19582
|
} catch (e) {
|
|
18721
19583
|
logger.log(`[claude-version] check failed: ${e?.message || e}`);
|
|
@@ -18764,7 +19626,7 @@ async function startDaemon(options) {
|
|
|
18764
19626
|
logger.log(`[supervision] parent ${parentId.slice(0, 8)} not live \u2014 recording verdict on the backlog issue only (no in-memory wake)`);
|
|
18765
19627
|
}
|
|
18766
19628
|
if (send) Promise.resolve(send({
|
|
18767
|
-
messageId: randomUUID
|
|
19629
|
+
messageId: randomUUID(),
|
|
18768
19630
|
from: `agent:${v.sessionId}`,
|
|
18769
19631
|
fromSession: v.sessionId,
|
|
18770
19632
|
to: parentId,
|
|
@@ -18802,7 +19664,7 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
|
|
|
18802
19664
|
return;
|
|
18803
19665
|
}
|
|
18804
19666
|
Promise.resolve(send({
|
|
18805
|
-
messageId: randomUUID
|
|
19667
|
+
messageId: randomUUID(),
|
|
18806
19668
|
from: "system:issues",
|
|
18807
19669
|
subject: "issue follow-up",
|
|
18808
19670
|
body: text,
|
|
@@ -19370,7 +20232,7 @@ ${parts.join("\n")}`);
|
|
|
19370
20232
|
sessionMetadata = {
|
|
19371
20233
|
...sessionMetadata,
|
|
19372
20234
|
messageQueue: [...existingQueue, {
|
|
19373
|
-
id: randomUUID
|
|
20235
|
+
id: randomUUID(),
|
|
19374
20236
|
text,
|
|
19375
20237
|
displayText: "\u{1F501} Resuming loop",
|
|
19376
20238
|
createdAt: Date.now()
|
|
@@ -19382,7 +20244,7 @@ ${parts.join("\n")}`);
|
|
|
19382
20244
|
const existingQueue = sessionMetadata.messageQueue || [];
|
|
19383
20245
|
sessionMetadata = {
|
|
19384
20246
|
...sessionMetadata,
|
|
19385
|
-
messageQueue: [...existingQueue, { id: randomUUID
|
|
20247
|
+
messageQueue: [...existingQueue, { id: randomUUID(), text, displayText, createdAt: Date.now() }]
|
|
19386
20248
|
};
|
|
19387
20249
|
sessionService.updateMetadata(sessionMetadata);
|
|
19388
20250
|
};
|
|
@@ -20498,7 +21360,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20498
21360
|
// NOT re-push a display bubble (that second copy has localId:null and the
|
|
20499
21361
|
// frontend can't reconcile it → the message appears TWICE). Only re-send to
|
|
20500
21362
|
// the model. Inbox/loop items (no flag) still get their displayText push.
|
|
20501
|
-
messageQueue: [...existingQueue, { id: randomUUID
|
|
21363
|
+
messageQueue: [...existingQueue, { id: randomUUID(), text, createdAt: Date.now(), alreadyStored: true }]
|
|
20502
21364
|
};
|
|
20503
21365
|
sessionService.updateMetadata(sessionMetadata);
|
|
20504
21366
|
signalProcessing(false);
|
|
@@ -20655,7 +21517,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20655
21517
|
const existingQueue = sessionMetadata.messageQueue || [];
|
|
20656
21518
|
sessionMetadata = {
|
|
20657
21519
|
...sessionMetadata,
|
|
20658
|
-
messageQueue: [...existingQueue, { id: randomUUID
|
|
21520
|
+
messageQueue: [...existingQueue, { id: randomUUID(), text, createdAt: Date.now(), alreadyStored: true }]
|
|
20659
21521
|
};
|
|
20660
21522
|
sessionService.updateMetadata(sessionMetadata);
|
|
20661
21523
|
signalProcessing(true);
|
|
@@ -21032,11 +21894,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21032
21894
|
});
|
|
21033
21895
|
},
|
|
21034
21896
|
onIssue: async (params) => {
|
|
21035
|
-
const { issueRpc } = await import('./rpc-
|
|
21897
|
+
const { issueRpc } = await import('./rpc-TXF-di9u.mjs');
|
|
21036
21898
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
21037
21899
|
},
|
|
21038
21900
|
onWorkflow: async (params) => {
|
|
21039
|
-
const { workflowRpc } = await import('./rpc-
|
|
21901
|
+
const { workflowRpc } = await import('./rpc-FGp88Yxp.mjs');
|
|
21040
21902
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
21041
21903
|
},
|
|
21042
21904
|
onRipgrep: async (args, cwd) => {
|
|
@@ -21269,7 +22131,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21269
22131
|
enqueueLoopContinue();
|
|
21270
22132
|
processMessageQueueRef?.();
|
|
21271
22133
|
};
|
|
21272
|
-
pidToTrackedSession.set(randomUUID
|
|
22134
|
+
pidToTrackedSession.set(randomUUID(), trackedSession);
|
|
21273
22135
|
sessionMetadata = { ...sessionMetadata, lifecycleState: "idle" };
|
|
21274
22136
|
sessionService.updateMetadata(sessionMetadata);
|
|
21275
22137
|
logger.log(`Session ${sessionId} registered on Hypha, waiting for first message to spawn Claude`);
|
|
@@ -21474,7 +22336,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21474
22336
|
...sessionMetadata,
|
|
21475
22337
|
// #0411: alreadyStored — see Claude path. deliverUserMessageCore already stored
|
|
21476
22338
|
// this user message; the drain must not re-push a localId:null duplicate.
|
|
21477
|
-
messageQueue: [...existingQueue, { id: randomUUID
|
|
22339
|
+
messageQueue: [...existingQueue, { id: randomUUID(), text, createdAt: Date.now(), alreadyStored: true }]
|
|
21478
22340
|
};
|
|
21479
22341
|
sessionService.updateMetadata(sessionMetadata);
|
|
21480
22342
|
return;
|
|
@@ -21486,7 +22348,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21486
22348
|
...sessionMetadata,
|
|
21487
22349
|
// #0411: alreadyStored — deliverUserMessageCore already stored this
|
|
21488
22350
|
// user message; the drain must not re-push a localId:null duplicate.
|
|
21489
|
-
messageQueue: [...existingQueue, { id: randomUUID
|
|
22351
|
+
messageQueue: [...existingQueue, { id: randomUUID(), text, createdAt: Date.now(), alreadyStored: true }]
|
|
21490
22352
|
};
|
|
21491
22353
|
sessionService.updateMetadata(sessionMetadata);
|
|
21492
22354
|
};
|
|
@@ -21743,11 +22605,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21743
22605
|
});
|
|
21744
22606
|
},
|
|
21745
22607
|
onIssue: async (params) => {
|
|
21746
|
-
const { issueRpc } = await import('./rpc-
|
|
22608
|
+
const { issueRpc } = await import('./rpc-TXF-di9u.mjs');
|
|
21747
22609
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
21748
22610
|
},
|
|
21749
22611
|
onWorkflow: async (params) => {
|
|
21750
|
-
const { workflowRpc } = await import('./rpc-
|
|
22612
|
+
const { workflowRpc } = await import('./rpc-FGp88Yxp.mjs');
|
|
21751
22613
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
21752
22614
|
},
|
|
21753
22615
|
onRipgrep: async (args, cwd) => {
|
|
@@ -22285,7 +23147,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
22285
23147
|
});
|
|
22286
23148
|
}
|
|
22287
23149
|
};
|
|
22288
|
-
pidToTrackedSession.set(randomUUID
|
|
23150
|
+
pidToTrackedSession.set(randomUUID(), trackedSession);
|
|
22289
23151
|
let acpInboxReconcileTimer = null;
|
|
22290
23152
|
const reconcileAcpIdleInbox = () => {
|
|
22291
23153
|
if (acpStopped) {
|
|
@@ -22730,7 +23592,15 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
22730
23592
|
};
|
|
22731
23593
|
writeDaemonStateFile(localState);
|
|
22732
23594
|
logger.log("Daemon state file written \u2014 daemon is up; restoring mounts/sessions in background");
|
|
22733
|
-
serveManager$1.restore().
|
|
23595
|
+
serveManager$1.restore().then(async () => {
|
|
23596
|
+
try {
|
|
23597
|
+
for (const m of serveManager$1.listMounts()) {
|
|
23598
|
+
if (m?.sessionId && m?.url) await reconcileMountSessionLink(m.sessionId, m.name, m.url);
|
|
23599
|
+
}
|
|
23600
|
+
} catch (err) {
|
|
23601
|
+
logger.log(`[serve] mount link reconcile failed: ${err?.message || err}`);
|
|
23602
|
+
}
|
|
23603
|
+
}).catch((err) => logger.error(`[serve] mount restore failed: ${err?.message || err}`));
|
|
22734
23604
|
const daemonOwnerEmail = parseJwtEmail(process.env.HYPHA_TOKEN || "") || null;
|
|
22735
23605
|
serveManager$1.setSessionResolver((sessionId) => {
|
|
22736
23606
|
for (const [, session] of pidToTrackedSession) {
|
|
@@ -22750,7 +23620,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
22750
23620
|
}
|
|
22751
23621
|
return null;
|
|
22752
23622
|
});
|
|
22753
|
-
const sessionFilesToken = createHash
|
|
23623
|
+
const sessionFilesToken = createHash("sha256").update(`session-files:${machineId}`).digest("hex").slice(0, 20);
|
|
22754
23624
|
serveManager$1.ensureSessionFilesGateway(sessionFilesToken).then((base) => logger.log(`[serve] session-files gateway: ${base || "(tunnel pending)"}`)).catch((err) => logger.error(`[serve] session-files gateway failed: ${err?.message || err}`));
|
|
22755
23625
|
const persistedSessions = loadPersistedSessions();
|
|
22756
23626
|
try {
|
|
@@ -22790,7 +23660,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
22790
23660
|
}
|
|
22791
23661
|
if (persistedSessions.length > 0) {
|
|
22792
23662
|
try {
|
|
22793
|
-
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-
|
|
23663
|
+
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-dbXgsGNK.mjs');
|
|
22794
23664
|
await awaitClaudeVersionReady();
|
|
22795
23665
|
} catch {
|
|
22796
23666
|
}
|
|
@@ -23035,7 +23905,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
23035
23905
|
const PING_TIMEOUT_MS = 15e3;
|
|
23036
23906
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
23037
23907
|
const RECONNECT_JITTER_MS = 2500;
|
|
23038
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
23908
|
+
const { WorkflowScheduler } = await import('./scheduler-b-ysyKIj.mjs');
|
|
23039
23909
|
const workflowProjectRoots = () => {
|
|
23040
23910
|
const dirs = /* @__PURE__ */ new Set();
|
|
23041
23911
|
for (const s of pidToTrackedSession.values()) {
|
|
@@ -23669,4 +24539,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
23669
24539
|
writeStopMarker: writeStopMarker
|
|
23670
24540
|
});
|
|
23671
24541
|
|
|
23672
|
-
export {
|
|
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, clearStopMarker as a1, stopMarkerExists as a2, formatHandle as a3, normalizeAllowedUser as a4, loadSecurityContextConfig as a5, resolveSecurityContext as a6, buildSecurityContextFromFlags as a7, mergeSecurityContexts as a8, buildSessionShareUrl as a9, kimiSetup as aA, api as aB, run as aC, computeOutboundHop as aa, registerAwaitingReply as ab, buildMachineShareUrl as ac, parseHandle as ad, handleMatchesMetadata as ae, PINNED_CODEX_VERSION 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 };
|