svamp-cli 0.2.103 → 0.2.104
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-oHt9FQJt.mjs → agentCommands-DhcQMAe-.mjs} +2 -2
- package/dist/{auth-Dg0s5H5y.mjs → auth-D4G47YjL.mjs} +2 -2
- package/dist/cli.mjs +51 -51
- package/dist/{commands-BvJ_Dl1l.mjs → commands-AkN7uDYW.mjs} +5 -5
- package/dist/{commands-BGE6zFa1.mjs → commands-C1dsiSL5.mjs} +15 -2
- package/dist/{commands-C_B8GNUT.mjs → commands-DsSzRtJu.mjs} +2 -2
- package/dist/{commands-C_DlMpl7.mjs → commands-o0MBJocy.mjs} +34 -23
- package/dist/{commands-BXFukv2v.mjs → commands-u519ohSC.mjs} +2 -2
- package/dist/{fleet-BlrT4zSC.mjs → fleet-CjAI4rxS.mjs} +1 -1
- package/dist/{frpc-Dn5pmk_f.mjs → frpc-B8ORdlOO.mjs} +2 -2
- package/dist/{headlessCli-D7NXB73S.mjs → headlessCli-CIMYmqci.mjs} +3 -3
- package/dist/{httpServer-D9qLS8ed.mjs → httpServer-CWn3F-0t.mjs} +2 -1
- package/dist/index.mjs +2 -2
- package/dist/{package-fu3Jsw1q.mjs → package-IQYoSW7o.mjs} +2 -2
- package/dist/{run-BmZjAEob.mjs → run-CoaYIdw1.mjs} +715 -557
- package/dist/{run-DTOSfKSH.mjs → run-aFkkky75.mjs} +1 -1
- package/dist/{serveCommands-zFOjNs-0.mjs → serveCommands-DSn_4Auj.mjs} +5 -5
- package/dist/{serveManager-D6lGn8jh.mjs → serveManager-Bq33kB5r.mjs} +3 -3
- package/dist/{sideband-CNyGVxRy.mjs → sideband-CO0bdYO_.mjs} +2 -2
- package/package.json +2 -2
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import os$1, { homedir as homedir$1 } from 'os';
|
|
2
2
|
import fs, { mkdir as mkdir$1, readdir as readdir$1, readFile, writeFile as writeFile$1, rename, unlink } from 'fs/promises';
|
|
3
|
-
import { readFileSync as readFileSync$1, mkdirSync, writeFileSync, renameSync, existsSync as existsSync$1, rmSync as rmSync$1, unlinkSync as unlinkSync$1, copyFileSync, watch, rmdirSync, readdirSync as readdirSync$1 } from 'fs';
|
|
4
|
-
import path__default, { join, dirname, basename, resolve } from 'path';
|
|
3
|
+
import { readFileSync as readFileSync$1, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1, renameSync as renameSync$1, existsSync as existsSync$1, rmSync as rmSync$1, unlinkSync as unlinkSync$1, copyFileSync, watch, rmdirSync, readdirSync as readdirSync$1 } from 'fs';
|
|
4
|
+
import path__default, { join as join$1, dirname, basename, resolve } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { execFile, spawn as spawn$1, execSync as execSync$1, spawnSync } from 'child_process';
|
|
7
7
|
import { randomUUID as randomUUID$1 } from 'crypto';
|
|
8
|
-
import { existsSync, readFileSync, mkdirSync
|
|
8
|
+
import { existsSync, readFileSync, mkdirSync, readdirSync, writeFileSync, renameSync, rmSync, appendFileSync, unlinkSync } from 'node:fs';
|
|
9
9
|
import { exec, spawn, execSync, execFile as execFile$1, execFileSync } from 'node:child_process';
|
|
10
10
|
import { promisify } from 'util';
|
|
11
|
+
import { join } from 'node:path';
|
|
11
12
|
import { randomBytes, randomUUID, createHash } from 'node:crypto';
|
|
12
|
-
import { join as join$1 } from 'node:path';
|
|
13
13
|
import os, { homedir, platform } from 'node:os';
|
|
14
14
|
import { EventEmitter } from 'node:events';
|
|
15
15
|
import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
|
|
@@ -1121,6 +1121,362 @@ async function killDescendant(rootPid, targetPid, signal = "SIGTERM") {
|
|
|
1121
1121
|
}
|
|
1122
1122
|
}
|
|
1123
1123
|
|
|
1124
|
+
const FIELD_RANGES = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
|
|
1125
|
+
function parseField(token, [min, max]) {
|
|
1126
|
+
const set = /* @__PURE__ */ new Set();
|
|
1127
|
+
for (const part of token.split(",")) {
|
|
1128
|
+
let m;
|
|
1129
|
+
if (part === "*") {
|
|
1130
|
+
for (let i = min; i <= max; i++) set.add(i);
|
|
1131
|
+
} else if (m = part.match(/^\*\/(\d+)$/)) {
|
|
1132
|
+
const s = +m[1];
|
|
1133
|
+
for (let i = min; i <= max; i += s) set.add(i);
|
|
1134
|
+
} else if (m = part.match(/^(\d+)-(\d+)\/(\d+)$/)) {
|
|
1135
|
+
for (let i = +m[1]; i <= +m[2]; i += +m[3]) set.add(i);
|
|
1136
|
+
} else if (m = part.match(/^(\d+)-(\d+)$/)) {
|
|
1137
|
+
for (let i = +m[1]; i <= +m[2]; i++) set.add(i);
|
|
1138
|
+
} else if (m = part.match(/^(\d+)$/)) {
|
|
1139
|
+
set.add(+m[1]);
|
|
1140
|
+
} else throw new Error(`invalid cron field: "${token}"`);
|
|
1141
|
+
}
|
|
1142
|
+
for (const v of set) if (v < min || v > max) throw new Error(`cron value ${v} out of range [${min},${max}]`);
|
|
1143
|
+
return set;
|
|
1144
|
+
}
|
|
1145
|
+
function parseCron(expr) {
|
|
1146
|
+
const fields = String(expr).trim().split(/\s+/);
|
|
1147
|
+
if (fields.length !== 5) throw new Error(`cron must have 5 fields, got ${fields.length}: "${expr}"`);
|
|
1148
|
+
const [minute, hour, dom, month, dow] = fields.map((f, i) => parseField(f, FIELD_RANGES[i]));
|
|
1149
|
+
return { minute, hour, dom, month, dow, domRestricted: fields[2] !== "*", dowRestricted: fields[4] !== "*" };
|
|
1150
|
+
}
|
|
1151
|
+
function cronMatches(expr, date) {
|
|
1152
|
+
const c = typeof expr === "string" ? parseCron(expr) : expr;
|
|
1153
|
+
if (!c.minute.has(date.getMinutes())) return false;
|
|
1154
|
+
if (!c.hour.has(date.getHours())) return false;
|
|
1155
|
+
if (!c.month.has(date.getMonth() + 1)) return false;
|
|
1156
|
+
const domOk = c.dom.has(date.getDate());
|
|
1157
|
+
const dowOk = c.dow.has(date.getDay());
|
|
1158
|
+
if (c.domRestricted && c.dowRestricted) return domOk || dowOk;
|
|
1159
|
+
return domOk && dowOk;
|
|
1160
|
+
}
|
|
1161
|
+
function inZone(date, tz) {
|
|
1162
|
+
if (!tz) return date;
|
|
1163
|
+
try {
|
|
1164
|
+
const p = new Intl.DateTimeFormat("en-US", {
|
|
1165
|
+
timeZone: tz,
|
|
1166
|
+
hour12: false,
|
|
1167
|
+
year: "numeric",
|
|
1168
|
+
month: "2-digit",
|
|
1169
|
+
day: "2-digit",
|
|
1170
|
+
hour: "2-digit",
|
|
1171
|
+
minute: "2-digit"
|
|
1172
|
+
}).formatToParts(date).reduce((o, x) => {
|
|
1173
|
+
o[x.type] = x.value;
|
|
1174
|
+
return o;
|
|
1175
|
+
}, {});
|
|
1176
|
+
return new Date(+p.year, +p.month - 1, +p.day, +(p.hour === "24" ? 0 : p.hour), +p.minute);
|
|
1177
|
+
} catch {
|
|
1178
|
+
return date;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
function nextFire(expr, from, tz) {
|
|
1182
|
+
const c = parseCron(expr);
|
|
1183
|
+
const d = new Date(from.getTime());
|
|
1184
|
+
d.setSeconds(0, 0);
|
|
1185
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
1186
|
+
for (let i = 0; i < 366 * 24 * 60; i++) {
|
|
1187
|
+
if (cronMatches(c, tz ? inZone(d, tz) : d)) return new Date(d.getTime());
|
|
1188
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
1189
|
+
}
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
function resolvePath(ctx, path) {
|
|
1193
|
+
return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], ctx);
|
|
1194
|
+
}
|
|
1195
|
+
function renderTemplate(template, ctx) {
|
|
1196
|
+
return String(template).replace(/\$\{([\w.$]+)\}/g, (_m, p) => {
|
|
1197
|
+
const v = resolvePath(ctx, p);
|
|
1198
|
+
return v == null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
const TRIGGER_TYPES = ["manual", "schedule", "webhook", "api"];
|
|
1202
|
+
const ACTION_KINDS = ["message", "loop"];
|
|
1203
|
+
const OVERLAP = ["queue", "skip", "replace"];
|
|
1204
|
+
function validateRoutine(r) {
|
|
1205
|
+
const errs = [];
|
|
1206
|
+
if (!r || typeof r !== "object") return ["routine must be an object"];
|
|
1207
|
+
if (!r.session_id) errs.push("session_id required");
|
|
1208
|
+
if (!r.name) errs.push("name required");
|
|
1209
|
+
const t = r.trigger;
|
|
1210
|
+
if (!t || !TRIGGER_TYPES.includes(t.type)) errs.push(`trigger.type must be one of ${TRIGGER_TYPES.join("|")}`);
|
|
1211
|
+
if (t?.type === "schedule") {
|
|
1212
|
+
try {
|
|
1213
|
+
parseCron(t.cron);
|
|
1214
|
+
} catch (e) {
|
|
1215
|
+
errs.push(`trigger.cron: ${e.message}`);
|
|
1216
|
+
}
|
|
1217
|
+
if (t.missed && !["catchup", "skip"].includes(t.missed)) errs.push("trigger.missed must be catchup|skip");
|
|
1218
|
+
}
|
|
1219
|
+
const a = r.action;
|
|
1220
|
+
if (!a || !ACTION_KINDS.includes(a.kind)) errs.push(`action.kind must be one of ${ACTION_KINDS.join("|")}`);
|
|
1221
|
+
if (a?.kind === "message" && !a.template) errs.push("action.template required for message action");
|
|
1222
|
+
if (a?.kind === "loop" && !a.loop && !a.task_template) errs.push("action.loop or action.task_template required for loop action");
|
|
1223
|
+
if (r.overlap && !OVERLAP.includes(r.overlap)) errs.push(`overlap must be one of ${OVERLAP.join("|")}`);
|
|
1224
|
+
if ((t?.type === "webhook" || t?.type === "api") && t.public && a?.kind === "loop")
|
|
1225
|
+
errs.push("a public webhook/api may not use a loop action (unauthenticated task injection) \u2014 use a message action or require a key");
|
|
1226
|
+
return errs;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
const genId$1 = () => "c_" + randomBytes(5).toString("hex");
|
|
1230
|
+
const genKey$1 = () => "ck_" + randomBytes(18).toString("base64url");
|
|
1231
|
+
const DEFAULT_TEMPLATE = `<inbound-message from="\${sender.name}" sender-type="\${sender.kind}" verified="\${sender.verified}" channel="\${channel.name}" call-id="\${call.id}" at="\${now}">
|
|
1232
|
+
\${body.message}
|
|
1233
|
+
</inbound-message>`;
|
|
1234
|
+
function validateChannel(c) {
|
|
1235
|
+
const errs = [];
|
|
1236
|
+
if (!c || typeof c !== "object") return ["channel must be an object"];
|
|
1237
|
+
if (!c.name) errs.push("name required");
|
|
1238
|
+
const m = c.identity?.mode;
|
|
1239
|
+
if (!["per-key", "caller-supplied", "fixed"].includes(m)) errs.push("identity.mode must be per-key|caller-supplied|fixed");
|
|
1240
|
+
if (m === "fixed" && !c.identity.fixed?.name) errs.push("identity.fixed.name required for fixed mode");
|
|
1241
|
+
if (!["message", "loop", "agent"].includes(c.action?.kind)) errs.push("action.kind must be message|loop|agent");
|
|
1242
|
+
const b = c.bind;
|
|
1243
|
+
const bindOk = b === "dynamic" || b === "stateless" || b && typeof b.session === "string" && !!b.session;
|
|
1244
|
+
if (!bindOk) errs.push('bind must be "dynamic", "stateless", or { session }');
|
|
1245
|
+
if (b === "stateless" && c.reply?.mode === "queue")
|
|
1246
|
+
errs.push('a stateless channel cannot use reply.mode "queue" (no persistent session to answer later)');
|
|
1247
|
+
if (b === "stateless" && (c.action?.kind === "loop" || c.action?.kind === "agent"))
|
|
1248
|
+
errs.push(`a stateless channel cannot use a ${c.action?.kind} action (needs a live session); use dynamic or { session }`);
|
|
1249
|
+
if (c.action?.kind === "loop" && m === "caller-supplied" && !c.identity?.shared_key)
|
|
1250
|
+
errs.push("a caller-supplied channel without a shared_key may not use a loop action (unauthenticated task injection)");
|
|
1251
|
+
if (c.action?.kind === "agent" && m === "caller-supplied" && !c.identity?.shared_key) {
|
|
1252
|
+
const MUTATING = ["run_bash", "send_to_session"];
|
|
1253
|
+
const ag = c.action.agent || {};
|
|
1254
|
+
const grantsMutating = (ag.tools || []).some((t) => MUTATING.includes(t)) || Object.values(ag.per_caller || {}).some((p) => (p?.tools || []).some((t) => MUTATING.includes(t)));
|
|
1255
|
+
if (grantsMutating) errs.push("a caller-supplied agent channel without a shared_key may not grant run_bash/send_to_session");
|
|
1256
|
+
}
|
|
1257
|
+
const unsafe = /[<>"'&\r\n]/;
|
|
1258
|
+
if (unsafe.test(c.name || "")) errs.push(`name must be single-line and not contain < > " ' &`);
|
|
1259
|
+
if (c.description && unsafe.test(c.description)) errs.push(`description must be single-line and not contain < > " ' &`);
|
|
1260
|
+
if (c.skill?.name && unsafe.test(c.skill.name)) errs.push(`skill.name must be single-line and not contain < > " ' &`);
|
|
1261
|
+
if (c.skill?.description && unsafe.test(c.skill.description)) errs.push(`skill.description must be single-line and not contain < > " ' &`);
|
|
1262
|
+
if (m === "fixed" && c.identity.fixed?.name && unsafe.test(c.identity.fixed.name)) errs.push(`identity.fixed.name must not contain < > " ' & or newlines`);
|
|
1263
|
+
for (const cl of c.identity?.callers || []) if (unsafe.test(cl.name || "")) errs.push(`caller name "${cl.name}" must not contain < > " ' & or newlines`);
|
|
1264
|
+
return errs;
|
|
1265
|
+
}
|
|
1266
|
+
function normalizeBind(c) {
|
|
1267
|
+
const b = c.bind;
|
|
1268
|
+
if (b === "dynamic" || b === "stateless" || b && typeof b.session === "string" && b.session) return c;
|
|
1269
|
+
c.bind = "dynamic";
|
|
1270
|
+
return c;
|
|
1271
|
+
}
|
|
1272
|
+
function bindMode(c) {
|
|
1273
|
+
const b = normalizeBind(c).bind;
|
|
1274
|
+
if (b === "stateless") return "stateless";
|
|
1275
|
+
if (b && typeof b.session === "string") return "fixed";
|
|
1276
|
+
return "dynamic";
|
|
1277
|
+
}
|
|
1278
|
+
function fixedSessionId(c) {
|
|
1279
|
+
const b = c.bind;
|
|
1280
|
+
return b && typeof b.session === "string" ? b.session : void 0;
|
|
1281
|
+
}
|
|
1282
|
+
class ChannelStore {
|
|
1283
|
+
dir;
|
|
1284
|
+
constructor(projectDir) {
|
|
1285
|
+
this.dir = join(projectDir, ".svamp", "channels");
|
|
1286
|
+
try {
|
|
1287
|
+
mkdirSync(this.dir, { recursive: true });
|
|
1288
|
+
} catch {
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
_path(id) {
|
|
1292
|
+
return join(this.dir, `${id}.json`);
|
|
1293
|
+
}
|
|
1294
|
+
list() {
|
|
1295
|
+
if (!existsSync(this.dir)) return [];
|
|
1296
|
+
return readdirSync(this.dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
1297
|
+
try {
|
|
1298
|
+
return normalizeBind(JSON.parse(readFileSync(join(this.dir, f), "utf8")));
|
|
1299
|
+
} catch {
|
|
1300
|
+
return null;
|
|
1301
|
+
}
|
|
1302
|
+
}).filter((c) => !!c);
|
|
1303
|
+
}
|
|
1304
|
+
get(id) {
|
|
1305
|
+
try {
|
|
1306
|
+
return normalizeBind(JSON.parse(readFileSync(this._path(id), "utf8")));
|
|
1307
|
+
} catch {
|
|
1308
|
+
return null;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
save(channel) {
|
|
1312
|
+
const c = { enabled: true, bind: "dynamic", template: DEFAULT_TEMPLATE, last_calls: [], ...channel };
|
|
1313
|
+
if (!c.id) c.id = genId$1();
|
|
1314
|
+
const errs = validateChannel(c);
|
|
1315
|
+
if (errs.length) throw new Error("invalid channel: " + errs.join("; "));
|
|
1316
|
+
mkdirSync(this.dir, { recursive: true });
|
|
1317
|
+
const tmp = this._path(c.id) + ".tmp";
|
|
1318
|
+
writeFileSync(tmp, JSON.stringify(c, null, 2));
|
|
1319
|
+
renameSync(tmp, this._path(c.id));
|
|
1320
|
+
return c;
|
|
1321
|
+
}
|
|
1322
|
+
remove(id) {
|
|
1323
|
+
const p = this._path(id);
|
|
1324
|
+
if (existsSync(p)) {
|
|
1325
|
+
rmSync(p);
|
|
1326
|
+
return true;
|
|
1327
|
+
}
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
setEnabled(id, enabled) {
|
|
1331
|
+
const c = this.get(id);
|
|
1332
|
+
if (!c) return null;
|
|
1333
|
+
c.enabled = enabled;
|
|
1334
|
+
return this.save(c);
|
|
1335
|
+
}
|
|
1336
|
+
recordCall(id, entry) {
|
|
1337
|
+
const c = this.get(id);
|
|
1338
|
+
if (!c) return;
|
|
1339
|
+
c.last_calls = c.last_calls || [];
|
|
1340
|
+
c.last_calls.unshift({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry });
|
|
1341
|
+
c.last_calls = c.last_calls.slice(0, 20);
|
|
1342
|
+
this.save(c);
|
|
1343
|
+
}
|
|
1344
|
+
addCaller(id, name, kind = "agent") {
|
|
1345
|
+
const c = this.get(id);
|
|
1346
|
+
if (!c) return null;
|
|
1347
|
+
c.identity.callers = c.identity.callers || [];
|
|
1348
|
+
const caller = { name, kind, key: genKey$1() };
|
|
1349
|
+
c.identity.callers.push(caller);
|
|
1350
|
+
this.save(c);
|
|
1351
|
+
return caller;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
function routingSession(channel, ctx) {
|
|
1355
|
+
const mode = bindMode(channel);
|
|
1356
|
+
if (mode === "fixed") return fixedSessionId(channel);
|
|
1357
|
+
if (mode === "dynamic") return ctx?.session;
|
|
1358
|
+
return void 0;
|
|
1359
|
+
}
|
|
1360
|
+
function gatewayBase(channelsServiceId, baseUrl) {
|
|
1361
|
+
const slash = channelsServiceId.indexOf("/");
|
|
1362
|
+
if (slash < 0) return `${baseUrl.replace(/\/$/, "")}/services/${channelsServiceId}`;
|
|
1363
|
+
const ws = channelsServiceId.slice(0, slash);
|
|
1364
|
+
const clientSvc = channelsServiceId.slice(slash + 1);
|
|
1365
|
+
return `${baseUrl.replace(/\/$/, "")}/${ws}/services/${clientSvc}`;
|
|
1366
|
+
}
|
|
1367
|
+
function generateSkillBody(channel, ctx) {
|
|
1368
|
+
const svc = ctx?.channelsServiceId || "<workspace>/<machine>:channels";
|
|
1369
|
+
const base = ctx?.baseUrl || "https://hypha.aicell.io";
|
|
1370
|
+
const gw = ctx?.channelsServiceId ? gatewayBase(svc, base) : `${base}/<workspace>/services/<machine>:channels`;
|
|
1371
|
+
const skillUrl = `${gw}/skill?channel=${channel.id}`;
|
|
1372
|
+
const sendUrl = `${gw}/send`;
|
|
1373
|
+
const key = ctx?.key || "<your-key>";
|
|
1374
|
+
const isAgent = channel.action?.kind === "agent";
|
|
1375
|
+
const isQueue = channel.reply?.mode === "queue";
|
|
1376
|
+
const recvUrl = `${gw}/receive`;
|
|
1377
|
+
const hyphaOpen = (channel.identity?.hypha_allow || []).length > 0;
|
|
1378
|
+
const mode = bindMode(channel);
|
|
1379
|
+
const rSession = routingSession(channel, ctx);
|
|
1380
|
+
const sessionLineJs = rSession ? `
|
|
1381
|
+
session: "${rSession}",` : "";
|
|
1382
|
+
const sessionKv = rSession ? `, "session": "${rSession}"` : "";
|
|
1383
|
+
const bindNote = mode === "stateless" ? `**Stateless channel** \u2014 each call spawns a fresh, isolated one-shot session (cold-start: a few seconds), runs, replies, and closes. No shared memory between calls.` : mode === "fixed" ? `Routes to a **fixed session** (\`${rSession}\`); reachable only while that session is live.` : rSession ? `Routes to **session \`${rSession}\`** (the one this instruction was copied from); reachable only while it is live, else a fresh stateless run answers.` : `**Dynamic channel** \u2014 pass \`session: "<id>"\` to target a specific live session, else a fresh stateless run answers.`;
|
|
1384
|
+
const name = channel.skill?.name || channel.name;
|
|
1385
|
+
const desc = channel.skill?.description || channel.description || `Send a message to the "${channel.name}" channel.`;
|
|
1386
|
+
const replyNote = isAgent ? `This is a **WISE Agent** channel: \`send()\` runs a fast assistant against the session's tools/skills and returns its answer synchronously in the result \`reply\`.` : isQueue ? `This is an **async** channel: \`send()\` returns a \`correlationId\` and the agent replies later \u2014 poll \`receive()\` (or GET /receive \xB7 /events) for replies addressed to you.` : `Delivery is fire-and-forget \u2014 the message lands in the agent's inbox, tagged with your verified identity (\`from\`).`;
|
|
1387
|
+
const queueSection = isQueue ? `
|
|
1388
|
+
|
|
1389
|
+
## Getting the reply (async)
|
|
1390
|
+
\`send()\` returns \`{ correlationId }\`. The agent answers later; retrieve replies addressed
|
|
1391
|
+
to you by **long-polling** \`receive\` with a cursor you advance each call:
|
|
1392
|
+
\`\`\`js
|
|
1393
|
+
const { correlationId } = await get_service("${svc}").send({ channel: "${channel.id}", message: "\u2026", from: "your-name" });
|
|
1394
|
+
let cursor = 0;
|
|
1395
|
+
while (true) {
|
|
1396
|
+
const r = await get_service("${svc}").receive({ channel: "${channel.id}", key: "${key}", cursor, wait: 25 });
|
|
1397
|
+
cursor = r.cursor;
|
|
1398
|
+
for (const reply of r.replies) if (reply.correlationId === correlationId) return reply.body;
|
|
1399
|
+
}
|
|
1400
|
+
\`\`\`
|
|
1401
|
+
**HTTP:** \`POST ${recvUrl}\` with \`{"kwargs": {"channel": "${channel.id}", "key": "${key}", "cursor": 0, "wait": 25}}\` (long-poll), or stream \`GET <channel-http>/channel/${channel.id}/events?key=${key}\` (SSE).` : "";
|
|
1402
|
+
const rpcLine = hyphaOpen ? `**Hypha RPC** \u2014 preferred. Your verified Hypha identity is accepted, no key needed:` : `**Hypha RPC** \u2014 verified identity:`;
|
|
1403
|
+
return `---
|
|
1404
|
+
name: ${name}
|
|
1405
|
+
description: ${desc}
|
|
1406
|
+
---
|
|
1407
|
+
# ${name}
|
|
1408
|
+
${channel.description || ""}
|
|
1409
|
+
|
|
1410
|
+
Self-contained guide for messaging the **${channel.name}** channel. ${replyNote}
|
|
1411
|
+
${bindNote}
|
|
1412
|
+
This skill (with every value below already filled in) is served at:
|
|
1413
|
+
${skillUrl}
|
|
1414
|
+
|
|
1415
|
+
${rpcLine}
|
|
1416
|
+
\`\`\`js
|
|
1417
|
+
await get_service("${svc}").send({
|
|
1418
|
+
channel: "${channel.id}",
|
|
1419
|
+
message: "your message here",
|
|
1420
|
+
from: "your-name",${sessionLineJs}
|
|
1421
|
+
});
|
|
1422
|
+
\`\`\`
|
|
1423
|
+
|
|
1424
|
+
**HTTP** \u2014 any client, no Hypha SDK needed (Hypha gateway wraps args under \`kwargs\`):
|
|
1425
|
+
\`\`\`
|
|
1426
|
+
POST ${sendUrl}
|
|
1427
|
+
Content-Type: application/json
|
|
1428
|
+
|
|
1429
|
+
{"kwargs": {"channel": "${channel.id}", "message": "your message here", "from": "your-name", "key": "${key}"${sessionKv}}}
|
|
1430
|
+
\`\`\`${queueSection}`;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
function resolveSender(channel, input = {}) {
|
|
1434
|
+
const { key, from, hyphaUser, hyphaWorkspace, hyphaAnonymous } = input;
|
|
1435
|
+
const id = channel.identity || {};
|
|
1436
|
+
if (hyphaUser && !hyphaAnonymous && Array.isArray(id.hypha_allow) && id.hypha_allow.length) {
|
|
1437
|
+
if (id.hypha_allow.includes("*") || id.hypha_allow.includes(hyphaUser) || hyphaWorkspace && id.hypha_allow.includes(hyphaWorkspace))
|
|
1438
|
+
return { sender: { name: hyphaUser, kind: "agent", verified: true } };
|
|
1439
|
+
return { error: "caller not in hypha_allow" };
|
|
1440
|
+
}
|
|
1441
|
+
if (id.mode === "fixed") {
|
|
1442
|
+
if (!id.fixed?.name) return { error: "fixed identity not configured" };
|
|
1443
|
+
return { sender: { name: id.fixed.name, kind: id.fixed.kind, verified: true } };
|
|
1444
|
+
}
|
|
1445
|
+
if (id.mode === "per-key") {
|
|
1446
|
+
const caller = (id.callers || []).find((c) => c.key && c.key === key);
|
|
1447
|
+
if (!caller) return { error: "invalid or missing key" };
|
|
1448
|
+
return { sender: { name: caller.name, kind: caller.kind, verified: true } };
|
|
1449
|
+
}
|
|
1450
|
+
if (id.mode === "caller-supplied") {
|
|
1451
|
+
if (id.shared_key && key !== id.shared_key) return { error: "invalid key" };
|
|
1452
|
+
return { sender: { name: from || "anonymous", kind: "user", verified: false } };
|
|
1453
|
+
}
|
|
1454
|
+
return { error: "unsupported identity mode" };
|
|
1455
|
+
}
|
|
1456
|
+
const MAX_BODY = 16 * 1024;
|
|
1457
|
+
function xmlEscape(s) {
|
|
1458
|
+
return String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1459
|
+
}
|
|
1460
|
+
const stripControl = (s) => String(s ?? "").replace(/[\x00-\x1f\x7f]/g, " ");
|
|
1461
|
+
function renderMessage(channel, { sender = {}, body = {}, query = {}, callId, now }) {
|
|
1462
|
+
const obj = (v) => typeof v === "object" && v !== null ? JSON.stringify(v) : v;
|
|
1463
|
+
const escVal = (v) => xmlEscape(obj(v));
|
|
1464
|
+
const escAttr = (v) => xmlEscape(stripControl(obj(v)));
|
|
1465
|
+
const bodyEsc = {};
|
|
1466
|
+
for (const [k, v] of Object.entries(body)) bodyEsc[k] = k === "message" ? escVal(String(v ?? "").slice(0, MAX_BODY)) : escAttr(v);
|
|
1467
|
+
const queryEsc = {};
|
|
1468
|
+
for (const [k, v] of Object.entries(query)) if (k !== "key") queryEsc[k] = escAttr(v);
|
|
1469
|
+
const ctx = {
|
|
1470
|
+
sender: { name: escAttr(sender.name), kind: escAttr(sender.kind), verified: sender.verified === true },
|
|
1471
|
+
body: bodyEsc,
|
|
1472
|
+
query: queryEsc,
|
|
1473
|
+
channel: { name: escAttr(channel.name), id: escAttr(channel.id) },
|
|
1474
|
+
call: { id: escAttr(callId) },
|
|
1475
|
+
now: escAttr(now || (/* @__PURE__ */ new Date()).toISOString())
|
|
1476
|
+
};
|
|
1477
|
+
return renderTemplate(channel.template || DEFAULT_TEMPLATE, ctx);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1124
1480
|
function getParamNames(fn) {
|
|
1125
1481
|
const src = fn.toString();
|
|
1126
1482
|
const match = src.match(/^(?:async\s+)?(?:function\s*\w*)?\s*\(([^)]*)\)/);
|
|
@@ -1151,7 +1507,7 @@ function filterTerminalResponses(data) {
|
|
|
1151
1507
|
return filtered;
|
|
1152
1508
|
}
|
|
1153
1509
|
function getMachineMetadataPath(svampHomeDir) {
|
|
1154
|
-
return join(svampHomeDir, "machine-metadata.json");
|
|
1510
|
+
return join$1(svampHomeDir, "machine-metadata.json");
|
|
1155
1511
|
}
|
|
1156
1512
|
async function mintRealtimeEphemeralKey(baseUrl, apiKey, opts) {
|
|
1157
1513
|
const realtimeBase = baseUrl || "https://api.openai.com";
|
|
@@ -1212,11 +1568,11 @@ function loadPersistedMachineMetadata(svampHomeDir) {
|
|
|
1212
1568
|
}
|
|
1213
1569
|
function savePersistedMachineMetadata(svampHomeDir, data) {
|
|
1214
1570
|
try {
|
|
1215
|
-
mkdirSync(svampHomeDir, { recursive: true });
|
|
1571
|
+
mkdirSync$1(svampHomeDir, { recursive: true });
|
|
1216
1572
|
const filePath = getMachineMetadataPath(svampHomeDir);
|
|
1217
1573
|
const tmpPath = filePath + ".tmp";
|
|
1218
|
-
writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
1219
|
-
renameSync(tmpPath, filePath);
|
|
1574
|
+
writeFileSync$1(tmpPath, JSON.stringify(data, null, 2));
|
|
1575
|
+
renameSync$1(tmpPath, filePath);
|
|
1220
1576
|
} catch (err) {
|
|
1221
1577
|
console.error("[HYPHA MACHINE] Failed to persist machine metadata:", err);
|
|
1222
1578
|
}
|
|
@@ -1663,11 +2019,11 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
1663
2019
|
if (newSharing.enabled && !newSharing.owner && context?.user?.email) {
|
|
1664
2020
|
newSharing = { ...newSharing, owner: context.user.email };
|
|
1665
2021
|
}
|
|
1666
|
-
const
|
|
2022
|
+
const ownerEmail2 = newSharing.owner || context?.user?.email || "";
|
|
1667
2023
|
newSharing = {
|
|
1668
2024
|
...newSharing,
|
|
1669
2025
|
allowedUsers: (newSharing.allowedUsers || []).map(
|
|
1670
|
-
(u) => normalizeAllowedUser(u,
|
|
2026
|
+
(u) => normalizeAllowedUser(u, ownerEmail2)
|
|
1671
2027
|
),
|
|
1672
2028
|
publicAccess: null
|
|
1673
2029
|
};
|
|
@@ -2166,7 +2522,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2166
2522
|
const tunnels = handlers.tunnels;
|
|
2167
2523
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
2168
2524
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
2169
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
2525
|
+
const { FrpcTunnel } = await import('./frpc-B8ORdlOO.mjs');
|
|
2170
2526
|
const tunnel = new FrpcTunnel({
|
|
2171
2527
|
name: params.name,
|
|
2172
2528
|
ports: params.ports,
|
|
@@ -2222,9 +2578,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2222
2578
|
authorizeRequest(context, currentMetadata.sharing, "interact");
|
|
2223
2579
|
const sm = handlers.serveManager;
|
|
2224
2580
|
if (!sm) throw new Error("Serve manager not available");
|
|
2225
|
-
const
|
|
2581
|
+
const ownerEmail2 = params.ownerEmail || context?.user?.email || void 0;
|
|
2226
2582
|
const access = params.access || "owner";
|
|
2227
|
-
return sm.addMount(params.name, params.directory, params.sessionId, access,
|
|
2583
|
+
return sm.addMount(params.name, params.directory, params.sessionId, access, ownerEmail2);
|
|
2228
2584
|
},
|
|
2229
2585
|
/**
|
|
2230
2586
|
* Apply a mount declaratively. Replaces existing mount with same name.
|
|
@@ -2235,7 +2591,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2235
2591
|
authorizeRequest(context, currentMetadata.sharing, "interact");
|
|
2236
2592
|
const sm = handlers.serveManager;
|
|
2237
2593
|
if (!sm) throw new Error("Serve manager not available");
|
|
2238
|
-
const
|
|
2594
|
+
const ownerEmail2 = params.ownerEmail || context?.user?.email || void 0;
|
|
2239
2595
|
const access = params.access ?? "owner";
|
|
2240
2596
|
return sm.applyMount({
|
|
2241
2597
|
name: params.name,
|
|
@@ -2243,7 +2599,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2243
2599
|
process: params.process,
|
|
2244
2600
|
sessionId: params.sessionId,
|
|
2245
2601
|
access,
|
|
2246
|
-
ownerEmail
|
|
2602
|
+
ownerEmail: ownerEmail2
|
|
2247
2603
|
});
|
|
2248
2604
|
},
|
|
2249
2605
|
/** Remove a mount from the shared static file server. */
|
|
@@ -2427,7 +2783,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
2427
2783
|
}
|
|
2428
2784
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
2429
2785
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
2430
|
-
const { toolsForRole } = await import('./sideband-
|
|
2786
|
+
const { toolsForRole } = await import('./sideband-CO0bdYO_.mjs');
|
|
2431
2787
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
2432
2788
|
return fmt(r2);
|
|
2433
2789
|
}
|
|
@@ -2462,6 +2818,89 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
2462
2818
|
}
|
|
2463
2819
|
return void 0;
|
|
2464
2820
|
};
|
|
2821
|
+
const readChannelFromDisk = (channelId) => {
|
|
2822
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2823
|
+
for (const t of handlers.getTrackedSessions?.() || []) {
|
|
2824
|
+
const dir = t.directory;
|
|
2825
|
+
if (!dir || seen.has(dir)) continue;
|
|
2826
|
+
seen.add(dir);
|
|
2827
|
+
try {
|
|
2828
|
+
const c = new ChannelStore(dir).get(channelId);
|
|
2829
|
+
if (c && c.enabled !== false) return { c, dir };
|
|
2830
|
+
} catch {
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
return void 0;
|
|
2834
|
+
};
|
|
2835
|
+
const liveSessionsInDir = (dir) => (handlers.getTrackedSessions?.() || []).filter((t) => t.directory === dir && handlers.getSessionRPCHandlers?.(t.sessionId)).map((t) => t.sessionId);
|
|
2836
|
+
const resolveChannel = (channelId, targetSession) => {
|
|
2837
|
+
const found = readChannelFromDisk(channelId);
|
|
2838
|
+
if (!found) return { error: "channel not found" };
|
|
2839
|
+
const { c, dir } = found;
|
|
2840
|
+
const mode = bindMode(c);
|
|
2841
|
+
const kind = c.action?.kind;
|
|
2842
|
+
if (kind === "agent" || kind === "loop") {
|
|
2843
|
+
const prefer = mode === "fixed" ? fixedSessionId(c) : mode === "dynamic" ? targetSession : void 0;
|
|
2844
|
+
const sid = prefer && handlers.getSessionRPCHandlers?.(prefer) ? prefer : liveSessionsInDir(dir)[0];
|
|
2845
|
+
if (!sid) {
|
|
2846
|
+
if (mode === "fixed") return { tier: "session", sessionId: fixedSessionId(c) || "?", stopped: true, c, dir };
|
|
2847
|
+
return { error: `no live session to host this ${kind} channel \u2014 start a session in ${dir}` };
|
|
2848
|
+
}
|
|
2849
|
+
return { tier: "session", sessionId: sid, rpc: handlers.getSessionRPCHandlers(sid), c, dir };
|
|
2850
|
+
}
|
|
2851
|
+
const target = mode === "fixed" ? fixedSessionId(c) : mode === "dynamic" ? targetSession : void 0;
|
|
2852
|
+
if (target) {
|
|
2853
|
+
const rpc = handlers.getSessionRPCHandlers?.(target);
|
|
2854
|
+
if (rpc) return { tier: "session", sessionId: target, rpc, c, dir };
|
|
2855
|
+
if (mode === "fixed") return { tier: "session", sessionId: target, stopped: true, c, dir };
|
|
2856
|
+
}
|
|
2857
|
+
return { tier: "stateless", c, dir };
|
|
2858
|
+
};
|
|
2859
|
+
const ownerEmail = currentMetadata.sharing?.owner;
|
|
2860
|
+
const ownerCtx = ownerEmail ? { user: { email: ownerEmail, id: ownerEmail } } : void 0;
|
|
2861
|
+
const selfMachine = {
|
|
2862
|
+
spawnSession: (opts) => handlers.spawnSession(opts),
|
|
2863
|
+
sessionRPC: async (sessionId, method, kwargs) => {
|
|
2864
|
+
const rpc = handlers.getSessionRPCHandlers?.(sessionId);
|
|
2865
|
+
if (!rpc) throw new Error(`Session ${sessionId} not found on this machine`);
|
|
2866
|
+
const handler = rpc[method];
|
|
2867
|
+
if (typeof handler !== "function") throw new Error(`Unknown session method: ${method}`);
|
|
2868
|
+
const paramNames = getParamNames(handler);
|
|
2869
|
+
const callArgs = paramNames.map((n) => n === "context" ? ownerCtx : kwargs?.[n] ?? void 0);
|
|
2870
|
+
return handler(...callArgs);
|
|
2871
|
+
}
|
|
2872
|
+
};
|
|
2873
|
+
const dispatchStateless = async (c, dir, kwargs, context) => {
|
|
2874
|
+
const u = context?.user;
|
|
2875
|
+
const r = resolveSender(c, {
|
|
2876
|
+
key: kwargs.key,
|
|
2877
|
+
from: kwargs.from,
|
|
2878
|
+
hyphaUser: u && u.is_anonymous !== true ? u.email || u.id : void 0,
|
|
2879
|
+
hyphaAnonymous: u?.is_anonymous === true,
|
|
2880
|
+
hyphaWorkspace: u?.scope?.current_workspace
|
|
2881
|
+
});
|
|
2882
|
+
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
2883
|
+
const callId = "call_" + Math.random().toString(16).slice(2, 12);
|
|
2884
|
+
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
2885
|
+
const { queryCore } = await import('./commands-o0MBJocy.mjs');
|
|
2886
|
+
const timeout = c.reply?.timeout_sec || 120;
|
|
2887
|
+
let result;
|
|
2888
|
+
try {
|
|
2889
|
+
result = await queryCore(selfMachine, dir, rendered, { permissionMode: "bypassPermissions", tag: "svamp-channel", timeout });
|
|
2890
|
+
} catch (e) {
|
|
2891
|
+
return { ok: false, call_id: callId, status: "error", error: e?.message || String(e) };
|
|
2892
|
+
} finally {
|
|
2893
|
+
if (result?.sessionId) {
|
|
2894
|
+
try {
|
|
2895
|
+
handlers.deleteSession?.(result.sessionId);
|
|
2896
|
+
} catch {
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
if (result.status === "error") return { ok: false, call_id: callId, status: "error", error: result.error };
|
|
2901
|
+
if (result.status === "permission-pending") return { ok: false, call_id: callId, status: "permission-pending", error: result.error };
|
|
2902
|
+
return { ok: true, call_id: callId, status: "completed", reply: result.response };
|
|
2903
|
+
};
|
|
2465
2904
|
const channelsServiceInfo = await server.registerService(
|
|
2466
2905
|
{
|
|
2467
2906
|
id: "channels",
|
|
@@ -2501,179 +2940,79 @@ ${d?.error || "not found"}`;
|
|
|
2501
2940
|
},
|
|
2502
2941
|
send: async (kwargs = {}, context) => {
|
|
2503
2942
|
trackInbound();
|
|
2504
|
-
const
|
|
2505
|
-
if (
|
|
2506
|
-
|
|
2943
|
+
const res = resolveChannel(kwargs.channel, kwargs.session);
|
|
2944
|
+
if ("error" in res) return { error: res.error };
|
|
2945
|
+
if (res.tier === "stateless") return dispatchStateless(res.c, res.dir, kwargs, context);
|
|
2946
|
+
if ("stopped" in res) return { error: `session ${res.sessionId.slice(0, 8)} is stopped \u2014 resume it to reach this channel` };
|
|
2947
|
+
return res.rpc.channelSend({ channel: kwargs.channel, message: kwargs.message, from: kwargs.from, key: kwargs.key, session: kwargs.session, reply_to: kwargs.reply_to }, context);
|
|
2507
2948
|
},
|
|
2508
2949
|
// Async reply retrieval for queue-mode channels — long-poll the channel outbox
|
|
2509
|
-
// for replies addressed to the caller.
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
}
|
|
2553
|
-
|
|
2554
|
-
const FIELD_RANGES = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
|
|
2555
|
-
function parseField(token, [min, max]) {
|
|
2556
|
-
const set = /* @__PURE__ */ new Set();
|
|
2557
|
-
for (const part of token.split(",")) {
|
|
2558
|
-
let m;
|
|
2559
|
-
if (part === "*") {
|
|
2560
|
-
for (let i = min; i <= max; i++) set.add(i);
|
|
2561
|
-
} else if (m = part.match(/^\*\/(\d+)$/)) {
|
|
2562
|
-
const s = +m[1];
|
|
2563
|
-
for (let i = min; i <= max; i += s) set.add(i);
|
|
2564
|
-
} else if (m = part.match(/^(\d+)-(\d+)\/(\d+)$/)) {
|
|
2565
|
-
for (let i = +m[1]; i <= +m[2]; i += +m[3]) set.add(i);
|
|
2566
|
-
} else if (m = part.match(/^(\d+)-(\d+)$/)) {
|
|
2567
|
-
for (let i = +m[1]; i <= +m[2]; i++) set.add(i);
|
|
2568
|
-
} else if (m = part.match(/^(\d+)$/)) {
|
|
2569
|
-
set.add(+m[1]);
|
|
2570
|
-
} else throw new Error(`invalid cron field: "${token}"`);
|
|
2571
|
-
}
|
|
2572
|
-
for (const v of set) if (v < min || v > max) throw new Error(`cron value ${v} out of range [${min},${max}]`);
|
|
2573
|
-
return set;
|
|
2574
|
-
}
|
|
2575
|
-
function parseCron(expr) {
|
|
2576
|
-
const fields = String(expr).trim().split(/\s+/);
|
|
2577
|
-
if (fields.length !== 5) throw new Error(`cron must have 5 fields, got ${fields.length}: "${expr}"`);
|
|
2578
|
-
const [minute, hour, dom, month, dow] = fields.map((f, i) => parseField(f, FIELD_RANGES[i]));
|
|
2579
|
-
return { minute, hour, dom, month, dow, domRestricted: fields[2] !== "*", dowRestricted: fields[4] !== "*" };
|
|
2580
|
-
}
|
|
2581
|
-
function cronMatches(expr, date) {
|
|
2582
|
-
const c = typeof expr === "string" ? parseCron(expr) : expr;
|
|
2583
|
-
if (!c.minute.has(date.getMinutes())) return false;
|
|
2584
|
-
if (!c.hour.has(date.getHours())) return false;
|
|
2585
|
-
if (!c.month.has(date.getMonth() + 1)) return false;
|
|
2586
|
-
const domOk = c.dom.has(date.getDate());
|
|
2587
|
-
const dowOk = c.dow.has(date.getDay());
|
|
2588
|
-
if (c.domRestricted && c.dowRestricted) return domOk || dowOk;
|
|
2589
|
-
return domOk && dowOk;
|
|
2590
|
-
}
|
|
2591
|
-
function inZone(date, tz) {
|
|
2592
|
-
if (!tz) return date;
|
|
2593
|
-
try {
|
|
2594
|
-
const p = new Intl.DateTimeFormat("en-US", {
|
|
2595
|
-
timeZone: tz,
|
|
2596
|
-
hour12: false,
|
|
2597
|
-
year: "numeric",
|
|
2598
|
-
month: "2-digit",
|
|
2599
|
-
day: "2-digit",
|
|
2600
|
-
hour: "2-digit",
|
|
2601
|
-
minute: "2-digit"
|
|
2602
|
-
}).formatToParts(date).reduce((o, x) => {
|
|
2603
|
-
o[x.type] = x.value;
|
|
2604
|
-
return o;
|
|
2605
|
-
}, {});
|
|
2606
|
-
return new Date(+p.year, +p.month - 1, +p.day, +(p.hour === "24" ? 0 : p.hour), +p.minute);
|
|
2607
|
-
} catch {
|
|
2608
|
-
return date;
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
function nextFire(expr, from, tz) {
|
|
2612
|
-
const c = parseCron(expr);
|
|
2613
|
-
const d = new Date(from.getTime());
|
|
2614
|
-
d.setSeconds(0, 0);
|
|
2615
|
-
d.setMinutes(d.getMinutes() + 1);
|
|
2616
|
-
for (let i = 0; i < 366 * 24 * 60; i++) {
|
|
2617
|
-
if (cronMatches(c, tz ? inZone(d, tz) : d)) return new Date(d.getTime());
|
|
2618
|
-
d.setMinutes(d.getMinutes() + 1);
|
|
2619
|
-
}
|
|
2620
|
-
return null;
|
|
2621
|
-
}
|
|
2622
|
-
function resolvePath(ctx, path) {
|
|
2623
|
-
return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], ctx);
|
|
2624
|
-
}
|
|
2625
|
-
function renderTemplate(template, ctx) {
|
|
2626
|
-
return String(template).replace(/\$\{([\w.$]+)\}/g, (_m, p) => {
|
|
2627
|
-
const v = resolvePath(ctx, p);
|
|
2628
|
-
return v == null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
2629
|
-
});
|
|
2630
|
-
}
|
|
2631
|
-
const TRIGGER_TYPES = ["manual", "schedule", "webhook", "api"];
|
|
2632
|
-
const ACTION_KINDS = ["message", "loop"];
|
|
2633
|
-
const OVERLAP = ["queue", "skip", "replace"];
|
|
2634
|
-
function validateRoutine(r) {
|
|
2635
|
-
const errs = [];
|
|
2636
|
-
if (!r || typeof r !== "object") return ["routine must be an object"];
|
|
2637
|
-
if (!r.session_id) errs.push("session_id required");
|
|
2638
|
-
if (!r.name) errs.push("name required");
|
|
2639
|
-
const t = r.trigger;
|
|
2640
|
-
if (!t || !TRIGGER_TYPES.includes(t.type)) errs.push(`trigger.type must be one of ${TRIGGER_TYPES.join("|")}`);
|
|
2641
|
-
if (t?.type === "schedule") {
|
|
2642
|
-
try {
|
|
2643
|
-
parseCron(t.cron);
|
|
2644
|
-
} catch (e) {
|
|
2645
|
-
errs.push(`trigger.cron: ${e.message}`);
|
|
2950
|
+
// for replies addressed to the caller. Routed to the session the message landed
|
|
2951
|
+
// in (the `session` target); stateless channels can't queue. Channel-identity auth.
|
|
2952
|
+
receive: async (kwargs = {}, context) => {
|
|
2953
|
+
trackInbound();
|
|
2954
|
+
const res = resolveChannel(kwargs.channel, kwargs.session);
|
|
2955
|
+
if ("error" in res) return { error: res.error };
|
|
2956
|
+
if (res.tier === "stateless") return { error: "stateless channels have no async replies (no persistent session)" };
|
|
2957
|
+
if ("stopped" in res) return { error: `session ${res.sessionId.slice(0, 8)} is stopped` };
|
|
2958
|
+
return res.rpc.channelReceive({ channel: kwargs.channel, key: kwargs.key, from: kwargs.from, cursor: kwargs.cursor, correlationId: kwargs.correlationId, wait: kwargs.wait }, context);
|
|
2959
|
+
}
|
|
2960
|
+
},
|
|
2961
|
+
{ overwrite: true }
|
|
2962
|
+
);
|
|
2963
|
+
console.log(`[HYPHA MACHINE] Channels service registered: ${channelsServiceInfo.id} (type svamp-channels)`);
|
|
2964
|
+
return {
|
|
2965
|
+
serviceInfo,
|
|
2966
|
+
notifySessionEvent: notifyListeners,
|
|
2967
|
+
updateMetadata: (newMetadata) => {
|
|
2968
|
+
currentMetadata = newMetadata;
|
|
2969
|
+
metadataVersion++;
|
|
2970
|
+
notifyListeners({
|
|
2971
|
+
type: "update-machine",
|
|
2972
|
+
machineId,
|
|
2973
|
+
metadata: { value: currentMetadata, version: metadataVersion }
|
|
2974
|
+
});
|
|
2975
|
+
},
|
|
2976
|
+
updateDaemonState: (newState) => {
|
|
2977
|
+
currentDaemonState = newState;
|
|
2978
|
+
daemonStateVersion++;
|
|
2979
|
+
notifyListeners({
|
|
2980
|
+
type: "update-machine",
|
|
2981
|
+
machineId,
|
|
2982
|
+
daemonState: { value: currentDaemonState, version: daemonStateVersion }
|
|
2983
|
+
});
|
|
2984
|
+
},
|
|
2985
|
+
getLastInboundRpcAt: () => lastInboundRpcAt,
|
|
2986
|
+
disconnect: async () => {
|
|
2987
|
+
const toRemove = [...listeners];
|
|
2988
|
+
for (const listener of toRemove) {
|
|
2989
|
+
removeListener(listener, "disconnect");
|
|
2990
|
+
}
|
|
2991
|
+
await server.unregisterService(serviceInfo.id);
|
|
2992
|
+
await server.unregisterService(channelsServiceInfo.id).catch(() => {
|
|
2993
|
+
});
|
|
2646
2994
|
}
|
|
2647
|
-
|
|
2648
|
-
}
|
|
2649
|
-
const a = r.action;
|
|
2650
|
-
if (!a || !ACTION_KINDS.includes(a.kind)) errs.push(`action.kind must be one of ${ACTION_KINDS.join("|")}`);
|
|
2651
|
-
if (a?.kind === "message" && !a.template) errs.push("action.template required for message action");
|
|
2652
|
-
if (a?.kind === "loop" && !a.loop && !a.task_template) errs.push("action.loop or action.task_template required for loop action");
|
|
2653
|
-
if (r.overlap && !OVERLAP.includes(r.overlap)) errs.push(`overlap must be one of ${OVERLAP.join("|")}`);
|
|
2654
|
-
if ((t?.type === "webhook" || t?.type === "api") && t.public && a?.kind === "loop")
|
|
2655
|
-
errs.push("a public webhook/api may not use a loop action (unauthenticated task injection) \u2014 use a message action or require a key");
|
|
2656
|
-
return errs;
|
|
2995
|
+
};
|
|
2657
2996
|
}
|
|
2658
2997
|
|
|
2659
2998
|
function defaultRoutinesDir() {
|
|
2660
|
-
return process.env.SVAMP_ROUTINES_DIR || join
|
|
2999
|
+
return process.env.SVAMP_ROUTINES_DIR || join(homedir(), ".svamp", "routines");
|
|
2661
3000
|
}
|
|
2662
|
-
const genId
|
|
2663
|
-
const genKey
|
|
3001
|
+
const genId = () => "rt_" + randomBytes(5).toString("hex");
|
|
3002
|
+
const genKey = () => randomBytes(18).toString("base64url");
|
|
2664
3003
|
class RoutineStore {
|
|
2665
3004
|
dir;
|
|
2666
3005
|
constructor(dir = defaultRoutinesDir()) {
|
|
2667
3006
|
this.dir = dir;
|
|
2668
|
-
mkdirSync
|
|
3007
|
+
mkdirSync(dir, { recursive: true });
|
|
2669
3008
|
}
|
|
2670
3009
|
_path(id) {
|
|
2671
|
-
return join
|
|
3010
|
+
return join(this.dir, `${id}.json`);
|
|
2672
3011
|
}
|
|
2673
3012
|
list(sessionId) {
|
|
2674
3013
|
const all = readdirSync(this.dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
2675
3014
|
try {
|
|
2676
|
-
return JSON.parse(readFileSync(join
|
|
3015
|
+
return JSON.parse(readFileSync(join(this.dir, f), "utf8"));
|
|
2677
3016
|
} catch {
|
|
2678
3017
|
return null;
|
|
2679
3018
|
}
|
|
@@ -2689,13 +3028,13 @@ class RoutineStore {
|
|
|
2689
3028
|
}
|
|
2690
3029
|
save(routine) {
|
|
2691
3030
|
const r = { overlap: "queue", enabled: true, last_runs: [], ...routine };
|
|
2692
|
-
if (!r.id) r.id = genId
|
|
2693
|
-
if ((r.trigger?.type === "webhook" || r.trigger?.type === "api") && !r.trigger.key) r.trigger.key = genKey
|
|
3031
|
+
if (!r.id) r.id = genId();
|
|
3032
|
+
if ((r.trigger?.type === "webhook" || r.trigger?.type === "api") && !r.trigger.key) r.trigger.key = genKey();
|
|
2694
3033
|
const errs = validateRoutine(r);
|
|
2695
3034
|
if (errs.length) throw new Error("invalid routine: " + errs.join("; "));
|
|
2696
3035
|
const tmp = this._path(r.id) + ".tmp";
|
|
2697
|
-
writeFileSync
|
|
2698
|
-
renameSync
|
|
3036
|
+
writeFileSync(tmp, JSON.stringify(r, null, 2));
|
|
3037
|
+
renameSync(tmp, this._path(r.id));
|
|
2699
3038
|
return r;
|
|
2700
3039
|
}
|
|
2701
3040
|
remove(id) {
|
|
@@ -2851,175 +3190,6 @@ class RoutineRunner {
|
|
|
2851
3190
|
}
|
|
2852
3191
|
}
|
|
2853
3192
|
|
|
2854
|
-
const genId = () => "c_" + randomBytes(5).toString("hex");
|
|
2855
|
-
const genKey = () => "ck_" + randomBytes(18).toString("base64url");
|
|
2856
|
-
const DEFAULT_TEMPLATE = `<inbound-message from="\${sender.name}" sender-type="\${sender.kind}" verified="\${sender.verified}" channel="\${channel.name}" call-id="\${call.id}" at="\${now}">
|
|
2857
|
-
\${body.message}
|
|
2858
|
-
</inbound-message>`;
|
|
2859
|
-
function validateChannel(c) {
|
|
2860
|
-
const errs = [];
|
|
2861
|
-
if (!c || typeof c !== "object") return ["channel must be an object"];
|
|
2862
|
-
if (!c.name) errs.push("name required");
|
|
2863
|
-
const m = c.identity?.mode;
|
|
2864
|
-
if (!["per-key", "caller-supplied", "fixed"].includes(m)) errs.push("identity.mode must be per-key|caller-supplied|fixed");
|
|
2865
|
-
if (m === "fixed" && !c.identity.fixed?.name) errs.push("identity.fixed.name required for fixed mode");
|
|
2866
|
-
if (!["message", "loop", "agent"].includes(c.action?.kind)) errs.push("action.kind must be message|loop|agent");
|
|
2867
|
-
if (c.bind !== "active" && !(c.bind && (c.bind.tag || c.bind.session))) errs.push('bind must be "active", {tag}, or {session}');
|
|
2868
|
-
if (c.action?.kind === "loop" && m === "caller-supplied" && !c.identity?.shared_key)
|
|
2869
|
-
errs.push("a caller-supplied channel without a shared_key may not use a loop action (unauthenticated task injection)");
|
|
2870
|
-
if (c.action?.kind === "agent" && m === "caller-supplied" && !c.identity?.shared_key) {
|
|
2871
|
-
const MUTATING = ["run_bash", "send_to_session"];
|
|
2872
|
-
const ag = c.action.agent || {};
|
|
2873
|
-
const grantsMutating = (ag.tools || []).some((t) => MUTATING.includes(t)) || Object.values(ag.per_caller || {}).some((p) => (p?.tools || []).some((t) => MUTATING.includes(t)));
|
|
2874
|
-
if (grantsMutating) errs.push("a caller-supplied agent channel without a shared_key may not grant run_bash/send_to_session");
|
|
2875
|
-
}
|
|
2876
|
-
const unsafe = /[<>"'&\r\n]/;
|
|
2877
|
-
if (unsafe.test(c.name || "")) errs.push(`name must be single-line and not contain < > " ' &`);
|
|
2878
|
-
if (c.description && unsafe.test(c.description)) errs.push(`description must be single-line and not contain < > " ' &`);
|
|
2879
|
-
if (c.skill?.name && unsafe.test(c.skill.name)) errs.push(`skill.name must be single-line and not contain < > " ' &`);
|
|
2880
|
-
if (c.skill?.description && unsafe.test(c.skill.description)) errs.push(`skill.description must be single-line and not contain < > " ' &`);
|
|
2881
|
-
if (m === "fixed" && c.identity.fixed?.name && unsafe.test(c.identity.fixed.name)) errs.push(`identity.fixed.name must not contain < > " ' & or newlines`);
|
|
2882
|
-
for (const cl of c.identity?.callers || []) if (unsafe.test(cl.name || "")) errs.push(`caller name "${cl.name}" must not contain < > " ' & or newlines`);
|
|
2883
|
-
return errs;
|
|
2884
|
-
}
|
|
2885
|
-
class ChannelStore {
|
|
2886
|
-
dir;
|
|
2887
|
-
constructor(projectDir) {
|
|
2888
|
-
this.dir = join$1(projectDir, ".svamp", "channels");
|
|
2889
|
-
try {
|
|
2890
|
-
mkdirSync$1(this.dir, { recursive: true });
|
|
2891
|
-
} catch {
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
_path(id) {
|
|
2895
|
-
return join$1(this.dir, `${id}.json`);
|
|
2896
|
-
}
|
|
2897
|
-
list() {
|
|
2898
|
-
if (!existsSync(this.dir)) return [];
|
|
2899
|
-
return readdirSync(this.dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
2900
|
-
try {
|
|
2901
|
-
return JSON.parse(readFileSync(join$1(this.dir, f), "utf8"));
|
|
2902
|
-
} catch {
|
|
2903
|
-
return null;
|
|
2904
|
-
}
|
|
2905
|
-
}).filter((c) => !!c);
|
|
2906
|
-
}
|
|
2907
|
-
get(id) {
|
|
2908
|
-
try {
|
|
2909
|
-
return JSON.parse(readFileSync(this._path(id), "utf8"));
|
|
2910
|
-
} catch {
|
|
2911
|
-
return null;
|
|
2912
|
-
}
|
|
2913
|
-
}
|
|
2914
|
-
save(channel) {
|
|
2915
|
-
const c = { enabled: true, bind: "active", template: DEFAULT_TEMPLATE, last_calls: [], ...channel };
|
|
2916
|
-
if (!c.id) c.id = genId();
|
|
2917
|
-
const errs = validateChannel(c);
|
|
2918
|
-
if (errs.length) throw new Error("invalid channel: " + errs.join("; "));
|
|
2919
|
-
mkdirSync$1(this.dir, { recursive: true });
|
|
2920
|
-
const tmp = this._path(c.id) + ".tmp";
|
|
2921
|
-
writeFileSync$1(tmp, JSON.stringify(c, null, 2));
|
|
2922
|
-
renameSync$1(tmp, this._path(c.id));
|
|
2923
|
-
return c;
|
|
2924
|
-
}
|
|
2925
|
-
remove(id) {
|
|
2926
|
-
const p = this._path(id);
|
|
2927
|
-
if (existsSync(p)) {
|
|
2928
|
-
rmSync(p);
|
|
2929
|
-
return true;
|
|
2930
|
-
}
|
|
2931
|
-
return false;
|
|
2932
|
-
}
|
|
2933
|
-
setEnabled(id, enabled) {
|
|
2934
|
-
const c = this.get(id);
|
|
2935
|
-
if (!c) return null;
|
|
2936
|
-
c.enabled = enabled;
|
|
2937
|
-
return this.save(c);
|
|
2938
|
-
}
|
|
2939
|
-
recordCall(id, entry) {
|
|
2940
|
-
const c = this.get(id);
|
|
2941
|
-
if (!c) return;
|
|
2942
|
-
c.last_calls = c.last_calls || [];
|
|
2943
|
-
c.last_calls.unshift({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry });
|
|
2944
|
-
c.last_calls = c.last_calls.slice(0, 20);
|
|
2945
|
-
this.save(c);
|
|
2946
|
-
}
|
|
2947
|
-
addCaller(id, name, kind = "agent") {
|
|
2948
|
-
const c = this.get(id);
|
|
2949
|
-
if (!c) return null;
|
|
2950
|
-
c.identity.callers = c.identity.callers || [];
|
|
2951
|
-
const caller = { name, kind, key: genKey() };
|
|
2952
|
-
c.identity.callers.push(caller);
|
|
2953
|
-
this.save(c);
|
|
2954
|
-
return caller;
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
function gatewayBase(channelsServiceId, baseUrl) {
|
|
2958
|
-
const slash = channelsServiceId.indexOf("/");
|
|
2959
|
-
if (slash < 0) return `${baseUrl.replace(/\/$/, "")}/services/${channelsServiceId}`;
|
|
2960
|
-
const ws = channelsServiceId.slice(0, slash);
|
|
2961
|
-
const clientSvc = channelsServiceId.slice(slash + 1);
|
|
2962
|
-
return `${baseUrl.replace(/\/$/, "")}/${ws}/services/${clientSvc}`;
|
|
2963
|
-
}
|
|
2964
|
-
function generateSkillBody(channel, ctx) {
|
|
2965
|
-
const svc = ctx?.channelsServiceId || "<workspace>/<machine>:channels";
|
|
2966
|
-
const base = ctx?.baseUrl || "https://hypha.aicell.io";
|
|
2967
|
-
const gw = ctx?.channelsServiceId ? gatewayBase(svc, base) : `${base}/<workspace>/services/<machine>:channels`;
|
|
2968
|
-
const skillUrl = `${gw}/skill?channel=${channel.id}`;
|
|
2969
|
-
const sendUrl = `${gw}/send`;
|
|
2970
|
-
const key = ctx?.key || "<your-key>";
|
|
2971
|
-
const isAgent = channel.action?.kind === "agent";
|
|
2972
|
-
const isQueue = channel.reply?.mode === "queue";
|
|
2973
|
-
const recvUrl = `${gw}/receive`;
|
|
2974
|
-
const hyphaOpen = (channel.identity?.hypha_allow || []).length > 0;
|
|
2975
|
-
const name = channel.skill?.name || channel.name;
|
|
2976
|
-
const desc = channel.skill?.description || channel.description || `Send a message to the "${channel.name}" channel.`;
|
|
2977
|
-
const replyNote = isAgent ? `This is a **WISE Agent** channel: \`send()\` runs a fast assistant against the session's tools/skills and returns its answer synchronously in the result \`reply\`.` : isQueue ? `This is an **async** channel: \`send()\` returns a \`correlationId\` and the agent replies later \u2014 poll \`receive()\` (or GET /receive \xB7 /events) for replies addressed to you.` : `Delivery is fire-and-forget \u2014 the message lands in the agent's inbox, tagged with your verified identity (\`from\`).`;
|
|
2978
|
-
const queueSection = isQueue ? `
|
|
2979
|
-
|
|
2980
|
-
## Getting the reply (async)
|
|
2981
|
-
\`send()\` returns \`{ correlationId }\`. The agent answers later; retrieve replies addressed
|
|
2982
|
-
to you by **long-polling** \`receive\` with a cursor you advance each call:
|
|
2983
|
-
\`\`\`js
|
|
2984
|
-
const { correlationId } = await get_service("${svc}").send({ channel: "${channel.id}", message: "\u2026", from: "your-name" });
|
|
2985
|
-
let cursor = 0;
|
|
2986
|
-
while (true) {
|
|
2987
|
-
const r = await get_service("${svc}").receive({ channel: "${channel.id}", key: "${key}", cursor, wait: 25 });
|
|
2988
|
-
cursor = r.cursor;
|
|
2989
|
-
for (const reply of r.replies) if (reply.correlationId === correlationId) return reply.body;
|
|
2990
|
-
}
|
|
2991
|
-
\`\`\`
|
|
2992
|
-
**HTTP:** \`POST ${recvUrl}\` with \`{"kwargs": {"channel": "${channel.id}", "key": "${key}", "cursor": 0, "wait": 25}}\` (long-poll), or stream \`GET <channel-http>/channel/${channel.id}/events?key=${key}\` (SSE).` : "";
|
|
2993
|
-
const rpcLine = hyphaOpen ? `**Hypha RPC** \u2014 preferred. Your verified Hypha identity is accepted, no key needed:` : `**Hypha RPC** \u2014 verified identity:`;
|
|
2994
|
-
return `---
|
|
2995
|
-
name: ${name}
|
|
2996
|
-
description: ${desc}
|
|
2997
|
-
---
|
|
2998
|
-
# ${name}
|
|
2999
|
-
${channel.description || ""}
|
|
3000
|
-
|
|
3001
|
-
Self-contained guide for messaging the **${channel.name}** channel. ${replyNote}
|
|
3002
|
-
This skill (with every value below already filled in) is served at:
|
|
3003
|
-
${skillUrl}
|
|
3004
|
-
|
|
3005
|
-
${rpcLine}
|
|
3006
|
-
\`\`\`js
|
|
3007
|
-
await get_service("${svc}").send({
|
|
3008
|
-
channel: "${channel.id}",
|
|
3009
|
-
message: "your message here",
|
|
3010
|
-
from: "your-name",
|
|
3011
|
-
});
|
|
3012
|
-
\`\`\`
|
|
3013
|
-
|
|
3014
|
-
**HTTP** \u2014 any client, no Hypha SDK needed (Hypha gateway wraps args under \`kwargs\`):
|
|
3015
|
-
\`\`\`
|
|
3016
|
-
POST ${sendUrl}
|
|
3017
|
-
Content-Type: application/json
|
|
3018
|
-
|
|
3019
|
-
{"kwargs": {"channel": "${channel.id}", "message": "your message here", "from": "your-name", "key": "${key}"}}
|
|
3020
|
-
\`\`\`${queueSection}`;
|
|
3021
|
-
}
|
|
3022
|
-
|
|
3023
3193
|
const MAX_PER_CHANNEL = 200;
|
|
3024
3194
|
const TTL_MS = 60 * 60 * 1e3;
|
|
3025
3195
|
class ChannelOutbox {
|
|
@@ -3028,11 +3198,11 @@ class ChannelOutbox {
|
|
|
3028
3198
|
seqByChannel = /* @__PURE__ */ new Map();
|
|
3029
3199
|
emitter = new EventEmitter();
|
|
3030
3200
|
constructor(projectDir) {
|
|
3031
|
-
const dir = join
|
|
3032
|
-
this.file = join
|
|
3201
|
+
const dir = join(projectDir, ".svamp", "channels");
|
|
3202
|
+
this.file = join(dir, "_outbox.jsonl");
|
|
3033
3203
|
this.emitter.setMaxListeners(0);
|
|
3034
3204
|
try {
|
|
3035
|
-
mkdirSync
|
|
3205
|
+
mkdirSync(dir, { recursive: true });
|
|
3036
3206
|
} catch {
|
|
3037
3207
|
}
|
|
3038
3208
|
this._load();
|
|
@@ -3081,7 +3251,7 @@ class ChannelOutbox {
|
|
|
3081
3251
|
appendFileSync(this.file, JSON.stringify({ channelId, ...reply }) + "\n");
|
|
3082
3252
|
} catch {
|
|
3083
3253
|
try {
|
|
3084
|
-
mkdirSync
|
|
3254
|
+
mkdirSync(join(this.file, ".."), { recursive: true });
|
|
3085
3255
|
appendFileSync(this.file, JSON.stringify({ channelId, ...reply }) + "\n");
|
|
3086
3256
|
} catch {
|
|
3087
3257
|
}
|
|
@@ -3145,62 +3315,15 @@ class ChannelOutbox {
|
|
|
3145
3315
|
}
|
|
3146
3316
|
});
|
|
3147
3317
|
const tmp = this.file + ".tmp";
|
|
3148
|
-
writeFileSync
|
|
3149
|
-
renameSync
|
|
3318
|
+
writeFileSync(tmp, kept.join("\n") + (kept.length ? "\n" : ""));
|
|
3319
|
+
renameSync(tmp, this.file);
|
|
3150
3320
|
} catch {
|
|
3151
3321
|
}
|
|
3152
3322
|
}
|
|
3153
3323
|
}
|
|
3154
3324
|
|
|
3155
|
-
function resolveSender(channel, input = {}) {
|
|
3156
|
-
const { key, from, hyphaUser, hyphaWorkspace, hyphaAnonymous } = input;
|
|
3157
|
-
const id = channel.identity || {};
|
|
3158
|
-
if (hyphaUser && !hyphaAnonymous && Array.isArray(id.hypha_allow) && id.hypha_allow.length) {
|
|
3159
|
-
if (id.hypha_allow.includes("*") || id.hypha_allow.includes(hyphaUser) || hyphaWorkspace && id.hypha_allow.includes(hyphaWorkspace))
|
|
3160
|
-
return { sender: { name: hyphaUser, kind: "agent", verified: true } };
|
|
3161
|
-
return { error: "caller not in hypha_allow" };
|
|
3162
|
-
}
|
|
3163
|
-
if (id.mode === "fixed") {
|
|
3164
|
-
if (!id.fixed?.name) return { error: "fixed identity not configured" };
|
|
3165
|
-
return { sender: { name: id.fixed.name, kind: id.fixed.kind, verified: true } };
|
|
3166
|
-
}
|
|
3167
|
-
if (id.mode === "per-key") {
|
|
3168
|
-
const caller = (id.callers || []).find((c) => c.key && c.key === key);
|
|
3169
|
-
if (!caller) return { error: "invalid or missing key" };
|
|
3170
|
-
return { sender: { name: caller.name, kind: caller.kind, verified: true } };
|
|
3171
|
-
}
|
|
3172
|
-
if (id.mode === "caller-supplied") {
|
|
3173
|
-
if (id.shared_key && key !== id.shared_key) return { error: "invalid key" };
|
|
3174
|
-
return { sender: { name: from || "anonymous", kind: "user", verified: false } };
|
|
3175
|
-
}
|
|
3176
|
-
return { error: "unsupported identity mode" };
|
|
3177
|
-
}
|
|
3178
|
-
const MAX_BODY = 16 * 1024;
|
|
3179
|
-
function xmlEscape(s) {
|
|
3180
|
-
return String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3181
|
-
}
|
|
3182
|
-
const stripControl = (s) => String(s ?? "").replace(/[\x00-\x1f\x7f]/g, " ");
|
|
3183
|
-
function renderMessage(channel, { sender = {}, body = {}, query = {}, callId, now }) {
|
|
3184
|
-
const obj = (v) => typeof v === "object" && v !== null ? JSON.stringify(v) : v;
|
|
3185
|
-
const escVal = (v) => xmlEscape(obj(v));
|
|
3186
|
-
const escAttr = (v) => xmlEscape(stripControl(obj(v)));
|
|
3187
|
-
const bodyEsc = {};
|
|
3188
|
-
for (const [k, v] of Object.entries(body)) bodyEsc[k] = k === "message" ? escVal(String(v ?? "").slice(0, MAX_BODY)) : escAttr(v);
|
|
3189
|
-
const queryEsc = {};
|
|
3190
|
-
for (const [k, v] of Object.entries(query)) if (k !== "key") queryEsc[k] = escAttr(v);
|
|
3191
|
-
const ctx = {
|
|
3192
|
-
sender: { name: escAttr(sender.name), kind: escAttr(sender.kind), verified: sender.verified === true },
|
|
3193
|
-
body: bodyEsc,
|
|
3194
|
-
query: queryEsc,
|
|
3195
|
-
channel: { name: escAttr(channel.name), id: escAttr(channel.id) },
|
|
3196
|
-
call: { id: escAttr(callId) },
|
|
3197
|
-
now: escAttr(now || (/* @__PURE__ */ new Date()).toISOString())
|
|
3198
|
-
};
|
|
3199
|
-
return renderTemplate(channel.template || DEFAULT_TEMPLATE, ctx);
|
|
3200
|
-
}
|
|
3201
|
-
|
|
3202
3325
|
function channelPublicView(c) {
|
|
3203
|
-
return { id: c.id, name: c.name, description: c.description, identity: { mode: c.identity?.mode }, action: c.action?.kind };
|
|
3326
|
+
return { id: c.id, name: c.name, description: c.description, identity: { mode: c.identity?.mode }, action: c.action?.kind, bind: c.bind };
|
|
3204
3327
|
}
|
|
3205
3328
|
function isStructuredMessage(msg) {
|
|
3206
3329
|
return !!(msg.from || msg.fromSession || msg.subject || msg.replyTo || msg.threadId || msg.channel);
|
|
@@ -3227,7 +3350,7 @@ ${msg.body}
|
|
|
3227
3350
|
</svamp-message>`;
|
|
3228
3351
|
}
|
|
3229
3352
|
function loadMessages(messagesDir, sessionId) {
|
|
3230
|
-
const filePath = join
|
|
3353
|
+
const filePath = join(messagesDir, "messages.jsonl");
|
|
3231
3354
|
if (!existsSync(filePath)) return [];
|
|
3232
3355
|
try {
|
|
3233
3356
|
const lines = readFileSync(filePath, "utf-8").split("\n").filter((l) => l.trim());
|
|
@@ -3244,7 +3367,7 @@ function loadMessages(messagesDir, sessionId) {
|
|
|
3244
3367
|
}
|
|
3245
3368
|
}
|
|
3246
3369
|
function loadMessagesFromDisk(messagesDir, afterSeq, limit) {
|
|
3247
|
-
const filePath = join
|
|
3370
|
+
const filePath = join(messagesDir, "messages.jsonl");
|
|
3248
3371
|
if (!existsSync(filePath)) return { messages: [], hasMore: false };
|
|
3249
3372
|
try {
|
|
3250
3373
|
const lines = readFileSync(filePath, "utf-8").split("\n").filter((l) => l.trim());
|
|
@@ -3263,7 +3386,7 @@ function loadMessagesFromDisk(messagesDir, afterSeq, limit) {
|
|
|
3263
3386
|
}
|
|
3264
3387
|
}
|
|
3265
3388
|
function loadMessagesFromDiskReverse(messagesDir, beforeSeq, limit) {
|
|
3266
|
-
const filePath = join
|
|
3389
|
+
const filePath = join(messagesDir, "messages.jsonl");
|
|
3267
3390
|
if (!existsSync(filePath)) return { messages: [], hasMore: false };
|
|
3268
3391
|
try {
|
|
3269
3392
|
const lines = readFileSync(filePath, "utf-8").split("\n").filter((l) => l.trim());
|
|
@@ -3282,7 +3405,7 @@ function loadMessagesFromDiskReverse(messagesDir, beforeSeq, limit) {
|
|
|
3282
3405
|
}
|
|
3283
3406
|
}
|
|
3284
3407
|
function countMessagesOnDisk(messagesDir, fallbackInMemory) {
|
|
3285
|
-
const filePath = join
|
|
3408
|
+
const filePath = join(messagesDir, "messages.jsonl");
|
|
3286
3409
|
if (!existsSync(filePath)) return fallbackInMemory;
|
|
3287
3410
|
try {
|
|
3288
3411
|
const data = readFileSync(filePath, "utf-8");
|
|
@@ -3296,9 +3419,9 @@ function countMessagesOnDisk(messagesDir, fallbackInMemory) {
|
|
|
3296
3419
|
}
|
|
3297
3420
|
function appendMessage(messagesDir, sessionId, msg) {
|
|
3298
3421
|
try {
|
|
3299
|
-
const filePath = join
|
|
3422
|
+
const filePath = join(messagesDir, "messages.jsonl");
|
|
3300
3423
|
if (!existsSync(messagesDir)) {
|
|
3301
|
-
mkdirSync
|
|
3424
|
+
mkdirSync(messagesDir, { recursive: true });
|
|
3302
3425
|
}
|
|
3303
3426
|
appendFileSync(filePath, JSON.stringify(msg) + "\n");
|
|
3304
3427
|
} catch (err) {
|
|
@@ -3434,7 +3557,10 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
3434
3557
|
const skillCtxFor = (c) => ({
|
|
3435
3558
|
channelsServiceId,
|
|
3436
3559
|
baseUrl: channelsBaseUrl,
|
|
3437
|
-
key: c.identity?.shared_key || c.identity?.callers?.[0]?.key
|
|
3560
|
+
key: c.identity?.shared_key || c.identity?.callers?.[0]?.key,
|
|
3561
|
+
// The session this skill is being copied FROM — baked into `dynamic` channel
|
|
3562
|
+
// instructions as the routing target so the copied link lands in THIS session.
|
|
3563
|
+
session: sessionId
|
|
3438
3564
|
});
|
|
3439
3565
|
const skillUrlsFor = (c) => {
|
|
3440
3566
|
if (!channelsServiceId) return { skillUrl: void 0, sendUrl: void 0 };
|
|
@@ -3625,6 +3751,10 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
3625
3751
|
saveChannel: async (channel, context) => {
|
|
3626
3752
|
authorizeRequest(context, metadata.sharing, "admin");
|
|
3627
3753
|
try {
|
|
3754
|
+
const b = channel.bind;
|
|
3755
|
+
if (b && typeof b === "object" && (b.session === "current" || b.session === "" || b.session == null)) {
|
|
3756
|
+
channel = { ...channel, bind: { session: sessionId } };
|
|
3757
|
+
}
|
|
3628
3758
|
const saved = channelStore.save(channel);
|
|
3629
3759
|
syncChannelsToMetadata();
|
|
3630
3760
|
return { success: true, channel: saved };
|
|
@@ -3656,6 +3786,8 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
3656
3786
|
const c = channelStore.get(id);
|
|
3657
3787
|
if (!c) return { error: "not found" };
|
|
3658
3788
|
const { skillUrl, sendUrl } = skillUrlsFor(c);
|
|
3789
|
+
const mode = bindMode(c);
|
|
3790
|
+
const routeSession = mode === "fixed" ? fixedSessionId(c) : mode === "dynamic" ? sessionId : void 0;
|
|
3659
3791
|
return {
|
|
3660
3792
|
skill: generateSkillBody(c, skillCtxFor(c)),
|
|
3661
3793
|
channelsServiceId,
|
|
@@ -3664,7 +3796,10 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
3664
3796
|
skillUrl,
|
|
3665
3797
|
sendUrl,
|
|
3666
3798
|
key: c.identity?.shared_key || c.identity?.callers?.[0]?.key,
|
|
3667
|
-
hyphaKeyless: (c.identity?.hypha_allow || []).length > 0
|
|
3799
|
+
hyphaKeyless: (c.identity?.hypha_allow || []).length > 0,
|
|
3800
|
+
bind: c.bind,
|
|
3801
|
+
bindMode: mode,
|
|
3802
|
+
session: routeSession
|
|
3668
3803
|
};
|
|
3669
3804
|
},
|
|
3670
3805
|
// Public channel discovery (no session authz — channels are deliberately
|
|
@@ -4186,9 +4321,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
4186
4321
|
messages.length = 0;
|
|
4187
4322
|
nextSeq = 1;
|
|
4188
4323
|
if (options?.messagesDir) {
|
|
4189
|
-
const filePath = join
|
|
4324
|
+
const filePath = join(options.messagesDir, "messages.jsonl");
|
|
4190
4325
|
try {
|
|
4191
|
-
writeFileSync
|
|
4326
|
+
writeFileSync(filePath, "");
|
|
4192
4327
|
} catch {
|
|
4193
4328
|
}
|
|
4194
4329
|
}
|
|
@@ -4216,7 +4351,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
4216
4351
|
return { store, rpcHandlers };
|
|
4217
4352
|
}
|
|
4218
4353
|
|
|
4219
|
-
const SVAMP_HOME$2 = process.env.SVAMP_HOME || join
|
|
4354
|
+
const SVAMP_HOME$2 = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
|
|
4220
4355
|
const num = (key, def) => {
|
|
4221
4356
|
const v = Number(process.env[key]);
|
|
4222
4357
|
return Number.isFinite(v) && v > 0 ? v : def;
|
|
@@ -4229,19 +4364,19 @@ const BREAKER_MAX_WAKES = num("SVAMP_INBOX_BREAKER_MAX_WAKES", 30);
|
|
|
4229
4364
|
const BREAKER_COOLDOWN_MS = num("SVAMP_INBOX_BREAKER_COOLDOWN_MS", 12e4);
|
|
4230
4365
|
const CTX_STALE_MS = num("SVAMP_INBOX_CTX_STALE_MS", 30 * 6e4);
|
|
4231
4366
|
function ctxPath(sessionId) {
|
|
4232
|
-
return join
|
|
4367
|
+
return join(SVAMP_HOME$2, "inbound", `${sessionId}.json`);
|
|
4233
4368
|
}
|
|
4234
4369
|
function writeInboundContext(sessionId, ctx) {
|
|
4235
4370
|
try {
|
|
4236
4371
|
const p = ctxPath(sessionId);
|
|
4237
|
-
mkdirSync
|
|
4372
|
+
mkdirSync(join(SVAMP_HOME$2, "inbound"), { recursive: true });
|
|
4238
4373
|
const tmp = p + ".tmp";
|
|
4239
|
-
writeFileSync
|
|
4374
|
+
writeFileSync(tmp, JSON.stringify({ ...ctx, deliveredAt: Date.now() }));
|
|
4240
4375
|
try {
|
|
4241
4376
|
rmSync(p, { force: true });
|
|
4242
4377
|
} catch {
|
|
4243
4378
|
}
|
|
4244
|
-
writeFileSync
|
|
4379
|
+
writeFileSync(p, readFileSync(tmp));
|
|
4245
4380
|
try {
|
|
4246
4381
|
rmSync(tmp, { force: true });
|
|
4247
4382
|
} catch {
|
|
@@ -4528,8 +4663,8 @@ class SessionArtifactSync {
|
|
|
4528
4663
|
this.syncing = true;
|
|
4529
4664
|
try {
|
|
4530
4665
|
const artifactAlias = `session-${sessionId}`;
|
|
4531
|
-
const sessionJsonPath = join
|
|
4532
|
-
const messagesPath = join
|
|
4666
|
+
const sessionJsonPath = join(sessionsDir, "session.json");
|
|
4667
|
+
const messagesPath = join(sessionsDir, "messages.jsonl");
|
|
4533
4668
|
let sessionData = null;
|
|
4534
4669
|
if (existsSync(sessionJsonPath)) {
|
|
4535
4670
|
try {
|
|
@@ -4639,18 +4774,18 @@ class SessionArtifactSync {
|
|
|
4639
4774
|
artifact_id: artifactAlias,
|
|
4640
4775
|
_rkwargs: true
|
|
4641
4776
|
});
|
|
4642
|
-
if (!existsSync(targetDir)) mkdirSync
|
|
4777
|
+
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
4643
4778
|
try {
|
|
4644
4779
|
const data = await this.downloadFile(artifact.id, "session.json");
|
|
4645
4780
|
if (data) {
|
|
4646
|
-
writeFileSync
|
|
4781
|
+
writeFileSync(join(targetDir, "session.json"), data);
|
|
4647
4782
|
}
|
|
4648
4783
|
} catch {
|
|
4649
4784
|
}
|
|
4650
4785
|
try {
|
|
4651
4786
|
const data = await this.downloadFile(artifact.id, "messages.jsonl");
|
|
4652
4787
|
if (data) {
|
|
4653
|
-
writeFileSync
|
|
4788
|
+
writeFileSync(join(targetDir, "messages.jsonl"), data);
|
|
4654
4789
|
}
|
|
4655
4790
|
} catch {
|
|
4656
4791
|
}
|
|
@@ -4703,13 +4838,13 @@ class SessionArtifactSync {
|
|
|
4703
4838
|
*/
|
|
4704
4839
|
async syncAll(svampHome, machineId) {
|
|
4705
4840
|
if (!this.initialized) return;
|
|
4706
|
-
const indexFile = join
|
|
4841
|
+
const indexFile = join(svampHome, "sessions-index.json");
|
|
4707
4842
|
if (!existsSync(indexFile)) return;
|
|
4708
4843
|
try {
|
|
4709
4844
|
const index = JSON.parse(readFileSync(indexFile, "utf-8"));
|
|
4710
4845
|
for (const [sessionId, entry] of Object.entries(index)) {
|
|
4711
|
-
const sessionDir = join
|
|
4712
|
-
if (existsSync(join
|
|
4846
|
+
const sessionDir = join(entry.directory, ".svamp", sessionId);
|
|
4847
|
+
if (existsSync(join(sessionDir, "session.json"))) {
|
|
4713
4848
|
await this.syncSession(sessionId, sessionDir, void 0, machineId);
|
|
4714
4849
|
}
|
|
4715
4850
|
}
|
|
@@ -5025,7 +5160,7 @@ var DefaultTransport$1 = /*#__PURE__*/Object.freeze({
|
|
|
5025
5160
|
|
|
5026
5161
|
function expandHome(p) {
|
|
5027
5162
|
if (p === "~") return homedir();
|
|
5028
|
-
if (p.startsWith("~/")) return join
|
|
5163
|
+
if (p.startsWith("~/")) return join(homedir(), p.slice(2));
|
|
5029
5164
|
return p;
|
|
5030
5165
|
}
|
|
5031
5166
|
function wrapWithIsolation(originalCommand, originalArgs, config) {
|
|
@@ -5052,11 +5187,11 @@ function wrapWithNono(command, args, config) {
|
|
|
5052
5187
|
nonoArgs.push("--allow-cwd");
|
|
5053
5188
|
if (config.credentialStagingPath) {
|
|
5054
5189
|
env.HOME = config.credentialStagingPath;
|
|
5055
|
-
const realLocalDir = join
|
|
5190
|
+
const realLocalDir = join(homedir(), ".local");
|
|
5056
5191
|
if (existsSync(realLocalDir)) {
|
|
5057
5192
|
nonoArgs.push("--read", realLocalDir);
|
|
5058
5193
|
}
|
|
5059
|
-
const realKeychainDir = join
|
|
5194
|
+
const realKeychainDir = join(homedir(), "Library", "Keychains");
|
|
5060
5195
|
if (existsSync(realKeychainDir)) {
|
|
5061
5196
|
nonoArgs.push("--read", realKeychainDir);
|
|
5062
5197
|
}
|
|
@@ -5117,9 +5252,9 @@ function wrapWithContainer(runtime, command, args, config) {
|
|
|
5117
5252
|
config.workspacePath
|
|
5118
5253
|
];
|
|
5119
5254
|
if (config.credentialStagingPath) {
|
|
5120
|
-
const stagedClaudeDir = join
|
|
5255
|
+
const stagedClaudeDir = join(config.credentialStagingPath, ".claude");
|
|
5121
5256
|
containerArgs.push("-v", `${stagedClaudeDir}:/root/.claude:ro`);
|
|
5122
|
-
const stagedGitconfig = join
|
|
5257
|
+
const stagedGitconfig = join(config.credentialStagingPath, ".gitconfig");
|
|
5123
5258
|
containerArgs.push("-v", `${stagedGitconfig}:/root/.gitconfig:ro`);
|
|
5124
5259
|
containerArgs.push("-e", "HOME=/root");
|
|
5125
5260
|
}
|
|
@@ -6868,8 +7003,8 @@ var GeminiTransport$1 = /*#__PURE__*/Object.freeze({
|
|
|
6868
7003
|
});
|
|
6869
7004
|
|
|
6870
7005
|
const execFileAsync = promisify$1(execFile$1);
|
|
6871
|
-
const SVAMP_TOOLS_DIR = join
|
|
6872
|
-
const SVAMP_BIN_DIR = join
|
|
7006
|
+
const SVAMP_TOOLS_DIR = join(homedir(), ".svamp", "tools");
|
|
7007
|
+
const SVAMP_BIN_DIR = join(SVAMP_TOOLS_DIR, "bin");
|
|
6873
7008
|
async function checkCommand(command, versionArgs) {
|
|
6874
7009
|
try {
|
|
6875
7010
|
const { stdout } = await execFileAsync(command, versionArgs, {
|
|
@@ -6879,7 +7014,7 @@ async function checkCommand(command, versionArgs) {
|
|
|
6879
7014
|
return { found: true, version, path: command };
|
|
6880
7015
|
} catch {
|
|
6881
7016
|
}
|
|
6882
|
-
const localPath = join
|
|
7017
|
+
const localPath = join(SVAMP_BIN_DIR, command);
|
|
6883
7018
|
try {
|
|
6884
7019
|
const { stdout } = await execFileAsync(localPath, versionArgs, {
|
|
6885
7020
|
timeout: 5e3
|
|
@@ -6946,7 +7081,7 @@ async function installNono() {
|
|
|
6946
7081
|
const downloadUrl = asset.browser_download_url;
|
|
6947
7082
|
console.log(`[isolation] Downloading nono ${version} from ${downloadUrl}...`);
|
|
6948
7083
|
await mkdir(SVAMP_BIN_DIR, { recursive: true });
|
|
6949
|
-
const tarball = join
|
|
7084
|
+
const tarball = join(SVAMP_BIN_DIR, assetName);
|
|
6950
7085
|
await execFileAsync("curl", [
|
|
6951
7086
|
"-fsSL",
|
|
6952
7087
|
"-o",
|
|
@@ -6963,7 +7098,7 @@ async function installNono() {
|
|
|
6963
7098
|
], { timeout: 15e3 });
|
|
6964
7099
|
await rm(tarball, { force: true }).catch(() => {
|
|
6965
7100
|
});
|
|
6966
|
-
const nonoPath = join
|
|
7101
|
+
const nonoPath = join(SVAMP_BIN_DIR, "nono");
|
|
6967
7102
|
await chmod(nonoPath, 493);
|
|
6968
7103
|
try {
|
|
6969
7104
|
await access(nonoPath);
|
|
@@ -7005,8 +7140,8 @@ async function parseIsolationTestOutput(stdout, probeFile) {
|
|
|
7005
7140
|
}
|
|
7006
7141
|
async function verifyNonoIsolation(binaryPath) {
|
|
7007
7142
|
const testBase = "/tmp";
|
|
7008
|
-
const workDir = await mkdtemp(join
|
|
7009
|
-
const probeFile = join
|
|
7143
|
+
const workDir = await mkdtemp(join(testBase, "svamp-iso-work-"));
|
|
7144
|
+
const probeFile = join(homedir(), `.svamp-iso-probe-${process.pid}`);
|
|
7010
7145
|
try {
|
|
7011
7146
|
const testScript = [
|
|
7012
7147
|
`echo ok > "${workDir}/test" 2>/dev/null; W=$?`,
|
|
@@ -7132,7 +7267,7 @@ async function detectIsolationCapabilities() {
|
|
|
7132
7267
|
return { available, preferred, details };
|
|
7133
7268
|
}
|
|
7134
7269
|
|
|
7135
|
-
const STAGED_HOMES_DIR = join
|
|
7270
|
+
const STAGED_HOMES_DIR = join(homedir(), ".svamp", "staged-homes");
|
|
7136
7271
|
const SENSITIVE_ENV_VARS = [
|
|
7137
7272
|
"HYPHA_TOKEN",
|
|
7138
7273
|
"HYPHA_CLIENT_ID",
|
|
@@ -7148,23 +7283,23 @@ const SENSITIVE_ENV_VARS = [
|
|
|
7148
7283
|
];
|
|
7149
7284
|
async function stageCredentialsForSharing(sessionId) {
|
|
7150
7285
|
const realHome = homedir();
|
|
7151
|
-
const realClaudeDir = join
|
|
7286
|
+
const realClaudeDir = join(realHome, ".claude");
|
|
7152
7287
|
await mkdir(STAGED_HOMES_DIR, { recursive: true });
|
|
7153
|
-
const tmpHome = join
|
|
7288
|
+
const tmpHome = join(STAGED_HOMES_DIR, sessionId);
|
|
7154
7289
|
await mkdir(tmpHome, { recursive: true });
|
|
7155
|
-
const stagedClaudeDir = join
|
|
7290
|
+
const stagedClaudeDir = join(tmpHome, ".claude");
|
|
7156
7291
|
await mkdir(stagedClaudeDir, { recursive: true });
|
|
7157
7292
|
const credentialFiles = ["credentials.json", ".credentials.json"];
|
|
7158
7293
|
let credentialsCopied = false;
|
|
7159
7294
|
for (const file of credentialFiles) {
|
|
7160
7295
|
try {
|
|
7161
|
-
await copyFile(join
|
|
7296
|
+
await copyFile(join(realClaudeDir, file), join(stagedClaudeDir, file));
|
|
7162
7297
|
credentialsCopied = true;
|
|
7163
7298
|
} catch {
|
|
7164
7299
|
}
|
|
7165
7300
|
}
|
|
7166
7301
|
if (!credentialsCopied && platform() === "darwin") {
|
|
7167
|
-
const stagedCredFile = join
|
|
7302
|
+
const stagedCredFile = join(stagedClaudeDir, ".credentials.json");
|
|
7168
7303
|
const hasExistingCredentials = existsSync(stagedCredFile);
|
|
7169
7304
|
if (!hasExistingCredentials) {
|
|
7170
7305
|
try {
|
|
@@ -7182,25 +7317,25 @@ async function stageCredentialsForSharing(sessionId) {
|
|
|
7182
7317
|
}
|
|
7183
7318
|
}
|
|
7184
7319
|
try {
|
|
7185
|
-
await copyFile(join
|
|
7320
|
+
await copyFile(join(realHome, ".gitconfig"), join(tmpHome, ".gitconfig"));
|
|
7186
7321
|
} catch {
|
|
7187
7322
|
}
|
|
7188
7323
|
try {
|
|
7189
7324
|
await copyFile(
|
|
7190
|
-
join
|
|
7191
|
-
join
|
|
7325
|
+
join(realHome, ".gitignore_global"),
|
|
7326
|
+
join(tmpHome, ".gitignore_global")
|
|
7192
7327
|
);
|
|
7193
7328
|
} catch {
|
|
7194
7329
|
}
|
|
7195
|
-
const claudeJsonPath = join
|
|
7330
|
+
const claudeJsonPath = join(tmpHome, ".claude.json");
|
|
7196
7331
|
if (!existsSync(claudeJsonPath)) {
|
|
7197
7332
|
try {
|
|
7198
7333
|
await writeFile(claudeJsonPath, "{}");
|
|
7199
7334
|
} catch {
|
|
7200
7335
|
}
|
|
7201
7336
|
}
|
|
7202
|
-
const realSkillsDir = join
|
|
7203
|
-
const stagedSkillsDir = join
|
|
7337
|
+
const realSkillsDir = join(realClaudeDir, "skills");
|
|
7338
|
+
const stagedSkillsDir = join(stagedClaudeDir, "skills");
|
|
7204
7339
|
try {
|
|
7205
7340
|
await copyDirRecursive(realSkillsDir, stagedSkillsDir);
|
|
7206
7341
|
} catch {
|
|
@@ -7234,7 +7369,7 @@ async function sweepOrphanedStagedHomes(activeSessionIds) {
|
|
|
7234
7369
|
for (const entry of entries) {
|
|
7235
7370
|
if (!entry.isDirectory()) continue;
|
|
7236
7371
|
if (active.has(entry.name)) continue;
|
|
7237
|
-
const path = join
|
|
7372
|
+
const path = join(STAGED_HOMES_DIR, entry.name);
|
|
7238
7373
|
try {
|
|
7239
7374
|
await rm(path, { recursive: true, force: true });
|
|
7240
7375
|
removed.push(entry.name);
|
|
@@ -7249,8 +7384,8 @@ async function copyDirRecursive(src, dest) {
|
|
|
7249
7384
|
await mkdir(dest, { recursive: true });
|
|
7250
7385
|
const entries = await readdir(src, { withFileTypes: true });
|
|
7251
7386
|
for (const entry of entries) {
|
|
7252
|
-
const srcPath = join
|
|
7253
|
-
const destPath = join
|
|
7387
|
+
const srcPath = join(src, entry.name);
|
|
7388
|
+
const destPath = join(dest, entry.name);
|
|
7254
7389
|
if (entry.isDirectory()) {
|
|
7255
7390
|
await copyDirRecursive(srcPath, destPath);
|
|
7256
7391
|
} else if (entry.isFile()) {
|
|
@@ -7302,8 +7437,8 @@ function resolveHyphaProxyUrl() {
|
|
|
7302
7437
|
}
|
|
7303
7438
|
}
|
|
7304
7439
|
function envFilePath() {
|
|
7305
|
-
const svampHome = process.env.SVAMP_HOME || join
|
|
7306
|
-
return join
|
|
7440
|
+
const svampHome = process.env.SVAMP_HOME || join(homedir(), ".svamp");
|
|
7441
|
+
return join(svampHome, ".env");
|
|
7307
7442
|
}
|
|
7308
7443
|
function readEnvLines() {
|
|
7309
7444
|
const file = envFilePath();
|
|
@@ -7312,12 +7447,12 @@ function readEnvLines() {
|
|
|
7312
7447
|
}
|
|
7313
7448
|
function writeEnvLines(lines) {
|
|
7314
7449
|
const file = envFilePath();
|
|
7315
|
-
const dir = join
|
|
7316
|
-
if (!existsSync(dir)) mkdirSync
|
|
7450
|
+
const dir = join(file, "..");
|
|
7451
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
7317
7452
|
while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
|
|
7318
7453
|
lines.pop();
|
|
7319
7454
|
}
|
|
7320
|
-
writeFileSync
|
|
7455
|
+
writeFileSync(file, lines.join("\n") + "\n", "utf-8");
|
|
7321
7456
|
}
|
|
7322
7457
|
function updateEnvFile(updates) {
|
|
7323
7458
|
const lines = readEnvLines();
|
|
@@ -7468,10 +7603,10 @@ var claudeAuth = /*#__PURE__*/Object.freeze({
|
|
|
7468
7603
|
});
|
|
7469
7604
|
|
|
7470
7605
|
function svampHome() {
|
|
7471
|
-
return process.env.SVAMP_HOME || join(homedir$1(), ".svamp");
|
|
7606
|
+
return process.env.SVAMP_HOME || join$1(homedir$1(), ".svamp");
|
|
7472
7607
|
}
|
|
7473
7608
|
function cacheFile() {
|
|
7474
|
-
return join(svampHome(), "instance-config.json");
|
|
7609
|
+
return join$1(svampHome(), "instance-config.json");
|
|
7475
7610
|
}
|
|
7476
7611
|
const CONFIG_FILENAME = "svamp.json";
|
|
7477
7612
|
let _config = null;
|
|
@@ -7514,8 +7649,8 @@ function readCache() {
|
|
|
7514
7649
|
}
|
|
7515
7650
|
function writeCache(cfg) {
|
|
7516
7651
|
try {
|
|
7517
|
-
mkdirSync(svampHome(), { recursive: true });
|
|
7518
|
-
writeFileSync(cacheFile(), JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
7652
|
+
mkdirSync$1(svampHome(), { recursive: true });
|
|
7653
|
+
writeFileSync$1(cacheFile(), JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
7519
7654
|
} catch {
|
|
7520
7655
|
}
|
|
7521
7656
|
}
|
|
@@ -8033,7 +8168,7 @@ function escapeHtml(s) {
|
|
|
8033
8168
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
8034
8169
|
}
|
|
8035
8170
|
|
|
8036
|
-
const SKILLS_DIR = join(os$1.homedir(), ".claude", "skills");
|
|
8171
|
+
const SKILLS_DIR = join$1(os$1.homedir(), ".claude", "skills");
|
|
8037
8172
|
function getSkillsWorkspaceName() {
|
|
8038
8173
|
return getSkillsWorkspace();
|
|
8039
8174
|
}
|
|
@@ -8764,7 +8899,7 @@ const REFRESH_BUFFER_MS = 60 * 60 * 1e3;
|
|
|
8764
8899
|
const OAUTH_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
8765
8900
|
const REFRESH_TIMEOUT_MS = 15e3;
|
|
8766
8901
|
function getCredentialsPath() {
|
|
8767
|
-
return join
|
|
8902
|
+
return join(homedir(), ".claude", ".credentials.json");
|
|
8768
8903
|
}
|
|
8769
8904
|
async function readCredentials() {
|
|
8770
8905
|
const path = getCredentialsPath();
|
|
@@ -8925,14 +9060,14 @@ function resolveContextWindow(opts) {
|
|
|
8925
9060
|
return candidate;
|
|
8926
9061
|
}
|
|
8927
9062
|
|
|
8928
|
-
const SVAMP_HOME$1 = process.env.SVAMP_HOME || join
|
|
9063
|
+
const SVAMP_HOME$1 = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
|
|
8929
9064
|
function generateHookSettings(portOrOptions = {}) {
|
|
8930
9065
|
const opts = typeof portOrOptions === "number" ? { sessionStartPort: portOrOptions } : portOrOptions;
|
|
8931
|
-
const hooksDir = join
|
|
8932
|
-
mkdirSync
|
|
9066
|
+
const hooksDir = join(SVAMP_HOME$1, "tmp", "hooks");
|
|
9067
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
8933
9068
|
const id = opts.id || String(process.pid);
|
|
8934
|
-
const validatorPath = join
|
|
8935
|
-
writeFileSync
|
|
9069
|
+
const validatorPath = join(hooksDir, `image-validator-${id}.cjs`);
|
|
9070
|
+
writeFileSync(validatorPath, IMAGE_VALIDATOR_SCRIPT, { mode: 493 });
|
|
8936
9071
|
const cleanupPaths = [validatorPath];
|
|
8937
9072
|
const hooks = {
|
|
8938
9073
|
PreToolUse: [
|
|
@@ -8949,7 +9084,7 @@ function generateHookSettings(portOrOptions = {}) {
|
|
|
8949
9084
|
]
|
|
8950
9085
|
};
|
|
8951
9086
|
if (typeof opts.sessionStartPort === "number" && opts.sessionStartPort > 0) {
|
|
8952
|
-
const forwarderPath = join
|
|
9087
|
+
const forwarderPath = join(hooksDir, `forwarder-${id}.cjs`);
|
|
8953
9088
|
const forwarderCode = `#!/usr/bin/env node
|
|
8954
9089
|
const http = require('http');
|
|
8955
9090
|
const port = parseInt(process.argv[2], 10);
|
|
@@ -8968,7 +9103,7 @@ process.stdin.on('end', () => {
|
|
|
8968
9103
|
});
|
|
8969
9104
|
process.stdin.resume();
|
|
8970
9105
|
`;
|
|
8971
|
-
writeFileSync
|
|
9106
|
+
writeFileSync(forwarderPath, forwarderCode, { mode: 493 });
|
|
8972
9107
|
cleanupPaths.push(forwarderPath);
|
|
8973
9108
|
hooks.SessionStart = [
|
|
8974
9109
|
{
|
|
@@ -8982,8 +9117,8 @@ process.stdin.resume();
|
|
|
8982
9117
|
}
|
|
8983
9118
|
];
|
|
8984
9119
|
}
|
|
8985
|
-
const settingsPath = join
|
|
8986
|
-
writeFileSync
|
|
9120
|
+
const settingsPath = join(hooksDir, `session-hook-${id}.json`);
|
|
9121
|
+
writeFileSync(settingsPath, JSON.stringify({ hooks }, null, 2));
|
|
8987
9122
|
cleanupPaths.push(settingsPath);
|
|
8988
9123
|
const cleanup = () => {
|
|
8989
9124
|
for (const p of cleanupPaths) {
|
|
@@ -9145,7 +9280,7 @@ async function readSessionFileBase64(resolvedPath) {
|
|
|
9145
9280
|
|
|
9146
9281
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
9147
9282
|
const __dirname$1 = dirname(__filename$1);
|
|
9148
|
-
const CLAUDE_SKILLS_DIR = join(os$1.homedir(), ".claude", "skills");
|
|
9283
|
+
const CLAUDE_SKILLS_DIR = join$1(os$1.homedir(), ".claude", "skills");
|
|
9149
9284
|
function looksLikeClaudeError(line) {
|
|
9150
9285
|
const l = line.toLowerCase();
|
|
9151
9286
|
return l.includes("api error") || l.includes("request rejected") || l.includes("error:") || l.includes("overloaded") || l.includes("rate limit") || l.includes("unauthorized") || l.includes("forbidden") || /\b(4\d\d|5\d\d)\b/.test(l) && (l.includes("status") || l.includes("error") || l.includes("rejected"));
|
|
@@ -9185,7 +9320,7 @@ function buildClaudeErrorHint(text, apiErrorStatus) {
|
|
|
9185
9320
|
}
|
|
9186
9321
|
function readSkillVersion(skillDir) {
|
|
9187
9322
|
try {
|
|
9188
|
-
const md = readFileSync$1(join(skillDir, "SKILL.md"), "utf-8");
|
|
9323
|
+
const md = readFileSync$1(join$1(skillDir, "SKILL.md"), "utf-8");
|
|
9189
9324
|
const m = md.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
9190
9325
|
if (!m) return null;
|
|
9191
9326
|
const versionLine = m[1].split("\n").find((l) => /^\s*version\s*:/.test(l));
|
|
@@ -9211,18 +9346,18 @@ async function installSkillFromEndpoint(name, baseUrl) {
|
|
|
9211
9346
|
const index = await resp.json();
|
|
9212
9347
|
const files = index.files || [];
|
|
9213
9348
|
if (files.length === 0) throw new Error(`Skill index at ${baseUrl} has no files`);
|
|
9214
|
-
const targetDir = join(CLAUDE_SKILLS_DIR, name);
|
|
9215
|
-
mkdirSync(targetDir, { recursive: true });
|
|
9349
|
+
const targetDir = join$1(CLAUDE_SKILLS_DIR, name);
|
|
9350
|
+
mkdirSync$1(targetDir, { recursive: true });
|
|
9216
9351
|
for (const filePath of files) {
|
|
9217
9352
|
if (!filePath) continue;
|
|
9218
9353
|
const url = `${baseUrl}${filePath}`;
|
|
9219
9354
|
const fileResp = await fetch(url, { signal: AbortSignal.timeout(3e4) });
|
|
9220
9355
|
if (!fileResp.ok) throw new Error(`Failed to download ${filePath}: HTTP ${fileResp.status}`);
|
|
9221
9356
|
const content = await fileResp.text();
|
|
9222
|
-
const localPath = join(targetDir, filePath);
|
|
9357
|
+
const localPath = join$1(targetDir, filePath);
|
|
9223
9358
|
if (!localPath.startsWith(targetDir + "/")) continue;
|
|
9224
|
-
mkdirSync(dirname(localPath), { recursive: true });
|
|
9225
|
-
writeFileSync(localPath, content, "utf-8");
|
|
9359
|
+
mkdirSync$1(dirname(localPath), { recursive: true });
|
|
9360
|
+
writeFileSync$1(localPath, content, "utf-8");
|
|
9226
9361
|
}
|
|
9227
9362
|
}
|
|
9228
9363
|
async function installSkillFromMarketplace(name) {
|
|
@@ -9246,26 +9381,26 @@ async function installSkillFromMarketplace(name) {
|
|
|
9246
9381
|
}
|
|
9247
9382
|
const files = await collectFiles();
|
|
9248
9383
|
if (files.length === 0) throw new Error(`Skill ${name} has no files in marketplace`);
|
|
9249
|
-
const targetDir = join(CLAUDE_SKILLS_DIR, name);
|
|
9250
|
-
mkdirSync(targetDir, { recursive: true });
|
|
9384
|
+
const targetDir = join$1(CLAUDE_SKILLS_DIR, name);
|
|
9385
|
+
mkdirSync$1(targetDir, { recursive: true });
|
|
9251
9386
|
for (const filePath of files) {
|
|
9252
9387
|
const url = `${BASE}/files/${filePath}`;
|
|
9253
9388
|
const resp = await fetch(url, { signal: AbortSignal.timeout(3e4) });
|
|
9254
9389
|
if (!resp.ok) throw new Error(`Failed to download ${filePath}: HTTP ${resp.status}`);
|
|
9255
9390
|
const content = await resp.text();
|
|
9256
|
-
const localPath = join(targetDir, filePath);
|
|
9391
|
+
const localPath = join$1(targetDir, filePath);
|
|
9257
9392
|
if (!localPath.startsWith(targetDir + "/")) continue;
|
|
9258
|
-
mkdirSync(dirname(localPath), { recursive: true });
|
|
9259
|
-
writeFileSync(localPath, content, "utf-8");
|
|
9393
|
+
mkdirSync$1(dirname(localPath), { recursive: true });
|
|
9394
|
+
writeFileSync$1(localPath, content, "utf-8");
|
|
9260
9395
|
}
|
|
9261
9396
|
}
|
|
9262
9397
|
function getBundledSkillsDir() {
|
|
9263
9398
|
try {
|
|
9264
9399
|
const here = fileURLToPath(import.meta.url);
|
|
9265
9400
|
const candidates = [
|
|
9266
|
-
join(dirname(here), "..", "bin", "skills"),
|
|
9401
|
+
join$1(dirname(here), "..", "bin", "skills"),
|
|
9267
9402
|
// built dist/ layout
|
|
9268
|
-
join(dirname(here), "..", "..", "bin", "skills")
|
|
9403
|
+
join$1(dirname(here), "..", "..", "bin", "skills")
|
|
9269
9404
|
// src/daemon → bin layout via tsx
|
|
9270
9405
|
];
|
|
9271
9406
|
for (const c of candidates) {
|
|
@@ -9278,17 +9413,17 @@ function getBundledSkillsDir() {
|
|
|
9278
9413
|
function installBundledSkill(name) {
|
|
9279
9414
|
const bundledDir = getBundledSkillsDir();
|
|
9280
9415
|
if (!bundledDir) throw new Error(`Bundled skills directory not found`);
|
|
9281
|
-
const src = join(bundledDir, name);
|
|
9416
|
+
const src = join$1(bundledDir, name);
|
|
9282
9417
|
if (!existsSync$1(src)) throw new Error(`Bundled skill not found: ${src}`);
|
|
9283
|
-
const dst = join(CLAUDE_SKILLS_DIR, name);
|
|
9284
|
-
mkdirSync(dst, { recursive: true });
|
|
9418
|
+
const dst = join$1(CLAUDE_SKILLS_DIR, name);
|
|
9419
|
+
mkdirSync$1(dst, { recursive: true });
|
|
9285
9420
|
function copyDir(s, d) {
|
|
9286
|
-
mkdirSync(d, { recursive: true });
|
|
9421
|
+
mkdirSync$1(d, { recursive: true });
|
|
9287
9422
|
for (const entry of readdirSync$1(s, { withFileTypes: true })) {
|
|
9288
|
-
const sp = join(s, entry.name);
|
|
9289
|
-
const dp = join(d, entry.name);
|
|
9423
|
+
const sp = join$1(s, entry.name);
|
|
9424
|
+
const dp = join$1(d, entry.name);
|
|
9290
9425
|
if (entry.isDirectory()) copyDir(sp, dp);
|
|
9291
|
-
else if (entry.isFile()) writeFileSync(dp, readFileSync$1(sp));
|
|
9426
|
+
else if (entry.isFile()) writeFileSync$1(dp, readFileSync$1(sp));
|
|
9292
9427
|
}
|
|
9293
9428
|
}
|
|
9294
9429
|
copyDir(src, dst);
|
|
@@ -9296,7 +9431,7 @@ function installBundledSkill(name) {
|
|
|
9296
9431
|
function readBundledSkillVersion(name) {
|
|
9297
9432
|
const bundledDir = getBundledSkillsDir();
|
|
9298
9433
|
if (!bundledDir) return null;
|
|
9299
|
-
return readSkillVersion(join(bundledDir, name));
|
|
9434
|
+
return readSkillVersion(join$1(bundledDir, name));
|
|
9300
9435
|
}
|
|
9301
9436
|
function preventMachineSleep(logger) {
|
|
9302
9437
|
if (process.platform === "darwin") {
|
|
@@ -9404,7 +9539,7 @@ async function ensureAutoInstalledSkills(logger) {
|
|
|
9404
9539
|
}
|
|
9405
9540
|
];
|
|
9406
9541
|
for (const task of tasks) {
|
|
9407
|
-
const targetDir = join(CLAUDE_SKILLS_DIR, task.name);
|
|
9542
|
+
const targetDir = join$1(CLAUDE_SKILLS_DIR, task.name);
|
|
9408
9543
|
const installed = existsSync$1(targetDir);
|
|
9409
9544
|
if (!installed) {
|
|
9410
9545
|
try {
|
|
@@ -9445,20 +9580,20 @@ function loadEnvFile(path) {
|
|
|
9445
9580
|
return true;
|
|
9446
9581
|
}
|
|
9447
9582
|
function loadDotEnv() {
|
|
9448
|
-
const svampEnv = join(process.env.SVAMP_HOME || os$1.homedir() + "/.svamp", ".env");
|
|
9583
|
+
const svampEnv = join$1(process.env.SVAMP_HOME || os$1.homedir() + "/.svamp", ".env");
|
|
9449
9584
|
if (!loadEnvFile(svampEnv)) {
|
|
9450
|
-
const hyphaEnv = join(process.env.HYPHA_HOME || os$1.homedir() + "/.hypha", ".env");
|
|
9585
|
+
const hyphaEnv = join$1(process.env.HYPHA_HOME || os$1.homedir() + "/.hypha", ".env");
|
|
9451
9586
|
loadEnvFile(hyphaEnv);
|
|
9452
9587
|
}
|
|
9453
9588
|
}
|
|
9454
9589
|
loadDotEnv();
|
|
9455
|
-
const SVAMP_HOME = process.env.SVAMP_HOME || join(os$1.homedir(), ".svamp");
|
|
9456
|
-
const DAEMON_STATE_FILE = join(SVAMP_HOME, "daemon.state.json");
|
|
9457
|
-
const DAEMON_LOCK_FILE = join(SVAMP_HOME, "daemon.lock");
|
|
9458
|
-
const DAEMON_STOP_MARKER_FILE = join(SVAMP_HOME, "daemon.stop");
|
|
9590
|
+
const SVAMP_HOME = process.env.SVAMP_HOME || join$1(os$1.homedir(), ".svamp");
|
|
9591
|
+
const DAEMON_STATE_FILE = join$1(SVAMP_HOME, "daemon.state.json");
|
|
9592
|
+
const DAEMON_LOCK_FILE = join$1(SVAMP_HOME, "daemon.lock");
|
|
9593
|
+
const DAEMON_STOP_MARKER_FILE = join$1(SVAMP_HOME, "daemon.stop");
|
|
9459
9594
|
function writeStopMarker(reason) {
|
|
9460
9595
|
try {
|
|
9461
|
-
writeFileSync(DAEMON_STOP_MARKER_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${reason}
|
|
9596
|
+
writeFileSync$1(DAEMON_STOP_MARKER_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${reason}
|
|
9462
9597
|
`, "utf-8");
|
|
9463
9598
|
} catch {
|
|
9464
9599
|
}
|
|
@@ -9476,11 +9611,11 @@ function stopMarkerExists() {
|
|
|
9476
9611
|
return false;
|
|
9477
9612
|
}
|
|
9478
9613
|
}
|
|
9479
|
-
const LOGS_DIR = join(SVAMP_HOME, "logs");
|
|
9480
|
-
const SESSION_INDEX_FILE = join(SVAMP_HOME, "sessions-index.json");
|
|
9614
|
+
const LOGS_DIR = join$1(SVAMP_HOME, "logs");
|
|
9615
|
+
const SESSION_INDEX_FILE = join$1(SVAMP_HOME, "sessions-index.json");
|
|
9481
9616
|
function readPackageVersion() {
|
|
9482
9617
|
try {
|
|
9483
|
-
const pkgPath = join(__dirname$1, "../package.json");
|
|
9618
|
+
const pkgPath = join$1(__dirname$1, "../package.json");
|
|
9484
9619
|
if (existsSync$1(pkgPath)) {
|
|
9485
9620
|
return JSON.parse(readFileSync$1(pkgPath, "utf-8")).version || "unknown";
|
|
9486
9621
|
}
|
|
@@ -9490,7 +9625,7 @@ function readPackageVersion() {
|
|
|
9490
9625
|
}
|
|
9491
9626
|
const DAEMON_VERSION = readPackageVersion();
|
|
9492
9627
|
function loadAgentConfig() {
|
|
9493
|
-
const configPath = join(SVAMP_HOME, "agent-config.json");
|
|
9628
|
+
const configPath = join$1(SVAMP_HOME, "agent-config.json");
|
|
9494
9629
|
if (existsSync$1(configPath)) {
|
|
9495
9630
|
try {
|
|
9496
9631
|
return JSON.parse(readFileSync$1(configPath, "utf-8"));
|
|
@@ -9501,19 +9636,19 @@ function loadAgentConfig() {
|
|
|
9501
9636
|
return {};
|
|
9502
9637
|
}
|
|
9503
9638
|
function getSessionSvampDir(directory) {
|
|
9504
|
-
return join(directory, ".svamp");
|
|
9639
|
+
return join$1(directory, ".svamp");
|
|
9505
9640
|
}
|
|
9506
9641
|
function getSessionDir(directory, sessionId) {
|
|
9507
|
-
return join(getSessionSvampDir(directory), sessionId);
|
|
9642
|
+
return join$1(getSessionSvampDir(directory), sessionId);
|
|
9508
9643
|
}
|
|
9509
9644
|
function getSessionFilePath(directory, sessionId) {
|
|
9510
|
-
return join(getSessionDir(directory, sessionId), "session.json");
|
|
9645
|
+
return join$1(getSessionDir(directory, sessionId), "session.json");
|
|
9511
9646
|
}
|
|
9512
9647
|
function getSessionMessagesPath(directory, sessionId) {
|
|
9513
|
-
return join(getSessionDir(directory, sessionId), "messages.jsonl");
|
|
9648
|
+
return join$1(getSessionDir(directory, sessionId), "messages.jsonl");
|
|
9514
9649
|
}
|
|
9515
9650
|
function getSvampConfigPath(directory, sessionId) {
|
|
9516
|
-
return join(getSessionDir(directory, sessionId), "config.json");
|
|
9651
|
+
return join$1(getSessionDir(directory, sessionId), "config.json");
|
|
9517
9652
|
}
|
|
9518
9653
|
function readSvampConfig(configPath) {
|
|
9519
9654
|
try {
|
|
@@ -9523,19 +9658,19 @@ function readSvampConfig(configPath) {
|
|
|
9523
9658
|
return {};
|
|
9524
9659
|
}
|
|
9525
9660
|
function writeSvampConfig(configPath, config) {
|
|
9526
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
9661
|
+
mkdirSync$1(dirname(configPath), { recursive: true });
|
|
9527
9662
|
const content = JSON.stringify(config, null, 2);
|
|
9528
9663
|
const tmpPath = configPath + ".tmp";
|
|
9529
|
-
writeFileSync(tmpPath, content);
|
|
9530
|
-
renameSync(tmpPath, configPath);
|
|
9664
|
+
writeFileSync$1(tmpPath, content);
|
|
9665
|
+
renameSync$1(tmpPath, configPath);
|
|
9531
9666
|
return content;
|
|
9532
9667
|
}
|
|
9533
9668
|
function getLoopDir(directory) {
|
|
9534
|
-
return join(directory, ".claude", "loop");
|
|
9669
|
+
return join$1(directory, ".claude", "loop");
|
|
9535
9670
|
}
|
|
9536
9671
|
function readLoopState(directory) {
|
|
9537
9672
|
try {
|
|
9538
|
-
const p = join(getLoopDir(directory), "loop-state.json");
|
|
9673
|
+
const p = join$1(getLoopDir(directory), "loop-state.json");
|
|
9539
9674
|
if (!existsSync$1(p)) return null;
|
|
9540
9675
|
return JSON.parse(readFileSync$1(p, "utf-8"));
|
|
9541
9676
|
} catch {
|
|
@@ -9559,8 +9694,8 @@ function isLoopActiveForSession(directory, sessionId) {
|
|
|
9559
9694
|
}
|
|
9560
9695
|
function resolveLoopInit() {
|
|
9561
9696
|
const candidates = [
|
|
9562
|
-
join(CLAUDE_SKILLS_DIR, "loop", "bin", "loop-init.mjs"),
|
|
9563
|
-
...getBundledSkillsDir() ? [join(getBundledSkillsDir(), "loop", "bin", "loop-init.mjs")] : []
|
|
9697
|
+
join$1(CLAUDE_SKILLS_DIR, "loop", "bin", "loop-init.mjs"),
|
|
9698
|
+
...getBundledSkillsDir() ? [join$1(getBundledSkillsDir(), "loop", "bin", "loop-init.mjs")] : []
|
|
9564
9699
|
];
|
|
9565
9700
|
for (const c of candidates) if (existsSync$1(c)) return c;
|
|
9566
9701
|
return null;
|
|
@@ -9580,14 +9715,14 @@ function initLoop(directory, cfg) {
|
|
|
9580
9715
|
}
|
|
9581
9716
|
function deactivateLoop(directory) {
|
|
9582
9717
|
try {
|
|
9583
|
-
const p = join(getLoopDir(directory), "loop-state.json");
|
|
9718
|
+
const p = join$1(getLoopDir(directory), "loop-state.json");
|
|
9584
9719
|
if (!existsSync$1(p)) return;
|
|
9585
9720
|
const s = JSON.parse(readFileSync$1(p, "utf-8"));
|
|
9586
9721
|
s.active = false;
|
|
9587
9722
|
s.phase = "cancelled";
|
|
9588
9723
|
const tmp = p + ".tmp";
|
|
9589
|
-
writeFileSync(tmp, JSON.stringify(s, null, 2));
|
|
9590
|
-
renameSync(tmp, p);
|
|
9724
|
+
writeFileSync$1(tmp, JSON.stringify(s, null, 2));
|
|
9725
|
+
renameSync$1(tmp, p);
|
|
9591
9726
|
} catch {
|
|
9592
9727
|
}
|
|
9593
9728
|
}
|
|
@@ -9725,7 +9860,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
9725
9860
|
let watcher = null;
|
|
9726
9861
|
try {
|
|
9727
9862
|
const configDir = dirname(configPath);
|
|
9728
|
-
mkdirSync(configDir, { recursive: true });
|
|
9863
|
+
mkdirSync$1(configDir, { recursive: true });
|
|
9729
9864
|
watcher = watch(configDir, (eventType, filename) => {
|
|
9730
9865
|
if (filename === "config.json") configChecker();
|
|
9731
9866
|
});
|
|
@@ -9751,18 +9886,18 @@ function loadSessionIndex() {
|
|
|
9751
9886
|
}
|
|
9752
9887
|
function saveSessionIndex(index) {
|
|
9753
9888
|
const tmp = SESSION_INDEX_FILE + ".tmp";
|
|
9754
|
-
writeFileSync(tmp, JSON.stringify(index, null, 2), "utf-8");
|
|
9755
|
-
renameSync(tmp, SESSION_INDEX_FILE);
|
|
9889
|
+
writeFileSync$1(tmp, JSON.stringify(index, null, 2), "utf-8");
|
|
9890
|
+
renameSync$1(tmp, SESSION_INDEX_FILE);
|
|
9756
9891
|
}
|
|
9757
9892
|
function saveSession(session) {
|
|
9758
9893
|
const sessionDir = getSessionDir(session.directory, session.sessionId);
|
|
9759
9894
|
if (!existsSync$1(sessionDir)) {
|
|
9760
|
-
mkdirSync(sessionDir, { recursive: true });
|
|
9895
|
+
mkdirSync$1(sessionDir, { recursive: true });
|
|
9761
9896
|
}
|
|
9762
9897
|
const filePath = getSessionFilePath(session.directory, session.sessionId);
|
|
9763
9898
|
const tmpPath = filePath + ".tmp";
|
|
9764
|
-
writeFileSync(tmpPath, JSON.stringify(session, null, 2), "utf-8");
|
|
9765
|
-
renameSync(tmpPath, filePath);
|
|
9899
|
+
writeFileSync$1(tmpPath, JSON.stringify(session, null, 2), "utf-8");
|
|
9900
|
+
renameSync$1(tmpPath, filePath);
|
|
9766
9901
|
const index = loadSessionIndex();
|
|
9767
9902
|
index[session.sessionId] = { directory: session.directory, createdAt: session.createdAt };
|
|
9768
9903
|
saveSessionIndex(index);
|
|
@@ -9806,8 +9941,8 @@ function markSessionAsArchived(sessionId) {
|
|
|
9806
9941
|
if (data.stopped === true) return true;
|
|
9807
9942
|
data.stopped = true;
|
|
9808
9943
|
const tmpPath = filePath + ".tmp";
|
|
9809
|
-
writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
9810
|
-
renameSync(tmpPath, filePath);
|
|
9944
|
+
writeFileSync$1(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
9945
|
+
renameSync$1(tmpPath, filePath);
|
|
9811
9946
|
return true;
|
|
9812
9947
|
} catch {
|
|
9813
9948
|
return false;
|
|
@@ -9824,8 +9959,8 @@ function clearSessionArchivedFlag(sessionId) {
|
|
|
9824
9959
|
if (data.stopped) {
|
|
9825
9960
|
delete data.stopped;
|
|
9826
9961
|
const tmpPath = filePath + ".tmp";
|
|
9827
|
-
writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
9828
|
-
renameSync(tmpPath, filePath);
|
|
9962
|
+
writeFileSync$1(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
9963
|
+
renameSync$1(tmpPath, filePath);
|
|
9829
9964
|
}
|
|
9830
9965
|
return data;
|
|
9831
9966
|
} catch {
|
|
@@ -9857,15 +9992,15 @@ function loadPersistedSessions() {
|
|
|
9857
9992
|
}
|
|
9858
9993
|
function ensureHomeDir() {
|
|
9859
9994
|
if (!existsSync$1(SVAMP_HOME)) {
|
|
9860
|
-
mkdirSync(SVAMP_HOME, { recursive: true });
|
|
9995
|
+
mkdirSync$1(SVAMP_HOME, { recursive: true });
|
|
9861
9996
|
}
|
|
9862
9997
|
if (!existsSync$1(LOGS_DIR)) {
|
|
9863
|
-
mkdirSync(LOGS_DIR, { recursive: true });
|
|
9998
|
+
mkdirSync$1(LOGS_DIR, { recursive: true });
|
|
9864
9999
|
}
|
|
9865
10000
|
}
|
|
9866
10001
|
function createLogger() {
|
|
9867
10002
|
ensureHomeDir();
|
|
9868
|
-
const logFile = join(LOGS_DIR, `daemon-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.log`);
|
|
10003
|
+
const logFile = join$1(LOGS_DIR, `daemon-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.log`);
|
|
9869
10004
|
return {
|
|
9870
10005
|
logFilePath: logFile,
|
|
9871
10006
|
log: (...args) => {
|
|
@@ -9889,8 +10024,8 @@ function createLogger() {
|
|
|
9889
10024
|
function writeDaemonStateFile(state) {
|
|
9890
10025
|
ensureHomeDir();
|
|
9891
10026
|
const tmpPath = DAEMON_STATE_FILE + ".tmp";
|
|
9892
|
-
writeFileSync(tmpPath, JSON.stringify(state, null, 2), "utf-8");
|
|
9893
|
-
renameSync(tmpPath, DAEMON_STATE_FILE);
|
|
10027
|
+
writeFileSync$1(tmpPath, JSON.stringify(state, null, 2), "utf-8");
|
|
10028
|
+
renameSync$1(tmpPath, DAEMON_STATE_FILE);
|
|
9894
10029
|
}
|
|
9895
10030
|
function readDaemonStateFile() {
|
|
9896
10031
|
try {
|
|
@@ -10089,7 +10224,7 @@ async function startDaemon(options) {
|
|
|
10089
10224
|
logger.log('Warning: No HYPHA_TOKEN set. Run "svamp login" to authenticate.');
|
|
10090
10225
|
logger.log("Connecting anonymously...");
|
|
10091
10226
|
}
|
|
10092
|
-
const machineIdFile = join(SVAMP_HOME, "machine-id");
|
|
10227
|
+
const machineIdFile = join$1(SVAMP_HOME, "machine-id");
|
|
10093
10228
|
let machineId = process.env.SVAMP_MACHINE_ID;
|
|
10094
10229
|
if (!machineId) {
|
|
10095
10230
|
if (existsSync$1(machineIdFile)) {
|
|
@@ -10098,7 +10233,7 @@ async function startDaemon(options) {
|
|
|
10098
10233
|
if (!machineId) {
|
|
10099
10234
|
machineId = `machine-${os$1.hostname()}-${randomUUID$1().slice(0, 8)}`;
|
|
10100
10235
|
try {
|
|
10101
|
-
writeFileSync(machineIdFile, machineId, "utf-8");
|
|
10236
|
+
writeFileSync$1(machineIdFile, machineId, "utf-8");
|
|
10102
10237
|
} catch {
|
|
10103
10238
|
}
|
|
10104
10239
|
}
|
|
@@ -10108,10 +10243,10 @@ async function startDaemon(options) {
|
|
|
10108
10243
|
logger.log(` Workspace: ${hyphaWorkspace || "(default)"}`);
|
|
10109
10244
|
logger.log(` Machine ID: ${machineId}`);
|
|
10110
10245
|
let server = null;
|
|
10111
|
-
const supervisor = new ProcessSupervisor(join(SVAMP_HOME, "processes"));
|
|
10246
|
+
const supervisor = new ProcessSupervisor(join$1(SVAMP_HOME, "processes"));
|
|
10112
10247
|
await supervisor.init();
|
|
10113
10248
|
const tunnels = /* @__PURE__ */ new Map();
|
|
10114
|
-
const EXPOSED_TUNNELS_FILE = join(SVAMP_HOME, "exposed-tunnels.json");
|
|
10249
|
+
const EXPOSED_TUNNELS_FILE = join$1(SVAMP_HOME, "exposed-tunnels.json");
|
|
10115
10250
|
function loadExposedTunnels() {
|
|
10116
10251
|
try {
|
|
10117
10252
|
if (!existsSync$1(EXPOSED_TUNNELS_FILE)) return [];
|
|
@@ -10124,8 +10259,8 @@ async function startDaemon(options) {
|
|
|
10124
10259
|
}
|
|
10125
10260
|
function saveExposedTunnels(specs) {
|
|
10126
10261
|
try {
|
|
10127
|
-
mkdirSync(SVAMP_HOME, { recursive: true });
|
|
10128
|
-
writeFileSync(EXPOSED_TUNNELS_FILE, JSON.stringify({ tunnels: specs }, null, 2));
|
|
10262
|
+
mkdirSync$1(SVAMP_HOME, { recursive: true });
|
|
10263
|
+
writeFileSync$1(EXPOSED_TUNNELS_FILE, JSON.stringify({ tunnels: specs }, null, 2));
|
|
10129
10264
|
} catch (err) {
|
|
10130
10265
|
logger.log(`[exposed-tunnels] Persist failed: ${err.message}`);
|
|
10131
10266
|
}
|
|
@@ -10139,7 +10274,7 @@ async function startDaemon(options) {
|
|
|
10139
10274
|
const list = loadExposedTunnels().filter((t) => t.name !== name);
|
|
10140
10275
|
saveExposedTunnels(list);
|
|
10141
10276
|
}
|
|
10142
|
-
const { ServeManager } = await import('./serveManager-
|
|
10277
|
+
const { ServeManager } = await import('./serveManager-Bq33kB5r.mjs');
|
|
10143
10278
|
const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
10144
10279
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
10145
10280
|
});
|
|
@@ -10330,8 +10465,8 @@ async function startDaemon(options) {
|
|
|
10330
10465
|
machineId,
|
|
10331
10466
|
homeDir: os$1.homedir(),
|
|
10332
10467
|
svampHomeDir: SVAMP_HOME,
|
|
10333
|
-
svampLibDir: join(__dirname$1, ".."),
|
|
10334
|
-
svampToolsDir: join(__dirname$1, "..", "tools"),
|
|
10468
|
+
svampLibDir: join$1(__dirname$1, ".."),
|
|
10469
|
+
svampToolsDir: join$1(__dirname$1, "..", "tools"),
|
|
10335
10470
|
startedFromDaemon: true,
|
|
10336
10471
|
startedBy: "daemon",
|
|
10337
10472
|
lifecycleState: resumeSessionId ? "idle" : "starting",
|
|
@@ -10438,7 +10573,7 @@ async function startDaemon(options) {
|
|
|
10438
10573
|
if (persisted && persisted.sessionId !== sessionId) {
|
|
10439
10574
|
const oldDir = persisted.directory || directory;
|
|
10440
10575
|
const newSessionDir = getSessionDir(directory, sessionId);
|
|
10441
|
-
if (!existsSync$1(newSessionDir)) mkdirSync(newSessionDir, { recursive: true });
|
|
10576
|
+
if (!existsSync$1(newSessionDir)) mkdirSync$1(newSessionDir, { recursive: true });
|
|
10442
10577
|
const oldMsgFile = getSessionMessagesPath(oldDir, persisted.sessionId);
|
|
10443
10578
|
const newMsgFile = getSessionMessagesPath(directory, sessionId);
|
|
10444
10579
|
try {
|
|
@@ -11562,7 +11697,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
11562
11697
|
const children = [];
|
|
11563
11698
|
await Promise.all(entries.map(async (entry) => {
|
|
11564
11699
|
if (entry.isSymbolicLink()) return;
|
|
11565
|
-
const childPath = join(p, entry.name);
|
|
11700
|
+
const childPath = join$1(p, entry.name);
|
|
11566
11701
|
const childNode = await buildTree(childPath, entry.name, depth + 1);
|
|
11567
11702
|
if (childNode) children.push(childNode);
|
|
11568
11703
|
}));
|
|
@@ -11725,8 +11860,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
11725
11860
|
machineId,
|
|
11726
11861
|
homeDir: os$1.homedir(),
|
|
11727
11862
|
svampHomeDir: SVAMP_HOME,
|
|
11728
|
-
svampLibDir: join(__dirname$1, ".."),
|
|
11729
|
-
svampToolsDir: join(__dirname$1, "..", "tools"),
|
|
11863
|
+
svampLibDir: join$1(__dirname$1, ".."),
|
|
11864
|
+
svampToolsDir: join$1(__dirname$1, "..", "tools"),
|
|
11730
11865
|
startedFromDaemon: true,
|
|
11731
11866
|
startedBy: "daemon",
|
|
11732
11867
|
lifecycleState: "starting",
|
|
@@ -12031,7 +12166,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
12031
12166
|
const children = [];
|
|
12032
12167
|
await Promise.all(entries.map(async (entry) => {
|
|
12033
12168
|
if (entry.isSymbolicLink()) return;
|
|
12034
|
-
const childPath = join(p, entry.name);
|
|
12169
|
+
const childPath = join$1(p, entry.name);
|
|
12035
12170
|
const childNode = await buildTree(childPath, entry.name, depth + 1);
|
|
12036
12171
|
if (childNode) children.push(childNode);
|
|
12037
12172
|
}));
|
|
@@ -12267,8 +12402,20 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
12267
12402
|
};
|
|
12268
12403
|
const archiveSession = (sessionId) => {
|
|
12269
12404
|
logger.log(`Archiving session: ${sessionId}`);
|
|
12405
|
+
let loopDir;
|
|
12406
|
+
for (const s of pidToTrackedSession.values()) {
|
|
12407
|
+
if (s.svampSessionId === sessionId) {
|
|
12408
|
+
loopDir = s.directory;
|
|
12409
|
+
break;
|
|
12410
|
+
}
|
|
12411
|
+
}
|
|
12412
|
+
if (!loopDir) loopDir = loadSessionIndex()[sessionId]?.directory;
|
|
12270
12413
|
const wasInMemory = teardownTrackedSession(sessionId);
|
|
12271
12414
|
const markedArchived = markSessionAsArchived(sessionId);
|
|
12415
|
+
if (loopDir && isLoopActiveForSession(loopDir, sessionId)) {
|
|
12416
|
+
deactivateLoop(loopDir);
|
|
12417
|
+
logger.log(`Deactivated loop for archived session ${sessionId}`);
|
|
12418
|
+
}
|
|
12272
12419
|
if (wasInMemory || markedArchived) {
|
|
12273
12420
|
logger.log(`Session ${sessionId} archived (inMemory=${wasInMemory}, persisted=${markedArchived})`);
|
|
12274
12421
|
return true;
|
|
@@ -12312,8 +12459,19 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
12312
12459
|
};
|
|
12313
12460
|
const deleteSession = (sessionId) => {
|
|
12314
12461
|
logger.log(`Deleting session: ${sessionId}`);
|
|
12462
|
+
let loopDir;
|
|
12463
|
+
for (const s of pidToTrackedSession.values()) {
|
|
12464
|
+
if (s.svampSessionId === sessionId) {
|
|
12465
|
+
loopDir = s.directory;
|
|
12466
|
+
break;
|
|
12467
|
+
}
|
|
12468
|
+
}
|
|
12469
|
+
if (!loopDir) loopDir = loadSessionIndex()[sessionId]?.directory;
|
|
12315
12470
|
teardownTrackedSession(sessionId);
|
|
12316
12471
|
deletePersistedSession(sessionId);
|
|
12472
|
+
if (loopDir && isLoopActiveForSession(loopDir, sessionId)) {
|
|
12473
|
+
deactivateLoop(loopDir);
|
|
12474
|
+
}
|
|
12317
12475
|
logger.log(`Session ${sessionId} deleted`);
|
|
12318
12476
|
return true;
|
|
12319
12477
|
};
|
|
@@ -12370,7 +12528,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
12370
12528
|
svampVersion: "0.1.0 (hypha)",
|
|
12371
12529
|
homeDir: defaultHomeDir,
|
|
12372
12530
|
svampHomeDir: SVAMP_HOME,
|
|
12373
|
-
svampLibDir: join(__dirname$1, ".."),
|
|
12531
|
+
svampLibDir: join$1(__dirname$1, ".."),
|
|
12374
12532
|
displayName: process.env.SVAMP_DISPLAY_NAME || void 0,
|
|
12375
12533
|
isolationCapabilities,
|
|
12376
12534
|
// Restore persisted sharing (possibly augmented with --share seed above),
|
|
@@ -12439,7 +12597,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
12439
12597
|
const channelHttpPort = Number(process.env.SVAMP_CHANNEL_HTTP_PORT) || 0;
|
|
12440
12598
|
if (channelHttpPort > 0) {
|
|
12441
12599
|
try {
|
|
12442
|
-
const { createChannelHttpServer } = await import('./httpServer-
|
|
12600
|
+
const { createChannelHttpServer } = await import('./httpServer-CWn3F-0t.mjs');
|
|
12443
12601
|
const channelHttpServer = createChannelHttpServer({
|
|
12444
12602
|
getSessionIds: () => {
|
|
12445
12603
|
const ids = [];
|
|
@@ -12460,7 +12618,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
12460
12618
|
const specs = loadExposedTunnels();
|
|
12461
12619
|
if (specs.length === 0) return;
|
|
12462
12620
|
logger.log(`[exposed-tunnels] Restoring ${specs.length} tunnel(s) from ${EXPOSED_TUNNELS_FILE}`);
|
|
12463
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
12621
|
+
const { FrpcTunnel } = await import('./frpc-B8ORdlOO.mjs');
|
|
12464
12622
|
for (const spec of specs) {
|
|
12465
12623
|
if (tunnels.has(spec.name)) continue;
|
|
12466
12624
|
try {
|
|
@@ -12924,8 +13082,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
12924
13082
|
if (existsSync$1(filePath)) {
|
|
12925
13083
|
const data = JSON.parse(readFileSync$1(filePath, "utf-8"));
|
|
12926
13084
|
const tmpPath = filePath + ".tmp";
|
|
12927
|
-
writeFileSync(tmpPath, JSON.stringify({ ...data, stopped: true }, null, 2), "utf-8");
|
|
12928
|
-
renameSync(tmpPath, filePath);
|
|
13085
|
+
writeFileSync$1(tmpPath, JSON.stringify({ ...data, stopped: true }, null, 2), "utf-8");
|
|
13086
|
+
renameSync$1(tmpPath, filePath);
|
|
12929
13087
|
markedCount++;
|
|
12930
13088
|
}
|
|
12931
13089
|
} catch {
|
|
@@ -12988,7 +13146,7 @@ async function stopDaemon(options) {
|
|
|
12988
13146
|
const mode = options?.cleanup ? "cleanup (sessions will be stopped)" : "quick (sessions preserved for auto-restore)";
|
|
12989
13147
|
writeStopMarker(`stopDaemon (${options?.cleanup ? "cleanup" : "quick"})`);
|
|
12990
13148
|
const pidsToSignal = [];
|
|
12991
|
-
const supervisorPidFile = join(SVAMP_HOME, "supervisor.pid");
|
|
13149
|
+
const supervisorPidFile = join$1(SVAMP_HOME, "supervisor.pid");
|
|
12992
13150
|
try {
|
|
12993
13151
|
if (existsSync$1(supervisorPidFile)) {
|
|
12994
13152
|
const supervisorPid = parseInt(readFileSync$1(supervisorPidFile, "utf-8").trim(), 10);
|
|
@@ -13093,7 +13251,7 @@ async function stopDaemon(options) {
|
|
|
13093
13251
|
}
|
|
13094
13252
|
}
|
|
13095
13253
|
async function restartDaemon() {
|
|
13096
|
-
const supervisorPidFile = join(SVAMP_HOME, "supervisor.pid");
|
|
13254
|
+
const supervisorPidFile = join$1(SVAMP_HOME, "supervisor.pid");
|
|
13097
13255
|
let supervisorPid = null;
|
|
13098
13256
|
try {
|
|
13099
13257
|
if (existsSync$1(supervisorPidFile)) {
|
|
@@ -13130,7 +13288,7 @@ async function restartDaemon() {
|
|
|
13130
13288
|
});
|
|
13131
13289
|
child.unref();
|
|
13132
13290
|
}
|
|
13133
|
-
const stateFile2 = join(SVAMP_HOME, "daemon.state.json");
|
|
13291
|
+
const stateFile2 = join$1(SVAMP_HOME, "daemon.state.json");
|
|
13134
13292
|
for (let i = 0; i < 100; i++) {
|
|
13135
13293
|
await new Promise((r) => setTimeout(r, 100));
|
|
13136
13294
|
if (existsSync$1(stateFile2)) {
|
|
@@ -13154,7 +13312,7 @@ async function restartDaemon() {
|
|
|
13154
13312
|
await doFullRestart("Failed to signal supervisor");
|
|
13155
13313
|
return;
|
|
13156
13314
|
}
|
|
13157
|
-
const stateFile = join(SVAMP_HOME, "daemon.state.json");
|
|
13315
|
+
const stateFile = join$1(SVAMP_HOME, "daemon.state.json");
|
|
13158
13316
|
for (let i = 0; i < 300; i++) {
|
|
13159
13317
|
await new Promise((r) => setTimeout(r, 100));
|
|
13160
13318
|
try {
|
|
@@ -13179,7 +13337,7 @@ async function restartDaemon() {
|
|
|
13179
13337
|
function daemonStatus() {
|
|
13180
13338
|
const state = readDaemonStateFile();
|
|
13181
13339
|
if (!state) {
|
|
13182
|
-
const plistPath = join(os$1.homedir(), "Library", "LaunchAgents", "io.hypha.svamp.daemon.plist");
|
|
13340
|
+
const plistPath = join$1(os$1.homedir(), "Library", "LaunchAgents", "io.hypha.svamp.daemon.plist");
|
|
13183
13341
|
if (existsSync$1(plistPath)) {
|
|
13184
13342
|
console.log("Status: Not running (launchd service installed \u2014 may be starting)");
|
|
13185
13343
|
} else {
|