71 lines
3.0 KiB
C#
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 };
|
|
}
|
|
}
|