svamp-cli 0.2.45 → 0.2.46
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/{agentCommands-dlpOoDcq.mjs → agentCommands-Bqjcj1EX.mjs} +2 -2
- package/dist/cli.mjs +32 -32
- package/dist/{commands-Cd_I1MXo.mjs → commands-B6zZtCuh.mjs} +1 -1
- package/dist/{commands-0xDVhPKr.mjs → commands-Cs5-T_lh.mjs} +12 -3
- package/dist/{commands-C6D6TMSl.mjs → commands-DsCkxa3k.mjs} +4 -4
- package/dist/{frpc-DzRFx60H.mjs → frpc-j60b46eU.mjs} +120 -4
- package/dist/index.mjs +1 -1
- package/dist/{package-Cx2tEoke.mjs → package-CyXoMaC5.mjs} +1 -1
- package/dist/{run-D59qJKn_.mjs → run-wfhhtkl1.mjs} +372 -61
- package/dist/{run-DZhogQUH.mjs → run-yJ1mtT8S.mjs} +3 -53
- package/dist/{serveCommands-DtKlt1DY.mjs → serveCommands-DhtNhaur.mjs} +4 -4
- package/dist/{serveManager-DOXI2QzY.mjs → serveManager-CUcu_V3q.mjs} +24 -3
- package/package.json +1 -1
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import os from 'os';
|
|
1
|
+
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import os$1 from 'os';
|
|
2
2
|
import fs, { mkdir as mkdir$1, readdir as readdir$1, readFile, writeFile as writeFile$1, rename, unlink } from 'fs/promises';
|
|
3
|
-
import { readFileSync as readFileSync$1, mkdirSync, writeFileSync, renameSync, existsSync as existsSync$1, copyFileSync, unlinkSync, watch, rmdirSync } from 'fs';
|
|
3
|
+
import { readFileSync as readFileSync$1, mkdirSync, writeFileSync, renameSync, existsSync as existsSync$1, copyFileSync, unlinkSync as unlinkSync$1, watch, rmdirSync } from 'fs';
|
|
4
4
|
import path__default, { join, dirname, resolve, basename } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { spawn as spawn$1, execSync as execSync$1 } from 'child_process';
|
|
7
7
|
import { randomUUID as randomUUID$1 } from 'crypto';
|
|
8
|
-
import { existsSync, readFileSync, writeFileSync as writeFileSync$1, mkdirSync as mkdirSync$1, appendFileSync } from 'node:fs';
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync as writeFileSync$1, mkdirSync as mkdirSync$1, appendFileSync, unlinkSync } from 'node:fs';
|
|
9
9
|
import { randomUUID, createHash } from 'node:crypto';
|
|
10
10
|
import { join as join$1 } from 'node:path';
|
|
11
11
|
import { spawn, execSync, execFile, execFileSync } from 'node:child_process';
|
|
12
12
|
import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
|
|
13
|
-
import { homedir, platform } from 'node:os';
|
|
13
|
+
import os, { homedir, platform } from 'node:os';
|
|
14
14
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
15
15
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
16
16
|
import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
@@ -1107,7 +1107,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
1107
1107
|
const tunnels = handlers.tunnels;
|
|
1108
1108
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
1109
1109
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
1110
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
1110
|
+
const { FrpcTunnel } = await import('./frpc-j60b46eU.mjs');
|
|
1111
1111
|
const tunnel = new FrpcTunnel({
|
|
1112
1112
|
name: params.name,
|
|
1113
1113
|
ports: params.ports,
|
|
@@ -5092,6 +5092,8 @@ const DEFAULT_PROBE_FAILURE_THRESHOLD = 3;
|
|
|
5092
5092
|
const MAX_RESTART_DELAY_S = 300;
|
|
5093
5093
|
const BACKOFF_RESET_WINDOW_MS = 6e4;
|
|
5094
5094
|
const MAX_LOG_LINES = 300;
|
|
5095
|
+
const CRASH_LOOP_FAILURE_THRESHOLD = 10;
|
|
5096
|
+
const CRASH_LOOP_WINDOW_MS = 3e5;
|
|
5095
5097
|
class ProcessSupervisor {
|
|
5096
5098
|
entries = /* @__PURE__ */ new Map();
|
|
5097
5099
|
persistDir;
|
|
@@ -5139,11 +5141,13 @@ class ProcessSupervisor {
|
|
|
5139
5141
|
/** Start a stopped/failed process by id or name. */
|
|
5140
5142
|
async start(idOrName) {
|
|
5141
5143
|
const entry = this.require(idOrName);
|
|
5144
|
+
if (entry.deleted) throw new Error(`Process '${idOrName}' has been removed`);
|
|
5142
5145
|
if (entry.child) {
|
|
5143
5146
|
if (entry.stopping) throw new Error(`Process '${entry.spec.name}' is being stopped, try again shortly`);
|
|
5144
5147
|
throw new Error(`Process '${entry.spec.name}' is already running`);
|
|
5145
5148
|
}
|
|
5146
5149
|
entry.stopping = false;
|
|
5150
|
+
this.resetFailureTracking(entry);
|
|
5147
5151
|
await this.startEntry(entry, false);
|
|
5148
5152
|
}
|
|
5149
5153
|
/** Stop a running process. Does NOT remove it from supervision. */
|
|
@@ -5162,6 +5166,7 @@ class ProcessSupervisor {
|
|
|
5162
5166
|
/** Restart a process (stop if running, then start again). */
|
|
5163
5167
|
async restart(idOrName) {
|
|
5164
5168
|
const entry = this.require(idOrName);
|
|
5169
|
+
if (entry.deleted) throw new Error(`Process '${idOrName}' has been removed`);
|
|
5165
5170
|
if (entry.restarting) return;
|
|
5166
5171
|
entry.restarting = true;
|
|
5167
5172
|
try {
|
|
@@ -5171,7 +5176,9 @@ class ProcessSupervisor {
|
|
|
5171
5176
|
await this.killChild(entry.child);
|
|
5172
5177
|
entry.child = void 0;
|
|
5173
5178
|
}
|
|
5179
|
+
if (entry.deleted) return;
|
|
5174
5180
|
entry.stopping = false;
|
|
5181
|
+
this.resetFailureTracking(entry);
|
|
5175
5182
|
entry.state.restartCount++;
|
|
5176
5183
|
await this.startEntry(entry, false);
|
|
5177
5184
|
} finally {
|
|
@@ -5182,7 +5189,16 @@ class ProcessSupervisor {
|
|
|
5182
5189
|
async remove(idOrName) {
|
|
5183
5190
|
const entry = this.require(idOrName);
|
|
5184
5191
|
const id = entry.spec.id;
|
|
5185
|
-
|
|
5192
|
+
entry.deleted = true;
|
|
5193
|
+
entry.stopping = true;
|
|
5194
|
+
this.clearTimers(entry);
|
|
5195
|
+
if (entry.child) {
|
|
5196
|
+
await this.killChild(entry.child);
|
|
5197
|
+
entry.child = void 0;
|
|
5198
|
+
}
|
|
5199
|
+
entry.state.status = "stopped";
|
|
5200
|
+
entry.state.stoppedAt = Date.now();
|
|
5201
|
+
entry.state.pid = void 0;
|
|
5186
5202
|
this.entries.delete(id);
|
|
5187
5203
|
await this.deleteSpec(id);
|
|
5188
5204
|
}
|
|
@@ -5232,7 +5248,9 @@ class ProcessSupervisor {
|
|
|
5232
5248
|
await this.killChild(existing.child);
|
|
5233
5249
|
existing.child = void 0;
|
|
5234
5250
|
}
|
|
5251
|
+
if (existing.deleted) return { action: "updated", info: this.toInfo(existing) };
|
|
5235
5252
|
existing.stopping = false;
|
|
5253
|
+
this.resetFailureTracking(existing);
|
|
5236
5254
|
existing.state.status = "starting";
|
|
5237
5255
|
this.spawnProcess(existing);
|
|
5238
5256
|
return { action: "updated", info: this.toInfo(existing) };
|
|
@@ -5243,6 +5261,7 @@ class ProcessSupervisor {
|
|
|
5243
5261
|
*/
|
|
5244
5262
|
async update(idOrName, partialSpec) {
|
|
5245
5263
|
const entry = this.require(idOrName);
|
|
5264
|
+
if (entry.deleted) throw new Error(`Process '${idOrName}' has been removed`);
|
|
5246
5265
|
const updatedSpec = { ...entry.spec, ...partialSpec };
|
|
5247
5266
|
entry.spec = updatedSpec;
|
|
5248
5267
|
await this.persistSpec(updatedSpec);
|
|
@@ -5252,7 +5271,9 @@ class ProcessSupervisor {
|
|
|
5252
5271
|
await this.killChild(entry.child);
|
|
5253
5272
|
entry.child = void 0;
|
|
5254
5273
|
}
|
|
5274
|
+
if (entry.deleted) return this.toInfo(entry);
|
|
5255
5275
|
entry.stopping = false;
|
|
5276
|
+
this.resetFailureTracking(entry);
|
|
5256
5277
|
entry.state.status = "starting";
|
|
5257
5278
|
this.spawnProcess(entry);
|
|
5258
5279
|
return this.toInfo(entry);
|
|
@@ -5328,7 +5349,8 @@ class ProcessSupervisor {
|
|
|
5328
5349
|
id: spec.id,
|
|
5329
5350
|
status: "pending",
|
|
5330
5351
|
restartCount: 0,
|
|
5331
|
-
consecutiveProbeFailures: 0
|
|
5352
|
+
consecutiveProbeFailures: 0,
|
|
5353
|
+
consecutiveFailures: 0
|
|
5332
5354
|
},
|
|
5333
5355
|
logBuffer: [],
|
|
5334
5356
|
stopping: false
|
|
@@ -5348,6 +5370,7 @@ class ProcessSupervisor {
|
|
|
5348
5370
|
// ── Process spawning ──────────────────────────────────────────────────────
|
|
5349
5371
|
async startEntry(entry, onRestore) {
|
|
5350
5372
|
const { spec } = entry;
|
|
5373
|
+
if (entry.deleted) return;
|
|
5351
5374
|
if (spec.ttl !== void 0 && spec.ttl > 0 && onRestore) {
|
|
5352
5375
|
const elapsedS = (Date.now() - spec.createdAt) / 1e3;
|
|
5353
5376
|
if (elapsedS >= spec.ttl) {
|
|
@@ -5366,6 +5389,7 @@ class ProcessSupervisor {
|
|
|
5366
5389
|
}
|
|
5367
5390
|
spawnProcess(entry) {
|
|
5368
5391
|
const { spec, state } = entry;
|
|
5392
|
+
if (entry.deleted) return;
|
|
5369
5393
|
try {
|
|
5370
5394
|
const env = { ...process.env, ...spec.env ?? {} };
|
|
5371
5395
|
const child = spawn$1(spec.command, spec.args, {
|
|
@@ -5408,6 +5432,7 @@ class ProcessSupervisor {
|
|
|
5408
5432
|
state.pid = void 0;
|
|
5409
5433
|
state.stoppedAt = Date.now();
|
|
5410
5434
|
this.clearTimers(entry);
|
|
5435
|
+
if (entry.deleted) return;
|
|
5411
5436
|
if (entry.stopping) {
|
|
5412
5437
|
state.status = "stopped";
|
|
5413
5438
|
console.log(`[SUPERVISOR] Process '${spec.name}' stopped (code=${code})`);
|
|
@@ -5417,31 +5442,65 @@ class ProcessSupervisor {
|
|
|
5417
5442
|
state.status = crashed ? "failed" : "stopped";
|
|
5418
5443
|
console.log(`[SUPERVISOR] Process '${spec.name}' exited (code=${code}, signal=${signal})`);
|
|
5419
5444
|
if (!spec.keepAlive) return;
|
|
5445
|
+
const uptime = state.startedAt ? Date.now() - state.startedAt : 0;
|
|
5446
|
+
const now = Date.now();
|
|
5447
|
+
if (uptime > BACKOFF_RESET_WINDOW_MS) {
|
|
5448
|
+
state.restartCount = 0;
|
|
5449
|
+
state.consecutiveFailures = 0;
|
|
5450
|
+
entry.failureWindowStart = void 0;
|
|
5451
|
+
} else {
|
|
5452
|
+
if (!entry.failureWindowStart || now - entry.failureWindowStart > CRASH_LOOP_WINDOW_MS) {
|
|
5453
|
+
entry.failureWindowStart = now;
|
|
5454
|
+
state.consecutiveFailures = 1;
|
|
5455
|
+
} else {
|
|
5456
|
+
state.consecutiveFailures++;
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5459
|
+
if (state.consecutiveFailures >= CRASH_LOOP_FAILURE_THRESHOLD) {
|
|
5460
|
+
const windowS = Math.round((now - (entry.failureWindowStart ?? now)) / 1e3);
|
|
5461
|
+
state.status = "crash-loop-backoff";
|
|
5462
|
+
state.crashLoopBackOff = {
|
|
5463
|
+
enteredAt: now,
|
|
5464
|
+
failureCount: state.consecutiveFailures,
|
|
5465
|
+
windowStart: entry.failureWindowStart ?? now
|
|
5466
|
+
};
|
|
5467
|
+
console.error(
|
|
5468
|
+
`[SUPERVISOR] '${spec.name}' entering CrashLoopBackOff: ${state.consecutiveFailures} failures in ${windowS}s. Restarts halted \u2014 run 'svamp process restart ${spec.name}' to retry.`
|
|
5469
|
+
);
|
|
5470
|
+
return;
|
|
5471
|
+
}
|
|
5420
5472
|
if (spec.maxRestarts > 0 && state.restartCount >= spec.maxRestarts) {
|
|
5421
5473
|
console.warn(`[SUPERVISOR] Process '${spec.name}' reached max restarts (${spec.maxRestarts}), not restarting`);
|
|
5422
5474
|
state.status = "failed";
|
|
5423
5475
|
return;
|
|
5424
5476
|
}
|
|
5425
|
-
const uptime = state.startedAt ? Date.now() - state.startedAt : 0;
|
|
5426
5477
|
const baseDelay = spec.restartDelay * 1e3;
|
|
5427
5478
|
let delayMs;
|
|
5428
5479
|
if (uptime > BACKOFF_RESET_WINDOW_MS) {
|
|
5429
|
-
state.restartCount = 0;
|
|
5430
5480
|
delayMs = baseDelay;
|
|
5431
5481
|
} else {
|
|
5432
|
-
const backoffExponent = Math.min(state.
|
|
5482
|
+
const backoffExponent = Math.min(state.consecutiveFailures - 1, 10);
|
|
5433
5483
|
delayMs = Math.min(baseDelay * Math.pow(2, backoffExponent), MAX_RESTART_DELAY_S * 1e3);
|
|
5434
5484
|
const jitter = (Math.random() * 0.2 - 0.1) * delayMs;
|
|
5435
5485
|
delayMs = Math.max(baseDelay, Math.round(delayMs + jitter));
|
|
5436
5486
|
}
|
|
5437
|
-
console.log(
|
|
5487
|
+
console.log(
|
|
5488
|
+
`[SUPERVISOR] Scheduling restart of '${spec.name}' in ${delayMs}ms (failure ${state.consecutiveFailures}/${CRASH_LOOP_FAILURE_THRESHOLD}, restart #${state.restartCount + 1}, uptime=${Math.round(uptime / 1e3)}s)`
|
|
5489
|
+
);
|
|
5438
5490
|
entry.restartTimer = setTimeout(() => {
|
|
5439
|
-
if (entry.stopping) return;
|
|
5491
|
+
if (entry.deleted || entry.stopping) return;
|
|
5440
5492
|
state.restartCount++;
|
|
5441
5493
|
state.status = "starting";
|
|
5442
5494
|
this.spawnProcess(entry);
|
|
5443
5495
|
}, delayMs);
|
|
5444
5496
|
}
|
|
5497
|
+
/** Reset all CrashLoopBackOff accounting. Called on user-initiated start/restart/spec-change. */
|
|
5498
|
+
resetFailureTracking(entry) {
|
|
5499
|
+
entry.state.consecutiveFailures = 0;
|
|
5500
|
+
entry.state.consecutiveProbeFailures = 0;
|
|
5501
|
+
entry.state.crashLoopBackOff = void 0;
|
|
5502
|
+
entry.failureWindowStart = void 0;
|
|
5503
|
+
}
|
|
5445
5504
|
// ── Health probes ─────────────────────────────────────────────────────────
|
|
5446
5505
|
setupProbe(entry) {
|
|
5447
5506
|
const intervalMs = (entry.spec.probe.interval ?? DEFAULT_PROBE_INTERVAL_S) * 1e3;
|
|
@@ -5451,6 +5510,7 @@ class ProcessSupervisor {
|
|
|
5451
5510
|
}, intervalMs);
|
|
5452
5511
|
}
|
|
5453
5512
|
async runHealthCheck(entry) {
|
|
5513
|
+
if (entry.deleted || entry.stopping) return;
|
|
5454
5514
|
if (!entry.child || entry.state.status !== "running") return;
|
|
5455
5515
|
const probe = entry.spec.probe;
|
|
5456
5516
|
const urlPath = probe.path ?? "/";
|
|
@@ -5491,15 +5551,17 @@ class ProcessSupervisor {
|
|
|
5491
5551
|
}
|
|
5492
5552
|
}
|
|
5493
5553
|
async triggerProbeRestart(entry) {
|
|
5494
|
-
if (entry.
|
|
5554
|
+
if (entry.deleted) return;
|
|
5495
5555
|
if (entry.stopping) return;
|
|
5496
|
-
|
|
5556
|
+
if (entry.restarting) return;
|
|
5557
|
+
if (entry.state.status === "crash-loop-backoff") return;
|
|
5558
|
+
console.warn(`[SUPERVISOR] Probe failed for '${entry.spec.name}' \u2014 killing to trigger restart`);
|
|
5497
5559
|
entry.state.consecutiveProbeFailures = 0;
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5560
|
+
if (entry.child) {
|
|
5561
|
+
try {
|
|
5562
|
+
entry.child.kill("SIGTERM");
|
|
5563
|
+
} catch {
|
|
5564
|
+
}
|
|
5503
5565
|
}
|
|
5504
5566
|
}
|
|
5505
5567
|
// ── TTL ───────────────────────────────────────────────────────────────────
|
|
@@ -5514,9 +5576,11 @@ class ProcessSupervisor {
|
|
|
5514
5576
|
entry.ttlTimer = setTimeout(() => this.expireProcess(entry), remainingS * 1e3);
|
|
5515
5577
|
}
|
|
5516
5578
|
expireProcess(entry) {
|
|
5579
|
+
if (entry.deleted) return;
|
|
5517
5580
|
console.log(`[SUPERVISOR] Process '${entry.spec.name}' TTL expired`);
|
|
5518
5581
|
entry.state.status = "expired";
|
|
5519
5582
|
entry.stopping = true;
|
|
5583
|
+
entry.deleted = true;
|
|
5520
5584
|
this.clearTimers(entry);
|
|
5521
5585
|
const cleanup = async () => {
|
|
5522
5586
|
if (entry.child) await this.killChild(entry.child);
|
|
@@ -5677,9 +5741,201 @@ You may be running in parallel with other agents \u2014 possibly sharing the sam
|
|
|
5677
5741
|
`;
|
|
5678
5742
|
}
|
|
5679
5743
|
|
|
5744
|
+
const SVAMP_HOME$1 = process.env.SVAMP_HOME || join$1(os.homedir(), ".svamp");
|
|
5745
|
+
function generateHookSettings(portOrOptions = {}) {
|
|
5746
|
+
const opts = typeof portOrOptions === "number" ? { sessionStartPort: portOrOptions } : portOrOptions;
|
|
5747
|
+
const hooksDir = join$1(SVAMP_HOME$1, "tmp", "hooks");
|
|
5748
|
+
mkdirSync$1(hooksDir, { recursive: true });
|
|
5749
|
+
const id = opts.id || String(process.pid);
|
|
5750
|
+
const validatorPath = join$1(hooksDir, `image-validator-${id}.cjs`);
|
|
5751
|
+
writeFileSync$1(validatorPath, IMAGE_VALIDATOR_SCRIPT, { mode: 493 });
|
|
5752
|
+
const cleanupPaths = [validatorPath];
|
|
5753
|
+
const hooks = {
|
|
5754
|
+
PreToolUse: [
|
|
5755
|
+
{
|
|
5756
|
+
matcher: "Read",
|
|
5757
|
+
hooks: [
|
|
5758
|
+
{
|
|
5759
|
+
type: "command",
|
|
5760
|
+
command: `node "${validatorPath}"`,
|
|
5761
|
+
timeout: 5
|
|
5762
|
+
}
|
|
5763
|
+
]
|
|
5764
|
+
}
|
|
5765
|
+
]
|
|
5766
|
+
};
|
|
5767
|
+
if (typeof opts.sessionStartPort === "number" && opts.sessionStartPort > 0) {
|
|
5768
|
+
const forwarderPath = join$1(hooksDir, `forwarder-${id}.cjs`);
|
|
5769
|
+
const forwarderCode = `#!/usr/bin/env node
|
|
5770
|
+
const http = require('http');
|
|
5771
|
+
const port = parseInt(process.argv[2], 10);
|
|
5772
|
+
if (!port || isNaN(port)) process.exit(1);
|
|
5773
|
+
const chunks = [];
|
|
5774
|
+
process.stdin.on('data', c => chunks.push(c));
|
|
5775
|
+
process.stdin.on('end', () => {
|
|
5776
|
+
const body = Buffer.concat(chunks);
|
|
5777
|
+
const req = http.request({
|
|
5778
|
+
host: '127.0.0.1', port, method: 'POST',
|
|
5779
|
+
path: '/hook/session-start',
|
|
5780
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': body.length }
|
|
5781
|
+
}, res => res.resume());
|
|
5782
|
+
req.on('error', () => {});
|
|
5783
|
+
req.end(body);
|
|
5784
|
+
});
|
|
5785
|
+
process.stdin.resume();
|
|
5786
|
+
`;
|
|
5787
|
+
writeFileSync$1(forwarderPath, forwarderCode, { mode: 493 });
|
|
5788
|
+
cleanupPaths.push(forwarderPath);
|
|
5789
|
+
hooks.SessionStart = [
|
|
5790
|
+
{
|
|
5791
|
+
matcher: "*",
|
|
5792
|
+
hooks: [
|
|
5793
|
+
{
|
|
5794
|
+
type: "command",
|
|
5795
|
+
command: `node "${forwarderPath}" ${opts.sessionStartPort}`
|
|
5796
|
+
}
|
|
5797
|
+
]
|
|
5798
|
+
}
|
|
5799
|
+
];
|
|
5800
|
+
}
|
|
5801
|
+
const settingsPath = join$1(hooksDir, `session-hook-${id}.json`);
|
|
5802
|
+
writeFileSync$1(settingsPath, JSON.stringify({ hooks }, null, 2));
|
|
5803
|
+
cleanupPaths.push(settingsPath);
|
|
5804
|
+
const cleanup = () => {
|
|
5805
|
+
for (const p of cleanupPaths) {
|
|
5806
|
+
try {
|
|
5807
|
+
if (existsSync(p)) unlinkSync(p);
|
|
5808
|
+
} catch {
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
};
|
|
5812
|
+
return { settingsPath, validatorPath, hooksDir, cleanup };
|
|
5813
|
+
}
|
|
5814
|
+
const IMAGE_VALIDATOR_SCRIPT = `#!/usr/bin/env node
|
|
5815
|
+
'use strict';
|
|
5816
|
+
const fs = require('node:fs');
|
|
5817
|
+
|
|
5818
|
+
const MAX_SIZE = 5 * 1024 * 1024;
|
|
5819
|
+
const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp']);
|
|
5820
|
+
|
|
5821
|
+
function readStdin() {
|
|
5822
|
+
return new Promise((resolve) => {
|
|
5823
|
+
let data = '';
|
|
5824
|
+
process.stdin.setEncoding('utf-8');
|
|
5825
|
+
process.stdin.on('data', (c) => { data += c; });
|
|
5826
|
+
process.stdin.on('end', () => resolve(data));
|
|
5827
|
+
process.stdin.on('error', () => resolve(''));
|
|
5828
|
+
});
|
|
5829
|
+
}
|
|
5830
|
+
|
|
5831
|
+
function deny(reason) {
|
|
5832
|
+
process.stderr.write(reason);
|
|
5833
|
+
process.exit(2);
|
|
5834
|
+
}
|
|
5835
|
+
|
|
5836
|
+
function detectFormat(head) {
|
|
5837
|
+
if (head.length >= 8
|
|
5838
|
+
&& head[0] === 0x89 && head[1] === 0x50 && head[2] === 0x4E && head[3] === 0x47
|
|
5839
|
+
&& head[4] === 0x0D && head[5] === 0x0A && head[6] === 0x1A && head[7] === 0x0A) return 'png';
|
|
5840
|
+
if (head.length >= 3
|
|
5841
|
+
&& head[0] === 0xFF && head[1] === 0xD8 && head[2] === 0xFF) return 'jpeg';
|
|
5842
|
+
if (head.length >= 4
|
|
5843
|
+
&& head[0] === 0x47 && head[1] === 0x49 && head[2] === 0x46 && head[3] === 0x38) return 'gif';
|
|
5844
|
+
if (head.length >= 12
|
|
5845
|
+
&& head[0] === 0x52 && head[1] === 0x49 && head[2] === 0x46 && head[3] === 0x46
|
|
5846
|
+
&& head[8] === 0x57 && head[9] === 0x45 && head[10] === 0x42 && head[11] === 0x50) return 'webp';
|
|
5847
|
+
return null;
|
|
5848
|
+
}
|
|
5849
|
+
|
|
5850
|
+
function checkTruncation(format, tail, fileSize, head) {
|
|
5851
|
+
if (format === 'png') {
|
|
5852
|
+
const iend = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82]);
|
|
5853
|
+
if (!tail.subarray(tail.length - 12).equals(iend)) {
|
|
5854
|
+
return 'PNG file is truncated or corrupt (missing IEND chunk).';
|
|
5855
|
+
}
|
|
5856
|
+
} else if (format === 'jpeg') {
|
|
5857
|
+
const last2 = tail.subarray(tail.length - 2);
|
|
5858
|
+
if (last2[0] !== 0xFF || last2[1] !== 0xD9) {
|
|
5859
|
+
return 'JPEG file is truncated (missing FF D9 EOI marker).';
|
|
5860
|
+
}
|
|
5861
|
+
} else if (format === 'gif') {
|
|
5862
|
+
if (tail[tail.length - 1] !== 0x3B) {
|
|
5863
|
+
return 'GIF file is truncated (missing 0x3B trailer).';
|
|
5864
|
+
}
|
|
5865
|
+
} else if (format === 'webp') {
|
|
5866
|
+
const riffSize = head.readUInt32LE(4);
|
|
5867
|
+
if (riffSize !== fileSize - 8) {
|
|
5868
|
+
return 'WebP file is corrupt (RIFF size ' + riffSize + ' != fileSize-8 ' + (fileSize - 8) + ').';
|
|
5869
|
+
}
|
|
5870
|
+
}
|
|
5871
|
+
return null;
|
|
5872
|
+
}
|
|
5873
|
+
|
|
5874
|
+
(async () => {
|
|
5875
|
+
const raw = await readStdin();
|
|
5876
|
+
let input;
|
|
5877
|
+
try { input = JSON.parse(raw); } catch { return; }
|
|
5878
|
+
if (!input || input.tool_name !== 'Read') return;
|
|
5879
|
+
const filePath = input.tool_input && input.tool_input.file_path;
|
|
5880
|
+
if (typeof filePath !== 'string' || !filePath) return;
|
|
5881
|
+
|
|
5882
|
+
const dot = filePath.lastIndexOf('.');
|
|
5883
|
+
if (dot < 0) return;
|
|
5884
|
+
const ext = filePath.slice(dot).toLowerCase();
|
|
5885
|
+
if (!IMAGE_EXTS.has(ext)) return;
|
|
5886
|
+
|
|
5887
|
+
let stat;
|
|
5888
|
+
try { stat = fs.statSync(filePath); } catch { return; } // missing \u2192 let Read tool report
|
|
5889
|
+
if (!stat.isFile()) return;
|
|
5890
|
+
|
|
5891
|
+
if (stat.size === 0) {
|
|
5892
|
+
deny('Image file is empty (0 bytes): ' + filePath + '. The Anthropic API will reject this image. Skip reading it.');
|
|
5893
|
+
}
|
|
5894
|
+
if (stat.size > MAX_SIZE) {
|
|
5895
|
+
deny('Image file is too large (' + (stat.size / 1024 / 1024).toFixed(1) + ' MB > 5 MB): ' + filePath + '. The Anthropic API rejects images larger than 5 MB. Skip reading it.');
|
|
5896
|
+
}
|
|
5897
|
+
if (stat.size < 12) {
|
|
5898
|
+
deny('Image file is too small to be a valid image (' + stat.size + ' bytes): ' + filePath + '. Skip reading it.');
|
|
5899
|
+
}
|
|
5900
|
+
|
|
5901
|
+
let head;
|
|
5902
|
+
let tail;
|
|
5903
|
+
try {
|
|
5904
|
+
const fd = fs.openSync(filePath, 'r');
|
|
5905
|
+
try {
|
|
5906
|
+
head = Buffer.alloc(32);
|
|
5907
|
+
const headLen = fs.readSync(fd, head, 0, 32, 0);
|
|
5908
|
+
head = head.subarray(0, headLen);
|
|
5909
|
+
const tailLen = Math.min(12, stat.size);
|
|
5910
|
+
tail = Buffer.alloc(tailLen);
|
|
5911
|
+
fs.readSync(fd, tail, 0, tailLen, stat.size - tailLen);
|
|
5912
|
+
} finally {
|
|
5913
|
+
fs.closeSync(fd);
|
|
5914
|
+
}
|
|
5915
|
+
} catch (err) {
|
|
5916
|
+
deny('Image file is unreadable: ' + filePath + ' \u2014 ' + (err && err.message || err) + '. Skip reading it.');
|
|
5917
|
+
}
|
|
5918
|
+
|
|
5919
|
+
const format = detectFormat(head);
|
|
5920
|
+
if (!format) {
|
|
5921
|
+
deny('Image file has invalid header \u2014 magic bytes do not match PNG/JPEG/GIF/WebP: ' + filePath + '. The file is corrupt or mislabeled. Skip reading it.');
|
|
5922
|
+
}
|
|
5923
|
+
|
|
5924
|
+
const expectedExts = { png: ['.png'], jpeg: ['.jpg', '.jpeg'], gif: ['.gif'], webp: ['.webp'] }[format];
|
|
5925
|
+
if (!expectedExts.includes(ext)) {
|
|
5926
|
+
deny('Image file extension "' + ext + '" does not match actual format "' + format + '": ' + filePath + '. Skip reading it.');
|
|
5927
|
+
}
|
|
5928
|
+
|
|
5929
|
+
const truncReason = checkTruncation(format, tail, stat.size, head);
|
|
5930
|
+
if (truncReason) {
|
|
5931
|
+
deny(truncReason + ' File: ' + filePath + '. Skip reading it.');
|
|
5932
|
+
}
|
|
5933
|
+
})().catch(() => { /* never block on validator errors */ });
|
|
5934
|
+
`;
|
|
5935
|
+
|
|
5680
5936
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
5681
5937
|
const __dirname$1 = dirname(__filename$1);
|
|
5682
|
-
const CLAUDE_SKILLS_DIR = join(os.homedir(), ".claude", "skills");
|
|
5938
|
+
const CLAUDE_SKILLS_DIR = join(os$1.homedir(), ".claude", "skills");
|
|
5683
5939
|
async function installSkillFromEndpoint(name, baseUrl) {
|
|
5684
5940
|
const resp = await fetch(baseUrl, { signal: AbortSignal.timeout(15e3) });
|
|
5685
5941
|
if (!resp.ok) throw new Error(`HTTP ${resp.status} from ${baseUrl}`);
|
|
@@ -5820,14 +6076,14 @@ function loadEnvFile(path) {
|
|
|
5820
6076
|
return true;
|
|
5821
6077
|
}
|
|
5822
6078
|
function loadDotEnv() {
|
|
5823
|
-
const svampEnv = join(process.env.SVAMP_HOME || os.homedir() + "/.svamp", ".env");
|
|
6079
|
+
const svampEnv = join(process.env.SVAMP_HOME || os$1.homedir() + "/.svamp", ".env");
|
|
5824
6080
|
if (!loadEnvFile(svampEnv)) {
|
|
5825
|
-
const hyphaEnv = join(process.env.HYPHA_HOME || os.homedir() + "/.hypha", ".env");
|
|
6081
|
+
const hyphaEnv = join(process.env.HYPHA_HOME || os$1.homedir() + "/.hypha", ".env");
|
|
5826
6082
|
loadEnvFile(hyphaEnv);
|
|
5827
6083
|
}
|
|
5828
6084
|
}
|
|
5829
6085
|
loadDotEnv();
|
|
5830
|
-
const SVAMP_HOME = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
|
|
6086
|
+
const SVAMP_HOME = process.env.SVAMP_HOME || join(os$1.homedir(), ".svamp");
|
|
5831
6087
|
const DAEMON_STATE_FILE = join(SVAMP_HOME, "daemon.state.json");
|
|
5832
6088
|
const DAEMON_LOCK_FILE = join(SVAMP_HOME, "daemon.lock");
|
|
5833
6089
|
const LOGS_DIR = join(SVAMP_HOME, "logs");
|
|
@@ -5941,7 +6197,7 @@ ${state.task}
|
|
|
5941
6197
|
}
|
|
5942
6198
|
function removeRalphState(filePath) {
|
|
5943
6199
|
try {
|
|
5944
|
-
unlinkSync(filePath);
|
|
6200
|
+
unlinkSync$1(filePath);
|
|
5945
6201
|
} catch {
|
|
5946
6202
|
}
|
|
5947
6203
|
}
|
|
@@ -6313,27 +6569,27 @@ function deletePersistedSession(sessionId) {
|
|
|
6313
6569
|
if (entry) {
|
|
6314
6570
|
const sessionFile = getSessionFilePath(entry.directory, sessionId);
|
|
6315
6571
|
try {
|
|
6316
|
-
if (existsSync$1(sessionFile)) unlinkSync(sessionFile);
|
|
6572
|
+
if (existsSync$1(sessionFile)) unlinkSync$1(sessionFile);
|
|
6317
6573
|
} catch {
|
|
6318
6574
|
}
|
|
6319
6575
|
const messagesFile = getSessionMessagesPath(entry.directory, sessionId);
|
|
6320
6576
|
try {
|
|
6321
|
-
if (existsSync$1(messagesFile)) unlinkSync(messagesFile);
|
|
6577
|
+
if (existsSync$1(messagesFile)) unlinkSync$1(messagesFile);
|
|
6322
6578
|
} catch {
|
|
6323
6579
|
}
|
|
6324
6580
|
const configFile = getSvampConfigPath(entry.directory, sessionId);
|
|
6325
6581
|
try {
|
|
6326
|
-
if (existsSync$1(configFile)) unlinkSync(configFile);
|
|
6582
|
+
if (existsSync$1(configFile)) unlinkSync$1(configFile);
|
|
6327
6583
|
} catch {
|
|
6328
6584
|
}
|
|
6329
6585
|
const ralphStateFile = getRalphStateFilePath(entry.directory, sessionId);
|
|
6330
6586
|
try {
|
|
6331
|
-
if (existsSync$1(ralphStateFile)) unlinkSync(ralphStateFile);
|
|
6587
|
+
if (existsSync$1(ralphStateFile)) unlinkSync$1(ralphStateFile);
|
|
6332
6588
|
} catch {
|
|
6333
6589
|
}
|
|
6334
6590
|
const ralphProgressFile = getRalphProgressFilePath(entry.directory, sessionId);
|
|
6335
6591
|
try {
|
|
6336
|
-
if (existsSync$1(ralphProgressFile)) unlinkSync(ralphProgressFile);
|
|
6592
|
+
if (existsSync$1(ralphProgressFile)) unlinkSync$1(ralphProgressFile);
|
|
6337
6593
|
} catch {
|
|
6338
6594
|
}
|
|
6339
6595
|
const sessionDir = getSessionDir(entry.directory, sessionId);
|
|
@@ -6603,7 +6859,7 @@ async function startDaemon(options) {
|
|
|
6603
6859
|
machineId = readFileSync$1(machineIdFile, "utf-8").trim();
|
|
6604
6860
|
}
|
|
6605
6861
|
if (!machineId) {
|
|
6606
|
-
machineId = `machine-${os.hostname()}-${randomUUID$1().slice(0, 8)}`;
|
|
6862
|
+
machineId = `machine-${os$1.hostname()}-${randomUUID$1().slice(0, 8)}`;
|
|
6607
6863
|
try {
|
|
6608
6864
|
writeFileSync(machineIdFile, machineId, "utf-8");
|
|
6609
6865
|
} catch {
|
|
@@ -6618,7 +6874,7 @@ async function startDaemon(options) {
|
|
|
6618
6874
|
const supervisor = new ProcessSupervisor(join(SVAMP_HOME, "processes"));
|
|
6619
6875
|
await supervisor.init();
|
|
6620
6876
|
const tunnels = /* @__PURE__ */ new Map();
|
|
6621
|
-
const { ServeManager } = await import('./serveManager-
|
|
6877
|
+
const { ServeManager } = await import('./serveManager-CUcu_V3q.mjs');
|
|
6622
6878
|
const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
6623
6879
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
6624
6880
|
});
|
|
@@ -6708,6 +6964,7 @@ async function startDaemon(options) {
|
|
|
6708
6964
|
return await spawnAgentSession(sessionId, directory, agentName, options2, resumeSessionId);
|
|
6709
6965
|
}
|
|
6710
6966
|
let stagedCredentials = null;
|
|
6967
|
+
let hookSettings = null;
|
|
6711
6968
|
try {
|
|
6712
6969
|
let parseBashPermission2 = function(permission) {
|
|
6713
6970
|
if (permission === "Bash") return;
|
|
@@ -6792,10 +7049,10 @@ async function startDaemon(options) {
|
|
|
6792
7049
|
var parseBashPermission = parseBashPermission2, shouldAutoAllow = shouldAutoAllow2, killAndWaitForExit = killAndWaitForExit2, buildIsolationConfig = buildIsolationConfig2;
|
|
6793
7050
|
let sessionMetadata = {
|
|
6794
7051
|
path: directory,
|
|
6795
|
-
host: os.hostname(),
|
|
7052
|
+
host: os$1.hostname(),
|
|
6796
7053
|
version: "0.1.0",
|
|
6797
7054
|
machineId,
|
|
6798
|
-
homeDir: os.homedir(),
|
|
7055
|
+
homeDir: os$1.homedir(),
|
|
6799
7056
|
svampHomeDir: SVAMP_HOME,
|
|
6800
7057
|
svampLibDir: join(__dirname$1, ".."),
|
|
6801
7058
|
svampToolsDir: join(__dirname$1, "..", "tools"),
|
|
@@ -6916,10 +7173,22 @@ async function startDaemon(options) {
|
|
|
6916
7173
|
let isSwitchingMode = false;
|
|
6917
7174
|
let checkSvampConfig;
|
|
6918
7175
|
let cleanupSvampConfig;
|
|
7176
|
+
const VALID_CLAUDE_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
6919
7177
|
const CLAUDE_PERMISSION_MODE_MAP = {
|
|
6920
|
-
"auto-approve-all": "bypassPermissions"
|
|
7178
|
+
"auto-approve-all": "bypassPermissions",
|
|
7179
|
+
"yolo": "bypassPermissions",
|
|
7180
|
+
"safe-yolo": "acceptEdits",
|
|
7181
|
+
"read-only": "plan"
|
|
7182
|
+
};
|
|
7183
|
+
const toClaudePermissionMode = (mode) => {
|
|
7184
|
+
if (!mode) return "default";
|
|
7185
|
+
const mapped = CLAUDE_PERMISSION_MODE_MAP[mode] ?? mode;
|
|
7186
|
+
if (!VALID_CLAUDE_PERMISSION_MODES.has(mapped)) {
|
|
7187
|
+
logger.log(`[Session ${sessionId}] Unknown permission mode '${mode}' \u2014 falling back to 'default'`);
|
|
7188
|
+
return "default";
|
|
7189
|
+
}
|
|
7190
|
+
return mapped;
|
|
6921
7191
|
};
|
|
6922
|
-
const toClaudePermissionMode = (mode) => CLAUDE_PERMISSION_MODE_MAP[mode] || mode;
|
|
6923
7192
|
const getActiveSecurityContext = () => sessionMetadata.securityContext ?? options2.securityContext;
|
|
6924
7193
|
const shouldIsolateSession = () => shouldIsolate({
|
|
6925
7194
|
forceIsolation: options2.forceIsolation,
|
|
@@ -6956,11 +7225,28 @@ async function startDaemon(options) {
|
|
|
6956
7225
|
if (model) args.push("--model", model);
|
|
6957
7226
|
if (appendSystemPrompt) args.push("--append-system-prompt", appendSystemPrompt);
|
|
6958
7227
|
if (claudeResumeId) args.push("--resume", claudeResumeId);
|
|
7228
|
+
if (!hookSettings) {
|
|
7229
|
+
try {
|
|
7230
|
+
hookSettings = generateHookSettings({ id: sessionId });
|
|
7231
|
+
logger.log(`[Session ${sessionId}] Hook settings: ${hookSettings.settingsPath} (validator: ${hookSettings.validatorPath})`);
|
|
7232
|
+
} catch (err) {
|
|
7233
|
+
logger.log(`[Session ${sessionId}] Failed to generate hook settings: ${err?.message || err}`);
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7236
|
+
if (hookSettings) args.push("--settings", hookSettings.settingsPath);
|
|
6959
7237
|
let spawnCommand = "claude";
|
|
6960
7238
|
let spawnArgs = args;
|
|
6961
7239
|
let extraEnv = {};
|
|
6962
7240
|
isolationCleanupFiles = [];
|
|
6963
7241
|
const isoConfig = buildIsolationConfig2(directory);
|
|
7242
|
+
if (isoConfig && hookSettings) {
|
|
7243
|
+
if (isoConfig.method === "nono") {
|
|
7244
|
+
isoConfig.nonoConfig = isoConfig.nonoConfig || {};
|
|
7245
|
+
const readFiles = isoConfig.nonoConfig.readFiles || [];
|
|
7246
|
+
readFiles.push(hookSettings.validatorPath, hookSettings.settingsPath);
|
|
7247
|
+
isoConfig.nonoConfig.readFiles = readFiles;
|
|
7248
|
+
}
|
|
7249
|
+
}
|
|
6964
7250
|
if (isoConfig) {
|
|
6965
7251
|
const wrapped = wrapWithIsolation(spawnCommand, spawnArgs, isoConfig);
|
|
6966
7252
|
spawnCommand = wrapped.command;
|
|
@@ -7316,7 +7602,7 @@ ${progressContent}
|
|
|
7316
7602
|
</system-reminder>
|
|
7317
7603
|
|
|
7318
7604
|
The automated loop has finished. Review the progress above and let me know if you need anything else.`;
|
|
7319
|
-
unlinkSync(progressPath);
|
|
7605
|
+
unlinkSync$1(progressPath);
|
|
7320
7606
|
logger.log(`[Session ${sessionId}] Injected progress file content and deleted ${progressPath}`);
|
|
7321
7607
|
}
|
|
7322
7608
|
}
|
|
@@ -7663,12 +7949,12 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7663
7949
|
} catch {
|
|
7664
7950
|
text = typeof content === "string" ? content : JSON.stringify(content);
|
|
7665
7951
|
}
|
|
7666
|
-
if (msgMeta?.permissionMode) {
|
|
7667
|
-
currentPermissionMode = toClaudePermissionMode(msgMeta.permissionMode);
|
|
7668
|
-
logger.log(`[Session ${sessionId}] Permission mode updated to: ${currentPermissionMode}`);
|
|
7669
|
-
}
|
|
7670
7952
|
if (msgMeta) {
|
|
7671
|
-
|
|
7953
|
+
const { permissionMode: incomingPermissionMode, ...safeMsgMeta } = msgMeta;
|
|
7954
|
+
if (incomingPermissionMode && toClaudePermissionMode(incomingPermissionMode) !== currentPermissionMode) {
|
|
7955
|
+
logger.log(`[Session ${sessionId}] Ignoring meta.permissionMode='${incomingPermissionMode}' from sendMessage (current: ${currentPermissionMode}; use switchMode to change)`);
|
|
7956
|
+
}
|
|
7957
|
+
lastSpawnMeta = { ...lastSpawnMeta, ...safeMsgMeta };
|
|
7672
7958
|
}
|
|
7673
7959
|
if (isKillingClaude || isRestartingClaude || isSwitchingMode) {
|
|
7674
7960
|
logger.log(`[Session ${sessionId}] Message received while restarting Claude, queuing to prevent loss`);
|
|
@@ -7832,12 +8118,13 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7832
8118
|
}
|
|
7833
8119
|
},
|
|
7834
8120
|
onSwitchMode: async (mode) => {
|
|
7835
|
-
|
|
8121
|
+
const normalizedMode = toClaudePermissionMode(mode);
|
|
8122
|
+
logger.log(`[Session ${sessionId}] Switch mode: ${mode}${mode !== normalizedMode ? ` \u2192 ${normalizedMode}` : ""}`);
|
|
7836
8123
|
if (isRestartingClaude || isSwitchingMode) {
|
|
7837
8124
|
logger.log(`[Session ${sessionId}] Switch mode deferred \u2014 restart/switch already in progress`);
|
|
7838
8125
|
return;
|
|
7839
8126
|
}
|
|
7840
|
-
currentPermissionMode =
|
|
8127
|
+
currentPermissionMode = normalizedMode;
|
|
7841
8128
|
if (claudeProcess && claudeProcess.exitCode === null) {
|
|
7842
8129
|
isSwitchingMode = true;
|
|
7843
8130
|
isKillingClaude = true;
|
|
@@ -7845,7 +8132,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7845
8132
|
await killAndWaitForExit2(claudeProcess);
|
|
7846
8133
|
isKillingClaude = false;
|
|
7847
8134
|
if (trackedSession?.stopped) return;
|
|
7848
|
-
spawnClaude(void 0, { permissionMode:
|
|
8135
|
+
spawnClaude(void 0, { permissionMode: normalizedMode });
|
|
7849
8136
|
} finally {
|
|
7850
8137
|
isKillingClaude = false;
|
|
7851
8138
|
isSwitchingMode = false;
|
|
@@ -8142,6 +8429,12 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8142
8429
|
resumeSessionId: claudeResumeId,
|
|
8143
8430
|
restartAgent: restartClaudeHandler,
|
|
8144
8431
|
cleanupCredentials: stagedCredentials?.cleanup,
|
|
8432
|
+
// Closure: hookSettings is generated lazily on first spawnClaude,
|
|
8433
|
+
// after this trackedSession is registered, so resolve the current
|
|
8434
|
+
// value at cleanup time rather than capturing null now.
|
|
8435
|
+
cleanupHookSettings: () => {
|
|
8436
|
+
hookSettings?.cleanup();
|
|
8437
|
+
},
|
|
8145
8438
|
get childProcess() {
|
|
8146
8439
|
return claudeProcess || void 0;
|
|
8147
8440
|
}
|
|
@@ -8161,6 +8454,8 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8161
8454
|
stagedCredentials.cleanup().catch(() => {
|
|
8162
8455
|
});
|
|
8163
8456
|
}
|
|
8457
|
+
const hs = hookSettings;
|
|
8458
|
+
if (hs) hs.cleanup();
|
|
8164
8459
|
return {
|
|
8165
8460
|
type: "error",
|
|
8166
8461
|
errorMessage: `Failed to register session service: ${err?.message || String(err)}`
|
|
@@ -8200,10 +8495,10 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8200
8495
|
var parseBashPermission = parseBashPermission2, shouldAutoAllow = shouldAutoAllow2;
|
|
8201
8496
|
let sessionMetadata = {
|
|
8202
8497
|
path: directory,
|
|
8203
|
-
host: os.hostname(),
|
|
8498
|
+
host: os$1.hostname(),
|
|
8204
8499
|
version: "0.1.0",
|
|
8205
8500
|
machineId,
|
|
8206
|
-
homeDir: os.homedir(),
|
|
8501
|
+
homeDir: os$1.homedir(),
|
|
8207
8502
|
svampHomeDir: SVAMP_HOME,
|
|
8208
8503
|
svampLibDir: join(__dirname$1, ".."),
|
|
8209
8504
|
svampToolsDir: join(__dirname$1, "..", "tools"),
|
|
@@ -8246,8 +8541,8 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8246
8541
|
} catch {
|
|
8247
8542
|
text = typeof content === "string" ? content : JSON.stringify(content);
|
|
8248
8543
|
}
|
|
8249
|
-
if (msgMeta?.permissionMode) {
|
|
8250
|
-
|
|
8544
|
+
if (msgMeta?.permissionMode && msgMeta.permissionMode !== currentPermissionMode) {
|
|
8545
|
+
logger.log(`[${agentName} Session ${sessionId}] Ignoring meta.permissionMode='${msgMeta.permissionMode}' from sendMessage (current: ${currentPermissionMode}; use switchMode to change)`);
|
|
8251
8546
|
}
|
|
8252
8547
|
if (!acpBackendReady) {
|
|
8253
8548
|
logger.log(`[${agentName} Session ${sessionId}] Backend not ready \u2014 queuing message`);
|
|
@@ -8792,6 +9087,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8792
9087
|
}
|
|
8793
9088
|
session.cleanupCredentials?.().catch(() => {
|
|
8794
9089
|
});
|
|
9090
|
+
session.cleanupHookSettings?.();
|
|
8795
9091
|
session.cleanupSvampConfig?.();
|
|
8796
9092
|
artifactSync.cancelSync(sessionId);
|
|
8797
9093
|
pidToTrackedSession.delete(pid);
|
|
@@ -8824,14 +9120,14 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8824
9120
|
logger.log(`Failed to detect isolation capabilities: ${err}`);
|
|
8825
9121
|
isolationCapabilities = { available: [], preferred: null, details: { nono: { found: false }, srt: { found: false }, bwrap: { found: false }, docker: { found: false }, podman: { found: false } } };
|
|
8826
9122
|
}
|
|
8827
|
-
const defaultHomeDir = existsSync$1("/data") ? "/data" : os.homedir();
|
|
9123
|
+
const defaultHomeDir = existsSync$1("/data") ? "/data" : os$1.homedir();
|
|
8828
9124
|
const persistedMachineMeta = loadPersistedMachineMetadata(SVAMP_HOME);
|
|
8829
9125
|
if (persistedMachineMeta) {
|
|
8830
9126
|
logger.log(`Restored machine metadata (sharing=${!!persistedMachineMeta.sharing}, securityContextConfig=${!!persistedMachineMeta.securityContextConfig}, injectPlatformGuidance=${persistedMachineMeta.injectPlatformGuidance})`);
|
|
8831
9127
|
}
|
|
8832
9128
|
const machineMetadata = {
|
|
8833
|
-
host: os.hostname(),
|
|
8834
|
-
platform: os.platform(),
|
|
9129
|
+
host: os$1.hostname(),
|
|
9130
|
+
platform: os$1.platform(),
|
|
8835
9131
|
svampVersion: "0.1.0 (hypha)",
|
|
8836
9132
|
homeDir: defaultHomeDir,
|
|
8837
9133
|
svampHomeDir: SVAMP_HOME,
|
|
@@ -9111,6 +9407,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
9111
9407
|
});
|
|
9112
9408
|
session.cleanupCredentials?.().catch(() => {
|
|
9113
9409
|
});
|
|
9410
|
+
session.cleanupHookSettings?.();
|
|
9114
9411
|
session.cleanupSvampConfig?.();
|
|
9115
9412
|
if (session.svampSessionId) artifactSync.cancelSync(session.svampSessionId);
|
|
9116
9413
|
pidToTrackedSession.delete(key);
|
|
@@ -9185,18 +9482,32 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
9185
9482
|
}
|
|
9186
9483
|
}
|
|
9187
9484
|
}
|
|
9188
|
-
const FRPC_FAILING_THRESHOLD_MS =
|
|
9485
|
+
const FRPC_FAILING_THRESHOLD_MS = 2 * 6e4;
|
|
9486
|
+
const PROBE_STALENESS_THRESHOLD_MS = 2 * 6e4;
|
|
9487
|
+
const tunnelLooksDead = (h) => {
|
|
9488
|
+
if (h.failingDurationMs > FRPC_FAILING_THRESHOLD_MS) {
|
|
9489
|
+
return `failing ${Math.round(h.failingDurationMs / 1e3)}s`;
|
|
9490
|
+
}
|
|
9491
|
+
if (h.probe && !h.probe.ok && h.probe.stalenessMs > PROBE_STALENESS_THRESHOLD_MS) {
|
|
9492
|
+
return `probe stale ${Math.round(h.probe.stalenessMs / 1e3)}s`;
|
|
9493
|
+
}
|
|
9494
|
+
return null;
|
|
9495
|
+
};
|
|
9189
9496
|
const serveHealth = serveManager.getTunnelHealth();
|
|
9190
|
-
if (serveHealth
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
logger.log(`
|
|
9194
|
-
|
|
9497
|
+
if (serveHealth) {
|
|
9498
|
+
const reason = tunnelLooksDead(serveHealth);
|
|
9499
|
+
if (reason) {
|
|
9500
|
+
logger.log(`Serve manager tunnel ${reason} (${serveHealth.consecutiveErrors} errors, ${serveHealth.restartAttempts} restarts) \u2014 recreating`);
|
|
9501
|
+
serveManager.recreateTunnel().catch((err) => {
|
|
9502
|
+
logger.log(`Failed to recreate serve tunnel: ${err.message}`);
|
|
9503
|
+
});
|
|
9504
|
+
}
|
|
9195
9505
|
}
|
|
9196
9506
|
for (const [name, tunnel] of tunnels) {
|
|
9197
9507
|
const health = tunnel.status;
|
|
9198
|
-
|
|
9199
|
-
|
|
9508
|
+
const reason = tunnelLooksDead(health);
|
|
9509
|
+
if (reason) {
|
|
9510
|
+
logger.log(`frpc tunnel '${name}' ${reason} \u2014 destroying stale tunnel`);
|
|
9200
9511
|
tunnel.destroy();
|
|
9201
9512
|
tunnels.delete(name);
|
|
9202
9513
|
}
|
|
@@ -9533,7 +9844,7 @@ async function restartDaemon() {
|
|
|
9533
9844
|
function daemonStatus() {
|
|
9534
9845
|
const state = readDaemonStateFile();
|
|
9535
9846
|
if (!state) {
|
|
9536
|
-
const plistPath = join(os.homedir(), "Library", "LaunchAgents", "io.hypha.svamp.daemon.plist");
|
|
9847
|
+
const plistPath = join(os$1.homedir(), "Library", "LaunchAgents", "io.hypha.svamp.daemon.plist");
|
|
9537
9848
|
if (existsSync$1(plistPath)) {
|
|
9538
9849
|
console.log("Status: Not running (launchd service installed \u2014 may be starting)");
|
|
9539
9850
|
} else {
|
|
@@ -9572,4 +9883,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
9572
9883
|
stopDaemon: stopDaemon
|
|
9573
9884
|
});
|
|
9574
9885
|
|
|
9575
|
-
export { DefaultTransport$1 as D, GeminiTransport$1 as G, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, resolveSecurityContext as e, buildSecurityContextFromFlags as f, getHyphaServerUrl as g,
|
|
9886
|
+
export { DefaultTransport$1 as D, GeminiTransport$1 as G, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, resolveSecurityContext as e, buildSecurityContextFromFlags as f, getHyphaServerUrl as g, generateHookSettings as h, acpBackend as i, acpAgentConfig as j, codexMcpBackend as k, loadSecurityContextConfig as l, mergeSecurityContexts as m, claudeAuth as n, run as o, registerMachineService as r, startDaemon as s };
|