svamp-cli 0.2.299 → 0.2.301
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adminCommands-D08GLn9K.mjs → adminCommands-DMiliv55.mjs} +1 -1
- package/dist/{agentCommands-DLpNORpD.mjs → agentCommands-BCkWdFFW.mjs} +5 -5
- package/dist/{auth-Cq1Tggti.mjs → auth-Ba1_zl1h.mjs} +1 -1
- package/dist/{cli-Dd82AdTd.mjs → cli-Bv0Zhf3M.mjs} +69 -69
- package/dist/cli.mjs +2 -2
- package/dist/{commands-BdgY6EVk.mjs → commands-B2mRza8S.mjs} +2 -2
- package/dist/{commands-C_ROarRL.mjs → commands-BA3eBROn.mjs} +1 -1
- package/dist/{commands-C60tZyej.mjs → commands-BAlaK67Y.mjs} +1 -1
- package/dist/{commands-CBXkcBgD.mjs → commands-BC3RuhRx.mjs} +2 -2
- package/dist/commands-BnirT9RR.mjs +161 -0
- package/dist/{commands-BAuKr5Bn.mjs → commands-DhB2iz0_.mjs} +7 -7
- package/dist/{commands-dC4o6d-s.mjs → commands-bIioWHWe.mjs} +2 -2
- package/dist/{commands-BzaZz_ke.mjs → commands-hHZu5qJh.mjs} +1 -1
- package/dist/{fleet-CbK3VsXL.mjs → fleet-BFurNsSA.mjs} +1 -1
- package/dist/{frpc-BqfCBMke.mjs → frpc-Cb6jUvED.mjs} +1 -1
- package/dist/{headlessCli-Wm7z6_EN.mjs → headlessCli-DP9gpAxv.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{notifyCommands-DM1mwI92.mjs → notifyCommands-C0i6P1yC.mjs} +1 -1
- package/dist/package-D8lHDQja.mjs +64 -0
- package/dist/{rpc-BJySEOZA.mjs → rpc-B-bxS-Pv.mjs} +1 -1
- package/dist/{rpc-DZekVCXH.mjs → rpc-CNkfUlz1.mjs} +1 -1
- package/dist/{run-C_5djiX4.mjs → run-C0GhrtVf.mjs} +433 -77
- package/dist/{run-BYIuK238.mjs → run-CvT_37Ov.mjs} +1 -1
- package/dist/{scheduler-CJ4qyaq2.mjs → scheduler-DT1_BBdZ.mjs} +1 -1
- package/dist/{serveCommands-DIutqRPk.mjs → serveCommands-CUabu97u.mjs} +5 -5
- package/dist/{sideband-BVmN7F-h.mjs → sideband-BlZBmbji.mjs} +1 -1
- package/package.json +2 -2
- package/dist/commands-QrHdpbuH.mjs +0 -293
- package/dist/package-BJe_70EQ.mjs +0 -64
|
@@ -2973,7 +2973,7 @@ Connection: close\r
|
|
|
2973
2973
|
const mount = this.mounts.get(mountName);
|
|
2974
2974
|
const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
|
|
2975
2975
|
try {
|
|
2976
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
2976
|
+
const { FrpcTunnel } = await import('./frpc-Cb6jUvED.mjs');
|
|
2977
2977
|
let tunnel;
|
|
2978
2978
|
tunnel = new FrpcTunnel({
|
|
2979
2979
|
name: tunnelName,
|
|
@@ -4343,6 +4343,9 @@ const MAX_BODY = 16 * 1024;
|
|
|
4343
4343
|
function xmlEscape(s) {
|
|
4344
4344
|
return String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4345
4345
|
}
|
|
4346
|
+
function xmlEscapeContent(s) {
|
|
4347
|
+
return String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
4348
|
+
}
|
|
4346
4349
|
const stripControl = (s) => String(s ?? "").replace(/[\x00-\x1f\x7f]/g, " ");
|
|
4347
4350
|
function neutralizeForgedEnvelope(text) {
|
|
4348
4351
|
const s = String(text ?? "");
|
|
@@ -4350,7 +4353,7 @@ function neutralizeForgedEnvelope(text) {
|
|
|
4350
4353
|
}
|
|
4351
4354
|
function renderMessage(channel, { sender = {}, body = {}, query = {}, callId, now }) {
|
|
4352
4355
|
const obj = (v) => typeof v === "object" && v !== null ? JSON.stringify(v) : v;
|
|
4353
|
-
const escVal = (v) =>
|
|
4356
|
+
const escVal = (v) => xmlEscapeContent(obj(v));
|
|
4354
4357
|
const escAttr = (v) => xmlEscape(stripControl(obj(v)));
|
|
4355
4358
|
const bodyEsc = {};
|
|
4356
4359
|
for (const [k, v] of Object.entries(body)) bodyEsc[k] = k === "message" ? escVal(String(v ?? "").slice(0, MAX_BODY)) : escAttr(v);
|
|
@@ -4636,11 +4639,6 @@ class ChannelOutbox {
|
|
|
4636
4639
|
}
|
|
4637
4640
|
}
|
|
4638
4641
|
|
|
4639
|
-
var outbox = /*#__PURE__*/Object.freeze({
|
|
4640
|
-
__proto__: null,
|
|
4641
|
-
ChannelOutbox: ChannelOutbox
|
|
4642
|
-
});
|
|
4643
|
-
|
|
4644
4642
|
function getParamNames(fn) {
|
|
4645
4643
|
const src = fn.toString();
|
|
4646
4644
|
const match = src.match(/^(?:async\s+)?(?:function\s*\w*)?\s*\(([^)]*)\)/);
|
|
@@ -5869,7 +5867,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5869
5867
|
const tunnels = handlers.tunnels;
|
|
5870
5868
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
5871
5869
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
5872
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
5870
|
+
const { FrpcTunnel } = await import('./frpc-Cb6jUvED.mjs');
|
|
5873
5871
|
const tunnel = new FrpcTunnel({
|
|
5874
5872
|
name: params.name,
|
|
5875
5873
|
ports: params.ports,
|
|
@@ -6345,7 +6343,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6345
6343
|
}
|
|
6346
6344
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
6347
6345
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
6348
|
-
const { toolsForRole } = await import('./sideband-
|
|
6346
|
+
const { toolsForRole } = await import('./sideband-BlZBmbji.mjs');
|
|
6349
6347
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
6350
6348
|
return fmt(r2);
|
|
6351
6349
|
}
|
|
@@ -6450,7 +6448,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6450
6448
|
return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
|
|
6451
6449
|
}
|
|
6452
6450
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
6453
|
-
const { queryCore } = await import('./commands-
|
|
6451
|
+
const { queryCore } = await import('./commands-BAlaK67Y.mjs');
|
|
6454
6452
|
const timeout = c.reply?.timeout_sec || 120;
|
|
6455
6453
|
let result;
|
|
6456
6454
|
try {
|
|
@@ -6602,6 +6600,44 @@ ${d?.error || "not found"}`;
|
|
|
6602
6600
|
{ overwrite: true }
|
|
6603
6601
|
);
|
|
6604
6602
|
console.log(`[HYPHA MACHINE] Channels service registered: ${channelsServiceInfo.id} (type svamp-channels)`);
|
|
6603
|
+
const OUTPOST_MISSING_SESSION = 'missing "session" \u2014 the outpost gateway routes by session id. Over HTTP wrap args: POST {"kwargs": {"session": "<id>", "token": "<token>", "machine": "<name>"}}.';
|
|
6604
|
+
const routeOutpost = (session) => {
|
|
6605
|
+
const sid = String(session || "").trim();
|
|
6606
|
+
if (!sid) return { error: OUTPOST_MISSING_SESSION };
|
|
6607
|
+
const rpc = handlers.getSessionRPCHandlers?.(sid);
|
|
6608
|
+
if (!rpc?.outpostHello) return { error: `session ${sid.slice(0, 12)} is not connected on this machine` };
|
|
6609
|
+
return { rpc };
|
|
6610
|
+
};
|
|
6611
|
+
const outpostServiceInfo = await server.registerService(
|
|
6612
|
+
{
|
|
6613
|
+
id: "outpost",
|
|
6614
|
+
name: `Svamp Outpost (${currentMetadata.displayName || machineId})`,
|
|
6615
|
+
type: "svamp-outpost",
|
|
6616
|
+
// Public so a remote machine (possibly anonymous) can dial in; token-authed per call,
|
|
6617
|
+
// so no Hypha identity/context is needed (the handlers ignore context entirely).
|
|
6618
|
+
config: { visibility: "public", require_context: false },
|
|
6619
|
+
hello: async (kwargs = {}) => {
|
|
6620
|
+
trackInbound();
|
|
6621
|
+
const r = routeOutpost(kwargs?.session);
|
|
6622
|
+
if ("error" in r) return { error: r.error };
|
|
6623
|
+
return r.rpc.outpostHello(kwargs);
|
|
6624
|
+
},
|
|
6625
|
+
poll: async (kwargs = {}) => {
|
|
6626
|
+
trackInbound();
|
|
6627
|
+
const r = routeOutpost(kwargs?.session);
|
|
6628
|
+
if ("error" in r) return { error: r.error };
|
|
6629
|
+
return r.rpc.outpostPoll(kwargs);
|
|
6630
|
+
},
|
|
6631
|
+
result: async (kwargs = {}) => {
|
|
6632
|
+
trackInbound();
|
|
6633
|
+
const r = routeOutpost(kwargs?.session);
|
|
6634
|
+
if ("error" in r) return { error: r.error };
|
|
6635
|
+
return r.rpc.outpostResult(kwargs);
|
|
6636
|
+
}
|
|
6637
|
+
},
|
|
6638
|
+
{ overwrite: true }
|
|
6639
|
+
);
|
|
6640
|
+
console.log(`[HYPHA MACHINE] Outpost service registered: ${outpostServiceInfo.id} (type svamp-outpost)`);
|
|
6605
6641
|
return {
|
|
6606
6642
|
serviceInfo,
|
|
6607
6643
|
notifySessionEvent: notifyListeners,
|
|
@@ -6636,6 +6672,8 @@ ${d?.error || "not found"}`;
|
|
|
6636
6672
|
await server.unregisterService(serviceInfo.id);
|
|
6637
6673
|
await server.unregisterService(channelsServiceInfo.id).catch(() => {
|
|
6638
6674
|
});
|
|
6675
|
+
await server.unregisterService(outpostServiceInfo.id).catch(() => {
|
|
6676
|
+
});
|
|
6639
6677
|
}
|
|
6640
6678
|
};
|
|
6641
6679
|
}
|
|
@@ -6917,6 +6955,52 @@ function nextBackoffMs(prevMs) {
|
|
|
6917
6955
|
if (!prevMs || prevMs <= 0) return RL_BACKOFF_MIN_MS;
|
|
6918
6956
|
return Math.min(prevMs * 2, RL_BACKOFF_MAX_MS);
|
|
6919
6957
|
}
|
|
6958
|
+
function retryDecision(listenerPresent, now, backoffUntil) {
|
|
6959
|
+
if (!listenerPresent) return "drop";
|
|
6960
|
+
if (backoffUntil !== void 0 && now < backoffUntil) return "reschedule";
|
|
6961
|
+
return "deliver";
|
|
6962
|
+
}
|
|
6963
|
+
function retryDelayMs(now, backoffUntil) {
|
|
6964
|
+
if (backoffUntil === void 0) return 0;
|
|
6965
|
+
return Math.max(0, backoffUntil - now);
|
|
6966
|
+
}
|
|
6967
|
+
|
|
6968
|
+
function tokenPath(projectDir, session) {
|
|
6969
|
+
return join$1(projectDir, ".svamp", session, "outpost.json");
|
|
6970
|
+
}
|
|
6971
|
+
function genToken() {
|
|
6972
|
+
return randomBytes(24).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
6973
|
+
}
|
|
6974
|
+
function loadOutpostToken(projectDir, session) {
|
|
6975
|
+
const file = tokenPath(projectDir, session);
|
|
6976
|
+
if (!existsSync(file)) return null;
|
|
6977
|
+
try {
|
|
6978
|
+
const parsed = JSON.parse(readFileSync(file, "utf-8"));
|
|
6979
|
+
if (!parsed || typeof parsed.token !== "string" || !parsed.token) return null;
|
|
6980
|
+
return parsed;
|
|
6981
|
+
} catch {
|
|
6982
|
+
return null;
|
|
6983
|
+
}
|
|
6984
|
+
}
|
|
6985
|
+
function ensureOutpostToken(projectDir, session) {
|
|
6986
|
+
const existing = loadOutpostToken(projectDir, session);
|
|
6987
|
+
if (existing) return existing;
|
|
6988
|
+
const rec = { version: 1, session, token: genToken(), createdAt: Date.now() };
|
|
6989
|
+
const file = tokenPath(projectDir, session);
|
|
6990
|
+
mkdirSync$1(dirname(file), { recursive: true });
|
|
6991
|
+
writeFileSync$1(file, JSON.stringify(rec, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
6992
|
+
try {
|
|
6993
|
+
chmodSync(file, 384);
|
|
6994
|
+
} catch {
|
|
6995
|
+
}
|
|
6996
|
+
return rec;
|
|
6997
|
+
}
|
|
6998
|
+
function outpostTokenMatches(rec, presented) {
|
|
6999
|
+
if (!rec || typeof presented !== "string" || presented.length !== rec.token.length) return false;
|
|
7000
|
+
let diff = 0;
|
|
7001
|
+
for (let i = 0; i < rec.token.length; i++) diff |= rec.token.charCodeAt(i) ^ presented.charCodeAt(i);
|
|
7002
|
+
return diff === 0;
|
|
7003
|
+
}
|
|
6920
7004
|
|
|
6921
7005
|
const DEFAULT_DOWNLOAD_BASE = "https://github.com/amun-ai/hypha-cloud/releases/latest/download";
|
|
6922
7006
|
function shq(v) {
|
|
@@ -6928,10 +7012,10 @@ function psq(v) {
|
|
|
6928
7012
|
function buildOutpostInstall(params) {
|
|
6929
7013
|
const base = params.serverBase.replace(/\/$/, "");
|
|
6930
7014
|
const dl = (params.downloadBase || DEFAULT_DOWNLOAD_BASE).replace(/\/$/, "");
|
|
6931
|
-
const session = params.session
|
|
6932
|
-
const unix = `curl -fsSL ${shq(`${dl}/install.sh`)} | SVAMP_OUTPOST_SERVER=${shq(base)}
|
|
6933
|
-
const windows = `$env:SVAMP_OUTPOST_SERVER=${psq(base)}; $env:
|
|
6934
|
-
const direct = `svamp-outpost --server ${shq(base)} --
|
|
7015
|
+
const session = params.session;
|
|
7016
|
+
const unix = `curl -fsSL ${shq(`${dl}/install.sh`)} | SVAMP_OUTPOST_SERVER=${shq(base)} SVAMP_OUTPOST_TOKEN=${shq(params.token)} SVAMP_OUTPOST_SESSION=${shq(session)} SVAMP_OUTPOST_DL=${shq(dl)} sh`;
|
|
7017
|
+
const windows = `$env:SVAMP_OUTPOST_SERVER=${psq(base)}; $env:SVAMP_OUTPOST_TOKEN=${psq(params.token)}; $env:SVAMP_OUTPOST_SESSION=${psq(session)}; $env:SVAMP_OUTPOST_DL=${psq(dl)}; irm ${psq(`${dl}/install.ps1`)} | iex`;
|
|
7018
|
+
const direct = `svamp-outpost --server ${shq(base)} --token ${shq(params.token)} --session ${shq(session)}`;
|
|
6935
7019
|
return { unix, windows, direct };
|
|
6936
7020
|
}
|
|
6937
7021
|
|
|
@@ -6943,37 +7027,19 @@ const OUTPOST_BINARIES = [
|
|
|
6943
7027
|
{ label: "Linux (arm64)", file: "svamp-outpost-linux-arm64" },
|
|
6944
7028
|
{ label: "Windows (x86-64)", file: "svamp-outpost-windows-amd64.exe" }
|
|
6945
7029
|
];
|
|
6946
|
-
function
|
|
7030
|
+
function setupOutpost(params) {
|
|
6947
7031
|
const serverUrl = params.serverUrl || "https://hypha.aicell.io";
|
|
6948
7032
|
const downloadBase = (params.downloadBase || DEFAULT_OUTPOST_DOWNLOAD_BASE).replace(/\/$/, "");
|
|
6949
|
-
const serverBase = gatewayBase(params.
|
|
6950
|
-
const
|
|
6951
|
-
const
|
|
6952
|
-
const existing = store.get(channelId);
|
|
6953
|
-
let token = existing?.identity?.callers?.find((c) => c.name === "outpost")?.key;
|
|
6954
|
-
if (!token) token = genKey();
|
|
6955
|
-
const channel = {
|
|
6956
|
-
id: channelId,
|
|
6957
|
-
name: `Remote outpost \u2014 ${params.session}`,
|
|
6958
|
-
description: "FDE remote machine: a svamp-outpost binary connects here and the agent runs commands on it.",
|
|
6959
|
-
enabled: true,
|
|
6960
|
-
bind: { session: params.session },
|
|
6961
|
-
identity: { mode: "per-key", callers: [{ name: "outpost", kind: "agent", key: token }] },
|
|
6962
|
-
action: { kind: "message" },
|
|
6963
|
-
reply: { mode: "queue" },
|
|
6964
|
-
skill: { name: "remote-outpost", description: "Run commands on the connected remote machine." }
|
|
6965
|
-
};
|
|
6966
|
-
store.save(channel);
|
|
6967
|
-
const install = buildOutpostInstall({ serverBase, channel: channelId, token, session: params.session, downloadBase });
|
|
6968
|
-
const connectedCount = (store.get(channelId)?.last_calls || []).filter((c) => c.sender === "outpost").length;
|
|
7033
|
+
const serverBase = gatewayBase(params.outpostServiceId, serverUrl);
|
|
7034
|
+
const { token } = ensureOutpostToken(params.projectDir, params.session);
|
|
7035
|
+
const install = buildOutpostInstall({ serverBase, session: params.session, token, downloadBase });
|
|
6969
7036
|
return {
|
|
6970
|
-
channelId,
|
|
6971
7037
|
token,
|
|
6972
7038
|
serverBase,
|
|
6973
7039
|
downloadBase,
|
|
6974
7040
|
install,
|
|
6975
7041
|
binaries: OUTPOST_BINARIES.map((b) => ({ label: b.label, url: `${downloadBase}/${b.file}` })),
|
|
6976
|
-
connectedCount
|
|
7042
|
+
connectedCount: params.connectedCount || 0
|
|
6977
7043
|
};
|
|
6978
7044
|
}
|
|
6979
7045
|
|
|
@@ -7058,7 +7124,6 @@ async function mintPairingCode(opts) {
|
|
|
7058
7124
|
const expiresAt = now + ttlMs;
|
|
7059
7125
|
const pairJson = JSON.stringify({
|
|
7060
7126
|
server: payload.server,
|
|
7061
|
-
channel: payload.channel,
|
|
7062
7127
|
token: payload.token,
|
|
7063
7128
|
session: payload.session,
|
|
7064
7129
|
expiresAt
|
|
@@ -7095,6 +7160,157 @@ async function mintPairingCode(opts) {
|
|
|
7095
7160
|
return { code, resolver, expiresAt };
|
|
7096
7161
|
}
|
|
7097
7162
|
|
|
7163
|
+
const OUTPOST_STALE_MS = 9e4;
|
|
7164
|
+
const OUTPOST_EXEC_TIMEOUT_MS = 10 * 6e4;
|
|
7165
|
+
class OutpostCoordinator {
|
|
7166
|
+
conns = /* @__PURE__ */ new Map();
|
|
7167
|
+
queue = /* @__PURE__ */ new Map();
|
|
7168
|
+
pending = /* @__PURE__ */ new Map();
|
|
7169
|
+
seq = 0;
|
|
7170
|
+
now;
|
|
7171
|
+
setTimer;
|
|
7172
|
+
clearTimer;
|
|
7173
|
+
execTimeoutMs;
|
|
7174
|
+
staleMs;
|
|
7175
|
+
constructor(opts = {}) {
|
|
7176
|
+
this.now = opts.now || (() => Date.now());
|
|
7177
|
+
this.setTimer = opts.setTimer || ((fn, ms) => setTimeout(fn, ms));
|
|
7178
|
+
this.clearTimer = opts.clearTimer || ((t) => clearTimeout(t));
|
|
7179
|
+
this.execTimeoutMs = opts.execTimeoutMs ?? OUTPOST_EXEC_TIMEOUT_MS;
|
|
7180
|
+
this.staleMs = opts.staleMs ?? OUTPOST_STALE_MS;
|
|
7181
|
+
}
|
|
7182
|
+
isStale(c, at) {
|
|
7183
|
+
return at - c.lastSeen > this.staleMs;
|
|
7184
|
+
}
|
|
7185
|
+
/** Drop dead connections; fail any in-flight execs bound to them so callers never hang. */
|
|
7186
|
+
prune(at = this.now()) {
|
|
7187
|
+
for (const [machine, c] of [...this.conns]) {
|
|
7188
|
+
if (this.isStale(c, at)) {
|
|
7189
|
+
this.conns.delete(machine);
|
|
7190
|
+
this.queue.delete(machine);
|
|
7191
|
+
this.failMachine(machine, "the remote outpost disconnected");
|
|
7192
|
+
}
|
|
7193
|
+
}
|
|
7194
|
+
}
|
|
7195
|
+
/** Reject every pending exec on a machine with a synthetic disconnect result (exitCode -1). */
|
|
7196
|
+
failMachine(machine, reason) {
|
|
7197
|
+
for (const [id, p] of [...this.pending]) {
|
|
7198
|
+
if (p.machine !== machine) continue;
|
|
7199
|
+
this.clearTimer(p.timer);
|
|
7200
|
+
this.pending.delete(id);
|
|
7201
|
+
p.resolve({ id, stdout: "", stderr: reason, exitCode: -1, synthetic: true, ts: this.now() });
|
|
7202
|
+
}
|
|
7203
|
+
}
|
|
7204
|
+
/**
|
|
7205
|
+
* A remote said hello (first connect OR reconnect after an outage). Upserts the connection and
|
|
7206
|
+
* returns `isNew` = true when it should trigger the ONE-TIME inbox announce — true on the first
|
|
7207
|
+
* appearance and on a reconnect after the connection had gone stale/pruned.
|
|
7208
|
+
*/
|
|
7209
|
+
registerConnection(machine, meta = {}) {
|
|
7210
|
+
const at = this.now();
|
|
7211
|
+
this.prune(at);
|
|
7212
|
+
const existing = this.conns.get(machine);
|
|
7213
|
+
const isNew = !existing;
|
|
7214
|
+
const connection = {
|
|
7215
|
+
machine,
|
|
7216
|
+
label: meta.label ?? existing?.label,
|
|
7217
|
+
os: meta.os ?? existing?.os,
|
|
7218
|
+
arch: meta.arch ?? existing?.arch,
|
|
7219
|
+
version: meta.version ?? existing?.version,
|
|
7220
|
+
connectedAt: existing?.connectedAt ?? at,
|
|
7221
|
+
lastSeen: at
|
|
7222
|
+
};
|
|
7223
|
+
this.conns.set(machine, connection);
|
|
7224
|
+
return { isNew, connection };
|
|
7225
|
+
}
|
|
7226
|
+
/** Refresh liveness for an active poll (keeps the connection "always on"). */
|
|
7227
|
+
touch(machine) {
|
|
7228
|
+
const c = this.conns.get(machine);
|
|
7229
|
+
if (!c) return false;
|
|
7230
|
+
c.lastSeen = this.now();
|
|
7231
|
+
return true;
|
|
7232
|
+
}
|
|
7233
|
+
/** Live connections only (prunes first). */
|
|
7234
|
+
listConnections() {
|
|
7235
|
+
this.prune();
|
|
7236
|
+
return [...this.conns.values()].sort((a, b) => a.connectedAt - b.connectedAt);
|
|
7237
|
+
}
|
|
7238
|
+
isConnected(machine) {
|
|
7239
|
+
this.prune();
|
|
7240
|
+
return this.conns.has(machine);
|
|
7241
|
+
}
|
|
7242
|
+
/**
|
|
7243
|
+
* Pick the machine an exec should target: an explicit live target, or the sole live outpost.
|
|
7244
|
+
* Ambiguity (0 or >1 without a target) is a clear error, never a silent guess.
|
|
7245
|
+
*/
|
|
7246
|
+
resolveTarget(target) {
|
|
7247
|
+
const live = this.listConnections();
|
|
7248
|
+
if (target) {
|
|
7249
|
+
const hit = live.find((c) => c.machine === target || c.label === target);
|
|
7250
|
+
if (!hit) return { error: `no connected outpost named "${target}" (connected: ${live.map((c) => c.machine).join(", ") || "none"})` };
|
|
7251
|
+
return { machine: hit.machine };
|
|
7252
|
+
}
|
|
7253
|
+
if (live.length === 0) return { error: "no remote outpost is connected \u2014 run the install one-liner on the remote machine first" };
|
|
7254
|
+
if (live.length > 1) return { error: `multiple outposts connected (${live.map((c) => c.machine).join(", ")}) \u2014 pass a target` };
|
|
7255
|
+
return { machine: live[0].machine };
|
|
7256
|
+
}
|
|
7257
|
+
/**
|
|
7258
|
+
* Enqueue a command for a machine and return a promise that resolves with the REAL result once
|
|
7259
|
+
* the outpost posts it back (or a synthetic exitCode -1 on disconnect/timeout). Fails fast if the
|
|
7260
|
+
* machine is not connected.
|
|
7261
|
+
*/
|
|
7262
|
+
enqueueExec(machine, cmd, cwd) {
|
|
7263
|
+
const id = `x${(++this.seq).toString(36)}_${this.now().toString(36)}`;
|
|
7264
|
+
if (!this.isConnected(machine)) {
|
|
7265
|
+
return {
|
|
7266
|
+
id,
|
|
7267
|
+
promise: Promise.resolve({ id, stdout: "", stderr: `outpost "${machine}" is not connected`, exitCode: -1, synthetic: true, ts: this.now() })
|
|
7268
|
+
};
|
|
7269
|
+
}
|
|
7270
|
+
const command = { id, cmd, cwd, ts: this.now() };
|
|
7271
|
+
const q = this.queue.get(machine) || [];
|
|
7272
|
+
q.push(command);
|
|
7273
|
+
this.queue.set(machine, q);
|
|
7274
|
+
const promise = new Promise((resolve) => {
|
|
7275
|
+
const timer = this.setTimer(() => {
|
|
7276
|
+
if (!this.pending.has(id)) return;
|
|
7277
|
+
this.pending.delete(id);
|
|
7278
|
+
resolve({ id, stdout: "", stderr: `command timed out after ${Math.round(this.execTimeoutMs / 1e3)}s`, exitCode: -1, synthetic: true, ts: this.now() });
|
|
7279
|
+
}, this.execTimeoutMs);
|
|
7280
|
+
this.pending.set(id, { machine, resolve, timer, enqueuedAt: this.now() });
|
|
7281
|
+
});
|
|
7282
|
+
return { id, promise };
|
|
7283
|
+
}
|
|
7284
|
+
/** Long-poll drain: return (and clear) the commands queued for a machine; refreshes liveness. */
|
|
7285
|
+
pollCommands(machine) {
|
|
7286
|
+
this.touch(machine);
|
|
7287
|
+
const q = this.queue.get(machine);
|
|
7288
|
+
if (!q || q.length === 0) return [];
|
|
7289
|
+
this.queue.set(machine, []);
|
|
7290
|
+
return q;
|
|
7291
|
+
}
|
|
7292
|
+
/** Deliver a result from the outpost to the waiting exec. Returns false if unknown/expired. */
|
|
7293
|
+
resolveResult(result) {
|
|
7294
|
+
const p = this.pending.get(result.id);
|
|
7295
|
+
if (!p) return false;
|
|
7296
|
+
this.clearTimer(p.timer);
|
|
7297
|
+
this.pending.delete(result.id);
|
|
7298
|
+
this.touch(p.machine);
|
|
7299
|
+
p.resolve({
|
|
7300
|
+
id: result.id,
|
|
7301
|
+
stdout: result.stdout ?? "",
|
|
7302
|
+
stderr: result.stderr ?? "",
|
|
7303
|
+
exitCode: typeof result.exitCode === "number" ? result.exitCode : 0,
|
|
7304
|
+
ts: this.now()
|
|
7305
|
+
});
|
|
7306
|
+
return true;
|
|
7307
|
+
}
|
|
7308
|
+
/** Test/introspection: how many execs are awaiting a result. */
|
|
7309
|
+
pendingCount() {
|
|
7310
|
+
return this.pending.size;
|
|
7311
|
+
}
|
|
7312
|
+
}
|
|
7313
|
+
|
|
7098
7314
|
const PARTICIPANTS_CHANNEL_ID = "sys-participants";
|
|
7099
7315
|
function channelPublicView(c) {
|
|
7100
7316
|
const u = c.upload;
|
|
@@ -7375,11 +7591,17 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
7375
7591
|
};
|
|
7376
7592
|
const listeners = [];
|
|
7377
7593
|
const rateLimitBackoff = /* @__PURE__ */ new Map();
|
|
7594
|
+
const rateLimitRetry = /* @__PURE__ */ new Map();
|
|
7378
7595
|
const removeListener = (listener, reason) => {
|
|
7379
7596
|
const idx = listeners.indexOf(listener);
|
|
7380
7597
|
if (idx >= 0) {
|
|
7381
7598
|
listeners.splice(idx, 1);
|
|
7382
7599
|
rateLimitBackoff.delete(listener);
|
|
7600
|
+
const t = rateLimitRetry.get(listener);
|
|
7601
|
+
if (t) {
|
|
7602
|
+
clearTimeout(t);
|
|
7603
|
+
rateLimitRetry.delete(listener);
|
|
7604
|
+
}
|
|
7383
7605
|
console.log(`[HYPHA SESSION ${sessionId}] Listener removed (${reason}), remaining: ${listeners.length}`);
|
|
7384
7606
|
const rintfId = listener._rintf_service_id;
|
|
7385
7607
|
if (rintfId) {
|
|
@@ -7388,6 +7610,50 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
7388
7610
|
}
|
|
7389
7611
|
}
|
|
7390
7612
|
};
|
|
7613
|
+
const handleListenerRejection = (listener, err) => {
|
|
7614
|
+
const msg = String(err?.message || err || "");
|
|
7615
|
+
const kind = classifyListenerError(msg);
|
|
7616
|
+
if (kind === "rate-limit") {
|
|
7617
|
+
const ms = nextBackoffMs(rateLimitBackoff.get(listener)?.ms);
|
|
7618
|
+
rateLimitBackoff.set(listener, { until: Date.now() + ms, ms });
|
|
7619
|
+
console.warn(`[HYPHA SESSION ${sessionId}] 429 rate-limited; backing off ${ms}ms (listener kept)`);
|
|
7620
|
+
scheduleBackoffRetry(listener);
|
|
7621
|
+
} else if (kind === "stale") {
|
|
7622
|
+
removeListener(listener, "stale connection");
|
|
7623
|
+
} else {
|
|
7624
|
+
console.error(`[HYPHA SESSION ${sessionId}] Async listener error:`, err);
|
|
7625
|
+
removeListener(listener, "async error");
|
|
7626
|
+
}
|
|
7627
|
+
};
|
|
7628
|
+
const scheduleBackoffRetry = (listener) => {
|
|
7629
|
+
if (rateLimitRetry.has(listener)) return;
|
|
7630
|
+
const bo = rateLimitBackoff.get(listener);
|
|
7631
|
+
const delay = retryDelayMs(Date.now(), bo?.until);
|
|
7632
|
+
const timer = setTimeout(() => {
|
|
7633
|
+
rateLimitRetry.delete(listener);
|
|
7634
|
+
const decision = retryDecision(listeners.indexOf(listener) >= 0, Date.now(), rateLimitBackoff.get(listener)?.until);
|
|
7635
|
+
if (decision === "drop") return;
|
|
7636
|
+
if (decision === "reschedule") {
|
|
7637
|
+
scheduleBackoffRetry(listener);
|
|
7638
|
+
return;
|
|
7639
|
+
}
|
|
7640
|
+
void (async () => {
|
|
7641
|
+
const tail = messages.slice(-50);
|
|
7642
|
+
for (const m of tail) {
|
|
7643
|
+
if (listeners.indexOf(listener) < 0) return;
|
|
7644
|
+
try {
|
|
7645
|
+
const r = listener.onUpdate({ type: "new-message", sessionId, message: m });
|
|
7646
|
+
if (r && typeof r.catch === "function") await r;
|
|
7647
|
+
} catch (err) {
|
|
7648
|
+
handleListenerRejection(listener, err);
|
|
7649
|
+
return;
|
|
7650
|
+
}
|
|
7651
|
+
}
|
|
7652
|
+
rateLimitBackoff.delete(listener);
|
|
7653
|
+
})();
|
|
7654
|
+
}, delay);
|
|
7655
|
+
rateLimitRetry.set(listener, timer);
|
|
7656
|
+
};
|
|
7391
7657
|
const notifyListeners = (update) => {
|
|
7392
7658
|
options?.onSessionEvent?.(update);
|
|
7393
7659
|
const now = Date.now();
|
|
@@ -7395,26 +7661,16 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
7395
7661
|
for (let i = snapshot.length - 1; i >= 0; i--) {
|
|
7396
7662
|
const listener = snapshot[i];
|
|
7397
7663
|
const bo = rateLimitBackoff.get(listener);
|
|
7398
|
-
if (bo && now < bo.until)
|
|
7664
|
+
if (bo && now < bo.until) {
|
|
7665
|
+
scheduleBackoffRetry(listener);
|
|
7666
|
+
continue;
|
|
7667
|
+
}
|
|
7399
7668
|
try {
|
|
7400
7669
|
const result = listener.onUpdate(update);
|
|
7401
7670
|
if (result && typeof result.catch === "function") {
|
|
7402
7671
|
result.then(() => {
|
|
7403
7672
|
rateLimitBackoff.delete(listener);
|
|
7404
|
-
}).catch((err) =>
|
|
7405
|
-
const msg = String(err?.message || err || "");
|
|
7406
|
-
const kind = classifyListenerError(msg);
|
|
7407
|
-
if (kind === "rate-limit") {
|
|
7408
|
-
const ms = nextBackoffMs(rateLimitBackoff.get(listener)?.ms);
|
|
7409
|
-
rateLimitBackoff.set(listener, { until: Date.now() + ms, ms });
|
|
7410
|
-
console.warn(`[HYPHA SESSION ${sessionId}] 429 rate-limited; backing off ${ms}ms (listener kept)`);
|
|
7411
|
-
} else if (kind === "stale") {
|
|
7412
|
-
removeListener(listener, "stale connection");
|
|
7413
|
-
} else {
|
|
7414
|
-
console.error(`[HYPHA SESSION ${sessionId}] Async listener error:`, err);
|
|
7415
|
-
removeListener(listener, "async error");
|
|
7416
|
-
}
|
|
7417
|
-
});
|
|
7673
|
+
}).catch((err) => handleListenerRejection(listener, err));
|
|
7418
7674
|
}
|
|
7419
7675
|
} catch (err) {
|
|
7420
7676
|
console.error(`[HYPHA SESSION ${sessionId}] Listener error:`, err);
|
|
@@ -7461,8 +7717,28 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
7461
7717
|
const channelStore = new ChannelStore(initialMetadata.path);
|
|
7462
7718
|
const meetingActivity = [];
|
|
7463
7719
|
const channelOutbox = new ChannelOutbox(initialMetadata.path);
|
|
7720
|
+
const outpostCoordinator = new OutpostCoordinator();
|
|
7721
|
+
const outpostProjectDir = () => metadata.path || process.cwd();
|
|
7722
|
+
const announceOutpost = (conn) => {
|
|
7723
|
+
const who = conn.label || conn.machine;
|
|
7724
|
+
const plat = [conn.os, conn.arch].filter(Boolean).join("/") || "unknown platform";
|
|
7725
|
+
const msg = {
|
|
7726
|
+
messageId: "op_" + Math.random().toString(16).slice(2, 10),
|
|
7727
|
+
body: `A remote machine connected as an outpost: ${xmlEscape(who)} (${xmlEscape(plat)}). You can now run shell commands on it \u2014 its output returns synchronously, like a local command. Run one with: svamp outpost exec "uname -a"`,
|
|
7728
|
+
timestamp: Date.now(),
|
|
7729
|
+
read: false,
|
|
7730
|
+
from: "outpost",
|
|
7731
|
+
subject: `Remote outpost connected: ${who}`
|
|
7732
|
+
};
|
|
7733
|
+
inbox.push(msg);
|
|
7734
|
+
while (inbox.length > INBOX_MAX) inbox.shift();
|
|
7735
|
+
syncInboxToMetadata();
|
|
7736
|
+
callbacks.onInboxMessage?.(msg);
|
|
7737
|
+
notifyListeners({ type: "inbox-update", sessionId, message: msg });
|
|
7738
|
+
};
|
|
7464
7739
|
const cfg = server?.config || {};
|
|
7465
7740
|
const channelsServiceId = cfg.workspace && cfg.client_id ? `${cfg.workspace}/${cfg.client_id}:channels` : void 0;
|
|
7741
|
+
const outpostServiceId = cfg.workspace && cfg.client_id ? `${cfg.workspace}/${cfg.client_id}:outpost` : void 0;
|
|
7466
7742
|
const channelsBaseUrl = cfg.public_base_url || process.env.HYPHA_SERVER_URL || "https://hypha.aicell.io";
|
|
7467
7743
|
const skillCtxFor = (c) => ({
|
|
7468
7744
|
channelsServiceId,
|
|
@@ -7846,34 +8122,36 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
7846
8122
|
syncChannelsToMetadata();
|
|
7847
8123
|
return { success: !!caller, caller };
|
|
7848
8124
|
},
|
|
7849
|
-
// FDE outpost (#
|
|
7850
|
-
// install one-liners + direct binary download links + a
|
|
7851
|
-
// "Connect a remote machine" panel calls this; it reuses the SAME
|
|
7852
|
-
// `svamp outpost up`, so the
|
|
7853
|
-
//
|
|
8125
|
+
// FDE outpost (#0565 redesign): provision (idempotently) this session's durable outpost TOKEN
|
|
8126
|
+
// and return the install one-liners + direct binary download links + a LIVE connected count.
|
|
8127
|
+
// The app's "Connect a remote machine" panel calls this; it reuses the SAME setupOutpost core as
|
|
8128
|
+
// `svamp outpost up`, so the install command never drifts. NO channel is created — the remote
|
|
8129
|
+
// dials the machine's public `:outpost` gateway and this session's token authenticates it.
|
|
8130
|
+
// Admin-gated: the returned token is a capability to run commands on whatever machine connects.
|
|
7854
8131
|
outpostConnectInfo: async (params, context) => {
|
|
7855
8132
|
authorizeRequest(context, metadata.sharing, "admin");
|
|
7856
|
-
if (!
|
|
8133
|
+
if (!outpostServiceId) return { error: "the outpost service is not available on this machine" };
|
|
7857
8134
|
try {
|
|
7858
|
-
const
|
|
8135
|
+
const connectedCount = outpostCoordinator.listConnections().length;
|
|
8136
|
+
const info = setupOutpost({
|
|
7859
8137
|
session: sessionId,
|
|
7860
|
-
projectDir:
|
|
7861
|
-
|
|
8138
|
+
projectDir: outpostProjectDir(),
|
|
8139
|
+
outpostServiceId,
|
|
7862
8140
|
serverUrl: channelsBaseUrl,
|
|
7863
|
-
downloadBase: params?.downloadBase || process.env.SVAMP_OUTPOST_DL
|
|
8141
|
+
downloadBase: params?.downloadBase || process.env.SVAMP_OUTPOST_DL,
|
|
8142
|
+
connectedCount
|
|
7864
8143
|
});
|
|
7865
|
-
syncChannelsToMetadata();
|
|
7866
8144
|
let pairing = {};
|
|
7867
8145
|
try {
|
|
7868
8146
|
const am = await server.getService("public/artifact-manager");
|
|
7869
8147
|
if (am) {
|
|
7870
|
-
if (
|
|
8148
|
+
if (connectedCount > 0) {
|
|
7871
8149
|
await burnSessionPairings(am, sessionId, void 0, (m) => console.log(m));
|
|
7872
8150
|
} else {
|
|
7873
8151
|
const r = await mintPairingCode({
|
|
7874
8152
|
am,
|
|
7875
8153
|
session: sessionId,
|
|
7876
|
-
payload: { server: info.serverBase,
|
|
8154
|
+
payload: { server: info.serverBase, token: info.token, session: sessionId },
|
|
7877
8155
|
resolverBase: channelsBaseUrl,
|
|
7878
8156
|
log: (m) => console.log(m)
|
|
7879
8157
|
});
|
|
@@ -7888,6 +8166,69 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
7888
8166
|
return { error: e?.message || String(e) };
|
|
7889
8167
|
}
|
|
7890
8168
|
},
|
|
8169
|
+
// ── FDE outpost transport (#0565): token-authed remote endpoints + owner-facing exec/list ──
|
|
8170
|
+
// hello/poll/result carry NO session-sharing auth (the remote is anonymous); the per-session
|
|
8171
|
+
// capability token IS the auth (checked against the durable on-disk token). Routed here by the
|
|
8172
|
+
// machine's public `:outpost` gateway, which passes the remote-supplied `session` as the key.
|
|
8173
|
+
outpostHello: async (params) => {
|
|
8174
|
+
const rec = loadOutpostToken(outpostProjectDir(), sessionId);
|
|
8175
|
+
if (!outpostTokenMatches(rec, params?.token)) return { error: "unauthorized" };
|
|
8176
|
+
const machine = (String(params?.machine || "").trim() || "outpost").slice(0, 128);
|
|
8177
|
+
const { isNew, connection } = outpostCoordinator.registerConnection(machine, {
|
|
8178
|
+
label: params?.label ? String(params.label).slice(0, 128) : void 0,
|
|
8179
|
+
os: params?.os ? String(params.os).slice(0, 64) : void 0,
|
|
8180
|
+
arch: params?.arch ? String(params.arch).slice(0, 64) : void 0,
|
|
8181
|
+
version: params?.version ? String(params.version).slice(0, 64) : void 0
|
|
8182
|
+
});
|
|
8183
|
+
if (isNew) {
|
|
8184
|
+
announceOutpost(connection);
|
|
8185
|
+
try {
|
|
8186
|
+
const am = await server.getService("public/artifact-manager");
|
|
8187
|
+
if (am) await burnSessionPairings(am, sessionId, void 0, (m) => console.log(m));
|
|
8188
|
+
} catch {
|
|
8189
|
+
}
|
|
8190
|
+
}
|
|
8191
|
+
return { ok: true, machine: connection.machine };
|
|
8192
|
+
},
|
|
8193
|
+
outpostPoll: async (params) => {
|
|
8194
|
+
const rec = loadOutpostToken(outpostProjectDir(), sessionId);
|
|
8195
|
+
if (!outpostTokenMatches(rec, params?.token)) return { error: "unauthorized" };
|
|
8196
|
+
const machine = (String(params?.machine || "").trim() || "outpost").slice(0, 128);
|
|
8197
|
+
if (!outpostCoordinator.isConnected(machine)) {
|
|
8198
|
+
const { isNew, connection } = outpostCoordinator.registerConnection(machine, {});
|
|
8199
|
+
if (isNew) announceOutpost(connection);
|
|
8200
|
+
}
|
|
8201
|
+
const waitRaw = Number(params?.wait ?? 25);
|
|
8202
|
+
const waitSec = Number.isFinite(waitRaw) ? waitRaw : 25;
|
|
8203
|
+
const waitMs = Math.min(Math.max(0, waitSec * 1e3), 6e4);
|
|
8204
|
+
const deadline = Date.now() + waitMs;
|
|
8205
|
+
for (; ; ) {
|
|
8206
|
+
const commands = outpostCoordinator.pollCommands(machine);
|
|
8207
|
+
if (commands.length || Date.now() >= deadline) return { ok: true, commands };
|
|
8208
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
8209
|
+
}
|
|
8210
|
+
},
|
|
8211
|
+
outpostResult: async (params) => {
|
|
8212
|
+
const rec = loadOutpostToken(outpostProjectDir(), sessionId);
|
|
8213
|
+
if (!outpostTokenMatches(rec, params?.token)) return { error: "unauthorized" };
|
|
8214
|
+
if (!params?.id) return { error: "missing result id" };
|
|
8215
|
+
const ok = outpostCoordinator.resolveResult({ id: params.id, stdout: params.stdout, stderr: params.stderr, exitCode: params.exitCode });
|
|
8216
|
+
return { ok };
|
|
8217
|
+
},
|
|
8218
|
+
outpostExec: async (params, context) => {
|
|
8219
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
8220
|
+
const cmd = String(params?.cmd || "").trim();
|
|
8221
|
+
if (!cmd) return { error: "cmd is required" };
|
|
8222
|
+
const t = outpostCoordinator.resolveTarget(params?.target ? String(params.target) : void 0);
|
|
8223
|
+
if ("error" in t) return { error: t.error };
|
|
8224
|
+
const { promise } = outpostCoordinator.enqueueExec(t.machine, cmd, params?.cwd ? String(params.cwd) : void 0);
|
|
8225
|
+
const r = await promise;
|
|
8226
|
+
return { ok: r.exitCode === 0, stdout: r.stdout, stderr: r.stderr, exitCode: r.exitCode, machine: t.machine };
|
|
8227
|
+
},
|
|
8228
|
+
outpostList: async (context) => {
|
|
8229
|
+
authorizeRequest(context, metadata.sharing, "view");
|
|
8230
|
+
return { ok: true, outposts: outpostCoordinator.listConnections() };
|
|
8231
|
+
},
|
|
7891
8232
|
getChannelSkill: async (id, context) => {
|
|
7892
8233
|
authorizeRequest(context, metadata.sharing, "view");
|
|
7893
8234
|
const c = channelStore.get(id);
|
|
@@ -9001,6 +9342,13 @@ function handleMatchesMetadata(parsed, metadata) {
|
|
|
9001
9342
|
return true;
|
|
9002
9343
|
}
|
|
9003
9344
|
|
|
9345
|
+
function buildSessionIdentityEnv(identity) {
|
|
9346
|
+
const env = { SVAMP_SESSION_ID: identity.sessionId };
|
|
9347
|
+
if (identity.handle) env.SVAMP_SESSION_HANDLE = identity.handle;
|
|
9348
|
+
if (identity.machineId) env.SVAMP_MACHINE_ID = identity.machineId;
|
|
9349
|
+
return env;
|
|
9350
|
+
}
|
|
9351
|
+
|
|
9004
9352
|
const HOTRELOAD_ENV = "SVAMP_DEV_HOTRELOAD";
|
|
9005
9353
|
function isHotReloadEnabled(env = process.env) {
|
|
9006
9354
|
const v = env[HOTRELOAD_ENV];
|
|
@@ -16634,7 +16982,7 @@ async function startDaemon(options) {
|
|
|
16634
16982
|
try {
|
|
16635
16983
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
16636
16984
|
if (!dir) return;
|
|
16637
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
16985
|
+
const { reconcileServiceLinks } = await import('./agentCommands-BCkWdFFW.mjs');
|
|
16638
16986
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
16639
16987
|
const config = readSvampConfig(configPath);
|
|
16640
16988
|
const entries = Array.from(urls.entries());
|
|
@@ -16652,7 +17000,7 @@ async function startDaemon(options) {
|
|
|
16652
17000
|
}
|
|
16653
17001
|
}
|
|
16654
17002
|
async function createExposedTunnel(spec) {
|
|
16655
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
17003
|
+
const { FrpcTunnel } = await import('./frpc-Cb6jUvED.mjs');
|
|
16656
17004
|
const tunnel = new FrpcTunnel({
|
|
16657
17005
|
name: spec.name,
|
|
16658
17006
|
ports: spec.ports,
|
|
@@ -18868,11 +19216,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
18868
19216
|
});
|
|
18869
19217
|
},
|
|
18870
19218
|
onIssue: async (params) => {
|
|
18871
|
-
const { issueRpc } = await import('./rpc-
|
|
19219
|
+
const { issueRpc } = await import('./rpc-B-bxS-Pv.mjs');
|
|
18872
19220
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
18873
19221
|
},
|
|
18874
19222
|
onWorkflow: async (params) => {
|
|
18875
|
-
const { workflowRpc } = await import('./rpc-
|
|
19223
|
+
const { workflowRpc } = await import('./rpc-CNkfUlz1.mjs');
|
|
18876
19224
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
18877
19225
|
},
|
|
18878
19226
|
onRipgrep: async (args, cwd) => {
|
|
@@ -19485,11 +19833,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19485
19833
|
});
|
|
19486
19834
|
},
|
|
19487
19835
|
onIssue: async (params) => {
|
|
19488
|
-
const { issueRpc } = await import('./rpc-
|
|
19836
|
+
const { issueRpc } = await import('./rpc-B-bxS-Pv.mjs');
|
|
19489
19837
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
19490
19838
|
},
|
|
19491
19839
|
onWorkflow: async (params) => {
|
|
19492
|
-
const { workflowRpc } = await import('./rpc-
|
|
19840
|
+
const { workflowRpc } = await import('./rpc-CNkfUlz1.mjs');
|
|
19493
19841
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
19494
19842
|
},
|
|
19495
19843
|
onRipgrep: async (args, cwd) => {
|
|
@@ -19637,6 +19985,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19637
19985
|
}
|
|
19638
19986
|
}
|
|
19639
19987
|
let agentBackend;
|
|
19988
|
+
const sessionIdentityEnv = buildSessionIdentityEnv({
|
|
19989
|
+
sessionId,
|
|
19990
|
+
handle: formatHandle(sessionMetadata.projectName, sessionMetadata.friendlyName),
|
|
19991
|
+
machineId
|
|
19992
|
+
});
|
|
19640
19993
|
if (KNOWN_CODEX_AGENTS[agentName]) {
|
|
19641
19994
|
const avail = codexAppServerAvailable();
|
|
19642
19995
|
if (avail === null) throw new Error("Codex CLI not found \u2014 install it (`npm i -g @openai/codex`) and authenticate (`svamp daemon codex-auth`).");
|
|
@@ -19681,8 +20034,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19681
20034
|
model: codexModel,
|
|
19682
20035
|
provider,
|
|
19683
20036
|
// Account extra env (OPENAI_API_KEY / SVAMP_CODEX_API_KEY / CODEX_HOME) layered
|
|
19684
|
-
// over any per-session environmentVariables from the composer
|
|
19685
|
-
|
|
20037
|
+
// over any per-session environmentVariables from the composer; session identity
|
|
20038
|
+
// (#0599) applied LAST so it can't be shadowed by composer/account vars.
|
|
20039
|
+
env: { ...options2.environmentVariables || {}, ...accountCodexEnv || {}, ...sessionIdentityEnv },
|
|
19686
20040
|
log: logger.log,
|
|
19687
20041
|
isolationConfig: agentIsoConfig,
|
|
19688
20042
|
approvalPolicy: codexPerm.approvalPolicy,
|
|
@@ -19698,7 +20052,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19698
20052
|
cwd: directory,
|
|
19699
20053
|
command: acpConfig.command,
|
|
19700
20054
|
args: acpConfig.args,
|
|
19701
|
-
|
|
20055
|
+
// #0599: session identity (SVAMP_SESSION_ID/HANDLE/MACHINE_ID) so ACP terminal
|
|
20056
|
+
// tools self-identify like Claude's do; applied last (identity wins).
|
|
20057
|
+
env: { ...options2.environmentVariables || {}, ...sessionIdentityEnv },
|
|
19702
20058
|
permissionHandler,
|
|
19703
20059
|
transportHandler,
|
|
19704
20060
|
log: logger.log,
|
|
@@ -20616,7 +20972,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
20616
20972
|
const PING_TIMEOUT_MS = 15e3;
|
|
20617
20973
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
20618
20974
|
const RECONNECT_JITTER_MS = 2500;
|
|
20619
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
20975
|
+
const { WorkflowScheduler } = await import('./scheduler-DT1_BBdZ.mjs');
|
|
20620
20976
|
const workflowScheduler = new WorkflowScheduler({
|
|
20621
20977
|
projectRoots: () => {
|
|
20622
20978
|
const dirs = /* @__PURE__ */ new Set();
|
|
@@ -21240,4 +21596,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
21240
21596
|
writeStopMarker: writeStopMarker
|
|
21241
21597
|
});
|
|
21242
21598
|
|
|
21243
|
-
export {
|
|
21599
|
+
export { listSkillFiles as $, removeWorkflow as A, saveWorkflow as B, rawWorkflow as C, listWorkflows as D, isWorkflowEnabled as E, workflowCrons as F, cronMatches as G, summarize as H, workflowSteps as I, parseJwtEmail as J, computeCollectionConfigUpdate as K, SYSTEM_COLLECTION_CONFIG as L, loadMachineContext as M, buildMachineInstructions as N, machineToolsForRole as O, buildMachineTools as P, parseFrontmatter as Q, READ_ONLY_TOOLS as R, SharingNotificationSync as S, getSkillsServer as T, getSkillsWorkspaceName as U, getSkillsCollectionName as V, fetchWithTimeout as W, searchSkills as X, SKILLS_DIR as Y, getSkillInfo as Z, downloadSkillFile as _, createSessionStore as a, resolveModel as a0, clearStopMarker as a1, stopMarkerExists as a2, formatHandle as a3, normalizeAllowedUser as a4, loadSecurityContextConfig as a5, resolveSecurityContext as a6, buildSecurityContextFromFlags as a7, mergeSecurityContexts as a8, buildSessionShareUrl as a9, computeOutboundHop as aa, registerAwaitingReply as ab, buildMachineShareUrl as ac, parseHandle as ad, handleMatchesMetadata as ae, describeMisconfiguration as af, buildMachineDeps as ag, applyClaudeProxyEnv as ah, composeSessionId as ai, generateFriendlyName as aj, generateHookSettings as ak, staticFileServer as al, instanceConfig as am, claudeAuth as an, codexProvider as ao, projectInfo as ap, DefaultTransport$1 as aq, acpBackend as ar, acpAgentConfig as as, codexAppServerBackend as at, GeminiTransport$1 as au, api as av, run as aw, stopDaemon as b, connectToHypha as c, daemonStatus as d, getFrpsSubdomainHost as e, getFrpsServerPort as f, getHyphaServerUrl$1 as g, getFrpsServerAddr as h, shortId as i, resolveProjectRoot as j, getIssue as k, resumeIssue as l, addComment as m, addIssue as n, listIssues as o, pauseIssue as p, searchIssues as q, registerMachineService as r, startDaemon as s, isVisibleTo as t, updateIssue as u, getRun as v, listRuns as w, getWorkflow as x, runWorkflow as y, setWorkflowEnabled as z };
|