svamp-cli 0.2.263 → 0.2.265
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agentCommands-DWshkZ3Z.mjs → agentCommands-CKd1jXfX.mjs} +5 -5
- package/dist/{auth-rJ0N4qyZ.mjs → auth-Ctsyrv9N.mjs} +2 -2
- package/dist/cli-DtMJLEig.mjs +2460 -0
- package/dist/cli.mjs +4 -2439
- package/dist/{commands-DfQA4ZX7.mjs → commands-BI7IDVIE.mjs} +5 -3
- package/dist/{commands-aGQUafx6.mjs → commands-BSBxY9iQ.mjs} +2 -2
- package/dist/{commands-f9G9DwC4.mjs → commands-BUhsW3bf.mjs} +2 -2
- package/dist/{commands-B_N0vONs.mjs → commands-Bx4WYv3I.mjs} +6 -6
- package/dist/{commands-BrjfwC7G.mjs → commands-CMZS3jBt.mjs} +2 -2
- package/dist/{commands-Dni5WrqV.mjs → commands-C_74tXoO.mjs} +1 -1
- package/dist/{commands-BsQqZG9I.mjs → commands-De9crCdY.mjs} +2 -2
- package/dist/{fleet-Bjg6QRMP.mjs → fleet-D3ycNACq.mjs} +1 -1
- package/dist/{frpc-YKxUZoMA.mjs → frpc-Cwn_s0Fs.mjs} +2 -2
- package/dist/{headlessCli-DfKlYX0C.mjs → headlessCli-nWnqozcZ.mjs} +3 -3
- package/dist/index.mjs +2 -2
- package/dist/package-BHHMByEa.mjs +64 -0
- package/dist/{rpc-DrA4F31T.mjs → rpc-BnHMvDxQ.mjs} +2 -2
- package/dist/{rpc-B4zSdme1.mjs → rpc-DwlZTOtK.mjs} +2 -2
- package/dist/{run-btCn5o6B.mjs → run-DW-5GWjY.mjs} +1 -1
- package/dist/{run-DAOLRpvC.mjs → run-b-b_szy6.mjs} +797 -342
- package/dist/{scheduler-DfbBMHWm.mjs → scheduler-BxijQDI8.mjs} +2 -2
- package/dist/{serveCommands-YmniTKX9.mjs → serveCommands-CL6B6kEf.mjs} +5 -5
- package/dist/{serveManager-DVDWrx2q.mjs → serveManager-Dy1ZZNaY.mjs} +3 -3
- package/dist/{sideband-DPLjT788.mjs → sideband-DywLL2xs.mjs} +2 -2
- package/package.json +2 -2
- package/dist/package-Cl0NCpCL.mjs +0 -64
|
@@ -5,13 +5,13 @@ import path__default, { join as join$1, resolve as resolve$1, dirname as dirname
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { execFile, spawn as spawn$1, execSync as execSync$1, exec as exec$1 } from 'child_process';
|
|
7
7
|
import { randomUUID as randomUUID$1 } from 'crypto';
|
|
8
|
-
import { randomBytes, createHash
|
|
9
|
-
import { existsSync, readFileSync,
|
|
8
|
+
import { randomUUID, randomBytes, createHash } from 'node:crypto';
|
|
9
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, chmodSync, realpathSync, readdirSync, statSync, renameSync, rmSync, appendFileSync, unlinkSync } from 'node:fs';
|
|
10
10
|
import { exec, execSync, spawn, execFile as execFile$1, execFileSync } from 'node:child_process';
|
|
11
11
|
import { promisify } from 'util';
|
|
12
|
-
import { extname, resolve, join, sep, basename, dirname } from 'node:path';
|
|
13
|
-
import { EventEmitter } from 'node:events';
|
|
14
12
|
import os, { homedir, platform } from 'node:os';
|
|
13
|
+
import { join, extname, resolve, sep, basename, dirname } from 'node:path';
|
|
14
|
+
import { EventEmitter } from 'node:events';
|
|
15
15
|
import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
|
|
16
16
|
import { createInterface } from 'node:readline';
|
|
17
17
|
import { mkdir, rm, chmod, access, mkdtemp, copyFile, writeFile, readdir, stat, readFile as readFile$1 } from 'node:fs/promises';
|
|
@@ -253,6 +253,34 @@ function mergeSecurityContexts(base, override) {
|
|
|
253
253
|
merged.allowedDirectories = mergeArrays(base.allowedDirectories, override.allowedDirectories);
|
|
254
254
|
return merged;
|
|
255
255
|
}
|
|
256
|
+
function clampSecurityContextToCeiling(ceiling, requested) {
|
|
257
|
+
if (!ceiling) return void 0;
|
|
258
|
+
if (!requested) return ceiling;
|
|
259
|
+
const merged = {};
|
|
260
|
+
merged.role = ceiling.role;
|
|
261
|
+
merged.denyAllNetwork = (ceiling.denyAllNetwork ?? false) || (requested.denyAllNetwork ?? false);
|
|
262
|
+
const cf = ceiling.filesystem;
|
|
263
|
+
const rf = requested.filesystem;
|
|
264
|
+
if (cf || rf) {
|
|
265
|
+
merged.filesystem = {
|
|
266
|
+
// Grants: ceiling only (caller cannot widen writable paths).
|
|
267
|
+
allowWrite: cf?.allowWrite ? dedup(cf.allowWrite) : void 0,
|
|
268
|
+
// Denials: union (caller may add more).
|
|
269
|
+
denyRead: mergeArrays(cf?.denyRead, rf?.denyRead),
|
|
270
|
+
denyWrite: mergeArrays(cf?.denyWrite, rf?.denyWrite)
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const cn = ceiling.network;
|
|
274
|
+
const rn = requested.network;
|
|
275
|
+
if (cn || rn) {
|
|
276
|
+
merged.network = {
|
|
277
|
+
allowedDomains: cn?.allowedDomains ? dedup(cn.allowedDomains) : void 0,
|
|
278
|
+
deniedDomains: mergeArrays(cn?.deniedDomains, rn?.deniedDomains)
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
merged.allowedDirectories = ceiling.allowedDirectories ? dedup(ceiling.allowedDirectories) : void 0;
|
|
282
|
+
return merged;
|
|
283
|
+
}
|
|
256
284
|
function resolveSecurityContext(config, userEmail) {
|
|
257
285
|
if (!config) return void 0;
|
|
258
286
|
const defaultCtx = config.default;
|
|
@@ -1190,6 +1218,547 @@ async function killDescendant(rootPid, targetPid, signal = "SIGTERM") {
|
|
|
1190
1218
|
}
|
|
1191
1219
|
}
|
|
1192
1220
|
|
|
1221
|
+
const EXAMPLE_HYPHA_PROXY_URL = "https://proxy.hypha.aicell.io";
|
|
1222
|
+
const MODE_KEY = "SVAMP_CLAUDE_PROXY";
|
|
1223
|
+
const HYPHA_PROXY_URL_KEY = "SVAMP_HYPHA_PROXY_URL";
|
|
1224
|
+
const MANAGED_KEYS = /* @__PURE__ */ new Set([
|
|
1225
|
+
MODE_KEY,
|
|
1226
|
+
HYPHA_PROXY_URL_KEY,
|
|
1227
|
+
"ANTHROPIC_BASE_URL",
|
|
1228
|
+
"ANTHROPIC_API_KEY"
|
|
1229
|
+
]);
|
|
1230
|
+
function normalizeProxyUrl(raw) {
|
|
1231
|
+
const trimmed = raw.trim().replace(/\/+$/, "");
|
|
1232
|
+
if (trimmed.endsWith("/v1")) {
|
|
1233
|
+
throw new Error(
|
|
1234
|
+
"Hypha proxy URL must not end in /v1 \u2014 the SDK appends it, producing double /v1/v1/messages"
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
return trimmed;
|
|
1238
|
+
}
|
|
1239
|
+
function resolveHyphaProxyUrl() {
|
|
1240
|
+
const configured = (process.env[HYPHA_PROXY_URL_KEY] || "").trim();
|
|
1241
|
+
if (!configured) return void 0;
|
|
1242
|
+
try {
|
|
1243
|
+
return normalizeProxyUrl(configured);
|
|
1244
|
+
} catch {
|
|
1245
|
+
return void 0;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
function envFilePath() {
|
|
1249
|
+
const svampHome = process.env.SVAMP_HOME || join(homedir(), ".svamp");
|
|
1250
|
+
return join(svampHome, ".env");
|
|
1251
|
+
}
|
|
1252
|
+
function readEnvLines() {
|
|
1253
|
+
const file = envFilePath();
|
|
1254
|
+
if (!existsSync(file)) return [];
|
|
1255
|
+
return readFileSync(file, "utf-8").split("\n");
|
|
1256
|
+
}
|
|
1257
|
+
function writeEnvLines(lines) {
|
|
1258
|
+
const file = envFilePath();
|
|
1259
|
+
const dir = join(file, "..");
|
|
1260
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1261
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
|
|
1262
|
+
lines.pop();
|
|
1263
|
+
}
|
|
1264
|
+
writeFileSync(file, lines.join("\n") + "\n", "utf-8");
|
|
1265
|
+
}
|
|
1266
|
+
function updateEnvFile(updates) {
|
|
1267
|
+
const lines = readEnvLines();
|
|
1268
|
+
const kept = [];
|
|
1269
|
+
const remainingUpdates = new Map(Object.entries(updates));
|
|
1270
|
+
for (const line of lines) {
|
|
1271
|
+
const trimmed = line.trim();
|
|
1272
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
1273
|
+
kept.push(line);
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
const eq = trimmed.indexOf("=");
|
|
1277
|
+
const key = eq === -1 ? trimmed : trimmed.slice(0, eq);
|
|
1278
|
+
if (remainingUpdates.has(key)) {
|
|
1279
|
+
const v = remainingUpdates.get(key);
|
|
1280
|
+
remainingUpdates.delete(key);
|
|
1281
|
+
if (v === void 0) continue;
|
|
1282
|
+
kept.push(`${key}=${v}`);
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
kept.push(line);
|
|
1286
|
+
}
|
|
1287
|
+
for (const [key, v] of remainingUpdates) {
|
|
1288
|
+
if (v === void 0) continue;
|
|
1289
|
+
kept.push(`${key}=${v}`);
|
|
1290
|
+
}
|
|
1291
|
+
writeEnvLines(kept);
|
|
1292
|
+
}
|
|
1293
|
+
function currentMode() {
|
|
1294
|
+
const raw = (process.env[MODE_KEY] || "").trim().toLowerCase();
|
|
1295
|
+
if (raw === "hypha" || raw === "hypha-proxy") return "hypha";
|
|
1296
|
+
if (raw === "custom") return "custom";
|
|
1297
|
+
return "login";
|
|
1298
|
+
}
|
|
1299
|
+
function redactKey(key) {
|
|
1300
|
+
if (!key) return void 0;
|
|
1301
|
+
if (key.length <= 12) return "***";
|
|
1302
|
+
return `${key.slice(0, 8)}\u2026${key.slice(-4)}`;
|
|
1303
|
+
}
|
|
1304
|
+
function getClaudeAuthStatus() {
|
|
1305
|
+
const mode = currentMode();
|
|
1306
|
+
if (mode === "hypha") {
|
|
1307
|
+
const token = process.env.HYPHA_TOKEN;
|
|
1308
|
+
const proxyUrl = resolveHyphaProxyUrl();
|
|
1309
|
+
return {
|
|
1310
|
+
mode,
|
|
1311
|
+
baseUrl: proxyUrl,
|
|
1312
|
+
apiKeyPreview: redactKey(token),
|
|
1313
|
+
hyphaTokenMissing: !token,
|
|
1314
|
+
hyphaProxyUrlMissing: !proxyUrl
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
if (mode === "custom") {
|
|
1318
|
+
return {
|
|
1319
|
+
mode,
|
|
1320
|
+
baseUrl: process.env.ANTHROPIC_BASE_URL,
|
|
1321
|
+
apiKeyPreview: redactKey(process.env.ANTHROPIC_API_KEY)
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
return { mode: "login" };
|
|
1325
|
+
}
|
|
1326
|
+
function setClaudeAuthHyphaProxy(url) {
|
|
1327
|
+
if (!url || !url.trim()) {
|
|
1328
|
+
throw new Error(
|
|
1329
|
+
`A Hypha proxy URL is required (no default). Example: svamp daemon auth use-hypha-proxy ${EXAMPLE_HYPHA_PROXY_URL}`
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
updateEnvFile({
|
|
1333
|
+
[MODE_KEY]: "hypha",
|
|
1334
|
+
[HYPHA_PROXY_URL_KEY]: normalizeProxyUrl(url),
|
|
1335
|
+
// Hypha mode resolves token live at spawn — no stored copy.
|
|
1336
|
+
ANTHROPIC_BASE_URL: void 0,
|
|
1337
|
+
ANTHROPIC_API_KEY: void 0
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
function setClaudeAuthLogin() {
|
|
1341
|
+
updateEnvFile({
|
|
1342
|
+
[MODE_KEY]: void 0,
|
|
1343
|
+
[HYPHA_PROXY_URL_KEY]: void 0,
|
|
1344
|
+
ANTHROPIC_BASE_URL: void 0,
|
|
1345
|
+
ANTHROPIC_API_KEY: void 0
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
function setClaudeAuthCustom(baseUrl, apiKey) {
|
|
1349
|
+
if (!baseUrl) throw new Error("ANTHROPIC_BASE_URL is required for custom mode");
|
|
1350
|
+
if (!apiKey) throw new Error("ANTHROPIC_API_KEY is required for custom mode");
|
|
1351
|
+
const trimmed = baseUrl.replace(/\/+$/, "");
|
|
1352
|
+
if (trimmed.endsWith("/v1")) {
|
|
1353
|
+
throw new Error(
|
|
1354
|
+
"ANTHROPIC_BASE_URL must not end in /v1 \u2014 the SDK appends it, producing double /v1/v1/messages"
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
updateEnvFile({
|
|
1358
|
+
[MODE_KEY]: "custom",
|
|
1359
|
+
[HYPHA_PROXY_URL_KEY]: void 0,
|
|
1360
|
+
ANTHROPIC_BASE_URL: trimmed,
|
|
1361
|
+
ANTHROPIC_API_KEY: apiKey
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
function applyClaudeProxyEnv(spawnEnv) {
|
|
1365
|
+
const mode = currentMode();
|
|
1366
|
+
if (mode === "hypha") {
|
|
1367
|
+
const proxyUrl = resolveHyphaProxyUrl();
|
|
1368
|
+
if (!proxyUrl) {
|
|
1369
|
+
delete spawnEnv.ANTHROPIC_BASE_URL;
|
|
1370
|
+
delete spawnEnv.ANTHROPIC_API_KEY;
|
|
1371
|
+
return `hypha mode but ${HYPHA_PROXY_URL_KEY} not configured \u2014 run \`svamp daemon auth use-hypha-proxy <URL>\`; falling back to login`;
|
|
1372
|
+
}
|
|
1373
|
+
const token = process.env.HYPHA_TOKEN;
|
|
1374
|
+
if (!token) {
|
|
1375
|
+
delete spawnEnv.ANTHROPIC_BASE_URL;
|
|
1376
|
+
delete spawnEnv.ANTHROPIC_API_KEY;
|
|
1377
|
+
return "hypha mode but HYPHA_TOKEN missing \u2014 falling back to login";
|
|
1378
|
+
}
|
|
1379
|
+
spawnEnv.ANTHROPIC_BASE_URL = proxyUrl;
|
|
1380
|
+
spawnEnv.ANTHROPIC_API_KEY = token;
|
|
1381
|
+
const cacheOverride = spawnEnv.ENABLE_PROMPT_CACHING_1H ?? process.env.ENABLE_PROMPT_CACHING_1H ?? spawnEnv.FORCE_PROMPT_CACHING_5M ?? process.env.FORCE_PROMPT_CACHING_5M;
|
|
1382
|
+
if (cacheOverride === void 0) spawnEnv.ENABLE_PROMPT_CACHING_1H = "1";
|
|
1383
|
+
return `hypha proxy (${proxyUrl}, 1h cache TTL)`;
|
|
1384
|
+
}
|
|
1385
|
+
if (mode === "custom") {
|
|
1386
|
+
const url = process.env.ANTHROPIC_BASE_URL;
|
|
1387
|
+
const key = process.env.ANTHROPIC_API_KEY;
|
|
1388
|
+
if (!url || !key) {
|
|
1389
|
+
delete spawnEnv.ANTHROPIC_BASE_URL;
|
|
1390
|
+
delete spawnEnv.ANTHROPIC_API_KEY;
|
|
1391
|
+
return "custom mode but ANTHROPIC_BASE_URL/API_KEY missing \u2014 falling back to login";
|
|
1392
|
+
}
|
|
1393
|
+
spawnEnv.ANTHROPIC_BASE_URL = url;
|
|
1394
|
+
spawnEnv.ANTHROPIC_API_KEY = key;
|
|
1395
|
+
return `custom proxy (${url})`;
|
|
1396
|
+
}
|
|
1397
|
+
delete spawnEnv.ANTHROPIC_BASE_URL;
|
|
1398
|
+
delete spawnEnv.ANTHROPIC_API_KEY;
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
const MANAGED_ENV_KEYS = Array.from(MANAGED_KEYS);
|
|
1402
|
+
|
|
1403
|
+
var claudeAuth = /*#__PURE__*/Object.freeze({
|
|
1404
|
+
__proto__: null,
|
|
1405
|
+
EXAMPLE_HYPHA_PROXY_URL: EXAMPLE_HYPHA_PROXY_URL,
|
|
1406
|
+
MANAGED_ENV_KEYS: MANAGED_ENV_KEYS,
|
|
1407
|
+
applyClaudeProxyEnv: applyClaudeProxyEnv,
|
|
1408
|
+
getClaudeAuthStatus: getClaudeAuthStatus,
|
|
1409
|
+
resolveHyphaProxyUrl: resolveHyphaProxyUrl,
|
|
1410
|
+
setClaudeAuthCustom: setClaudeAuthCustom,
|
|
1411
|
+
setClaudeAuthHyphaProxy: setClaudeAuthHyphaProxy,
|
|
1412
|
+
setClaudeAuthLogin: setClaudeAuthLogin,
|
|
1413
|
+
updateEnvFile: updateEnvFile
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
function codexPermissionSettings(mode) {
|
|
1417
|
+
switch (mode) {
|
|
1418
|
+
case "yolo":
|
|
1419
|
+
case "bypassPermissions":
|
|
1420
|
+
case "danger-full-access":
|
|
1421
|
+
return { approvalPolicy: "never", sandbox: "danger-full-access" };
|
|
1422
|
+
case "safe-yolo":
|
|
1423
|
+
case "acceptEdits":
|
|
1424
|
+
return { approvalPolicy: "on-failure", sandbox: "workspace-write" };
|
|
1425
|
+
case "read-only":
|
|
1426
|
+
case "plan":
|
|
1427
|
+
return { approvalPolicy: "on-request", sandbox: "read-only" };
|
|
1428
|
+
case "default":
|
|
1429
|
+
default:
|
|
1430
|
+
return { approvalPolicy: "on-request", sandbox: "workspace-write" };
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
const CODEX_PROVIDER_KEY_ENV = "SVAMP_CODEX_API_KEY";
|
|
1434
|
+
const PROVIDER_NAME = "svamp";
|
|
1435
|
+
function resolveCodexProvider(env = process.env) {
|
|
1436
|
+
return {
|
|
1437
|
+
apiBase: env.SVAMP_CODEX_API_BASE || env.LLM_API_BASE || void 0,
|
|
1438
|
+
apiKey: env.SVAMP_CODEX_API_KEY || env.LLM_API_KEY || void 0,
|
|
1439
|
+
model: env.SVAMP_CODEX_MODEL || env.LLM_MODEL || void 0
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
function hasCustomProvider(cfg) {
|
|
1443
|
+
return !!(cfg.apiBase && cfg.apiKey);
|
|
1444
|
+
}
|
|
1445
|
+
function buildCodexProviderArgs(cfg) {
|
|
1446
|
+
if (!hasCustomProvider(cfg)) {
|
|
1447
|
+
return { configArgs: cfg.model ? ["-c", `model=${JSON.stringify(cfg.model)}`] : [], env: {}, model: cfg.model };
|
|
1448
|
+
}
|
|
1449
|
+
const configArgs = [
|
|
1450
|
+
"-c",
|
|
1451
|
+
`model_provider=${JSON.stringify(PROVIDER_NAME)}`,
|
|
1452
|
+
"-c",
|
|
1453
|
+
`model_providers.${PROVIDER_NAME}.name=${JSON.stringify(PROVIDER_NAME)}`,
|
|
1454
|
+
"-c",
|
|
1455
|
+
`model_providers.${PROVIDER_NAME}.base_url=${JSON.stringify(cfg.apiBase)}`,
|
|
1456
|
+
"-c",
|
|
1457
|
+
`model_providers.${PROVIDER_NAME}.env_key=${JSON.stringify(CODEX_PROVIDER_KEY_ENV)}`,
|
|
1458
|
+
"-c",
|
|
1459
|
+
`model_providers.${PROVIDER_NAME}.wire_api=${JSON.stringify("responses")}`
|
|
1460
|
+
];
|
|
1461
|
+
if (cfg.model) configArgs.push("-c", `model=${JSON.stringify(cfg.model)}`);
|
|
1462
|
+
return { configArgs, env: { [CODEX_PROVIDER_KEY_ENV]: cfg.apiKey }, model: cfg.model };
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
var codexProvider = /*#__PURE__*/Object.freeze({
|
|
1466
|
+
__proto__: null,
|
|
1467
|
+
CODEX_PROVIDER_KEY_ENV: CODEX_PROVIDER_KEY_ENV,
|
|
1468
|
+
buildCodexProviderArgs: buildCodexProviderArgs,
|
|
1469
|
+
codexPermissionSettings: codexPermissionSettings,
|
|
1470
|
+
hasCustomProvider: hasCustomProvider,
|
|
1471
|
+
resolveCodexProvider: resolveCodexProvider
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
const METHODS_BY_VENDOR = {
|
|
1475
|
+
anthropic: ["anthropic-api-key", "claude-oauth-token", "hypha-proxy", "anthropic-gateway"],
|
|
1476
|
+
openai: ["openai-api-key", "chatgpt-oauth", "openai-gateway"]
|
|
1477
|
+
};
|
|
1478
|
+
function accountsFilePath(svampHome) {
|
|
1479
|
+
const home = svampHome || process.env.SVAMP_HOME || join(homedir(), ".svamp");
|
|
1480
|
+
return join(home, "accounts.json");
|
|
1481
|
+
}
|
|
1482
|
+
function accountCodexHomeDir(accountId, svampHome) {
|
|
1483
|
+
const home = svampHome || process.env.SVAMP_HOME || join(homedir(), ".svamp");
|
|
1484
|
+
return join(home, "codex-accounts", accountId);
|
|
1485
|
+
}
|
|
1486
|
+
function loadAccounts(svampHome) {
|
|
1487
|
+
const file = accountsFilePath(svampHome);
|
|
1488
|
+
if (!existsSync(file)) return [];
|
|
1489
|
+
try {
|
|
1490
|
+
const parsed = JSON.parse(readFileSync(file, "utf-8"));
|
|
1491
|
+
if (!parsed || !Array.isArray(parsed.accounts)) return [];
|
|
1492
|
+
return parsed.accounts;
|
|
1493
|
+
} catch {
|
|
1494
|
+
return [];
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
function saveAccounts(accounts, svampHome) {
|
|
1498
|
+
const file = accountsFilePath(svampHome);
|
|
1499
|
+
const dir = join(file, "..");
|
|
1500
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1501
|
+
const payload = { version: 1, accounts };
|
|
1502
|
+
writeFileSync(file, JSON.stringify(payload, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
1503
|
+
try {
|
|
1504
|
+
chmodSync(file, 384);
|
|
1505
|
+
} catch {
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
function previewSecret(secret) {
|
|
1509
|
+
if (!secret) return void 0;
|
|
1510
|
+
if (secret.length <= 8) return "\u2022\u2022\u2022\u2022";
|
|
1511
|
+
return `${secret.slice(0, 4)}\u2026${secret.slice(-4)}`;
|
|
1512
|
+
}
|
|
1513
|
+
function redactAccount(a) {
|
|
1514
|
+
const { secret, ...rest } = a;
|
|
1515
|
+
return { ...rest, secretPreview: previewSecret(secret) };
|
|
1516
|
+
}
|
|
1517
|
+
function redactAccounts(accounts) {
|
|
1518
|
+
return accounts.map(redactAccount);
|
|
1519
|
+
}
|
|
1520
|
+
function defaultAccountIds(accounts) {
|
|
1521
|
+
const out = { anthropic: null, openai: null };
|
|
1522
|
+
for (const a of accounts) {
|
|
1523
|
+
if (a.isDefault) out[a.vendor] = a.id;
|
|
1524
|
+
}
|
|
1525
|
+
return out;
|
|
1526
|
+
}
|
|
1527
|
+
function validateAccountInput(input) {
|
|
1528
|
+
const vendor = input.vendor;
|
|
1529
|
+
const method = input.method;
|
|
1530
|
+
if (vendor !== "anthropic" && vendor !== "openai") {
|
|
1531
|
+
throw new Error(`Invalid account vendor: ${String(vendor)}`);
|
|
1532
|
+
}
|
|
1533
|
+
if (!method || !METHODS_BY_VENDOR[vendor].includes(method)) {
|
|
1534
|
+
throw new Error(`Invalid method "${String(method)}" for vendor "${vendor}"`);
|
|
1535
|
+
}
|
|
1536
|
+
if (!input.label || !input.label.trim()) {
|
|
1537
|
+
throw new Error("Account label is required");
|
|
1538
|
+
}
|
|
1539
|
+
const needsSecret = method !== "hypha-proxy";
|
|
1540
|
+
if (needsSecret && !input.secret && !input.id) {
|
|
1541
|
+
throw new Error(`A credential is required for method "${method}"`);
|
|
1542
|
+
}
|
|
1543
|
+
if ((method === "anthropic-gateway" || method === "openai-gateway") && !input.baseUrl) {
|
|
1544
|
+
throw new Error(`A base URL is required for a custom gateway`);
|
|
1545
|
+
}
|
|
1546
|
+
return { vendor, method };
|
|
1547
|
+
}
|
|
1548
|
+
function upsertAccount(input, svampHome) {
|
|
1549
|
+
const { vendor, method } = validateAccountInput(input);
|
|
1550
|
+
const accounts = loadAccounts(svampHome);
|
|
1551
|
+
const now = Date.now();
|
|
1552
|
+
const existing = input.id ? accounts.find((a) => a.id === input.id) : void 0;
|
|
1553
|
+
const account = {
|
|
1554
|
+
id: existing?.id || input.id || randomUUID(),
|
|
1555
|
+
label: input.label.trim(),
|
|
1556
|
+
vendor,
|
|
1557
|
+
method,
|
|
1558
|
+
// Keep the old secret if the update left it blank (edit label/model without re-entry).
|
|
1559
|
+
secret: input.secret && input.secret.length > 0 ? input.secret : existing?.secret,
|
|
1560
|
+
baseUrl: input.baseUrl?.trim() || void 0,
|
|
1561
|
+
model: input.model?.trim() || void 0,
|
|
1562
|
+
isDefault: input.isDefault ?? existing?.isDefault ?? false,
|
|
1563
|
+
createdBy: existing?.createdBy || input.createdBy,
|
|
1564
|
+
createdAt: existing?.createdAt || now,
|
|
1565
|
+
updatedAt: now
|
|
1566
|
+
};
|
|
1567
|
+
let next = accounts.filter((a) => a.id !== account.id);
|
|
1568
|
+
if (account.isDefault) {
|
|
1569
|
+
next = next.map((a) => a.vendor === vendor ? { ...a, isDefault: false } : a);
|
|
1570
|
+
}
|
|
1571
|
+
if (!next.some((a) => a.vendor === vendor && a.isDefault) && !account.isDefault) {
|
|
1572
|
+
account.isDefault = true;
|
|
1573
|
+
}
|
|
1574
|
+
next.push(account);
|
|
1575
|
+
saveAccounts(next, svampHome);
|
|
1576
|
+
return account;
|
|
1577
|
+
}
|
|
1578
|
+
function deleteAccount(id, svampHome) {
|
|
1579
|
+
const accounts = loadAccounts(svampHome);
|
|
1580
|
+
const removed = accounts.find((a) => a.id === id);
|
|
1581
|
+
let next = accounts.filter((a) => a.id !== id);
|
|
1582
|
+
if (removed?.isDefault) {
|
|
1583
|
+
const idx = next.findIndex((a) => a.vendor === removed.vendor);
|
|
1584
|
+
if (idx >= 0) next[idx] = { ...next[idx], isDefault: true };
|
|
1585
|
+
}
|
|
1586
|
+
saveAccounts(next, svampHome);
|
|
1587
|
+
}
|
|
1588
|
+
function setDefaultAccount(vendor, id, svampHome) {
|
|
1589
|
+
const accounts = loadAccounts(svampHome);
|
|
1590
|
+
if (!accounts.some((a) => a.id === id && a.vendor === vendor)) {
|
|
1591
|
+
throw new Error(`No ${vendor} account with id ${id}`);
|
|
1592
|
+
}
|
|
1593
|
+
const next = accounts.map(
|
|
1594
|
+
(a) => a.vendor === vendor ? { ...a, isDefault: a.id === id, updatedAt: Date.now() } : a
|
|
1595
|
+
);
|
|
1596
|
+
saveAccounts(next, svampHome);
|
|
1597
|
+
}
|
|
1598
|
+
function getAccount(id, svampHome) {
|
|
1599
|
+
return loadAccounts(svampHome).find((a) => a.id === id);
|
|
1600
|
+
}
|
|
1601
|
+
function stripV1(url) {
|
|
1602
|
+
const trimmed = url.trim().replace(/\/+$/, "");
|
|
1603
|
+
if (trimmed.endsWith("/v1")) {
|
|
1604
|
+
throw new Error("Base URL must not end in /v1 \u2014 the SDK appends it (double /v1/v1)");
|
|
1605
|
+
}
|
|
1606
|
+
return trimmed;
|
|
1607
|
+
}
|
|
1608
|
+
function resolveAccountEnv(account, env = process.env, svampHome) {
|
|
1609
|
+
switch (account.method) {
|
|
1610
|
+
case "anthropic-api-key":
|
|
1611
|
+
return {
|
|
1612
|
+
vendor: "anthropic",
|
|
1613
|
+
claudeEnv: {
|
|
1614
|
+
ANTHROPIC_API_KEY: account.secret,
|
|
1615
|
+
ANTHROPIC_BASE_URL: account.baseUrl ? stripV1(account.baseUrl) : void 0,
|
|
1616
|
+
CLAUDE_CODE_OAUTH_TOKEN: void 0
|
|
1617
|
+
},
|
|
1618
|
+
describe: `anthropic API key (${account.label})`
|
|
1619
|
+
};
|
|
1620
|
+
case "anthropic-gateway": {
|
|
1621
|
+
const url = stripV1(account.baseUrl || "");
|
|
1622
|
+
return {
|
|
1623
|
+
vendor: "anthropic",
|
|
1624
|
+
claudeEnv: {
|
|
1625
|
+
ANTHROPIC_BASE_URL: url,
|
|
1626
|
+
ANTHROPIC_API_KEY: account.secret,
|
|
1627
|
+
CLAUDE_CODE_OAUTH_TOKEN: void 0
|
|
1628
|
+
},
|
|
1629
|
+
describe: `anthropic gateway ${url} (${account.label})`
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
case "hypha-proxy": {
|
|
1633
|
+
const url = account.baseUrl ? stripV1(account.baseUrl) : resolveHyphaProxyUrl();
|
|
1634
|
+
const token = env.HYPHA_TOKEN;
|
|
1635
|
+
if (!url) throw new Error("hypha-proxy account has no URL (set SVAMP_HYPHA_PROXY_URL or an account base URL)");
|
|
1636
|
+
if (!token) throw new Error("hypha-proxy account requires HYPHA_TOKEN on the machine");
|
|
1637
|
+
return {
|
|
1638
|
+
vendor: "anthropic",
|
|
1639
|
+
claudeEnv: {
|
|
1640
|
+
ANTHROPIC_BASE_URL: url,
|
|
1641
|
+
ANTHROPIC_API_KEY: token,
|
|
1642
|
+
CLAUDE_CODE_OAUTH_TOKEN: void 0,
|
|
1643
|
+
// #0203: subscription backend qualifies for the free 1h prompt-cache TTL.
|
|
1644
|
+
ENABLE_PROMPT_CACHING_1H: env.FORCE_PROMPT_CACHING_5M ? void 0 : "1"
|
|
1645
|
+
},
|
|
1646
|
+
describe: `hypha proxy ${url} (${account.label})`
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
case "claude-oauth-token":
|
|
1650
|
+
return {
|
|
1651
|
+
vendor: "anthropic",
|
|
1652
|
+
claudeEnv: {
|
|
1653
|
+
CLAUDE_CODE_OAUTH_TOKEN: account.secret,
|
|
1654
|
+
ANTHROPIC_BASE_URL: void 0,
|
|
1655
|
+
ANTHROPIC_API_KEY: void 0
|
|
1656
|
+
},
|
|
1657
|
+
describe: `claude subscription token (${account.label})`
|
|
1658
|
+
};
|
|
1659
|
+
case "openai-api-key":
|
|
1660
|
+
return {
|
|
1661
|
+
vendor: "openai",
|
|
1662
|
+
codexProvider: { model: account.model },
|
|
1663
|
+
extraEnv: { OPENAI_API_KEY: account.secret || "" },
|
|
1664
|
+
describe: `openai API key (${account.label})`
|
|
1665
|
+
};
|
|
1666
|
+
case "openai-gateway": {
|
|
1667
|
+
const url = stripV1(account.baseUrl || "");
|
|
1668
|
+
const provider = { apiBase: url, apiKey: account.secret, model: account.model };
|
|
1669
|
+
return {
|
|
1670
|
+
vendor: "openai",
|
|
1671
|
+
codexProvider: provider,
|
|
1672
|
+
extraEnv: { [CODEX_PROVIDER_KEY_ENV]: account.secret || "" },
|
|
1673
|
+
describe: `openai gateway ${url} (${account.label})`
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
case "chatgpt-oauth": {
|
|
1677
|
+
const codexHome = accountCodexHomeDir(account.id, svampHome);
|
|
1678
|
+
return {
|
|
1679
|
+
vendor: "openai",
|
|
1680
|
+
codexProvider: { model: account.model },
|
|
1681
|
+
codexHome,
|
|
1682
|
+
extraEnv: { CODEX_HOME: codexHome },
|
|
1683
|
+
describe: `chatgpt subscription (${account.label})`
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
default:
|
|
1687
|
+
throw new Error(`Unknown account method: ${account.method}`);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
function ensureCodexHome(account, svampHome) {
|
|
1691
|
+
const dir = accountCodexHomeDir(account.id, svampHome);
|
|
1692
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1693
|
+
const authPath = join(dir, "auth.json");
|
|
1694
|
+
const body = JSON.stringify({ OPENAI_API_KEY: null, tokens: { access_token: account.secret || "" } }, null, 2);
|
|
1695
|
+
writeFileSync(authPath, body + "\n", { encoding: "utf-8", mode: 384 });
|
|
1696
|
+
try {
|
|
1697
|
+
chmodSync(authPath, 384);
|
|
1698
|
+
} catch {
|
|
1699
|
+
}
|
|
1700
|
+
return dir;
|
|
1701
|
+
}
|
|
1702
|
+
async function probeAccount(account, env = process.env) {
|
|
1703
|
+
let resolved;
|
|
1704
|
+
try {
|
|
1705
|
+
resolved = resolveAccountEnv(account, env);
|
|
1706
|
+
} catch (e) {
|
|
1707
|
+
return { ok: false, detail: `Misconfigured: ${e?.message || e}` };
|
|
1708
|
+
}
|
|
1709
|
+
const withTimeout = async (url, init) => {
|
|
1710
|
+
const ctrl = new AbortController();
|
|
1711
|
+
const t = setTimeout(() => ctrl.abort(), 8e3);
|
|
1712
|
+
try {
|
|
1713
|
+
return await fetch(url, { ...init, signal: ctrl.signal });
|
|
1714
|
+
} finally {
|
|
1715
|
+
clearTimeout(t);
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
try {
|
|
1719
|
+
switch (account.method) {
|
|
1720
|
+
case "anthropic-api-key":
|
|
1721
|
+
case "anthropic-gateway":
|
|
1722
|
+
case "hypha-proxy": {
|
|
1723
|
+
const base = resolved.claudeEnv?.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
|
|
1724
|
+
const key = resolved.claudeEnv?.ANTHROPIC_API_KEY;
|
|
1725
|
+
if (!key) return { ok: false, detail: "No credential resolved" };
|
|
1726
|
+
const res = await withTimeout(`${base}/v1/messages`, {
|
|
1727
|
+
method: "POST",
|
|
1728
|
+
headers: {
|
|
1729
|
+
"content-type": "application/json",
|
|
1730
|
+
"x-api-key": key,
|
|
1731
|
+
"authorization": `Bearer ${key}`,
|
|
1732
|
+
"anthropic-version": "2023-06-01"
|
|
1733
|
+
},
|
|
1734
|
+
body: JSON.stringify({ model: "claude-3-5-haiku-20241022", max_tokens: 1, messages: [{ role: "user", content: "hi" }] })
|
|
1735
|
+
});
|
|
1736
|
+
if (res.status === 401 || res.status === 403) return { ok: false, detail: `Auth rejected (HTTP ${res.status})` };
|
|
1737
|
+
if (res.ok || res.status === 400 || res.status === 429) return { ok: true, detail: `Reachable (HTTP ${res.status})` };
|
|
1738
|
+
return { ok: true, detail: `Reachable, unexpected HTTP ${res.status}` };
|
|
1739
|
+
}
|
|
1740
|
+
case "openai-api-key":
|
|
1741
|
+
case "openai-gateway": {
|
|
1742
|
+
const base = account.method === "openai-gateway" ? stripV1(account.baseUrl || "") : "https://api.openai.com/v1";
|
|
1743
|
+
const key = account.secret || "";
|
|
1744
|
+
if (!key) return { ok: false, detail: "No credential resolved" };
|
|
1745
|
+
const res = await withTimeout(`${base}/models`, { headers: { authorization: `Bearer ${key}` } });
|
|
1746
|
+
if (res.status === 401 || res.status === 403) return { ok: false, detail: `Auth rejected (HTTP ${res.status})` };
|
|
1747
|
+
if (res.ok || res.status === 400 || res.status === 429) return { ok: true, detail: `Reachable (HTTP ${res.status})` };
|
|
1748
|
+
return { ok: true, detail: `Reachable, unexpected HTTP ${res.status}` };
|
|
1749
|
+
}
|
|
1750
|
+
case "claude-oauth-token":
|
|
1751
|
+
return { ok: !!account.secret, detail: account.secret ? "Token stored \u2014 verified when a session starts (Claude CLI uses CLAUDE_CODE_OAUTH_TOKEN)" : "No token stored" };
|
|
1752
|
+
case "chatgpt-oauth":
|
|
1753
|
+
return { ok: !!account.secret, detail: account.secret ? "Token stored \u2014 verified when a Codex session starts (per-account CODEX_HOME)" : "No token stored" };
|
|
1754
|
+
default:
|
|
1755
|
+
return { ok: false, detail: "Unknown method" };
|
|
1756
|
+
}
|
|
1757
|
+
} catch (e) {
|
|
1758
|
+
return { ok: false, detail: `Unreachable: ${e?.message || e}` };
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1193
1762
|
const FIELD_RANGES = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
|
|
1194
1763
|
function parseField(token, [min, max]) {
|
|
1195
1764
|
const set = /* @__PURE__ */ new Set();
|
|
@@ -2356,7 +2925,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2356
2925
|
if (machineCtx) {
|
|
2357
2926
|
options = {
|
|
2358
2927
|
...options,
|
|
2359
|
-
securityContext: mergeSecurityContexts(machineCtx, options.securityContext)
|
|
2928
|
+
securityContext: isSharedUser ? clampSecurityContextToCeiling(machineCtx, options.securityContext) : mergeSecurityContexts(machineCtx, options.securityContext)
|
|
2360
2929
|
};
|
|
2361
2930
|
}
|
|
2362
2931
|
}
|
|
@@ -2572,6 +3141,43 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2572
3141
|
});
|
|
2573
3142
|
return { success: true };
|
|
2574
3143
|
},
|
|
3144
|
+
// ── Backend Accounts (per-machine credentials; admin-gated) ──
|
|
3145
|
+
// Secrets live on the machine (~/.svamp/accounts.json, 0600) and are NEVER returned —
|
|
3146
|
+
// only redacted previews. The composer sends an opaque accountId; the daemon resolves it
|
|
3147
|
+
// to concrete auth at spawn (daemon/backendAccounts.ts). See docs/sharing-and-security.md.
|
|
3148
|
+
listBackendAccounts: async (context) => {
|
|
3149
|
+
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
3150
|
+
const accounts = loadAccounts(metadata.svampHomeDir);
|
|
3151
|
+
return {
|
|
3152
|
+
accounts: redactAccounts(accounts),
|
|
3153
|
+
defaults: defaultAccountIds(accounts)
|
|
3154
|
+
};
|
|
3155
|
+
},
|
|
3156
|
+
setBackendAccount: async (account, context) => {
|
|
3157
|
+
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
3158
|
+
const createdBy = context?.user?.email;
|
|
3159
|
+
const saved = upsertAccount({ ...account, createdBy: account.createdBy || createdBy }, metadata.svampHomeDir);
|
|
3160
|
+
notifyListeners({ type: "update-machine", machineId, backendAccountsChanged: true });
|
|
3161
|
+
return { success: true, account: redactAccount(saved) };
|
|
3162
|
+
},
|
|
3163
|
+
deleteBackendAccount: async (id, context) => {
|
|
3164
|
+
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
3165
|
+
deleteAccount(id, metadata.svampHomeDir);
|
|
3166
|
+
notifyListeners({ type: "update-machine", machineId, backendAccountsChanged: true });
|
|
3167
|
+
return { success: true };
|
|
3168
|
+
},
|
|
3169
|
+
setDefaultBackendAccount: async (vendor, id, context) => {
|
|
3170
|
+
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
3171
|
+
setDefaultAccount(vendor, id, metadata.svampHomeDir);
|
|
3172
|
+
notifyListeners({ type: "update-machine", machineId, backendAccountsChanged: true });
|
|
3173
|
+
return { success: true };
|
|
3174
|
+
},
|
|
3175
|
+
testBackendAccount: async (id, context) => {
|
|
3176
|
+
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
3177
|
+
const acct = getAccount(id, metadata.svampHomeDir);
|
|
3178
|
+
if (!acct) return { ok: false, detail: "Account not found" };
|
|
3179
|
+
return await probeAccount(acct);
|
|
3180
|
+
},
|
|
2575
3181
|
// Register a listener for real-time updates (app calls this with _rintf callback)
|
|
2576
3182
|
registerListener: async (callback, context) => {
|
|
2577
3183
|
trackInbound();
|
|
@@ -3013,7 +3619,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
3013
3619
|
const tunnels = handlers.tunnels;
|
|
3014
3620
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
3015
3621
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
3016
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
3622
|
+
const { FrpcTunnel } = await import('./frpc-Cwn_s0Fs.mjs');
|
|
3017
3623
|
const tunnel = new FrpcTunnel({
|
|
3018
3624
|
name: params.name,
|
|
3019
3625
|
ports: params.ports,
|
|
@@ -3480,7 +4086,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
3480
4086
|
}
|
|
3481
4087
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
3482
4088
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
3483
|
-
const { toolsForRole } = await import('./sideband-
|
|
4089
|
+
const { toolsForRole } = await import('./sideband-DywLL2xs.mjs');
|
|
3484
4090
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
3485
4091
|
return fmt(r2);
|
|
3486
4092
|
}
|
|
@@ -3579,7 +4185,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
3579
4185
|
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
3580
4186
|
const callId = "call_" + Math.random().toString(16).slice(2, 12);
|
|
3581
4187
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
3582
|
-
const { queryCore } = await import('./commands-
|
|
4188
|
+
const { queryCore } = await import('./commands-C_74tXoO.mjs');
|
|
3583
4189
|
const timeout = c.reply?.timeout_sec || 120;
|
|
3584
4190
|
let result;
|
|
3585
4191
|
try {
|
|
@@ -3927,290 +4533,95 @@ function classifyInbound(message, now = Date.now(), opts) {
|
|
|
3927
4533
|
return { action: "wake", reason: self ? "always-wake (self)" : "always-wake (human)", breakerTripped: tripped2 };
|
|
3928
4534
|
}
|
|
3929
4535
|
if (openThreadReply) {
|
|
3930
|
-
if (isBreakerOpenAt(now)) {
|
|
3931
|
-
return { action: "queue", reason: "open-thread reply (breaker open)" };
|
|
3932
|
-
}
|
|
3933
|
-
const tripped2 = recordWake(now);
|
|
3934
|
-
return { action: "wake", reason: "open-thread reply", breakerTripped: tripped2 };
|
|
3935
|
-
}
|
|
3936
|
-
if (message.urgency !== "urgent") {
|
|
3937
|
-
return { action: "queue", reason: "non-urgent" };
|
|
3938
|
-
}
|
|
3939
|
-
if (isBreakerOpenAt(now)) {
|
|
3940
|
-
return { action: "queue", reason: "circuit-breaker-open" };
|
|
3941
|
-
}
|
|
3942
|
-
if (!takeUrgentToken(senderKey(message), now)) {
|
|
3943
|
-
return { action: "queue", reason: "rate-limited" };
|
|
3944
|
-
}
|
|
3945
|
-
const tripped = recordWake(now);
|
|
3946
|
-
return { action: "wake", reason: "urgent", breakerTripped: tripped };
|
|
3947
|
-
}
|
|
3948
|
-
function applyInboxClear(inbox, opts) {
|
|
3949
|
-
let kept;
|
|
3950
|
-
if (opts?.messageId) kept = inbox.filter((m) => m.messageId !== opts.messageId);
|
|
3951
|
-
else if (opts?.all) kept = [];
|
|
3952
|
-
else kept = inbox.filter((m) => !m.read);
|
|
3953
|
-
return { kept, removed: inbox.length - kept.length };
|
|
3954
|
-
}
|
|
3955
|
-
|
|
3956
|
-
function inboxFilePath(projectDir, sessionId) {
|
|
3957
|
-
return join(projectDir, ".svamp", sessionId, "inbox.json");
|
|
3958
|
-
}
|
|
3959
|
-
function loadInbox(projectDir, sessionId) {
|
|
3960
|
-
try {
|
|
3961
|
-
const p = inboxFilePath(projectDir, sessionId);
|
|
3962
|
-
if (!existsSync(p)) return [];
|
|
3963
|
-
const data = JSON.parse(readFileSync(p, "utf8"));
|
|
3964
|
-
if (Array.isArray(data)) return data;
|
|
3965
|
-
if (data && Array.isArray(data.messages)) return data.messages;
|
|
3966
|
-
return [];
|
|
3967
|
-
} catch {
|
|
3968
|
-
return [];
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
|
-
function saveInbox(projectDir, sessionId, inbox) {
|
|
3972
|
-
try {
|
|
3973
|
-
const p = inboxFilePath(projectDir, sessionId);
|
|
3974
|
-
mkdirSync(dirname(p), { recursive: true });
|
|
3975
|
-
const tmp = `${p}.tmp-${process.pid}`;
|
|
3976
|
-
writeFileSync(tmp, JSON.stringify(inbox));
|
|
3977
|
-
renameSync(tmp, p);
|
|
3978
|
-
} catch {
|
|
3979
|
-
}
|
|
3980
|
-
}
|
|
3981
|
-
|
|
3982
|
-
function envInt(name, fallback) {
|
|
3983
|
-
const raw = process.env[name];
|
|
3984
|
-
if (raw === void 0 || raw === "") return fallback;
|
|
3985
|
-
const n = parseInt(raw, 10);
|
|
3986
|
-
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
3987
|
-
}
|
|
3988
|
-
function getRateLimitRetryConfig() {
|
|
3989
|
-
return {
|
|
3990
|
-
maxRetries: envInt("SVAMP_RATELIMIT_MAX_RETRIES", 200),
|
|
3991
|
-
baseMs: envInt("SVAMP_RATELIMIT_BASE_MS", 5e3),
|
|
3992
|
-
capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4)
|
|
3993
|
-
};
|
|
3994
|
-
}
|
|
3995
|
-
function computeRetryDelayMs(attempt, cfg = getRateLimitRetryConfig(), rng = Math.random) {
|
|
3996
|
-
const a = Math.max(0, Math.floor(attempt));
|
|
3997
|
-
const expo = Math.min(a, 30);
|
|
3998
|
-
const nominal = Math.min(cfg.capMs, cfg.baseMs * Math.pow(2, expo));
|
|
3999
|
-
const half = nominal / 2;
|
|
4000
|
-
return Math.round(half + rng() * half);
|
|
4001
|
-
}
|
|
4002
|
-
function isRetryableRateLimit(text, apiErrorStatus) {
|
|
4003
|
-
const t = (text || "").toLowerCase();
|
|
4004
|
-
const hardLimit = t.includes("quota") || // The Hypha LLM proxy emits a 429 "Daily spending limit reached … Resets
|
|
4005
|
-
// at 00:00 UTC. Contact the admin to raise your limit." That is a hard cap
|
|
4006
|
-
// that resets at a fixed time / needs human action — NOT a transient
|
|
4007
|
-
// upstream throttle. Retrying it 10× just delays surfacing the real error.
|
|
4008
|
-
t.includes("spending limit") || t.includes("credit") || t.includes("balance") || t.includes("billing") || t.includes("payment") || t.includes("subscription") || t.includes("1m-context") || t.includes("1m context") || t.includes("insufficient") || t.includes("usage limit") && !t.includes("not your usage limit");
|
|
4009
|
-
if (hardLimit) return false;
|
|
4010
|
-
const authIssue = apiErrorStatus === 401 || apiErrorStatus === 403 || t.includes("unauthorized") || t.includes("invalid api key") || t.includes("authentication") || t.includes("forbidden");
|
|
4011
|
-
if (authIssue) return false;
|
|
4012
|
-
const clientError = apiErrorStatus === 400 || apiErrorStatus === 404 || apiErrorStatus === 422 || t.includes("invalid_request") || t.includes("invalid request") || t.includes("bad request");
|
|
4013
|
-
if (clientError) return false;
|
|
4014
|
-
const serverTransient = typeof apiErrorStatus === "number" && apiErrorStatus >= 500 && apiErrorStatus <= 599 || apiErrorStatus === 429 || /\b(429|500|502|503|504|520|521|522|523|524|529)\b/.test(t) || t.includes("temporarily limiting") || t.includes("not your usage limit") || t.includes("overloaded") || t.includes("overload") || t.includes("too many requests") || t.includes("server is busy") || t.includes("internal server error") || t.includes("bad gateway") || t.includes("service unavailable") || t.includes("gateway timeout") || t.includes("rate limit") || t.includes("rate-limit") || t.includes("rate limited");
|
|
4015
|
-
if (serverTransient) return true;
|
|
4016
|
-
const networkTransient = t.includes("socket hang up") || t.includes("closed unexpectedly") || t.includes("connection closed") || t.includes("connection reset") || t.includes("connection error") || t.includes("econnreset") || t.includes("etimedout") || t.includes("econnaborted") || t.includes("epipe") || t.includes("enetunreach") || t.includes("enetreset") || t.includes("ehostunreach") || t.includes("eai_again") || t.includes("network error") || t.includes("fetch failed") || t.includes("request timed out") || t.includes("timed out");
|
|
4017
|
-
return networkTransient;
|
|
4018
|
-
}
|
|
4019
|
-
|
|
4020
|
-
const EXAMPLE_HYPHA_PROXY_URL = "https://proxy.hypha.aicell.io";
|
|
4021
|
-
const MODE_KEY = "SVAMP_CLAUDE_PROXY";
|
|
4022
|
-
const HYPHA_PROXY_URL_KEY = "SVAMP_HYPHA_PROXY_URL";
|
|
4023
|
-
const MANAGED_KEYS = /* @__PURE__ */ new Set([
|
|
4024
|
-
MODE_KEY,
|
|
4025
|
-
HYPHA_PROXY_URL_KEY,
|
|
4026
|
-
"ANTHROPIC_BASE_URL",
|
|
4027
|
-
"ANTHROPIC_API_KEY"
|
|
4028
|
-
]);
|
|
4029
|
-
function normalizeProxyUrl(raw) {
|
|
4030
|
-
const trimmed = raw.trim().replace(/\/+$/, "");
|
|
4031
|
-
if (trimmed.endsWith("/v1")) {
|
|
4032
|
-
throw new Error(
|
|
4033
|
-
"Hypha proxy URL must not end in /v1 \u2014 the SDK appends it, producing double /v1/v1/messages"
|
|
4034
|
-
);
|
|
4035
|
-
}
|
|
4036
|
-
return trimmed;
|
|
4037
|
-
}
|
|
4038
|
-
function resolveHyphaProxyUrl() {
|
|
4039
|
-
const configured = (process.env[HYPHA_PROXY_URL_KEY] || "").trim();
|
|
4040
|
-
if (!configured) return void 0;
|
|
4041
|
-
try {
|
|
4042
|
-
return normalizeProxyUrl(configured);
|
|
4043
|
-
} catch {
|
|
4044
|
-
return void 0;
|
|
4045
|
-
}
|
|
4046
|
-
}
|
|
4047
|
-
function envFilePath() {
|
|
4048
|
-
const svampHome = process.env.SVAMP_HOME || join(homedir(), ".svamp");
|
|
4049
|
-
return join(svampHome, ".env");
|
|
4050
|
-
}
|
|
4051
|
-
function readEnvLines() {
|
|
4052
|
-
const file = envFilePath();
|
|
4053
|
-
if (!existsSync(file)) return [];
|
|
4054
|
-
return readFileSync(file, "utf-8").split("\n");
|
|
4055
|
-
}
|
|
4056
|
-
function writeEnvLines(lines) {
|
|
4057
|
-
const file = envFilePath();
|
|
4058
|
-
const dir = join(file, "..");
|
|
4059
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
4060
|
-
while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
|
|
4061
|
-
lines.pop();
|
|
4062
|
-
}
|
|
4063
|
-
writeFileSync(file, lines.join("\n") + "\n", "utf-8");
|
|
4064
|
-
}
|
|
4065
|
-
function updateEnvFile(updates) {
|
|
4066
|
-
const lines = readEnvLines();
|
|
4067
|
-
const kept = [];
|
|
4068
|
-
const remainingUpdates = new Map(Object.entries(updates));
|
|
4069
|
-
for (const line of lines) {
|
|
4070
|
-
const trimmed = line.trim();
|
|
4071
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
4072
|
-
kept.push(line);
|
|
4073
|
-
continue;
|
|
4074
|
-
}
|
|
4075
|
-
const eq = trimmed.indexOf("=");
|
|
4076
|
-
const key = eq === -1 ? trimmed : trimmed.slice(0, eq);
|
|
4077
|
-
if (remainingUpdates.has(key)) {
|
|
4078
|
-
const v = remainingUpdates.get(key);
|
|
4079
|
-
remainingUpdates.delete(key);
|
|
4080
|
-
if (v === void 0) continue;
|
|
4081
|
-
kept.push(`${key}=${v}`);
|
|
4082
|
-
continue;
|
|
4536
|
+
if (isBreakerOpenAt(now)) {
|
|
4537
|
+
return { action: "queue", reason: "open-thread reply (breaker open)" };
|
|
4083
4538
|
}
|
|
4084
|
-
|
|
4539
|
+
const tripped2 = recordWake(now);
|
|
4540
|
+
return { action: "wake", reason: "open-thread reply", breakerTripped: tripped2 };
|
|
4085
4541
|
}
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
kept.push(`${key}=${v}`);
|
|
4542
|
+
if (message.urgency !== "urgent") {
|
|
4543
|
+
return { action: "queue", reason: "non-urgent" };
|
|
4089
4544
|
}
|
|
4090
|
-
|
|
4091
|
-
}
|
|
4092
|
-
function currentMode() {
|
|
4093
|
-
const raw = (process.env[MODE_KEY] || "").trim().toLowerCase();
|
|
4094
|
-
if (raw === "hypha" || raw === "hypha-proxy") return "hypha";
|
|
4095
|
-
if (raw === "custom") return "custom";
|
|
4096
|
-
return "login";
|
|
4097
|
-
}
|
|
4098
|
-
function redactKey(key) {
|
|
4099
|
-
if (!key) return void 0;
|
|
4100
|
-
if (key.length <= 12) return "***";
|
|
4101
|
-
return `${key.slice(0, 8)}\u2026${key.slice(-4)}`;
|
|
4102
|
-
}
|
|
4103
|
-
function getClaudeAuthStatus() {
|
|
4104
|
-
const mode = currentMode();
|
|
4105
|
-
if (mode === "hypha") {
|
|
4106
|
-
const token = process.env.HYPHA_TOKEN;
|
|
4107
|
-
const proxyUrl = resolveHyphaProxyUrl();
|
|
4108
|
-
return {
|
|
4109
|
-
mode,
|
|
4110
|
-
baseUrl: proxyUrl,
|
|
4111
|
-
apiKeyPreview: redactKey(token),
|
|
4112
|
-
hyphaTokenMissing: !token,
|
|
4113
|
-
hyphaProxyUrlMissing: !proxyUrl
|
|
4114
|
-
};
|
|
4545
|
+
if (isBreakerOpenAt(now)) {
|
|
4546
|
+
return { action: "queue", reason: "circuit-breaker-open" };
|
|
4115
4547
|
}
|
|
4116
|
-
if (
|
|
4117
|
-
return {
|
|
4118
|
-
mode,
|
|
4119
|
-
baseUrl: process.env.ANTHROPIC_BASE_URL,
|
|
4120
|
-
apiKeyPreview: redactKey(process.env.ANTHROPIC_API_KEY)
|
|
4121
|
-
};
|
|
4548
|
+
if (!takeUrgentToken(senderKey(message), now)) {
|
|
4549
|
+
return { action: "queue", reason: "rate-limited" };
|
|
4122
4550
|
}
|
|
4123
|
-
|
|
4551
|
+
const tripped = recordWake(now);
|
|
4552
|
+
return { action: "wake", reason: "urgent", breakerTripped: tripped };
|
|
4124
4553
|
}
|
|
4125
|
-
function
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
}
|
|
4131
|
-
updateEnvFile({
|
|
4132
|
-
[MODE_KEY]: "hypha",
|
|
4133
|
-
[HYPHA_PROXY_URL_KEY]: normalizeProxyUrl(url),
|
|
4134
|
-
// Hypha mode resolves token live at spawn — no stored copy.
|
|
4135
|
-
ANTHROPIC_BASE_URL: void 0,
|
|
4136
|
-
ANTHROPIC_API_KEY: void 0
|
|
4137
|
-
});
|
|
4554
|
+
function applyInboxClear(inbox, opts) {
|
|
4555
|
+
let kept;
|
|
4556
|
+
if (opts?.messageId) kept = inbox.filter((m) => m.messageId !== opts.messageId);
|
|
4557
|
+
else if (opts?.all) kept = [];
|
|
4558
|
+
else kept = inbox.filter((m) => !m.read);
|
|
4559
|
+
return { kept, removed: inbox.length - kept.length };
|
|
4138
4560
|
}
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
[HYPHA_PROXY_URL_KEY]: void 0,
|
|
4143
|
-
ANTHROPIC_BASE_URL: void 0,
|
|
4144
|
-
ANTHROPIC_API_KEY: void 0
|
|
4145
|
-
});
|
|
4561
|
+
|
|
4562
|
+
function inboxFilePath(projectDir, sessionId) {
|
|
4563
|
+
return join(projectDir, ".svamp", sessionId, "inbox.json");
|
|
4146
4564
|
}
|
|
4147
|
-
function
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4565
|
+
function loadInbox(projectDir, sessionId) {
|
|
4566
|
+
try {
|
|
4567
|
+
const p = inboxFilePath(projectDir, sessionId);
|
|
4568
|
+
if (!existsSync(p)) return [];
|
|
4569
|
+
const data = JSON.parse(readFileSync(p, "utf8"));
|
|
4570
|
+
if (Array.isArray(data)) return data;
|
|
4571
|
+
if (data && Array.isArray(data.messages)) return data.messages;
|
|
4572
|
+
return [];
|
|
4573
|
+
} catch {
|
|
4574
|
+
return [];
|
|
4155
4575
|
}
|
|
4156
|
-
updateEnvFile({
|
|
4157
|
-
[MODE_KEY]: "custom",
|
|
4158
|
-
[HYPHA_PROXY_URL_KEY]: void 0,
|
|
4159
|
-
ANTHROPIC_BASE_URL: trimmed,
|
|
4160
|
-
ANTHROPIC_API_KEY: apiKey
|
|
4161
|
-
});
|
|
4162
4576
|
}
|
|
4163
|
-
function
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
}
|
|
4172
|
-
const token = process.env.HYPHA_TOKEN;
|
|
4173
|
-
if (!token) {
|
|
4174
|
-
delete spawnEnv.ANTHROPIC_BASE_URL;
|
|
4175
|
-
delete spawnEnv.ANTHROPIC_API_KEY;
|
|
4176
|
-
return "hypha mode but HYPHA_TOKEN missing \u2014 falling back to login";
|
|
4177
|
-
}
|
|
4178
|
-
spawnEnv.ANTHROPIC_BASE_URL = proxyUrl;
|
|
4179
|
-
spawnEnv.ANTHROPIC_API_KEY = token;
|
|
4180
|
-
const cacheOverride = spawnEnv.ENABLE_PROMPT_CACHING_1H ?? process.env.ENABLE_PROMPT_CACHING_1H ?? spawnEnv.FORCE_PROMPT_CACHING_5M ?? process.env.FORCE_PROMPT_CACHING_5M;
|
|
4181
|
-
if (cacheOverride === void 0) spawnEnv.ENABLE_PROMPT_CACHING_1H = "1";
|
|
4182
|
-
return `hypha proxy (${proxyUrl}, 1h cache TTL)`;
|
|
4183
|
-
}
|
|
4184
|
-
if (mode === "custom") {
|
|
4185
|
-
const url = process.env.ANTHROPIC_BASE_URL;
|
|
4186
|
-
const key = process.env.ANTHROPIC_API_KEY;
|
|
4187
|
-
if (!url || !key) {
|
|
4188
|
-
delete spawnEnv.ANTHROPIC_BASE_URL;
|
|
4189
|
-
delete spawnEnv.ANTHROPIC_API_KEY;
|
|
4190
|
-
return "custom mode but ANTHROPIC_BASE_URL/API_KEY missing \u2014 falling back to login";
|
|
4191
|
-
}
|
|
4192
|
-
spawnEnv.ANTHROPIC_BASE_URL = url;
|
|
4193
|
-
spawnEnv.ANTHROPIC_API_KEY = key;
|
|
4194
|
-
return `custom proxy (${url})`;
|
|
4577
|
+
function saveInbox(projectDir, sessionId, inbox) {
|
|
4578
|
+
try {
|
|
4579
|
+
const p = inboxFilePath(projectDir, sessionId);
|
|
4580
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
4581
|
+
const tmp = `${p}.tmp-${process.pid}`;
|
|
4582
|
+
writeFileSync(tmp, JSON.stringify(inbox));
|
|
4583
|
+
renameSync(tmp, p);
|
|
4584
|
+
} catch {
|
|
4195
4585
|
}
|
|
4196
|
-
delete spawnEnv.ANTHROPIC_BASE_URL;
|
|
4197
|
-
delete spawnEnv.ANTHROPIC_API_KEY;
|
|
4198
|
-
return null;
|
|
4199
4586
|
}
|
|
4200
|
-
const MANAGED_ENV_KEYS = Array.from(MANAGED_KEYS);
|
|
4201
4587
|
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
}
|
|
4588
|
+
function envInt(name, fallback) {
|
|
4589
|
+
const raw = process.env[name];
|
|
4590
|
+
if (raw === void 0 || raw === "") return fallback;
|
|
4591
|
+
const n = parseInt(raw, 10);
|
|
4592
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
4593
|
+
}
|
|
4594
|
+
function getRateLimitRetryConfig() {
|
|
4595
|
+
return {
|
|
4596
|
+
maxRetries: envInt("SVAMP_RATELIMIT_MAX_RETRIES", 200),
|
|
4597
|
+
baseMs: envInt("SVAMP_RATELIMIT_BASE_MS", 5e3),
|
|
4598
|
+
capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4)
|
|
4599
|
+
};
|
|
4600
|
+
}
|
|
4601
|
+
function computeRetryDelayMs(attempt, cfg = getRateLimitRetryConfig(), rng = Math.random) {
|
|
4602
|
+
const a = Math.max(0, Math.floor(attempt));
|
|
4603
|
+
const expo = Math.min(a, 30);
|
|
4604
|
+
const nominal = Math.min(cfg.capMs, cfg.baseMs * Math.pow(2, expo));
|
|
4605
|
+
const half = nominal / 2;
|
|
4606
|
+
return Math.round(half + rng() * half);
|
|
4607
|
+
}
|
|
4608
|
+
function isRetryableRateLimit(text, apiErrorStatus) {
|
|
4609
|
+
const t = (text || "").toLowerCase();
|
|
4610
|
+
const hardLimit = t.includes("quota") || // The Hypha LLM proxy emits a 429 "Daily spending limit reached … Resets
|
|
4611
|
+
// at 00:00 UTC. Contact the admin to raise your limit." That is a hard cap
|
|
4612
|
+
// that resets at a fixed time / needs human action — NOT a transient
|
|
4613
|
+
// upstream throttle. Retrying it 10× just delays surfacing the real error.
|
|
4614
|
+
t.includes("spending limit") || t.includes("credit") || t.includes("balance") || t.includes("billing") || t.includes("payment") || t.includes("subscription") || t.includes("1m-context") || t.includes("1m context") || t.includes("insufficient") || t.includes("usage limit") && !t.includes("not your usage limit");
|
|
4615
|
+
if (hardLimit) return false;
|
|
4616
|
+
const authIssue = apiErrorStatus === 401 || apiErrorStatus === 403 || t.includes("unauthorized") || t.includes("invalid api key") || t.includes("authentication") || t.includes("forbidden");
|
|
4617
|
+
if (authIssue) return false;
|
|
4618
|
+
const clientError = apiErrorStatus === 400 || apiErrorStatus === 404 || apiErrorStatus === 422 || t.includes("invalid_request") || t.includes("invalid request") || t.includes("bad request");
|
|
4619
|
+
if (clientError) return false;
|
|
4620
|
+
const serverTransient = typeof apiErrorStatus === "number" && apiErrorStatus >= 500 && apiErrorStatus <= 599 || apiErrorStatus === 429 || /\b(429|500|502|503|504|520|521|522|523|524|529)\b/.test(t) || t.includes("temporarily limiting") || t.includes("not your usage limit") || t.includes("overloaded") || t.includes("overload") || t.includes("too many requests") || t.includes("server is busy") || t.includes("internal server error") || t.includes("bad gateway") || t.includes("service unavailable") || t.includes("gateway timeout") || t.includes("rate limit") || t.includes("rate-limit") || t.includes("rate limited");
|
|
4621
|
+
if (serverTransient) return true;
|
|
4622
|
+
const networkTransient = t.includes("socket hang up") || t.includes("closed unexpectedly") || t.includes("connection closed") || t.includes("connection reset") || t.includes("connection error") || t.includes("econnreset") || t.includes("etimedout") || t.includes("econnaborted") || t.includes("epipe") || t.includes("enetunreach") || t.includes("enetreset") || t.includes("ehostunreach") || t.includes("eai_again") || t.includes("network error") || t.includes("fetch failed") || t.includes("request timed out") || t.includes("timed out");
|
|
4623
|
+
return networkTransient;
|
|
4624
|
+
}
|
|
4214
4625
|
|
|
4215
4626
|
const PARTICIPANTS_CHANNEL_ID = "sys-participants";
|
|
4216
4627
|
function channelPublicView(c) {
|
|
@@ -8692,46 +9103,6 @@ function mapDecisionToWire(d) {
|
|
|
8692
9103
|
}
|
|
8693
9104
|
}
|
|
8694
9105
|
|
|
8695
|
-
const CODEX_PROVIDER_KEY_ENV = "SVAMP_CODEX_API_KEY";
|
|
8696
|
-
const PROVIDER_NAME = "svamp";
|
|
8697
|
-
function resolveCodexProvider(env = process.env) {
|
|
8698
|
-
return {
|
|
8699
|
-
apiBase: env.SVAMP_CODEX_API_BASE || env.LLM_API_BASE || void 0,
|
|
8700
|
-
apiKey: env.SVAMP_CODEX_API_KEY || env.LLM_API_KEY || void 0,
|
|
8701
|
-
model: env.SVAMP_CODEX_MODEL || env.LLM_MODEL || void 0
|
|
8702
|
-
};
|
|
8703
|
-
}
|
|
8704
|
-
function hasCustomProvider(cfg) {
|
|
8705
|
-
return !!(cfg.apiBase && cfg.apiKey);
|
|
8706
|
-
}
|
|
8707
|
-
function buildCodexProviderArgs(cfg) {
|
|
8708
|
-
if (!hasCustomProvider(cfg)) {
|
|
8709
|
-
return { configArgs: cfg.model ? ["-c", `model=${JSON.stringify(cfg.model)}`] : [], env: {}, model: cfg.model };
|
|
8710
|
-
}
|
|
8711
|
-
const configArgs = [
|
|
8712
|
-
"-c",
|
|
8713
|
-
`model_provider=${JSON.stringify(PROVIDER_NAME)}`,
|
|
8714
|
-
"-c",
|
|
8715
|
-
`model_providers.${PROVIDER_NAME}.name=${JSON.stringify(PROVIDER_NAME)}`,
|
|
8716
|
-
"-c",
|
|
8717
|
-
`model_providers.${PROVIDER_NAME}.base_url=${JSON.stringify(cfg.apiBase)}`,
|
|
8718
|
-
"-c",
|
|
8719
|
-
`model_providers.${PROVIDER_NAME}.env_key=${JSON.stringify(CODEX_PROVIDER_KEY_ENV)}`,
|
|
8720
|
-
"-c",
|
|
8721
|
-
`model_providers.${PROVIDER_NAME}.wire_api=${JSON.stringify("responses")}`
|
|
8722
|
-
];
|
|
8723
|
-
if (cfg.model) configArgs.push("-c", `model=${JSON.stringify(cfg.model)}`);
|
|
8724
|
-
return { configArgs, env: { [CODEX_PROVIDER_KEY_ENV]: cfg.apiKey }, model: cfg.model };
|
|
8725
|
-
}
|
|
8726
|
-
|
|
8727
|
-
var codexProvider = /*#__PURE__*/Object.freeze({
|
|
8728
|
-
__proto__: null,
|
|
8729
|
-
CODEX_PROVIDER_KEY_ENV: CODEX_PROVIDER_KEY_ENV,
|
|
8730
|
-
buildCodexProviderArgs: buildCodexProviderArgs,
|
|
8731
|
-
hasCustomProvider: hasCustomProvider,
|
|
8732
|
-
resolveCodexProvider: resolveCodexProvider
|
|
8733
|
-
});
|
|
8734
|
-
|
|
8735
9106
|
class CodexAppServerBackend {
|
|
8736
9107
|
constructor(opts) {
|
|
8737
9108
|
this.opts = opts;
|
|
@@ -8740,6 +9111,8 @@ class CodexAppServerBackend {
|
|
|
8740
9111
|
const provider = opts.provider ?? resolveCodexProvider();
|
|
8741
9112
|
const { configArgs, env: providerEnv, model: providerModel } = buildCodexProviderArgs(provider);
|
|
8742
9113
|
this.model = opts.model ?? providerModel;
|
|
9114
|
+
this.approvalPolicy = opts.approvalPolicy;
|
|
9115
|
+
this.sandbox = opts.sandbox;
|
|
8743
9116
|
this.client = new CodexAppServerClient({
|
|
8744
9117
|
cwd: opts.cwd,
|
|
8745
9118
|
env: { ...opts.env ?? {}, ...providerEnv },
|
|
@@ -8756,6 +9129,11 @@ class CodexAppServerBackend {
|
|
|
8756
9129
|
sessionId = randomUUID();
|
|
8757
9130
|
log;
|
|
8758
9131
|
model;
|
|
9132
|
+
// #0438: codex's own approval policy + sandbox, derived from the svamp permission mode. Mutable
|
|
9133
|
+
// so a mid-session switchMode takes effect on the next turn. yolo → 'never' + 'danger-full-access'
|
|
9134
|
+
// so codex NEVER prompts for writes (and never times out waiting on a prompt).
|
|
9135
|
+
approvalPolicy;
|
|
9136
|
+
sandbox;
|
|
8759
9137
|
started = false;
|
|
8760
9138
|
// Approval promises keyed by callId, resolved by respondToPermission().
|
|
8761
9139
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
@@ -8779,9 +9157,9 @@ class CodexAppServerBackend {
|
|
|
8779
9157
|
if (avail === false) throw new Error("Installed Codex is too old for app-server \u2014 upgrade to >= 0.100 (`npm i -g @openai/codex@latest`).");
|
|
8780
9158
|
await this.client.start();
|
|
8781
9159
|
if (this.opts.resumeThreadId) {
|
|
8782
|
-
await this.client.resumeThread({ threadId: this.opts.resumeThreadId, model: this.model, approvalPolicy: this.
|
|
9160
|
+
await this.client.resumeThread({ threadId: this.opts.resumeThreadId, model: this.model, approvalPolicy: this.approvalPolicy, sandbox: this.sandbox });
|
|
8783
9161
|
} else {
|
|
8784
|
-
const r = await this.client.startThread({ model: this.model, approvalPolicy: this.
|
|
9162
|
+
const r = await this.client.startThread({ model: this.model, approvalPolicy: this.approvalPolicy, sandbox: this.sandbox });
|
|
8785
9163
|
this.model = this.model ?? r.model;
|
|
8786
9164
|
}
|
|
8787
9165
|
this.started = true;
|
|
@@ -8798,7 +9176,7 @@ class CodexAppServerBackend {
|
|
|
8798
9176
|
resolveTurn = res;
|
|
8799
9177
|
});
|
|
8800
9178
|
try {
|
|
8801
|
-
await this.client.sendTurnAndWait(prompt, { model: this.model, approvalPolicy: this.
|
|
9179
|
+
await this.client.sendTurnAndWait(prompt, { model: this.model, approvalPolicy: this.approvalPolicy, sandbox: this.sandbox });
|
|
8802
9180
|
} catch (err) {
|
|
8803
9181
|
this.emit({ type: "status", status: "error", detail: err instanceof Error ? err.message : String(err) });
|
|
8804
9182
|
} finally {
|
|
@@ -8849,6 +9227,12 @@ class CodexAppServerBackend {
|
|
|
8849
9227
|
setModel(model) {
|
|
8850
9228
|
this.model = model;
|
|
8851
9229
|
}
|
|
9230
|
+
/** #0438: update codex's approval policy + sandbox (from a svamp permission-mode switch). Takes
|
|
9231
|
+
* effect on the next turn (and next thread start). yolo → 'never' + 'danger-full-access'. */
|
|
9232
|
+
setPermission(approvalPolicy, sandbox) {
|
|
9233
|
+
this.approvalPolicy = approvalPolicy;
|
|
9234
|
+
this.sandbox = sandbox;
|
|
9235
|
+
}
|
|
8852
9236
|
/** The model label for assistant-message parity with Claude (frontend schema requires a non-empty `model`). */
|
|
8853
9237
|
getModel() {
|
|
8854
9238
|
return this.model || "codex";
|
|
@@ -11985,17 +12369,31 @@ function resolveEvaluatorModel(envValue) {
|
|
|
11985
12369
|
function parseEvaluatorVerdict(text) {
|
|
11986
12370
|
if (!text) return null;
|
|
11987
12371
|
const candidates = [];
|
|
11988
|
-
let depth = 0, start = -1;
|
|
12372
|
+
let depth = 0, start = -1, inStr = false, esc = false;
|
|
11989
12373
|
for (let i = 0; i < text.length; i++) {
|
|
11990
12374
|
const c = text[i];
|
|
11991
|
-
if (
|
|
12375
|
+
if (inStr) {
|
|
12376
|
+
if (esc) {
|
|
12377
|
+
esc = false;
|
|
12378
|
+
} else if (c === "\\") {
|
|
12379
|
+
esc = true;
|
|
12380
|
+
} else if (c === '"') {
|
|
12381
|
+
inStr = false;
|
|
12382
|
+
}
|
|
12383
|
+
continue;
|
|
12384
|
+
}
|
|
12385
|
+
if (c === '"') {
|
|
12386
|
+
inStr = true;
|
|
12387
|
+
} else if (c === "{") {
|
|
11992
12388
|
if (depth === 0) start = i;
|
|
11993
12389
|
depth++;
|
|
11994
12390
|
} else if (c === "}") {
|
|
11995
|
-
depth
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
12391
|
+
if (depth > 0) {
|
|
12392
|
+
depth--;
|
|
12393
|
+
if (depth === 0 && start >= 0) {
|
|
12394
|
+
candidates.push(text.slice(start, i + 1));
|
|
12395
|
+
start = -1;
|
|
12396
|
+
}
|
|
11999
12397
|
}
|
|
12000
12398
|
}
|
|
12001
12399
|
}
|
|
@@ -13867,7 +14265,7 @@ async function startDaemon(options) {
|
|
|
13867
14265
|
try {
|
|
13868
14266
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
13869
14267
|
if (!dir) return;
|
|
13870
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
14268
|
+
const { reconcileServiceLinks } = await import('./agentCommands-CKd1jXfX.mjs');
|
|
13871
14269
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
13872
14270
|
const config = readSvampConfig(configPath);
|
|
13873
14271
|
const entries = Array.from(urls.entries());
|
|
@@ -13885,7 +14283,7 @@ async function startDaemon(options) {
|
|
|
13885
14283
|
}
|
|
13886
14284
|
}
|
|
13887
14285
|
async function createExposedTunnel(spec) {
|
|
13888
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
14286
|
+
const { FrpcTunnel } = await import('./frpc-Cwn_s0Fs.mjs');
|
|
13889
14287
|
const tunnel = new FrpcTunnel({
|
|
13890
14288
|
name: spec.name,
|
|
13891
14289
|
ports: spec.ports,
|
|
@@ -13906,7 +14304,7 @@ async function startDaemon(options) {
|
|
|
13906
14304
|
}
|
|
13907
14305
|
const tunnelRecreateState = /* @__PURE__ */ new Map();
|
|
13908
14306
|
const tunnelRecreateInFlight = /* @__PURE__ */ new Set();
|
|
13909
|
-
const { ServeManager } = await import('./serveManager-
|
|
14307
|
+
const { ServeManager } = await import('./serveManager-Dy1ZZNaY.mjs');
|
|
13910
14308
|
const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
13911
14309
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
13912
14310
|
});
|
|
@@ -14343,7 +14741,10 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
|
|
|
14343
14741
|
...sessionMetadata,
|
|
14344
14742
|
// Derive the handle name from the id (new-format ids embed it) so the two
|
|
14345
14743
|
// never diverge; fall back to persisted name (legacy ids) then a fresh one.
|
|
14346
|
-
friendlyName: persisted?.metadata?.friendlyName || friendlyNameFromId(sessionId) || generateFriendlyName(collectKnownFriendlyNames())
|
|
14744
|
+
friendlyName: persisted?.metadata?.friendlyName || friendlyNameFromId(sessionId) || generateFriendlyName(collectKnownFriendlyNames()),
|
|
14745
|
+
// Selected backend account (composer). Sticky across respawns + restart via the
|
|
14746
|
+
// persisted metadata; the daemon resolves it to concrete auth at each Claude spawn.
|
|
14747
|
+
accountId: options2.accountId ?? persisted?.metadata?.accountId ?? void 0
|
|
14347
14748
|
};
|
|
14348
14749
|
let currentPermissionMode = options2.permissionMode || persisted?.permissionMode || "bypassPermissions";
|
|
14349
14750
|
const sessionCreatedAt = persisted?.createdAt || Date.now();
|
|
@@ -14843,6 +15244,28 @@ ${parts.join("\n")}`);
|
|
|
14843
15244
|
if (proxyDesc) {
|
|
14844
15245
|
logger.log(`[Session ${sessionId}] Claude auth: ${proxyDesc}`);
|
|
14845
15246
|
}
|
|
15247
|
+
{
|
|
15248
|
+
const acctId = sessionMetadata.accountId;
|
|
15249
|
+
if (acctId) {
|
|
15250
|
+
try {
|
|
15251
|
+
const acct = getAccount(acctId, SVAMP_HOME);
|
|
15252
|
+
if (!acct) {
|
|
15253
|
+
logger.log(`[Session ${sessionId}] account ${acctId} not found \u2014 using machine default`);
|
|
15254
|
+
} else if (acct.vendor !== "anthropic") {
|
|
15255
|
+
logger.log(`[Session ${sessionId}] account ${acctId} is a ${acct.vendor} account, not usable for Claude \u2014 using machine default`);
|
|
15256
|
+
} else {
|
|
15257
|
+
const resolved = resolveAccountEnv(acct, process.env, SVAMP_HOME);
|
|
15258
|
+
for (const [k, v] of Object.entries(resolved.claudeEnv || {})) {
|
|
15259
|
+
if (v === void 0) delete spawnEnv[k];
|
|
15260
|
+
else spawnEnv[k] = v;
|
|
15261
|
+
}
|
|
15262
|
+
logger.log(`[Session ${sessionId}] Claude auth (account): ${resolved.describe}`);
|
|
15263
|
+
}
|
|
15264
|
+
} catch (e) {
|
|
15265
|
+
logger.log(`[Session ${sessionId}] account ${acctId} resolve failed (${e?.message || e}) \u2014 using machine default`);
|
|
15266
|
+
}
|
|
15267
|
+
}
|
|
15268
|
+
}
|
|
14846
15269
|
if (isoConfig && stagedCredentials) {
|
|
14847
15270
|
Object.assign(spawnEnv, stagedCredentials.env);
|
|
14848
15271
|
const filtered = {};
|
|
@@ -16028,11 +16451,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
16028
16451
|
});
|
|
16029
16452
|
},
|
|
16030
16453
|
onIssue: async (params) => {
|
|
16031
|
-
const { issueRpc } = await import('./rpc-
|
|
16454
|
+
const { issueRpc } = await import('./rpc-BnHMvDxQ.mjs');
|
|
16032
16455
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
16033
16456
|
},
|
|
16034
16457
|
onWorkflow: async (params) => {
|
|
16035
|
-
const { workflowRpc } = await import('./rpc-
|
|
16458
|
+
const { workflowRpc } = await import('./rpc-DwlZTOtK.mjs');
|
|
16036
16459
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
16037
16460
|
},
|
|
16038
16461
|
onRipgrep: async (args, cwd) => {
|
|
@@ -16323,7 +16746,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
16323
16746
|
sharing: options2.sharing,
|
|
16324
16747
|
securityContext: options2.securityContext,
|
|
16325
16748
|
tags: options2.tags,
|
|
16326
|
-
parentSessionId: options2.parentSessionId
|
|
16749
|
+
parentSessionId: options2.parentSessionId,
|
|
16750
|
+
// Selected backend account — sticky across restart via persisted metadata.
|
|
16751
|
+
accountId: options2.accountId ?? acpPersisted?.metadata?.accountId ?? void 0
|
|
16327
16752
|
};
|
|
16328
16753
|
let currentPermissionMode = options2.permissionMode || "bypassPermissions";
|
|
16329
16754
|
const allowedTools = /* @__PURE__ */ new Set();
|
|
@@ -16426,6 +16851,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
16426
16851
|
if (currentPermissionMode === mode) return;
|
|
16427
16852
|
logger.log(`[${agentName} Session ${sessionId}] Switch mode: ${mode}`);
|
|
16428
16853
|
currentPermissionMode = mode;
|
|
16854
|
+
const cp = codexPermissionSettings(mode);
|
|
16855
|
+
agentBackend.setPermission?.(cp.approvalPolicy, cp.sandbox);
|
|
16429
16856
|
},
|
|
16430
16857
|
onRestartClaude: async () => {
|
|
16431
16858
|
logger.log(`[${agentName} Session ${sessionId}] Restart agent requested`);
|
|
@@ -16588,11 +17015,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
16588
17015
|
});
|
|
16589
17016
|
},
|
|
16590
17017
|
onIssue: async (params) => {
|
|
16591
|
-
const { issueRpc } = await import('./rpc-
|
|
17018
|
+
const { issueRpc } = await import('./rpc-BnHMvDxQ.mjs');
|
|
16592
17019
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
16593
17020
|
},
|
|
16594
17021
|
onWorkflow: async (params) => {
|
|
16595
|
-
const { workflowRpc } = await import('./rpc-
|
|
17022
|
+
const { workflowRpc } = await import('./rpc-DwlZTOtK.mjs');
|
|
16596
17023
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
16597
17024
|
},
|
|
16598
17025
|
onRipgrep: async (args, cwd) => {
|
|
@@ -16743,17 +17170,45 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
16743
17170
|
const avail = codexAppServerAvailable();
|
|
16744
17171
|
if (avail === null) throw new Error("Codex CLI not found \u2014 install it (`npm i -g @openai/codex`) and authenticate (`svamp daemon codex-auth`).");
|
|
16745
17172
|
if (avail === false) throw new Error("Installed Codex is too old for app-server \u2014 upgrade to >= 0.100 (`npm i -g @openai/codex@latest`).");
|
|
16746
|
-
|
|
17173
|
+
let provider = resolveCodexProvider();
|
|
17174
|
+
let accountCodexEnv;
|
|
17175
|
+
let accountDesc;
|
|
17176
|
+
{
|
|
17177
|
+
const acctId = sessionMetadata.accountId;
|
|
17178
|
+
if (acctId) {
|
|
17179
|
+
try {
|
|
17180
|
+
const acct = getAccount(acctId, SVAMP_HOME);
|
|
17181
|
+
if (!acct) {
|
|
17182
|
+
logger.log(`[Agent Session ${sessionId}] account ${acctId} not found \u2014 using machine default`);
|
|
17183
|
+
} else if (acct.vendor !== "openai") {
|
|
17184
|
+
logger.log(`[Agent Session ${sessionId}] account ${acctId} is a ${acct.vendor} account, not usable for Codex \u2014 using machine default`);
|
|
17185
|
+
} else {
|
|
17186
|
+
const resolved = resolveAccountEnv(acct, process.env, SVAMP_HOME);
|
|
17187
|
+
if (resolved.codexHome) ensureCodexHome(acct, SVAMP_HOME);
|
|
17188
|
+
provider = resolved.codexProvider || {};
|
|
17189
|
+
accountCodexEnv = resolved.extraEnv;
|
|
17190
|
+
accountDesc = resolved.describe;
|
|
17191
|
+
}
|
|
17192
|
+
} catch (e) {
|
|
17193
|
+
logger.log(`[Agent Session ${sessionId}] account ${acctId} resolve failed (${e?.message || e}) \u2014 using machine default`);
|
|
17194
|
+
}
|
|
17195
|
+
}
|
|
17196
|
+
}
|
|
16747
17197
|
const codexModel = options2.model || agentConfig?.default_model || provider.model || void 0;
|
|
16748
|
-
|
|
17198
|
+
const codexPerm = codexPermissionSettings(currentPermissionMode);
|
|
17199
|
+
logger.log(`[Agent Session ${sessionId}] Codex backend: app-server (model=${codexModel ?? "default"}${provider.apiBase ? `, provider=${provider.apiBase}` : ""}${accountDesc ? `, account=${accountDesc}` : ""}, mode=${currentPermissionMode} \u2192 approval=${codexPerm.approvalPolicy}/sandbox=${codexPerm.sandbox})`);
|
|
16749
17200
|
if (acpResumeThreadId) logger.log(`[Agent Session ${sessionId}] Resuming Codex thread ${acpResumeThreadId} (restart recovery)`);
|
|
16750
17201
|
agentBackend = new CodexAppServerBackend({
|
|
16751
17202
|
cwd: directory,
|
|
16752
17203
|
model: codexModel,
|
|
16753
17204
|
provider,
|
|
16754
|
-
env
|
|
17205
|
+
// Account extra env (OPENAI_API_KEY / SVAMP_CODEX_API_KEY / CODEX_HOME) layered
|
|
17206
|
+
// over any per-session environmentVariables from the composer.
|
|
17207
|
+
env: { ...options2.environmentVariables || {}, ...accountCodexEnv || {} },
|
|
16755
17208
|
log: logger.log,
|
|
16756
17209
|
isolationConfig: agentIsoConfig,
|
|
17210
|
+
approvalPolicy: codexPerm.approvalPolicy,
|
|
17211
|
+
sandbox: codexPerm.sandbox,
|
|
16757
17212
|
// #0412 P2: resume the persisted thread across a daemon restart (context intact).
|
|
16758
17213
|
resumeThreadId: acpResumeThreadId
|
|
16759
17214
|
});
|
|
@@ -17600,7 +18055,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
17600
18055
|
const PING_TIMEOUT_MS = 15e3;
|
|
17601
18056
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
17602
18057
|
const RECONNECT_JITTER_MS = 2500;
|
|
17603
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
18058
|
+
const { WorkflowScheduler } = await import('./scheduler-BxijQDI8.mjs');
|
|
17604
18059
|
const workflowScheduler = new WorkflowScheduler({
|
|
17605
18060
|
projectRoots: () => {
|
|
17606
18061
|
const dirs = /* @__PURE__ */ new Set();
|
|
@@ -18211,4 +18666,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
18211
18666
|
writeStopMarker: writeStopMarker
|
|
18212
18667
|
});
|
|
18213
18668
|
|
|
18214
|
-
export {
|
|
18669
|
+
export { resolveModel as $, runWorkflow as A, setWorkflowEnabled as B, removeWorkflow as C, saveWorkflow as D, rawWorkflow as E, listWorkflows as F, isWorkflowEnabled as G, workflowCrons as H, cronMatches as I, summarize as J, workflowSteps as K, loadMachineContext as L, buildMachineInstructions as M, machineToolsForRole as N, buildMachineTools as O, parseFrontmatter as P, getSkillsServer as Q, READ_ONLY_TOOLS as R, ServeAuth as S, getSkillsWorkspaceName as T, getSkillsCollectionName as U, fetchWithTimeout as V, searchSkills as W, SKILLS_DIR as X, getSkillInfo as Y, downloadSkillFile as Z, listSkillFiles as _, createSessionStore as a, clearStopMarker as a0, stopMarkerExists as a1, formatHandle as a2, normalizeAllowedUser as a3, loadSecurityContextConfig as a4, resolveSecurityContext as a5, buildSecurityContextFromFlags as a6, mergeSecurityContexts as a7, buildSessionShareUrl as a8, computeOutboundHop as a9, registerAwaitingReply as aa, buildMachineShareUrl as ab, parseHandle as ac, handleMatchesMetadata as ad, describeMisconfiguration as ae, buildMachineDeps as af, applyClaudeProxyEnv as ag, composeSessionId as ah, generateFriendlyName as ai, generateHookSettings as aj, claudeAuth as ak, codexProvider as al, projectInfo as am, DefaultTransport$1 as an, acpBackend as ao, acpAgentConfig as ap, codexAppServerBackend as aq, GeminiTransport$1 as ar, instanceConfig as as, api as at, run as au, stopDaemon as b, connectToHypha as c, daemonStatus as d, getFrpsSubdomainHost as e, getFrpsServerPort as f, getHyphaServerUrl$1 as g, getFrpsServerAddr as h, shortId as i, getHyphaServerUrl as j, hasCookieToken as k, resolveProjectRoot as l, getIssue as m, resumeIssue as n, addComment as o, pauseIssue as p, addIssue as q, registerMachineService as r, startDaemon as s, listIssues as t, updateIssue as u, searchIssues as v, isVisibleTo as w, getRun as x, listRuns as y, getWorkflow as z };
|