Files
ClawdDotNet/src/ClawdDotNet.Core/Runtimes/CommandRuntime.cs
T

71 lines
3.0 KiB
C#

using ClawdDotNet.Core.Engine;
using ClawdDotNet.Core.Sandbox;
namespace ClawdDotNet.Core.Runtimes;
/// <summary>
/// #712: A program as the runtime — for special cases, and the way to try the whole path
/// without a model. The message comes on stdin, the briefing in the file named by
/// <c>CLAWD_BRIEFING_FILE</c>; what the program prints is the result. A non-zero exit code
/// is a failure, with the end of stderr as its reason.
///
/// <para>The program can reach the MCP server itself: <c>$CLAWD_HELPER --mcp-bridge --socket
/// $CLAWD_MCP_SOCKET</c> with the token file in <c>CLAWD_MCP_TOKEN_FILE</c>.</para>
/// </summary>
public sealed class CommandRuntime : IForeignRuntime
{
public string Kind => RuntimeKinds.Command;
public SandboxRequest Prepare(ForeignRunContext context)
{
var runtime = context.Agent.Runtime;
if (string.IsNullOrWhiteSpace(runtime.Command))
throw new ForeignRunException("No command configured for this agent ('runtime.command').");
File.WriteAllText(Path.Combine(context.PrivateDirectory, ForeignRunContext.BriefingFileName), context.Briefing);
var command = runtime.Command.Trim();
var readOnly = new List<string>(context.HelperPaths());
if (Path.IsPathRooted(command))
{
var directory = Path.GetDirectoryName(command)!;
if (!directory.StartsWith("/usr/", StringComparison.Ordinal) && directory != "/bin")
readOnly.Add(directory);
}
else if (command.Contains('/'))
{
// Relative to the workspace — the run's copy of it.
command = SandboxPaths.Work + "/" + command.TrimStart('.', '/');
}
return new SandboxRequest
{
Executable = command,
Arguments = runtime.Arguments,
WorkspacePath = context.WorkCopy,
ProtectedPaths = [],
ReadOnlyPaths = readOnly,
Mounts = context.Mounts(),
Environment = context.McpEnvironment(),
Egress = runtime.Egress,
StandardInput = context.Message,
Limits = ForeignRunner.Limits(runtime)
};
}
public ForeignRunOutcome Interpret(SandboxResult result, ForeignRunContext context, Action<string> note)
{
if (result.TimedOut)
return new ForeignRunOutcome { Status = AgentRunStatus.Cancelled, Text = "[The command timed out]" + ClaudeCodeRuntime.Tail(result.StandardError) };
if (result.OutputTruncated)
return new ForeignRunOutcome { Status = AgentRunStatus.Failed, Text = "[The command produced more output than allowed]" };
if (result.ExitCode != 0)
return new ForeignRunOutcome
{
Status = AgentRunStatus.Failed,
Text = $"[The command ended with exit code {result.ExitCode}]" + ClaudeCodeRuntime.Tail(result.StandardError)
};
return new ForeignRunOutcome { Status = AgentRunStatus.Completed, Text = result.StandardOutput.Trim(), Turns = 1 };
}
}