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.
Files changed (29) hide show
  1. package/dist/{adminCommands-D08GLn9K.mjs → adminCommands-DMiliv55.mjs} +1 -1
  2. package/dist/{agentCommands-DLpNORpD.mjs → agentCommands-BCkWdFFW.mjs} +5 -5
  3. package/dist/{auth-Cq1Tggti.mjs → auth-Ba1_zl1h.mjs} +1 -1
  4. package/dist/{cli-Dd82AdTd.mjs → cli-Bv0Zhf3M.mjs} +69 -69
  5. package/dist/cli.mjs +2 -2
  6. package/dist/{commands-BdgY6EVk.mjs → commands-B2mRza8S.mjs} +2 -2
  7. package/dist/{commands-C_ROarRL.mjs → commands-BA3eBROn.mjs} +1 -1
  8. package/dist/{commands-C60tZyej.mjs → commands-BAlaK67Y.mjs} +1 -1
  9. package/dist/{commands-CBXkcBgD.mjs → commands-BC3RuhRx.mjs} +2 -2
  10. package/dist/commands-BnirT9RR.mjs +161 -0
  11. package/dist/{commands-BAuKr5Bn.mjs → commands-DhB2iz0_.mjs} +7 -7
  12. package/dist/{commands-dC4o6d-s.mjs → commands-bIioWHWe.mjs} +2 -2
  13. package/dist/{commands-BzaZz_ke.mjs → commands-hHZu5qJh.mjs} +1 -1
  14. package/dist/{fleet-CbK3VsXL.mjs → fleet-BFurNsSA.mjs} +1 -1
  15. package/dist/{frpc-BqfCBMke.mjs → frpc-Cb6jUvED.mjs} +1 -1
  16. package/dist/{headlessCli-Wm7z6_EN.mjs → headlessCli-DP9gpAxv.mjs} +2 -2
  17. package/dist/index.mjs +1 -1
  18. package/dist/{notifyCommands-DM1mwI92.mjs → notifyCommands-C0i6P1yC.mjs} +1 -1
  19. package/dist/package-D8lHDQja.mjs +64 -0
  20. package/dist/{rpc-BJySEOZA.mjs → rpc-B-bxS-Pv.mjs} +1 -1
  21. package/dist/{rpc-DZekVCXH.mjs → rpc-CNkfUlz1.mjs} +1 -1
  22. package/dist/{run-C_5djiX4.mjs → run-C0GhrtVf.mjs} +433 -77
  23. package/dist/{run-BYIuK238.mjs → run-CvT_37Ov.mjs} +1 -1
  24. package/dist/{scheduler-CJ4qyaq2.mjs → scheduler-DT1_BBdZ.mjs} +1 -1
  25. package/dist/{serveCommands-DIutqRPk.mjs → serveCommands-CUabu97u.mjs} +5 -5
  26. package/dist/{sideband-BVmN7F-h.mjs → sideband-BlZBmbji.mjs} +1 -1
  27. package/package.json +2 -2
  28. package/dist/commands-QrHdpbuH.mjs +0 -293
  29. 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-BqfCBMke.mjs');
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4345
4345
  }
4346
+ function xmlEscapeContent(s) {
4347
+ return String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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) => xmlEscape(obj(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-BqfCBMke.mjs');
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-BVmN7F-h.mjs');
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-C60tZyej.mjs');
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 || params.channel;
6932
- const unix = `curl -fsSL ${shq(`${dl}/install.sh`)} | SVAMP_OUTPOST_SERVER=${shq(base)} SVAMP_OUTPOST_CHANNEL=${shq(params.channel)} SVAMP_OUTPOST_TOKEN=${shq(params.token)} SVAMP_OUTPOST_SESSION=${shq(session)} SVAMP_OUTPOST_DL=${shq(dl)} sh`;
6933
- const windows = `$env:SVAMP_OUTPOST_SERVER=${psq(base)}; $env:SVAMP_OUTPOST_CHANNEL=${psq(params.channel)}; $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`;
6934
- const direct = `svamp-outpost --server ${shq(base)} --channel ${shq(params.channel)} --token ${shq(params.token)} --session ${shq(session)}`;
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 setupOutpostChannel(params) {
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.channelsServiceId, serverUrl);
6950
- const channelId = `outpost-${params.session}`;
6951
- const store = new ChannelStore(params.projectDir);
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) continue;
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 (#0589): provision (idempotently) this session's outpost channel and return the
7850
- // install one-liners + direct binary download links + a best-effort connected count. The app's
7851
- // "Connect a remote machine" panel calls this; it reuses the SAME setupOutpostChannel core as
7852
- // `svamp outpost up`, so the channel shape + install command never drift. Admin-gated: the
7853
- // returned token is a capability to run commands on whatever machine connects.
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 (!channelsServiceId) return { error: "the channels service is not available on this machine" };
8133
+ if (!outpostServiceId) return { error: "the outpost service is not available on this machine" };
7857
8134
  try {
7858
- const info = setupOutpostChannel({
8135
+ const connectedCount = outpostCoordinator.listConnections().length;
8136
+ const info = setupOutpost({
7859
8137
  session: sessionId,
7860
- projectDir: metadata.path || process.cwd(),
7861
- channelsServiceId,
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 (info.connectedCount > 0) {
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, channel: info.channelId, token: info.token, session: sessionId },
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-DLpNORpD.mjs');
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-BqfCBMke.mjs');
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-BJySEOZA.mjs');
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-DZekVCXH.mjs');
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-BJySEOZA.mjs');
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-DZekVCXH.mjs');
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
- env: { ...options2.environmentVariables || {}, ...accountCodexEnv || {} },
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
- env: options2.environmentVariables,
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-CJ4qyaq2.mjs');
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 { searchSkills as $, removeWorkflow as A, saveWorkflow as B, rawWorkflow as C, listWorkflows as D, isWorkflowEnabled as E, workflowCrons as F, cronMatches as G, buildOutpostInstall as H, ChannelStore as I, DEFAULT_OUTPOST_DOWNLOAD_BASE as J, setupOutpostChannel as K, summarize as L, workflowSteps as M, parseJwtEmail as N, computeCollectionConfigUpdate as O, SYSTEM_COLLECTION_CONFIG as P, loadMachineContext as Q, READ_ONLY_TOOLS as R, SharingNotificationSync as S, buildMachineInstructions as T, machineToolsForRole as U, buildMachineTools as V, parseFrontmatter as W, getSkillsServer as X, getSkillsWorkspaceName as Y, getSkillsCollectionName as Z, fetchWithTimeout as _, createSessionStore as a, SKILLS_DIR as a0, getSkillInfo as a1, downloadSkillFile as a2, listSkillFiles as a3, resolveModel as a4, clearStopMarker as a5, stopMarkerExists as a6, formatHandle as a7, normalizeAllowedUser as a8, loadSecurityContextConfig as a9, api as aA, run as aB, resolveSecurityContext as aa, buildSecurityContextFromFlags as ab, mergeSecurityContexts as ac, buildSessionShareUrl as ad, computeOutboundHop as ae, registerAwaitingReply as af, buildMachineShareUrl as ag, parseHandle as ah, handleMatchesMetadata as ai, describeMisconfiguration as aj, buildMachineDeps as ak, applyClaudeProxyEnv as al, composeSessionId as am, generateFriendlyName as an, generateHookSettings as ao, staticFileServer as ap, instanceConfig as aq, claudeAuth as ar, codexProvider as as, outbox as at, projectInfo as au, DefaultTransport$1 as av, acpBackend as aw, acpAgentConfig as ax, codexAppServerBackend as ay, GeminiTransport$1 as az, 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 };
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 };