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,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 = [this.options.command, ...args].join(" ");
1639
+ const fullCommand = [spawnCommand, ...spawnArgs].join(" ");
1489
1640
  this.process = spawn("cmd.exe", ["/c", fullCommand], {
1490
1641
  cwd: this.options.cwd,
1491
- env: { ...process.env, ...this.options.env },
1642
+ env: spawnEnv,
1492
1643
  stdio: ["pipe", "pipe", "pipe"],
1493
1644
  windowsHide: true
1494
1645
  });
1495
1646
  } else {
1496
- this.process = spawn(this.options.command, args, {
1647
+ this.process = spawn(spawnCommand, spawnArgs, {
1497
1648
  cwd: this.options.cwd,
1498
- env: { ...process.env, ...this.options.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: "assistant",
2116
- content: [{ type: "text", text: `Error: ${msg.detail || "Unknown error"}` }]
2117
- }, "agent");
2118
- sessionService.sendKeepAlive(false);
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.sendKeepAlive(false);
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: "codex",
2421
- args: [mcpCommand],
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
- logger.log(`[Session ${sessionId}] Spawning Claude: claude ${args.join(" ")} (cwd: ${directory})`);
3592
- 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 };
3593
3799
  delete spawnEnv.CLAUDECODE;
3594
- const child = spawn$1("claude", args, {
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: "assistant",
3606
- content: [{
3607
- type: "text",
3608
- text: `Error: Failed to start Claude Code CLI: ${err.message}
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: "assistant",
3815
- content: [{
3816
- type: "text",
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.sendKeepAlive(false);
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: async () => {
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: "assistant",
4370
- content: [{
4371
- type: "text",
4372
- text: `Error: Failed to start ${agentName} agent: ${err.message}
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, recoverSessions as c, daemonStatus as d, acpBackend as e, acpAgentConfig as f, registerMachineService as r, startDaemon as s };
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 };