Fremde Laufzeiten: Claude Code und Befehl in der Sandbox, Egress-Proxy, Vorschlag statt Einsatz (#712)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-25 22:46:49 +02:00
co-authored by Claude Opus 5.5
parent 7d6c2c9495
commit 367ea387f6
40 changed files with 3428 additions and 29 deletions
@@ -298,6 +298,81 @@ public sealed class AgentAdministration(AppHost host)
: $"Prompt history of '{agent.DisplayName}': {Core.Transcripts.PromptHistorySettings.Describe(retentionDays.Value)}.");
}
/// <summary>
/// #712: The runtime an agent works in. Checked here, not at the next run: a list of
/// hosts that does not parse or a command-less command runtime would otherwise show up
/// only as a failed run at night.
/// </summary>
public AdminResult SetRuntime(string agentId, Core.Runtimes.AgentRuntimeConfig runtime)
{
if (Find(agentId) is not { } agent)
return AdminResult.Failure($"No agent with id '{agentId}'.");
if (ValidateRuntime(runtime) is { } problem)
return AdminResult.Failure(problem);
runtime.Kind = Core.Runtimes.RuntimeKinds.Normalize(runtime.Kind);
runtime.AllowedTools = runtime.AllowedTools.Select(t => t.Trim()).Where(t => t.Length > 0).Distinct().ToList();
runtime.Egress = runtime.Egress.Select(e => e.Trim()).Where(e => e.Length > 0).Distinct().ToList();
runtime.Arguments = runtime.Arguments.ToList();
agent.Runtime = runtime;
PersistAgent(agent);
host.SaveInstance(new InstanceChange(InstanceChangeKind.Agents, agentId));
return AdminResult.Success(runtime.IsForeign
? $"'{agent.DisplayName}' now works in {runtime.Kind}, in the sandbox."
: $"'{agent.DisplayName}' works natively again.");
}
public static string? ValidateRuntime(Core.Runtimes.AgentRuntimeConfig runtime)
{
if (!Core.Runtimes.RuntimeKinds.IsKnown(runtime.Kind))
return $"Unknown runtime '{runtime.Kind}' — known are {string.Join(", ", Core.Runtimes.RuntimeKinds.All)}.";
if (runtime.MaxTurns is < 1 or > 500)
return "Turns must be between 1 and 500.";
if (runtime.TimeoutMinutes is < 1 or > 240)
return "The time limit must be between 1 and 240 minutes.";
if (Core.Runtimes.RuntimeKinds.Normalize(runtime.Kind) == Core.Runtimes.RuntimeKinds.Command
&& string.IsNullOrWhiteSpace(runtime.Command))
return "The command runtime needs a command.";
// A tool name, possibly with a pattern in parentheses — never an option in disguise.
if (runtime.AllowedTools.FirstOrDefault(t => t.TrimStart().StartsWith('-') || t.Contains(',')) is { } odd)
return $"'{odd}' is not a tool name.";
try
{
Core.Sandbox.EgressRules.Parse(runtime.Egress);
}
catch (ArgumentException ex)
{
return ex.Message;
}
return null;
}
/// <summary>
/// #712: The instance's Claude Code account. Keys are only ever replaced, never read
/// back; an empty value keeps the one there is.
/// </summary>
public AdminResult SetClaudeCode(string? executable, string? auth, string? apiKey, string? oauthToken)
{
var settings = host.Instance.ClaudeCode;
var mode = string.IsNullOrWhiteSpace(auth) ? settings.Auth : auth.Trim().ToLowerInvariant();
if (mode is not (Core.Runtimes.ClaudeCodeSettings.ApiKeyAuth or Core.Runtimes.ClaudeCodeSettings.SubscriptionAuth))
return AdminResult.Failure($"Unknown sign-in '{auth}'.");
settings.Auth = mode;
if (executable is not null)
settings.Executable = executable.Trim();
if (!string.IsNullOrWhiteSpace(apiKey))
settings.ApiKey = apiKey.Trim();
if (!string.IsNullOrWhiteSpace(oauthToken))
settings.OAuthToken = oauthToken.Trim();
host.SaveInstance(new InstanceChange(InstanceChangeKind.Settings));
return AdminResult.Success(settings.OnSubscription
? "Claude Code runs on the subscription."
: "Claude Code runs on the API key.");
}
/// <summary>#660: Instance default retention of the prompt history, in days.</summary>
public AdminResult SetDefaultPromptHistoryRetention(int retentionDays)
{