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,6 +183,11 @@ 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");
|
|
@@ -382,6 +430,8 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
382
430
|
data.uuid = randomUUID();
|
|
383
431
|
}
|
|
384
432
|
wrappedContent = { role: "agent", content: { type: "output", data } };
|
|
433
|
+
} else if (role === "event") {
|
|
434
|
+
wrappedContent = { role: "agent", content: { type: "event", data: content } };
|
|
385
435
|
} else if (role === "session") {
|
|
386
436
|
wrappedContent = { role: "session", content: { type: "session", data: content } };
|
|
387
437
|
} else {
|
|
@@ -624,6 +674,9 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
624
674
|
if (metadata.sharing && context?.user?.email && context.user.email !== metadata.sharing.owner) {
|
|
625
675
|
throw new Error("Only the session owner can update sharing settings");
|
|
626
676
|
}
|
|
677
|
+
if (newSharing.enabled && !newSharing.owner && context?.user?.email) {
|
|
678
|
+
newSharing = { ...newSharing, owner: context.user.email };
|
|
679
|
+
}
|
|
627
680
|
metadata = { ...metadata, sharing: newSharing };
|
|
628
681
|
metadataVersion++;
|
|
629
682
|
notifyListeners({
|
|
@@ -1151,6 +1204,106 @@ var DefaultTransport$1 = /*#__PURE__*/Object.freeze({
|
|
|
1151
1204
|
DefaultTransport: DefaultTransport
|
|
1152
1205
|
});
|
|
1153
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
|
+
|
|
1154
1307
|
const DEFAULT_IDLE_TIMEOUT_MS = 500;
|
|
1155
1308
|
const DEFAULT_TOOL_CALL_TIMEOUT_MS = 12e4;
|
|
1156
1309
|
function parseArgsFromContent(content) {
|
|
@@ -1469,21 +1622,41 @@ class AcpBackend {
|
|
|
1469
1622
|
let startupStatusErrorEmitted = false;
|
|
1470
1623
|
try {
|
|
1471
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 };
|
|
1472
1638
|
if (process.platform === "win32") {
|
|
1473
|
-
const fullCommand = [
|
|
1639
|
+
const fullCommand = [spawnCommand, ...spawnArgs].join(" ");
|
|
1474
1640
|
this.process = spawn("cmd.exe", ["/c", fullCommand], {
|
|
1475
1641
|
cwd: this.options.cwd,
|
|
1476
|
-
env:
|
|
1642
|
+
env: spawnEnv,
|
|
1477
1643
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1478
1644
|
windowsHide: true
|
|
1479
1645
|
});
|
|
1480
1646
|
} else {
|
|
1481
|
-
this.process = spawn(
|
|
1647
|
+
this.process = spawn(spawnCommand, spawnArgs, {
|
|
1482
1648
|
cwd: this.options.cwd,
|
|
1483
|
-
env:
|
|
1649
|
+
env: spawnEnv,
|
|
1484
1650
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1485
1651
|
});
|
|
1486
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
|
+
}
|
|
1487
1660
|
if (!this.process.stdin || !this.process.stdout || !this.process.stderr) {
|
|
1488
1661
|
throw new Error("Failed to create stdio pipes");
|
|
1489
1662
|
}
|
|
@@ -2096,15 +2269,15 @@ function bridgeAcpToSession(backend, sessionService, getMetadata, setMetadata, l
|
|
|
2096
2269
|
setMetadata((m) => ({ ...m, lifecycleState: "running" }));
|
|
2097
2270
|
} else if (msg.status === "error") {
|
|
2098
2271
|
flushText();
|
|
2099
|
-
sessionService.pushMessage(
|
|
2100
|
-
type: "
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
sessionService.
|
|
2272
|
+
sessionService.pushMessage(
|
|
2273
|
+
{ type: "message", message: `Agent process exited unexpectedly: ${msg.detail || "Unknown error"}` },
|
|
2274
|
+
"event"
|
|
2275
|
+
);
|
|
2276
|
+
sessionService.sendSessionEnd();
|
|
2104
2277
|
setMetadata((m) => ({ ...m, lifecycleState: "error" }));
|
|
2105
2278
|
} else if (msg.status === "stopped") {
|
|
2106
2279
|
flushText();
|
|
2107
|
-
sessionService.
|
|
2280
|
+
sessionService.sendSessionEnd();
|
|
2108
2281
|
setMetadata((m) => ({ ...m, lifecycleState: "stopped" }));
|
|
2109
2282
|
}
|
|
2110
2283
|
break;
|
|
@@ -2259,6 +2432,8 @@ class CodexMcpBackend {
|
|
|
2259
2432
|
connected = false;
|
|
2260
2433
|
// Pending elicitation approvals
|
|
2261
2434
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
2435
|
+
// Temp files from isolation wrapping (cleaned up on disconnect)
|
|
2436
|
+
_isolationCleanupFiles = [];
|
|
2262
2437
|
constructor(options) {
|
|
2263
2438
|
this.options = options;
|
|
2264
2439
|
this.log = options.log || (() => {
|
|
@@ -2401,9 +2576,21 @@ class CodexMcpBackend {
|
|
|
2401
2576
|
} else if (!existingRustLog.includes("codex_core::rollout::list=")) {
|
|
2402
2577
|
env.RUST_LOG = `${existingRustLog},${rolloutFilter}`;
|
|
2403
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
|
+
}
|
|
2404
2591
|
this.transport = new StdioClientTransport({
|
|
2405
|
-
command:
|
|
2406
|
-
args:
|
|
2592
|
+
command: transportCommand,
|
|
2593
|
+
args: transportArgs,
|
|
2407
2594
|
env
|
|
2408
2595
|
});
|
|
2409
2596
|
this.registerPermissionHandlers();
|
|
@@ -2440,6 +2627,12 @@ class CodexMcpBackend {
|
|
|
2440
2627
|
}
|
|
2441
2628
|
this.transport = null;
|
|
2442
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
|
+
}
|
|
2443
2636
|
}
|
|
2444
2637
|
// ── Permission handling ────────────────────────────────────────────
|
|
2445
2638
|
registerPermissionHandlers() {
|
|
@@ -3499,8 +3692,19 @@ async function startDaemon() {
|
|
|
3499
3692
|
proc.kill(signal);
|
|
3500
3693
|
}
|
|
3501
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
|
+
};
|
|
3502
3706
|
};
|
|
3503
|
-
var parseBashPermission = parseBashPermission2, shouldAutoAllow = shouldAutoAllow2, killAndWaitForExit = killAndWaitForExit2;
|
|
3707
|
+
var parseBashPermission = parseBashPermission2, shouldAutoAllow = shouldAutoAllow2, killAndWaitForExit = killAndWaitForExit2, buildIsolationConfig = buildIsolationConfig2;
|
|
3504
3708
|
let sessionMetadata = {
|
|
3505
3709
|
path: directory,
|
|
3506
3710
|
host: os.hostname(),
|
|
@@ -3553,6 +3757,7 @@ async function startDaemon() {
|
|
|
3553
3757
|
"auto-approve-all": "bypassPermissions"
|
|
3554
3758
|
};
|
|
3555
3759
|
const toClaudePermissionMode = (mode) => CLAUDE_PERMISSION_MODE_MAP[mode] || mode;
|
|
3760
|
+
let isolationCleanupFiles = [];
|
|
3556
3761
|
const spawnClaude = (initialMessage, meta) => {
|
|
3557
3762
|
const rawPermissionMode = meta?.permissionMode || agentConfig.default_permission_mode || currentPermissionMode;
|
|
3558
3763
|
const permissionMode = toClaudePermissionMode(rawPermissionMode);
|
|
@@ -3573,10 +3778,26 @@ async function startDaemon() {
|
|
|
3573
3778
|
if (model) args.push("--model", model);
|
|
3574
3779
|
if (appendSystemPrompt) args.push("--append-system-prompt", appendSystemPrompt);
|
|
3575
3780
|
if (claudeResumeId) args.push("--resume", claudeResumeId);
|
|
3576
|
-
|
|
3577
|
-
|
|
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 };
|
|
3578
3799
|
delete spawnEnv.CLAUDECODE;
|
|
3579
|
-
const child = spawn$1(
|
|
3800
|
+
const child = spawn$1(spawnCommand, spawnArgs, {
|
|
3580
3801
|
cwd: directory,
|
|
3581
3802
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3582
3803
|
env: spawnEnv,
|
|
@@ -3586,17 +3807,11 @@ async function startDaemon() {
|
|
|
3586
3807
|
logger.log(`[Session ${sessionId}] Claude PID: ${child.pid}, stdin: ${!!child.stdin}, stdout: ${!!child.stdout}, stderr: ${!!child.stderr}`);
|
|
3587
3808
|
child.on("error", (err) => {
|
|
3588
3809
|
logger.log(`[Session ${sessionId}] Claude process error: ${err.message}`);
|
|
3589
|
-
sessionService.pushMessage(
|
|
3590
|
-
type: "
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
Please ensure Claude Code CLI is installed on this machine. You can install it with:
|
|
3596
|
-
\`npm install -g @anthropic-ai/claude-code\``
|
|
3597
|
-
}]
|
|
3598
|
-
}, "agent");
|
|
3599
|
-
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();
|
|
3600
3815
|
});
|
|
3601
3816
|
let stdoutBuffer = "";
|
|
3602
3817
|
let lastErrorMessagePushed = false;
|
|
@@ -3790,25 +4005,25 @@ Please ensure Claude Code CLI is installed on this machine. You can install it w
|
|
|
3790
4005
|
child.on("exit", (code, signal) => {
|
|
3791
4006
|
logger.log(`[Session ${sessionId}] Claude exited: code=${code}, signal=${signal}`);
|
|
3792
4007
|
claudeProcess = null;
|
|
4008
|
+
for (const f of isolationCleanupFiles) {
|
|
4009
|
+
fs.rm(f, { force: true }).catch(() => {
|
|
4010
|
+
});
|
|
4011
|
+
}
|
|
4012
|
+
isolationCleanupFiles = [];
|
|
3793
4013
|
for (const [id, pending] of pendingPermissions) {
|
|
3794
4014
|
pending.resolve({ behavior: "deny", message: "Claude process exited" });
|
|
3795
4015
|
}
|
|
3796
4016
|
pendingPermissions.clear();
|
|
3797
4017
|
if (code !== 0 && code !== null && !lastErrorMessagePushed) {
|
|
3798
|
-
sessionService.pushMessage(
|
|
3799
|
-
type: "
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
text: `Error: Claude process exited with code ${code}${signal ? ` (signal: ${signal})` : ""}.
|
|
3803
|
-
|
|
3804
|
-
This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
3805
|
-
}]
|
|
3806
|
-
}, "agent");
|
|
4018
|
+
sessionService.pushMessage(
|
|
4019
|
+
{ type: "message", message: `Agent process exited unexpectedly (code ${code}${signal ? `, signal: ${signal}` : ""})` },
|
|
4020
|
+
"event"
|
|
4021
|
+
);
|
|
3807
4022
|
}
|
|
3808
4023
|
lastErrorMessagePushed = false;
|
|
3809
4024
|
sessionMetadata = { ...sessionMetadata, lifecycleState: claudeResumeId ? "idle" : "stopped" };
|
|
3810
4025
|
sessionService.updateMetadata(sessionMetadata);
|
|
3811
|
-
sessionService.
|
|
4026
|
+
sessionService.sendSessionEnd();
|
|
3812
4027
|
if (claudeResumeId && !trackedSession.stopped) {
|
|
3813
4028
|
saveSession({
|
|
3814
4029
|
sessionId,
|
|
@@ -3832,6 +4047,30 @@ This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
|
3832
4047
|
}
|
|
3833
4048
|
return child;
|
|
3834
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
|
+
};
|
|
3835
4074
|
const sessionService = await registerSessionService(
|
|
3836
4075
|
server,
|
|
3837
4076
|
sessionId,
|
|
@@ -3929,30 +4168,7 @@ This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
|
3929
4168
|
spawnClaude(void 0, { permissionMode: mode });
|
|
3930
4169
|
}
|
|
3931
4170
|
},
|
|
3932
|
-
onRestartClaude:
|
|
3933
|
-
logger.log(`[Session ${sessionId}] Restart Claude requested`);
|
|
3934
|
-
try {
|
|
3935
|
-
if (claudeProcess && claudeProcess.exitCode === null) {
|
|
3936
|
-
isKillingClaude = true;
|
|
3937
|
-
sessionMetadata = { ...sessionMetadata, lifecycleState: "restarting" };
|
|
3938
|
-
sessionService.updateMetadata(sessionMetadata);
|
|
3939
|
-
await killAndWaitForExit2(claudeProcess);
|
|
3940
|
-
isKillingClaude = false;
|
|
3941
|
-
}
|
|
3942
|
-
if (claudeResumeId) {
|
|
3943
|
-
spawnClaude(void 0, { permissionMode: currentPermissionMode });
|
|
3944
|
-
logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
|
|
3945
|
-
return { success: true, message: "Claude process restarted successfully." };
|
|
3946
|
-
} else {
|
|
3947
|
-
logger.log(`[Session ${sessionId}] No resume ID \u2014 cannot restart`);
|
|
3948
|
-
return { success: false, message: "No session to resume. Send a message to start a new session." };
|
|
3949
|
-
}
|
|
3950
|
-
} catch (err) {
|
|
3951
|
-
isKillingClaude = false;
|
|
3952
|
-
logger.log(`[Session ${sessionId}] Restart failed: ${err.message}`);
|
|
3953
|
-
return { success: false, message: `Restart failed: ${err.message}` };
|
|
3954
|
-
}
|
|
3955
|
-
},
|
|
4171
|
+
onRestartClaude: restartClaudeHandler,
|
|
3956
4172
|
onKillSession: () => {
|
|
3957
4173
|
logger.log(`[Session ${sessionId}] Kill session requested`);
|
|
3958
4174
|
stopSession(sessionId);
|
|
@@ -4065,6 +4281,7 @@ This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
|
4065
4281
|
checkSvampConfig,
|
|
4066
4282
|
directory,
|
|
4067
4283
|
resumeSessionId: claudeResumeId,
|
|
4284
|
+
restartAgent: restartClaudeHandler,
|
|
4068
4285
|
get childProcess() {
|
|
4069
4286
|
return claudeProcess || void 0;
|
|
4070
4287
|
}
|
|
@@ -4300,12 +4517,27 @@ This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
|
4300
4517
|
logger
|
|
4301
4518
|
);
|
|
4302
4519
|
const permissionHandler = new HyphaPermissionHandler(shouldAutoAllow2, logger.log);
|
|
4520
|
+
let agentIsoConfig;
|
|
4521
|
+
if (sessionMetadata.sharing?.enabled && isolationCapabilities.preferred) {
|
|
4522
|
+
const method = isolationCapabilities.preferred;
|
|
4523
|
+
const detail = isolationCapabilities.details[method];
|
|
4524
|
+
if (detail.found && detail.verified !== false) {
|
|
4525
|
+
agentIsoConfig = {
|
|
4526
|
+
method,
|
|
4527
|
+
binaryPath: detail.path || method,
|
|
4528
|
+
workspacePath: directory
|
|
4529
|
+
};
|
|
4530
|
+
sessionMetadata = { ...sessionMetadata, isolationMethod: method };
|
|
4531
|
+
logger.log(`[Agent Session ${sessionId}] Isolation: ${method}`);
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4303
4534
|
let agentBackend;
|
|
4304
4535
|
if (KNOWN_MCP_AGENTS[agentName]) {
|
|
4305
4536
|
agentBackend = new CodexMcpBackend({
|
|
4306
4537
|
cwd: directory,
|
|
4307
4538
|
env: options.environmentVariables,
|
|
4308
|
-
log: logger.log
|
|
4539
|
+
log: logger.log,
|
|
4540
|
+
isolationConfig: agentIsoConfig
|
|
4309
4541
|
});
|
|
4310
4542
|
} else {
|
|
4311
4543
|
const transportHandler = agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(agentName);
|
|
@@ -4318,7 +4550,8 @@ This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
|
4318
4550
|
env: options.environmentVariables,
|
|
4319
4551
|
permissionHandler,
|
|
4320
4552
|
transportHandler,
|
|
4321
|
-
log: logger.log
|
|
4553
|
+
log: logger.log,
|
|
4554
|
+
isolationConfig: agentIsoConfig
|
|
4322
4555
|
});
|
|
4323
4556
|
}
|
|
4324
4557
|
bridgeAcpToSession(
|
|
@@ -4350,16 +4583,11 @@ This may indicate that Claude Code CLI is not properly installed or configured.`
|
|
|
4350
4583
|
logger.log(`[Agent Session ${sessionId}] ${agentName} backend started, waiting for first message`);
|
|
4351
4584
|
}).catch((err) => {
|
|
4352
4585
|
logger.error(`[Agent Session ${sessionId}] Failed to start ${agentName}:`, err);
|
|
4353
|
-
sessionService.pushMessage(
|
|
4354
|
-
type: "
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
Please ensure the ${agentName} CLI is installed.`
|
|
4360
|
-
}]
|
|
4361
|
-
}, "agent");
|
|
4362
|
-
sessionService.sendKeepAlive(false);
|
|
4586
|
+
sessionService.pushMessage(
|
|
4587
|
+
{ type: "message", message: `Agent process exited unexpectedly: ${err.message}. Please ensure the ${agentName} CLI is installed.` },
|
|
4588
|
+
"event"
|
|
4589
|
+
);
|
|
4590
|
+
sessionService.sendSessionEnd();
|
|
4363
4591
|
});
|
|
4364
4592
|
return {
|
|
4365
4593
|
type: "success",
|
|
@@ -4396,6 +4624,17 @@ Please ensure the ${agentName} CLI is installed.`
|
|
|
4396
4624
|
logger.log(`Session ${sessionId} not found`);
|
|
4397
4625
|
return false;
|
|
4398
4626
|
};
|
|
4627
|
+
const restartSession = async (sessionId) => {
|
|
4628
|
+
for (const session of pidToTrackedSession.values()) {
|
|
4629
|
+
if (session.svampSessionId === sessionId && !session.stopped) {
|
|
4630
|
+
if (session.restartAgent) {
|
|
4631
|
+
return await session.restartAgent();
|
|
4632
|
+
}
|
|
4633
|
+
return { success: false, message: "This session does not support restart." };
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
return { success: false, message: `Session ${sessionId} not found or already stopped.` };
|
|
4637
|
+
};
|
|
4399
4638
|
let isolationCapabilities;
|
|
4400
4639
|
try {
|
|
4401
4640
|
isolationCapabilities = await detectIsolationCapabilities();
|
|
@@ -4428,6 +4667,7 @@ Please ensure the ${agentName} CLI is installed.`
|
|
|
4428
4667
|
{
|
|
4429
4668
|
spawnSession,
|
|
4430
4669
|
stopSession,
|
|
4670
|
+
restartSession,
|
|
4431
4671
|
requestShutdown: () => requestShutdown("hypha-app"),
|
|
4432
4672
|
getTrackedSessions: getCurrentChildren
|
|
4433
4673
|
}
|
|
@@ -4734,4 +4974,4 @@ function daemonStatus() {
|
|
|
4734
4974
|
}
|
|
4735
4975
|
}
|
|
4736
4976
|
|
|
4737
|
-
export { DefaultTransport$1 as D, GeminiTransport$1 as G, registerSessionService as a, stopDaemon as b,
|
|
4977
|
+
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 };
|