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.
Files changed (95) hide show
  1. package/dist/cli.mjs +7 -7
  2. package/dist/{commands-CgT3AgJ0.mjs → commands-B2xQb9u7.mjs} +1 -1
  3. package/dist/{commands-CKTIJoV0.mjs → commands-C5pW2VmI.mjs} +286 -60
  4. package/dist/{commands-DDB3y1L1.mjs → commands-TZkNivgV.mjs} +295 -44
  5. package/dist/index.mjs +1 -1
  6. package/dist/{run-DZmxHj-e.mjs → run-CtCTd6if.mjs} +234 -50
  7. package/dist/{run-CuN6K7pN.mjs → run-Cxdc5Zmw.mjs} +316 -164
  8. package/dist/{run-DMD0N00A.mjs → run-dBWhjQRf.mjs} +316 -76
  9. package/package.json +1 -1
  10. package/dist/agent-cli.mjs +0 -453
  11. package/dist/commands-1CYZC6Xh.mjs +0 -481
  12. package/dist/commands-B1DcpgLW.mjs +0 -481
  13. package/dist/commands-BGmdgMAC.mjs +0 -485
  14. package/dist/commands-BOeSil-P.mjs +0 -459
  15. package/dist/commands-BU4GZQuH.mjs +0 -481
  16. package/dist/commands-Ba66PxtQ.mjs +0 -481
  17. package/dist/commands-C0-xqIIc.mjs +0 -481
  18. package/dist/commands-C7Qy5n6d.mjs +0 -481
  19. package/dist/commands-CKpC8R9T.mjs +0 -481
  20. package/dist/commands-CNqOjR1y.mjs +0 -481
  21. package/dist/commands-CVKh1tWr.mjs +0 -485
  22. package/dist/commands-CYBblX73.mjs +0 -485
  23. package/dist/commands-CZBYmj16.mjs +0 -485
  24. package/dist/commands-CcWIvCA4.mjs +0 -481
  25. package/dist/commands-Cfwf-cQG.mjs +0 -481
  26. package/dist/commands-DCNO2m66.mjs +0 -471
  27. package/dist/commands-DRIFvhmC.mjs +0 -481
  28. package/dist/commands-DXmw2dzy.mjs +0 -481
  29. package/dist/commands-DkSvlKFF.mjs +0 -485
  30. package/dist/commands-DnDd4Sew.mjs +0 -481
  31. package/dist/commands-DnpnAFQW.mjs +0 -485
  32. package/dist/commands-Do-TVYFm.mjs +0 -481
  33. package/dist/commands-GEXri0yz.mjs +0 -484
  34. package/dist/commands-Kzm0_XNH.mjs +0 -481
  35. package/dist/commands-MQvNbIid.mjs +0 -481
  36. package/dist/commands-_uCC3U1U.mjs +0 -481
  37. package/dist/commands-y2WG29W9.mjs +0 -485
  38. package/dist/hyphaClient-DLkclazm.mjs +0 -39
  39. package/dist/package-ASJ9pMHk.mjs +0 -60
  40. package/dist/package-B2FOzHaM.mjs +0 -57
  41. package/dist/package-Bk_PFVA0.mjs +0 -57
  42. package/dist/package-Bnij-ZtR.mjs +0 -57
  43. package/dist/package-BtRbHfjz.mjs +0 -57
  44. package/dist/package-C5B0twb8.mjs +0 -57
  45. package/dist/package-CC5d8_0L.mjs +0 -57
  46. package/dist/package-CCJ045H0.mjs +0 -60
  47. package/dist/package-CS219SXn.mjs +0 -57
  48. package/dist/package-Cd-9ktpd.mjs +0 -60
  49. package/dist/package-CgBD49cA.mjs +0 -57
  50. package/dist/package-CvnNnsm7.mjs +0 -60
  51. package/dist/package-DPXkSwHu.mjs +0 -57
  52. package/dist/package-DpqWz9Cr.mjs +0 -60
  53. package/dist/package-JqEt5Ib4.mjs +0 -57
  54. package/dist/package-k18Su1iE.mjs +0 -58
  55. package/dist/package-nzkXV1aM.mjs +0 -57
  56. package/dist/package-pNo6GC3a.mjs +0 -60
  57. package/dist/package-pZp14zKI.mjs +0 -57
  58. package/dist/run-4fyJcaRE.mjs +0 -3856
  59. package/dist/run-B6oqR83K.mjs +0 -4631
  60. package/dist/run-BI32lPRK.mjs +0 -3870
  61. package/dist/run-BQHneHfW.mjs +0 -3834
  62. package/dist/run-Bb4fyIWZ.mjs +0 -3812
  63. package/dist/run-BglwnB-A.mjs +0 -3889
  64. package/dist/run-BjVWuitO.mjs +0 -3919
  65. package/dist/run-BzUE-JUT.mjs +0 -3708
  66. package/dist/run-BzqS97Sx.mjs +0 -3666
  67. package/dist/run-C6snRxyh.mjs +0 -3826
  68. package/dist/run-C8CI8Ujj.mjs +0 -3693
  69. package/dist/run-CL-FS4Yc.mjs +0 -3933
  70. package/dist/run-CS1Z4GcM.mjs +0 -3786
  71. package/dist/run-CT7uizQo.mjs +0 -4492
  72. package/dist/run-CUIj4xbE.mjs +0 -4880
  73. package/dist/run-CW26vPqj.mjs +0 -3919
  74. package/dist/run-CkTufc0D.mjs +0 -3875
  75. package/dist/run-Cmostc0S.mjs +0 -3902
  76. package/dist/run-Cp3kKdzm.mjs +0 -3865
  77. package/dist/run-D0bCTY72.mjs +0 -3816
  78. package/dist/run-D4N6FQON.mjs +0 -4673
  79. package/dist/run-D4dlA0jo.mjs +0 -4813
  80. package/dist/run-DMW8ibIw.mjs +0 -3958
  81. package/dist/run-DO52unxE.mjs +0 -3950
  82. package/dist/run-DQ5FOQ_c.mjs +0 -4788
  83. package/dist/run-DT7FgL8L.mjs +0 -4339
  84. package/dist/run-DYhBROuo.mjs +0 -3934
  85. package/dist/run-DjfPjgOb.mjs +0 -3904
  86. package/dist/run-DlL4JALM.mjs +0 -4719
  87. package/dist/run-Dp2JPkGI.mjs +0 -3913
  88. package/dist/run-Dptna3Je.mjs +0 -3867
  89. package/dist/run-DwK3dfHd.mjs +0 -3875
  90. package/dist/run-M_SMt96j.mjs +0 -3913
  91. package/dist/run-MlpxQUPN.mjs +0 -3869
  92. package/dist/run-PuTIelbv.mjs +0 -3706
  93. package/dist/run-h37iSCUB.mjs +0 -3934
  94. package/dist/run-lpV0oguG.mjs +0 -3897
  95. 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 = [this.options.command, ...args].join(" ");
1639
+ const fullCommand = [spawnCommand, ...spawnArgs].join(" ");
1474
1640
  this.process = spawn("cmd.exe", ["/c", fullCommand], {
1475
1641
  cwd: this.options.cwd,
1476
- env: { ...process.env, ...this.options.env },
1642
+ env: spawnEnv,
1477
1643
  stdio: ["pipe", "pipe", "pipe"],
1478
1644
  windowsHide: true
1479
1645
  });
1480
1646
  } else {
1481
- this.process = spawn(this.options.command, args, {
1647
+ this.process = spawn(spawnCommand, spawnArgs, {
1482
1648
  cwd: this.options.cwd,
1483
- env: { ...process.env, ...this.options.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: "assistant",
2101
- content: [{ type: "text", text: `Error: ${msg.detail || "Unknown error"}` }]
2102
- }, "agent");
2103
- sessionService.sendKeepAlive(false);
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.sendKeepAlive(false);
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: "codex",
2406
- args: [mcpCommand],
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
- logger.log(`[Session ${sessionId}] Spawning Claude: claude ${args.join(" ")} (cwd: ${directory})`);
3577
- const spawnEnv = { ...process.env };
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("claude", args, {
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: "assistant",
3591
- content: [{
3592
- type: "text",
3593
- text: `Error: Failed to start Claude Code CLI: ${err.message}
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: "assistant",
3800
- content: [{
3801
- type: "text",
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.sendKeepAlive(false);
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: async () => {
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: "assistant",
4355
- content: [{
4356
- type: "text",
4357
- text: `Error: Failed to start ${agentName} agent: ${err.message}
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, acpBackend as c, daemonStatus as d, acpAgentConfig as e, registerMachineService as r, startDaemon as s };
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",