svamp-cli 0.2.315 → 0.2.317

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 (30) hide show
  1. package/bin/commands/workflow.md +3 -2
  2. package/dist/{adminCommands-DzNor_Oi.mjs → adminCommands-fJVu-h9l.mjs} +1 -1
  3. package/dist/{agentCommands-RhxyIA_z.mjs → agentCommands-DdOSpUEW.mjs} +5 -5
  4. package/dist/{auth-DwbfJdef.mjs → auth-B2XK13r-.mjs} +1 -1
  5. package/dist/{cli-3UOhFKAA.mjs → cli-DGVQaDbd.mjs} +115 -85
  6. package/dist/cli.mjs +2 -2
  7. package/dist/{commands-BTkUJBuQ.mjs → commands-Bx2JkNft.mjs} +1 -1
  8. package/dist/{commands-Clvx4KkJ.mjs → commands-C4p4v9eO.mjs} +2 -2
  9. package/dist/{commands-Cfmfo9-g.mjs → commands-CoBMjrtP.mjs} +2 -2
  10. package/dist/{commands-C5Lmdg3I.mjs → commands-DEsDSuMW.mjs} +3 -3
  11. package/dist/{commands-8cNqkP6Q.mjs → commands-DUdStLKb.mjs} +2 -2
  12. package/dist/{commands-DpWQUbS1.mjs → commands-I9jHLkrI.mjs} +1 -1
  13. package/dist/{commands-DOXnuGx3.mjs → commands-ino-Gtcu.mjs} +8 -8
  14. package/dist/{commands-CjBsJQX-.mjs → commands-wQYdc5EK.mjs} +1 -1
  15. package/dist/{fleet-C9RYmsHH.mjs → fleet-NRi2YMso.mjs} +6 -5
  16. package/dist/{frpc-pwqsONPX.mjs → frpc-CNZJM3qC.mjs} +1 -1
  17. package/dist/{headlessCli-BoaIinBA.mjs → headlessCli-DpTQ00fL.mjs} +2 -2
  18. package/dist/index.mjs +1 -1
  19. package/dist/{notifyCommands-8hUYEN4j.mjs → notifyCommands-BFRuzxXP.mjs} +1 -1
  20. package/dist/package-DN_Oa_OT.mjs +64 -0
  21. package/dist/{pinnedClaudeCode-mM9Q6Q2G.mjs → pinnedClaudeCode-CnCpensV.mjs} +1 -1
  22. package/dist/{rpc-BFIy8FTe.mjs → rpc-CclCeDcu.mjs} +1 -1
  23. package/dist/{rpc-DAcODVs3.mjs → rpc-Cx6KsBYC.mjs} +2 -2
  24. package/dist/{run-CkmRGy8q.mjs → run-DKhnAl2f.mjs} +1 -1
  25. package/dist/{run-vEbmTx6x.mjs → run-RA4Vr6lj.mjs} +582 -83
  26. package/dist/{scheduler-DlDYY_wS.mjs → scheduler-Wzzed1CU.mjs} +1 -1
  27. package/dist/{serveCommands-wx5GnznD.mjs → serveCommands-DQeSesYI.mjs} +5 -5
  28. package/dist/{sideband-DU8QZetT.mjs → sideband-gQ7oQEsx.mjs} +1 -1
  29. package/package.json +4 -4
  30. package/dist/package-D_KxkJI4.mjs +0 -64
@@ -1,15 +1,15 @@
1
1
  import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import os$1, { homedir } from 'os';
2
2
  import fs$1, { mkdir as mkdir$1, readdir as readdir$1, readFile, writeFile as writeFile$1, rename, unlink } from 'fs/promises';
3
3
  import * as fs from 'fs';
4
- import fs__default, { existsSync as existsSync$1, rmSync, readFileSync as readFileSync$1, mkdirSync, writeFileSync, renameSync as renameSync$1, realpathSync as realpathSync$1, copyFileSync, unlinkSync as unlinkSync$1, readdirSync as readdirSync$1, watch, rmdirSync } from 'fs';
4
+ import fs__default, { existsSync as existsSync$1, rmSync, readFileSync as readFileSync$1, mkdirSync, writeFileSync, renameSync as renameSync$1, realpathSync as realpathSync$1, copyFileSync, unlinkSync as unlinkSync$1, readdirSync as readdirSync$1, watch, rmdirSync, statSync as statSync$1 } from 'fs';
5
5
  import * as path from 'path';
6
6
  import path__default, { join, delimiter, resolve as resolve$1, dirname as dirname$1, basename as basename$1 } from 'path';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { execFile, spawn, execSync, exec as exec$1 } from 'child_process';
9
9
  import * as crypto from 'crypto';
10
- import { randomUUID as randomUUID$1 } from 'crypto';
10
+ import { randomUUID as randomUUID$1, createHash as createHash$1 } from 'crypto';
11
11
  import { randomUUID, randomBytes, createHash, timingSafeEqual } from 'node:crypto';
12
- import { existsSync, readFileSync, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1, chmodSync, rmSync as rmSync$1, cpSync, statSync, realpathSync, readdirSync, renameSync, appendFileSync, unlinkSync } from 'node:fs';
12
+ import { existsSync, readFileSync, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1, chmodSync, rmSync as rmSync$1, cpSync, statSync, realpathSync, readdirSync, renameSync, appendFileSync, openSync, readSync, closeSync, unlinkSync } from 'node:fs';
13
13
  import { exec, spawn as spawn$1, execSync as execSync$1, execFile as execFile$1, execFileSync } from 'node:child_process';
14
14
  import { promisify } from 'util';
15
15
  import * as http from 'http';
@@ -321,7 +321,26 @@ function buildSecurityContextFromFlags(flags) {
321
321
  }
322
322
  return ctx;
323
323
  }
324
+ function unenforceableRules(method, context) {
325
+ const unsupported = [];
326
+ const fs = context.filesystem;
327
+ if (fs?.denyRead?.length) unsupported.push("filesystem.denyRead");
328
+ if (fs?.denyWrite?.length) unsupported.push("filesystem.denyWrite");
329
+ if (context.network?.deniedDomains?.length) unsupported.push("network.deniedDomains");
330
+ if (method === "docker" || method === "podman") {
331
+ if (fs?.allowWrite?.length) unsupported.push("filesystem.allowWrite");
332
+ if (context.network?.allowedDomains?.length) unsupported.push("network.allowedDomains");
333
+ }
334
+ return unsupported;
335
+ }
336
+ function describeUnenforceable(method, context) {
337
+ const unsupported = unenforceableRules(method, context);
338
+ if (!unsupported.length) return null;
339
+ return `securityContext: ${unsupported.join(", ")} is NOT ENFORCED by the "${method}" sandbox \u2014 these rules are accepted but have no effect (#0878). ` + (method === "nono" ? `nono is allow-list based and the workspace is granted via --allow-cwd, so a deny rule for a path inside the workspace does nothing. Use allowedDirectories / filesystem.allowWrite, or keep the sensitive path outside the workspace.` : `The container backend maps only allowedDirectories and denyAllNetwork. Use those, or run the machine under the nono sandbox.`);
340
+ }
324
341
  function applySecurityContext(baseConfig, context) {
342
+ const warning = describeUnenforceable(baseConfig.method, context);
343
+ if (warning) console.warn(`[security] ${warning}`);
325
344
  const config = { ...baseConfig };
326
345
  if (config.method === "nono") {
327
346
  const nonoConfig = { ...config.nonoConfig };
@@ -439,6 +458,7 @@ function buildSkillsPromptSection(skills) {
439
458
  }
440
459
 
441
460
  const READ_ONLY_TOOLS = ["get_context", "ask_session", "summarize_session", "use_skill"];
461
+ const MUTATING_TOOLS = ["run_bash", "send_to_session", "create_loop", "set_checklist", "stop_loop", "create_channel"];
442
462
  const str$1 = (v) => v == null ? "" : String(v);
443
463
  function buildTools(deps, skills) {
444
464
  return [
@@ -1566,6 +1586,8 @@ class TokenCache {
1566
1586
  this.cache.delete(token);
1567
1587
  return null;
1568
1588
  }
1589
+ this.cache.delete(token);
1590
+ this.cache.set(token, info);
1569
1591
  return info;
1570
1592
  }
1571
1593
  set(token, email) {
@@ -2088,6 +2110,7 @@ function getSkillsCollection() {
2088
2110
  return (process.env.HYPHA_SKILLS_COLLECTION || "").trim() || "marketplace";
2089
2111
  }
2090
2112
 
2113
+ const SESSION_FILES_MOUNT = "svamp-session-files";
2091
2114
  function generateLinkToken() {
2092
2115
  return crypto.randomBytes(11).toString("hex");
2093
2116
  }
@@ -2267,6 +2290,8 @@ class ServeManager {
2267
2290
  managedPidDir;
2268
2291
  log;
2269
2292
  hyphaServerUrl;
2293
+ /** #126: daemon-provided lookup of sessionId → { cwd, sharing, ownerEmail } for the files gateway. */
2294
+ sessionResolver = null;
2270
2295
  constructor(svampHome, logger, hyphaServerUrl) {
2271
2296
  this.persistFile = path.join(svampHome, "serve-mounts.json");
2272
2297
  this.managedPidDir = path.join(svampHome, "serve-managed");
@@ -2279,6 +2304,60 @@ class ServeManager {
2279
2304
  this.auth = new ServeAuth({ hyphaServerUrl: this.hyphaServerUrl });
2280
2305
  }
2281
2306
  // ── Public API ───────────────────────────────────────────────────────
2307
+ /**
2308
+ * #126: register the daemon's sessionId → { cwd, sharing, ownerEmail } lookup used by the
2309
+ * session-files gateway to resolve + authorize each request. Call once at daemon start.
2310
+ */
2311
+ setSessionResolver(fn) {
2312
+ this.sessionResolver = fn;
2313
+ }
2314
+ /**
2315
+ * #126: bring up the always-on, per-machine session-files gateway. Idempotent. `stableToken`
2316
+ * must be a stable per-machine hex/alnum string (DNS-label-safe) so the gateway's subdomain is
2317
+ * unique across machines AND stable across daemon restarts. The token is NOT a capability —
2318
+ * every request is authorized per-session inside handleSessionFilesRequest. Returns the gateway
2319
+ * base URL (`https://static-svamp-session-files-<token>.svc…/`) or null if the tunnel isn't up.
2320
+ */
2321
+ async ensureSessionFilesGateway(stableToken) {
2322
+ if (!stableToken || !/^[a-z0-9-]{8,}$/i.test(stableToken)) {
2323
+ throw new Error("ensureSessionFilesGateway: stableToken must be a DNS-label-safe string (\u22658 alnum/hyphen chars)");
2324
+ }
2325
+ const token = stableToken.toLowerCase();
2326
+ const existing = this.mounts.get(SESSION_FILES_MOUNT);
2327
+ if (!existing?.sessionFilesGateway || existing.linkToken !== token) {
2328
+ this.mounts.set(SESSION_FILES_MOUNT, {
2329
+ name: SESSION_FILES_MOUNT,
2330
+ access: "link",
2331
+ linkToken: token,
2332
+ sessionFilesGateway: true,
2333
+ addedAt: Date.now()
2334
+ });
2335
+ }
2336
+ await this.ensureRunning();
2337
+ if (!this.mountTunnels.has(SESSION_FILES_MOUNT)) {
2338
+ await this.startMountTunnel(SESSION_FILES_MOUNT);
2339
+ }
2340
+ const base = this.getSessionFilesBaseUrl();
2341
+ this.log(`Session-files gateway ready: ${base || "(tunnel pending)"}`);
2342
+ return base;
2343
+ }
2344
+ /** #126: base URL of the session-files gateway, or null when the gateway/tunnel isn't up. */
2345
+ getSessionFilesBaseUrl() {
2346
+ const mount = this.mounts.get(SESSION_FILES_MOUNT);
2347
+ if (!mount?.sessionFilesGateway) return null;
2348
+ return this.getMountUrl(SESSION_FILES_MOUNT);
2349
+ }
2350
+ /**
2351
+ * #126: the per-session file endpoint URL — `<base>s/<sessionId>/`. The frontend appends a
2352
+ * relative file path and issues GET (download, Range-streamed) / PUT (upload, streamed) /
2353
+ * DELETE / GET?list=1 (JSON dir listing). Null when the gateway tunnel isn't up (→ RPC fallback).
2354
+ */
2355
+ getSessionFilesEndpoint(sessionId) {
2356
+ const base = this.getSessionFilesBaseUrl();
2357
+ if (!base) return null;
2358
+ const b = base.endsWith("/") ? base : `${base}/`;
2359
+ return `${b}s/${encodeURIComponent(sessionId)}/`;
2360
+ }
2282
2361
  /**
2283
2362
  * Add a static mount (backward-compatible thin wrapper around applyMount).
2284
2363
  * Throws if a mount with the same name already exists, preserving the
@@ -2384,7 +2463,7 @@ class ServeManager {
2384
2463
  * List mounts, optionally filtered by sessionId.
2385
2464
  */
2386
2465
  listMounts(sessionId) {
2387
- const all = Array.from(this.mounts.values());
2466
+ const all = Array.from(this.mounts.values()).filter((m) => !m.sessionFilesGateway);
2388
2467
  const filtered = sessionId ? all.filter((m) => m.sessionId === sessionId) : all;
2389
2468
  return filtered.map((m) => ({ ...m, url: this.getMountUrl(m.name) || void 0 }));
2390
2469
  }
@@ -2402,8 +2481,9 @@ class ServeManager {
2402
2481
  port: running ? this.port : 0,
2403
2482
  authProxyPort: running ? this.port : 0,
2404
2483
  running,
2405
- mountCount: this.mounts.size,
2406
- mounts: Array.from(this.mounts.values()).map((m) => ({
2484
+ // #126: exclude the internal session-files gateway from user-facing counts/listings.
2485
+ mountCount: Array.from(this.mounts.values()).filter((m) => !m.sessionFilesGateway).length,
2486
+ mounts: Array.from(this.mounts.values()).filter((m) => !m.sessionFilesGateway).map((m) => ({
2407
2487
  ...m,
2408
2488
  url: this.getMountUrl(m.name) || void 0
2409
2489
  }))
@@ -2717,7 +2797,9 @@ class ServeManager {
2717
2797
  }
2718
2798
  persist() {
2719
2799
  const state = {
2720
- mounts: Array.from(this.mounts.values()),
2800
+ // #126: the session-files gateway is recreated fresh at daemon start (directory-less,
2801
+ // token owned by the daemon) — never persist it.
2802
+ mounts: Array.from(this.mounts.values()).filter((m) => !m.sessionFilesGateway),
2721
2803
  ...this.port > 0 ? { port: this.port } : {}
2722
2804
  };
2723
2805
  try {
@@ -2754,6 +2836,195 @@ h1{font-size:1.3rem}ul{list-style:none;padding:0}li{padding:8px 0;border-bottom:
2754
2836
  a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:underline}</style></head>
2755
2837
  <body><h1>Svamp File Server</h1><ul>${items || "<li>No mounts registered.</li>"}</ul></body></html>`;
2756
2838
  }
2839
+ /**
2840
+ * #126: handle a request to the session-files gateway. Routes `/s/<sessionId>/<relPath>`:
2841
+ * OPTIONS → CORS preflight (GET/HEAD/PUT/DELETE)
2842
+ * GET/HEAD → download (Range/206 streamed via serveStaticMount)
2843
+ * GET …?list=1 → JSON directory listing [{name,isDir,size,mtime}]
2844
+ * PUT → streamed upload to a temp file + atomic rename (large-file safe)
2845
+ * DELETE → unlink
2846
+ *
2847
+ * Auth (live boundary): authenticate the requester's email via ServeAuth (parse_token), then
2848
+ * authorize against THAT session's owner/sharing at role 'admin' — mirroring the RPC file ops.
2849
+ * CRITICAL: unlike the RPC path (which relies on Hypha workspace isolation for null sharing),
2850
+ * this is a PUBLIC HTTP ingress, so it FAILS CLOSED: a null/disabled sharing config admits ONLY
2851
+ * the session owner. No token → 401; valid token but not owner/admin → 403.
2852
+ */
2853
+ async handleSessionFilesRequest(req, res, url, basePath) {
2854
+ const CORS = {
2855
+ "Access-Control-Allow-Origin": "*",
2856
+ "Access-Control-Allow-Methods": "GET, HEAD, PUT, DELETE, OPTIONS",
2857
+ "Access-Control-Allow-Headers": "*",
2858
+ "Access-Control-Expose-Headers": "Content-Length, Content-Range, ETag",
2859
+ "Access-Control-Max-Age": "600"
2860
+ };
2861
+ const method = (req.method || "GET").toUpperCase();
2862
+ const deny = (code, msg) => {
2863
+ res.writeHead(code, { ...CORS, "Content-Type": "text/plain", "Cache-Control": "no-store" });
2864
+ res.end(msg);
2865
+ };
2866
+ if (method === "OPTIONS") {
2867
+ res.writeHead(204, CORS);
2868
+ res.end();
2869
+ return;
2870
+ }
2871
+ const m = /^\/s\/([^/]+)(?:\/(.*))?$/.exec(basePath);
2872
+ if (!m) {
2873
+ deny(404, "Not found: expected /s/<sessionId>/<path>");
2874
+ return;
2875
+ }
2876
+ const sessionId = decodeURIComponent(m[1]);
2877
+ const relPath = m[2] ? `/${m[2]}` : "/";
2878
+ const target = this.sessionResolver ? this.sessionResolver(sessionId) : void 0;
2879
+ if (!target || !target.cwd) {
2880
+ deny(404, "Unknown session");
2881
+ return;
2882
+ }
2883
+ const userEmail = this.auth ? await this.auth.authenticate(req).catch(() => null) : null;
2884
+ if (!userEmail) {
2885
+ deny(401, "Authentication required");
2886
+ return;
2887
+ }
2888
+ const ownerEmail = (target.ownerEmail || "").toLowerCase();
2889
+ const isOwner = !!ownerEmail && userEmail.toLowerCase() === ownerEmail;
2890
+ if (!isOwner) {
2891
+ const sharing = target.sharing;
2892
+ if (!sharing || !sharing.enabled) {
2893
+ this.log(`Files-gateway DENY '${sessionId}': ${userEmail} not owner and session not shared`);
2894
+ deny(403, "Access denied");
2895
+ return;
2896
+ }
2897
+ try {
2898
+ authorizeRequest({ user: { email: userEmail } }, sharing, "admin");
2899
+ } catch (err) {
2900
+ this.log(`Files-gateway DENY '${sessionId}': ${userEmail} \u2014 ${err?.message || "not admin"}`);
2901
+ deny(403, "Access denied");
2902
+ return;
2903
+ }
2904
+ }
2905
+ const rootDir = target.cwd;
2906
+ if (method === "GET" || method === "HEAD") {
2907
+ if (url.searchParams.get("list") === "1") {
2908
+ const lex = containedPath(rootDir, relPath.replace(/^\/+/, ""));
2909
+ if (!lex) {
2910
+ deny(403, "Forbidden");
2911
+ return;
2912
+ }
2913
+ let entries;
2914
+ try {
2915
+ const dirents = fs.readdirSync(lex, { withFileTypes: true });
2916
+ entries = dirents.map((d) => {
2917
+ let size = 0, mtime = 0, isDir = d.isDirectory();
2918
+ try {
2919
+ const st = fs.statSync(path.join(lex, d.name));
2920
+ size = st.size;
2921
+ mtime = Math.floor(st.mtimeMs);
2922
+ isDir = st.isDirectory();
2923
+ } catch {
2924
+ }
2925
+ return { name: d.name, isDir, size, mtime };
2926
+ });
2927
+ } catch (err) {
2928
+ deny(err?.code === "ENOENT" ? 404 : 500, `Listing failed: ${err?.message || err}`);
2929
+ return;
2930
+ }
2931
+ res.writeHead(200, { ...CORS, "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
2932
+ res.end(method === "HEAD" ? void 0 : JSON.stringify({ path: relPath, entries }));
2933
+ return;
2934
+ }
2935
+ serveStaticMount(req, res, { rootDir, relPath, mountUrlPrefix: "", browse: false});
2936
+ return;
2937
+ }
2938
+ if (method === "PUT" || method === "DELETE") {
2939
+ const filePath = this.resolveContainedPath(rootDir, relPath);
2940
+ if (!filePath) {
2941
+ deny(403, "Forbidden: path escapes session root");
2942
+ return;
2943
+ }
2944
+ if (method === "DELETE") {
2945
+ try {
2946
+ fs.unlinkSync(filePath);
2947
+ res.writeHead(204, CORS);
2948
+ res.end();
2949
+ } catch (err) {
2950
+ deny(err?.code === "ENOENT" ? 404 : 500, err?.message || "delete failed");
2951
+ }
2952
+ return;
2953
+ }
2954
+ let dir;
2955
+ try {
2956
+ dir = path.dirname(filePath);
2957
+ fs.mkdirSync(dir, { recursive: true });
2958
+ } catch (err) {
2959
+ deny(500, `mkdir failed: ${err?.message || err}`);
2960
+ return;
2961
+ }
2962
+ const tmp = path.join(dir, `.svamp-upload-${process.pid}-${crypto.randomBytes(6).toString("hex")}.part`);
2963
+ const ws = fs.createWriteStream(tmp);
2964
+ let settled = false;
2965
+ const cleanupPartial = () => {
2966
+ if (settled) return;
2967
+ settled = true;
2968
+ ws.destroy();
2969
+ fs.unlink(tmp, () => {
2970
+ });
2971
+ };
2972
+ req.on("aborted", cleanupPartial);
2973
+ req.on("close", () => {
2974
+ if (!ws.writableFinished && !req.complete) cleanupPartial();
2975
+ });
2976
+ ws.on("error", (err) => {
2977
+ if (settled) return;
2978
+ settled = true;
2979
+ fs.unlink(tmp, () => {
2980
+ });
2981
+ if (!res.headersSent) deny(500, err.message);
2982
+ });
2983
+ ws.on("finish", () => {
2984
+ if (settled) return;
2985
+ settled = true;
2986
+ try {
2987
+ fs.renameSync(tmp, filePath);
2988
+ res.writeHead(201, CORS);
2989
+ res.end();
2990
+ } catch (err) {
2991
+ fs.unlink(tmp, () => {
2992
+ });
2993
+ if (!res.headersSent) deny(500, `rename failed: ${err?.message || err}`);
2994
+ }
2995
+ });
2996
+ req.pipe(ws);
2997
+ return;
2998
+ }
2999
+ deny(405, "Method not allowed");
3000
+ }
3001
+ /**
3002
+ * #126: resolve a session-relative path against `rootDir` with lexical + symlink containment
3003
+ * (mirrors the static PUT handler's resolveContained). Allows a not-yet-existing target (for
3004
+ * PUT) as long as its deepest existing ancestor stays within the real root. Returns the
3005
+ * contained absolute path, or null on any traversal/symlink escape (fail-closed).
3006
+ */
3007
+ resolveContainedPath(rootDir, relPath) {
3008
+ const base = path.resolve(rootDir);
3009
+ const lex = containedPath(base, relPath.replace(/^\/+/, ""));
3010
+ if (!lex) return null;
3011
+ try {
3012
+ const realRoot = fs.realpathSync(base);
3013
+ let ancestor = lex;
3014
+ while (!fs.existsSync(ancestor) && path.dirname(ancestor) !== ancestor) {
3015
+ ancestor = path.dirname(ancestor);
3016
+ }
3017
+ const realAncestor = fs.realpathSync(ancestor);
3018
+ if (realAncestor !== realRoot && !realAncestor.startsWith(realRoot + path.sep)) return null;
3019
+ if (fs.existsSync(lex) && fs.lstatSync(lex).isSymbolicLink()) {
3020
+ const realTarget = fs.realpathSync(lex);
3021
+ if (realTarget !== realRoot && !realTarget.startsWith(realRoot + path.sep)) return null;
3022
+ }
3023
+ } catch {
3024
+ return null;
3025
+ }
3026
+ return lex;
3027
+ }
2757
3028
  /** Start a lightweight Node.js HTTP proxy with auth + upload support. */
2758
3029
  startAuthProxy() {
2759
3030
  return new Promise((resolve, reject) => {
@@ -2798,6 +3069,10 @@ a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:und
2798
3069
  res.end(html);
2799
3070
  return;
2800
3071
  }
3072
+ if (mount?.sessionFilesGateway) {
3073
+ await this.handleSessionFilesRequest(req, res, url, basePath);
3074
+ return;
3075
+ }
2801
3076
  if (mount && mount.access !== "public" && mount.access !== "link") {
2802
3077
  const userEmail = this.auth ? await this.auth.authenticate(req).catch(() => null) : null;
2803
3078
  if (mount.access === "owner" && !mount.ownerEmail) {
@@ -3153,7 +3428,7 @@ Connection: close\r
3153
3428
  const mount = this.mounts.get(mountName);
3154
3429
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
3155
3430
  try {
3156
- const { FrpcTunnel } = await import('./frpc-pwqsONPX.mjs');
3431
+ const { FrpcTunnel } = await import('./frpc-CNZJM3qC.mjs');
3157
3432
  let tunnel;
3158
3433
  tunnel = new FrpcTunnel({
3159
3434
  name: tunnelName,
@@ -3249,7 +3524,11 @@ function writeEnvLines(lines) {
3249
3524
  while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
3250
3525
  lines.pop();
3251
3526
  }
3252
- writeFileSync$1(file, lines.join("\n") + "\n", "utf-8");
3527
+ writeFileSync$1(file, lines.join("\n") + "\n", { encoding: "utf-8", mode: 384 });
3528
+ try {
3529
+ chmodSync(file, 384);
3530
+ } catch {
3531
+ }
3253
3532
  }
3254
3533
  function updateEnvFile(updates) {
3255
3534
  const lines = readEnvLines();
@@ -4323,10 +4602,17 @@ function validateChannel(c) {
4323
4602
  if (c.action?.kind === "loop" && m === "caller-supplied" && !c.identity?.shared_key)
4324
4603
  errs.push("a caller-supplied channel without a shared_key may not use a loop action (unauthenticated task injection)");
4325
4604
  if (c.action?.kind === "agent" && m === "caller-supplied" && !c.identity?.shared_key) {
4326
- const MUTATING = ["run_bash", "send_to_session"];
4327
4605
  const ag = c.action.agent || {};
4328
- const grantsMutating = (ag.tools || []).some((t) => MUTATING.includes(t)) || Object.values(ag.per_caller || {}).some((p) => (p?.tools || []).some((t) => MUTATING.includes(t)));
4329
- if (grantsMutating) errs.push("a caller-supplied agent channel without a shared_key may not grant run_bash/send_to_session");
4606
+ const offending = /* @__PURE__ */ new Set();
4607
+ for (const t of ag.tools || []) if (MUTATING_TOOLS.includes(t)) offending.add(t);
4608
+ for (const p of Object.values(ag.per_caller || {})) {
4609
+ for (const t of p?.tools || []) if (MUTATING_TOOLS.includes(t)) offending.add(t);
4610
+ }
4611
+ if (offending.size) {
4612
+ errs.push(
4613
+ `a caller-supplied agent channel without a shared_key may not grant mutating tools (${[...offending].sort().join(", ")})`
4614
+ );
4615
+ }
4330
4616
  }
4331
4617
  const unsafe = /[<>"'&\r\n]/;
4332
4618
  if (unsafe.test(c.name || "")) errs.push(`name must be single-line and not contain < > " ' &`);
@@ -4503,11 +4789,15 @@ function generateSkillBody(channel, ctx) {
4503
4789
  const res = await get_service("${svc}").send({ channel: "${channel.id}", message: "\u2026", from: "your-name" });
4504
4790
  if (res.status === "completed") return res.reply; // answered now \u2014 done
4505
4791
  let cursor = 0; // else poll for the async reply
4506
- while (true) {
4792
+ // BOUNDED: ~10 min (24 x wait:25). Do not loop forever \u2014 repliability is now the DEFAULT on
4793
+ // every ordinary message channel, so you may well be polling a notification channel nobody
4794
+ // is watching, and an unbounded loop would spin (and re-read the server-side outbox) forever.
4795
+ for (let i = 0; i < 24; i++) {
4507
4796
  const r = await get_service("${svc}").receive({ channel: "${channel.id}", key: "${key}", correlationId: res.correlationId, cursor, wait: 25 });
4508
4797
  cursor = r.cursor;
4509
4798
  if (r.replies.length) return r.replies[0].body; // server matched it by correlationId
4510
4799
  }
4800
+ return null; // no reply \u2014 this channel may be fire-and-forget
4511
4801
  \`\`\`
4512
4802
  **HTTP:** \`POST ${recvUrl}\` with \`{"kwargs": {"channel": "${channel.id}", "key": "${key}", "correlationId": "<from send>", "cursor": 0, "wait": 25}}\` (long-poll), or stream \`GET <channel-http>/channel/${channel.id}/events?key=${key}\` (SSE).
4513
4803
  > Tip: always pass the \`correlationId\` from \`send\` to \`receive\` \u2014 it is the reply key, so the reply finds you with no identity matching. Omit it and \`receive\` falls back to matching your \`from\`, which you must then supply identically on every call.
@@ -4987,6 +5277,7 @@ const SESSION_METHOD_MIN_ROLE = {
4987
5277
  readFile: "admin",
4988
5278
  writeFile: "admin",
4989
5279
  writeFileChunk: "admin",
5280
+ filesEndpoint: "admin",
4990
5281
  listDirectory: "admin",
4991
5282
  bash: "admin",
4992
5283
  issue: "admin",
@@ -5911,13 +6202,15 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5911
6202
  if (session.outputBuffer.length > 1e3) {
5912
6203
  session.outputBuffer.splice(0, session.outputBuffer.length - 500);
5913
6204
  }
5914
- try {
5915
- server.emit({
5916
- type: "svamp:terminal-output",
5917
- data: { type: "output", content: batch, sessionId },
5918
- to: terminalClient ?? "*"
5919
- });
5920
- } catch {
6205
+ if (terminalClient) {
6206
+ try {
6207
+ server.emit({
6208
+ type: "svamp:terminal-output",
6209
+ data: { type: "output", content: batch, sessionId },
6210
+ to: terminalClient
6211
+ });
6212
+ } catch {
6213
+ }
5921
6214
  }
5922
6215
  };
5923
6216
  ptyProcess.onData((data) => {
@@ -5930,13 +6223,15 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5930
6223
  session.exited = true;
5931
6224
  session.exitCode = exitCode;
5932
6225
  session.exitSignal = signal;
5933
- try {
5934
- server.emit({
5935
- type: "svamp:terminal-output",
5936
- data: { type: "exit", content: "", sessionId, exitCode, signal },
5937
- to: terminalClient ?? "*"
5938
- });
5939
- } catch {
6226
+ if (terminalClient) {
6227
+ try {
6228
+ server.emit({
6229
+ type: "svamp:terminal-output",
6230
+ data: { type: "exit", content: "", sessionId, exitCode, signal },
6231
+ to: terminalClient
6232
+ });
6233
+ } catch {
6234
+ }
5940
6235
  }
5941
6236
  setTimeout(() => {
5942
6237
  if (terminalSessions.has(sessionId)) {
@@ -6292,7 +6587,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
6292
6587
  const tunnels = handlers.tunnels;
6293
6588
  if (!tunnels) throw new Error("Tunnel management not available");
6294
6589
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
6295
- const { FrpcTunnel } = await import('./frpc-pwqsONPX.mjs');
6590
+ const { FrpcTunnel } = await import('./frpc-CNZJM3qC.mjs');
6296
6591
  const tunnel = new FrpcTunnel({
6297
6592
  name: params.name,
6298
6593
  ports: params.ports,
@@ -6785,7 +7080,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6785
7080
  }
6786
7081
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
6787
7082
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
6788
- const { toolsForRole } = await import('./sideband-DU8QZetT.mjs');
7083
+ const { toolsForRole } = await import('./sideband-gQ7oQEsx.mjs');
6789
7084
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
6790
7085
  return fmt(r2);
6791
7086
  }
@@ -6890,7 +7185,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6890
7185
  return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
6891
7186
  }
6892
7187
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
6893
- const { queryCore } = await import('./commands-CjBsJQX-.mjs');
7188
+ const { queryCore } = await import('./commands-wQYdc5EK.mjs');
6894
7189
  const timeout = c.reply?.timeout_sec || 120;
6895
7190
  let result;
6896
7191
  try {
@@ -8034,14 +8329,35 @@ function loadMessagesFromDiskReverse(messagesDir, beforeSeq, limit) {
8034
8329
  return { messages: [], hasMore: false };
8035
8330
  }
8036
8331
  }
8332
+ const messageCountCache = /* @__PURE__ */ new Map();
8037
8333
  function countMessagesOnDisk(messagesDir, fallbackInMemory) {
8038
8334
  const filePath = join$1(messagesDir, "messages.jsonl");
8039
8335
  if (!existsSync(filePath)) return fallbackInMemory;
8040
8336
  try {
8041
- const data = readFileSync(filePath, "utf-8");
8042
- let count = 0;
8043
- for (let i = 0; i < data.length; i++) if (data.charCodeAt(i) === 10) count++;
8044
- if (data.length > 0 && data[data.length - 1] !== "\n") count++;
8337
+ const st = statSync(filePath);
8338
+ const cached = messageCountCache.get(filePath);
8339
+ if (cached && cached.size === st.size && cached.mtimeMs === st.mtimeMs) return cached.count;
8340
+ let count;
8341
+ if (cached && st.size > cached.size) {
8342
+ const fd = openSync(filePath, "r");
8343
+ try {
8344
+ const len = st.size - cached.size;
8345
+ const buf = Buffer.allocUnsafe(len);
8346
+ const read = readSync(fd, buf, 0, len, cached.size);
8347
+ let added = 0;
8348
+ for (let i = 0; i < read; i++) if (buf[i] === 10) added++;
8349
+ count = cached.count + added;
8350
+ if (read > 0 && buf[read - 1] !== 10) count++;
8351
+ } finally {
8352
+ closeSync(fd);
8353
+ }
8354
+ } else {
8355
+ const data = readFileSync(filePath, "utf-8");
8356
+ count = 0;
8357
+ for (let i = 0; i < data.length; i++) if (data.charCodeAt(i) === 10) count++;
8358
+ if (data.length > 0 && data[data.length - 1] !== "\n") count++;
8359
+ }
8360
+ messageCountCache.set(filePath, { size: st.size, mtimeMs: st.mtimeMs, count });
8045
8361
  return count;
8046
8362
  } catch {
8047
8363
  return fallbackInMemory;
@@ -9065,6 +9381,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
9065
9381
  channelReceive: async (params, context) => {
9066
9382
  const c = channelStore.get(params.channel);
9067
9383
  if (!c || c.enabled === false) return { error: "channel not found" };
9384
+ if (!channelAsyncRepliable(c)) {
9385
+ return { error: "this channel has no async replies (agent/loop/stateless channels reply synchronously)" };
9386
+ }
9068
9387
  const u = context?.user;
9069
9388
  const r = resolveSender(c, {
9070
9389
  key: params.key,
@@ -9194,6 +9513,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
9194
9513
  await callbacks.onWriteFileChunk(path, content, uploadId, chunkIndex, totalChunks, isLast);
9195
9514
  return { success: true };
9196
9515
  },
9516
+ // #126: HTTP files-gateway base URL for this session (large upload/download over the frp
9517
+ // tunnel). Admin-gated like the other file ops. Returns { url: null } when the gateway tunnel
9518
+ // isn't up yet → the frontend falls back to the RPC chunked path.
9519
+ filesEndpoint: async (context) => {
9520
+ authorizeRequest(context, metadata.sharing, "admin");
9521
+ return { url: callbacks.onFilesEndpoint?.() ?? null };
9522
+ },
9197
9523
  listDirectory: async (path, context) => {
9198
9524
  authorizeRequest(context, metadata.sharing, "admin");
9199
9525
  if (!callbacks.onListDirectory) throw new Error("listDirectory not supported");
@@ -11650,6 +11976,7 @@ class AcpBackend {
11650
11976
  this.emit({ type: "status", status: "error", detail: err.message });
11651
11977
  });
11652
11978
  this.process.on("exit", (code, signal) => {
11979
+ this.process = null;
11653
11980
  if (this.disposed) return;
11654
11981
  if (code !== 0 && code !== null) {
11655
11982
  signalStartupFailure(new Error(`Exit code: ${code}`));
@@ -12216,6 +12543,25 @@ var acpAgentConfig = /*#__PURE__*/Object.freeze({
12216
12543
  resolveAcpAgentConfig: resolveAcpAgentConfig
12217
12544
  });
12218
12545
 
12546
+ function applyPermissionResolution(sessionService, requestId, approved) {
12547
+ const reqs = { ...sessionService._agentState?.requests };
12548
+ if (!(requestId in reqs)) return;
12549
+ const completedReqs = { ...sessionService._agentState?.completedRequests };
12550
+ const existingReq = reqs[requestId];
12551
+ delete reqs[requestId];
12552
+ sessionService.updateAgentState({
12553
+ controlledByUser: false,
12554
+ requests: reqs,
12555
+ completedRequests: {
12556
+ ...completedReqs,
12557
+ [requestId]: {
12558
+ ...existingReq || {},
12559
+ completedAt: Date.now(),
12560
+ status: approved ? "approved" : "denied"
12561
+ }
12562
+ }
12563
+ });
12564
+ }
12219
12565
  function bridgeAcpToSession(backend, sessionService, getMetadata, setMetadata, log, onTurnEnd, getModelLabel) {
12220
12566
  let pendingText = "";
12221
12567
  let pendingThinking = "";
@@ -12325,22 +12671,7 @@ function bridgeAcpToSession(backend, sessionService, getMetadata, setMetadata, l
12325
12671
  break;
12326
12672
  }
12327
12673
  case "permission-response": {
12328
- const reqs = { ...sessionService._agentState?.requests };
12329
- const completedReqs = { ...sessionService._agentState?.completedRequests };
12330
- const existingReq = reqs[msg.id];
12331
- delete reqs[msg.id];
12332
- sessionService.updateAgentState({
12333
- controlledByUser: false,
12334
- requests: reqs,
12335
- completedRequests: {
12336
- ...completedReqs,
12337
- [msg.id]: {
12338
- ...existingReq || {},
12339
- completedAt: Date.now(),
12340
- status: msg.approved ? "approved" : "denied"
12341
- }
12342
- }
12343
- });
12674
+ applyPermissionResolution(sessionService, msg.id, msg.approved);
12344
12675
  break;
12345
12676
  }
12346
12677
  case "event": {
@@ -12372,9 +12703,10 @@ function bridgeAcpToSession(backend, sessionService, getMetadata, setMetadata, l
12372
12703
  };
12373
12704
  }
12374
12705
  class HyphaPermissionHandler {
12375
- constructor(shouldAutoAllow, log) {
12706
+ constructor(shouldAutoAllow, log, onAutoResolved) {
12376
12707
  this.shouldAutoAllow = shouldAutoAllow;
12377
12708
  this.log = log;
12709
+ this.onAutoResolved = onAutoResolved;
12378
12710
  }
12379
12711
  pendingPermissions = /* @__PURE__ */ new Map();
12380
12712
  // #0445: safety auto-decline so an unanswered ACP/Gemini permission can't wedge the turn
@@ -12396,6 +12728,10 @@ class HyphaPermissionHandler {
12396
12728
  this.pendingPermissions.delete(toolCallId);
12397
12729
  this.log(`[ACP Permission] Auto-declining ${toolName} after ${timeoutMs}ms with no response`);
12398
12730
  p.resolve({ decision: "denied" });
12731
+ try {
12732
+ this.onAutoResolved?.(toolCallId, false);
12733
+ } catch {
12734
+ }
12399
12735
  }
12400
12736
  }, timeoutMs);
12401
12737
  timer.unref?.();
@@ -12422,6 +12758,10 @@ class HyphaPermissionHandler {
12422
12758
  for (const [id, pending] of this.pendingPermissions) {
12423
12759
  if (pending.timer) clearTimeout(pending.timer);
12424
12760
  pending.resolve({ decision: "denied" });
12761
+ try {
12762
+ this.onAutoResolved?.(id, false);
12763
+ } catch {
12764
+ }
12425
12765
  }
12426
12766
  this.pendingPermissions.clear();
12427
12767
  }
@@ -12502,7 +12842,7 @@ function codexCumulativeTokens(payload) {
12502
12842
  return nonCachedInput + output + Math.round(cached * 0.1);
12503
12843
  }
12504
12844
 
12505
- const PINNED_CODEX_VERSION = "0.146.0";
12845
+ const PINNED_CODEX_VERSION = "0.147.0";
12506
12846
  const MIN_CODEX_STEER_VERSION = { major: 0, minor: 129, patch: 0 };
12507
12847
  function cmpVer(a, b) {
12508
12848
  return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
@@ -12605,6 +12945,7 @@ class CodexAppServerClient {
12605
12945
  child.stderr?.on("data", (d) => this.log("[codex-app-server:stderr]", d.toString().trim()));
12606
12946
  child.on("exit", (code) => {
12607
12947
  const wasConnected = this.connected;
12948
+ if (this.process === child) this.process = null;
12608
12949
  this.connected = false;
12609
12950
  this.failAllPending(new Error(`codex app-server exited (code ${code})`));
12610
12951
  this.resolvePendingTurn(true);
@@ -13516,7 +13857,7 @@ function hasKimiProvider(cfg) {
13516
13857
  }
13517
13858
  function buildKimiCatalogAddArgs(cfg) {
13518
13859
  if (!hasKimiProvider(cfg)) return null;
13519
- const args = ["provider", "catalog", "add", cfg.provider, "--api-key", cfg.apiKey];
13860
+ const args = ["provider", "catalog", "add", cfg.provider];
13520
13861
  if (cfg.apiBase) args.push("--base-url", cfg.apiBase);
13521
13862
  const defaultModel = kimiDefaultModelId(cfg);
13522
13863
  if (defaultModel) args.push("--default-model", defaultModel);
@@ -13532,6 +13873,19 @@ function kimiProviderConfigured(configTomlText, provider) {
13532
13873
  const block = nextTable < 0 ? rest : rest.slice(0, nextTable);
13533
13874
  return /(^|\n)\s*api_key\s*=/.test(block);
13534
13875
  }
13876
+ function kimiModelMenu(env = process.env) {
13877
+ return (env.SVAMP_KIMI_MODELS || "").split(",").map((s) => s.trim()).filter(Boolean).filter((m, i, a) => a.indexOf(m) === i);
13878
+ }
13879
+ function kimiDefaultModel(env = process.env) {
13880
+ const menu = kimiModelMenu(env);
13881
+ return env.SVAMP_KIMI_MODEL || env.LLM_MODEL || menu[0] || void 0;
13882
+ }
13883
+ function kimiClampModel(requested, env = process.env) {
13884
+ const menu = kimiModelMenu(env);
13885
+ if (!menu.length) return requested;
13886
+ if (requested && menu.includes(requested)) return requested;
13887
+ return kimiDefaultModel(env);
13888
+ }
13535
13889
 
13536
13890
  var kimiProvider = /*#__PURE__*/Object.freeze({
13537
13891
  __proto__: null,
@@ -13539,7 +13893,10 @@ var kimiProvider = /*#__PURE__*/Object.freeze({
13539
13893
  DEFAULT_KIMI_PROVIDER: DEFAULT_KIMI_PROVIDER,
13540
13894
  buildKimiCatalogAddArgs: buildKimiCatalogAddArgs,
13541
13895
  hasKimiProvider: hasKimiProvider,
13896
+ kimiClampModel: kimiClampModel,
13897
+ kimiDefaultModel: kimiDefaultModel,
13542
13898
  kimiDefaultModelId: kimiDefaultModelId,
13899
+ kimiModelMenu: kimiModelMenu,
13543
13900
  kimiProviderConfigured: kimiProviderConfigured,
13544
13901
  kimiSpawnEnv: kimiSpawnEnv,
13545
13902
  resolveKimiProvider: resolveKimiProvider
@@ -13553,7 +13910,7 @@ function readKimiConfig() {
13553
13910
  return null;
13554
13911
  }
13555
13912
  }
13556
- function ensureKimiProviderConfigured(env = process.env, command = "kimi", log = () => {
13913
+ async function ensureKimiProviderConfigured(env = process.env, command = "kimi", log = () => {
13557
13914
  }) {
13558
13915
  const cfg = resolveKimiProvider(env);
13559
13916
  if (!hasKimiProvider(cfg)) return false;
@@ -13561,10 +13918,18 @@ function ensureKimiProviderConfigured(env = process.env, command = "kimi", log =
13561
13918
  const args = buildKimiCatalogAddArgs(cfg);
13562
13919
  if (!args) return false;
13563
13920
  try {
13564
- execFileSync(command, args, {
13565
- stdio: "ignore",
13566
- timeout: 3e4,
13567
- env: { ...process.env, KIMI_CLI_NO_AUTO_UPDATE: "1" }
13921
+ await new Promise((resolve, reject) => {
13922
+ execFile$1(
13923
+ command,
13924
+ args,
13925
+ {
13926
+ timeout: 3e4,
13927
+ // #0848: the API key travels in the child ENV, never on argv (argv is world-readable
13928
+ // via ps / /proc/<pid>/cmdline). kimi reads KIMI_REGISTRY_API_KEY when --api-key is omitted.
13929
+ env: { ...process.env, KIMI_CLI_NO_AUTO_UPDATE: "1", KIMI_REGISTRY_API_KEY: cfg.apiKey }
13930
+ },
13931
+ (err) => err ? reject(err) : resolve()
13932
+ );
13568
13933
  });
13569
13934
  log(`[kimi] configured provider '${cfg.provider}' in ${KIMI_CONFIG_PATH}`);
13570
13935
  return kimiProviderConfigured(readKimiConfig(), cfg.provider);
@@ -15154,6 +15519,9 @@ function listIssues(projectRoot, opts = {}) {
15154
15519
  if (opts.scope) items = items.filter((i) => i.scope === opts.scope);
15155
15520
  return items.sort((a, b) => Number(a.id) - Number(b.id));
15156
15521
  }
15522
+ function isPendingIssue(issue) {
15523
+ return issue.status !== "archived" && issue.status !== "paused";
15524
+ }
15157
15525
  function isVisibleTo(issue, session) {
15158
15526
  if (issue.scope === "session") return issue.session === session || issue.owner === session;
15159
15527
  if (issue.owner === session) return true;
@@ -15323,6 +15691,47 @@ function summarize(issues) {
15323
15691
  return { total: issues.length, ready: by("ready"), in_progress: by("in_progress"), paused: by("paused"), archived: by("archived") };
15324
15692
  }
15325
15693
 
15694
+ const DEFAULT_LOG_PRUNE_POLICY = {
15695
+ keepFiles: 10,
15696
+ maxAgeDays: 14,
15697
+ maxBytes: 64 * 1024 * 1024
15698
+ // 64 MiB
15699
+ };
15700
+ const APPEND_ONLY_LOGS = ["daemon-supervised.log", "daemon-boot.log"];
15701
+ function isPerStartLog(name) {
15702
+ if (!name.startsWith("daemon-") || !name.endsWith(".log")) return false;
15703
+ return !APPEND_ONLY_LOGS.includes(name);
15704
+ }
15705
+ function planLogPrune(files, policy = DEFAULT_LOG_PRUNE_POLICY, nowMs = Date.now(), exclude = []) {
15706
+ const excluded = new Set(exclude);
15707
+ const keepFiles = Math.max(0, policy.keepFiles);
15708
+ const maxAgeMs = Math.max(0, policy.maxAgeDays) * 24 * 60 * 60 * 1e3;
15709
+ const perStart = files.filter((f) => isPerStartLog(f.name) && !excluded.has(f.name)).sort((a, b) => b.mtimeMs - a.mtimeMs);
15710
+ const overCount = keepFiles > 0 ? perStart.slice(keepFiles) : perStart;
15711
+ const tooOld = maxAgeMs > 0 ? perStart.filter((f) => nowMs - f.mtimeMs > maxAgeMs) : [];
15712
+ const deleteFiles = [...new Set([...overCount, ...tooOld].map((f) => f.name))];
15713
+ const truncateFiles = files.filter((f) => APPEND_ONLY_LOGS.includes(f.name) && !excluded.has(f.name)).filter((f) => policy.maxBytes > 0 && f.size > policy.maxBytes).map((f) => ({ name: f.name, size: f.size }));
15714
+ return { deleteFiles, truncateFiles };
15715
+ }
15716
+ function resolveLogPrunePolicy(env = process.env) {
15717
+ const num = (raw, fallback) => {
15718
+ if (raw === void 0 || raw === "") return fallback;
15719
+ const n = Number(raw);
15720
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
15721
+ };
15722
+ return {
15723
+ keepFiles: num(env.SVAMP_LOG_KEEP_FILES, DEFAULT_LOG_PRUNE_POLICY.keepFiles),
15724
+ maxAgeDays: num(env.SVAMP_LOG_MAX_AGE_DAYS, DEFAULT_LOG_PRUNE_POLICY.maxAgeDays),
15725
+ maxBytes: num(env.SVAMP_LOG_MAX_BYTES, DEFAULT_LOG_PRUNE_POLICY.maxBytes)
15726
+ };
15727
+ }
15728
+ function tailSlice(buf, tailBytes) {
15729
+ if (buf.length <= tailBytes) return buf;
15730
+ const cut = buf.subarray(buf.length - tailBytes);
15731
+ const nl = cut.indexOf(10);
15732
+ return nl >= 0 && nl + 1 < cut.length ? cut.subarray(nl + 1) : cut;
15733
+ }
15734
+
15326
15735
  function isWorkflowEnabled(wf) {
15327
15736
  return wf.enabled !== false;
15328
15737
  }
@@ -15878,12 +16287,19 @@ function loopCancelledDuringVerify(current) {
15878
16287
  }
15879
16288
  function backlogOraclePending(projectRoot, sessionId) {
15880
16289
  const inScope = (i) => !sessionId || isVisibleTo(i, sessionId);
15881
- return listIssues(projectRoot).filter(inScope).filter((i) => i.status !== "archived" && i.status !== "paused").map((i) => ({ id: i.id }));
16290
+ return listIssues(projectRoot).filter(inScope).filter(isPendingIssue).map((i) => ({ id: i.id }));
15882
16291
  }
15883
16292
  function isBacklogDrainerOracle(oracle) {
15884
16293
  if (!oracle || typeof oracle !== "string") return false;
15885
16294
  return /(^|\s)svamp\s+issue\s+pending(\s|$)/.test(oracle.trim());
15886
16295
  }
16296
+ function oracleScopeSessionId(oracle, selfSessionId) {
16297
+ if (!oracle || typeof oracle !== "string") return void 0;
16298
+ const explicit = /(^|\s)--session[=\s]+(\S+)/.exec(oracle);
16299
+ if (explicit) return explicit[2];
16300
+ if (/(^|\s)--mine(\s|$)/.test(oracle)) return selfSessionId;
16301
+ return void 0;
16302
+ }
15887
16303
  function resolveLoopDurable(cfg) {
15888
16304
  if (typeof cfg.durable === "boolean") return cfg.durable;
15889
16305
  return isBacklogDrainerOracle(cfg.oracle);
@@ -16790,16 +17206,24 @@ async function installSkillFromMarketplace(name) {
16790
17206
  const files = await collectFiles();
16791
17207
  if (files.length === 0) throw new Error(`Skill ${name} has no files in marketplace`);
16792
17208
  const targetDir = join(CLAUDE_SKILLS_DIR, name);
16793
- mkdirSync(targetDir, { recursive: true });
16794
- for (const filePath of files) {
16795
- const url = `${BASE}/files/${filePath}`;
16796
- const resp = await fetch(url, { signal: AbortSignal.timeout(3e4) });
16797
- if (!resp.ok) throw new Error(`Failed to download ${filePath}: HTTP ${resp.status}`);
16798
- const content = await resp.text();
16799
- const localPath = join(targetDir, filePath);
16800
- if (!localPath.startsWith(targetDir + "/")) continue;
16801
- mkdirSync(dirname$1(localPath), { recursive: true });
16802
- writeFileSync(localPath, content, "utf-8");
17209
+ const stagingDir = `${targetDir}.tmp-${process.pid}-${Date.now().toString(36)}`;
17210
+ try {
17211
+ mkdirSync(stagingDir, { recursive: true });
17212
+ for (const filePath of files) {
17213
+ const url = `${BASE}/files/${filePath}`;
17214
+ const resp = await fetch(url, { signal: AbortSignal.timeout(3e4) });
17215
+ if (!resp.ok) throw new Error(`Failed to download ${filePath}: HTTP ${resp.status}`);
17216
+ const content = await resp.text();
17217
+ const localPath = join(stagingDir, filePath);
17218
+ if (!localPath.startsWith(stagingDir + "/")) continue;
17219
+ mkdirSync(dirname$1(localPath), { recursive: true });
17220
+ writeFileSync(localPath, content, "utf-8");
17221
+ }
17222
+ rmSync(targetDir, { recursive: true, force: true });
17223
+ renameSync$1(stagingDir, targetDir);
17224
+ } catch (err) {
17225
+ rmSync(stagingDir, { recursive: true, force: true });
17226
+ throw err;
16803
17227
  }
16804
17228
  }
16805
17229
  function getBundledSkillsDir() {
@@ -17934,8 +18358,38 @@ function ensureHomeDir() {
17934
18358
  mkdirSync(LOGS_DIR, { recursive: true });
17935
18359
  }
17936
18360
  }
18361
+ function pruneDaemonLogs() {
18362
+ try {
18363
+ const policy = resolveLogPrunePolicy(process.env);
18364
+ const entries = readdirSync$1(LOGS_DIR).map((name) => {
18365
+ try {
18366
+ const st = statSync$1(join(LOGS_DIR, name));
18367
+ return { name, size: st.size, mtimeMs: st.mtimeMs };
18368
+ } catch {
18369
+ return null;
18370
+ }
18371
+ }).filter((e) => e !== null);
18372
+ const plan = planLogPrune(entries, policy);
18373
+ for (const name of plan.deleteFiles) {
18374
+ try {
18375
+ unlinkSync$1(join(LOGS_DIR, name));
18376
+ } catch {
18377
+ }
18378
+ }
18379
+ for (const { name } of plan.truncateFiles) {
18380
+ try {
18381
+ const p = join(LOGS_DIR, name);
18382
+ const kept = tailSlice(readFileSync$1(p), Math.floor(policy.maxBytes / 8));
18383
+ writeFileSync(p, kept);
18384
+ } catch {
18385
+ }
18386
+ }
18387
+ } catch {
18388
+ }
18389
+ }
17937
18390
  function createLogger() {
17938
18391
  ensureHomeDir();
18392
+ pruneDaemonLogs();
17939
18393
  const logFile = join(LOGS_DIR, `daemon-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.log`);
17940
18394
  return {
17941
18395
  logFilePath: logFile,
@@ -18215,7 +18669,7 @@ async function startDaemon(options) {
18215
18669
  try {
18216
18670
  const dir = loadSessionIndex()[sessionId]?.directory;
18217
18671
  if (!dir) return;
18218
- const { reconcileServiceLinks } = await import('./agentCommands-RhxyIA_z.mjs');
18672
+ const { reconcileServiceLinks } = await import('./agentCommands-DdOSpUEW.mjs');
18219
18673
  const configPath = getSvampConfigPath(dir, sessionId);
18220
18674
  const config = readSvampConfig(configPath);
18221
18675
  const entries = Array.from(urls.entries());
@@ -18233,7 +18687,7 @@ async function startDaemon(options) {
18233
18687
  }
18234
18688
  }
18235
18689
  async function createExposedTunnel(spec) {
18236
- const { FrpcTunnel } = await import('./frpc-pwqsONPX.mjs');
18690
+ const { FrpcTunnel } = await import('./frpc-CNZJM3qC.mjs');
18237
18691
  const tunnel = new FrpcTunnel({
18238
18692
  name: spec.name,
18239
18693
  ports: spec.ports,
@@ -18261,7 +18715,7 @@ async function startDaemon(options) {
18261
18715
  ensureAutoInstalledCommands(logger);
18262
18716
  (async () => {
18263
18717
  try {
18264
- const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-mM9Q6Q2G.mjs');
18718
+ const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-CnCpensV.mjs');
18265
18719
  beginClaudeVersionReconcile((msg) => logger.log(msg));
18266
18720
  } catch (e) {
18267
18721
  logger.log(`[claude-version] check failed: ${e?.message || e}`);
@@ -20578,11 +21032,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20578
21032
  });
20579
21033
  },
20580
21034
  onIssue: async (params) => {
20581
- const { issueRpc } = await import('./rpc-DAcODVs3.mjs');
21035
+ const { issueRpc } = await import('./rpc-Cx6KsBYC.mjs');
20582
21036
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
20583
21037
  },
20584
21038
  onWorkflow: async (params) => {
20585
- const { workflowRpc } = await import('./rpc-BFIy8FTe.mjs');
21039
+ const { workflowRpc } = await import('./rpc-CclCeDcu.mjs');
20586
21040
  return workflowRpc(params?.cwd || directory, params || {});
20587
21041
  },
20588
21042
  onRipgrep: async (args, cwd) => {
@@ -20617,6 +21071,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20617
21071
  onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
20618
21072
  await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
20619
21073
  },
21074
+ onFilesEndpoint: () => serveManager$1.getSessionFilesEndpoint(sessionId),
20620
21075
  onListDirectory: async (path) => {
20621
21076
  const resolvedDir = resolve$1(directory, path || ".");
20622
21077
  if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
@@ -20876,7 +21331,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20876
21331
  if (!s || s.phase !== "dormant") return;
20877
21332
  let pending = [];
20878
21333
  try {
20879
- pending = backlogOraclePending(directory, sessionId);
21334
+ const root = resolveProjectRoot(directory);
21335
+ const scope = oracleScopeSessionId(typeof s.oracle === "string" ? s.oracle : void 0, sessionId);
21336
+ pending = backlogOraclePending(root, scope);
20880
21337
  } catch {
20881
21338
  return;
20882
21339
  }
@@ -21002,7 +21459,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
21002
21459
  {
21003
21460
  const forcedModel = agentName === "codex" ? codexForcedModel() : void 0;
21004
21461
  const composerModel = typeof msgMeta?.model === "string" && msgMeta.model ? msgMeta.model : void 0;
21005
- const rawModel = forcedModel || (agentName === "codex" && codexModelMenu().length ? codexClampModel(composerModel) : composerModel);
21462
+ const rawModel = forcedModel || (agentName === "codex" && codexModelMenu().length ? codexClampModel(composerModel) : void 0) || (agentName === "kimi" && kimiModelMenu().length ? kimiClampModel(composerModel) : void 0) || composerModel;
21006
21463
  const desiredModel = rawModel || agentConfig?.default_model || void 0;
21007
21464
  if (desiredModel !== appliedAgentModel) {
21008
21465
  agentBackend?.setModel?.(desiredModel);
@@ -21286,11 +21743,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
21286
21743
  });
21287
21744
  },
21288
21745
  onIssue: async (params) => {
21289
- const { issueRpc } = await import('./rpc-DAcODVs3.mjs');
21746
+ const { issueRpc } = await import('./rpc-Cx6KsBYC.mjs');
21290
21747
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
21291
21748
  },
21292
21749
  onWorkflow: async (params) => {
21293
- const { workflowRpc } = await import('./rpc-BFIy8FTe.mjs');
21750
+ const { workflowRpc } = await import('./rpc-CclCeDcu.mjs');
21294
21751
  return workflowRpc(params?.cwd || directory, params || {});
21295
21752
  },
21296
21753
  onRipgrep: async (args, cwd) => {
@@ -21325,6 +21782,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
21325
21782
  onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
21326
21783
  await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
21327
21784
  },
21785
+ onFilesEndpoint: () => serveManager$1.getSessionFilesEndpoint(sessionId),
21328
21786
  onListDirectory: async (path) => {
21329
21787
  const resolvedDir = resolve$1(directory, path || ".");
21330
21788
  if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
@@ -21424,7 +21882,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
21424
21882
  );
21425
21883
  const checkSvampConfig = svampConfigChecker.check;
21426
21884
  const writeSvampConfigPatchAcp = svampConfigChecker.writeConfig;
21427
- const permissionHandler = new HyphaPermissionHandler(shouldAutoAllow2, logger.log);
21885
+ const permissionHandler = new HyphaPermissionHandler(
21886
+ shouldAutoAllow2,
21887
+ logger.log,
21888
+ (requestId, approved) => applyPermissionResolution(sessionService, requestId, approved)
21889
+ );
21428
21890
  let agentIsoConfig;
21429
21891
  const agentShouldIsolate = Boolean(options2.forceIsolation) || Boolean(options2.securityContext);
21430
21892
  if (agentShouldIsolate && isolationCapabilities.preferred) {
@@ -21527,10 +21989,24 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
21527
21989
  let kimiEnv = {};
21528
21990
  if (agentName === "kimi") {
21529
21991
  try {
21530
- ensureKimiProviderConfigured(process.env, acpConfig.command, logger.log);
21992
+ await ensureKimiProviderConfigured(process.env, acpConfig.command, logger.log);
21531
21993
  } catch {
21532
21994
  }
21533
21995
  kimiEnv = kimiSpawnEnv(process.env);
21996
+ const kimiMenu = kimiModelMenu();
21997
+ if (kimiMenu.length) {
21998
+ const kimiModel = kimiClampModel(options2.model ?? void 0) || kimiDefaultModel();
21999
+ sessionMetadata.models = kimiMenu.map((m) => ({
22000
+ code: m,
22001
+ value: m,
22002
+ description: m === kimiModel ? "Default on this machine" : "Available on this machine"
22003
+ }));
22004
+ try {
22005
+ sessionService.updateMetadata(sessionMetadata);
22006
+ } catch {
22007
+ }
22008
+ logger.log(`[Agent Session ${sessionId}] Kimi model menu advertised (SVAMP_KIMI_MODELS): [${kimiMenu.join(", ")}] default=${kimiModel}`);
22009
+ }
21534
22010
  }
21535
22011
  agentBackend = new AcpBackend({
21536
22012
  agentName,
@@ -21875,6 +22351,8 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
21875
22351
  "event"
21876
22352
  );
21877
22353
  sessionService.sendSessionEnd();
22354
+ agentBackend.dispose().catch(() => {
22355
+ });
21878
22356
  if (!acpPersisted) {
21879
22357
  deleteSession(sessionId);
21880
22358
  } else {
@@ -22253,6 +22731,27 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
22253
22731
  writeDaemonStateFile(localState);
22254
22732
  logger.log("Daemon state file written \u2014 daemon is up; restoring mounts/sessions in background");
22255
22733
  serveManager$1.restore().catch((err) => logger.error(`[serve] mount restore failed: ${err?.message || err}`));
22734
+ const daemonOwnerEmail = parseJwtEmail(process.env.HYPHA_TOKEN || "") || null;
22735
+ serveManager$1.setSessionResolver((sessionId) => {
22736
+ for (const [, session] of pidToTrackedSession) {
22737
+ if (session.svampSessionId === sessionId && !session.stopped && session.directory) {
22738
+ let sharing = null;
22739
+ try {
22740
+ sharing = session.hyphaService?.getMetadata?.()?.sharing ?? null;
22741
+ } catch {
22742
+ sharing = null;
22743
+ }
22744
+ return {
22745
+ cwd: session.directory,
22746
+ sharing,
22747
+ ownerEmail: sharing?.owner || daemonOwnerEmail
22748
+ };
22749
+ }
22750
+ }
22751
+ return null;
22752
+ });
22753
+ const sessionFilesToken = createHash$1("sha256").update(`session-files:${machineId}`).digest("hex").slice(0, 20);
22754
+ serveManager$1.ensureSessionFilesGateway(sessionFilesToken).then((base) => logger.log(`[serve] session-files gateway: ${base || "(tunnel pending)"}`)).catch((err) => logger.error(`[serve] session-files gateway failed: ${err?.message || err}`));
22256
22755
  const persistedSessions = loadPersistedSessions();
22257
22756
  try {
22258
22757
  const knownIds = new Set(persistedSessions.map((s) => s.sessionId).concat(Object.keys(loadSessionIndex())));
@@ -22291,7 +22790,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
22291
22790
  }
22292
22791
  if (persistedSessions.length > 0) {
22293
22792
  try {
22294
- const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-mM9Q6Q2G.mjs');
22793
+ const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-CnCpensV.mjs');
22295
22794
  await awaitClaudeVersionReady();
22296
22795
  } catch {
22297
22796
  }
@@ -22536,7 +23035,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
22536
23035
  const PING_TIMEOUT_MS = 15e3;
22537
23036
  const POST_RECONNECT_GRACE_MS = 2e4;
22538
23037
  const RECONNECT_JITTER_MS = 2500;
22539
- const { WorkflowScheduler } = await import('./scheduler-DlDYY_wS.mjs');
23038
+ const { WorkflowScheduler } = await import('./scheduler-Wzzed1CU.mjs');
22540
23039
  const workflowProjectRoots = () => {
22541
23040
  const dirs = /* @__PURE__ */ new Set();
22542
23041
  for (const s of pidToTrackedSession.values()) {
@@ -23170,4 +23669,4 @@ var run = /*#__PURE__*/Object.freeze({
23170
23669
  writeStopMarker: writeStopMarker
23171
23670
  });
23172
23671
 
23173
- export { getSkillInfo as $, removeWorkflow as A, validateWorkflowName as B, saveWorkflow as C, rawWorkflow as D, listWorkflows as E, isWorkflowEnabled as F, workflowSchedules as G, inZone as H, cronMatches as I, summarize as J, workflowSteps as K, parseJwtEmail as L, computeCollectionConfigUpdate as M, SYSTEM_COLLECTION_CONFIG as N, loadMachineContext as O, buildMachineInstructions as P, machineToolsForRole as Q, READ_ONLY_TOOLS as R, SharingNotificationSync as S, buildMachineTools as T, parseFrontmatter as U, getSkillsServer as V, getSkillsWorkspaceName as W, getSkillsCollectionName as X, fetchWithTimeout as Y, searchSkills as Z, SKILLS_DIR as _, createSessionStore as a, downloadSkillFile as a0, listSkillFiles as a1, resolveModel as a2, clearStopMarker as a3, stopMarkerExists as a4, formatHandle as a5, normalizeAllowedUser as a6, loadSecurityContextConfig as a7, resolveSecurityContext as a8, buildSecurityContextFromFlags as a9, kimiProvider as aA, kimiSetup as aB, api as aC, run as aD, mergeSecurityContexts as aa, buildSessionShareUrl as ab, computeOutboundHop as ac, registerAwaitingReply as ad, buildMachineShareUrl as ae, parseHandle as af, handleMatchesMetadata as ag, PINNED_CODEX_VERSION as ah, withFileLock 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, projectInfo as at, DefaultTransport$1 as au, acpBackend as av, acpAgentConfig as aw, codexAppServerBackend as ax, GeminiTransport$1 as ay, KimiTransport$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 };
23672
+ export { SKILLS_DIR as $, setWorkflowEnabled as A, removeWorkflow as B, validateWorkflowName as C, saveWorkflow as D, rawWorkflow as E, listWorkflows as F, isWorkflowEnabled as G, workflowSchedules as H, inZone as I, cronMatches as J, summarize as K, workflowSteps as L, parseJwtEmail as M, computeCollectionConfigUpdate as N, SYSTEM_COLLECTION_CONFIG as O, loadMachineContext as P, buildMachineInstructions as Q, READ_ONLY_TOOLS as R, SharingNotificationSync as S, machineToolsForRole as T, buildMachineTools as U, parseFrontmatter as V, getSkillsServer as W, getSkillsWorkspaceName as X, getSkillsCollectionName as Y, fetchWithTimeout as Z, searchSkills as _, createSessionStore as a, getSkillInfo as a0, downloadSkillFile as a1, listSkillFiles as a2, resolveModel as a3, clearStopMarker as a4, stopMarkerExists as a5, formatHandle as a6, normalizeAllowedUser as a7, loadSecurityContextConfig as a8, resolveSecurityContext as a9, KimiTransport$1 as aA, kimiProvider as aB, kimiSetup as aC, api as aD, run as aE, buildSecurityContextFromFlags as aa, mergeSecurityContexts as ab, buildSessionShareUrl as ac, computeOutboundHop as ad, registerAwaitingReply as ae, buildMachineShareUrl as af, parseHandle as ag, handleMatchesMetadata as ah, PINNED_CODEX_VERSION as ai, withFileLock as aj, describeMisconfiguration as ak, buildMachineDeps as al, applyClaudeProxyEnv as am, composeSessionId as an, generateFriendlyName as ao, generateHookSettings as ap, staticFileServer as aq, instanceConfig as ar, claudeAuth as as, codexProvider 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, isPendingIssue as q, registerMachineService as r, startDaemon as s, searchIssues as t, updateIssue as u, isVisibleTo as v, getRun as w, listRuns as x, getWorkflow as y, runWorkflow as z };