svamp-cli 0.2.308 → 0.2.309
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-BTLHQXmA.mjs} +2 -2
- package/dist/{agentCommands-DEJpgdgc.mjs → agentCommands-DdNUACV8.mjs} +6 -6
- package/dist/{auth-BTodo5Et.mjs → auth-Bn8TbaOP.mjs} +2 -2
- package/dist/{cli-Ko4XpjRo.mjs → cli-BaDWWDiz.mjs} +70 -70
- package/dist/cli.mjs +3 -3
- package/dist/{commands-DKOYCcmo.mjs → commands-2uOorIjB.mjs} +2 -2
- package/dist/{commands-B2ARVwag.mjs → commands-BPgxgkXW.mjs} +3 -3
- package/dist/{commands-D3Nu5cYE.mjs → commands-BTN9XxVx.mjs} +3 -2
- package/dist/{commands-GNplzSQX.mjs → commands-Bb0oF3eZ.mjs} +37 -6
- package/dist/{commands-B-u0fICz.mjs → commands-C2u988x1.mjs} +3 -3
- package/dist/{commands-dBiQcDJ1.mjs → commands-DGbLwT_h.mjs} +23 -11
- package/dist/{commands-DA7rAZqn.mjs → commands-DPvy0iVT.mjs} +2 -2
- package/dist/{commands-DfbOYRLs.mjs → commands-r86haNJD.mjs} +3 -3
- package/dist/{fleet-CXixARxe.mjs → fleet-CeLE0t97.mjs} +3 -3
- package/dist/{frpc-DV_IIYpM.mjs → frpc-Cv6J2Mex.mjs} +5 -2
- package/dist/{headlessCli-DfkgeGnQ.mjs → headlessCli-BXc9Fvzb.mjs} +3 -3
- package/dist/index.mjs +2 -2
- package/dist/{notifyCommands-BUE_-OCX.mjs → notifyCommands-iVnI_Lb2.mjs} +2 -2
- package/dist/{package-GdCOuQCx.mjs → package-CeseSmYI.mjs} +2 -2
- package/dist/{pinnedClaudeCode-BaMR97BE.mjs → pinnedClaudeCode-C6MSkqS9.mjs} +1 -1
- package/dist/{rpc-BjKmyKdA.mjs → rpc-BwxwusfB.mjs} +2 -2
- package/dist/{rpc-wL-45Raf.mjs → rpc-CkcBAtcs.mjs} +2 -2
- package/dist/{run-ofbo9uzA.mjs → run-2Stz5l7J.mjs} +2 -2
- package/dist/{run-VQIHMUdo.mjs → run-DLG-_xa7.mjs} +366 -82
- package/dist/{scheduler-Dia9Akaw.mjs → scheduler-CurN4UtP.mjs} +5 -5
- package/dist/{serveCommands-DehmO1cO.mjs → serveCommands-CtVZhR-V.mjs} +5 -5
- package/dist/{sideband-CGiNKfNE.mjs → sideband-C_3PVGh-.mjs} +2 -2
- package/package.json +2 -2
|
@@ -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) {
|
|
@@ -2443,6 +2504,13 @@ class ServeManager {
|
|
|
2443
2504
|
async ensureManagedRunning(name) {
|
|
2444
2505
|
const mount = this.mounts.get(name);
|
|
2445
2506
|
if (!mount?.process) return;
|
|
2507
|
+
const pendingStop = this.stoppingProcs.get(name);
|
|
2508
|
+
if (pendingStop) {
|
|
2509
|
+
try {
|
|
2510
|
+
await pendingStop;
|
|
2511
|
+
} catch {
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2446
2514
|
const handle = this.managedProcs.get(name);
|
|
2447
2515
|
if (handle && handle.warmupPromise) {
|
|
2448
2516
|
return handle.warmupPromise;
|
|
@@ -2474,7 +2542,10 @@ class ServeManager {
|
|
|
2474
2542
|
child.on("exit", (code, signal) => {
|
|
2475
2543
|
this.log(`Managed process '${name}' exited (code=${code}, signal=${signal})`);
|
|
2476
2544
|
const h = this.managedProcs.get(name);
|
|
2477
|
-
if (h && h.child === child)
|
|
2545
|
+
if (h && h.child === child) {
|
|
2546
|
+
this.managedProcs.delete(name);
|
|
2547
|
+
this.removeManagedPidFile(name);
|
|
2548
|
+
}
|
|
2478
2549
|
});
|
|
2479
2550
|
const warmupPath = cfg.warmupPath ?? "/";
|
|
2480
2551
|
const warmupTimeoutMs = cfg.warmupTimeoutMs ?? 3e4;
|
|
@@ -2500,6 +2571,7 @@ class ServeManager {
|
|
|
2500
2571
|
warmupPromise
|
|
2501
2572
|
};
|
|
2502
2573
|
this.managedProcs.set(name, newHandle);
|
|
2574
|
+
this.writeManagedPidFile(name, child, cfg);
|
|
2503
2575
|
this.log(`Managed process '${name}' starting: ${cfg.command} ${(cfg.args ?? []).join(" ")} (port ${cfg.port})`);
|
|
2504
2576
|
return warmupPromise;
|
|
2505
2577
|
}
|
|
@@ -2509,6 +2581,25 @@ class ServeManager {
|
|
|
2509
2581
|
* that a direct child.kill() would orphan, leaving the port bound. Falls back to a direct
|
|
2510
2582
|
* child.kill() if the group signal fails (e.g. pid already gone).
|
|
2511
2583
|
*/
|
|
2584
|
+
// ── #0758: managed-process pidfiles (crash-orphan reaping) ──────────────
|
|
2585
|
+
managedPidFile(name) {
|
|
2586
|
+
const safe = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
2587
|
+
return path.join(this.managedPidDir, `${safe}.pid`);
|
|
2588
|
+
}
|
|
2589
|
+
writeManagedPidFile(name, child, cfg) {
|
|
2590
|
+
try {
|
|
2591
|
+
fs.mkdirSync(this.managedPidDir, { recursive: true, mode: 448 });
|
|
2592
|
+
const rec = { pid: child.pid ?? 0, command: cfg.command, args: cfg.args ?? [], port: cfg.port, startedAt: Date.now() };
|
|
2593
|
+
fs.writeFileSync(this.managedPidFile(name), JSON.stringify(rec), { mode: 384 });
|
|
2594
|
+
} catch {
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
removeManagedPidFile(name) {
|
|
2598
|
+
try {
|
|
2599
|
+
fs.unlinkSync(this.managedPidFile(name));
|
|
2600
|
+
} catch {
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2512
2603
|
killManagedTree(child, signal) {
|
|
2513
2604
|
const pid = child.pid;
|
|
2514
2605
|
if (pid && pid > 0) {
|
|
@@ -2558,18 +2649,30 @@ class ServeManager {
|
|
|
2558
2649
|
const h = this.managedProcs.get(name);
|
|
2559
2650
|
if (!h) return;
|
|
2560
2651
|
this.managedProcs.delete(name);
|
|
2652
|
+
this.removeManagedPidFile(name);
|
|
2561
2653
|
const child = h.child;
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2654
|
+
const done = (async () => {
|
|
2655
|
+
if (child.exitCode !== null) return;
|
|
2656
|
+
this.killManagedTree(child, "SIGTERM");
|
|
2657
|
+
await new Promise((resolve) => {
|
|
2658
|
+
const onExit = () => {
|
|
2659
|
+
clearTimeout(t);
|
|
2660
|
+
clearTimeout(cap);
|
|
2661
|
+
resolve();
|
|
2662
|
+
};
|
|
2663
|
+
const t = setTimeout(() => {
|
|
2664
|
+
this.killManagedTree(child, "SIGKILL");
|
|
2665
|
+
}, 5e3);
|
|
2666
|
+
const cap = setTimeout(onExit, 8e3);
|
|
2667
|
+
child.once("exit", onExit);
|
|
2571
2668
|
});
|
|
2572
|
-
});
|
|
2669
|
+
})();
|
|
2670
|
+
this.stoppingProcs.set(name, done);
|
|
2671
|
+
try {
|
|
2672
|
+
await done;
|
|
2673
|
+
} finally {
|
|
2674
|
+
if (this.stoppingProcs.get(name) === done) this.stoppingProcs.delete(name);
|
|
2675
|
+
}
|
|
2573
2676
|
this.log(`Managed process '${name}' stopped`);
|
|
2574
2677
|
}
|
|
2575
2678
|
/** Idle eviction loop — stops processes that have been idle longer than configured. */
|
|
@@ -2631,6 +2734,7 @@ class ServeManager {
|
|
|
2631
2734
|
async ensureRunning() {
|
|
2632
2735
|
if (this.proxyServer) return;
|
|
2633
2736
|
killOrphanedCaddy((m) => this.log(m));
|
|
2737
|
+
reapOrphanedManagedProcesses(this.managedPidDir, (m) => this.log(m));
|
|
2634
2738
|
this.port = await tryReservePort(this.persistedPort);
|
|
2635
2739
|
if (this.persistedPort && this.port !== this.persistedPort) {
|
|
2636
2740
|
this.log(`\u26A0 Previous serve port ${this.persistedPort} unavailable \u2014 using ${this.port}. Downstream configs referencing the old port will need updating.`);
|
|
@@ -3047,7 +3151,7 @@ Connection: close\r
|
|
|
3047
3151
|
const mount = this.mounts.get(mountName);
|
|
3048
3152
|
const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
|
|
3049
3153
|
try {
|
|
3050
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
3154
|
+
const { FrpcTunnel } = await import('./frpc-Cv6J2Mex.mjs');
|
|
3051
3155
|
let tunnel;
|
|
3052
3156
|
tunnel = new FrpcTunnel({
|
|
3053
3157
|
name: tunnelName,
|
|
@@ -3974,6 +4078,26 @@ function cronMatches(expr, date) {
|
|
|
3974
4078
|
if (c.domRestricted && c.dowRestricted) return domOk || dowOk;
|
|
3975
4079
|
return domOk && dowOk;
|
|
3976
4080
|
}
|
|
4081
|
+
function inZone(date, tz) {
|
|
4082
|
+
if (!tz) return date;
|
|
4083
|
+
try {
|
|
4084
|
+
const p = new Intl.DateTimeFormat("en-US", {
|
|
4085
|
+
timeZone: tz,
|
|
4086
|
+
hour12: false,
|
|
4087
|
+
year: "numeric",
|
|
4088
|
+
month: "2-digit",
|
|
4089
|
+
day: "2-digit",
|
|
4090
|
+
hour: "2-digit",
|
|
4091
|
+
minute: "2-digit"
|
|
4092
|
+
}).formatToParts(date).reduce((o, x) => {
|
|
4093
|
+
o[x.type] = x.value;
|
|
4094
|
+
return o;
|
|
4095
|
+
}, {});
|
|
4096
|
+
return new Date(+p.year, +p.month - 1, +p.day, +(p.hour === "24" ? 0 : p.hour), +p.minute);
|
|
4097
|
+
} catch {
|
|
4098
|
+
return date;
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
3977
4101
|
function resolvePath(ctx, path) {
|
|
3978
4102
|
return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], ctx);
|
|
3979
4103
|
}
|
|
@@ -3992,8 +4116,8 @@ function sleepSync$1(ms) {
|
|
|
3992
4116
|
}
|
|
3993
4117
|
}
|
|
3994
4118
|
function withFileLock(lockPath, fn, opts) {
|
|
3995
|
-
const deadlineMs = 50;
|
|
3996
|
-
const staleMs = 5e3;
|
|
4119
|
+
const deadlineMs = opts?.deadlineMs ?? 50;
|
|
4120
|
+
const staleMs = opts?.staleMs ?? 5e3;
|
|
3997
4121
|
const deadline = Date.now() + deadlineMs;
|
|
3998
4122
|
let held = false;
|
|
3999
4123
|
while (Date.now() < deadline) {
|
|
@@ -4277,12 +4401,14 @@ class ChannelStore {
|
|
|
4277
4401
|
return this._writeChannel(channel);
|
|
4278
4402
|
}
|
|
4279
4403
|
remove(id) {
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4404
|
+
return withFileLock(this._lock(id), () => {
|
|
4405
|
+
const p = this._path(id);
|
|
4406
|
+
if (existsSync(p)) {
|
|
4407
|
+
rmSync$1(p);
|
|
4408
|
+
return true;
|
|
4409
|
+
}
|
|
4410
|
+
return false;
|
|
4411
|
+
});
|
|
4286
4412
|
}
|
|
4287
4413
|
// #0679: setEnabled/recordCall/addCaller are read-modify-write mutators — multiple ChannelStore
|
|
4288
4414
|
// instances (one per session) point at the same .svamp/channels/<id>.json, so without
|
|
@@ -4711,10 +4837,14 @@ class ChannelOutbox {
|
|
|
4711
4837
|
/** Append a reply addressed to `to`. Assigns seq + ts, persists, and wakes waiters. */
|
|
4712
4838
|
append(channelId, r) {
|
|
4713
4839
|
this.reload();
|
|
4714
|
-
const seq =
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4840
|
+
const seq = withFileLock(`${this.seqFile}.lock`, () => {
|
|
4841
|
+
this._loadHighWater();
|
|
4842
|
+
const s = Math.max(this.highWater.get(channelId) || 0, this.seqByChannel.get(channelId) || 0) + 1;
|
|
4843
|
+
this.seqByChannel.set(channelId, s);
|
|
4844
|
+
this.highWater.set(channelId, s);
|
|
4845
|
+
this._persistHighWater();
|
|
4846
|
+
return s;
|
|
4847
|
+
});
|
|
4718
4848
|
const reply = { seq, ts: Date.now(), to: r.to, body: r.body, ...r.correlationId ? { correlationId: r.correlationId } : {} };
|
|
4719
4849
|
const arr = this.byChannel.get(channelId) || [];
|
|
4720
4850
|
arr.push(reply);
|
|
@@ -4841,6 +4971,7 @@ const SESSION_METHOD_MIN_ROLE = {
|
|
|
4841
4971
|
archiveSession: "admin",
|
|
4842
4972
|
readFile: "admin",
|
|
4843
4973
|
writeFile: "admin",
|
|
4974
|
+
writeFileChunk: "admin",
|
|
4844
4975
|
listDirectory: "admin",
|
|
4845
4976
|
bash: "admin",
|
|
4846
4977
|
issue: "admin",
|
|
@@ -6123,7 +6254,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
6123
6254
|
const tunnels = handlers.tunnels;
|
|
6124
6255
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
6125
6256
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
6126
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
6257
|
+
const { FrpcTunnel } = await import('./frpc-Cv6J2Mex.mjs');
|
|
6127
6258
|
const tunnel = new FrpcTunnel({
|
|
6128
6259
|
name: params.name,
|
|
6129
6260
|
ports: params.ports,
|
|
@@ -6617,7 +6748,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6617
6748
|
}
|
|
6618
6749
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
6619
6750
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
6620
|
-
const { toolsForRole } = await import('./sideband-
|
|
6751
|
+
const { toolsForRole } = await import('./sideband-C_3PVGh-.mjs');
|
|
6621
6752
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
6622
6753
|
return fmt(r2);
|
|
6623
6754
|
}
|
|
@@ -6722,7 +6853,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6722
6853
|
return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
|
|
6723
6854
|
}
|
|
6724
6855
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
6725
|
-
const { queryCore } = await import('./commands-
|
|
6856
|
+
const { queryCore } = await import('./commands-BTN9XxVx.mjs');
|
|
6726
6857
|
const timeout = c.reply?.timeout_sec || 120;
|
|
6727
6858
|
let result;
|
|
6728
6859
|
try {
|
|
@@ -7232,9 +7363,17 @@ function getRateLimitRetryConfig() {
|
|
|
7232
7363
|
return {
|
|
7233
7364
|
maxRetries: envInt("SVAMP_RATELIMIT_MAX_RETRIES", 200),
|
|
7234
7365
|
baseMs: envInt("SVAMP_RATELIMIT_BASE_MS", 5e3),
|
|
7235
|
-
capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4)
|
|
7366
|
+
capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4),
|
|
7367
|
+
maxElapsedMs: envInt("SVAMP_RATELIMIT_MAX_ELAPSED_MS", 36e5)
|
|
7236
7368
|
};
|
|
7237
7369
|
}
|
|
7370
|
+
function retryStreakExhausted(attempts, streakStartedAtMs, nowMs, cfg = getRateLimitRetryConfig()) {
|
|
7371
|
+
if (attempts >= cfg.maxRetries) return { exhausted: true, reason: "count" };
|
|
7372
|
+
if (cfg.maxElapsedMs > 0 && streakStartedAtMs && streakStartedAtMs > 0 && nowMs - streakStartedAtMs >= cfg.maxElapsedMs) {
|
|
7373
|
+
return { exhausted: true, reason: "elapsed" };
|
|
7374
|
+
}
|
|
7375
|
+
return { exhausted: false };
|
|
7376
|
+
}
|
|
7238
7377
|
function computeRetryDelayMs(attempt, cfg = getRateLimitRetryConfig(), rng = Math.random) {
|
|
7239
7378
|
const a = Math.max(0, Math.floor(attempt));
|
|
7240
7379
|
const expo = Math.min(a, 30);
|
|
@@ -7413,6 +7552,9 @@ async function deletePairingArtifact(am, artifactId, log) {
|
|
|
7413
7552
|
}
|
|
7414
7553
|
}
|
|
7415
7554
|
async function sweepExpiredPairings(am, svampHome, log) {
|
|
7555
|
+
return withMintLock("*sweep*", () => _sweepExpiredPairingsUnlocked(am, svampHome, log));
|
|
7556
|
+
}
|
|
7557
|
+
async function _sweepExpiredPairingsUnlocked(am, svampHome, log) {
|
|
7416
7558
|
const now = Date.now();
|
|
7417
7559
|
const all = loadPairings(svampHome);
|
|
7418
7560
|
const expired = all.filter((p) => p.expiresAt <= now);
|
|
@@ -7420,18 +7562,40 @@ async function sweepExpiredPairings(am, svampHome, log) {
|
|
|
7420
7562
|
for (const p of expired) await deletePairingArtifact(am, p.artifactId, log);
|
|
7421
7563
|
savePairings(all.filter((p) => p.expiresAt > now), svampHome);
|
|
7422
7564
|
}
|
|
7565
|
+
let _lastSweepAt = 0;
|
|
7566
|
+
async function sweepExpiredPairingsThrottled(am, svampHome, log, minIntervalMs = 5 * 6e4) {
|
|
7567
|
+
const now = Date.now();
|
|
7568
|
+
if (now - _lastSweepAt < minIntervalMs) return;
|
|
7569
|
+
_lastSweepAt = now;
|
|
7570
|
+
await sweepExpiredPairings(am, svampHome, log);
|
|
7571
|
+
}
|
|
7572
|
+
let _pairingChain = Promise.resolve();
|
|
7573
|
+
function withMintLock(_session, fn) {
|
|
7574
|
+
const prior = _pairingChain.catch(() => {
|
|
7575
|
+
});
|
|
7576
|
+
const result = prior.then(fn);
|
|
7577
|
+
_pairingChain = result.catch(() => {
|
|
7578
|
+
});
|
|
7579
|
+
return result;
|
|
7580
|
+
}
|
|
7423
7581
|
async function burnSessionPairings(am, session, svampHome, log) {
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7582
|
+
return withMintLock(session, async () => {
|
|
7583
|
+
const all = loadPairings(svampHome);
|
|
7584
|
+
const mine = all.filter((p) => p.session === session);
|
|
7585
|
+
if (mine.length === 0) return;
|
|
7586
|
+
for (const p of mine) await deletePairingArtifact(am, p.artifactId, log);
|
|
7587
|
+
savePairings(all.filter((p) => p.session !== session), svampHome);
|
|
7588
|
+
});
|
|
7429
7589
|
}
|
|
7430
7590
|
async function mintPairingCode(opts) {
|
|
7431
|
-
const {
|
|
7591
|
+
const { session} = opts;
|
|
7432
7592
|
const resolver = (opts.resolverBase || "https://hypha.aicell.io").replace(/\/$/, "");
|
|
7433
7593
|
const ttlMs = opts.ttlMs && opts.ttlMs > 0 ? opts.ttlMs : DEFAULT_PAIRING_TTL_MS;
|
|
7434
|
-
|
|
7594
|
+
return withMintLock(session, () => _mintPairingCodeLocked(opts, resolver, ttlMs));
|
|
7595
|
+
}
|
|
7596
|
+
async function _mintPairingCodeLocked(opts, resolver, ttlMs) {
|
|
7597
|
+
const { am, session, payload, log } = opts;
|
|
7598
|
+
await _sweepExpiredPairingsUnlocked(am, opts.svampHome, log).catch(() => {
|
|
7435
7599
|
});
|
|
7436
7600
|
const now = Date.now();
|
|
7437
7601
|
const existing = loadPairings(opts.svampHome).find((p) => p.session === session && p.expiresAt > now + 3e4);
|
|
@@ -8072,6 +8236,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8072
8236
|
const channelOutbox = new ChannelOutbox(initialMetadata.path);
|
|
8073
8237
|
const outpostCoordinator = new OutpostCoordinator();
|
|
8074
8238
|
outpostCoordinator.startSweeper();
|
|
8239
|
+
void (async () => {
|
|
8240
|
+
try {
|
|
8241
|
+
const am = await server.getService("public/artifact-manager");
|
|
8242
|
+
if (am) await sweepExpiredPairingsThrottled(am, void 0, (m) => console.log(m));
|
|
8243
|
+
} catch {
|
|
8244
|
+
}
|
|
8245
|
+
})();
|
|
8075
8246
|
const outpostProjectDir = () => metadata.path || process.cwd();
|
|
8076
8247
|
const announceOutpost = (conn) => {
|
|
8077
8248
|
const who = conn.label || conn.machine;
|
|
@@ -8847,7 +9018,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8847
9018
|
});
|
|
8848
9019
|
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
8849
9020
|
const cursor = Math.max(0, Number(params.cursor) || 0);
|
|
8850
|
-
const
|
|
9021
|
+
const waitSecRaw = Number(params.wait);
|
|
9022
|
+
const waitSec = Number.isFinite(waitSecRaw) ? waitSecRaw : 25;
|
|
9023
|
+
const waitMs = Math.min(Math.max(0, waitSec * 1e3), 6e4);
|
|
8851
9024
|
const out = await channelOutbox.wait(c.id, cursor, r.sender.name, waitMs, params.correlationId);
|
|
8852
9025
|
return { ok: true, replies: out.replies, cursor: out.cursor };
|
|
8853
9026
|
},
|
|
@@ -8958,6 +9131,12 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8958
9131
|
await callbacks.onWriteFile(path, content);
|
|
8959
9132
|
return { success: true };
|
|
8960
9133
|
},
|
|
9134
|
+
writeFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast, context) => {
|
|
9135
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
9136
|
+
if (!callbacks.onWriteFileChunk) throw new Error("writeFileChunk not supported");
|
|
9137
|
+
await callbacks.onWriteFileChunk(path, content, uploadId, chunkIndex, totalChunks, isLast);
|
|
9138
|
+
return { success: true };
|
|
9139
|
+
},
|
|
8961
9140
|
listDirectory: async (path, context) => {
|
|
8962
9141
|
authorizeRequest(context, metadata.sharing, "admin");
|
|
8963
9142
|
if (!callbacks.onListDirectory) throw new Error("listDirectory not supported");
|
|
@@ -12291,15 +12470,30 @@ function codexAppServerAvailable() {
|
|
|
12291
12470
|
return null;
|
|
12292
12471
|
}
|
|
12293
12472
|
}
|
|
12473
|
+
const _execFileAsync = promisify$1(execFile$1);
|
|
12294
12474
|
let _steerSupport = null;
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12475
|
+
let _steerProbedAt = 0;
|
|
12476
|
+
let _steerProbe = null;
|
|
12477
|
+
const _STEER_TTL_MS = 3e4;
|
|
12478
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
|
|
12479
|
+
function _steerFresh() {
|
|
12480
|
+
return _steerSupport !== null && Date.now() - _steerProbedAt < _STEER_TTL_MS;
|
|
12481
|
+
}
|
|
12482
|
+
async function codexSupportsSteerInstalledAsync() {
|
|
12483
|
+
if (_steerFresh()) return _steerSupport;
|
|
12484
|
+
if (_steerProbe) return _steerProbe;
|
|
12485
|
+
_steerProbe = (async () => {
|
|
12486
|
+
try {
|
|
12487
|
+
const { stdout } = await _execFileAsync("codex", ["--version"], { encoding: "utf8" });
|
|
12488
|
+
_steerSupport = codexSupportsSteer(parseCodexVersion(stdout.trim()));
|
|
12489
|
+
} catch {
|
|
12490
|
+
_steerSupport = false;
|
|
12491
|
+
}
|
|
12492
|
+
_steerProbedAt = Date.now();
|
|
12493
|
+
_steerProbe = null;
|
|
12494
|
+
return _steerSupport;
|
|
12495
|
+
})();
|
|
12496
|
+
return _steerProbe;
|
|
12303
12497
|
}
|
|
12304
12498
|
class CodexAppServerClient {
|
|
12305
12499
|
constructor(opts) {
|
|
@@ -12474,7 +12668,7 @@ class CodexAppServerClient {
|
|
|
12474
12668
|
*/
|
|
12475
12669
|
async injectInput(prompt, _o) {
|
|
12476
12670
|
if (!this._threadId || !this.pendingTurn || !this._turnId) return false;
|
|
12477
|
-
if (!
|
|
12671
|
+
if (!await codexSupportsSteerInstalledAsync()) return false;
|
|
12478
12672
|
const params = {
|
|
12479
12673
|
threadId: this._threadId,
|
|
12480
12674
|
expectedTurnId: this._turnId,
|
|
@@ -12529,17 +12723,16 @@ class CodexAppServerClient {
|
|
|
12529
12723
|
// ── JSON-RPC plumbing ──────────────────────────────────────────────────────
|
|
12530
12724
|
request(method, params, timeoutMs) {
|
|
12531
12725
|
const id = this.nextId++;
|
|
12726
|
+
const effTimeout = timeoutMs ?? this.opts.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
12532
12727
|
return new Promise((resolve, reject) => {
|
|
12533
12728
|
this.pending.set(id, { resolve, reject, method });
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
12539
|
-
|
|
12540
|
-
|
|
12541
|
-
t.unref?.();
|
|
12542
|
-
}
|
|
12729
|
+
const t = setTimeout(() => {
|
|
12730
|
+
if (this.pending.has(id)) {
|
|
12731
|
+
this.pending.delete(id);
|
|
12732
|
+
reject(new Error(`codex ${method} timed out after ${effTimeout}ms`));
|
|
12733
|
+
}
|
|
12734
|
+
}, effTimeout);
|
|
12735
|
+
t.unref?.();
|
|
12543
12736
|
this.write({ jsonrpc: "2.0", id, method, params });
|
|
12544
12737
|
});
|
|
12545
12738
|
}
|
|
@@ -14839,8 +15032,8 @@ function isWorkflowEnabled(wf) {
|
|
|
14839
15032
|
function workflowSteps(wf) {
|
|
14840
15033
|
return Object.values(wf.jobs || {}).flatMap((j) => j?.steps || []);
|
|
14841
15034
|
}
|
|
14842
|
-
function
|
|
14843
|
-
return (wf.on?.schedule || []).
|
|
15035
|
+
function workflowSchedules(wf) {
|
|
15036
|
+
return (wf.on?.schedule || []).filter((s) => !!s?.cron);
|
|
14844
15037
|
}
|
|
14845
15038
|
function workflowsDir(projectRoot) {
|
|
14846
15039
|
return join$1(projectRoot, ".svamp", "workflows");
|
|
@@ -14860,7 +15053,11 @@ function normalizeOn(on) {
|
|
|
14860
15053
|
}
|
|
14861
15054
|
const out = {};
|
|
14862
15055
|
if (Array.isArray(on.schedule)) {
|
|
14863
|
-
const sched = on.schedule.map((s) =>
|
|
15056
|
+
const sched = on.schedule.map((s) => {
|
|
15057
|
+
const entry = { cron: String(s?.cron ?? s ?? "") };
|
|
15058
|
+
if (s && typeof s === "object" && s.tz != null && String(s.tz).trim()) entry.tz = String(s.tz).trim();
|
|
15059
|
+
return entry;
|
|
15060
|
+
}).filter((s) => s.cron);
|
|
14864
15061
|
if (sched.length) out.schedule = sched;
|
|
14865
15062
|
} else if (typeof on.schedule === "string" && on.schedule.trim()) {
|
|
14866
15063
|
out.schedule = [{ cron: on.schedule.trim() }];
|
|
@@ -14908,7 +15105,7 @@ function serializeWorkflow(wf) {
|
|
|
14908
15105
|
if (wf.session) clean.session = wf.session;
|
|
14909
15106
|
if (wf.enabled === false) clean.enabled = false;
|
|
14910
15107
|
const on = {};
|
|
14911
|
-
if (wf.on?.schedule?.length) on.schedule = wf.on.schedule.map((s) =>
|
|
15108
|
+
if (wf.on?.schedule?.length) on.schedule = wf.on.schedule.map((s) => s.tz ? { cron: s.cron, tz: s.tz } : { cron: s.cron });
|
|
14912
15109
|
if (wf.on?.workflow_dispatch) on.workflow_dispatch = {};
|
|
14913
15110
|
if (wf.on?.channel) on.channel = wf.on.channel;
|
|
14914
15111
|
if (wf.on?.issue?.length) on.issue = wf.on.issue;
|
|
@@ -15174,7 +15371,7 @@ function escalateWorkflowFailure(projectRoot, wf, run) {
|
|
|
15174
15371
|
const open = listIssues(projectRoot, { label: failLabel }).filter((i) => i.status !== "archived");
|
|
15175
15372
|
if (open.length > 0) return void 0;
|
|
15176
15373
|
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 ${
|
|
15374
|
+
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
15375
|
const defLines = workflowSteps(wf).map((s, i) => ` ${i}. ${s.run}`).join("\n") || " (no steps)";
|
|
15179
15376
|
const stderrTail = failed?.stderr ? capStream(failed.stderr, 3e3) : "(none)";
|
|
15180
15377
|
const stdoutTail = failed?.stdout ? capStream(failed.stdout, 1500) : "(none)";
|
|
@@ -15358,7 +15555,9 @@ function fireIdleWorkflows(root, sessionId, deps = {}) {
|
|
|
15358
15555
|
} catch {
|
|
15359
15556
|
return [];
|
|
15360
15557
|
}
|
|
15361
|
-
const matches = workflows.filter(
|
|
15558
|
+
const matches = workflows.filter(
|
|
15559
|
+
(wf) => wf.on?.idle === true && isWorkflowEnabled(wf) && (!wf.session || wf.session === sessionId)
|
|
15560
|
+
);
|
|
15362
15561
|
return matches.map((wf) => {
|
|
15363
15562
|
deps.log?.(`[workflow] idle fired "${wf.name}" in ${root} for session ${sessionId}`);
|
|
15364
15563
|
return runWorkflow(root, wf, {
|
|
@@ -16114,6 +16313,24 @@ async function readSessionFileBase64(resolvedPath) {
|
|
|
16114
16313
|
const buffer = await fs$1.readFile(resolvedPath);
|
|
16115
16314
|
return buffer.toString("base64");
|
|
16116
16315
|
}
|
|
16316
|
+
async function writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast) {
|
|
16317
|
+
const resolvedPath = resolve$1(directory, path);
|
|
16318
|
+
if (sessionMetadata?.securityContext && resolvedPath !== resolve$1(directory) && !resolvedPath.startsWith(resolve$1(directory) + "/")) {
|
|
16319
|
+
throw new Error("Path outside working directory");
|
|
16320
|
+
}
|
|
16321
|
+
const safeUploadId = String(uploadId).replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64) || "upload";
|
|
16322
|
+
const tempPath = `${resolvedPath}.svamp-upload-${safeUploadId}.part`;
|
|
16323
|
+
const buffer = Buffer.from(content || "", "base64");
|
|
16324
|
+
if (chunkIndex === 0) {
|
|
16325
|
+
await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
|
|
16326
|
+
await fs$1.writeFile(tempPath, buffer);
|
|
16327
|
+
} else {
|
|
16328
|
+
await fs$1.appendFile(tempPath, buffer);
|
|
16329
|
+
}
|
|
16330
|
+
if (isLast) {
|
|
16331
|
+
await fs$1.rename(tempPath, resolvedPath);
|
|
16332
|
+
}
|
|
16333
|
+
}
|
|
16117
16334
|
|
|
16118
16335
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
16119
16336
|
const __dirname$1 = dirname$1(__filename$1);
|
|
@@ -17612,7 +17829,7 @@ async function startDaemon(options) {
|
|
|
17612
17829
|
try {
|
|
17613
17830
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
17614
17831
|
if (!dir) return;
|
|
17615
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
17832
|
+
const { reconcileServiceLinks } = await import('./agentCommands-DdNUACV8.mjs');
|
|
17616
17833
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
17617
17834
|
const config = readSvampConfig(configPath);
|
|
17618
17835
|
const entries = Array.from(urls.entries());
|
|
@@ -17630,7 +17847,7 @@ async function startDaemon(options) {
|
|
|
17630
17847
|
}
|
|
17631
17848
|
}
|
|
17632
17849
|
async function createExposedTunnel(spec) {
|
|
17633
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
17850
|
+
const { FrpcTunnel } = await import('./frpc-Cv6J2Mex.mjs');
|
|
17634
17851
|
const tunnel = new FrpcTunnel({
|
|
17635
17852
|
name: spec.name,
|
|
17636
17853
|
ports: spec.ports,
|
|
@@ -17658,7 +17875,7 @@ async function startDaemon(options) {
|
|
|
17658
17875
|
ensureAutoInstalledCommands(logger);
|
|
17659
17876
|
(async () => {
|
|
17660
17877
|
try {
|
|
17661
|
-
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-
|
|
17878
|
+
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-C6MSkqS9.mjs');
|
|
17662
17879
|
beginClaudeVersionReconcile((msg) => logger.log(msg));
|
|
17663
17880
|
} catch (e) {
|
|
17664
17881
|
logger.log(`[claude-version] check failed: ${e?.message || e}`);
|
|
@@ -17941,8 +18158,16 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
|
|
|
17941
18158
|
if (RATELIMIT_CFG.maxRetries <= 0) return false;
|
|
17942
18159
|
if (trackedSession?.stopped) return false;
|
|
17943
18160
|
if (currentTurnMessage === void 0) return false;
|
|
17944
|
-
|
|
17945
|
-
|
|
18161
|
+
const nowTs = Date.now();
|
|
18162
|
+
if (rateLimitStreakStartedAt === 0) {
|
|
18163
|
+
rateLimitStreakStartedAt = nowTs;
|
|
18164
|
+
sessionMetadata = { ...sessionMetadata, rateLimitStreakStartedAt: nowTs };
|
|
18165
|
+
}
|
|
18166
|
+
const budget = retryStreakExhausted(rateLimitRetryCount, rateLimitStreakStartedAt, nowTs, RATELIMIT_CFG);
|
|
18167
|
+
if (budget.exhausted) {
|
|
18168
|
+
const elapsedMin = Math.round((nowTs - rateLimitStreakStartedAt) / 6e4);
|
|
18169
|
+
logger.log(`[Session ${sessionId}] Rate-limit retries exhausted (${budget.reason}: ${rateLimitRetryCount}/${RATELIMIT_CFG.maxRetries} attempts, ${elapsedMin}m elapsed) \u2014 surfacing error`);
|
|
18170
|
+
resetRateLimitStreak();
|
|
17946
18171
|
return false;
|
|
17947
18172
|
}
|
|
17948
18173
|
const attempt = rateLimitRetryCount++;
|
|
@@ -18410,18 +18635,18 @@ ${parts.join("\n")}`);
|
|
|
18410
18635
|
enqueueLoopMessage(push, label);
|
|
18411
18636
|
if (!trackedSession.stopped) setTimeout(() => processMessageQueueRef?.(), 200);
|
|
18412
18637
|
} 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 });
|
|
18638
|
+
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
18639
|
logger.log(`[Session ${sessionId}] [loop-persist] stuck-stop: ${decision.reason}`);
|
|
18415
18640
|
checkSvampConfig?.();
|
|
18416
18641
|
}
|
|
18417
18642
|
} else {
|
|
18418
|
-
writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now(), holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
|
|
18643
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now(), holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
|
|
18419
18644
|
checkSvampConfig?.();
|
|
18420
18645
|
}
|
|
18421
18646
|
} catch (e) {
|
|
18422
18647
|
logger.log(`[Session ${sessionId}] verifyGoalCompletion error \u2014 failing open (done): ${e?.message || e}`);
|
|
18423
18648
|
try {
|
|
18424
|
-
writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now() });
|
|
18649
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now() });
|
|
18425
18650
|
checkSvampConfig?.();
|
|
18426
18651
|
} catch {
|
|
18427
18652
|
}
|
|
@@ -18479,8 +18704,16 @@ ${parts.join("\n")}`);
|
|
|
18479
18704
|
const RATELIMIT_CFG = getRateLimitRetryConfig();
|
|
18480
18705
|
let currentTurnMessage;
|
|
18481
18706
|
let rateLimitRetryCount = 0;
|
|
18707
|
+
let rateLimitStreakStartedAt = Number(sessionMetadata?.rateLimitStreakStartedAt) || 0;
|
|
18482
18708
|
let rateLimitRetryTimer = null;
|
|
18483
18709
|
let rateLimitRetryScheduled = false;
|
|
18710
|
+
const resetRateLimitStreak = () => {
|
|
18711
|
+
rateLimitRetryCount = 0;
|
|
18712
|
+
rateLimitStreakStartedAt = 0;
|
|
18713
|
+
if (sessionMetadata?.rateLimitStreakStartedAt) {
|
|
18714
|
+
sessionMetadata = { ...sessionMetadata, rateLimitStreakStartedAt: 0 };
|
|
18715
|
+
}
|
|
18716
|
+
};
|
|
18484
18717
|
let checkSvampConfig;
|
|
18485
18718
|
let cleanupSvampConfig;
|
|
18486
18719
|
const VALID_CLAUDE_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
@@ -18895,7 +19128,7 @@ ${parts.join("\n")}`);
|
|
|
18895
19128
|
"event"
|
|
18896
19129
|
);
|
|
18897
19130
|
}
|
|
18898
|
-
if (!msg.is_error)
|
|
19131
|
+
if (!msg.is_error) resetRateLimitStreak();
|
|
18899
19132
|
if (msg.session_id) {
|
|
18900
19133
|
claudeResumeId = msg.session_id;
|
|
18901
19134
|
if (sessionMetadata.claudeSessionId !== msg.session_id) {
|
|
@@ -19467,7 +19700,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19467
19700
|
return;
|
|
19468
19701
|
}
|
|
19469
19702
|
currentTurnMessage = text;
|
|
19470
|
-
|
|
19703
|
+
resetRateLimitStreak();
|
|
19471
19704
|
if (rateLimitRetryTimer) {
|
|
19472
19705
|
clearTimeout(rateLimitRetryTimer);
|
|
19473
19706
|
rateLimitRetryTimer = null;
|
|
@@ -19491,7 +19724,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19491
19724
|
clearTimeout(rateLimitRetryTimer);
|
|
19492
19725
|
rateLimitRetryTimer = null;
|
|
19493
19726
|
}
|
|
19494
|
-
|
|
19727
|
+
resetRateLimitStreak();
|
|
19495
19728
|
if (claudeProcess && !claudeProcess.killed) {
|
|
19496
19729
|
try {
|
|
19497
19730
|
const interruptMsg = JSON.stringify({ type: "control_request", request: { type: "interrupt" } });
|
|
@@ -19848,11 +20081,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19848
20081
|
});
|
|
19849
20082
|
},
|
|
19850
20083
|
onIssue: async (params) => {
|
|
19851
|
-
const { issueRpc } = await import('./rpc-
|
|
20084
|
+
const { issueRpc } = await import('./rpc-CkcBAtcs.mjs');
|
|
19852
20085
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
19853
20086
|
},
|
|
19854
20087
|
onWorkflow: async (params) => {
|
|
19855
|
-
const { workflowRpc } = await import('./rpc-
|
|
20088
|
+
const { workflowRpc } = await import('./rpc-BwxwusfB.mjs');
|
|
19856
20089
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
19857
20090
|
},
|
|
19858
20091
|
onRipgrep: async (args, cwd) => {
|
|
@@ -19884,6 +20117,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19884
20117
|
await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
|
|
19885
20118
|
await fs$1.writeFile(resolvedPath, Buffer.from(content, "base64"));
|
|
19886
20119
|
},
|
|
20120
|
+
onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
|
|
20121
|
+
await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
|
|
20122
|
+
},
|
|
19887
20123
|
onListDirectory: async (path) => {
|
|
19888
20124
|
const resolvedDir = resolve$1(directory, path || ".");
|
|
19889
20125
|
if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
|
|
@@ -19893,8 +20129,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19893
20129
|
return entries.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }));
|
|
19894
20130
|
},
|
|
19895
20131
|
onGetDirectoryTree: async (treePath, maxDepth) => {
|
|
20132
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".svamp", ".expo", "dist", "build", ".next", ".cache", ".venv", "venv", "__pycache__", ".turbo", ".gradle", "target"]);
|
|
20133
|
+
const MAX_TREE_DEPTH = 8;
|
|
20134
|
+
const MAX_TREE_NODES = 2e4;
|
|
20135
|
+
const effectiveMaxDepth = Math.min(Math.max(0, Math.floor(Number(maxDepth)) || 0), MAX_TREE_DEPTH);
|
|
20136
|
+
let treeNodeCount = 0;
|
|
19896
20137
|
async function buildTree(p, name, depth) {
|
|
19897
20138
|
try {
|
|
20139
|
+
if (treeNodeCount >= MAX_TREE_NODES) return null;
|
|
20140
|
+
treeNodeCount++;
|
|
19898
20141
|
const stats = await fs$1.stat(p);
|
|
19899
20142
|
const node = {
|
|
19900
20143
|
name,
|
|
@@ -19903,11 +20146,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19903
20146
|
size: stats.size,
|
|
19904
20147
|
modified: stats.mtime.getTime()
|
|
19905
20148
|
};
|
|
19906
|
-
if (stats.isDirectory() && depth <
|
|
20149
|
+
if (stats.isDirectory() && depth < effectiveMaxDepth) {
|
|
19907
20150
|
const entries = await fs$1.readdir(p, { withFileTypes: true });
|
|
19908
20151
|
const children = [];
|
|
19909
20152
|
await Promise.all(entries.map(async (entry) => {
|
|
19910
20153
|
if (entry.isSymbolicLink()) return;
|
|
20154
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) return;
|
|
20155
|
+
if (treeNodeCount >= MAX_TREE_NODES) return;
|
|
19911
20156
|
const childPath = join(p, entry.name);
|
|
19912
20157
|
const childNode = await buildTree(childPath, entry.name, depth + 1);
|
|
19913
20158
|
if (childNode) children.push(childNode);
|
|
@@ -19978,7 +20223,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19978
20223
|
userMessagePending = true;
|
|
19979
20224
|
turnInitiatedByUser = true;
|
|
19980
20225
|
currentTurnMessage = next.text;
|
|
19981
|
-
|
|
20226
|
+
resetRateLimitStreak();
|
|
19982
20227
|
if (rateLimitRetryTimer) {
|
|
19983
20228
|
clearTimeout(rateLimitRetryTimer);
|
|
19984
20229
|
rateLimitRetryTimer = null;
|
|
@@ -20257,6 +20502,30 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20257
20502
|
};
|
|
20258
20503
|
sessionService.updateMetadata(sessionMetadata);
|
|
20259
20504
|
};
|
|
20505
|
+
const drainQueueHeadIfIdle = () => {
|
|
20506
|
+
if (acpStopped || !acpBackendReady) return;
|
|
20507
|
+
if (sessionMetadata.lifecycleState !== "idle") return;
|
|
20508
|
+
const queue = sessionMetadata.messageQueue;
|
|
20509
|
+
if (!queue || queue.length === 0) return;
|
|
20510
|
+
const next = queue[0];
|
|
20511
|
+
const remaining = queue.slice(1);
|
|
20512
|
+
sessionMetadata = { ...sessionMetadata, messageQueue: remaining.length > 0 ? remaining : void 0, lifecycleState: "running" };
|
|
20513
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
20514
|
+
if (typeof next.inboxHop === "number") {
|
|
20515
|
+
writeInboundContext(sessionId, { hopCount: next.inboxHop, threadId: next.inboxThreadId });
|
|
20516
|
+
}
|
|
20517
|
+
sessionService.markInboxHandled(next.id);
|
|
20518
|
+
if (!next.alreadyStored) sessionService.pushMessage(next.displayText || next.text, "user");
|
|
20519
|
+
sessionService.sendKeepAlive(true);
|
|
20520
|
+
agentBackend.sendPrompt(sessionId, next.text).catch((err) => {
|
|
20521
|
+
logger.error(`[${agentName} Session ${sessionId}] Error draining queued message after inject miss: ${err?.message ?? err}`);
|
|
20522
|
+
if (!acpStopped) {
|
|
20523
|
+
sessionMetadata = { ...sessionMetadata, lifecycleState: "idle" };
|
|
20524
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
20525
|
+
sessionService.sendSessionEnd();
|
|
20526
|
+
}
|
|
20527
|
+
});
|
|
20528
|
+
};
|
|
20260
20529
|
const injectFn = agentBackend.injectInput;
|
|
20261
20530
|
if (typeof injectFn === "function") {
|
|
20262
20531
|
Promise.resolve(injectFn.call(agentBackend, text)).then((injected) => {
|
|
@@ -20265,10 +20534,12 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20265
20534
|
} else {
|
|
20266
20535
|
logger.log(`[${agentName} Session ${sessionId}] No active turn to inject \u2014 queuing message`);
|
|
20267
20536
|
enqueueBusy();
|
|
20537
|
+
drainQueueHeadIfIdle();
|
|
20268
20538
|
}
|
|
20269
20539
|
}).catch((err) => {
|
|
20270
20540
|
logger.error(`[${agentName} Session ${sessionId}] Mid-turn inject failed \u2014 queuing:`, err?.message ?? err);
|
|
20271
20541
|
enqueueBusy();
|
|
20542
|
+
drainQueueHeadIfIdle();
|
|
20272
20543
|
});
|
|
20273
20544
|
return;
|
|
20274
20545
|
}
|
|
@@ -20484,11 +20755,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20484
20755
|
});
|
|
20485
20756
|
},
|
|
20486
20757
|
onIssue: async (params) => {
|
|
20487
|
-
const { issueRpc } = await import('./rpc-
|
|
20758
|
+
const { issueRpc } = await import('./rpc-CkcBAtcs.mjs');
|
|
20488
20759
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
20489
20760
|
},
|
|
20490
20761
|
onWorkflow: async (params) => {
|
|
20491
|
-
const { workflowRpc } = await import('./rpc-
|
|
20762
|
+
const { workflowRpc } = await import('./rpc-BwxwusfB.mjs');
|
|
20492
20763
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
20493
20764
|
},
|
|
20494
20765
|
onRipgrep: async (args, cwd) => {
|
|
@@ -20520,6 +20791,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20520
20791
|
await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
|
|
20521
20792
|
await fs$1.writeFile(resolvedPath, Buffer.from(content, "base64"));
|
|
20522
20793
|
},
|
|
20794
|
+
onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
|
|
20795
|
+
await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
|
|
20796
|
+
},
|
|
20523
20797
|
onListDirectory: async (path) => {
|
|
20524
20798
|
const resolvedDir = resolve$1(directory, path || ".");
|
|
20525
20799
|
if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
|
|
@@ -20529,8 +20803,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20529
20803
|
return entries.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }));
|
|
20530
20804
|
},
|
|
20531
20805
|
onGetDirectoryTree: async (treePath, maxDepth) => {
|
|
20806
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".svamp", ".expo", "dist", "build", ".next", ".cache", ".venv", "venv", "__pycache__", ".turbo", ".gradle", "target"]);
|
|
20807
|
+
const MAX_TREE_DEPTH = 8;
|
|
20808
|
+
const MAX_TREE_NODES = 2e4;
|
|
20809
|
+
const effectiveMaxDepth = Math.min(Math.max(0, Math.floor(Number(maxDepth)) || 0), MAX_TREE_DEPTH);
|
|
20810
|
+
let treeNodeCount = 0;
|
|
20532
20811
|
async function buildTree(p, name, depth) {
|
|
20533
20812
|
try {
|
|
20813
|
+
if (treeNodeCount >= MAX_TREE_NODES) return null;
|
|
20814
|
+
treeNodeCount++;
|
|
20534
20815
|
const stats = await fs$1.stat(p);
|
|
20535
20816
|
const node = {
|
|
20536
20817
|
name,
|
|
@@ -20539,11 +20820,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20539
20820
|
size: stats.size,
|
|
20540
20821
|
modified: stats.mtime.getTime()
|
|
20541
20822
|
};
|
|
20542
|
-
if (stats.isDirectory() && depth <
|
|
20823
|
+
if (stats.isDirectory() && depth < effectiveMaxDepth) {
|
|
20543
20824
|
const entries = await fs$1.readdir(p, { withFileTypes: true });
|
|
20544
20825
|
const children = [];
|
|
20545
20826
|
await Promise.all(entries.map(async (entry) => {
|
|
20546
20827
|
if (entry.isSymbolicLink()) return;
|
|
20828
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) return;
|
|
20829
|
+
if (treeNodeCount >= MAX_TREE_NODES) return;
|
|
20547
20830
|
const childPath = join(p, entry.name);
|
|
20548
20831
|
const childNode = await buildTree(childPath, entry.name, depth + 1);
|
|
20549
20832
|
if (childNode) children.push(childNode);
|
|
@@ -21434,7 +21717,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21434
21717
|
}
|
|
21435
21718
|
if (persistedSessions.length > 0) {
|
|
21436
21719
|
try {
|
|
21437
|
-
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-
|
|
21720
|
+
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-C6MSkqS9.mjs');
|
|
21438
21721
|
await awaitClaudeVersionReady();
|
|
21439
21722
|
} catch {
|
|
21440
21723
|
}
|
|
@@ -21639,7 +21922,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21639
21922
|
const PING_TIMEOUT_MS = 15e3;
|
|
21640
21923
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
21641
21924
|
const RECONNECT_JITTER_MS = 2500;
|
|
21642
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
21925
|
+
const { WorkflowScheduler } = await import('./scheduler-CurN4UtP.mjs');
|
|
21643
21926
|
const workflowProjectRoots = () => {
|
|
21644
21927
|
const dirs = /* @__PURE__ */ new Set();
|
|
21645
21928
|
for (const s of pidToTrackedSession.values()) {
|
|
@@ -21928,6 +22211,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21928
22211
|
logger.log(`Cleaning up (source: ${source})...`);
|
|
21929
22212
|
clearInterval(heartbeatInterval);
|
|
21930
22213
|
clearInterval(workflowSchedulerInterval);
|
|
22214
|
+
clearInterval(backendAccountRefreshInterval);
|
|
21931
22215
|
if (proxyTokenRefreshInterval) clearInterval(proxyTokenRefreshInterval);
|
|
21932
22216
|
if (oauthRefreshInterval) clearInterval(oauthRefreshInterval);
|
|
21933
22217
|
if (unhandledRejectionResetTimer) clearTimeout(unhandledRejectionResetTimer);
|
|
@@ -22272,4 +22556,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
22272
22556
|
writeStopMarker: writeStopMarker
|
|
22273
22557
|
});
|
|
22274
22558
|
|
|
22275
|
-
export {
|
|
22559
|
+
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 };
|