svamp-cli 0.2.314 → 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.
- package/bin/commands/workflow.md +3 -2
- package/dist/{adminCommands-DCttALYO.mjs → adminCommands-fJVu-h9l.mjs} +1 -1
- package/dist/{agentCommands-BUsSOz5M.mjs → agentCommands-DdOSpUEW.mjs} +5 -5
- package/dist/{auth-D5yTcsy2.mjs → auth-B2XK13r-.mjs} +1 -1
- package/dist/{cli-ahWO_ULm.mjs → cli-DGVQaDbd.mjs} +115 -85
- package/dist/cli.mjs +2 -2
- package/dist/{commands-ZML4FIzg.mjs → commands-Bx2JkNft.mjs} +1 -1
- package/dist/{commands-CJmKY6Qo.mjs → commands-C4p4v9eO.mjs} +2 -2
- package/dist/{commands-bS4ErsEr.mjs → commands-CoBMjrtP.mjs} +2 -2
- package/dist/{commands-Ce7MP6Ut.mjs → commands-DEsDSuMW.mjs} +3 -3
- package/dist/{commands-Cl-TIip1.mjs → commands-DUdStLKb.mjs} +2 -2
- package/dist/{commands-CuinsdU0.mjs → commands-I9jHLkrI.mjs} +1 -1
- package/dist/{commands-BSHZGcCC.mjs → commands-ino-Gtcu.mjs} +8 -8
- package/dist/{commands-r4QVEq12.mjs → commands-wQYdc5EK.mjs} +1 -1
- package/dist/{fleet-CSwFR8B0.mjs → fleet-NRi2YMso.mjs} +6 -5
- package/dist/{frpc-CpuE7d3Y.mjs → frpc-CNZJM3qC.mjs} +1 -1
- package/dist/{headlessCli-Dg-awx4h.mjs → headlessCli-DpTQ00fL.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{notifyCommands-Dlm4RafU.mjs → notifyCommands-BFRuzxXP.mjs} +1 -1
- package/dist/package-DN_Oa_OT.mjs +64 -0
- package/dist/{pinnedClaudeCode-mM9Q6Q2G.mjs → pinnedClaudeCode-CnCpensV.mjs} +1 -1
- package/dist/{rpc-BN7kFhGk.mjs → rpc-CclCeDcu.mjs} +1 -1
- package/dist/{rpc-CLTPF1K6.mjs → rpc-Cx6KsBYC.mjs} +2 -2
- package/dist/{run-B6-6GYde.mjs → run-DKhnAl2f.mjs} +1 -1
- package/dist/{run-Y3YLx8x7.mjs → run-RA4Vr6lj.mjs} +595 -90
- package/dist/{scheduler-CWlkmpMz.mjs → scheduler-Wzzed1CU.mjs} +1 -1
- package/dist/{serveCommands-mnocpHcO.mjs → serveCommands-DQeSesYI.mjs} +5 -5
- package/dist/{sideband-c4lkIKWJ.mjs → sideband-gQ7oQEsx.mjs} +1 -1
- package/package.json +4 -4
- package/dist/package-DO2e6EnU.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
|
-
|
|
2406
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
4329
|
-
|
|
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 < > " ' &`);
|
|
@@ -4354,6 +4640,11 @@ function fixedSessionId(c) {
|
|
|
4354
4640
|
const b = c.bind;
|
|
4355
4641
|
return b && typeof b.session === "string" ? b.session : void 0;
|
|
4356
4642
|
}
|
|
4643
|
+
function channelAsyncRepliable(c) {
|
|
4644
|
+
if (!c || c.system) return false;
|
|
4645
|
+
if ((c.action?.kind ?? "message") !== "message") return false;
|
|
4646
|
+
return bindMode(c) !== "stateless";
|
|
4647
|
+
}
|
|
4357
4648
|
class ChannelStore {
|
|
4358
4649
|
dir;
|
|
4359
4650
|
constructor(projectDir) {
|
|
@@ -4471,7 +4762,8 @@ function generateSkillBody(channel, ctx) {
|
|
|
4471
4762
|
const uploadUrl = `${gw}/upload`;
|
|
4472
4763
|
const key = ctx?.key || "<your-key>";
|
|
4473
4764
|
const isAgent = channel.action?.kind === "agent";
|
|
4474
|
-
|
|
4765
|
+
channel.reply?.mode === "queue";
|
|
4766
|
+
const asyncReply = channelAsyncRepliable(channel);
|
|
4475
4767
|
const recvUrl = `${gw}/receive`;
|
|
4476
4768
|
const hyphaOpen = (channel.identity?.hypha_allow || []).length > 0;
|
|
4477
4769
|
const mode = bindMode(channel);
|
|
@@ -4482,8 +4774,8 @@ function generateSkillBody(channel, ctx) {
|
|
|
4482
4774
|
const bindNote = mode === "stateless" ? `**Stateless channel** \u2014 each call spawns a fresh, isolated one-shot session (cold-start: a few seconds), runs, replies, and closes. No shared memory between calls.` : mode === "fixed" ? `Routes to a **fixed session** (\`${rSession}\`); reachable only while that session is live.` : rSession ? `Routes to **session \`${rSession}\`** (the one this instruction was copied from); reachable only while it is live, else a fresh stateless run answers.` : `**Dynamic channel** \u2014 pass \`session: "<id>"\` to target a specific live session, else a fresh stateless run answers.`;
|
|
4483
4775
|
const name = channel.skill?.name || channel.name;
|
|
4484
4776
|
const desc = channel.skill?.description || channel.description || `Send a message to the "${channel.name}" channel.`;
|
|
4485
|
-
const replyNote = isAgent ? `This is a **WISE Agent** channel: \`send()\` runs a fast assistant against the session's tools/skills and returns its answer synchronously in the result \`reply\`.` :
|
|
4486
|
-
const queueSection =
|
|
4777
|
+
const replyNote = isAgent ? `This is a **WISE Agent** channel: \`send()\` runs a fast assistant against the session's tools/skills and returns its answer synchronously in the result \`reply\`.` : asyncReply ? `This channel is **repliable**: \`send()\` usually returns \`{ status: "queued", correlationId }\` and the agent replies later \u2014 poll \`receive()\` (or GET /receive \xB7 /events) for replies addressed to you. **But it may also answer synchronously** with \`{ status: "completed", reply }\` (e.g. when the target session is gone and a fresh one-shot run handles the call) \u2014 always check \`status\` first and use \`reply\` directly when present; \`receive()\` has nothing to return for a one-shot.` : `Delivery is fire-and-forget \u2014 the message lands in the agent's inbox, tagged with your verified identity (\`from\`).`;
|
|
4778
|
+
const queueSection = asyncReply ? `
|
|
4487
4779
|
|
|
4488
4780
|
## Getting the reply
|
|
4489
4781
|
\`send()\` returns one of two shapes \u2014 **check \`status\` first**:
|
|
@@ -4497,11 +4789,15 @@ function generateSkillBody(channel, ctx) {
|
|
|
4497
4789
|
const res = await get_service("${svc}").send({ channel: "${channel.id}", message: "\u2026", from: "your-name" });
|
|
4498
4790
|
if (res.status === "completed") return res.reply; // answered now \u2014 done
|
|
4499
4791
|
let cursor = 0; // else poll for the async reply
|
|
4500
|
-
|
|
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++) {
|
|
4501
4796
|
const r = await get_service("${svc}").receive({ channel: "${channel.id}", key: "${key}", correlationId: res.correlationId, cursor, wait: 25 });
|
|
4502
4797
|
cursor = r.cursor;
|
|
4503
4798
|
if (r.replies.length) return r.replies[0].body; // server matched it by correlationId
|
|
4504
4799
|
}
|
|
4800
|
+
return null; // no reply \u2014 this channel may be fire-and-forget
|
|
4505
4801
|
\`\`\`
|
|
4506
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).
|
|
4507
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.
|
|
@@ -4981,6 +5277,7 @@ const SESSION_METHOD_MIN_ROLE = {
|
|
|
4981
5277
|
readFile: "admin",
|
|
4982
5278
|
writeFile: "admin",
|
|
4983
5279
|
writeFileChunk: "admin",
|
|
5280
|
+
filesEndpoint: "admin",
|
|
4984
5281
|
listDirectory: "admin",
|
|
4985
5282
|
bash: "admin",
|
|
4986
5283
|
issue: "admin",
|
|
@@ -5905,13 +6202,15 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5905
6202
|
if (session.outputBuffer.length > 1e3) {
|
|
5906
6203
|
session.outputBuffer.splice(0, session.outputBuffer.length - 500);
|
|
5907
6204
|
}
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
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
|
+
}
|
|
5915
6214
|
}
|
|
5916
6215
|
};
|
|
5917
6216
|
ptyProcess.onData((data) => {
|
|
@@ -5924,13 +6223,15 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5924
6223
|
session.exited = true;
|
|
5925
6224
|
session.exitCode = exitCode;
|
|
5926
6225
|
session.exitSignal = signal;
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
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
|
+
}
|
|
5934
6235
|
}
|
|
5935
6236
|
setTimeout(() => {
|
|
5936
6237
|
if (terminalSessions.has(sessionId)) {
|
|
@@ -6286,7 +6587,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
6286
6587
|
const tunnels = handlers.tunnels;
|
|
6287
6588
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
6288
6589
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
6289
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
6590
|
+
const { FrpcTunnel } = await import('./frpc-CNZJM3qC.mjs');
|
|
6290
6591
|
const tunnel = new FrpcTunnel({
|
|
6291
6592
|
name: params.name,
|
|
6292
6593
|
ports: params.ports,
|
|
@@ -6779,7 +7080,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6779
7080
|
}
|
|
6780
7081
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
6781
7082
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
6782
|
-
const { toolsForRole } = await import('./sideband-
|
|
7083
|
+
const { toolsForRole } = await import('./sideband-gQ7oQEsx.mjs');
|
|
6783
7084
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
6784
7085
|
return fmt(r2);
|
|
6785
7086
|
}
|
|
@@ -6884,7 +7185,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6884
7185
|
return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
|
|
6885
7186
|
}
|
|
6886
7187
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
6887
|
-
const { queryCore } = await import('./commands-
|
|
7188
|
+
const { queryCore } = await import('./commands-wQYdc5EK.mjs');
|
|
6888
7189
|
const timeout = c.reply?.timeout_sec || 120;
|
|
6889
7190
|
let result;
|
|
6890
7191
|
try {
|
|
@@ -6967,8 +7268,8 @@ ${d?.error || "not found"}`;
|
|
|
6967
7268
|
const res = resolveChannel(kwargs.channel, kwargs.session);
|
|
6968
7269
|
if ("error" in res) return { error: res.error };
|
|
6969
7270
|
const { c, dir } = res;
|
|
6970
|
-
if (c
|
|
6971
|
-
return { error: "this channel has no async replies (
|
|
7271
|
+
if (!channelAsyncRepliable(c)) {
|
|
7272
|
+
return { error: "this channel has no async replies (agent/loop/stateless channels reply synchronously)" };
|
|
6972
7273
|
}
|
|
6973
7274
|
const u = context?.user;
|
|
6974
7275
|
const r = resolveSender(c, {
|
|
@@ -8028,14 +8329,35 @@ function loadMessagesFromDiskReverse(messagesDir, beforeSeq, limit) {
|
|
|
8028
8329
|
return { messages: [], hasMore: false };
|
|
8029
8330
|
}
|
|
8030
8331
|
}
|
|
8332
|
+
const messageCountCache = /* @__PURE__ */ new Map();
|
|
8031
8333
|
function countMessagesOnDisk(messagesDir, fallbackInMemory) {
|
|
8032
8334
|
const filePath = join$1(messagesDir, "messages.jsonl");
|
|
8033
8335
|
if (!existsSync(filePath)) return fallbackInMemory;
|
|
8034
8336
|
try {
|
|
8035
|
-
const
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
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 });
|
|
8039
8361
|
return count;
|
|
8040
8362
|
} catch {
|
|
8041
8363
|
return fallbackInMemory;
|
|
@@ -8975,8 +9297,8 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8975
9297
|
syncChannelsToMetadata();
|
|
8976
9298
|
return { ok: true, call_id: callId, status: "accepted" };
|
|
8977
9299
|
}
|
|
8978
|
-
const
|
|
8979
|
-
const wantReply =
|
|
9300
|
+
const repliable = channelAsyncRepliable(c);
|
|
9301
|
+
const wantReply = repliable && !params.no_reply;
|
|
8980
9302
|
const replySession = wantReply ? params.reply_to?.session : void 0;
|
|
8981
9303
|
const inboxMsg = {
|
|
8982
9304
|
messageId: callId,
|
|
@@ -9059,6 +9381,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
9059
9381
|
channelReceive: async (params, context) => {
|
|
9060
9382
|
const c = channelStore.get(params.channel);
|
|
9061
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
|
+
}
|
|
9062
9387
|
const u = context?.user;
|
|
9063
9388
|
const r = resolveSender(c, {
|
|
9064
9389
|
key: params.key,
|
|
@@ -9188,6 +9513,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
9188
9513
|
await callbacks.onWriteFileChunk(path, content, uploadId, chunkIndex, totalChunks, isLast);
|
|
9189
9514
|
return { success: true };
|
|
9190
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
|
+
},
|
|
9191
9523
|
listDirectory: async (path, context) => {
|
|
9192
9524
|
authorizeRequest(context, metadata.sharing, "admin");
|
|
9193
9525
|
if (!callbacks.onListDirectory) throw new Error("listDirectory not supported");
|
|
@@ -11644,6 +11976,7 @@ class AcpBackend {
|
|
|
11644
11976
|
this.emit({ type: "status", status: "error", detail: err.message });
|
|
11645
11977
|
});
|
|
11646
11978
|
this.process.on("exit", (code, signal) => {
|
|
11979
|
+
this.process = null;
|
|
11647
11980
|
if (this.disposed) return;
|
|
11648
11981
|
if (code !== 0 && code !== null) {
|
|
11649
11982
|
signalStartupFailure(new Error(`Exit code: ${code}`));
|
|
@@ -12210,6 +12543,25 @@ var acpAgentConfig = /*#__PURE__*/Object.freeze({
|
|
|
12210
12543
|
resolveAcpAgentConfig: resolveAcpAgentConfig
|
|
12211
12544
|
});
|
|
12212
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
|
+
}
|
|
12213
12565
|
function bridgeAcpToSession(backend, sessionService, getMetadata, setMetadata, log, onTurnEnd, getModelLabel) {
|
|
12214
12566
|
let pendingText = "";
|
|
12215
12567
|
let pendingThinking = "";
|
|
@@ -12319,22 +12671,7 @@ function bridgeAcpToSession(backend, sessionService, getMetadata, setMetadata, l
|
|
|
12319
12671
|
break;
|
|
12320
12672
|
}
|
|
12321
12673
|
case "permission-response": {
|
|
12322
|
-
|
|
12323
|
-
const completedReqs = { ...sessionService._agentState?.completedRequests };
|
|
12324
|
-
const existingReq = reqs[msg.id];
|
|
12325
|
-
delete reqs[msg.id];
|
|
12326
|
-
sessionService.updateAgentState({
|
|
12327
|
-
controlledByUser: false,
|
|
12328
|
-
requests: reqs,
|
|
12329
|
-
completedRequests: {
|
|
12330
|
-
...completedReqs,
|
|
12331
|
-
[msg.id]: {
|
|
12332
|
-
...existingReq || {},
|
|
12333
|
-
completedAt: Date.now(),
|
|
12334
|
-
status: msg.approved ? "approved" : "denied"
|
|
12335
|
-
}
|
|
12336
|
-
}
|
|
12337
|
-
});
|
|
12674
|
+
applyPermissionResolution(sessionService, msg.id, msg.approved);
|
|
12338
12675
|
break;
|
|
12339
12676
|
}
|
|
12340
12677
|
case "event": {
|
|
@@ -12366,9 +12703,10 @@ function bridgeAcpToSession(backend, sessionService, getMetadata, setMetadata, l
|
|
|
12366
12703
|
};
|
|
12367
12704
|
}
|
|
12368
12705
|
class HyphaPermissionHandler {
|
|
12369
|
-
constructor(shouldAutoAllow, log) {
|
|
12706
|
+
constructor(shouldAutoAllow, log, onAutoResolved) {
|
|
12370
12707
|
this.shouldAutoAllow = shouldAutoAllow;
|
|
12371
12708
|
this.log = log;
|
|
12709
|
+
this.onAutoResolved = onAutoResolved;
|
|
12372
12710
|
}
|
|
12373
12711
|
pendingPermissions = /* @__PURE__ */ new Map();
|
|
12374
12712
|
// #0445: safety auto-decline so an unanswered ACP/Gemini permission can't wedge the turn
|
|
@@ -12390,6 +12728,10 @@ class HyphaPermissionHandler {
|
|
|
12390
12728
|
this.pendingPermissions.delete(toolCallId);
|
|
12391
12729
|
this.log(`[ACP Permission] Auto-declining ${toolName} after ${timeoutMs}ms with no response`);
|
|
12392
12730
|
p.resolve({ decision: "denied" });
|
|
12731
|
+
try {
|
|
12732
|
+
this.onAutoResolved?.(toolCallId, false);
|
|
12733
|
+
} catch {
|
|
12734
|
+
}
|
|
12393
12735
|
}
|
|
12394
12736
|
}, timeoutMs);
|
|
12395
12737
|
timer.unref?.();
|
|
@@ -12416,6 +12758,10 @@ class HyphaPermissionHandler {
|
|
|
12416
12758
|
for (const [id, pending] of this.pendingPermissions) {
|
|
12417
12759
|
if (pending.timer) clearTimeout(pending.timer);
|
|
12418
12760
|
pending.resolve({ decision: "denied" });
|
|
12761
|
+
try {
|
|
12762
|
+
this.onAutoResolved?.(id, false);
|
|
12763
|
+
} catch {
|
|
12764
|
+
}
|
|
12419
12765
|
}
|
|
12420
12766
|
this.pendingPermissions.clear();
|
|
12421
12767
|
}
|
|
@@ -12496,7 +12842,7 @@ function codexCumulativeTokens(payload) {
|
|
|
12496
12842
|
return nonCachedInput + output + Math.round(cached * 0.1);
|
|
12497
12843
|
}
|
|
12498
12844
|
|
|
12499
|
-
const PINNED_CODEX_VERSION = "0.
|
|
12845
|
+
const PINNED_CODEX_VERSION = "0.147.0";
|
|
12500
12846
|
const MIN_CODEX_STEER_VERSION = { major: 0, minor: 129, patch: 0 };
|
|
12501
12847
|
function cmpVer(a, b) {
|
|
12502
12848
|
return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
|
|
@@ -12599,6 +12945,7 @@ class CodexAppServerClient {
|
|
|
12599
12945
|
child.stderr?.on("data", (d) => this.log("[codex-app-server:stderr]", d.toString().trim()));
|
|
12600
12946
|
child.on("exit", (code) => {
|
|
12601
12947
|
const wasConnected = this.connected;
|
|
12948
|
+
if (this.process === child) this.process = null;
|
|
12602
12949
|
this.connected = false;
|
|
12603
12950
|
this.failAllPending(new Error(`codex app-server exited (code ${code})`));
|
|
12604
12951
|
this.resolvePendingTurn(true);
|
|
@@ -13510,7 +13857,7 @@ function hasKimiProvider(cfg) {
|
|
|
13510
13857
|
}
|
|
13511
13858
|
function buildKimiCatalogAddArgs(cfg) {
|
|
13512
13859
|
if (!hasKimiProvider(cfg)) return null;
|
|
13513
|
-
const args = ["provider", "catalog", "add", cfg.provider
|
|
13860
|
+
const args = ["provider", "catalog", "add", cfg.provider];
|
|
13514
13861
|
if (cfg.apiBase) args.push("--base-url", cfg.apiBase);
|
|
13515
13862
|
const defaultModel = kimiDefaultModelId(cfg);
|
|
13516
13863
|
if (defaultModel) args.push("--default-model", defaultModel);
|
|
@@ -13526,6 +13873,19 @@ function kimiProviderConfigured(configTomlText, provider) {
|
|
|
13526
13873
|
const block = nextTable < 0 ? rest : rest.slice(0, nextTable);
|
|
13527
13874
|
return /(^|\n)\s*api_key\s*=/.test(block);
|
|
13528
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
|
+
}
|
|
13529
13889
|
|
|
13530
13890
|
var kimiProvider = /*#__PURE__*/Object.freeze({
|
|
13531
13891
|
__proto__: null,
|
|
@@ -13533,7 +13893,10 @@ var kimiProvider = /*#__PURE__*/Object.freeze({
|
|
|
13533
13893
|
DEFAULT_KIMI_PROVIDER: DEFAULT_KIMI_PROVIDER,
|
|
13534
13894
|
buildKimiCatalogAddArgs: buildKimiCatalogAddArgs,
|
|
13535
13895
|
hasKimiProvider: hasKimiProvider,
|
|
13896
|
+
kimiClampModel: kimiClampModel,
|
|
13897
|
+
kimiDefaultModel: kimiDefaultModel,
|
|
13536
13898
|
kimiDefaultModelId: kimiDefaultModelId,
|
|
13899
|
+
kimiModelMenu: kimiModelMenu,
|
|
13537
13900
|
kimiProviderConfigured: kimiProviderConfigured,
|
|
13538
13901
|
kimiSpawnEnv: kimiSpawnEnv,
|
|
13539
13902
|
resolveKimiProvider: resolveKimiProvider
|
|
@@ -13547,7 +13910,7 @@ function readKimiConfig() {
|
|
|
13547
13910
|
return null;
|
|
13548
13911
|
}
|
|
13549
13912
|
}
|
|
13550
|
-
function ensureKimiProviderConfigured(env = process.env, command = "kimi", log = () => {
|
|
13913
|
+
async function ensureKimiProviderConfigured(env = process.env, command = "kimi", log = () => {
|
|
13551
13914
|
}) {
|
|
13552
13915
|
const cfg = resolveKimiProvider(env);
|
|
13553
13916
|
if (!hasKimiProvider(cfg)) return false;
|
|
@@ -13555,10 +13918,18 @@ function ensureKimiProviderConfigured(env = process.env, command = "kimi", log =
|
|
|
13555
13918
|
const args = buildKimiCatalogAddArgs(cfg);
|
|
13556
13919
|
if (!args) return false;
|
|
13557
13920
|
try {
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
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
|
+
);
|
|
13562
13933
|
});
|
|
13563
13934
|
log(`[kimi] configured provider '${cfg.provider}' in ${KIMI_CONFIG_PATH}`);
|
|
13564
13935
|
return kimiProviderConfigured(readKimiConfig(), cfg.provider);
|
|
@@ -15148,6 +15519,9 @@ function listIssues(projectRoot, opts = {}) {
|
|
|
15148
15519
|
if (opts.scope) items = items.filter((i) => i.scope === opts.scope);
|
|
15149
15520
|
return items.sort((a, b) => Number(a.id) - Number(b.id));
|
|
15150
15521
|
}
|
|
15522
|
+
function isPendingIssue(issue) {
|
|
15523
|
+
return issue.status !== "archived" && issue.status !== "paused";
|
|
15524
|
+
}
|
|
15151
15525
|
function isVisibleTo(issue, session) {
|
|
15152
15526
|
if (issue.scope === "session") return issue.session === session || issue.owner === session;
|
|
15153
15527
|
if (issue.owner === session) return true;
|
|
@@ -15317,6 +15691,47 @@ function summarize(issues) {
|
|
|
15317
15691
|
return { total: issues.length, ready: by("ready"), in_progress: by("in_progress"), paused: by("paused"), archived: by("archived") };
|
|
15318
15692
|
}
|
|
15319
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
|
+
|
|
15320
15735
|
function isWorkflowEnabled(wf) {
|
|
15321
15736
|
return wf.enabled !== false;
|
|
15322
15737
|
}
|
|
@@ -15872,12 +16287,19 @@ function loopCancelledDuringVerify(current) {
|
|
|
15872
16287
|
}
|
|
15873
16288
|
function backlogOraclePending(projectRoot, sessionId) {
|
|
15874
16289
|
const inScope = (i) => !sessionId || isVisibleTo(i, sessionId);
|
|
15875
|
-
return listIssues(projectRoot).filter(inScope).filter(
|
|
16290
|
+
return listIssues(projectRoot).filter(inScope).filter(isPendingIssue).map((i) => ({ id: i.id }));
|
|
15876
16291
|
}
|
|
15877
16292
|
function isBacklogDrainerOracle(oracle) {
|
|
15878
16293
|
if (!oracle || typeof oracle !== "string") return false;
|
|
15879
16294
|
return /(^|\s)svamp\s+issue\s+pending(\s|$)/.test(oracle.trim());
|
|
15880
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
|
+
}
|
|
15881
16303
|
function resolveLoopDurable(cfg) {
|
|
15882
16304
|
if (typeof cfg.durable === "boolean") return cfg.durable;
|
|
15883
16305
|
return isBacklogDrainerOracle(cfg.oracle);
|
|
@@ -16784,16 +17206,24 @@ async function installSkillFromMarketplace(name) {
|
|
|
16784
17206
|
const files = await collectFiles();
|
|
16785
17207
|
if (files.length === 0) throw new Error(`Skill ${name} has no files in marketplace`);
|
|
16786
17208
|
const targetDir = join(CLAUDE_SKILLS_DIR, name);
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
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;
|
|
16797
17227
|
}
|
|
16798
17228
|
}
|
|
16799
17229
|
function getBundledSkillsDir() {
|
|
@@ -17928,8 +18358,38 @@ function ensureHomeDir() {
|
|
|
17928
18358
|
mkdirSync(LOGS_DIR, { recursive: true });
|
|
17929
18359
|
}
|
|
17930
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
|
+
}
|
|
17931
18390
|
function createLogger() {
|
|
17932
18391
|
ensureHomeDir();
|
|
18392
|
+
pruneDaemonLogs();
|
|
17933
18393
|
const logFile = join(LOGS_DIR, `daemon-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.log`);
|
|
17934
18394
|
return {
|
|
17935
18395
|
logFilePath: logFile,
|
|
@@ -18209,7 +18669,7 @@ async function startDaemon(options) {
|
|
|
18209
18669
|
try {
|
|
18210
18670
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
18211
18671
|
if (!dir) return;
|
|
18212
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
18672
|
+
const { reconcileServiceLinks } = await import('./agentCommands-DdOSpUEW.mjs');
|
|
18213
18673
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
18214
18674
|
const config = readSvampConfig(configPath);
|
|
18215
18675
|
const entries = Array.from(urls.entries());
|
|
@@ -18227,7 +18687,7 @@ async function startDaemon(options) {
|
|
|
18227
18687
|
}
|
|
18228
18688
|
}
|
|
18229
18689
|
async function createExposedTunnel(spec) {
|
|
18230
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
18690
|
+
const { FrpcTunnel } = await import('./frpc-CNZJM3qC.mjs');
|
|
18231
18691
|
const tunnel = new FrpcTunnel({
|
|
18232
18692
|
name: spec.name,
|
|
18233
18693
|
ports: spec.ports,
|
|
@@ -18255,7 +18715,7 @@ async function startDaemon(options) {
|
|
|
18255
18715
|
ensureAutoInstalledCommands(logger);
|
|
18256
18716
|
(async () => {
|
|
18257
18717
|
try {
|
|
18258
|
-
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-
|
|
18718
|
+
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-CnCpensV.mjs');
|
|
18259
18719
|
beginClaudeVersionReconcile((msg) => logger.log(msg));
|
|
18260
18720
|
} catch (e) {
|
|
18261
18721
|
logger.log(`[claude-version] check failed: ${e?.message || e}`);
|
|
@@ -20572,11 +21032,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20572
21032
|
});
|
|
20573
21033
|
},
|
|
20574
21034
|
onIssue: async (params) => {
|
|
20575
|
-
const { issueRpc } = await import('./rpc-
|
|
21035
|
+
const { issueRpc } = await import('./rpc-Cx6KsBYC.mjs');
|
|
20576
21036
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
20577
21037
|
},
|
|
20578
21038
|
onWorkflow: async (params) => {
|
|
20579
|
-
const { workflowRpc } = await import('./rpc-
|
|
21039
|
+
const { workflowRpc } = await import('./rpc-CclCeDcu.mjs');
|
|
20580
21040
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
20581
21041
|
},
|
|
20582
21042
|
onRipgrep: async (args, cwd) => {
|
|
@@ -20611,6 +21071,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20611
21071
|
onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
|
|
20612
21072
|
await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
|
|
20613
21073
|
},
|
|
21074
|
+
onFilesEndpoint: () => serveManager$1.getSessionFilesEndpoint(sessionId),
|
|
20614
21075
|
onListDirectory: async (path) => {
|
|
20615
21076
|
const resolvedDir = resolve$1(directory, path || ".");
|
|
20616
21077
|
if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
|
|
@@ -20870,7 +21331,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20870
21331
|
if (!s || s.phase !== "dormant") return;
|
|
20871
21332
|
let pending = [];
|
|
20872
21333
|
try {
|
|
20873
|
-
|
|
21334
|
+
const root = resolveProjectRoot(directory);
|
|
21335
|
+
const scope = oracleScopeSessionId(typeof s.oracle === "string" ? s.oracle : void 0, sessionId);
|
|
21336
|
+
pending = backlogOraclePending(root, scope);
|
|
20874
21337
|
} catch {
|
|
20875
21338
|
return;
|
|
20876
21339
|
}
|
|
@@ -20996,7 +21459,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20996
21459
|
{
|
|
20997
21460
|
const forcedModel = agentName === "codex" ? codexForcedModel() : void 0;
|
|
20998
21461
|
const composerModel = typeof msgMeta?.model === "string" && msgMeta.model ? msgMeta.model : void 0;
|
|
20999
|
-
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;
|
|
21000
21463
|
const desiredModel = rawModel || agentConfig?.default_model || void 0;
|
|
21001
21464
|
if (desiredModel !== appliedAgentModel) {
|
|
21002
21465
|
agentBackend?.setModel?.(desiredModel);
|
|
@@ -21280,11 +21743,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21280
21743
|
});
|
|
21281
21744
|
},
|
|
21282
21745
|
onIssue: async (params) => {
|
|
21283
|
-
const { issueRpc } = await import('./rpc-
|
|
21746
|
+
const { issueRpc } = await import('./rpc-Cx6KsBYC.mjs');
|
|
21284
21747
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
21285
21748
|
},
|
|
21286
21749
|
onWorkflow: async (params) => {
|
|
21287
|
-
const { workflowRpc } = await import('./rpc-
|
|
21750
|
+
const { workflowRpc } = await import('./rpc-CclCeDcu.mjs');
|
|
21288
21751
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
21289
21752
|
},
|
|
21290
21753
|
onRipgrep: async (args, cwd) => {
|
|
@@ -21319,6 +21782,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21319
21782
|
onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
|
|
21320
21783
|
await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
|
|
21321
21784
|
},
|
|
21785
|
+
onFilesEndpoint: () => serveManager$1.getSessionFilesEndpoint(sessionId),
|
|
21322
21786
|
onListDirectory: async (path) => {
|
|
21323
21787
|
const resolvedDir = resolve$1(directory, path || ".");
|
|
21324
21788
|
if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
|
|
@@ -21418,7 +21882,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21418
21882
|
);
|
|
21419
21883
|
const checkSvampConfig = svampConfigChecker.check;
|
|
21420
21884
|
const writeSvampConfigPatchAcp = svampConfigChecker.writeConfig;
|
|
21421
|
-
const permissionHandler = new HyphaPermissionHandler(
|
|
21885
|
+
const permissionHandler = new HyphaPermissionHandler(
|
|
21886
|
+
shouldAutoAllow2,
|
|
21887
|
+
logger.log,
|
|
21888
|
+
(requestId, approved) => applyPermissionResolution(sessionService, requestId, approved)
|
|
21889
|
+
);
|
|
21422
21890
|
let agentIsoConfig;
|
|
21423
21891
|
const agentShouldIsolate = Boolean(options2.forceIsolation) || Boolean(options2.securityContext);
|
|
21424
21892
|
if (agentShouldIsolate && isolationCapabilities.preferred) {
|
|
@@ -21521,10 +21989,24 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
21521
21989
|
let kimiEnv = {};
|
|
21522
21990
|
if (agentName === "kimi") {
|
|
21523
21991
|
try {
|
|
21524
|
-
ensureKimiProviderConfigured(process.env, acpConfig.command, logger.log);
|
|
21992
|
+
await ensureKimiProviderConfigured(process.env, acpConfig.command, logger.log);
|
|
21525
21993
|
} catch {
|
|
21526
21994
|
}
|
|
21527
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
|
+
}
|
|
21528
22010
|
}
|
|
21529
22011
|
agentBackend = new AcpBackend({
|
|
21530
22012
|
agentName,
|
|
@@ -21869,6 +22351,8 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21869
22351
|
"event"
|
|
21870
22352
|
);
|
|
21871
22353
|
sessionService.sendSessionEnd();
|
|
22354
|
+
agentBackend.dispose().catch(() => {
|
|
22355
|
+
});
|
|
21872
22356
|
if (!acpPersisted) {
|
|
21873
22357
|
deleteSession(sessionId);
|
|
21874
22358
|
} else {
|
|
@@ -22247,6 +22731,27 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
22247
22731
|
writeDaemonStateFile(localState);
|
|
22248
22732
|
logger.log("Daemon state file written \u2014 daemon is up; restoring mounts/sessions in background");
|
|
22249
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}`));
|
|
22250
22755
|
const persistedSessions = loadPersistedSessions();
|
|
22251
22756
|
try {
|
|
22252
22757
|
const knownIds = new Set(persistedSessions.map((s) => s.sessionId).concat(Object.keys(loadSessionIndex())));
|
|
@@ -22285,7 +22790,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
22285
22790
|
}
|
|
22286
22791
|
if (persistedSessions.length > 0) {
|
|
22287
22792
|
try {
|
|
22288
|
-
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-
|
|
22793
|
+
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-CnCpensV.mjs');
|
|
22289
22794
|
await awaitClaudeVersionReady();
|
|
22290
22795
|
} catch {
|
|
22291
22796
|
}
|
|
@@ -22530,7 +23035,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
22530
23035
|
const PING_TIMEOUT_MS = 15e3;
|
|
22531
23036
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
22532
23037
|
const RECONNECT_JITTER_MS = 2500;
|
|
22533
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
23038
|
+
const { WorkflowScheduler } = await import('./scheduler-Wzzed1CU.mjs');
|
|
22534
23039
|
const workflowProjectRoots = () => {
|
|
22535
23040
|
const dirs = /* @__PURE__ */ new Set();
|
|
22536
23041
|
for (const s of pidToTrackedSession.values()) {
|
|
@@ -23164,4 +23669,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
23164
23669
|
writeStopMarker: writeStopMarker
|
|
23165
23670
|
});
|
|
23166
23671
|
|
|
23167
|
-
export {
|
|
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 };
|