Forge
csharpfc256dbb
1using System.Net;
2using System.Net.Sockets;
3
4namespace CascadeIDE.Features.Agent.Environment;
5
6/// <summary>
7/// Per-run substrate bundle (ADR 0148 §8.1.2): isolated DB path + ephemeral dev port.
8/// Layout: <c>{runDirectory}/substrate/wit.db</c>, <c>port.txt</c>, <c>owner.txt</c>.
9/// </summary>
10public static class AgentSandboxSubstrate
11{
12 public static AgentSandboxSubstrateBundle Allocate(string runDirectory)
13 {
14 var substrateDir = Path.Combine(runDirectory, "substrate");
15 Directory.CreateDirectory(substrateDir);
16
17 var port = ReserveFreeTcpPort();
18 var dbPath = Path.Combine(substrateDir, "wit.db");
19 var markerPath = Path.Combine(substrateDir, "owner.txt");
20 var portPath = Path.Combine(substrateDir, "port.txt");
21
22 var ownerId = Path.GetFileName(runDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
23 File.WriteAllText(markerPath, ownerId);
24 File.WriteAllText(portPath, port.ToString());
25 File.WriteAllText(dbPath, ownerId);
26
27 return new AgentSandboxSubstrateBundle(port, dbPath, markerPath, portPath, substrateDir);
28 }
29
30 public static void WriteHeavyMarker(AgentSandboxSubstrateBundle bundle, string payload)
31 {
32 Directory.CreateDirectory(bundle.SubstrateDirectory);
33 File.WriteAllText(bundle.DatabasePath, payload);
34 File.AppendAllText(bundle.MarkerPath, "|" + payload);
35 }
36
37 public static string ReadDatabaseOwner(AgentSandboxSubstrateBundle bundle) =>
38 File.ReadAllText(bundle.DatabasePath);
39
40 private static int ReserveFreeTcpPort()
41 {
42 using var listener = new TcpListener(IPAddress.Loopback, 0);
43 listener.Start();
44 try
45 {
46 return ((IPEndPoint)listener.LocalEndpoint).Port;
47 }
48 finally
49 {
50 listener.Stop();
51 }
52 }
53}
54
55public sealed record AgentSandboxSubstrateBundle(
56 int DevPort,
57 string DatabasePath,
58 string MarkerPath,
59 string PortFilePath,
60 string SubstrateDirectory);
61
62/// <summary>Переменные среды для дочернего <c>dotnet test</c> (ADR 0148 §8.1.2). Тесты могут явно опираться на ключи при необходимости изоляции.</summary>
63public static class AgentSandboxProcessEnvironmentKeys
64{
65 public const string WitDbPath = "CASCADE_AGENT_SUBSTRATE_WIT_DB";
66
67 public const string DevPort = "CASCADE_AGENT_SUBSTRATE_DEV_PORT";
68
69 /// <summary>Каталог данных Intercom host (ADR 0148 W4 / 0147).</summary>
70 public const string IntercomDataDirectory = "Intercom__DataDirectory";
71
72 public static Dictionary<string, string> ForBundle(AgentSandboxSubstrateBundle bundle) => new(StringComparer.Ordinal)
73 {
74 [WitDbPath] = bundle.DatabasePath,
75 [DevPort] = bundle.DevPort.ToString(),
76 [IntercomDataDirectory] = bundle.SubstrateDirectory,
77 };
78}
79
View only · write via MCP/CIDE