svamp-cli 0.1.76 → 0.1.78
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-NVZzP_Vo.mjs → agentCommands-uNFhhdN1.mjs} +16 -51
- package/dist/cli.mjs +82 -30
- package/dist/{commands-lJ8V7MJE.mjs → commands-B6FEeZeP.mjs} +32 -36
- package/dist/{commands-CADr1mQg.mjs → commands-BYbuedOK.mjs} +4 -4
- package/dist/{commands-7Iw1nFwf.mjs → commands-Cf3mXxPZ.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{package-Dpz1MLO4.mjs → package-DTOqWYBv.mjs} +2 -2
- package/dist/{run-B29grSMh.mjs → run-DqvxMsWh.mjs} +1 -1
- package/dist/{run-BnFGIK0c.mjs → run-DsXDjwLW.mjs} +199 -50
- package/dist/staticServer-CWcmMF5V.mjs +477 -0
- package/dist/{tunnel-C2kqST5d.mjs → tunnel-BDKdemh0.mjs} +51 -9
- package/package.json +2 -2
- package/dist/staticServer-B-S9sl6E.mjs +0 -198
|
@@ -296,7 +296,7 @@ Service is live:`);
|
|
|
296
296
|
}
|
|
297
297
|
} else {
|
|
298
298
|
console.log(`No SANDBOX_ID detected \u2014 starting reverse tunnel.`);
|
|
299
|
-
const { runTunnel } = await import('./tunnel-
|
|
299
|
+
const { runTunnel } = await import('./tunnel-BDKdemh0.mjs');
|
|
300
300
|
await runTunnel(name, ports);
|
|
301
301
|
}
|
|
302
302
|
} catch (err) {
|
|
@@ -320,7 +320,7 @@ async function serviceServe(args) {
|
|
|
320
320
|
if (subdomain) validateSubdomain(subdomain);
|
|
321
321
|
const healthInterval = healthIntervalStr ? parseInt(healthIntervalStr, 10) : void 0;
|
|
322
322
|
try {
|
|
323
|
-
const { startStaticServer } = await import('./staticServer-
|
|
323
|
+
const { startStaticServer } = await import('./staticServer-CWcmMF5V.mjs');
|
|
324
324
|
const resolvedDir = require("path").resolve(directory);
|
|
325
325
|
console.log(`Serving ${resolvedDir}`);
|
|
326
326
|
const staticServer = await startStaticServer({
|
|
@@ -345,7 +345,7 @@ Serving ${resolvedDir}:`);
|
|
|
345
345
|
}
|
|
346
346
|
} else {
|
|
347
347
|
console.log(`No SANDBOX_ID detected \u2014 starting reverse tunnel.`);
|
|
348
|
-
const { runTunnel } = await import('./tunnel-
|
|
348
|
+
const { runTunnel } = await import('./tunnel-BDKdemh0.mjs');
|
|
349
349
|
await runTunnel(name, ports);
|
|
350
350
|
}
|
|
351
351
|
const cleanup = () => {
|
|
@@ -367,7 +367,7 @@ async function serviceTunnel(args) {
|
|
|
367
367
|
console.error("Usage: svamp service tunnel <name> --port <port> [--port <port2>]");
|
|
368
368
|
process.exit(1);
|
|
369
369
|
}
|
|
370
|
-
const { runTunnel } = await import('./tunnel-
|
|
370
|
+
const { runTunnel } = await import('./tunnel-BDKdemh0.mjs');
|
|
371
371
|
await runTunnel(name, ports);
|
|
372
372
|
}
|
|
373
373
|
async function handleServiceCommand() {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { writeFileSync, readFileSync } from 'fs';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
|
-
import { connectAndGetMachine } from './commands-
|
|
3
|
+
import { connectAndGetMachine } from './commands-B6FEeZeP.mjs';
|
|
4
4
|
import 'node:fs';
|
|
5
5
|
import 'node:child_process';
|
|
6
6
|
import 'node:path';
|
|
7
7
|
import 'node:os';
|
|
8
|
-
import './run-
|
|
8
|
+
import './run-DsXDjwLW.mjs';
|
|
9
9
|
import 'os';
|
|
10
10
|
import 'fs/promises';
|
|
11
11
|
import 'url';
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-
|
|
1
|
+
export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-DsXDjwLW.mjs';
|
|
2
2
|
import 'os';
|
|
3
3
|
import 'fs/promises';
|
|
4
4
|
import 'fs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var name = "svamp-cli";
|
|
2
|
-
var version = "0.1.
|
|
2
|
+
var version = "0.1.78";
|
|
3
3
|
var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
|
|
4
4
|
var author = "Amun AI AB";
|
|
5
5
|
var license = "SEE LICENSE IN LICENSE";
|
|
@@ -29,7 +29,7 @@ var dependencies = {
|
|
|
29
29
|
"@agentclientprotocol/sdk": "^0.14.1",
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
31
31
|
"hypha-rpc": "0.21.34",
|
|
32
|
-
"node-pty": "
|
|
32
|
+
"node-pty": "1.2.0-beta.11",
|
|
33
33
|
ws: "^8.18.0",
|
|
34
34
|
yaml: "^2.8.2",
|
|
35
35
|
zod: "^3.24.4"
|
|
@@ -2,7 +2,7 @@ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(im
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { mkdirSync, writeFileSync, existsSync, unlinkSync, readFileSync, watch } from 'node:fs';
|
|
5
|
-
import { c as connectToHypha, a as registerSessionService } from './run-
|
|
5
|
+
import { c as connectToHypha, a as registerSessionService } from './run-DsXDjwLW.mjs';
|
|
6
6
|
import { createServer } from 'node:http';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { createInterface } from 'node:readline';
|
|
@@ -309,18 +309,6 @@ async function getPtyModule() {
|
|
|
309
309
|
function generateTerminalId() {
|
|
310
310
|
return `term-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
311
311
|
}
|
|
312
|
-
const ALLOWED_CONTROL_CHARS = /* @__PURE__ */ new Set([7, 8, 9, 10, 11, 12, 13, 27]);
|
|
313
|
-
function filterForXterm(text) {
|
|
314
|
-
if (!text) return text;
|
|
315
|
-
let result = "";
|
|
316
|
-
for (let i = 0; i < text.length; i++) {
|
|
317
|
-
const code = text.charCodeAt(i);
|
|
318
|
-
if (code >= 32 && code <= 126 || ALLOWED_CONTROL_CHARS.has(code) || code > 127) {
|
|
319
|
-
result += text[i];
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return result;
|
|
323
|
-
}
|
|
324
312
|
function getMachineMetadataPath(svampHomeDir) {
|
|
325
313
|
return join(svampHomeDir, "machine-metadata.json");
|
|
326
314
|
}
|
|
@@ -705,13 +693,25 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
705
693
|
const cwd = params.cwd || getHomedir();
|
|
706
694
|
const shell = params.shell || process.env.SHELL || "bash";
|
|
707
695
|
const sessionId = generateTerminalId();
|
|
696
|
+
const cleanEnv = { ...process.env };
|
|
697
|
+
const claudeEnvVars = Object.keys(cleanEnv).filter(
|
|
698
|
+
(k) => k.startsWith("CLAUDE_") || k.startsWith("MCP_") || k === "ANTHROPIC_API_KEY"
|
|
699
|
+
);
|
|
700
|
+
for (const k of claudeEnvVars) delete cleanEnv[k];
|
|
708
701
|
const ptyProcess = pty.spawn(shell, [], {
|
|
709
702
|
name: "xterm-256color",
|
|
710
703
|
cols,
|
|
711
704
|
rows,
|
|
712
705
|
cwd,
|
|
713
|
-
env: {
|
|
706
|
+
env: {
|
|
707
|
+
...cleanEnv,
|
|
708
|
+
TERM: "xterm-256color"
|
|
709
|
+
}
|
|
714
710
|
});
|
|
711
|
+
if (shell.endsWith("zsh")) {
|
|
712
|
+
ptyProcess.write("unsetopt PROMPT_SP\n");
|
|
713
|
+
ptyProcess.write("clear\n");
|
|
714
|
+
}
|
|
715
715
|
const session = {
|
|
716
716
|
id: sessionId,
|
|
717
717
|
pty: ptyProcess,
|
|
@@ -723,21 +723,30 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
723
723
|
cwd
|
|
724
724
|
};
|
|
725
725
|
terminalSessions.set(sessionId, session);
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
726
|
+
let outputBatch = "";
|
|
727
|
+
let outputTimer = null;
|
|
728
|
+
const flushOutput = () => {
|
|
729
|
+
outputTimer = null;
|
|
730
|
+
if (!outputBatch) return;
|
|
731
|
+
const batch = outputBatch;
|
|
732
|
+
outputBatch = "";
|
|
733
|
+
session.outputBuffer.push(batch);
|
|
730
734
|
if (session.outputBuffer.length > 1e3) {
|
|
731
735
|
session.outputBuffer.splice(0, session.outputBuffer.length - 500);
|
|
732
736
|
}
|
|
733
737
|
try {
|
|
734
738
|
server.emit({
|
|
735
739
|
type: "svamp:terminal-output",
|
|
736
|
-
data: { type: "output", content:
|
|
740
|
+
data: { type: "output", content: batch, sessionId },
|
|
737
741
|
to: "*"
|
|
738
742
|
});
|
|
739
743
|
} catch {
|
|
740
744
|
}
|
|
745
|
+
};
|
|
746
|
+
ptyProcess.onData((data) => {
|
|
747
|
+
if (!data) return;
|
|
748
|
+
outputBatch += data;
|
|
749
|
+
if (!outputTimer) outputTimer = setTimeout(flushOutput, 16);
|
|
741
750
|
});
|
|
742
751
|
ptyProcess.onExit(({ exitCode, signal }) => {
|
|
743
752
|
session.exited = true;
|
|
@@ -751,6 +760,11 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
751
760
|
});
|
|
752
761
|
} catch {
|
|
753
762
|
}
|
|
763
|
+
setTimeout(() => {
|
|
764
|
+
if (terminalSessions.has(sessionId)) {
|
|
765
|
+
terminalSessions.delete(sessionId);
|
|
766
|
+
}
|
|
767
|
+
}, 6e4);
|
|
754
768
|
});
|
|
755
769
|
return { sessionId, cols, rows };
|
|
756
770
|
},
|
|
@@ -792,12 +806,15 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
792
806
|
}
|
|
793
807
|
return result;
|
|
794
808
|
},
|
|
795
|
-
/** Stop (kill) a terminal session. */
|
|
809
|
+
/** Stop (kill) a terminal session. Idempotent — returns success even if already gone. */
|
|
796
810
|
terminalStop: async (params, context) => {
|
|
797
811
|
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
798
812
|
const session = terminalSessions.get(params.sessionId);
|
|
799
|
-
if (!session)
|
|
800
|
-
|
|
813
|
+
if (!session) return { success: true };
|
|
814
|
+
try {
|
|
815
|
+
session.pty.kill();
|
|
816
|
+
} catch {
|
|
817
|
+
}
|
|
801
818
|
terminalSessions.delete(params.sessionId);
|
|
802
819
|
return { success: true };
|
|
803
820
|
},
|
|
@@ -813,6 +830,19 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
813
830
|
exited: s.exited
|
|
814
831
|
}));
|
|
815
832
|
},
|
|
833
|
+
/** Reattach to an existing terminal session. Returns buffered output for scrollback replay. */
|
|
834
|
+
terminalReattach: async (params, context) => {
|
|
835
|
+
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
836
|
+
const session = terminalSessions.get(params.sessionId);
|
|
837
|
+
if (!session) throw new Error(`Terminal session ${params.sessionId} not found`);
|
|
838
|
+
const scrollback = session.outputBuffer.join("");
|
|
839
|
+
if (params.cols && params.rows) {
|
|
840
|
+
session.pty.resize(params.cols, params.rows);
|
|
841
|
+
session.cols = params.cols;
|
|
842
|
+
session.rows = params.rows;
|
|
843
|
+
}
|
|
844
|
+
return { sessionId: session.id, cols: session.cols, rows: session.rows, scrollback, exited: session.exited };
|
|
845
|
+
},
|
|
816
846
|
// Machine-level directory listing (read-only, view role)
|
|
817
847
|
listDirectory: async (path, options, context) => {
|
|
818
848
|
authorizeRequest(context, currentMetadata.sharing, "view");
|
|
@@ -944,6 +974,48 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
944
974
|
const { deleteServiceGroup } = await import('./api-BRbsyqJ4.mjs');
|
|
945
975
|
return deleteServiceGroup(params.name);
|
|
946
976
|
},
|
|
977
|
+
// ── Tunnel management ────────────────────────────────────────────
|
|
978
|
+
/** Start a reverse tunnel for a service group (local/cloud machine). */
|
|
979
|
+
tunnelStart: async (params, context) => {
|
|
980
|
+
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
981
|
+
const tunnels = handlers.tunnels;
|
|
982
|
+
if (!tunnels) throw new Error("Tunnel management not available");
|
|
983
|
+
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
984
|
+
const { TunnelClient } = await import('./tunnel-BDKdemh0.mjs');
|
|
985
|
+
const client = new TunnelClient({
|
|
986
|
+
name: params.name,
|
|
987
|
+
ports: params.ports,
|
|
988
|
+
maxReconnectAttempts: 0,
|
|
989
|
+
// infinite for daemon
|
|
990
|
+
onError: (err) => console.error(`[TUNNEL] ${params.name}: ${err.message}`),
|
|
991
|
+
onConnect: () => console.log(`[TUNNEL] ${params.name}: connected`),
|
|
992
|
+
onDisconnect: () => console.log(`[TUNNEL] ${params.name}: disconnected, reconnecting...`)
|
|
993
|
+
});
|
|
994
|
+
await client.connect();
|
|
995
|
+
tunnels.set(params.name, client);
|
|
996
|
+
return { name: params.name, ports: params.ports, ...client.status };
|
|
997
|
+
},
|
|
998
|
+
/** Stop a tunnel. */
|
|
999
|
+
tunnelStop: async (params, context) => {
|
|
1000
|
+
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
1001
|
+
const tunnels = handlers.tunnels;
|
|
1002
|
+
if (!tunnels) throw new Error("Tunnel management not available");
|
|
1003
|
+
const client = tunnels.get(params.name);
|
|
1004
|
+
if (!client) throw new Error(`Tunnel '${params.name}' not found`);
|
|
1005
|
+
client.destroy();
|
|
1006
|
+
tunnels.delete(params.name);
|
|
1007
|
+
return { name: params.name, stopped: true };
|
|
1008
|
+
},
|
|
1009
|
+
/** List active tunnels with health status. */
|
|
1010
|
+
tunnelList: async (context) => {
|
|
1011
|
+
authorizeRequest(context, currentMetadata.sharing, "view");
|
|
1012
|
+
const tunnels = handlers.tunnels;
|
|
1013
|
+
if (!tunnels) return [];
|
|
1014
|
+
return Array.from(tunnels.entries()).map(([name, client]) => ({
|
|
1015
|
+
name,
|
|
1016
|
+
...client.status
|
|
1017
|
+
}));
|
|
1018
|
+
},
|
|
947
1019
|
// WISE voice — create ephemeral token for OpenAI Realtime API
|
|
948
1020
|
wiseCreateEphemeralToken: async (params, context) => {
|
|
949
1021
|
authorizeRequest(context, currentMetadata.sharing, "interact");
|
|
@@ -1350,10 +1422,10 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
1350
1422
|
if (!callbacks.onListDirectory) throw new Error("listDirectory not supported");
|
|
1351
1423
|
return await callbacks.onListDirectory(path);
|
|
1352
1424
|
},
|
|
1353
|
-
bash: async (command, cwd, context) => {
|
|
1425
|
+
bash: async (command, cwd, timeout, context) => {
|
|
1354
1426
|
authorizeRequest(context, metadata.sharing, "admin");
|
|
1355
1427
|
if (!callbacks.onBash) throw new Error("bash not supported");
|
|
1356
|
-
return await callbacks.onBash(command, cwd);
|
|
1428
|
+
return await callbacks.onBash(command, cwd, timeout);
|
|
1357
1429
|
},
|
|
1358
1430
|
ripgrep: async (args, cwd, context) => {
|
|
1359
1431
|
authorizeRequest(context, metadata.sharing, "admin");
|
|
@@ -1399,7 +1471,8 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
1399
1471
|
callbacks.onSharingUpdate?.(newSharing);
|
|
1400
1472
|
return { success: true, sharing: newSharing };
|
|
1401
1473
|
},
|
|
1402
|
-
/** Update security context and restart the agent process with new rules
|
|
1474
|
+
/** Update security context and restart the agent process with new rules.
|
|
1475
|
+
* Pass '__disable__' sentinel (from frontend) or null to disable isolation entirely. */
|
|
1403
1476
|
updateSecurityContext: async (newSecurityContext, context) => {
|
|
1404
1477
|
authorizeRequest(context, metadata.sharing, "admin");
|
|
1405
1478
|
if (metadata.sharing && context?.user?.email && metadata.sharing.owner && context.user.email.toLowerCase() !== metadata.sharing.owner.toLowerCase()) {
|
|
@@ -1408,14 +1481,15 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
1408
1481
|
if (!callbacks.onUpdateSecurityContext) {
|
|
1409
1482
|
throw new Error("Security context updates are not supported for this session");
|
|
1410
1483
|
}
|
|
1411
|
-
|
|
1484
|
+
const resolvedContext = newSecurityContext === "__disable__" ? null : newSecurityContext;
|
|
1485
|
+
metadata = { ...metadata, securityContext: resolvedContext };
|
|
1412
1486
|
metadataVersion++;
|
|
1413
1487
|
notifyListeners({
|
|
1414
1488
|
type: "update-session",
|
|
1415
1489
|
sessionId,
|
|
1416
1490
|
metadata: { value: metadata, version: metadataVersion }
|
|
1417
1491
|
});
|
|
1418
|
-
return await callbacks.onUpdateSecurityContext(
|
|
1492
|
+
return await callbacks.onUpdateSecurityContext(resolvedContext);
|
|
1419
1493
|
},
|
|
1420
1494
|
/** Apply a new system prompt and restart the agent process */
|
|
1421
1495
|
applySystemPrompt: async (prompt, context) => {
|
|
@@ -5629,6 +5703,7 @@ async function startDaemon(options) {
|
|
|
5629
5703
|
let server = null;
|
|
5630
5704
|
const supervisor = new ProcessSupervisor(join(SVAMP_HOME, "processes"));
|
|
5631
5705
|
await supervisor.init();
|
|
5706
|
+
const tunnels = /* @__PURE__ */ new Map();
|
|
5632
5707
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
5633
5708
|
});
|
|
5634
5709
|
preventMachineSleep(logger);
|
|
@@ -5749,6 +5824,7 @@ async function startDaemon(options) {
|
|
|
5749
5824
|
});
|
|
5750
5825
|
}, buildIsolationConfig2 = function(dir) {
|
|
5751
5826
|
if (!options2.forceIsolation && !sessionMetadata.sharing?.enabled) return null;
|
|
5827
|
+
if (sessionMetadata.securityContext === null) return null;
|
|
5752
5828
|
const method = isolationCapabilities.preferred;
|
|
5753
5829
|
if (!method) return null;
|
|
5754
5830
|
const detail = isolationCapabilities.details[method];
|
|
@@ -5768,7 +5844,7 @@ async function startDaemon(options) {
|
|
|
5768
5844
|
if (sessionMetadata.sharing?.enabled && stagedCredentials) {
|
|
5769
5845
|
config.credentialStagingPath = stagedCredentials.homePath;
|
|
5770
5846
|
}
|
|
5771
|
-
const activeSecurityContext = sessionMetadata.securityContext
|
|
5847
|
+
const activeSecurityContext = sessionMetadata.securityContext ?? options2.securityContext;
|
|
5772
5848
|
if (activeSecurityContext) {
|
|
5773
5849
|
config = applySecurityContext(config, activeSecurityContext);
|
|
5774
5850
|
}
|
|
@@ -6484,6 +6560,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6484
6560
|
}
|
|
6485
6561
|
if (claudeResumeId) {
|
|
6486
6562
|
spawnClaude(void 0, { permissionMode: currentPermissionMode });
|
|
6563
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
6487
6564
|
logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
|
|
6488
6565
|
return { success: true, message: "Claude process restarted successfully." };
|
|
6489
6566
|
} else {
|
|
@@ -6737,11 +6814,12 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6737
6814
|
onUpdateConfig: (patch) => {
|
|
6738
6815
|
writeSvampConfigPatch(patch);
|
|
6739
6816
|
},
|
|
6740
|
-
onBash: async (command, cwd) => {
|
|
6741
|
-
|
|
6817
|
+
onBash: async (command, cwd, timeout) => {
|
|
6818
|
+
const execTimeout = timeout || 12e4;
|
|
6819
|
+
logger.log(`[Session ${sessionId}] Bash: ${command} (cwd: ${cwd || directory}, timeout: ${execTimeout}ms)`);
|
|
6742
6820
|
const { exec } = await import('child_process');
|
|
6743
6821
|
return new Promise((resolve2) => {
|
|
6744
|
-
exec(command, { cwd: cwd || directory, timeout:
|
|
6822
|
+
exec(command, { cwd: cwd || directory, timeout: execTimeout, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
6745
6823
|
if (err) {
|
|
6746
6824
|
resolve2({ success: false, stdout: stdout || "", stderr: stderr || err.message, exitCode: err.code ?? 1 });
|
|
6747
6825
|
} else {
|
|
@@ -7170,10 +7248,11 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7170
7248
|
onUpdateConfig: (patch) => {
|
|
7171
7249
|
writeSvampConfigPatchAcp(patch);
|
|
7172
7250
|
},
|
|
7173
|
-
onBash: async (command, cwd) => {
|
|
7251
|
+
onBash: async (command, cwd, timeout) => {
|
|
7252
|
+
const execTimeout = timeout || 12e4;
|
|
7174
7253
|
const { exec } = await import('child_process');
|
|
7175
7254
|
return new Promise((resolve2) => {
|
|
7176
|
-
exec(command, { cwd: cwd || directory, timeout:
|
|
7255
|
+
exec(command, { cwd: cwd || directory, timeout: execTimeout, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
7177
7256
|
if (err) {
|
|
7178
7257
|
resolve2({ success: false, stdout: stdout || "", stderr: stderr || err.message, exitCode: err.code ?? 1 });
|
|
7179
7258
|
} else {
|
|
@@ -7624,7 +7703,8 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7624
7703
|
}
|
|
7625
7704
|
return ids;
|
|
7626
7705
|
},
|
|
7627
|
-
supervisor
|
|
7706
|
+
supervisor,
|
|
7707
|
+
tunnels
|
|
7628
7708
|
}
|
|
7629
7709
|
);
|
|
7630
7710
|
logger.log(`Machine service registered: svamp-machine-${machineId}`);
|
|
@@ -8003,6 +8083,11 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8003
8083
|
}
|
|
8004
8084
|
await supervisor.stopAll().catch(() => {
|
|
8005
8085
|
});
|
|
8086
|
+
for (const [name, client] of tunnels) {
|
|
8087
|
+
client.destroy();
|
|
8088
|
+
logger.log(`Tunnel '${name}' destroyed`);
|
|
8089
|
+
}
|
|
8090
|
+
tunnels.clear();
|
|
8006
8091
|
artifactSync.destroy();
|
|
8007
8092
|
try {
|
|
8008
8093
|
await server.disconnect();
|
|
@@ -8033,33 +8118,97 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8033
8118
|
}
|
|
8034
8119
|
}
|
|
8035
8120
|
async function stopDaemon(options) {
|
|
8121
|
+
const signal = options?.cleanup ? "SIGUSR1" : "SIGTERM";
|
|
8122
|
+
const mode = options?.cleanup ? "cleanup (sessions will be stopped)" : "quick (sessions preserved for auto-restore)";
|
|
8123
|
+
const pidsToSignal = [];
|
|
8124
|
+
const supervisorPidFile = join(SVAMP_HOME, "supervisor.pid");
|
|
8125
|
+
try {
|
|
8126
|
+
if (existsSync$1(supervisorPidFile)) {
|
|
8127
|
+
const supervisorPid = parseInt(readFileSync$1(supervisorPidFile, "utf-8").trim(), 10);
|
|
8128
|
+
if (supervisorPid && !isNaN(supervisorPid)) {
|
|
8129
|
+
try {
|
|
8130
|
+
process.kill(supervisorPid, 0);
|
|
8131
|
+
pidsToSignal.push(supervisorPid);
|
|
8132
|
+
} catch {
|
|
8133
|
+
}
|
|
8134
|
+
}
|
|
8135
|
+
}
|
|
8136
|
+
} catch {
|
|
8137
|
+
}
|
|
8036
8138
|
const state = readDaemonStateFile();
|
|
8037
|
-
if (
|
|
8139
|
+
if (state) {
|
|
8140
|
+
try {
|
|
8141
|
+
process.kill(state.pid, 0);
|
|
8142
|
+
if (!pidsToSignal.includes(state.pid)) {
|
|
8143
|
+
pidsToSignal.push(state.pid);
|
|
8144
|
+
}
|
|
8145
|
+
} catch {
|
|
8146
|
+
}
|
|
8147
|
+
}
|
|
8148
|
+
if (pidsToSignal.length === 0) {
|
|
8149
|
+
try {
|
|
8150
|
+
const { execSync } = await import('child_process');
|
|
8151
|
+
const pgrepOutput = execSync(
|
|
8152
|
+
'pgrep -f "svamp daemon start-(supervised|sync)" 2>/dev/null || true',
|
|
8153
|
+
{ encoding: "utf-8" }
|
|
8154
|
+
).trim();
|
|
8155
|
+
if (pgrepOutput) {
|
|
8156
|
+
for (const line of pgrepOutput.split("\n")) {
|
|
8157
|
+
const pid = parseInt(line.trim(), 10);
|
|
8158
|
+
if (pid && !isNaN(pid) && pid !== process.pid) {
|
|
8159
|
+
pidsToSignal.push(pid);
|
|
8160
|
+
}
|
|
8161
|
+
}
|
|
8162
|
+
}
|
|
8163
|
+
} catch {
|
|
8164
|
+
}
|
|
8165
|
+
}
|
|
8166
|
+
if (pidsToSignal.length === 0) {
|
|
8038
8167
|
console.log("No daemon running");
|
|
8168
|
+
cleanupDaemonStateFile();
|
|
8039
8169
|
return;
|
|
8040
8170
|
}
|
|
8041
|
-
const
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8171
|
+
for (const pid of pidsToSignal) {
|
|
8172
|
+
try {
|
|
8173
|
+
process.kill(pid, signal);
|
|
8174
|
+
console.log(`Sent ${signal} to PID ${pid} \u2014 ${mode}`);
|
|
8175
|
+
} catch {
|
|
8176
|
+
console.log(`PID ${pid} already gone`);
|
|
8177
|
+
}
|
|
8178
|
+
}
|
|
8179
|
+
pidsToSignal[0];
|
|
8180
|
+
for (let i = 0; i < 100; i++) {
|
|
8181
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
8182
|
+
const anyAlive = pidsToSignal.some((pid) => {
|
|
8049
8183
|
try {
|
|
8050
|
-
process.kill(
|
|
8184
|
+
process.kill(pid, 0);
|
|
8185
|
+
return true;
|
|
8186
|
+
} catch {
|
|
8187
|
+
return false;
|
|
8188
|
+
}
|
|
8189
|
+
});
|
|
8190
|
+
if (!anyAlive) {
|
|
8191
|
+
console.log("Daemon stopped");
|
|
8192
|
+
cleanupDaemonStateFile();
|
|
8193
|
+
try {
|
|
8194
|
+
if (existsSync$1(supervisorPidFile)) await import('fs').then((fs2) => fs2.promises.unlink(supervisorPidFile));
|
|
8051
8195
|
} catch {
|
|
8052
|
-
console.log("Daemon stopped");
|
|
8053
|
-
cleanupDaemonStateFile();
|
|
8054
|
-
return;
|
|
8055
8196
|
}
|
|
8197
|
+
return;
|
|
8198
|
+
}
|
|
8199
|
+
}
|
|
8200
|
+
console.log("Daemon did not stop in time, sending SIGKILL");
|
|
8201
|
+
for (const pid of pidsToSignal) {
|
|
8202
|
+
try {
|
|
8203
|
+
process.kill(pid, "SIGKILL");
|
|
8204
|
+
} catch {
|
|
8056
8205
|
}
|
|
8057
|
-
console.log("Daemon did not stop in time, sending SIGKILL");
|
|
8058
|
-
process.kill(state.pid, "SIGKILL");
|
|
8059
|
-
} catch {
|
|
8060
|
-
console.log("Daemon is not running (stale state file)");
|
|
8061
8206
|
}
|
|
8062
8207
|
cleanupDaemonStateFile();
|
|
8208
|
+
try {
|
|
8209
|
+
if (existsSync$1(supervisorPidFile)) await import('fs').then((fs2) => fs2.promises.unlink(supervisorPidFile));
|
|
8210
|
+
} catch {
|
|
8211
|
+
}
|
|
8063
8212
|
}
|
|
8064
8213
|
function daemonStatus() {
|
|
8065
8214
|
const state = readDaemonStateFile();
|