123 lines
5.0 KiB
C#
123 lines
5.0 KiB
C#
using ClawdDotNet.Core.Config;
|
|
using ClawdDotNet.Core.Sandbox;
|
|
|
|
namespace ClawdDotNet.Core.Runtimes;
|
|
|
|
/// <summary>#712: Everything a runtime needs to lay out one run.</summary>
|
|
public sealed record ForeignRunContext
|
|
{
|
|
public required AgentConfig Agent { get; init; }
|
|
public required string RunId { get; init; }
|
|
public string? TaskId { get; init; }
|
|
|
|
/// <summary>The system prompt as the native engine would send it — rules, organisation.</summary>
|
|
public required string Briefing { get; init; }
|
|
|
|
/// <summary>The message of this run.</summary>
|
|
public required string Message { get; init; }
|
|
|
|
/// <summary>The run's copy of the workspace, on the host; <c>/work</c> in the sandbox.</summary>
|
|
public required string WorkCopy { get; init; }
|
|
|
|
/// <summary>The runtime's home, kept between runs — on the host; <see cref="Home"/> in the sandbox.</summary>
|
|
public required string HomeDirectory { get; init; }
|
|
|
|
/// <summary>Files of this run only (token, briefing, configuration) — on the host; <see cref="Private"/> in the sandbox.</summary>
|
|
public required string PrivateDirectory { get; init; }
|
|
|
|
/// <summary>The instance's MCP socket on the host, when it has one.</summary>
|
|
public string? McpSocket { get; init; }
|
|
|
|
/// <summary>The application, for the MCP bridge inside the sandbox.</summary>
|
|
public SandboxHelper? Helper { get; init; }
|
|
|
|
public const string Home = "/home/agent";
|
|
public const string Private = "/run/agent";
|
|
public const string McpSocketInSandbox = "/run/mcp.sock";
|
|
public const string TokenFileName = "mcp-token";
|
|
public const string BriefingFileName = "briefing.md";
|
|
|
|
/// <summary>
|
|
/// The sandbox's side of the MCP server: the socket, the token file, the bridge. Without
|
|
/// a socket or a helper there is none — the run works without taskboard access.
|
|
/// </summary>
|
|
public bool HasMcp => McpSocket is not null && Helper is not null;
|
|
|
|
/// <summary>The mounts every foreign run gets: home (writable), the run's own files, the MCP socket.</summary>
|
|
public IReadOnlyList<SandboxMount> Mounts()
|
|
{
|
|
var mounts = new List<SandboxMount>
|
|
{
|
|
new(HomeDirectory, Home, Writable: true),
|
|
new(PrivateDirectory, Private)
|
|
};
|
|
if (HasMcp)
|
|
mounts.Add(new SandboxMount(McpSocket!, McpSocketInSandbox, Writable: true));
|
|
return mounts;
|
|
}
|
|
|
|
/// <summary>
|
|
/// What must be visible read-only for the MCP bridge: the application. Mounted whenever
|
|
/// there is MCP — not only when the launcher runs, which needs network or secrets.
|
|
/// </summary>
|
|
public IReadOnlyList<string> HelperPaths() => HasMcp ? Helper!.ReadOnlyPaths : [];
|
|
|
|
/// <summary>What a program in the sandbox needs to reach the MCP server itself.</summary>
|
|
public IReadOnlyDictionary<string, string> McpEnvironment()
|
|
{
|
|
var environment = new Dictionary<string, string>(StringComparer.Ordinal)
|
|
{
|
|
["HOME"] = Home,
|
|
["CLAWD_RUN_ID"] = RunId,
|
|
["CLAWD_BRIEFING_FILE"] = Private + "/" + BriefingFileName
|
|
};
|
|
if (TaskId is not null)
|
|
environment["CLAWD_TASK_ID"] = TaskId;
|
|
if (HasMcp)
|
|
{
|
|
environment["CLAWD_MCP_SOCKET"] = McpSocketInSandbox;
|
|
environment[Mcp.McpStdioBridge.TokenFileVariable] = Private + "/" + TokenFileName;
|
|
environment["CLAWD_HELPER"] = Helper!.Executable;
|
|
foreach (var (key, value) in Helper.Environment)
|
|
environment[key] = value;
|
|
}
|
|
return environment;
|
|
}
|
|
}
|
|
|
|
/// <summary>#712: What came out of a run, in the engine's terms.</summary>
|
|
public sealed record ForeignRunOutcome
|
|
{
|
|
public Engine.AgentRunStatus Status { get; init; } = Engine.AgentRunStatus.Completed;
|
|
public string? Text { get; init; }
|
|
public int Turns { get; init; }
|
|
public int PromptTokens { get; init; }
|
|
public int CompletionTokens { get; init; }
|
|
public int CachedTokens { get; init; }
|
|
|
|
/// <summary>What the provider billed, if it said so; on a subscription 0.</summary>
|
|
public decimal? CostUsd { get; init; }
|
|
|
|
/// <summary>Paid by a subscription's quota — costs nothing, uses something (#699).</summary>
|
|
public bool OnSubscription { get; init; }
|
|
|
|
/// <summary>The model the runtime reported, if any.</summary>
|
|
public string? Model { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// #712: One runtime — how a run is laid out for the sandbox, and how its output is read.
|
|
/// The runner around it does everything that is the same for all: the copy, the token,
|
|
/// the proposal, the clean-up.
|
|
/// </summary>
|
|
public interface IForeignRuntime
|
|
{
|
|
string Kind { get; }
|
|
|
|
/// <summary>Writes what the run needs into the private directory and returns the sandbox request.</summary>
|
|
SandboxRequest Prepare(ForeignRunContext context);
|
|
|
|
/// <summary>Reads the result. <paramref name="note"/> takes lines for the run's transcript.</summary>
|
|
ForeignRunOutcome Interpret(SandboxResult result, ForeignRunContext context, Action<string> note);
|
|
}
|