svamp-cli 0.2.308 → 0.2.310
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adminCommands-CtecEU3a.mjs → adminCommands-BcH3OlSL.mjs} +2 -2
- package/dist/{agentCommands-DEJpgdgc.mjs → agentCommands-9GHpj3mp.mjs} +6 -6
- package/dist/{auth-BTodo5Et.mjs → auth-CuN-K8i9.mjs} +2 -2
- package/dist/{cli-Ko4XpjRo.mjs → cli-Co_GXbrT.mjs} +70 -70
- package/dist/cli.mjs +3 -3
- package/dist/{commands-B-u0fICz.mjs → commands-B0cRa6cc.mjs} +3 -3
- package/dist/{commands-D3Nu5cYE.mjs → commands-D-RsuMqs.mjs} +3 -2
- package/dist/{commands-DA7rAZqn.mjs → commands-DBiIsRQ_.mjs} +2 -2
- package/dist/{commands-DfbOYRLs.mjs → commands-DiuHHJ6r.mjs} +3 -3
- package/dist/{commands-dBiQcDJ1.mjs → commands-DrVsg0Q1.mjs} +23 -11
- package/dist/{commands-B2ARVwag.mjs → commands-DuLlH97k.mjs} +3 -3
- package/dist/{commands-DKOYCcmo.mjs → commands-Dx4S6f_G.mjs} +2 -2
- package/dist/{commands-GNplzSQX.mjs → commands-Eo0kocpm.mjs} +37 -6
- package/dist/{fleet-CXixARxe.mjs → fleet-CFpET-T5.mjs} +3 -3
- package/dist/{frpc-DV_IIYpM.mjs → frpc-DxIjZ8o_.mjs} +5 -2
- package/dist/{headlessCli-DfkgeGnQ.mjs → headlessCli-BIY-YePw.mjs} +3 -3
- package/dist/index.mjs +2 -2
- package/dist/{notifyCommands-BUE_-OCX.mjs → notifyCommands-DldLQlDU.mjs} +2 -2
- package/dist/package-BKmrvBsb.mjs +64 -0
- package/dist/{pinnedClaudeCode-BaMR97BE.mjs → pinnedClaudeCode-zDzkOTi2.mjs} +1 -1
- package/dist/{rpc-BjKmyKdA.mjs → rpc-BVw8Tv9l.mjs} +2 -2
- package/dist/{rpc-wL-45Raf.mjs → rpc-D7_Gj0Kt.mjs} +2 -2
- package/dist/{run-VQIHMUdo.mjs → run-CqO23ro_.mjs} +420 -110
- package/dist/{run-ofbo9uzA.mjs → run-DLk7NGih.mjs} +2 -2
- package/dist/{scheduler-Dia9Akaw.mjs → scheduler-q7Refevo.mjs} +5 -5
- package/dist/{serveCommands-DehmO1cO.mjs → serveCommands-BJjvhDxp.mjs} +19 -12
- package/dist/{sideband-CGiNKfNE.mjs → sideband-Dv3qNlQN.mjs} +2 -2
- package/package.json +2 -2
- package/dist/package-GdCOuQCx.mjs +0 -64
|
@@ -18,9 +18,9 @@ import os, { homedir as homedir$1, platform } from 'node:os';
|
|
|
18
18
|
import { join as join$1, extname, resolve, sep, basename, dirname } from 'node:path';
|
|
19
19
|
import { EventEmitter } from 'node:events';
|
|
20
20
|
import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
|
|
21
|
+
import { promisify as promisify$1 } from 'node:util';
|
|
21
22
|
import { createInterface } from 'node:readline';
|
|
22
23
|
import { mkdir, rm, chmod, access, mkdtemp, copyFile, writeFile, readdir, stat, readFile as readFile$1, rename as rename$1 } from 'node:fs/promises';
|
|
23
|
-
import { promisify as promisify$1 } from 'node:util';
|
|
24
24
|
import { parse, stringify } from 'yaml';
|
|
25
25
|
|
|
26
26
|
let connectToServerFn = null;
|
|
@@ -2172,6 +2172,61 @@ function killOrphanedCaddy(log) {
|
|
|
2172
2172
|
} catch {
|
|
2173
2173
|
}
|
|
2174
2174
|
}
|
|
2175
|
+
function reapOrphanedManagedProcesses(pidDir, log) {
|
|
2176
|
+
if (process.platform === "win32") return;
|
|
2177
|
+
let files;
|
|
2178
|
+
try {
|
|
2179
|
+
files = fs.readdirSync(pidDir).filter((f) => f.endsWith(".pid"));
|
|
2180
|
+
} catch {
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
if (!files.length) return;
|
|
2184
|
+
const cmdByPid = /* @__PURE__ */ new Map();
|
|
2185
|
+
try {
|
|
2186
|
+
const out = execSync("ps -axo pid=,command=", { encoding: "utf-8" });
|
|
2187
|
+
for (const line of out.split("\n")) {
|
|
2188
|
+
const m = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
2189
|
+
if (m) cmdByPid.set(m[1], m[2]);
|
|
2190
|
+
}
|
|
2191
|
+
} catch {
|
|
2192
|
+
}
|
|
2193
|
+
let reaped = 0;
|
|
2194
|
+
for (const f of files) {
|
|
2195
|
+
const full = path.join(pidDir, f);
|
|
2196
|
+
let rec = {};
|
|
2197
|
+
try {
|
|
2198
|
+
rec = JSON.parse(fs.readFileSync(full, "utf-8"));
|
|
2199
|
+
} catch {
|
|
2200
|
+
}
|
|
2201
|
+
const pid = Number(rec.pid);
|
|
2202
|
+
if (pid > 0 && pid !== process.pid) {
|
|
2203
|
+
const liveCmd = cmdByPid.get(String(pid));
|
|
2204
|
+
const base = rec.command ? path.basename(rec.command) : "";
|
|
2205
|
+
const args = Array.isArray(rec.args) ? rec.args : [];
|
|
2206
|
+
const argOk = args.length === 0 || args.some((a) => a && liveCmd?.includes(a));
|
|
2207
|
+
if (liveCmd && base && liveCmd.includes(base) && argOk) {
|
|
2208
|
+
try {
|
|
2209
|
+
process.kill(-pid, "SIGKILL");
|
|
2210
|
+
reaped++;
|
|
2211
|
+
} catch {
|
|
2212
|
+
try {
|
|
2213
|
+
process.kill(pid, "SIGKILL");
|
|
2214
|
+
reaped++;
|
|
2215
|
+
} catch {
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
log(`Reaped orphaned managed process pid=${pid} (port ${rec.port ?? "?"}) from a prior daemon crash (#0758).`);
|
|
2219
|
+
} else if (liveCmd) {
|
|
2220
|
+
log(`Managed pidfile ${f}: live pid=${pid} command does not match recorded '${rec.command} ${args.join(" ")}' \u2014 NOT reaping (likely PID reuse); dropping stale pidfile.`);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
try {
|
|
2224
|
+
fs.unlinkSync(full);
|
|
2225
|
+
} catch {
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
if (reaped) log(`Reaped ${reaped} orphaned managed process(es) on startup (#0758).`);
|
|
2229
|
+
}
|
|
2175
2230
|
const MOUNT_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
2176
2231
|
function validateMountName(name) {
|
|
2177
2232
|
if (!name || name.length > 128) {
|
|
@@ -2196,13 +2251,19 @@ class ServeManager {
|
|
|
2196
2251
|
auth = null;
|
|
2197
2252
|
/** Live child processes for managed mounts. Keyed by mount name. */
|
|
2198
2253
|
managedProcs = /* @__PURE__ */ new Map();
|
|
2254
|
+
/** #0783: per-mount 'stopping' barriers — a wake (ensureManagedRunning) awaits any pending stop
|
|
2255
|
+
* so it never spawns a replacement on a port the terminating process still holds (EADDRINUSE). */
|
|
2256
|
+
stoppingProcs = /* @__PURE__ */ new Map();
|
|
2199
2257
|
/** Single timer that scans managed mounts every 30s for idle eviction. */
|
|
2200
2258
|
idleTimer = null;
|
|
2201
2259
|
persistFile;
|
|
2260
|
+
/** #0758: dir holding per-managed-mount pidfiles so a crash-orphaned process is reapable on restart. */
|
|
2261
|
+
managedPidDir;
|
|
2202
2262
|
log;
|
|
2203
2263
|
hyphaServerUrl;
|
|
2204
2264
|
constructor(svampHome, logger, hyphaServerUrl) {
|
|
2205
2265
|
this.persistFile = path.join(svampHome, "serve-mounts.json");
|
|
2266
|
+
this.managedPidDir = path.join(svampHome, "serve-managed");
|
|
2206
2267
|
this.log = logger || ((msg) => console.log(`[SERVE] ${msg}`));
|
|
2207
2268
|
const resolvedServerUrl = hyphaServerUrl || getHyphaServerUrl();
|
|
2208
2269
|
if (!resolvedServerUrl) {
|
|
@@ -2318,10 +2379,8 @@ class ServeManager {
|
|
|
2318
2379
|
*/
|
|
2319
2380
|
listMounts(sessionId) {
|
|
2320
2381
|
const all = Array.from(this.mounts.values());
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
}
|
|
2324
|
-
return all;
|
|
2382
|
+
const filtered = sessionId ? all.filter((m) => m.sessionId === sessionId) : all;
|
|
2383
|
+
return filtered.map((m) => ({ ...m, url: this.getMountUrl(m.name) || void 0 }));
|
|
2325
2384
|
}
|
|
2326
2385
|
/** #0649: look up a single mount by name (for ownership checks before remove/replace). */
|
|
2327
2386
|
getMount(name) {
|
|
@@ -2332,10 +2391,8 @@ class ServeManager {
|
|
|
2332
2391
|
*/
|
|
2333
2392
|
getInfo() {
|
|
2334
2393
|
const running = this.proxyServer != null;
|
|
2335
|
-
const firstMount = this.mounts.values().next().value;
|
|
2336
|
-
const firstUrl = firstMount ? this.getMountUrl(firstMount.name) : null;
|
|
2337
2394
|
return {
|
|
2338
|
-
url:
|
|
2395
|
+
url: null,
|
|
2339
2396
|
port: running ? this.port : 0,
|
|
2340
2397
|
authProxyPort: running ? this.port : 0,
|
|
2341
2398
|
running,
|
|
@@ -2443,6 +2500,13 @@ class ServeManager {
|
|
|
2443
2500
|
async ensureManagedRunning(name) {
|
|
2444
2501
|
const mount = this.mounts.get(name);
|
|
2445
2502
|
if (!mount?.process) return;
|
|
2503
|
+
const pendingStop = this.stoppingProcs.get(name);
|
|
2504
|
+
if (pendingStop) {
|
|
2505
|
+
try {
|
|
2506
|
+
await pendingStop;
|
|
2507
|
+
} catch {
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2446
2510
|
const handle = this.managedProcs.get(name);
|
|
2447
2511
|
if (handle && handle.warmupPromise) {
|
|
2448
2512
|
return handle.warmupPromise;
|
|
@@ -2474,7 +2538,10 @@ class ServeManager {
|
|
|
2474
2538
|
child.on("exit", (code, signal) => {
|
|
2475
2539
|
this.log(`Managed process '${name}' exited (code=${code}, signal=${signal})`);
|
|
2476
2540
|
const h = this.managedProcs.get(name);
|
|
2477
|
-
if (h && h.child === child)
|
|
2541
|
+
if (h && h.child === child) {
|
|
2542
|
+
this.managedProcs.delete(name);
|
|
2543
|
+
this.removeManagedPidFile(name);
|
|
2544
|
+
}
|
|
2478
2545
|
});
|
|
2479
2546
|
const warmupPath = cfg.warmupPath ?? "/";
|
|
2480
2547
|
const warmupTimeoutMs = cfg.warmupTimeoutMs ?? 3e4;
|
|
@@ -2500,6 +2567,7 @@ class ServeManager {
|
|
|
2500
2567
|
warmupPromise
|
|
2501
2568
|
};
|
|
2502
2569
|
this.managedProcs.set(name, newHandle);
|
|
2570
|
+
this.writeManagedPidFile(name, child, cfg);
|
|
2503
2571
|
this.log(`Managed process '${name}' starting: ${cfg.command} ${(cfg.args ?? []).join(" ")} (port ${cfg.port})`);
|
|
2504
2572
|
return warmupPromise;
|
|
2505
2573
|
}
|
|
@@ -2509,6 +2577,25 @@ class ServeManager {
|
|
|
2509
2577
|
* that a direct child.kill() would orphan, leaving the port bound. Falls back to a direct
|
|
2510
2578
|
* child.kill() if the group signal fails (e.g. pid already gone).
|
|
2511
2579
|
*/
|
|
2580
|
+
// ── #0758: managed-process pidfiles (crash-orphan reaping) ──────────────
|
|
2581
|
+
managedPidFile(name) {
|
|
2582
|
+
const safe = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
2583
|
+
return path.join(this.managedPidDir, `${safe}.pid`);
|
|
2584
|
+
}
|
|
2585
|
+
writeManagedPidFile(name, child, cfg) {
|
|
2586
|
+
try {
|
|
2587
|
+
fs.mkdirSync(this.managedPidDir, { recursive: true, mode: 448 });
|
|
2588
|
+
const rec = { pid: child.pid ?? 0, command: cfg.command, args: cfg.args ?? [], port: cfg.port, startedAt: Date.now() };
|
|
2589
|
+
fs.writeFileSync(this.managedPidFile(name), JSON.stringify(rec), { mode: 384 });
|
|
2590
|
+
} catch {
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
removeManagedPidFile(name) {
|
|
2594
|
+
try {
|
|
2595
|
+
fs.unlinkSync(this.managedPidFile(name));
|
|
2596
|
+
} catch {
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2512
2599
|
killManagedTree(child, signal) {
|
|
2513
2600
|
const pid = child.pid;
|
|
2514
2601
|
if (pid && pid > 0) {
|
|
@@ -2558,18 +2645,30 @@ class ServeManager {
|
|
|
2558
2645
|
const h = this.managedProcs.get(name);
|
|
2559
2646
|
if (!h) return;
|
|
2560
2647
|
this.managedProcs.delete(name);
|
|
2648
|
+
this.removeManagedPidFile(name);
|
|
2561
2649
|
const child = h.child;
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2650
|
+
const done = (async () => {
|
|
2651
|
+
if (child.exitCode !== null) return;
|
|
2652
|
+
this.killManagedTree(child, "SIGTERM");
|
|
2653
|
+
await new Promise((resolve) => {
|
|
2654
|
+
const onExit = () => {
|
|
2655
|
+
clearTimeout(t);
|
|
2656
|
+
clearTimeout(cap);
|
|
2657
|
+
resolve();
|
|
2658
|
+
};
|
|
2659
|
+
const t = setTimeout(() => {
|
|
2660
|
+
this.killManagedTree(child, "SIGKILL");
|
|
2661
|
+
}, 5e3);
|
|
2662
|
+
const cap = setTimeout(onExit, 8e3);
|
|
2663
|
+
child.once("exit", onExit);
|
|
2571
2664
|
});
|
|
2572
|
-
});
|
|
2665
|
+
})();
|
|
2666
|
+
this.stoppingProcs.set(name, done);
|
|
2667
|
+
try {
|
|
2668
|
+
await done;
|
|
2669
|
+
} finally {
|
|
2670
|
+
if (this.stoppingProcs.get(name) === done) this.stoppingProcs.delete(name);
|
|
2671
|
+
}
|
|
2573
2672
|
this.log(`Managed process '${name}' stopped`);
|
|
2574
2673
|
}
|
|
2575
2674
|
/** Idle eviction loop — stops processes that have been idle longer than configured. */
|
|
@@ -2631,6 +2730,7 @@ class ServeManager {
|
|
|
2631
2730
|
async ensureRunning() {
|
|
2632
2731
|
if (this.proxyServer) return;
|
|
2633
2732
|
killOrphanedCaddy((m) => this.log(m));
|
|
2733
|
+
reapOrphanedManagedProcesses(this.managedPidDir, (m) => this.log(m));
|
|
2634
2734
|
this.port = await tryReservePort(this.persistedPort);
|
|
2635
2735
|
if (this.persistedPort && this.port !== this.persistedPort) {
|
|
2636
2736
|
this.log(`\u26A0 Previous serve port ${this.persistedPort} unavailable \u2014 using ${this.port}. Downstream configs referencing the old port will need updating.`);
|
|
@@ -3047,7 +3147,7 @@ Connection: close\r
|
|
|
3047
3147
|
const mount = this.mounts.get(mountName);
|
|
3048
3148
|
const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
|
|
3049
3149
|
try {
|
|
3050
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
3150
|
+
const { FrpcTunnel } = await import('./frpc-DxIjZ8o_.mjs');
|
|
3051
3151
|
let tunnel;
|
|
3052
3152
|
tunnel = new FrpcTunnel({
|
|
3053
3153
|
name: tunnelName,
|
|
@@ -3974,6 +4074,27 @@ function cronMatches(expr, date) {
|
|
|
3974
4074
|
if (c.domRestricted && c.dowRestricted) return domOk || dowOk;
|
|
3975
4075
|
return domOk && dowOk;
|
|
3976
4076
|
}
|
|
4077
|
+
function inZone(date, tz) {
|
|
4078
|
+
if (!tz) return date;
|
|
4079
|
+
try {
|
|
4080
|
+
const p = new Intl.DateTimeFormat("en-US", {
|
|
4081
|
+
timeZone: tz,
|
|
4082
|
+
hour12: false,
|
|
4083
|
+
year: "numeric",
|
|
4084
|
+
month: "2-digit",
|
|
4085
|
+
day: "2-digit",
|
|
4086
|
+
hour: "2-digit",
|
|
4087
|
+
minute: "2-digit"
|
|
4088
|
+
}).formatToParts(date).reduce((o, x) => {
|
|
4089
|
+
o[x.type] = x.value;
|
|
4090
|
+
return o;
|
|
4091
|
+
}, {});
|
|
4092
|
+
const midnight24 = p.hour === "24";
|
|
4093
|
+
return new Date(+p.year, +p.month - 1, +p.day + (midnight24 ? 1 : 0), midnight24 ? 0 : +p.hour, +p.minute);
|
|
4094
|
+
} catch {
|
|
4095
|
+
return date;
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
3977
4098
|
function resolvePath(ctx, path) {
|
|
3978
4099
|
return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], ctx);
|
|
3979
4100
|
}
|
|
@@ -3992,8 +4113,8 @@ function sleepSync$1(ms) {
|
|
|
3992
4113
|
}
|
|
3993
4114
|
}
|
|
3994
4115
|
function withFileLock(lockPath, fn, opts) {
|
|
3995
|
-
const deadlineMs = 50;
|
|
3996
|
-
const staleMs = 5e3;
|
|
4116
|
+
const deadlineMs = opts?.deadlineMs ?? 50;
|
|
4117
|
+
const staleMs = opts?.staleMs ?? 5e3;
|
|
3997
4118
|
const deadline = Date.now() + deadlineMs;
|
|
3998
4119
|
let held = false;
|
|
3999
4120
|
while (Date.now() < deadline) {
|
|
@@ -4277,12 +4398,14 @@ class ChannelStore {
|
|
|
4277
4398
|
return this._writeChannel(channel);
|
|
4278
4399
|
}
|
|
4279
4400
|
remove(id) {
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4401
|
+
return withFileLock(this._lock(id), () => {
|
|
4402
|
+
const p = this._path(id);
|
|
4403
|
+
if (existsSync(p)) {
|
|
4404
|
+
rmSync$1(p);
|
|
4405
|
+
return true;
|
|
4406
|
+
}
|
|
4407
|
+
return false;
|
|
4408
|
+
});
|
|
4286
4409
|
}
|
|
4287
4410
|
// #0679: setEnabled/recordCall/addCaller are read-modify-write mutators — multiple ChannelStore
|
|
4288
4411
|
// instances (one per session) point at the same .svamp/channels/<id>.json, so without
|
|
@@ -4711,10 +4834,14 @@ class ChannelOutbox {
|
|
|
4711
4834
|
/** Append a reply addressed to `to`. Assigns seq + ts, persists, and wakes waiters. */
|
|
4712
4835
|
append(channelId, r) {
|
|
4713
4836
|
this.reload();
|
|
4714
|
-
const seq =
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4837
|
+
const seq = withFileLock(`${this.seqFile}.lock`, () => {
|
|
4838
|
+
this._loadHighWater();
|
|
4839
|
+
const s = Math.max(this.highWater.get(channelId) || 0, this.seqByChannel.get(channelId) || 0) + 1;
|
|
4840
|
+
this.seqByChannel.set(channelId, s);
|
|
4841
|
+
this.highWater.set(channelId, s);
|
|
4842
|
+
this._persistHighWater();
|
|
4843
|
+
return s;
|
|
4844
|
+
});
|
|
4718
4845
|
const reply = { seq, ts: Date.now(), to: r.to, body: r.body, ...r.correlationId ? { correlationId: r.correlationId } : {} };
|
|
4719
4846
|
const arr = this.byChannel.get(channelId) || [];
|
|
4720
4847
|
arr.push(reply);
|
|
@@ -4796,10 +4923,16 @@ class ChannelOutbox {
|
|
|
4796
4923
|
this.emitter.on(channelId, onReply);
|
|
4797
4924
|
});
|
|
4798
4925
|
}
|
|
4799
|
-
/**
|
|
4800
|
-
|
|
4926
|
+
/**
|
|
4927
|
+
* Push subscription for SSE: calls onReply for each new matching reply.
|
|
4928
|
+
* Same key rule as since()/wait(): correlationId-first (knowing the unguessable reply
|
|
4929
|
+
* key proves ownership of that conversation), identity (`to`) only as the fallback.
|
|
4930
|
+
* Without this, several callers sharing a `to` (e.g. caller-supplied/'anonymous'
|
|
4931
|
+
* senders) would receive each other's replies — the cross-caller leak class of #0786.
|
|
4932
|
+
*/
|
|
4933
|
+
subscribe(channelId, to, onReply, correlationId) {
|
|
4801
4934
|
const handler = (r) => {
|
|
4802
|
-
if (r.to === to) onReply(r);
|
|
4935
|
+
if (correlationId ? r.correlationId === correlationId : r.to === to) onReply(r);
|
|
4803
4936
|
};
|
|
4804
4937
|
this.emitter.on(channelId, handler);
|
|
4805
4938
|
return () => this.emitter.off(channelId, handler);
|
|
@@ -4841,6 +4974,7 @@ const SESSION_METHOD_MIN_ROLE = {
|
|
|
4841
4974
|
archiveSession: "admin",
|
|
4842
4975
|
readFile: "admin",
|
|
4843
4976
|
writeFile: "admin",
|
|
4977
|
+
writeFileChunk: "admin",
|
|
4844
4978
|
listDirectory: "admin",
|
|
4845
4979
|
bash: "admin",
|
|
4846
4980
|
issue: "admin",
|
|
@@ -4889,6 +5023,17 @@ const SESSION_VIEW_METHODS = /* @__PURE__ */ new Set([
|
|
|
4889
5023
|
"reregister",
|
|
4890
5024
|
"recordMeetingActivity"
|
|
4891
5025
|
]);
|
|
5026
|
+
const MACHINE_MEMBER_READABLE = /* @__PURE__ */ new Set([
|
|
5027
|
+
"getMetadata",
|
|
5028
|
+
"getAgentState",
|
|
5029
|
+
"getActivityState",
|
|
5030
|
+
"getMessages",
|
|
5031
|
+
"getLatestMessages",
|
|
5032
|
+
"getMessageCount",
|
|
5033
|
+
"getSharing",
|
|
5034
|
+
"listChannels",
|
|
5035
|
+
"registerListener"
|
|
5036
|
+
]);
|
|
4892
5037
|
function sessionMethodMinRole(method) {
|
|
4893
5038
|
const mapped = SESSION_METHOD_MIN_ROLE[method];
|
|
4894
5039
|
if (mapped) return mapped;
|
|
@@ -5231,11 +5376,12 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5231
5376
|
const rpc = handlers.getSessionRPCHandlers?.(sid);
|
|
5232
5377
|
if (!rpc) continue;
|
|
5233
5378
|
if (!hasMachineAccess && !await hasExplicitSessionAccess(rpc, "view", context)) continue;
|
|
5379
|
+
const readCtx = hasMachineAccess ? void 0 : context;
|
|
5234
5380
|
try {
|
|
5235
5381
|
const [metaResult, stateResult, activity] = await Promise.all([
|
|
5236
|
-
rpc.getMetadata(
|
|
5237
|
-
rpc.getAgentState(
|
|
5238
|
-
rpc.getActivityState(
|
|
5382
|
+
rpc.getMetadata(readCtx),
|
|
5383
|
+
rpc.getAgentState(readCtx),
|
|
5384
|
+
rpc.getActivityState(readCtx).catch(() => ({
|
|
5239
5385
|
active: false,
|
|
5240
5386
|
thinking: false,
|
|
5241
5387
|
time: Date.now()
|
|
@@ -5251,7 +5397,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5251
5397
|
thinking: activity.thinking ?? false,
|
|
5252
5398
|
activeAt: activity.time || Date.now()
|
|
5253
5399
|
});
|
|
5254
|
-
} catch {
|
|
5400
|
+
} catch (err) {
|
|
5401
|
+
console.debug(
|
|
5402
|
+
`[HYPHA MACHINE] getSessions: skipped session ${sid} (${hasMachineAccess ? "machine-access" : "session-shared"}): ${err?.message ?? err}`
|
|
5403
|
+
);
|
|
5255
5404
|
}
|
|
5256
5405
|
}
|
|
5257
5406
|
return sessions;
|
|
@@ -5271,8 +5420,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5271
5420
|
throw new Error(`Session ${sessionId} not found on this machine`);
|
|
5272
5421
|
}
|
|
5273
5422
|
const requiredRole = sessionMethodMinRole(method);
|
|
5423
|
+
let machineAuthorized = false;
|
|
5274
5424
|
try {
|
|
5275
5425
|
authorizeRequest(context, currentMetadata.sharing, requiredRole);
|
|
5426
|
+
machineAuthorized = true;
|
|
5276
5427
|
} catch (machineErr) {
|
|
5277
5428
|
if (!await hasExplicitSessionAccess(rpc, requiredRole, context)) throw machineErr;
|
|
5278
5429
|
}
|
|
@@ -5280,9 +5431,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5280
5431
|
if (typeof handler !== "function") {
|
|
5281
5432
|
throw new Error(`Unknown session method: ${method}`);
|
|
5282
5433
|
}
|
|
5434
|
+
const dispatchCtx = machineAuthorized && MACHINE_MEMBER_READABLE.has(method) ? void 0 : context;
|
|
5283
5435
|
const paramNames = getParamNames(handler);
|
|
5284
5436
|
const callArgs = paramNames.map(
|
|
5285
|
-
(name) => name === "context" ?
|
|
5437
|
+
(name) => name === "context" ? dispatchCtx : kwargs?.[name] ?? void 0
|
|
5286
5438
|
);
|
|
5287
5439
|
return await handler(...callArgs);
|
|
5288
5440
|
},
|
|
@@ -5296,14 +5448,16 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5296
5448
|
if (!rpc) {
|
|
5297
5449
|
throw new Error(`Session ${sessionId} not found on this machine`);
|
|
5298
5450
|
}
|
|
5451
|
+
let machineAuthorized = false;
|
|
5299
5452
|
try {
|
|
5300
5453
|
authorizeRequest(context, currentMetadata.sharing, "view");
|
|
5454
|
+
machineAuthorized = true;
|
|
5301
5455
|
} catch (machineErr) {
|
|
5302
5456
|
if (!await hasExplicitSessionAccess(rpc, "view", context)) {
|
|
5303
5457
|
throw machineErr;
|
|
5304
5458
|
}
|
|
5305
5459
|
}
|
|
5306
|
-
return await rpc.registerListener(callback, context);
|
|
5460
|
+
return await rpc.registerListener(callback, machineAuthorized ? void 0 : context);
|
|
5307
5461
|
},
|
|
5308
5462
|
// Spawn a new session
|
|
5309
5463
|
spawnSession: async (options, context) => {
|
|
@@ -5679,9 +5833,11 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5679
5833
|
const { exec } = await import('child_process');
|
|
5680
5834
|
const { homedir } = await import('os');
|
|
5681
5835
|
return new Promise((resolve) => {
|
|
5682
|
-
exec(command, { cwd: cwd || homedir(), timeout: 3e4, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
5836
|
+
exec(command, { cwd: cwd || homedir(), timeout: 3e4, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
5683
5837
|
if (err) {
|
|
5684
|
-
|
|
5838
|
+
const enobufs = err.code === "ENOBUFS";
|
|
5839
|
+
const errText = enobufs ? `output exceeded the 16 MB buffer and was truncated (the command may have completed): ${stderr || err.message}` : stderr || err.message;
|
|
5840
|
+
resolve({ success: false, stdout: stdout || "", stderr: errText, exitCode: err.code ?? 1 });
|
|
5685
5841
|
} else {
|
|
5686
5842
|
resolve({ success: true, stdout, stderr: stderr || "", exitCode: 0 });
|
|
5687
5843
|
}
|
|
@@ -5692,6 +5848,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5692
5848
|
/** Start a new terminal PTY session. Returns { sessionId, cols, rows }. */
|
|
5693
5849
|
terminalStart: async (params = {}, context) => {
|
|
5694
5850
|
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
5851
|
+
const terminalClient = context?.from;
|
|
5695
5852
|
const pty = await getPtyModule();
|
|
5696
5853
|
const { homedir: getHomedir } = await import('os');
|
|
5697
5854
|
const cols = params.cols || 80;
|
|
@@ -5746,7 +5903,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5746
5903
|
server.emit({
|
|
5747
5904
|
type: "svamp:terminal-output",
|
|
5748
5905
|
data: { type: "output", content: batch, sessionId },
|
|
5749
|
-
to: "*"
|
|
5906
|
+
to: terminalClient ?? "*"
|
|
5750
5907
|
});
|
|
5751
5908
|
} catch {
|
|
5752
5909
|
}
|
|
@@ -5765,7 +5922,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
5765
5922
|
server.emit({
|
|
5766
5923
|
type: "svamp:terminal-output",
|
|
5767
5924
|
data: { type: "exit", content: "", sessionId, exitCode, signal },
|
|
5768
|
-
to: "*"
|
|
5925
|
+
to: terminalClient ?? "*"
|
|
5769
5926
|
});
|
|
5770
5927
|
} catch {
|
|
5771
5928
|
}
|
|
@@ -6123,7 +6280,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
6123
6280
|
const tunnels = handlers.tunnels;
|
|
6124
6281
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
6125
6282
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
6126
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
6283
|
+
const { FrpcTunnel } = await import('./frpc-DxIjZ8o_.mjs');
|
|
6127
6284
|
const tunnel = new FrpcTunnel({
|
|
6128
6285
|
name: params.name,
|
|
6129
6286
|
ports: params.ports,
|
|
@@ -6290,8 +6447,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
6290
6447
|
const info = sm.getInfo();
|
|
6291
6448
|
const isAdmin = serveCallerTrusted(context);
|
|
6292
6449
|
const mounts = (info.mounts || []).map((m) => sanitizeMountForRole(m, isAdmin));
|
|
6293
|
-
|
|
6294
|
-
return { ...info, url: topUrl, mounts };
|
|
6450
|
+
return { ...info, url: null, mounts };
|
|
6295
6451
|
},
|
|
6296
6452
|
/**
|
|
6297
6453
|
* Aggregate frpc tunnel health for all serve mounts.
|
|
@@ -6617,7 +6773,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6617
6773
|
}
|
|
6618
6774
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
6619
6775
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
6620
|
-
const { toolsForRole } = await import('./sideband-
|
|
6776
|
+
const { toolsForRole } = await import('./sideband-Dv3qNlQN.mjs');
|
|
6621
6777
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
6622
6778
|
return fmt(r2);
|
|
6623
6779
|
}
|
|
@@ -6722,7 +6878,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6722
6878
|
return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
|
|
6723
6879
|
}
|
|
6724
6880
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
6725
|
-
const { queryCore } = await import('./commands-
|
|
6881
|
+
const { queryCore } = await import('./commands-D-RsuMqs.mjs');
|
|
6726
6882
|
const timeout = c.reply?.timeout_sec || 120;
|
|
6727
6883
|
let result;
|
|
6728
6884
|
try {
|
|
@@ -7232,9 +7388,17 @@ function getRateLimitRetryConfig() {
|
|
|
7232
7388
|
return {
|
|
7233
7389
|
maxRetries: envInt("SVAMP_RATELIMIT_MAX_RETRIES", 200),
|
|
7234
7390
|
baseMs: envInt("SVAMP_RATELIMIT_BASE_MS", 5e3),
|
|
7235
|
-
capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4)
|
|
7391
|
+
capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4),
|
|
7392
|
+
maxElapsedMs: envInt("SVAMP_RATELIMIT_MAX_ELAPSED_MS", 36e5)
|
|
7236
7393
|
};
|
|
7237
7394
|
}
|
|
7395
|
+
function retryStreakExhausted(attempts, streakStartedAtMs, nowMs, cfg = getRateLimitRetryConfig()) {
|
|
7396
|
+
if (attempts >= cfg.maxRetries) return { exhausted: true, reason: "count" };
|
|
7397
|
+
if (cfg.maxElapsedMs > 0 && streakStartedAtMs && streakStartedAtMs > 0 && nowMs - streakStartedAtMs >= cfg.maxElapsedMs) {
|
|
7398
|
+
return { exhausted: true, reason: "elapsed" };
|
|
7399
|
+
}
|
|
7400
|
+
return { exhausted: false };
|
|
7401
|
+
}
|
|
7238
7402
|
function computeRetryDelayMs(attempt, cfg = getRateLimitRetryConfig(), rng = Math.random) {
|
|
7239
7403
|
const a = Math.max(0, Math.floor(attempt));
|
|
7240
7404
|
const expo = Math.min(a, 30);
|
|
@@ -7413,6 +7577,9 @@ async function deletePairingArtifact(am, artifactId, log) {
|
|
|
7413
7577
|
}
|
|
7414
7578
|
}
|
|
7415
7579
|
async function sweepExpiredPairings(am, svampHome, log) {
|
|
7580
|
+
return withMintLock("*sweep*", () => _sweepExpiredPairingsUnlocked(am, svampHome, log));
|
|
7581
|
+
}
|
|
7582
|
+
async function _sweepExpiredPairingsUnlocked(am, svampHome, log) {
|
|
7416
7583
|
const now = Date.now();
|
|
7417
7584
|
const all = loadPairings(svampHome);
|
|
7418
7585
|
const expired = all.filter((p) => p.expiresAt <= now);
|
|
@@ -7420,18 +7587,40 @@ async function sweepExpiredPairings(am, svampHome, log) {
|
|
|
7420
7587
|
for (const p of expired) await deletePairingArtifact(am, p.artifactId, log);
|
|
7421
7588
|
savePairings(all.filter((p) => p.expiresAt > now), svampHome);
|
|
7422
7589
|
}
|
|
7590
|
+
let _lastSweepAt = 0;
|
|
7591
|
+
async function sweepExpiredPairingsThrottled(am, svampHome, log, minIntervalMs = 5 * 6e4) {
|
|
7592
|
+
const now = Date.now();
|
|
7593
|
+
if (now - _lastSweepAt < minIntervalMs) return;
|
|
7594
|
+
_lastSweepAt = now;
|
|
7595
|
+
await sweepExpiredPairings(am, svampHome, log);
|
|
7596
|
+
}
|
|
7597
|
+
let _pairingChain = Promise.resolve();
|
|
7598
|
+
function withMintLock(_session, fn) {
|
|
7599
|
+
const prior = _pairingChain.catch(() => {
|
|
7600
|
+
});
|
|
7601
|
+
const result = prior.then(fn);
|
|
7602
|
+
_pairingChain = result.catch(() => {
|
|
7603
|
+
});
|
|
7604
|
+
return result;
|
|
7605
|
+
}
|
|
7423
7606
|
async function burnSessionPairings(am, session, svampHome, log) {
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7607
|
+
return withMintLock(session, async () => {
|
|
7608
|
+
const all = loadPairings(svampHome);
|
|
7609
|
+
const mine = all.filter((p) => p.session === session);
|
|
7610
|
+
if (mine.length === 0) return;
|
|
7611
|
+
for (const p of mine) await deletePairingArtifact(am, p.artifactId, log);
|
|
7612
|
+
savePairings(all.filter((p) => p.session !== session), svampHome);
|
|
7613
|
+
});
|
|
7429
7614
|
}
|
|
7430
7615
|
async function mintPairingCode(opts) {
|
|
7431
|
-
const {
|
|
7616
|
+
const { session} = opts;
|
|
7432
7617
|
const resolver = (opts.resolverBase || "https://hypha.aicell.io").replace(/\/$/, "");
|
|
7433
7618
|
const ttlMs = opts.ttlMs && opts.ttlMs > 0 ? opts.ttlMs : DEFAULT_PAIRING_TTL_MS;
|
|
7434
|
-
|
|
7619
|
+
return withMintLock(session, () => _mintPairingCodeLocked(opts, resolver, ttlMs));
|
|
7620
|
+
}
|
|
7621
|
+
async function _mintPairingCodeLocked(opts, resolver, ttlMs) {
|
|
7622
|
+
const { am, session, payload, log } = opts;
|
|
7623
|
+
await _sweepExpiredPairingsUnlocked(am, opts.svampHome, log).catch(() => {
|
|
7435
7624
|
});
|
|
7436
7625
|
const now = Date.now();
|
|
7437
7626
|
const existing = loadPairings(opts.svampHome).find((p) => p.session === session && p.expiresAt > now + 3e4);
|
|
@@ -8072,6 +8261,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8072
8261
|
const channelOutbox = new ChannelOutbox(initialMetadata.path);
|
|
8073
8262
|
const outpostCoordinator = new OutpostCoordinator();
|
|
8074
8263
|
outpostCoordinator.startSweeper();
|
|
8264
|
+
void (async () => {
|
|
8265
|
+
try {
|
|
8266
|
+
const am = await server.getService("public/artifact-manager");
|
|
8267
|
+
if (am) await sweepExpiredPairingsThrottled(am, void 0, (m) => console.log(m));
|
|
8268
|
+
} catch {
|
|
8269
|
+
}
|
|
8270
|
+
})();
|
|
8075
8271
|
const outpostProjectDir = () => metadata.path || process.cwd();
|
|
8076
8272
|
const announceOutpost = (conn) => {
|
|
8077
8273
|
const who = conn.label || conn.machine;
|
|
@@ -8720,7 +8916,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8720
8916
|
const ownerCtx = ownerEmail ? { user: { email: ownerEmail, id: ownerEmail } } : void 0;
|
|
8721
8917
|
try {
|
|
8722
8918
|
if (c.system && c.action?.kind !== "agent") {
|
|
8723
|
-
const role = ownerEmail && r.sender.name.toLowerCase() === ownerEmail.toLowerCase() ? "owner" : "collaborator";
|
|
8919
|
+
const role = r.sender.verified && ownerEmail && r.sender.name.toLowerCase() === ownerEmail.toLowerCase() ? "owner" : "collaborator";
|
|
8724
8920
|
const envelope = renderMessage(c, {
|
|
8725
8921
|
sender: r.sender,
|
|
8726
8922
|
body: { message: params.message },
|
|
@@ -8847,7 +9043,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8847
9043
|
});
|
|
8848
9044
|
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
8849
9045
|
const cursor = Math.max(0, Number(params.cursor) || 0);
|
|
8850
|
-
const
|
|
9046
|
+
const waitSecRaw = Number(params.wait);
|
|
9047
|
+
const waitSec = Number.isFinite(waitSecRaw) ? waitSecRaw : 25;
|
|
9048
|
+
const waitMs = Math.min(Math.max(0, waitSec * 1e3), 6e4);
|
|
8851
9049
|
const out = await channelOutbox.wait(c.id, cursor, r.sender.name, waitMs, params.correlationId);
|
|
8852
9050
|
return { ok: true, replies: out.replies, cursor: out.cursor };
|
|
8853
9051
|
},
|
|
@@ -8958,6 +9156,12 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8958
9156
|
await callbacks.onWriteFile(path, content);
|
|
8959
9157
|
return { success: true };
|
|
8960
9158
|
},
|
|
9159
|
+
writeFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast, context) => {
|
|
9160
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
9161
|
+
if (!callbacks.onWriteFileChunk) throw new Error("writeFileChunk not supported");
|
|
9162
|
+
await callbacks.onWriteFileChunk(path, content, uploadId, chunkIndex, totalChunks, isLast);
|
|
9163
|
+
return { success: true };
|
|
9164
|
+
},
|
|
8961
9165
|
listDirectory: async (path, context) => {
|
|
8962
9166
|
authorizeRequest(context, metadata.sharing, "admin");
|
|
8963
9167
|
if (!callbacks.onListDirectory) throw new Error("listDirectory not supported");
|
|
@@ -12291,15 +12495,30 @@ function codexAppServerAvailable() {
|
|
|
12291
12495
|
return null;
|
|
12292
12496
|
}
|
|
12293
12497
|
}
|
|
12498
|
+
const _execFileAsync = promisify$1(execFile$1);
|
|
12294
12499
|
let _steerSupport = null;
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12500
|
+
let _steerProbedAt = 0;
|
|
12501
|
+
let _steerProbe = null;
|
|
12502
|
+
const _STEER_TTL_MS = 3e4;
|
|
12503
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
|
|
12504
|
+
function _steerFresh() {
|
|
12505
|
+
return _steerSupport !== null && Date.now() - _steerProbedAt < _STEER_TTL_MS;
|
|
12506
|
+
}
|
|
12507
|
+
async function codexSupportsSteerInstalledAsync() {
|
|
12508
|
+
if (_steerFresh()) return _steerSupport;
|
|
12509
|
+
if (_steerProbe) return _steerProbe;
|
|
12510
|
+
_steerProbe = (async () => {
|
|
12511
|
+
try {
|
|
12512
|
+
const { stdout } = await _execFileAsync("codex", ["--version"], { encoding: "utf8" });
|
|
12513
|
+
_steerSupport = codexSupportsSteer(parseCodexVersion(stdout.trim()));
|
|
12514
|
+
} catch {
|
|
12515
|
+
_steerSupport = false;
|
|
12516
|
+
}
|
|
12517
|
+
_steerProbedAt = Date.now();
|
|
12518
|
+
_steerProbe = null;
|
|
12519
|
+
return _steerSupport;
|
|
12520
|
+
})();
|
|
12521
|
+
return _steerProbe;
|
|
12303
12522
|
}
|
|
12304
12523
|
class CodexAppServerClient {
|
|
12305
12524
|
constructor(opts) {
|
|
@@ -12474,7 +12693,7 @@ class CodexAppServerClient {
|
|
|
12474
12693
|
*/
|
|
12475
12694
|
async injectInput(prompt, _o) {
|
|
12476
12695
|
if (!this._threadId || !this.pendingTurn || !this._turnId) return false;
|
|
12477
|
-
if (!
|
|
12696
|
+
if (!await codexSupportsSteerInstalledAsync()) return false;
|
|
12478
12697
|
const params = {
|
|
12479
12698
|
threadId: this._threadId,
|
|
12480
12699
|
expectedTurnId: this._turnId,
|
|
@@ -12529,17 +12748,16 @@ class CodexAppServerClient {
|
|
|
12529
12748
|
// ── JSON-RPC plumbing ──────────────────────────────────────────────────────
|
|
12530
12749
|
request(method, params, timeoutMs) {
|
|
12531
12750
|
const id = this.nextId++;
|
|
12751
|
+
const effTimeout = timeoutMs ?? this.opts.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
12532
12752
|
return new Promise((resolve, reject) => {
|
|
12533
12753
|
this.pending.set(id, { resolve, reject, method });
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
12539
|
-
|
|
12540
|
-
|
|
12541
|
-
t.unref?.();
|
|
12542
|
-
}
|
|
12754
|
+
const t = setTimeout(() => {
|
|
12755
|
+
if (this.pending.has(id)) {
|
|
12756
|
+
this.pending.delete(id);
|
|
12757
|
+
reject(new Error(`codex ${method} timed out after ${effTimeout}ms`));
|
|
12758
|
+
}
|
|
12759
|
+
}, effTimeout);
|
|
12760
|
+
t.unref?.();
|
|
12543
12761
|
this.write({ jsonrpc: "2.0", id, method, params });
|
|
12544
12762
|
});
|
|
12545
12763
|
}
|
|
@@ -14839,8 +15057,8 @@ function isWorkflowEnabled(wf) {
|
|
|
14839
15057
|
function workflowSteps(wf) {
|
|
14840
15058
|
return Object.values(wf.jobs || {}).flatMap((j) => j?.steps || []);
|
|
14841
15059
|
}
|
|
14842
|
-
function
|
|
14843
|
-
return (wf.on?.schedule || []).
|
|
15060
|
+
function workflowSchedules(wf) {
|
|
15061
|
+
return (wf.on?.schedule || []).filter((s) => !!s?.cron);
|
|
14844
15062
|
}
|
|
14845
15063
|
function workflowsDir(projectRoot) {
|
|
14846
15064
|
return join$1(projectRoot, ".svamp", "workflows");
|
|
@@ -14860,7 +15078,11 @@ function normalizeOn(on) {
|
|
|
14860
15078
|
}
|
|
14861
15079
|
const out = {};
|
|
14862
15080
|
if (Array.isArray(on.schedule)) {
|
|
14863
|
-
const sched = on.schedule.map((s) =>
|
|
15081
|
+
const sched = on.schedule.map((s) => {
|
|
15082
|
+
const entry = { cron: String(s?.cron ?? s ?? "") };
|
|
15083
|
+
if (s && typeof s === "object" && s.tz != null && String(s.tz).trim()) entry.tz = String(s.tz).trim();
|
|
15084
|
+
return entry;
|
|
15085
|
+
}).filter((s) => s.cron);
|
|
14864
15086
|
if (sched.length) out.schedule = sched;
|
|
14865
15087
|
} else if (typeof on.schedule === "string" && on.schedule.trim()) {
|
|
14866
15088
|
out.schedule = [{ cron: on.schedule.trim() }];
|
|
@@ -14908,7 +15130,7 @@ function serializeWorkflow(wf) {
|
|
|
14908
15130
|
if (wf.session) clean.session = wf.session;
|
|
14909
15131
|
if (wf.enabled === false) clean.enabled = false;
|
|
14910
15132
|
const on = {};
|
|
14911
|
-
if (wf.on?.schedule?.length) on.schedule = wf.on.schedule.map((s) =>
|
|
15133
|
+
if (wf.on?.schedule?.length) on.schedule = wf.on.schedule.map((s) => s.tz ? { cron: s.cron, tz: s.tz } : { cron: s.cron });
|
|
14912
15134
|
if (wf.on?.workflow_dispatch) on.workflow_dispatch = {};
|
|
14913
15135
|
if (wf.on?.channel) on.channel = wf.on.channel;
|
|
14914
15136
|
if (wf.on?.issue?.length) on.issue = wf.on.issue;
|
|
@@ -15022,6 +15244,7 @@ function runsFile(projectRoot, workflow) {
|
|
|
15022
15244
|
const safe = workflow.replace(/[^\w.-]+/g, "_");
|
|
15023
15245
|
return join$1(runsDir(projectRoot), `${safe}.jsonl`);
|
|
15024
15246
|
}
|
|
15247
|
+
let _runSeq = 0;
|
|
15025
15248
|
function shortRunId(seed) {
|
|
15026
15249
|
let h = 2166136261;
|
|
15027
15250
|
for (let i = 0; i < seed.length; i++) {
|
|
@@ -15174,7 +15397,7 @@ function escalateWorkflowFailure(projectRoot, wf, run) {
|
|
|
15174
15397
|
const open = listIssues(projectRoot, { label: failLabel }).filter((i) => i.status !== "archived");
|
|
15175
15398
|
if (open.length > 0) return void 0;
|
|
15176
15399
|
const failed = run.steps.find((s) => s.timedOut || s.exitCode !== 0 && s.exitCode !== null) || run.steps[run.steps.length - 1];
|
|
15177
|
-
const failCause = run.error ? `runner error: ${run.error}` : failed?.timedOut ? `step timed out after ${
|
|
15400
|
+
const failCause = run.error ? `runner error: ${run.error}` : failed?.timedOut ? `step timed out after ${stepTimeoutMs() / 1e3}s` : `step exited with code ${failed?.exitCode ?? "null (killed/signal)"}`;
|
|
15178
15401
|
const defLines = workflowSteps(wf).map((s, i) => ` ${i}. ${s.run}`).join("\n") || " (no steps)";
|
|
15179
15402
|
const stderrTail = failed?.stderr ? capStream(failed.stderr, 3e3) : "(none)";
|
|
15180
15403
|
const stdoutTail = failed?.stdout ? capStream(failed.stdout, 1500) : "(none)";
|
|
@@ -15247,7 +15470,7 @@ async function runWorkflowInner(projectRoot, wf, opts) {
|
|
|
15247
15470
|
});
|
|
15248
15471
|
const exec = opts.execStep || defaultExecStep;
|
|
15249
15472
|
const start = now();
|
|
15250
|
-
const id = shortRunId(`${wf.name}:${start.iso}:${start.ms}`);
|
|
15473
|
+
const id = shortRunId(`${wf.name}:${start.iso}:${start.ms}:${_runSeq++}`);
|
|
15251
15474
|
const env = wf.session ? { ...process.env, SVAMP_SESSION_ID: wf.session } : process.env;
|
|
15252
15475
|
const run = {
|
|
15253
15476
|
id,
|
|
@@ -15358,7 +15581,9 @@ function fireIdleWorkflows(root, sessionId, deps = {}) {
|
|
|
15358
15581
|
} catch {
|
|
15359
15582
|
return [];
|
|
15360
15583
|
}
|
|
15361
|
-
const matches = workflows.filter(
|
|
15584
|
+
const matches = workflows.filter(
|
|
15585
|
+
(wf) => wf.on?.idle === true && isWorkflowEnabled(wf) && (!wf.session || wf.session === sessionId)
|
|
15586
|
+
);
|
|
15362
15587
|
return matches.map((wf) => {
|
|
15363
15588
|
deps.log?.(`[workflow] idle fired "${wf.name}" in ${root} for session ${sessionId}`);
|
|
15364
15589
|
return runWorkflow(root, wf, {
|
|
@@ -16114,6 +16339,24 @@ async function readSessionFileBase64(resolvedPath) {
|
|
|
16114
16339
|
const buffer = await fs$1.readFile(resolvedPath);
|
|
16115
16340
|
return buffer.toString("base64");
|
|
16116
16341
|
}
|
|
16342
|
+
async function writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast) {
|
|
16343
|
+
const resolvedPath = resolve$1(directory, path);
|
|
16344
|
+
if (sessionMetadata?.securityContext && resolvedPath !== resolve$1(directory) && !resolvedPath.startsWith(resolve$1(directory) + "/")) {
|
|
16345
|
+
throw new Error("Path outside working directory");
|
|
16346
|
+
}
|
|
16347
|
+
const safeUploadId = String(uploadId).replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64) || "upload";
|
|
16348
|
+
const tempPath = `${resolvedPath}.svamp-upload-${safeUploadId}.part`;
|
|
16349
|
+
const buffer = Buffer.from(content || "", "base64");
|
|
16350
|
+
if (chunkIndex === 0) {
|
|
16351
|
+
await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
|
|
16352
|
+
await fs$1.writeFile(tempPath, buffer);
|
|
16353
|
+
} else {
|
|
16354
|
+
await fs$1.appendFile(tempPath, buffer);
|
|
16355
|
+
}
|
|
16356
|
+
if (isLast) {
|
|
16357
|
+
await fs$1.rename(tempPath, resolvedPath);
|
|
16358
|
+
}
|
|
16359
|
+
}
|
|
16117
16360
|
|
|
16118
16361
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
16119
16362
|
const __dirname$1 = dirname$1(__filename$1);
|
|
@@ -16989,7 +17232,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
16989
17232
|
const gaveUpCfg = readLoopJson(directory, sessionId, "loop.config.json") || {};
|
|
16990
17233
|
const curMax = typeof gaveUpCfg.max_iterations === "number" ? gaveUpCfg.max_iterations : iter;
|
|
16991
17234
|
const nextMax = Math.max(curMax * 2, iter + 10);
|
|
16992
|
-
const msg = s.phase === "done" ? `\u{1F501} Loop complete \u2705 \u2014 finished after ${iter} iteration${plural}.` : `\u{1F501} Loop paused \u{1F6D1} \u2014 did NOT finish after ${iter} iteration${plural}${s.gave_up_reason ? ` (${s.gave_up_reason})` : ""}. Resume & extend with: svamp session loop-
|
|
17235
|
+
const msg = s.phase === "done" ? `\u{1F501} Loop complete \u2705 \u2014 finished after ${iter} iteration${plural}.` : `\u{1F501} Loop paused \u{1F6D1} \u2014 did NOT finish after ${iter} iteration${plural}${s.gave_up_reason ? ` (${s.gave_up_reason})` : ""}. Resume & extend with: svamp session loop-extend ${sessionId} --reason "<why more turns are needed>" --turns ${Math.max(10, nextMax - iter)}`;
|
|
16993
17236
|
sessionService.pushMessage(
|
|
16994
17237
|
s.phase === "gave_up" ? { type: "message", message: msg, level: "warning" } : { type: "message", message: msg },
|
|
16995
17238
|
"event"
|
|
@@ -17058,7 +17301,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
17058
17301
|
maxExtensions
|
|
17059
17302
|
});
|
|
17060
17303
|
if (!grant.granted) {
|
|
17061
|
-
sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Extension refused \u2014 this loop already used all ${grant.max} allowed extension${grant.max === 1 ? "" : "s"} (${grant.used}/${grant.max}). A hard cap is a hard cap; do the remaining work or raise the limit manually: svamp session loop-
|
|
17304
|
+
sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Extension refused \u2014 this loop already used all ${grant.max} allowed extension${grant.max === 1 ? "" : "s"} (${grant.used}/${grant.max}). A hard cap is a hard cap; do the remaining work or raise the limit manually: svamp session loop-extend ${sessionId} --reason "<why>" --turns <N>`, level: "warning" }, "event");
|
|
17062
17305
|
logger.log(`[svampConfig] loop-extend refused (bounded ${grant.used}/${grant.max})`);
|
|
17063
17306
|
} else {
|
|
17064
17307
|
const wasStopped = existing.active === false || existing.phase === "done" || existing.phase === "gave_up" || existing.phase === "cancelled";
|
|
@@ -17612,7 +17855,7 @@ async function startDaemon(options) {
|
|
|
17612
17855
|
try {
|
|
17613
17856
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
17614
17857
|
if (!dir) return;
|
|
17615
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
17858
|
+
const { reconcileServiceLinks } = await import('./agentCommands-9GHpj3mp.mjs');
|
|
17616
17859
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
17617
17860
|
const config = readSvampConfig(configPath);
|
|
17618
17861
|
const entries = Array.from(urls.entries());
|
|
@@ -17630,7 +17873,7 @@ async function startDaemon(options) {
|
|
|
17630
17873
|
}
|
|
17631
17874
|
}
|
|
17632
17875
|
async function createExposedTunnel(spec) {
|
|
17633
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
17876
|
+
const { FrpcTunnel } = await import('./frpc-DxIjZ8o_.mjs');
|
|
17634
17877
|
const tunnel = new FrpcTunnel({
|
|
17635
17878
|
name: spec.name,
|
|
17636
17879
|
ports: spec.ports,
|
|
@@ -17658,7 +17901,7 @@ async function startDaemon(options) {
|
|
|
17658
17901
|
ensureAutoInstalledCommands(logger);
|
|
17659
17902
|
(async () => {
|
|
17660
17903
|
try {
|
|
17661
|
-
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-
|
|
17904
|
+
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-zDzkOTi2.mjs');
|
|
17662
17905
|
beginClaudeVersionReconcile((msg) => logger.log(msg));
|
|
17663
17906
|
} catch (e) {
|
|
17664
17907
|
logger.log(`[claude-version] check failed: ${e?.message || e}`);
|
|
@@ -17941,8 +18184,16 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
|
|
|
17941
18184
|
if (RATELIMIT_CFG.maxRetries <= 0) return false;
|
|
17942
18185
|
if (trackedSession?.stopped) return false;
|
|
17943
18186
|
if (currentTurnMessage === void 0) return false;
|
|
17944
|
-
|
|
17945
|
-
|
|
18187
|
+
const nowTs = Date.now();
|
|
18188
|
+
if (rateLimitStreakStartedAt === 0) {
|
|
18189
|
+
rateLimitStreakStartedAt = nowTs;
|
|
18190
|
+
sessionMetadata = { ...sessionMetadata, rateLimitStreakStartedAt: nowTs };
|
|
18191
|
+
}
|
|
18192
|
+
const budget = retryStreakExhausted(rateLimitRetryCount, rateLimitStreakStartedAt, nowTs, RATELIMIT_CFG);
|
|
18193
|
+
if (budget.exhausted) {
|
|
18194
|
+
const elapsedMin = Math.round((nowTs - rateLimitStreakStartedAt) / 6e4);
|
|
18195
|
+
logger.log(`[Session ${sessionId}] Rate-limit retries exhausted (${budget.reason}: ${rateLimitRetryCount}/${RATELIMIT_CFG.maxRetries} attempts, ${elapsedMin}m elapsed) \u2014 surfacing error`);
|
|
18196
|
+
resetRateLimitStreak();
|
|
17946
18197
|
return false;
|
|
17947
18198
|
}
|
|
17948
18199
|
const attempt = rateLimitRetryCount++;
|
|
@@ -18410,18 +18661,18 @@ ${parts.join("\n")}`);
|
|
|
18410
18661
|
enqueueLoopMessage(push, label);
|
|
18411
18662
|
if (!trackedSession.stopped) setTimeout(() => processMessageQueueRef?.(), 200);
|
|
18412
18663
|
} else {
|
|
18413
|
-
writeGoalLoopState(directory, sessionId, { active: false, phase: "gave_up", completed_at: Date.now(), gave_up_reason: decision.reason, holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
|
|
18664
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "gave_up", completed_at: Date.now(), gave_up_reason: decision.reason, holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
|
|
18414
18665
|
logger.log(`[Session ${sessionId}] [loop-persist] stuck-stop: ${decision.reason}`);
|
|
18415
18666
|
checkSvampConfig?.();
|
|
18416
18667
|
}
|
|
18417
18668
|
} else {
|
|
18418
|
-
writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now(), holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
|
|
18669
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now(), holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
|
|
18419
18670
|
checkSvampConfig?.();
|
|
18420
18671
|
}
|
|
18421
18672
|
} catch (e) {
|
|
18422
18673
|
logger.log(`[Session ${sessionId}] verifyGoalCompletion error \u2014 failing open (done): ${e?.message || e}`);
|
|
18423
18674
|
try {
|
|
18424
|
-
writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now() });
|
|
18675
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now() });
|
|
18425
18676
|
checkSvampConfig?.();
|
|
18426
18677
|
} catch {
|
|
18427
18678
|
}
|
|
@@ -18479,8 +18730,16 @@ ${parts.join("\n")}`);
|
|
|
18479
18730
|
const RATELIMIT_CFG = getRateLimitRetryConfig();
|
|
18480
18731
|
let currentTurnMessage;
|
|
18481
18732
|
let rateLimitRetryCount = 0;
|
|
18733
|
+
let rateLimitStreakStartedAt = Number(sessionMetadata?.rateLimitStreakStartedAt) || 0;
|
|
18482
18734
|
let rateLimitRetryTimer = null;
|
|
18483
18735
|
let rateLimitRetryScheduled = false;
|
|
18736
|
+
const resetRateLimitStreak = () => {
|
|
18737
|
+
rateLimitRetryCount = 0;
|
|
18738
|
+
rateLimitStreakStartedAt = 0;
|
|
18739
|
+
if (sessionMetadata?.rateLimitStreakStartedAt) {
|
|
18740
|
+
sessionMetadata = { ...sessionMetadata, rateLimitStreakStartedAt: 0 };
|
|
18741
|
+
}
|
|
18742
|
+
};
|
|
18484
18743
|
let checkSvampConfig;
|
|
18485
18744
|
let cleanupSvampConfig;
|
|
18486
18745
|
const VALID_CLAUDE_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
@@ -18895,7 +19154,7 @@ ${parts.join("\n")}`);
|
|
|
18895
19154
|
"event"
|
|
18896
19155
|
);
|
|
18897
19156
|
}
|
|
18898
|
-
if (!msg.is_error)
|
|
19157
|
+
if (!msg.is_error) resetRateLimitStreak();
|
|
18899
19158
|
if (msg.session_id) {
|
|
18900
19159
|
claudeResumeId = msg.session_id;
|
|
18901
19160
|
if (sessionMetadata.claudeSessionId !== msg.session_id) {
|
|
@@ -18961,7 +19220,7 @@ ${parts.join("\n")}`);
|
|
|
18961
19220
|
auto_resumes: prog.auto_resumes
|
|
18962
19221
|
});
|
|
18963
19222
|
const extNote = summarizeExtensions(ls.extensions, maxExt);
|
|
18964
|
-
const resumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>"
|
|
19223
|
+
const resumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>" [--turns <N>]` : `Raise the cost cap to resume (--max won't \u2014 it only raises the iteration cap): svamp session loop ${sessionId} --max-runtime-sec <N> and/or --max-tokens-per-hour <N>`;
|
|
18965
19224
|
const detail = `${budgetCheck.reason} (hard ${iterStop ? "iteration ceiling" : "cost ceiling"} \u2014 will not auto-resume)${extNote ? ` [${extNote}]` : ""}. ${resumeHint}`;
|
|
18966
19225
|
sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Loop stopped \u2014 ${detail}`, level: "warning" }, "event");
|
|
18967
19226
|
checkSvampConfig?.();
|
|
@@ -19467,7 +19726,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19467
19726
|
return;
|
|
19468
19727
|
}
|
|
19469
19728
|
currentTurnMessage = text;
|
|
19470
|
-
|
|
19729
|
+
resetRateLimitStreak();
|
|
19471
19730
|
if (rateLimitRetryTimer) {
|
|
19472
19731
|
clearTimeout(rateLimitRetryTimer);
|
|
19473
19732
|
rateLimitRetryTimer = null;
|
|
@@ -19491,7 +19750,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19491
19750
|
clearTimeout(rateLimitRetryTimer);
|
|
19492
19751
|
rateLimitRetryTimer = null;
|
|
19493
19752
|
}
|
|
19494
|
-
|
|
19753
|
+
resetRateLimitStreak();
|
|
19495
19754
|
if (claudeProcess && !claudeProcess.killed) {
|
|
19496
19755
|
try {
|
|
19497
19756
|
const interruptMsg = JSON.stringify({ type: "control_request", request: { type: "interrupt" } });
|
|
@@ -19848,11 +20107,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19848
20107
|
});
|
|
19849
20108
|
},
|
|
19850
20109
|
onIssue: async (params) => {
|
|
19851
|
-
const { issueRpc } = await import('./rpc-
|
|
20110
|
+
const { issueRpc } = await import('./rpc-D7_Gj0Kt.mjs');
|
|
19852
20111
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
19853
20112
|
},
|
|
19854
20113
|
onWorkflow: async (params) => {
|
|
19855
|
-
const { workflowRpc } = await import('./rpc-
|
|
20114
|
+
const { workflowRpc } = await import('./rpc-BVw8Tv9l.mjs');
|
|
19856
20115
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
19857
20116
|
},
|
|
19858
20117
|
onRipgrep: async (args, cwd) => {
|
|
@@ -19884,6 +20143,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19884
20143
|
await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
|
|
19885
20144
|
await fs$1.writeFile(resolvedPath, Buffer.from(content, "base64"));
|
|
19886
20145
|
},
|
|
20146
|
+
onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
|
|
20147
|
+
await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
|
|
20148
|
+
},
|
|
19887
20149
|
onListDirectory: async (path) => {
|
|
19888
20150
|
const resolvedDir = resolve$1(directory, path || ".");
|
|
19889
20151
|
if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
|
|
@@ -19893,8 +20155,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19893
20155
|
return entries.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }));
|
|
19894
20156
|
},
|
|
19895
20157
|
onGetDirectoryTree: async (treePath, maxDepth) => {
|
|
20158
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".svamp", ".expo", "dist", "build", ".next", ".cache", ".venv", "venv", "__pycache__", ".turbo", ".gradle", "target"]);
|
|
20159
|
+
const MAX_TREE_DEPTH = 8;
|
|
20160
|
+
const MAX_TREE_NODES = 2e4;
|
|
20161
|
+
const effectiveMaxDepth = Math.min(Math.max(0, Math.floor(Number(maxDepth)) || 0), MAX_TREE_DEPTH);
|
|
20162
|
+
let treeNodeCount = 0;
|
|
19896
20163
|
async function buildTree(p, name, depth) {
|
|
19897
20164
|
try {
|
|
20165
|
+
if (treeNodeCount >= MAX_TREE_NODES) return null;
|
|
20166
|
+
treeNodeCount++;
|
|
19898
20167
|
const stats = await fs$1.stat(p);
|
|
19899
20168
|
const node = {
|
|
19900
20169
|
name,
|
|
@@ -19903,11 +20172,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19903
20172
|
size: stats.size,
|
|
19904
20173
|
modified: stats.mtime.getTime()
|
|
19905
20174
|
};
|
|
19906
|
-
if (stats.isDirectory() && depth <
|
|
20175
|
+
if (stats.isDirectory() && depth < effectiveMaxDepth) {
|
|
19907
20176
|
const entries = await fs$1.readdir(p, { withFileTypes: true });
|
|
19908
20177
|
const children = [];
|
|
19909
20178
|
await Promise.all(entries.map(async (entry) => {
|
|
19910
20179
|
if (entry.isSymbolicLink()) return;
|
|
20180
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) return;
|
|
20181
|
+
if (treeNodeCount >= MAX_TREE_NODES) return;
|
|
19911
20182
|
const childPath = join(p, entry.name);
|
|
19912
20183
|
const childNode = await buildTree(childPath, entry.name, depth + 1);
|
|
19913
20184
|
if (childNode) children.push(childNode);
|
|
@@ -19978,7 +20249,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19978
20249
|
userMessagePending = true;
|
|
19979
20250
|
turnInitiatedByUser = true;
|
|
19980
20251
|
currentTurnMessage = next.text;
|
|
19981
|
-
|
|
20252
|
+
resetRateLimitStreak();
|
|
19982
20253
|
if (rateLimitRetryTimer) {
|
|
19983
20254
|
clearTimeout(rateLimitRetryTimer);
|
|
19984
20255
|
rateLimitRetryTimer = null;
|
|
@@ -20257,6 +20528,30 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20257
20528
|
};
|
|
20258
20529
|
sessionService.updateMetadata(sessionMetadata);
|
|
20259
20530
|
};
|
|
20531
|
+
const drainQueueHeadIfIdle = () => {
|
|
20532
|
+
if (acpStopped || !acpBackendReady) return;
|
|
20533
|
+
if (sessionMetadata.lifecycleState !== "idle") return;
|
|
20534
|
+
const queue = sessionMetadata.messageQueue;
|
|
20535
|
+
if (!queue || queue.length === 0) return;
|
|
20536
|
+
const next = queue[0];
|
|
20537
|
+
const remaining = queue.slice(1);
|
|
20538
|
+
sessionMetadata = { ...sessionMetadata, messageQueue: remaining.length > 0 ? remaining : void 0, lifecycleState: "running" };
|
|
20539
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
20540
|
+
if (typeof next.inboxHop === "number") {
|
|
20541
|
+
writeInboundContext(sessionId, { hopCount: next.inboxHop, threadId: next.inboxThreadId });
|
|
20542
|
+
}
|
|
20543
|
+
sessionService.markInboxHandled(next.id);
|
|
20544
|
+
if (!next.alreadyStored) sessionService.pushMessage(next.displayText || next.text, "user");
|
|
20545
|
+
sessionService.sendKeepAlive(true);
|
|
20546
|
+
agentBackend.sendPrompt(sessionId, next.text).catch((err) => {
|
|
20547
|
+
logger.error(`[${agentName} Session ${sessionId}] Error draining queued message after inject miss: ${err?.message ?? err}`);
|
|
20548
|
+
if (!acpStopped) {
|
|
20549
|
+
sessionMetadata = { ...sessionMetadata, lifecycleState: "idle" };
|
|
20550
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
20551
|
+
sessionService.sendSessionEnd();
|
|
20552
|
+
}
|
|
20553
|
+
});
|
|
20554
|
+
};
|
|
20260
20555
|
const injectFn = agentBackend.injectInput;
|
|
20261
20556
|
if (typeof injectFn === "function") {
|
|
20262
20557
|
Promise.resolve(injectFn.call(agentBackend, text)).then((injected) => {
|
|
@@ -20265,10 +20560,12 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20265
20560
|
} else {
|
|
20266
20561
|
logger.log(`[${agentName} Session ${sessionId}] No active turn to inject \u2014 queuing message`);
|
|
20267
20562
|
enqueueBusy();
|
|
20563
|
+
drainQueueHeadIfIdle();
|
|
20268
20564
|
}
|
|
20269
20565
|
}).catch((err) => {
|
|
20270
20566
|
logger.error(`[${agentName} Session ${sessionId}] Mid-turn inject failed \u2014 queuing:`, err?.message ?? err);
|
|
20271
20567
|
enqueueBusy();
|
|
20568
|
+
drainQueueHeadIfIdle();
|
|
20272
20569
|
});
|
|
20273
20570
|
return;
|
|
20274
20571
|
}
|
|
@@ -20484,11 +20781,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20484
20781
|
});
|
|
20485
20782
|
},
|
|
20486
20783
|
onIssue: async (params) => {
|
|
20487
|
-
const { issueRpc } = await import('./rpc-
|
|
20784
|
+
const { issueRpc } = await import('./rpc-D7_Gj0Kt.mjs');
|
|
20488
20785
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
20489
20786
|
},
|
|
20490
20787
|
onWorkflow: async (params) => {
|
|
20491
|
-
const { workflowRpc } = await import('./rpc-
|
|
20788
|
+
const { workflowRpc } = await import('./rpc-BVw8Tv9l.mjs');
|
|
20492
20789
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
20493
20790
|
},
|
|
20494
20791
|
onRipgrep: async (args, cwd) => {
|
|
@@ -20520,6 +20817,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20520
20817
|
await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
|
|
20521
20818
|
await fs$1.writeFile(resolvedPath, Buffer.from(content, "base64"));
|
|
20522
20819
|
},
|
|
20820
|
+
onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
|
|
20821
|
+
await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
|
|
20822
|
+
},
|
|
20523
20823
|
onListDirectory: async (path) => {
|
|
20524
20824
|
const resolvedDir = resolve$1(directory, path || ".");
|
|
20525
20825
|
if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
|
|
@@ -20529,8 +20829,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20529
20829
|
return entries.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }));
|
|
20530
20830
|
},
|
|
20531
20831
|
onGetDirectoryTree: async (treePath, maxDepth) => {
|
|
20832
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".svamp", ".expo", "dist", "build", ".next", ".cache", ".venv", "venv", "__pycache__", ".turbo", ".gradle", "target"]);
|
|
20833
|
+
const MAX_TREE_DEPTH = 8;
|
|
20834
|
+
const MAX_TREE_NODES = 2e4;
|
|
20835
|
+
const effectiveMaxDepth = Math.min(Math.max(0, Math.floor(Number(maxDepth)) || 0), MAX_TREE_DEPTH);
|
|
20836
|
+
let treeNodeCount = 0;
|
|
20532
20837
|
async function buildTree(p, name, depth) {
|
|
20533
20838
|
try {
|
|
20839
|
+
if (treeNodeCount >= MAX_TREE_NODES) return null;
|
|
20840
|
+
treeNodeCount++;
|
|
20534
20841
|
const stats = await fs$1.stat(p);
|
|
20535
20842
|
const node = {
|
|
20536
20843
|
name,
|
|
@@ -20539,11 +20846,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20539
20846
|
size: stats.size,
|
|
20540
20847
|
modified: stats.mtime.getTime()
|
|
20541
20848
|
};
|
|
20542
|
-
if (stats.isDirectory() && depth <
|
|
20849
|
+
if (stats.isDirectory() && depth < effectiveMaxDepth) {
|
|
20543
20850
|
const entries = await fs$1.readdir(p, { withFileTypes: true });
|
|
20544
20851
|
const children = [];
|
|
20545
20852
|
await Promise.all(entries.map(async (entry) => {
|
|
20546
20853
|
if (entry.isSymbolicLink()) return;
|
|
20854
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) return;
|
|
20855
|
+
if (treeNodeCount >= MAX_TREE_NODES) return;
|
|
20547
20856
|
const childPath = join(p, entry.name);
|
|
20548
20857
|
const childNode = await buildTree(childPath, entry.name, depth + 1);
|
|
20549
20858
|
if (childNode) children.push(childNode);
|
|
@@ -20806,7 +21115,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20806
21115
|
const iterStop = !isCostCeiling(budgetCheck.kind);
|
|
20807
21116
|
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "gave_up", completed_at: now, gave_up_reason: `resource budget exhausted \u2014 ${budgetCheck.reason}`, ledger });
|
|
20808
21117
|
const acpExtNote = summarizeExtensions(ls.extensions, acpMaxExt);
|
|
20809
|
-
const acpResumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>"
|
|
21118
|
+
const acpResumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>" [--turns <N>]` : `Raise the cost cap to resume (--max won't \u2014 it only raises the iteration cap): svamp session loop ${sessionId} --max-runtime-sec <N> and/or --max-tokens-per-hour <N>`;
|
|
20810
21119
|
sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Loop stopped \u2014 ${budgetCheck.reason} (hard ${iterStop ? "iteration ceiling" : "cost ceiling"} \u2014 will not auto-resume)${acpExtNote ? ` [${acpExtNote}]` : ""}. ${acpResumeHint}`, level: "warning" }, "event");
|
|
20811
21120
|
checkSvampConfig?.();
|
|
20812
21121
|
return;
|
|
@@ -21434,7 +21743,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21434
21743
|
}
|
|
21435
21744
|
if (persistedSessions.length > 0) {
|
|
21436
21745
|
try {
|
|
21437
|
-
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-
|
|
21746
|
+
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-zDzkOTi2.mjs');
|
|
21438
21747
|
await awaitClaudeVersionReady();
|
|
21439
21748
|
} catch {
|
|
21440
21749
|
}
|
|
@@ -21639,7 +21948,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21639
21948
|
const PING_TIMEOUT_MS = 15e3;
|
|
21640
21949
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
21641
21950
|
const RECONNECT_JITTER_MS = 2500;
|
|
21642
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
21951
|
+
const { WorkflowScheduler } = await import('./scheduler-q7Refevo.mjs');
|
|
21643
21952
|
const workflowProjectRoots = () => {
|
|
21644
21953
|
const dirs = /* @__PURE__ */ new Set();
|
|
21645
21954
|
for (const s of pidToTrackedSession.values()) {
|
|
@@ -21928,6 +22237,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21928
22237
|
logger.log(`Cleaning up (source: ${source})...`);
|
|
21929
22238
|
clearInterval(heartbeatInterval);
|
|
21930
22239
|
clearInterval(workflowSchedulerInterval);
|
|
22240
|
+
clearInterval(backendAccountRefreshInterval);
|
|
21931
22241
|
if (proxyTokenRefreshInterval) clearInterval(proxyTokenRefreshInterval);
|
|
21932
22242
|
if (oauthRefreshInterval) clearInterval(oauthRefreshInterval);
|
|
21933
22243
|
if (unhandledRejectionResetTimer) clearTimeout(unhandledRejectionResetTimer);
|
|
@@ -22272,4 +22582,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
22272
22582
|
writeStopMarker: writeStopMarker
|
|
22273
22583
|
});
|
|
22274
22584
|
|
|
22275
|
-
export {
|
|
22585
|
+
export { SKILLS_DIR 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, PINNED_CODEX_VERSION 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, run as aA, buildSecurityContextFromFlags as aa, mergeSecurityContexts as ab, buildSessionShareUrl as ac, computeOutboundHop as ad, registerAwaitingReply as ae, buildMachineShareUrl as af, parseHandle as ag, handleMatchesMetadata 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, api 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 };
|