svamp-cli 0.1.29 → 0.1.30
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/cli.mjs +7 -7
- package/dist/{commands-CgT3AgJ0.mjs → commands-B2xQb9u7.mjs} +1 -1
- package/dist/{commands-CKTIJoV0.mjs → commands-C5pW2VmI.mjs} +286 -60
- package/dist/{commands-DDB3y1L1.mjs → commands-TZkNivgV.mjs} +295 -44
- package/dist/index.mjs +1 -1
- package/dist/{run-DZmxHj-e.mjs → run-CtCTd6if.mjs} +234 -50
- package/dist/{run-CuN6K7pN.mjs → run-Cxdc5Zmw.mjs} +316 -164
- package/dist/{run-DMD0N00A.mjs → run-dBWhjQRf.mjs} +316 -76
- package/package.json +1 -1
- package/dist/agent-cli.mjs +0 -453
- package/dist/commands-1CYZC6Xh.mjs +0 -481
- package/dist/commands-B1DcpgLW.mjs +0 -481
- package/dist/commands-BGmdgMAC.mjs +0 -485
- package/dist/commands-BOeSil-P.mjs +0 -459
- package/dist/commands-BU4GZQuH.mjs +0 -481
- package/dist/commands-Ba66PxtQ.mjs +0 -481
- package/dist/commands-C0-xqIIc.mjs +0 -481
- package/dist/commands-C7Qy5n6d.mjs +0 -481
- package/dist/commands-CKpC8R9T.mjs +0 -481
- package/dist/commands-CNqOjR1y.mjs +0 -481
- package/dist/commands-CVKh1tWr.mjs +0 -485
- package/dist/commands-CYBblX73.mjs +0 -485
- package/dist/commands-CZBYmj16.mjs +0 -485
- package/dist/commands-CcWIvCA4.mjs +0 -481
- package/dist/commands-Cfwf-cQG.mjs +0 -481
- package/dist/commands-DCNO2m66.mjs +0 -471
- package/dist/commands-DRIFvhmC.mjs +0 -481
- package/dist/commands-DXmw2dzy.mjs +0 -481
- package/dist/commands-DkSvlKFF.mjs +0 -485
- package/dist/commands-DnDd4Sew.mjs +0 -481
- package/dist/commands-DnpnAFQW.mjs +0 -485
- package/dist/commands-Do-TVYFm.mjs +0 -481
- package/dist/commands-GEXri0yz.mjs +0 -484
- package/dist/commands-Kzm0_XNH.mjs +0 -481
- package/dist/commands-MQvNbIid.mjs +0 -481
- package/dist/commands-_uCC3U1U.mjs +0 -481
- package/dist/commands-y2WG29W9.mjs +0 -485
- package/dist/hyphaClient-DLkclazm.mjs +0 -39
- package/dist/package-ASJ9pMHk.mjs +0 -60
- package/dist/package-B2FOzHaM.mjs +0 -57
- package/dist/package-Bk_PFVA0.mjs +0 -57
- package/dist/package-Bnij-ZtR.mjs +0 -57
- package/dist/package-BtRbHfjz.mjs +0 -57
- package/dist/package-C5B0twb8.mjs +0 -57
- package/dist/package-CC5d8_0L.mjs +0 -57
- package/dist/package-CCJ045H0.mjs +0 -60
- package/dist/package-CS219SXn.mjs +0 -57
- package/dist/package-Cd-9ktpd.mjs +0 -60
- package/dist/package-CgBD49cA.mjs +0 -57
- package/dist/package-CvnNnsm7.mjs +0 -60
- package/dist/package-DPXkSwHu.mjs +0 -57
- package/dist/package-DpqWz9Cr.mjs +0 -60
- package/dist/package-JqEt5Ib4.mjs +0 -57
- package/dist/package-k18Su1iE.mjs +0 -58
- package/dist/package-nzkXV1aM.mjs +0 -57
- package/dist/package-pNo6GC3a.mjs +0 -60
- package/dist/package-pZp14zKI.mjs +0 -57
- package/dist/run-4fyJcaRE.mjs +0 -3856
- package/dist/run-B6oqR83K.mjs +0 -4631
- package/dist/run-BI32lPRK.mjs +0 -3870
- package/dist/run-BQHneHfW.mjs +0 -3834
- package/dist/run-Bb4fyIWZ.mjs +0 -3812
- package/dist/run-BglwnB-A.mjs +0 -3889
- package/dist/run-BjVWuitO.mjs +0 -3919
- package/dist/run-BzUE-JUT.mjs +0 -3708
- package/dist/run-BzqS97Sx.mjs +0 -3666
- package/dist/run-C6snRxyh.mjs +0 -3826
- package/dist/run-C8CI8Ujj.mjs +0 -3693
- package/dist/run-CL-FS4Yc.mjs +0 -3933
- package/dist/run-CS1Z4GcM.mjs +0 -3786
- package/dist/run-CT7uizQo.mjs +0 -4492
- package/dist/run-CUIj4xbE.mjs +0 -4880
- package/dist/run-CW26vPqj.mjs +0 -3919
- package/dist/run-CkTufc0D.mjs +0 -3875
- package/dist/run-Cmostc0S.mjs +0 -3902
- package/dist/run-Cp3kKdzm.mjs +0 -3865
- package/dist/run-D0bCTY72.mjs +0 -3816
- package/dist/run-D4N6FQON.mjs +0 -4673
- package/dist/run-D4dlA0jo.mjs +0 -4813
- package/dist/run-DMW8ibIw.mjs +0 -3958
- package/dist/run-DO52unxE.mjs +0 -3950
- package/dist/run-DQ5FOQ_c.mjs +0 -4788
- package/dist/run-DT7FgL8L.mjs +0 -4339
- package/dist/run-DYhBROuo.mjs +0 -3934
- package/dist/run-DjfPjgOb.mjs +0 -3904
- package/dist/run-DlL4JALM.mjs +0 -4719
- package/dist/run-Dp2JPkGI.mjs +0 -3913
- package/dist/run-Dptna3Je.mjs +0 -3867
- package/dist/run-DwK3dfHd.mjs +0 -3875
- package/dist/run-M_SMt96j.mjs +0 -3913
- package/dist/run-MlpxQUPN.mjs +0 -3869
- package/dist/run-PuTIelbv.mjs +0 -3706
- package/dist/run-h37iSCUB.mjs +0 -3934
- package/dist/run-lpV0oguG.mjs +0 -3897
- package/dist/run-oHmTMcv8.mjs +0 -4721
|
@@ -5,20 +5,57 @@ import { dirname, join as join$1, resolve, basename } from 'path';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { spawn as spawn$1 } from 'child_process';
|
|
7
7
|
import { randomUUID as randomUUID$1 } from 'crypto';
|
|
8
|
-
import { c as connectToHypha } from './hyphaClient-DLkclazm.mjs';
|
|
9
8
|
import { randomUUID } from 'node:crypto';
|
|
10
9
|
import { existsSync, readFileSync, mkdirSync, appendFileSync, writeFileSync } from 'node:fs';
|
|
11
10
|
import { join } from 'node:path';
|
|
12
11
|
import { spawn, execSync, execFile } from 'node:child_process';
|
|
13
12
|
import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
|
|
13
|
+
import { homedir, tmpdir } 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';
|
|
17
17
|
import { z } from 'zod';
|
|
18
18
|
import { mkdir, access, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
19
|
-
import { homedir } from 'node:os';
|
|
20
19
|
import { promisify } from 'node:util';
|
|
21
20
|
|
|
21
|
+
let connectToServerFn = null;
|
|
22
|
+
async function getConnectToServer() {
|
|
23
|
+
if (!connectToServerFn) {
|
|
24
|
+
const mod = await import('hypha-rpc');
|
|
25
|
+
connectToServerFn = mod.connectToServer || mod.default && mod.default.connectToServer || mod.hyphaWebsocketClient && mod.hyphaWebsocketClient.connectToServer;
|
|
26
|
+
if (!connectToServerFn) {
|
|
27
|
+
throw new Error("Could not find connectToServer in hypha-rpc module");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return connectToServerFn;
|
|
31
|
+
}
|
|
32
|
+
async function connectToHypha(config) {
|
|
33
|
+
const connectToServer = await getConnectToServer();
|
|
34
|
+
const workspace = config.token ? parseWorkspaceFromToken(config.token) : void 0;
|
|
35
|
+
const server = await connectToServer({
|
|
36
|
+
server_url: config.serverUrl,
|
|
37
|
+
token: config.token,
|
|
38
|
+
client_id: config.clientId,
|
|
39
|
+
name: config.name || "svamp-cli",
|
|
40
|
+
workspace,
|
|
41
|
+
...config.transport ? { transport: config.transport } : {}
|
|
42
|
+
});
|
|
43
|
+
return server;
|
|
44
|
+
}
|
|
45
|
+
function parseWorkspaceFromToken(token) {
|
|
46
|
+
try {
|
|
47
|
+
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
|
|
48
|
+
const scope = payload.scope || "";
|
|
49
|
+
const match = scope.match(/wid:([^\s]+)/);
|
|
50
|
+
return match ? match[1] : void 0;
|
|
51
|
+
} catch {
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function getHyphaServerUrl() {
|
|
56
|
+
return process.env.HYPHA_SERVER_URL || "http://localhost:9527";
|
|
57
|
+
}
|
|
58
|
+
|
|
22
59
|
const ROLE_HIERARCHY = {
|
|
23
60
|
view: 0,
|
|
24
61
|
interact: 1,
|
|
@@ -116,6 +153,12 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
116
153
|
// Spawn a new session
|
|
117
154
|
spawnSession: async (options, context) => {
|
|
118
155
|
authorizeRequest(context, currentMetadata.sharing, "interact");
|
|
156
|
+
if (options.sharing?.enabled && !options.sharing.owner && context?.user?.email) {
|
|
157
|
+
options = {
|
|
158
|
+
...options,
|
|
159
|
+
sharing: { ...options.sharing, owner: context.user.email }
|
|
160
|
+
};
|
|
161
|
+
}
|
|
119
162
|
const result = await handlers.spawnSession({
|
|
120
163
|
...options,
|
|
121
164
|
machineId
|
|
@@ -140,27 +183,17 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
140
183
|
});
|
|
141
184
|
return result;
|
|
142
185
|
},
|
|
186
|
+
// Restart agent process in a session (machine-level fallback)
|
|
187
|
+
restartSession: async (sessionId, context) => {
|
|
188
|
+
authorizeRequest(context, currentMetadata.sharing, "interact");
|
|
189
|
+
return await handlers.restartSession(sessionId);
|
|
190
|
+
},
|
|
143
191
|
// Stop the daemon
|
|
144
192
|
stopDaemon: async (context) => {
|
|
145
193
|
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
146
194
|
handlers.requestShutdown();
|
|
147
195
|
return { success: true };
|
|
148
196
|
},
|
|
149
|
-
// Recover crashed sessions (scan disk, restore stopped sessions)
|
|
150
|
-
recoverSessions: async (context) => {
|
|
151
|
-
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
152
|
-
const result = await handlers.recoverSessions();
|
|
153
|
-
for (const detail of result.details) {
|
|
154
|
-
if (detail.status === "recovered") {
|
|
155
|
-
notifyListeners({
|
|
156
|
-
type: "new-session",
|
|
157
|
-
sessionId: detail.sessionId,
|
|
158
|
-
machineId
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return result;
|
|
163
|
-
},
|
|
164
197
|
// Metadata management (with optimistic concurrency)
|
|
165
198
|
getMetadata: async (context) => {
|
|
166
199
|
authorizeRequest(context, currentMetadata.sharing, "view");
|
|
@@ -397,6 +430,8 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
397
430
|
data.uuid = randomUUID();
|
|
398
431
|
}
|
|
399
432
|
wrappedContent = { role: "agent", content: { type: "output", data } };
|
|
433
|
+
} else if (role === "event") {
|
|
434
|
+
wrappedContent = { role: "agent", content: { type: "event", data: content } };
|
|
400
435
|
} else if (role === "session") {
|
|
401
436
|
wrappedContent = { role: "session", content: { type: "session", data: content } };
|
|
402
437
|
} else {
|
|
@@ -639,6 +674,9 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
639
674
|
if (metadata.sharing && context?.user?.email && context.user.email !== metadata.sharing.owner) {
|
|
640
675
|
throw new Error("Only the session owner can update sharing settings");
|
|
641
676
|
}
|
|
677
|
+
if (newSharing.enabled && !newSharing.owner && context?.user?.email) {
|
|
678
|
+
newSharing = { ...newSharing, owner: context.user.email };
|
|
679
|
+
}
|
|
642
680
|
metadata = { ...metadata, sharing: newSharing };
|
|
643
681
|
metadataVersion++;
|
|
644
682
|
notifyListeners({
|
|
@@ -1166,6 +1204,106 @@ var DefaultTransport$1 = /*#__PURE__*/Object.freeze({
|
|
|
1166
1204
|
DefaultTransport: DefaultTransport
|
|
1167
1205
|
});
|
|
1168
1206
|
|
|
1207
|
+
const SVAMP_TOOLS_DIR$1 = join(homedir(), ".svamp", "tools");
|
|
1208
|
+
const SVAMP_TOOLS_BIN$1 = join(SVAMP_TOOLS_DIR$1, "node_modules", ".bin");
|
|
1209
|
+
const SVAMP_TOOLS_RG_BIN$1 = join(SVAMP_TOOLS_DIR$1, "node_modules", "@vscode", "ripgrep", "bin");
|
|
1210
|
+
function wrapWithIsolation(originalCommand, originalArgs, config) {
|
|
1211
|
+
switch (config.method) {
|
|
1212
|
+
case "srt":
|
|
1213
|
+
return wrapWithSrt(originalCommand, originalArgs, config);
|
|
1214
|
+
case "bwrap":
|
|
1215
|
+
return wrapWithBwrap(originalCommand, originalArgs, config);
|
|
1216
|
+
case "docker":
|
|
1217
|
+
return wrapWithContainer("docker", originalCommand, originalArgs, config);
|
|
1218
|
+
case "podman":
|
|
1219
|
+
return wrapWithContainer("podman", originalCommand, originalArgs, config);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
function wrapWithSrt(command, args, config) {
|
|
1223
|
+
const settings = {
|
|
1224
|
+
filesystem: {
|
|
1225
|
+
denyRead: config.srtConfig?.filesystem?.denyRead ?? [],
|
|
1226
|
+
allowWrite: config.srtConfig?.filesystem?.allowWrite ?? [config.workspacePath],
|
|
1227
|
+
denyWrite: config.srtConfig?.filesystem?.denyWrite ?? []
|
|
1228
|
+
},
|
|
1229
|
+
network: {
|
|
1230
|
+
allowedDomains: config.srtConfig?.network?.allowedDomains ?? [],
|
|
1231
|
+
deniedDomains: config.srtConfig?.network?.deniedDomains ?? []
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
const settingsPath = join(tmpdir(), `srt-settings-${process.pid}-${Date.now()}.json`);
|
|
1235
|
+
writeFileSync(settingsPath, JSON.stringify(settings));
|
|
1236
|
+
const pathParts = [SVAMP_TOOLS_BIN$1, SVAMP_TOOLS_RG_BIN$1];
|
|
1237
|
+
if (process.env.PATH) pathParts.push(process.env.PATH);
|
|
1238
|
+
return {
|
|
1239
|
+
command: config.binaryPath,
|
|
1240
|
+
args: ["--settings", settingsPath, command, ...args],
|
|
1241
|
+
env: { PATH: pathParts.join(":") },
|
|
1242
|
+
cleanupFiles: [settingsPath]
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
function wrapWithBwrap(command, args, config) {
|
|
1246
|
+
const bwrapArgs = [
|
|
1247
|
+
// Mount root filesystem read-only
|
|
1248
|
+
"--ro-bind",
|
|
1249
|
+
"/",
|
|
1250
|
+
"/",
|
|
1251
|
+
// Mount workspace read-write
|
|
1252
|
+
"--bind",
|
|
1253
|
+
config.workspacePath,
|
|
1254
|
+
config.workspacePath,
|
|
1255
|
+
// Mount /tmp read-write (many tools need it)
|
|
1256
|
+
"--bind",
|
|
1257
|
+
"/tmp",
|
|
1258
|
+
"/tmp",
|
|
1259
|
+
// Mount /dev read-write
|
|
1260
|
+
"--dev",
|
|
1261
|
+
"/dev",
|
|
1262
|
+
// Mount /proc
|
|
1263
|
+
"--proc",
|
|
1264
|
+
"/proc",
|
|
1265
|
+
// Unshare network — no network access inside sandbox
|
|
1266
|
+
"--unshare-net",
|
|
1267
|
+
// Unshare PID namespace
|
|
1268
|
+
"--unshare-pid",
|
|
1269
|
+
// Die when parent dies
|
|
1270
|
+
"--die-with-parent",
|
|
1271
|
+
// The actual command
|
|
1272
|
+
"--",
|
|
1273
|
+
command,
|
|
1274
|
+
...args
|
|
1275
|
+
];
|
|
1276
|
+
return {
|
|
1277
|
+
command: config.binaryPath,
|
|
1278
|
+
args: bwrapArgs
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
function wrapWithContainer(runtime, command, args, config) {
|
|
1282
|
+
const image = config.containerConfig?.image || "node:lts-slim";
|
|
1283
|
+
const networkMode = config.containerConfig?.networkMode || "none";
|
|
1284
|
+
const containerArgs = [
|
|
1285
|
+
"run",
|
|
1286
|
+
"--rm",
|
|
1287
|
+
"-i",
|
|
1288
|
+
// interactive (for stdin/stdout piping)
|
|
1289
|
+
`--network=${networkMode}`,
|
|
1290
|
+
"-v",
|
|
1291
|
+
`${config.workspacePath}:${config.workspacePath}`,
|
|
1292
|
+
"-w",
|
|
1293
|
+
config.workspacePath
|
|
1294
|
+
];
|
|
1295
|
+
if (config.containerConfig?.extraMounts) {
|
|
1296
|
+
for (const mount of config.containerConfig.extraMounts) {
|
|
1297
|
+
containerArgs.push("-v", mount);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
containerArgs.push(image, command, ...args);
|
|
1301
|
+
return {
|
|
1302
|
+
command: config.binaryPath,
|
|
1303
|
+
args: containerArgs
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1169
1307
|
const DEFAULT_IDLE_TIMEOUT_MS = 500;
|
|
1170
1308
|
const DEFAULT_TOOL_CALL_TIMEOUT_MS = 12e4;
|
|
1171
1309
|
function parseArgsFromContent(content) {
|
|
@@ -1484,21 +1622,41 @@ class AcpBackend {
|
|
|
1484
1622
|
let startupStatusErrorEmitted = false;
|
|
1485
1623
|
try {
|
|
1486
1624
|
const args = this.options.args || [];
|
|
1625
|
+
let spawnCommand = this.options.command;
|
|
1626
|
+
let spawnArgs = args;
|
|
1627
|
+
let isoEnv = {};
|
|
1628
|
+
let isoCleanupFiles = [];
|
|
1629
|
+
if (this.options.isolationConfig) {
|
|
1630
|
+
const wrapped = wrapWithIsolation(spawnCommand, spawnArgs, this.options.isolationConfig);
|
|
1631
|
+
spawnCommand = wrapped.command;
|
|
1632
|
+
spawnArgs = wrapped.args;
|
|
1633
|
+
if (wrapped.env) isoEnv = wrapped.env;
|
|
1634
|
+
if (wrapped.cleanupFiles) isoCleanupFiles = wrapped.cleanupFiles;
|
|
1635
|
+
this.log(`[ACP] Isolation: ${this.options.isolationConfig.method}`);
|
|
1636
|
+
}
|
|
1637
|
+
const spawnEnv = { ...process.env, ...this.options.env, ...isoEnv };
|
|
1487
1638
|
if (process.platform === "win32") {
|
|
1488
|
-
const fullCommand = [
|
|
1639
|
+
const fullCommand = [spawnCommand, ...spawnArgs].join(" ");
|
|
1489
1640
|
this.process = spawn("cmd.exe", ["/c", fullCommand], {
|
|
1490
1641
|
cwd: this.options.cwd,
|
|
1491
|
-
env:
|
|
1642
|
+
env: spawnEnv,
|
|
1492
1643
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1493
1644
|
windowsHide: true
|
|
1494
1645
|
});
|
|
1495
1646
|
} else {
|
|
1496
|
-
this.process = spawn(
|
|
1647
|
+
this.process = spawn(spawnCommand, spawnArgs, {
|
|
1497
1648
|
cwd: this.options.cwd,
|
|
1498
|
-
env:
|
|
1649
|
+
env: spawnEnv,
|
|
1499
1650
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1500
1651
|
});
|
|
1501
1652
|
}
|
|
1653
|
+
if (isoCleanupFiles.length > 0) {
|
|
1654
|
+
this.process.on("exit", async () => {
|
|
1655
|
+
const { rm } = await import('node:fs/promises');
|
|
1656
|
+
for (const f of isoCleanupFiles) rm(f, { force: true }).catch(() => {
|
|
1657
|
+
});
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1502
1660
|
if (!this.process.stdin || !this.process.stdout || !this.process.stderr) {
|
|
1503
1661
|
throw new Error("Failed to create stdio pipes");
|
|
1504
1662
|
}
|
|
@@ -2111,15 +2269,15 @@ function bridgeAcpToSession(backend, sessionService, getMetadata, setMetadata, l
|
|
|
2111
2269
|
setMetadata((m) => ({ ...m, lifecycleState: "running" }));
|
|
2112
2270
|
} else if (msg.status === "error") {
|
|
2113
2271
|
flushText();
|
|
2114
|
-
sessionService.pushMessage(
|
|
2115
|
-
type: "
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
sessionService.
|
|
2272
|
+
sessionService.pushMessage(
|
|
2273
|
+
{ type: "message", message: `Agent process exited unexpectedly: ${msg.detail || "Unknown error"}` },
|
|
2274
|
+
"event"
|
|
2275
|
+
);
|
|
2276
|
+
sessionService.sendSessionEnd();
|
|
2119
2277
|
setMetadata((m) => ({ ...m, lifecycleState: "error" }));
|
|
2120
2278
|
} else if (msg.status === "stopped") {
|
|
2121
2279
|
flushText();
|
|
2122
|
-
sessionService.
|
|
2280
|
+
sessionService.sendSessionEnd();
|
|
2123
2281
|
setMetadata((m) => ({ ...m, lifecycleState: "stopped" }));
|
|
2124
2282
|
}
|
|
2125
2283
|
break;
|
|
@@ -2274,6 +2432,8 @@ class CodexMcpBackend {
|
|
|
2274
2432
|
connected = false;
|
|
2275
2433
|
// Pending elicitation approvals
|
|
2276
2434
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
2435
|
+
// Temp files from isolation wrapping (cleaned up on disconnect)
|
|
2436
|
+
_isolationCleanupFiles = [];
|
|
2277
2437
|
constructor(options) {
|
|
2278
2438
|
this.options = options;
|
|
2279
2439
|
this.log = options.log || (() => {
|
|
@@ -2416,9 +2576,21 @@ class CodexMcpBackend {
|
|
|
2416
2576
|
} else if (!existingRustLog.includes("codex_core::rollout::list=")) {
|
|
2417
2577
|
env.RUST_LOG = `${existingRustLog},${rolloutFilter}`;
|
|
2418
2578
|
}
|
|
2579
|
+
let transportCommand = "codex";
|
|
2580
|
+
let transportArgs = [mcpCommand];
|
|
2581
|
+
if (this.options.isolationConfig) {
|
|
2582
|
+
const wrapped = wrapWithIsolation(transportCommand, transportArgs, this.options.isolationConfig);
|
|
2583
|
+
transportCommand = wrapped.command;
|
|
2584
|
+
transportArgs = wrapped.args;
|
|
2585
|
+
if (wrapped.env) Object.assign(env, wrapped.env);
|
|
2586
|
+
if (wrapped.cleanupFiles) {
|
|
2587
|
+
this._isolationCleanupFiles = wrapped.cleanupFiles;
|
|
2588
|
+
}
|
|
2589
|
+
this.log(`[Codex] Isolation: ${this.options.isolationConfig.method}`);
|
|
2590
|
+
}
|
|
2419
2591
|
this.transport = new StdioClientTransport({
|
|
2420
|
-
command:
|
|
2421
|
-
args:
|
|
2592
|
+
command: transportCommand,
|
|
2593
|
+
args: transportArgs,
|
|
2422
2594
|
env
|
|
2423
2595
|
});
|
|
2424
2596
|
this.registerPermissionHandlers();
|
|
@@ -2455,6 +2627,12 @@ class CodexMcpBackend {
|
|
|
2455
2627
|
}
|
|
2456
2628
|
this.transport = null;
|
|
2457
2629
|
this.connected = false;
|
|
2630
|
+
if (this._isolationCleanupFiles.length > 0) {
|
|
2631
|
+
const { rm } = await import('node:fs/promises');
|
|
2632
|
+
for (const f of this._isolationCleanupFiles) rm(f, { force: true }).catch(() => {
|
|
2633
|
+
});
|
|
2634
|
+
this._isolationCleanupFiles = [];
|
|
2635
|
+
}
|
|
2458
2636
|
}
|
|
2459
2637
|
// ── Permission handling ────────────────────────────────────────────
|
|
2460
2638
|
registerPermissionHandlers() {
|
|
@@ -3514,8 +3692,19 @@ async function startDaemon() {
|
|
|
3514
3692
|
proc.kill(signal);
|
|
3515
3693
|
}
|
|
3516
3694
|
});
|
|
3695
|
+
}, buildIsolationConfig2 = function(dir) {
|
|
3696
|
+
if (!sessionMetadata.sharing?.enabled) return null;
|
|
3697
|
+
const method = isolationCapabilities.preferred;
|
|
3698
|
+
if (!method) return null;
|
|
3699
|
+
const detail = isolationCapabilities.details[method];
|
|
3700
|
+
if (!detail.found || detail.verified === false) return null;
|
|
3701
|
+
return {
|
|
3702
|
+
method,
|
|
3703
|
+
binaryPath: detail.path || method,
|
|
3704
|
+
workspacePath: dir
|
|
3705
|
+
};
|
|
3517
3706
|
};
|
|
3518
|
-
var parseBashPermission = parseBashPermission2, shouldAutoAllow = shouldAutoAllow2, killAndWaitForExit = killAndWaitForExit2;
|
|
3707
|
+
var parseBashPermission = parseBashPermission2, shouldAutoAllow = shouldAutoAllow2, killAndWaitForExit = killAndWaitForExit2, buildIsolationConfig = buildIsolationConfig2;
|
|
3519
3708
|
let sessionMetadata = {
|
|
3520
3709
|
path: directory,
|
|
3521
3710
|
host: os.hostname(),
|
|
@@ -3568,6 +3757,7 @@ async function startDaemon() {
|
|
|
3568
3757
|
"auto-approve-all": "bypassPermissions"
|
|
3569
3758
|
};
|
|
3570
3759
|
const toClaudePermissionMode = (mode) => CLAUDE_PERMISSION_MODE_MAP[mode] || mode;
|
|
3760
|
+
let isolationCleanupFiles = [];
|
|
3571
3761
|
const spawnClaude = (initialMessage, meta) => {
|
|
3572
3762
|
const rawPermissionMode = meta?.permissionMode || agentConfig.default_permission_mode || currentPermissionMode;
|
|
3573
3763
|
const permissionMode = toClaudePermissionMode(rawPermissionMode);
|
|
@@ -3588,10 +3778,26 @@ async function startDaemon() {
|
|
|
3588
3778
|
if (model) args.push("--model", model);
|
|
3589
3779
|
if (appendSystemPrompt) args.push("--append-system-prompt", appendSystemPrompt);
|
|
3590
3780
|
if (claudeResumeId) args.push("--resume", claudeResumeId);
|
|
3591
|
-
|
|
3592
|
-
|
|
3781
|
+
let spawnCommand = "claude";
|
|
3782
|
+
let spawnArgs = args;
|
|
3783
|
+
let extraEnv = {};
|
|
3784
|
+
isolationCleanupFiles = [];
|
|
3785
|
+
const isoConfig = buildIsolationConfig2(directory);
|
|
3786
|
+
if (isoConfig) {
|
|
3787
|
+
const wrapped = wrapWithIsolation(spawnCommand, spawnArgs, isoConfig);
|
|
3788
|
+
spawnCommand = wrapped.command;
|
|
3789
|
+
spawnArgs = wrapped.args;
|
|
3790
|
+
if (wrapped.env) extraEnv = wrapped.env;
|
|
3791
|
+
if (wrapped.cleanupFiles) isolationCleanupFiles = wrapped.cleanupFiles;
|
|
3792
|
+
sessionMetadata = { ...sessionMetadata, isolationMethod: isoConfig.method };
|
|
3793
|
+
logger.log(`[Session ${sessionId}] Isolation: ${isoConfig.method} (binary: ${isoConfig.binaryPath})`);
|
|
3794
|
+
} else {
|
|
3795
|
+
sessionMetadata = { ...sessionMetadata, isolationMethod: void 0 };
|
|
3796
|
+
}
|
|
3797
|
+
logger.log(`[Session ${sessionId}] Spawning Claude: ${spawnCommand} ${spawnArgs.join(" ")} (cwd: ${directory})`);
|
|
3798
|
+
const spawnEnv = { ...process.env, ...extraEnv };
|
|
3593
3799
|
delete spawnEnv.CLAUDECODE;
|
|
3594
|
-
const child = spawn$1(
|
|
3800
|
+
const child = spawn$1(spawnCommand, spawnArgs, {
|
|
3595
3801
|
cwd: directory,
|
|
3596
3802
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3597
3803
|
env: spawnEnv,
|
|
@@ -3601,17 +3807,11 @@ async function startDaemon() {
|
|
|
3601
3807
|
logger.log(`[Session ${sessionId}] Claude PID: ${child.pid}, stdin: ${!!child.stdin}, stdout: ${!!child.stdout}, stderr: ${!!child.stderr}`);
|
|
3602
3808
|
child.on("error", (err) => {
|
|
3603
3809
|
logger.log(`[Session ${sessionId}] Claude process error: ${err.message}`);
|
|
3604
|
-
sessionService.pushMessage(
|
|
3605
|
-
type: "
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
Please ensure Claude Code CLI is installed on this machine. You can install it with:
|
|
3611
|
-
\`npm install -g @anthropic-ai/claude-code\``
|
|
3612
|
-
}]
|
|
3613
|
-
}, "agent");
|
|
3614
|
-
sessionService.sendKeepAlive(false);
|
|
3810
|
+
sessionService.pushMessage(
|
|
3811
|
+
{ type: "message", message: `Agent process exited unexpectedly: ${err.message}. Please ensure Claude Code CLI is installed.` },
|
|
3812
|
+
"event"
|
|
3813
|
+
);
|
|
3814
|
+
sessionService.sendSessionEnd();
|
|
3615
3815
|
});
|
|
3616
3816
|
let stdoutBuffer = "";
|
|
3617
3817
|
let lastErrorMessagePushed = false;
|
|
@@ -3805,25 +4005,25 @@ Please ensure Claude Code CLI is installed on this machine. You can install it w
|
|
|
3805
4005
|
child.on("exit", (code, signal) => {
|
|
3806
4006
|
logger.log(`[Session ${sessionId}] Claude exited: code=${code}, signal=${signal}`);
|
|
3807
4007
|
claudeProcess = null;
|
|
4008
|
+
for (const f of isolationCleanupFiles) {
|
|
4009
|
+
fs.rm(f, { force: true }).catch(() => {
|
|
4010
|
+
});
|
|
4011
|
+
}
|
|
4012
|
+
isolationCleanupFiles = [];
|
|
3808
4013
|
for (const [id, pending] of pendingPermissions) {
|
|
3809
4014
|
pending.resolve({ behavior: "deny", message: "Claude process exited" });
|
|
3810
4015
|
}
|
|
3811
4016
|
pendingPermissions.clear();
|
|
3812
4017
|
if (code !== 0 && code !== null && !lastErrorMessagePushed) {
|
|
3813
|
-
sessionService.pushMessage(
|
|
3814
|
-
type: "
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
text: `Error: Claude process exited with code ${code}${signal ? ` (signal: ${signal})` : ""}.
|
|
3818
|
-
|
|
3819
|
-
This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
3820
|
-
}]
|
|
3821
|
-
}, "agent");
|
|
4018
|
+
sessionService.pushMessage(
|
|
4019
|
+
{ type: "message", message: `Agent process exited unexpectedly (code ${code}${signal ? `, signal: ${signal}` : ""})` },
|
|
4020
|
+
"event"
|
|
4021
|
+
);
|
|
3822
4022
|
}
|
|
3823
4023
|
lastErrorMessagePushed = false;
|
|
3824
4024
|
sessionMetadata = { ...sessionMetadata, lifecycleState: claudeResumeId ? "idle" : "stopped" };
|
|
3825
4025
|
sessionService.updateMetadata(sessionMetadata);
|
|
3826
|
-
sessionService.
|
|
4026
|
+
sessionService.sendSessionEnd();
|
|
3827
4027
|
if (claudeResumeId && !trackedSession.stopped) {
|
|
3828
4028
|
saveSession({
|
|
3829
4029
|
sessionId,
|
|
@@ -3847,6 +4047,30 @@ This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
|
3847
4047
|
}
|
|
3848
4048
|
return child;
|
|
3849
4049
|
};
|
|
4050
|
+
const restartClaudeHandler = async () => {
|
|
4051
|
+
logger.log(`[Session ${sessionId}] Restart Claude requested`);
|
|
4052
|
+
try {
|
|
4053
|
+
if (claudeProcess && claudeProcess.exitCode === null) {
|
|
4054
|
+
isKillingClaude = true;
|
|
4055
|
+
sessionMetadata = { ...sessionMetadata, lifecycleState: "restarting" };
|
|
4056
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
4057
|
+
await killAndWaitForExit2(claudeProcess);
|
|
4058
|
+
isKillingClaude = false;
|
|
4059
|
+
}
|
|
4060
|
+
if (claudeResumeId) {
|
|
4061
|
+
spawnClaude(void 0, { permissionMode: currentPermissionMode });
|
|
4062
|
+
logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
|
|
4063
|
+
return { success: true, message: "Claude process restarted successfully." };
|
|
4064
|
+
} else {
|
|
4065
|
+
logger.log(`[Session ${sessionId}] No resume ID \u2014 cannot restart`);
|
|
4066
|
+
return { success: false, message: "No session to resume. Send a message to start a new session." };
|
|
4067
|
+
}
|
|
4068
|
+
} catch (err) {
|
|
4069
|
+
isKillingClaude = false;
|
|
4070
|
+
logger.log(`[Session ${sessionId}] Restart failed: ${err.message}`);
|
|
4071
|
+
return { success: false, message: `Restart failed: ${err.message}` };
|
|
4072
|
+
}
|
|
4073
|
+
};
|
|
3850
4074
|
const sessionService = await registerSessionService(
|
|
3851
4075
|
server,
|
|
3852
4076
|
sessionId,
|
|
@@ -3944,30 +4168,7 @@ This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
|
3944
4168
|
spawnClaude(void 0, { permissionMode: mode });
|
|
3945
4169
|
}
|
|
3946
4170
|
},
|
|
3947
|
-
onRestartClaude:
|
|
3948
|
-
logger.log(`[Session ${sessionId}] Restart Claude requested`);
|
|
3949
|
-
try {
|
|
3950
|
-
if (claudeProcess && claudeProcess.exitCode === null) {
|
|
3951
|
-
isKillingClaude = true;
|
|
3952
|
-
sessionMetadata = { ...sessionMetadata, lifecycleState: "restarting" };
|
|
3953
|
-
sessionService.updateMetadata(sessionMetadata);
|
|
3954
|
-
await killAndWaitForExit2(claudeProcess);
|
|
3955
|
-
isKillingClaude = false;
|
|
3956
|
-
}
|
|
3957
|
-
if (claudeResumeId) {
|
|
3958
|
-
spawnClaude(void 0, { permissionMode: currentPermissionMode });
|
|
3959
|
-
logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
|
|
3960
|
-
return { success: true, message: "Claude process restarted successfully." };
|
|
3961
|
-
} else {
|
|
3962
|
-
logger.log(`[Session ${sessionId}] No resume ID \u2014 cannot restart`);
|
|
3963
|
-
return { success: false, message: "No session to resume. Send a message to start a new session." };
|
|
3964
|
-
}
|
|
3965
|
-
} catch (err) {
|
|
3966
|
-
isKillingClaude = false;
|
|
3967
|
-
logger.log(`[Session ${sessionId}] Restart failed: ${err.message}`);
|
|
3968
|
-
return { success: false, message: `Restart failed: ${err.message}` };
|
|
3969
|
-
}
|
|
3970
|
-
},
|
|
4171
|
+
onRestartClaude: restartClaudeHandler,
|
|
3971
4172
|
onKillSession: () => {
|
|
3972
4173
|
logger.log(`[Session ${sessionId}] Kill session requested`);
|
|
3973
4174
|
stopSession(sessionId);
|
|
@@ -4315,12 +4516,27 @@ This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
|
4315
4516
|
logger
|
|
4316
4517
|
);
|
|
4317
4518
|
const permissionHandler = new HyphaPermissionHandler(shouldAutoAllow2, logger.log);
|
|
4519
|
+
let agentIsoConfig;
|
|
4520
|
+
if (sessionMetadata.sharing?.enabled && isolationCapabilities.preferred) {
|
|
4521
|
+
const method = isolationCapabilities.preferred;
|
|
4522
|
+
const detail = isolationCapabilities.details[method];
|
|
4523
|
+
if (detail.found && detail.verified !== false) {
|
|
4524
|
+
agentIsoConfig = {
|
|
4525
|
+
method,
|
|
4526
|
+
binaryPath: detail.path || method,
|
|
4527
|
+
workspacePath: directory
|
|
4528
|
+
};
|
|
4529
|
+
sessionMetadata = { ...sessionMetadata, isolationMethod: method };
|
|
4530
|
+
logger.log(`[Agent Session ${sessionId}] Isolation: ${method}`);
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4318
4533
|
let agentBackend;
|
|
4319
4534
|
if (KNOWN_MCP_AGENTS[agentName]) {
|
|
4320
4535
|
agentBackend = new CodexMcpBackend({
|
|
4321
4536
|
cwd: directory,
|
|
4322
4537
|
env: options.environmentVariables,
|
|
4323
|
-
log: logger.log
|
|
4538
|
+
log: logger.log,
|
|
4539
|
+
isolationConfig: agentIsoConfig
|
|
4324
4540
|
});
|
|
4325
4541
|
} else {
|
|
4326
4542
|
const transportHandler = agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(agentName);
|
|
@@ -4333,7 +4549,8 @@ This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
|
4333
4549
|
env: options.environmentVariables,
|
|
4334
4550
|
permissionHandler,
|
|
4335
4551
|
transportHandler,
|
|
4336
|
-
log: logger.log
|
|
4552
|
+
log: logger.log,
|
|
4553
|
+
isolationConfig: agentIsoConfig
|
|
4337
4554
|
});
|
|
4338
4555
|
}
|
|
4339
4556
|
bridgeAcpToSession(
|
|
@@ -4365,16 +4582,11 @@ This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
|
4365
4582
|
logger.log(`[Agent Session ${sessionId}] ${agentName} backend started, waiting for first message`);
|
|
4366
4583
|
}).catch((err) => {
|
|
4367
4584
|
logger.error(`[Agent Session ${sessionId}] Failed to start ${agentName}:`, err);
|
|
4368
|
-
sessionService.pushMessage(
|
|
4369
|
-
type: "
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
Please ensure the ${agentName} CLI is installed.`
|
|
4375
|
-
}]
|
|
4376
|
-
}, "agent");
|
|
4377
|
-
sessionService.sendKeepAlive(false);
|
|
4585
|
+
sessionService.pushMessage(
|
|
4586
|
+
{ type: "message", message: `Agent process exited unexpectedly: ${err.message}. Please ensure the ${agentName} CLI is installed.` },
|
|
4587
|
+
"event"
|
|
4588
|
+
);
|
|
4589
|
+
sessionService.sendSessionEnd();
|
|
4378
4590
|
});
|
|
4379
4591
|
return {
|
|
4380
4592
|
type: "success",
|
|
@@ -4411,6 +4623,17 @@ Please ensure the ${agentName} CLI is installed.`
|
|
|
4411
4623
|
logger.log(`Session ${sessionId} not found`);
|
|
4412
4624
|
return false;
|
|
4413
4625
|
};
|
|
4626
|
+
const restartSession = async (sessionId) => {
|
|
4627
|
+
for (const session of pidToTrackedSession.values()) {
|
|
4628
|
+
if (session.svampSessionId === sessionId && !session.stopped) {
|
|
4629
|
+
if (session.restartAgent) {
|
|
4630
|
+
return await session.restartAgent();
|
|
4631
|
+
}
|
|
4632
|
+
return { success: false, message: "This session does not support restart." };
|
|
4633
|
+
}
|
|
4634
|
+
}
|
|
4635
|
+
return { success: false, message: `Session ${sessionId} not found or already stopped.` };
|
|
4636
|
+
};
|
|
4414
4637
|
let isolationCapabilities;
|
|
4415
4638
|
try {
|
|
4416
4639
|
isolationCapabilities = await detectIsolationCapabilities();
|
|
@@ -4435,48 +4658,6 @@ Please ensure the ${agentName} CLI is installed.`
|
|
|
4435
4658
|
pid: process.pid,
|
|
4436
4659
|
startedAt: Date.now()
|
|
4437
4660
|
};
|
|
4438
|
-
const recoverSessionsLive = async () => {
|
|
4439
|
-
const activeSessionIds = new Set(
|
|
4440
|
-
Array.from(pidToTrackedSession.values()).map((s) => s.svampSessionId).filter(Boolean)
|
|
4441
|
-
);
|
|
4442
|
-
const index = loadSessionIndex();
|
|
4443
|
-
const result = { recovered: 0, failed: 0, alreadyActive: 0, details: [] };
|
|
4444
|
-
for (const [sessionId, entry] of Object.entries(index)) {
|
|
4445
|
-
if (activeSessionIds.has(sessionId)) {
|
|
4446
|
-
result.alreadyActive++;
|
|
4447
|
-
result.details.push({ sessionId, directory: entry.directory, status: "active" });
|
|
4448
|
-
continue;
|
|
4449
|
-
}
|
|
4450
|
-
const sessionFile = getSessionFilePath(entry.directory, sessionId);
|
|
4451
|
-
if (!existsSync$1(sessionFile)) continue;
|
|
4452
|
-
try {
|
|
4453
|
-
const data = JSON.parse(readFileSync$1(sessionFile, "utf-8"));
|
|
4454
|
-
if (!data.sessionId || !data.directory) continue;
|
|
4455
|
-
if (!data.stopped) continue;
|
|
4456
|
-
delete data.stopped;
|
|
4457
|
-
writeFileSync$1(sessionFile, JSON.stringify(data, null, 2), "utf-8");
|
|
4458
|
-
const spawnResult = await spawnSession({
|
|
4459
|
-
directory: data.directory,
|
|
4460
|
-
sessionId: data.sessionId,
|
|
4461
|
-
resumeSessionId: data.claudeResumeId
|
|
4462
|
-
});
|
|
4463
|
-
if (spawnResult.type === "success") {
|
|
4464
|
-
result.recovered++;
|
|
4465
|
-
result.details.push({ sessionId: data.sessionId, directory: data.directory, status: "recovered" });
|
|
4466
|
-
logger.log(`[RECOVER] Restored session ${data.sessionId} (${data.directory})`);
|
|
4467
|
-
} else {
|
|
4468
|
-
result.failed++;
|
|
4469
|
-
result.details.push({ sessionId: data.sessionId, directory: data.directory, status: "failed", error: spawnResult.errorMessage });
|
|
4470
|
-
logger.log(`[RECOVER] Failed to restore session ${data.sessionId}: ${spawnResult.errorMessage}`);
|
|
4471
|
-
}
|
|
4472
|
-
} catch (err) {
|
|
4473
|
-
result.failed++;
|
|
4474
|
-
result.details.push({ sessionId, directory: entry.directory, status: "failed", error: err.message });
|
|
4475
|
-
}
|
|
4476
|
-
}
|
|
4477
|
-
logger.log(`[RECOVER] Done: ${result.recovered} recovered, ${result.failed} failed, ${result.alreadyActive} already active`);
|
|
4478
|
-
return result;
|
|
4479
|
-
};
|
|
4480
4661
|
const machineService = await registerMachineService(
|
|
4481
4662
|
server,
|
|
4482
4663
|
machineId,
|
|
@@ -4485,9 +4666,9 @@ Please ensure the ${agentName} CLI is installed.`
|
|
|
4485
4666
|
{
|
|
4486
4667
|
spawnSession,
|
|
4487
4668
|
stopSession,
|
|
4669
|
+
restartSession,
|
|
4488
4670
|
requestShutdown: () => requestShutdown("hypha-app"),
|
|
4489
|
-
getTrackedSessions: getCurrentChildren
|
|
4490
|
-
recoverSessions: recoverSessionsLive
|
|
4671
|
+
getTrackedSessions: getCurrentChildren
|
|
4491
4672
|
}
|
|
4492
4673
|
);
|
|
4493
4674
|
logger.log(`Machine service registered: svamp-machine-${machineId}`);
|
|
@@ -4791,34 +4972,5 @@ function daemonStatus() {
|
|
|
4791
4972
|
cleanupDaemonStateFile();
|
|
4792
4973
|
}
|
|
4793
4974
|
}
|
|
4794
|
-
function recoverSessions() {
|
|
4795
|
-
const index = loadSessionIndex();
|
|
4796
|
-
let recovered = 0;
|
|
4797
|
-
let alreadyActive = 0;
|
|
4798
|
-
for (const [sessionId, entry] of Object.entries(index)) {
|
|
4799
|
-
const sessionFile = getSessionFilePath(entry.directory, sessionId);
|
|
4800
|
-
if (!existsSync$1(sessionFile)) continue;
|
|
4801
|
-
try {
|
|
4802
|
-
const data = JSON.parse(readFileSync$1(sessionFile, "utf-8"));
|
|
4803
|
-
if (!data.sessionId || !data.directory) continue;
|
|
4804
|
-
if (data.stopped) {
|
|
4805
|
-
delete data.stopped;
|
|
4806
|
-
writeFileSync$1(sessionFile, JSON.stringify(data, null, 2), "utf-8");
|
|
4807
|
-
console.log(` Recovered: ${data.sessionId} (${data.directory})`);
|
|
4808
|
-
recovered++;
|
|
4809
|
-
} else {
|
|
4810
|
-
alreadyActive++;
|
|
4811
|
-
}
|
|
4812
|
-
} catch {
|
|
4813
|
-
}
|
|
4814
|
-
}
|
|
4815
|
-
console.log(`
|
|
4816
|
-
Recovery complete: ${recovered} session(s) recovered, ${alreadyActive} already active.`);
|
|
4817
|
-
console.log(`Sessions index has ${Object.keys(index).length} total session(s).`);
|
|
4818
|
-
if (recovered > 0) {
|
|
4819
|
-
console.log(`
|
|
4820
|
-
Restart the daemon to restore them: svamp daemon stop && svamp daemon start`);
|
|
4821
|
-
}
|
|
4822
|
-
}
|
|
4823
4975
|
|
|
4824
|
-
export { DefaultTransport$1 as D, GeminiTransport$1 as G, registerSessionService as a, stopDaemon as b,
|
|
4976
|
+
export { DefaultTransport$1 as D, GeminiTransport$1 as G, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, acpBackend as e, acpAgentConfig as f, getHyphaServerUrl as g, registerMachineService as r, startDaemon as s };
|