svamp-cli 0.2.307 → 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-DZ9MECkY.mjs → adminCommands-BTLHQXmA.mjs} +2 -2
- package/dist/{agentCommands-D68wBf-y.mjs → agentCommands-DdNUACV8.mjs} +6 -6
- package/dist/{auth-DDgr4YFE.mjs → auth-Bn8TbaOP.mjs} +2 -2
- package/dist/{cli-Bk6IPB9M.mjs → cli-BaDWWDiz.mjs} +76 -70
- package/dist/cli.mjs +3 -3
- package/dist/{commands-Dh2S9Gf1.mjs → commands-2uOorIjB.mjs} +2 -2
- package/dist/{commands-BlIR3KBq.mjs → commands-BPgxgkXW.mjs} +3 -3
- package/dist/{commands-CJVqMnNf.mjs → commands-BTN9XxVx.mjs} +3 -2
- package/dist/{commands-rGFSTXKu.mjs → commands-Bb0oF3eZ.mjs} +37 -6
- package/dist/{commands-Bl9AtWkD.mjs → commands-C2u988x1.mjs} +3 -3
- package/dist/{commands-DsfIIri_.mjs → commands-DGbLwT_h.mjs} +23 -11
- package/dist/{commands-DJwSJOPD.mjs → commands-DPvy0iVT.mjs} +2 -2
- package/dist/{commands-CESl13XD.mjs → commands-r86haNJD.mjs} +3 -3
- package/dist/{fleet-okzhGLEY.mjs → fleet-CeLE0t97.mjs} +40 -4
- package/dist/{frpc-NDcBtpwa.mjs → frpc-Cv6J2Mex.mjs} +5 -2
- package/dist/{headlessCli-BkU5B5Jp.mjs → headlessCli-BXc9Fvzb.mjs} +3 -3
- package/dist/index.mjs +2 -2
- package/dist/{notifyCommands-Oh0kvQgj.mjs → notifyCommands-iVnI_Lb2.mjs} +2 -2
- package/dist/{package-k1G6ygGP.mjs → package-CeseSmYI.mjs} +2 -2
- package/dist/{pinnedClaudeCode-BaMR97BE.mjs → pinnedClaudeCode-C6MSkqS9.mjs} +1 -1
- package/dist/{rpc-DSyBSuTF.mjs → rpc-BwxwusfB.mjs} +2 -2
- package/dist/{rpc-K5xzddMF.mjs → rpc-CkcBAtcs.mjs} +2 -2
- package/dist/{run-09G0RL2B.mjs → run-2Stz5l7J.mjs} +2 -2
- package/dist/{run-DYVmYsvg.mjs → run-DLG-_xa7.mjs} +471 -94
- package/dist/{scheduler-CtKlSyIq.mjs → scheduler-CurN4UtP.mjs} +5 -5
- package/dist/{serveCommands-BnOmV2Z3.mjs → serveCommands-CtVZhR-V.mjs} +5 -5
- package/dist/{sideband-CdJ0O6L6.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",
|
|
@@ -4857,8 +4988,43 @@ const SESSION_METHOD_MIN_ROLE = {
|
|
|
4857
4988
|
refineLastReply: "admin",
|
|
4858
4989
|
undoLastEdit: "admin"
|
|
4859
4990
|
};
|
|
4991
|
+
const SESSION_VIEW_METHODS = /* @__PURE__ */ new Set([
|
|
4992
|
+
// read-only (authorizeRequest at view-role)
|
|
4993
|
+
"getMessages",
|
|
4994
|
+
"getLatestMessages",
|
|
4995
|
+
"getMessageCount",
|
|
4996
|
+
"getMetadata",
|
|
4997
|
+
"getSharing",
|
|
4998
|
+
"getInbox",
|
|
4999
|
+
"getEffectiveRole",
|
|
5000
|
+
"getActivityState",
|
|
5001
|
+
"getAgentState",
|
|
5002
|
+
"getChannelSkill",
|
|
5003
|
+
"getMeetingActivity",
|
|
5004
|
+
"listChannels",
|
|
5005
|
+
"outpostList",
|
|
5006
|
+
"registerListener",
|
|
5007
|
+
// self-authenticating (own capability key / token) or liveness — machine role stays view
|
|
5008
|
+
"channelDescribe",
|
|
5009
|
+
"channelExecTool",
|
|
5010
|
+
"channelGetProfile",
|
|
5011
|
+
"channelList",
|
|
5012
|
+
"channelOwns",
|
|
5013
|
+
"channelReceive",
|
|
5014
|
+
"channelSend",
|
|
5015
|
+
"channelUpload",
|
|
5016
|
+
"outpostHello",
|
|
5017
|
+
"outpostPoll",
|
|
5018
|
+
"outpostResult",
|
|
5019
|
+
"disconnect",
|
|
5020
|
+
"reregister",
|
|
5021
|
+
"recordMeetingActivity"
|
|
5022
|
+
]);
|
|
4860
5023
|
function sessionMethodMinRole(method) {
|
|
4861
|
-
|
|
5024
|
+
const mapped = SESSION_METHOD_MIN_ROLE[method];
|
|
5025
|
+
if (mapped) return mapped;
|
|
5026
|
+
if (SESSION_VIEW_METHODS.has(method)) return "view";
|
|
5027
|
+
return "admin";
|
|
4862
5028
|
}
|
|
4863
5029
|
function getParamNames(fn) {
|
|
4864
5030
|
const src = fn.toString();
|
|
@@ -6088,7 +6254,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
6088
6254
|
const tunnels = handlers.tunnels;
|
|
6089
6255
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
6090
6256
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
6091
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
6257
|
+
const { FrpcTunnel } = await import('./frpc-Cv6J2Mex.mjs');
|
|
6092
6258
|
const tunnel = new FrpcTunnel({
|
|
6093
6259
|
name: params.name,
|
|
6094
6260
|
ports: params.ports,
|
|
@@ -6573,7 +6739,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6573
6739
|
let owner;
|
|
6574
6740
|
try {
|
|
6575
6741
|
role2 = (await rpc.getEffectiveRole(context))?.role ?? null;
|
|
6576
|
-
if (!role2) return { success: false, error: "Not authorized
|
|
6742
|
+
if (!roleAtLeast(role2, "interact")) return { success: false, error: "Not authorized: WISE ask requires interact access to this session." };
|
|
6577
6743
|
const m = await rpc.getMetadata(context);
|
|
6578
6744
|
cwd = m?.metadata?.path;
|
|
6579
6745
|
owner = m?.metadata?.sharing?.owner;
|
|
@@ -6582,12 +6748,12 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6582
6748
|
}
|
|
6583
6749
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
6584
6750
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
6585
|
-
const { toolsForRole } = await import('./sideband-
|
|
6751
|
+
const { toolsForRole } = await import('./sideband-C_3PVGh-.mjs');
|
|
6586
6752
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
6587
6753
|
return fmt(r2);
|
|
6588
6754
|
}
|
|
6589
6755
|
const role = getEffectiveRole(context, currentMetadata.sharing);
|
|
6590
|
-
if (!role) return { success: false, error: "Not authorized
|
|
6756
|
+
if (!roleAtLeast(role, "interact")) return { success: false, error: "Not authorized: WISE ask requires interact access to this machine." };
|
|
6591
6757
|
const machineDeps = buildMachineDeps(
|
|
6592
6758
|
{
|
|
6593
6759
|
getSessionIds: handlers.getSessionIds,
|
|
@@ -6687,7 +6853,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6687
6853
|
return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
|
|
6688
6854
|
}
|
|
6689
6855
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
6690
|
-
const { queryCore } = await import('./commands-
|
|
6856
|
+
const { queryCore } = await import('./commands-BTN9XxVx.mjs');
|
|
6691
6857
|
const timeout = c.reply?.timeout_sec || 120;
|
|
6692
6858
|
let result;
|
|
6693
6859
|
try {
|
|
@@ -7197,9 +7363,17 @@ function getRateLimitRetryConfig() {
|
|
|
7197
7363
|
return {
|
|
7198
7364
|
maxRetries: envInt("SVAMP_RATELIMIT_MAX_RETRIES", 200),
|
|
7199
7365
|
baseMs: envInt("SVAMP_RATELIMIT_BASE_MS", 5e3),
|
|
7200
|
-
capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4)
|
|
7366
|
+
capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4),
|
|
7367
|
+
maxElapsedMs: envInt("SVAMP_RATELIMIT_MAX_ELAPSED_MS", 36e5)
|
|
7201
7368
|
};
|
|
7202
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
|
+
}
|
|
7203
7377
|
function computeRetryDelayMs(attempt, cfg = getRateLimitRetryConfig(), rng = Math.random) {
|
|
7204
7378
|
const a = Math.max(0, Math.floor(attempt));
|
|
7205
7379
|
const expo = Math.min(a, 30);
|
|
@@ -7378,6 +7552,9 @@ async function deletePairingArtifact(am, artifactId, log) {
|
|
|
7378
7552
|
}
|
|
7379
7553
|
}
|
|
7380
7554
|
async function sweepExpiredPairings(am, svampHome, log) {
|
|
7555
|
+
return withMintLock("*sweep*", () => _sweepExpiredPairingsUnlocked(am, svampHome, log));
|
|
7556
|
+
}
|
|
7557
|
+
async function _sweepExpiredPairingsUnlocked(am, svampHome, log) {
|
|
7381
7558
|
const now = Date.now();
|
|
7382
7559
|
const all = loadPairings(svampHome);
|
|
7383
7560
|
const expired = all.filter((p) => p.expiresAt <= now);
|
|
@@ -7385,18 +7562,40 @@ async function sweepExpiredPairings(am, svampHome, log) {
|
|
|
7385
7562
|
for (const p of expired) await deletePairingArtifact(am, p.artifactId, log);
|
|
7386
7563
|
savePairings(all.filter((p) => p.expiresAt > now), svampHome);
|
|
7387
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
|
+
}
|
|
7388
7581
|
async function burnSessionPairings(am, session, svampHome, log) {
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
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
|
+
});
|
|
7394
7589
|
}
|
|
7395
7590
|
async function mintPairingCode(opts) {
|
|
7396
|
-
const {
|
|
7591
|
+
const { session} = opts;
|
|
7397
7592
|
const resolver = (opts.resolverBase || "https://hypha.aicell.io").replace(/\/$/, "");
|
|
7398
7593
|
const ttlMs = opts.ttlMs && opts.ttlMs > 0 ? opts.ttlMs : DEFAULT_PAIRING_TTL_MS;
|
|
7399
|
-
|
|
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(() => {
|
|
7400
7599
|
});
|
|
7401
7600
|
const now = Date.now();
|
|
7402
7601
|
const existing = loadPairings(opts.svampHome).find((p) => p.session === session && p.expiresAt > now + 3e4);
|
|
@@ -7457,6 +7656,7 @@ class OutpostCoordinator {
|
|
|
7457
7656
|
clearTimer;
|
|
7458
7657
|
execTimeoutMs;
|
|
7459
7658
|
staleMs;
|
|
7659
|
+
randToken;
|
|
7460
7660
|
sweeper = null;
|
|
7461
7661
|
constructor(opts = {}) {
|
|
7462
7662
|
this.now = opts.now || (() => Date.now());
|
|
@@ -7464,6 +7664,7 @@ class OutpostCoordinator {
|
|
|
7464
7664
|
this.clearTimer = opts.clearTimer || ((t) => clearTimeout(t));
|
|
7465
7665
|
this.execTimeoutMs = opts.execTimeoutMs ?? OUTPOST_EXEC_TIMEOUT_MS;
|
|
7466
7666
|
this.staleMs = opts.staleMs ?? OUTPOST_STALE_MS;
|
|
7667
|
+
this.randToken = opts.randToken || (() => randomBytes(9).toString("base64url"));
|
|
7467
7668
|
}
|
|
7468
7669
|
isStale(c, at) {
|
|
7469
7670
|
return at - c.lastSeen > this.staleMs;
|
|
@@ -7575,7 +7776,7 @@ class OutpostCoordinator {
|
|
|
7575
7776
|
* machine is not connected.
|
|
7576
7777
|
*/
|
|
7577
7778
|
enqueueExec(machine, cmd, cwd) {
|
|
7578
|
-
const id = `x${(++this.seq).toString(36)}_${this.now().toString(36)}`;
|
|
7779
|
+
const id = `x${(++this.seq).toString(36)}_${this.now().toString(36)}_${this.randToken()}`;
|
|
7579
7780
|
if (!this.isConnected(machine)) {
|
|
7580
7781
|
return {
|
|
7581
7782
|
id,
|
|
@@ -8035,6 +8236,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8035
8236
|
const channelOutbox = new ChannelOutbox(initialMetadata.path);
|
|
8036
8237
|
const outpostCoordinator = new OutpostCoordinator();
|
|
8037
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
|
+
})();
|
|
8038
8246
|
const outpostProjectDir = () => metadata.path || process.cwd();
|
|
8039
8247
|
const announceOutpost = (conn) => {
|
|
8040
8248
|
const who = conn.label || conn.machine;
|
|
@@ -8810,7 +9018,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8810
9018
|
});
|
|
8811
9019
|
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
8812
9020
|
const cursor = Math.max(0, Number(params.cursor) || 0);
|
|
8813
|
-
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);
|
|
8814
9024
|
const out = await channelOutbox.wait(c.id, cursor, r.sender.name, waitMs, params.correlationId);
|
|
8815
9025
|
return { ok: true, replies: out.replies, cursor: out.cursor };
|
|
8816
9026
|
},
|
|
@@ -8921,6 +9131,12 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8921
9131
|
await callbacks.onWriteFile(path, content);
|
|
8922
9132
|
return { success: true };
|
|
8923
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
|
+
},
|
|
8924
9140
|
listDirectory: async (path, context) => {
|
|
8925
9141
|
authorizeRequest(context, metadata.sharing, "admin");
|
|
8926
9142
|
if (!callbacks.onListDirectory) throw new Error("listDirectory not supported");
|
|
@@ -10470,8 +10686,23 @@ function computeCollectionConfigUpdate(existingConfig, extra) {
|
|
|
10470
10686
|
function emailHash(email) {
|
|
10471
10687
|
return createHash("sha256").update(email.toLowerCase()).digest("hex").slice(0, 12);
|
|
10472
10688
|
}
|
|
10689
|
+
function sessionIdHash(sessionId) {
|
|
10690
|
+
return createHash("sha256").update(sessionId).digest("hex").slice(0, 12);
|
|
10691
|
+
}
|
|
10692
|
+
function roleLabel(role) {
|
|
10693
|
+
switch (role) {
|
|
10694
|
+
case "admin":
|
|
10695
|
+
return "full access";
|
|
10696
|
+
case "interact":
|
|
10697
|
+
return "can interact";
|
|
10698
|
+
case "view":
|
|
10699
|
+
return "view only";
|
|
10700
|
+
default:
|
|
10701
|
+
return String(role);
|
|
10702
|
+
}
|
|
10703
|
+
}
|
|
10473
10704
|
function shareAlias(sessionId, recipientEmail) {
|
|
10474
|
-
return `share-${sessionId
|
|
10705
|
+
return `share-${sessionIdHash(sessionId)}-${emailHash(recipientEmail)}`;
|
|
10475
10706
|
}
|
|
10476
10707
|
function eventAlias(eventId, recipientEmail) {
|
|
10477
10708
|
return `evt-${emailHash(recipientEmail)}-${eventId.slice(0, 8)}`;
|
|
@@ -10585,8 +10816,10 @@ class SharingNotificationSync {
|
|
|
10585
10816
|
userEmail: params.ownerEmail
|
|
10586
10817
|
},
|
|
10587
10818
|
title: `${typeLabel} shared with you`,
|
|
10588
|
-
//
|
|
10589
|
-
|
|
10819
|
+
// #0689: render the recipient's ACTUAL role (view/interact/admin) — authorize.ts genuinely
|
|
10820
|
+
// enforces per-user roles, so the old hardcoded "(full access)" misstated a view/interact
|
|
10821
|
+
// invitee's access.
|
|
10822
|
+
body: `${params.ownerEmail} shared "${params.label || "Untitled"}" with you (${roleLabel(params.role)})`,
|
|
10590
10823
|
level: "info",
|
|
10591
10824
|
action: shareType === "session" ? {
|
|
10592
10825
|
type: "add-session-bookmark",
|
|
@@ -10766,13 +10999,15 @@ class SharingNotificationSync {
|
|
|
10766
10999
|
// ── Sharing config diff + sync ───────────────────────────────────
|
|
10767
11000
|
async syncSharing(sessionId, oldSharing, newSharing, context) {
|
|
10768
11001
|
if (!this.initialized) return;
|
|
10769
|
-
const
|
|
10770
|
-
(oldSharing?.allowedUsers || []).map((u) => u.email.toLowerCase())
|
|
11002
|
+
const oldRoleByEmail = new Map(
|
|
11003
|
+
(oldSharing?.allowedUsers || []).map((u) => [u.email.toLowerCase(), u.role])
|
|
10771
11004
|
);
|
|
11005
|
+
const oldEmails = new Set(oldRoleByEmail.keys());
|
|
10772
11006
|
const newUsers = newSharing.allowedUsers || [];
|
|
10773
11007
|
const newEmails = new Set(newUsers.map((u) => u.email.toLowerCase()));
|
|
10774
11008
|
for (const user of newUsers) {
|
|
10775
|
-
|
|
11009
|
+
const prevRole = oldRoleByEmail.get(user.email.toLowerCase());
|
|
11010
|
+
if (prevRole === void 0 || prevRole !== user.role) {
|
|
10776
11011
|
this.notifyShare({
|
|
10777
11012
|
recipientEmail: user.email,
|
|
10778
11013
|
sessionId,
|
|
@@ -12209,6 +12444,16 @@ function codexCumulativeTokens(payload) {
|
|
|
12209
12444
|
return nonCachedInput + output + Math.round(cached * 0.1);
|
|
12210
12445
|
}
|
|
12211
12446
|
|
|
12447
|
+
const PINNED_CODEX_VERSION = "0.146.0";
|
|
12448
|
+
const MIN_CODEX_STEER_VERSION = { major: 0, minor: 129, patch: 0 };
|
|
12449
|
+
function cmpVer(a, b) {
|
|
12450
|
+
return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
|
|
12451
|
+
}
|
|
12452
|
+
function codexSupportsSteer(v) {
|
|
12453
|
+
if (!v) return false;
|
|
12454
|
+
return cmpVer(v, MIN_CODEX_STEER_VERSION) >= 0;
|
|
12455
|
+
}
|
|
12456
|
+
|
|
12212
12457
|
function parseCodexVersion(v) {
|
|
12213
12458
|
const m = v.match(/codex-cli\s+(\d+)\.(\d+)\.(\d+)/);
|
|
12214
12459
|
if (!m) return null;
|
|
@@ -12225,6 +12470,31 @@ function codexAppServerAvailable() {
|
|
|
12225
12470
|
return null;
|
|
12226
12471
|
}
|
|
12227
12472
|
}
|
|
12473
|
+
const _execFileAsync = promisify$1(execFile$1);
|
|
12474
|
+
let _steerSupport = null;
|
|
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;
|
|
12497
|
+
}
|
|
12228
12498
|
class CodexAppServerClient {
|
|
12229
12499
|
constructor(opts) {
|
|
12230
12500
|
this.opts = opts;
|
|
@@ -12381,19 +12651,36 @@ class CodexAppServerClient {
|
|
|
12381
12651
|
/**
|
|
12382
12652
|
* Inject additional input into the CURRENTLY RUNNING turn (Claude-Code-style mid-turn steering).
|
|
12383
12653
|
*
|
|
12384
|
-
* codex app-server
|
|
12385
|
-
*
|
|
12386
|
-
*
|
|
12387
|
-
*
|
|
12388
|
-
*
|
|
12654
|
+
* Uses codex app-server's NATIVE `turn/steer` — the correct primitive for "append this message to
|
|
12655
|
+
* the in-flight turn without interrupting it." The steered input is applied at the next model step
|
|
12656
|
+
* (it does NOT abort a running tool call), the assistant finishes the current work and drains the
|
|
12657
|
+
* steered message as user input before its next LLM call, and NO separate `turn/started` /
|
|
12658
|
+
* `turn/completed` fires — the original single in-flight completion still resolves `sendTurnAndWait`.
|
|
12659
|
+
* So we send it WITHOUT arming a new `pendingTurn` slot and WITHOUT overwriting `_turnId`.
|
|
12660
|
+
*
|
|
12661
|
+
* (The earlier #86 implementation fired a second `turn/start` mid-turn; on codex 0.144 that
|
|
12662
|
+
* ABORTS/REPLACES the running turn instead of folding — the "cuts off the previous task" bug.)
|
|
12389
12663
|
*
|
|
12390
|
-
* Returns false when there is
|
|
12391
|
-
* turn
|
|
12664
|
+
* Returns false when there is no active turn to steer, or when the server REJECTS the steer
|
|
12665
|
+
* (no active turn / expectedTurnId mismatch / non-steerable turn kind such as review or manual
|
|
12666
|
+
* compact). The caller then falls back to client-side queueing (post-turn drain via sendPrompt),
|
|
12667
|
+
* so a rejected steer degrades gracefully rather than losing the message.
|
|
12392
12668
|
*/
|
|
12393
|
-
async injectInput(prompt,
|
|
12394
|
-
if (!this._threadId || !this.pendingTurn) return false;
|
|
12395
|
-
|
|
12396
|
-
|
|
12669
|
+
async injectInput(prompt, _o) {
|
|
12670
|
+
if (!this._threadId || !this.pendingTurn || !this._turnId) return false;
|
|
12671
|
+
if (!await codexSupportsSteerInstalledAsync()) return false;
|
|
12672
|
+
const params = {
|
|
12673
|
+
threadId: this._threadId,
|
|
12674
|
+
expectedTurnId: this._turnId,
|
|
12675
|
+
input: [{ type: "text", text: prompt }]
|
|
12676
|
+
};
|
|
12677
|
+
try {
|
|
12678
|
+
await this.request("turn/steer", params);
|
|
12679
|
+
return true;
|
|
12680
|
+
} catch (e) {
|
|
12681
|
+
this.log("[codex-app-server] turn/steer rejected \u2014 falling back to post-turn queue", e);
|
|
12682
|
+
return false;
|
|
12683
|
+
}
|
|
12397
12684
|
}
|
|
12398
12685
|
/** Interrupt the in-flight turn (real server-side abort — the old MCP path couldn't do this). */
|
|
12399
12686
|
async interrupt() {
|
|
@@ -12436,17 +12723,16 @@ class CodexAppServerClient {
|
|
|
12436
12723
|
// ── JSON-RPC plumbing ──────────────────────────────────────────────────────
|
|
12437
12724
|
request(method, params, timeoutMs) {
|
|
12438
12725
|
const id = this.nextId++;
|
|
12726
|
+
const effTimeout = timeoutMs ?? this.opts.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
12439
12727
|
return new Promise((resolve, reject) => {
|
|
12440
12728
|
this.pending.set(id, { resolve, reject, method });
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
t.unref?.();
|
|
12449
|
-
}
|
|
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?.();
|
|
12450
12736
|
this.write({ jsonrpc: "2.0", id, method, params });
|
|
12451
12737
|
});
|
|
12452
12738
|
}
|
|
@@ -14746,8 +15032,8 @@ function isWorkflowEnabled(wf) {
|
|
|
14746
15032
|
function workflowSteps(wf) {
|
|
14747
15033
|
return Object.values(wf.jobs || {}).flatMap((j) => j?.steps || []);
|
|
14748
15034
|
}
|
|
14749
|
-
function
|
|
14750
|
-
return (wf.on?.schedule || []).
|
|
15035
|
+
function workflowSchedules(wf) {
|
|
15036
|
+
return (wf.on?.schedule || []).filter((s) => !!s?.cron);
|
|
14751
15037
|
}
|
|
14752
15038
|
function workflowsDir(projectRoot) {
|
|
14753
15039
|
return join$1(projectRoot, ".svamp", "workflows");
|
|
@@ -14767,7 +15053,11 @@ function normalizeOn(on) {
|
|
|
14767
15053
|
}
|
|
14768
15054
|
const out = {};
|
|
14769
15055
|
if (Array.isArray(on.schedule)) {
|
|
14770
|
-
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);
|
|
14771
15061
|
if (sched.length) out.schedule = sched;
|
|
14772
15062
|
} else if (typeof on.schedule === "string" && on.schedule.trim()) {
|
|
14773
15063
|
out.schedule = [{ cron: on.schedule.trim() }];
|
|
@@ -14815,7 +15105,7 @@ function serializeWorkflow(wf) {
|
|
|
14815
15105
|
if (wf.session) clean.session = wf.session;
|
|
14816
15106
|
if (wf.enabled === false) clean.enabled = false;
|
|
14817
15107
|
const on = {};
|
|
14818
|
-
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 });
|
|
14819
15109
|
if (wf.on?.workflow_dispatch) on.workflow_dispatch = {};
|
|
14820
15110
|
if (wf.on?.channel) on.channel = wf.on.channel;
|
|
14821
15111
|
if (wf.on?.issue?.length) on.issue = wf.on.issue;
|
|
@@ -15081,7 +15371,7 @@ function escalateWorkflowFailure(projectRoot, wf, run) {
|
|
|
15081
15371
|
const open = listIssues(projectRoot, { label: failLabel }).filter((i) => i.status !== "archived");
|
|
15082
15372
|
if (open.length > 0) return void 0;
|
|
15083
15373
|
const failed = run.steps.find((s) => s.timedOut || s.exitCode !== 0 && s.exitCode !== null) || run.steps[run.steps.length - 1];
|
|
15084
|
-
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)"}`;
|
|
15085
15375
|
const defLines = workflowSteps(wf).map((s, i) => ` ${i}. ${s.run}`).join("\n") || " (no steps)";
|
|
15086
15376
|
const stderrTail = failed?.stderr ? capStream(failed.stderr, 3e3) : "(none)";
|
|
15087
15377
|
const stdoutTail = failed?.stdout ? capStream(failed.stdout, 1500) : "(none)";
|
|
@@ -15265,7 +15555,9 @@ function fireIdleWorkflows(root, sessionId, deps = {}) {
|
|
|
15265
15555
|
} catch {
|
|
15266
15556
|
return [];
|
|
15267
15557
|
}
|
|
15268
|
-
const matches = workflows.filter(
|
|
15558
|
+
const matches = workflows.filter(
|
|
15559
|
+
(wf) => wf.on?.idle === true && isWorkflowEnabled(wf) && (!wf.session || wf.session === sessionId)
|
|
15560
|
+
);
|
|
15269
15561
|
return matches.map((wf) => {
|
|
15270
15562
|
deps.log?.(`[workflow] idle fired "${wf.name}" in ${root} for session ${sessionId}`);
|
|
15271
15563
|
return runWorkflow(root, wf, {
|
|
@@ -16021,6 +16313,24 @@ async function readSessionFileBase64(resolvedPath) {
|
|
|
16021
16313
|
const buffer = await fs$1.readFile(resolvedPath);
|
|
16022
16314
|
return buffer.toString("base64");
|
|
16023
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
|
+
}
|
|
16024
16334
|
|
|
16025
16335
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
16026
16336
|
const __dirname$1 = dirname$1(__filename$1);
|
|
@@ -17519,7 +17829,7 @@ async function startDaemon(options) {
|
|
|
17519
17829
|
try {
|
|
17520
17830
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
17521
17831
|
if (!dir) return;
|
|
17522
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
17832
|
+
const { reconcileServiceLinks } = await import('./agentCommands-DdNUACV8.mjs');
|
|
17523
17833
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
17524
17834
|
const config = readSvampConfig(configPath);
|
|
17525
17835
|
const entries = Array.from(urls.entries());
|
|
@@ -17537,7 +17847,7 @@ async function startDaemon(options) {
|
|
|
17537
17847
|
}
|
|
17538
17848
|
}
|
|
17539
17849
|
async function createExposedTunnel(spec) {
|
|
17540
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
17850
|
+
const { FrpcTunnel } = await import('./frpc-Cv6J2Mex.mjs');
|
|
17541
17851
|
const tunnel = new FrpcTunnel({
|
|
17542
17852
|
name: spec.name,
|
|
17543
17853
|
ports: spec.ports,
|
|
@@ -17565,7 +17875,7 @@ async function startDaemon(options) {
|
|
|
17565
17875
|
ensureAutoInstalledCommands(logger);
|
|
17566
17876
|
(async () => {
|
|
17567
17877
|
try {
|
|
17568
|
-
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-
|
|
17878
|
+
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-C6MSkqS9.mjs');
|
|
17569
17879
|
beginClaudeVersionReconcile((msg) => logger.log(msg));
|
|
17570
17880
|
} catch (e) {
|
|
17571
17881
|
logger.log(`[claude-version] check failed: ${e?.message || e}`);
|
|
@@ -17848,8 +18158,16 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
|
|
|
17848
18158
|
if (RATELIMIT_CFG.maxRetries <= 0) return false;
|
|
17849
18159
|
if (trackedSession?.stopped) return false;
|
|
17850
18160
|
if (currentTurnMessage === void 0) return false;
|
|
17851
|
-
|
|
17852
|
-
|
|
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();
|
|
17853
18171
|
return false;
|
|
17854
18172
|
}
|
|
17855
18173
|
const attempt = rateLimitRetryCount++;
|
|
@@ -18317,18 +18635,18 @@ ${parts.join("\n")}`);
|
|
|
18317
18635
|
enqueueLoopMessage(push, label);
|
|
18318
18636
|
if (!trackedSession.stopped) setTimeout(() => processMessageQueueRef?.(), 200);
|
|
18319
18637
|
} else {
|
|
18320
|
-
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 });
|
|
18321
18639
|
logger.log(`[Session ${sessionId}] [loop-persist] stuck-stop: ${decision.reason}`);
|
|
18322
18640
|
checkSvampConfig?.();
|
|
18323
18641
|
}
|
|
18324
18642
|
} else {
|
|
18325
|
-
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 });
|
|
18326
18644
|
checkSvampConfig?.();
|
|
18327
18645
|
}
|
|
18328
18646
|
} catch (e) {
|
|
18329
18647
|
logger.log(`[Session ${sessionId}] verifyGoalCompletion error \u2014 failing open (done): ${e?.message || e}`);
|
|
18330
18648
|
try {
|
|
18331
|
-
writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now() });
|
|
18649
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now() });
|
|
18332
18650
|
checkSvampConfig?.();
|
|
18333
18651
|
} catch {
|
|
18334
18652
|
}
|
|
@@ -18386,8 +18704,16 @@ ${parts.join("\n")}`);
|
|
|
18386
18704
|
const RATELIMIT_CFG = getRateLimitRetryConfig();
|
|
18387
18705
|
let currentTurnMessage;
|
|
18388
18706
|
let rateLimitRetryCount = 0;
|
|
18707
|
+
let rateLimitStreakStartedAt = Number(sessionMetadata?.rateLimitStreakStartedAt) || 0;
|
|
18389
18708
|
let rateLimitRetryTimer = null;
|
|
18390
18709
|
let rateLimitRetryScheduled = false;
|
|
18710
|
+
const resetRateLimitStreak = () => {
|
|
18711
|
+
rateLimitRetryCount = 0;
|
|
18712
|
+
rateLimitStreakStartedAt = 0;
|
|
18713
|
+
if (sessionMetadata?.rateLimitStreakStartedAt) {
|
|
18714
|
+
sessionMetadata = { ...sessionMetadata, rateLimitStreakStartedAt: 0 };
|
|
18715
|
+
}
|
|
18716
|
+
};
|
|
18391
18717
|
let checkSvampConfig;
|
|
18392
18718
|
let cleanupSvampConfig;
|
|
18393
18719
|
const VALID_CLAUDE_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
@@ -18802,7 +19128,7 @@ ${parts.join("\n")}`);
|
|
|
18802
19128
|
"event"
|
|
18803
19129
|
);
|
|
18804
19130
|
}
|
|
18805
|
-
if (!msg.is_error)
|
|
19131
|
+
if (!msg.is_error) resetRateLimitStreak();
|
|
18806
19132
|
if (msg.session_id) {
|
|
18807
19133
|
claudeResumeId = msg.session_id;
|
|
18808
19134
|
if (sessionMetadata.claudeSessionId !== msg.session_id) {
|
|
@@ -19374,7 +19700,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19374
19700
|
return;
|
|
19375
19701
|
}
|
|
19376
19702
|
currentTurnMessage = text;
|
|
19377
|
-
|
|
19703
|
+
resetRateLimitStreak();
|
|
19378
19704
|
if (rateLimitRetryTimer) {
|
|
19379
19705
|
clearTimeout(rateLimitRetryTimer);
|
|
19380
19706
|
rateLimitRetryTimer = null;
|
|
@@ -19398,7 +19724,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19398
19724
|
clearTimeout(rateLimitRetryTimer);
|
|
19399
19725
|
rateLimitRetryTimer = null;
|
|
19400
19726
|
}
|
|
19401
|
-
|
|
19727
|
+
resetRateLimitStreak();
|
|
19402
19728
|
if (claudeProcess && !claudeProcess.killed) {
|
|
19403
19729
|
try {
|
|
19404
19730
|
const interruptMsg = JSON.stringify({ type: "control_request", request: { type: "interrupt" } });
|
|
@@ -19755,11 +20081,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19755
20081
|
});
|
|
19756
20082
|
},
|
|
19757
20083
|
onIssue: async (params) => {
|
|
19758
|
-
const { issueRpc } = await import('./rpc-
|
|
20084
|
+
const { issueRpc } = await import('./rpc-CkcBAtcs.mjs');
|
|
19759
20085
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
19760
20086
|
},
|
|
19761
20087
|
onWorkflow: async (params) => {
|
|
19762
|
-
const { workflowRpc } = await import('./rpc-
|
|
20088
|
+
const { workflowRpc } = await import('./rpc-BwxwusfB.mjs');
|
|
19763
20089
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
19764
20090
|
},
|
|
19765
20091
|
onRipgrep: async (args, cwd) => {
|
|
@@ -19791,6 +20117,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19791
20117
|
await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
|
|
19792
20118
|
await fs$1.writeFile(resolvedPath, Buffer.from(content, "base64"));
|
|
19793
20119
|
},
|
|
20120
|
+
onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
|
|
20121
|
+
await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
|
|
20122
|
+
},
|
|
19794
20123
|
onListDirectory: async (path) => {
|
|
19795
20124
|
const resolvedDir = resolve$1(directory, path || ".");
|
|
19796
20125
|
if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
|
|
@@ -19800,8 +20129,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19800
20129
|
return entries.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }));
|
|
19801
20130
|
},
|
|
19802
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;
|
|
19803
20137
|
async function buildTree(p, name, depth) {
|
|
19804
20138
|
try {
|
|
20139
|
+
if (treeNodeCount >= MAX_TREE_NODES) return null;
|
|
20140
|
+
treeNodeCount++;
|
|
19805
20141
|
const stats = await fs$1.stat(p);
|
|
19806
20142
|
const node = {
|
|
19807
20143
|
name,
|
|
@@ -19810,11 +20146,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19810
20146
|
size: stats.size,
|
|
19811
20147
|
modified: stats.mtime.getTime()
|
|
19812
20148
|
};
|
|
19813
|
-
if (stats.isDirectory() && depth <
|
|
20149
|
+
if (stats.isDirectory() && depth < effectiveMaxDepth) {
|
|
19814
20150
|
const entries = await fs$1.readdir(p, { withFileTypes: true });
|
|
19815
20151
|
const children = [];
|
|
19816
20152
|
await Promise.all(entries.map(async (entry) => {
|
|
19817
20153
|
if (entry.isSymbolicLink()) return;
|
|
20154
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) return;
|
|
20155
|
+
if (treeNodeCount >= MAX_TREE_NODES) return;
|
|
19818
20156
|
const childPath = join(p, entry.name);
|
|
19819
20157
|
const childNode = await buildTree(childPath, entry.name, depth + 1);
|
|
19820
20158
|
if (childNode) children.push(childNode);
|
|
@@ -19885,7 +20223,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19885
20223
|
userMessagePending = true;
|
|
19886
20224
|
turnInitiatedByUser = true;
|
|
19887
20225
|
currentTurnMessage = next.text;
|
|
19888
|
-
|
|
20226
|
+
resetRateLimitStreak();
|
|
19889
20227
|
if (rateLimitRetryTimer) {
|
|
19890
20228
|
clearTimeout(rateLimitRetryTimer);
|
|
19891
20229
|
rateLimitRetryTimer = null;
|
|
@@ -20164,6 +20502,30 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20164
20502
|
};
|
|
20165
20503
|
sessionService.updateMetadata(sessionMetadata);
|
|
20166
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
|
+
};
|
|
20167
20529
|
const injectFn = agentBackend.injectInput;
|
|
20168
20530
|
if (typeof injectFn === "function") {
|
|
20169
20531
|
Promise.resolve(injectFn.call(agentBackend, text)).then((injected) => {
|
|
@@ -20172,10 +20534,12 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20172
20534
|
} else {
|
|
20173
20535
|
logger.log(`[${agentName} Session ${sessionId}] No active turn to inject \u2014 queuing message`);
|
|
20174
20536
|
enqueueBusy();
|
|
20537
|
+
drainQueueHeadIfIdle();
|
|
20175
20538
|
}
|
|
20176
20539
|
}).catch((err) => {
|
|
20177
20540
|
logger.error(`[${agentName} Session ${sessionId}] Mid-turn inject failed \u2014 queuing:`, err?.message ?? err);
|
|
20178
20541
|
enqueueBusy();
|
|
20542
|
+
drainQueueHeadIfIdle();
|
|
20179
20543
|
});
|
|
20180
20544
|
return;
|
|
20181
20545
|
}
|
|
@@ -20391,11 +20755,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20391
20755
|
});
|
|
20392
20756
|
},
|
|
20393
20757
|
onIssue: async (params) => {
|
|
20394
|
-
const { issueRpc } = await import('./rpc-
|
|
20758
|
+
const { issueRpc } = await import('./rpc-CkcBAtcs.mjs');
|
|
20395
20759
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
20396
20760
|
},
|
|
20397
20761
|
onWorkflow: async (params) => {
|
|
20398
|
-
const { workflowRpc } = await import('./rpc-
|
|
20762
|
+
const { workflowRpc } = await import('./rpc-BwxwusfB.mjs');
|
|
20399
20763
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
20400
20764
|
},
|
|
20401
20765
|
onRipgrep: async (args, cwd) => {
|
|
@@ -20427,6 +20791,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20427
20791
|
await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
|
|
20428
20792
|
await fs$1.writeFile(resolvedPath, Buffer.from(content, "base64"));
|
|
20429
20793
|
},
|
|
20794
|
+
onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
|
|
20795
|
+
await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
|
|
20796
|
+
},
|
|
20430
20797
|
onListDirectory: async (path) => {
|
|
20431
20798
|
const resolvedDir = resolve$1(directory, path || ".");
|
|
20432
20799
|
if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
|
|
@@ -20436,8 +20803,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20436
20803
|
return entries.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }));
|
|
20437
20804
|
},
|
|
20438
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;
|
|
20439
20811
|
async function buildTree(p, name, depth) {
|
|
20440
20812
|
try {
|
|
20813
|
+
if (treeNodeCount >= MAX_TREE_NODES) return null;
|
|
20814
|
+
treeNodeCount++;
|
|
20441
20815
|
const stats = await fs$1.stat(p);
|
|
20442
20816
|
const node = {
|
|
20443
20817
|
name,
|
|
@@ -20446,11 +20820,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20446
20820
|
size: stats.size,
|
|
20447
20821
|
modified: stats.mtime.getTime()
|
|
20448
20822
|
};
|
|
20449
|
-
if (stats.isDirectory() && depth <
|
|
20823
|
+
if (stats.isDirectory() && depth < effectiveMaxDepth) {
|
|
20450
20824
|
const entries = await fs$1.readdir(p, { withFileTypes: true });
|
|
20451
20825
|
const children = [];
|
|
20452
20826
|
await Promise.all(entries.map(async (entry) => {
|
|
20453
20827
|
if (entry.isSymbolicLink()) return;
|
|
20828
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) return;
|
|
20829
|
+
if (treeNodeCount >= MAX_TREE_NODES) return;
|
|
20454
20830
|
const childPath = join(p, entry.name);
|
|
20455
20831
|
const childNode = await buildTree(childPath, entry.name, depth + 1);
|
|
20456
20832
|
if (childNode) children.push(childNode);
|
|
@@ -21341,7 +21717,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21341
21717
|
}
|
|
21342
21718
|
if (persistedSessions.length > 0) {
|
|
21343
21719
|
try {
|
|
21344
|
-
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-
|
|
21720
|
+
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-C6MSkqS9.mjs');
|
|
21345
21721
|
await awaitClaudeVersionReady();
|
|
21346
21722
|
} catch {
|
|
21347
21723
|
}
|
|
@@ -21546,7 +21922,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21546
21922
|
const PING_TIMEOUT_MS = 15e3;
|
|
21547
21923
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
21548
21924
|
const RECONNECT_JITTER_MS = 2500;
|
|
21549
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
21925
|
+
const { WorkflowScheduler } = await import('./scheduler-CurN4UtP.mjs');
|
|
21550
21926
|
const workflowProjectRoots = () => {
|
|
21551
21927
|
const dirs = /* @__PURE__ */ new Set();
|
|
21552
21928
|
for (const s of pidToTrackedSession.values()) {
|
|
@@ -21835,6 +22211,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21835
22211
|
logger.log(`Cleaning up (source: ${source})...`);
|
|
21836
22212
|
clearInterval(heartbeatInterval);
|
|
21837
22213
|
clearInterval(workflowSchedulerInterval);
|
|
22214
|
+
clearInterval(backendAccountRefreshInterval);
|
|
21838
22215
|
if (proxyTokenRefreshInterval) clearInterval(proxyTokenRefreshInterval);
|
|
21839
22216
|
if (oauthRefreshInterval) clearInterval(oauthRefreshInterval);
|
|
21840
22217
|
if (unhandledRejectionResetTimer) clearTimeout(unhandledRejectionResetTimer);
|
|
@@ -22179,4 +22556,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
22179
22556
|
writeStopMarker: writeStopMarker
|
|
22180
22557
|
});
|
|
22181
22558
|
|
|
22182
|
-
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 };
|