- TeamOverview.RollUp: je Einheit die Zustaende aller Agenten darin und
darunter, offene Freigaben und der dringlichste Zustand (Urgency:
haengt > Problem > Freigabe > Budget > arbeitet > frei). Gezaehlt wird in
der Instanz, die Seite zeichnet nur.
- /api/team liefert Einheiten und je Agent Einheit und Leitung.
- team.js: Einheiten als Container, zugeklappt als Kasten mit
Zusammenfassung ("6 agents · ⚠ 1 · ✋ 1 · ▶ 2") in der Farbe des
dringlichsten Zustands. Kanten laufen zum sichtbaren Stellvertreter und
werden gebuendelt (×n); was in einer zugeklappten Einheit bleibt, wird
nicht gezeichnet. Bis 15 Agenten alles offen, darueber nur die oberste
Ebene; der Zustand je Instanz im localStorage.
- Seitenleiste fuer Einheiten: Pfad, Ziel, Mitglieder (Leitung zuerst),
Untereinheiten, Auf-/Zuklappen, Link zur Organisation.
- Layout: rectpacking trifft das Seitenverhaeltnis nur grob; es werden
bis zu vier Vorgaben probiert und die zur Flaeche passendste behalten.
- Sprachschluessel en/de.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
201 lines
7.7 KiB
C#
201 lines
7.7 KiB
C#
using ClawdDotNet.App.Services;
|
|
using ClawdDotNet.Core.Accounting;
|
|
using ClawdDotNet.Core.Config;
|
|
using ClawdDotNet.Core.Engine;
|
|
using ClawdDotNet.Core.Security;
|
|
using ClawdDotNet.Core.Staging;
|
|
using ClawdDotNet.Core.Tasks;
|
|
|
|
namespace ClawdDotNet.Web.Contracts;
|
|
|
|
/// <summary>
|
|
/// Die eine Stelle, an der Kerntypen zu Übertragungstypen werden.
|
|
///
|
|
/// <para>Eine Stelle, weil die Maskierung von Zugangsdaten hier hängt. Verstreut über
|
|
/// zehn Endpunkte wäre die Frage „kommt hier ein Passwort heraus?" nicht mehr zu
|
|
/// beantworten, sondern nur noch zu hoffen.</para>
|
|
/// </summary>
|
|
internal static class Mapping
|
|
{
|
|
public static AgentDto ToDto(
|
|
this AgentConfig agent,
|
|
IReadOnlyDictionary<string, string> toolDescriptions,
|
|
bool isRunning)
|
|
=> new(
|
|
agent.AgentId,
|
|
agent.DisplayName,
|
|
agent.Model,
|
|
agent.Description,
|
|
agent.PromptCaching,
|
|
agent.MaxToolResultChars,
|
|
agent.LoopGuard.MaxSteps,
|
|
agent.LoopGuard.MaxContextTokens,
|
|
agent.LoopGuard.TimeoutSeconds,
|
|
agent.Budget.DailyCostUsd,
|
|
agent.Budget.DailyTokens,
|
|
isRunning,
|
|
AgentAdministration.KnownTools
|
|
.Select(name => new AgentToolDto(
|
|
name,
|
|
toolDescriptions.GetValueOrDefault(name) ?? "",
|
|
agent.Tools.ContainsKey(name)))
|
|
.ToList());
|
|
|
|
/// <summary>
|
|
/// Die Einstellungen eines Werkzeugs — <b>ohne die Geheimnisse</b>.
|
|
///
|
|
/// <para>Welcher Schlüssel eines ist, entscheidet <see cref="ConfigSecrets"/>: der
|
|
/// Kern weiß es bereits, und eine zweite Liste hier wäre eine, die beim nächsten
|
|
/// neuen Werkzeug vergessen wird. Statt des Werts steht in <c>Secrets</c>, ob
|
|
/// überhaupt einer gesetzt ist.</para>
|
|
/// </summary>
|
|
public static ToolSettingsDto ToDto(
|
|
string toolName, IReadOnlyDictionary<string, object?> settings)
|
|
{
|
|
var values = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
|
|
var secrets = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (var (key, value) in settings)
|
|
{
|
|
if (ConfigSecrets.IsSecretKey(key))
|
|
secrets[key] = value is not null && value is not string { Length: 0 };
|
|
else
|
|
values[key] = value;
|
|
}
|
|
|
|
return new ToolSettingsDto(toolName, values, secrets);
|
|
}
|
|
|
|
public static ChatEntryDto ToDto(this ChatEntry entry)
|
|
=> new(entry.Role, entry.Content, entry.Timestamp, entry.Source);
|
|
|
|
public static TeamSnapshotDto ToDto(this App.Services.TeamSnapshot snapshot)
|
|
=> new(
|
|
snapshot.Members.Select(m => new TeamMemberDto(
|
|
m.AgentId, m.DisplayName, m.Model, m.State.ToString(),
|
|
m.Runs.Select(ToDto).ToList(),
|
|
m.PendingApprovals, m.LastRunStatus, m.LastRunAtUtc, m.LastRunResult, m.TypicalRunMs,
|
|
m.UnitId, m.IsLead)).ToList(),
|
|
snapshot.RecentLinks.Select(ToDto).ToList(),
|
|
snapshot.AtUtc,
|
|
(snapshot.Units ?? []).Select(u => new TeamUnitDto(
|
|
u.Id, u.Name, u.ParentId, u.Path, u.LeaderAgentId, u.Goal, u.Agents,
|
|
u.States.ToDictionary(s => s.Key.ToString(), s => s.Value),
|
|
u.PendingApprovals, u.Worst.ToString())).ToList());
|
|
|
|
public static ActiveRunDto ToDto(this Core.Activity.ActiveRun run)
|
|
=> new(run.RunId, run.AgentId, run.Source, run.TaskId, run.CalledBy, run.StartedAtUtc);
|
|
|
|
public static AgentLinkDto ToDto(this Core.Activity.AgentLink link)
|
|
=> new(link.From, link.To, link.Kind, link.AtUtc);
|
|
|
|
public static TaskDto ToDto(this TaskOverview overview)
|
|
=> overview.Task.ToDto() with
|
|
{
|
|
NextRunUtc = overview.NextRunUtc,
|
|
LastRun = overview.LastRun is { } lauf
|
|
? new TaskLastRunDto(lauf.OccurredAt, lauf.Status, lauf.CostUsd, lauf.CostIsKnown, lauf.ResultRef)
|
|
: null,
|
|
Hinweis = overview.Hinweis
|
|
};
|
|
|
|
public static TaskInput ToInput(this TaskInputDto input)
|
|
=> new(
|
|
input.Title ?? "",
|
|
input.Body ?? "",
|
|
input.Assignee ?? "",
|
|
input.WhenKind,
|
|
input.WhenValue,
|
|
input.TimeZone,
|
|
input.Acceptance ?? "",
|
|
input.Priority);
|
|
|
|
public static ForecastDto ToDto(this InstanzPrognose prognose)
|
|
=> new(
|
|
prognose.Aufgaben.Select(a => new ForecastTaskDto(
|
|
a.TaskId, a.Title, a.AgentId, a.Model, a.LaeufeProTag, a.EinmaligOffen,
|
|
a.KostenJeLauf, a.Grundlage.ToString(), a.Stichprobe, a.KostenProTag)).ToList(),
|
|
prognose.AufgabenProTag,
|
|
prognose.SonstigeProTag,
|
|
prognose.SummeProTag,
|
|
prognose.SummeProMonat,
|
|
prognose.EinmaligOffen,
|
|
prognose.OhneKostenbasis,
|
|
prognose.Budgetwarnungen.Select(w => new ForecastBudgetDto(w.AgentId, w.KostenProTag, w.BudgetProTag)).ToList());
|
|
|
|
public static ServiceDto ToDto(this ServiceConfig service)
|
|
=> new(
|
|
service.ServiceId,
|
|
service.Name,
|
|
service.Type,
|
|
service.Port,
|
|
service.Enabled,
|
|
service.BuiltIn,
|
|
service.Description);
|
|
|
|
public static TaskDto ToDto(this TaskItem task)
|
|
=> new(
|
|
task.Id,
|
|
task.Title,
|
|
task.Status.ToString(),
|
|
task.Type.ToString(),
|
|
task.Priority,
|
|
task.Assignee,
|
|
task.RequireApproval,
|
|
task.Acceptance,
|
|
task.BlockedBy,
|
|
task.Body,
|
|
task.CreatedAt,
|
|
task.UpdatedAt,
|
|
task.When is { } when
|
|
? new TaskWhenDto(when.Kind.ToString().ToLowerInvariant(), when.Value, when.TimeZone)
|
|
: null,
|
|
BudgetMiss: task.BudgetMissCount > 0 || task.CatchUpPending || task.DeferredUntil is not null
|
|
? new TaskBudgetMissDto(task.BudgetMissCount, task.LastBudgetMissAt, task.DeferredUntil,
|
|
task.CatchUpPending, task.IsCarriedOver)
|
|
: null);
|
|
|
|
/// <summary>
|
|
/// Ein wartender Aufruf samt seiner eingefrorenen Argumente.
|
|
///
|
|
/// <para>Die Argumente gehen <b>roh</b> heraus, und das ist der ganze Sinn der
|
|
/// Freigabe: Wer entscheidet, muss sehen, was genau ausgeführt würde — nicht eine
|
|
/// Zusammenfassung davon. Sie sind zugleich Fremdtext aus einem Modell; die Anzeige
|
|
/// setzt sie als Text ein, nie als HTML.</para>
|
|
/// </summary>
|
|
public static StagedCallDto ToDto(this StagedCall call, int bisherFreigegeben = 0, bool dauerfreigabenAktiv = false)
|
|
{
|
|
var erlaubbar = dauerfreigabenAktiv && StagingService.IstDauerhaftErlaubbar(call);
|
|
return new(
|
|
call.Id,
|
|
call.RunId,
|
|
call.AgentId,
|
|
call.Tool,
|
|
call.Action,
|
|
call.Proposal,
|
|
call.Status.ToString(),
|
|
call.ArgumentsJson,
|
|
call.CreatedAt,
|
|
bisherFreigegeben,
|
|
erlaubbar,
|
|
erlaubbar && bisherFreigegeben >= StagingService.VorschlagAb);
|
|
}
|
|
|
|
public static StandingApprovalDto ToDto(this Dauerfreigabe d)
|
|
=> new(d.Id, d.AgentId, d.Tool, d.Operation, d.ErteiltVon, d.ErteiltAm, d.AnlassId, d.WiderrufenAm, d.WiderrufenVon);
|
|
|
|
public static UsageDto ToDto(this RunUsage usage)
|
|
=> new(
|
|
usage.Id,
|
|
usage.AgentId,
|
|
usage.Model,
|
|
usage.PromptTokens,
|
|
usage.CompletionTokens,
|
|
usage.CachedTokens,
|
|
usage.CostUsd,
|
|
usage.CostIsKnown,
|
|
usage.Status,
|
|
usage.StepCount,
|
|
usage.OccurredAt);
|
|
}
|