Einheiten, Stufe 4: API und WebUI-Seite "Organisation" (#683)
- /api/organisation: Baum (Einheiten mit Pfad, Tiefe, eigener und wirksamer Obergrenze, Vorgaben; Agenten mit Einheit, Leitung, effektiver Leitung, Werkzeugen, Budget), Probleme der Struktur, bekannte Werkzeuge. Anlegen, Aendern, Loeschen, Leitung, Umzug von Agent und Einheit, Obergrenze, Sammelwerte mit Vorschau. Umzug und Obergrenze kennen preview=true; die Entscheidung trifft allein OrgAdministration. - Neue Agenten koennen ueber die API gleich in eine Einheit gelegt werden. - WebUI #/organisation: links der Baum (oberste Ebene, Einheiten, Leitungen mit Stern, Mitglieder), rechts die Bearbeitung. Eine Begruendung fuer das Audit gilt fuer jede Aenderung. Wo etwas verloren gehen kann, zuerst die Vorschau: laufende Agenten und Probleme halten an, wegfallende Werkzeuge muessen in einem Dialog bestaetigt werden. Sammelwerte als Tabelle mit Ausreissern markiert und abgewaehlt. Bei aktivierter Begrenzung ist vorausgewaehlt, was von oben ohnehin erlaubt ist. Sprachpakete en/de. Geprueft im Browser mit einem Pruefgestell (API durch Attrappe ersetzt): Baum, Auswahl, Obergrenze mit Bestaetigungsdialog, Sammelwert-Vorschau mit Ausreisser und das Schreiben nur der gewaehlten Agenten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
68a432fec6
commit
e4ecf2d5ca
@@ -0,0 +1,90 @@
|
||||
using ClawdDotNet.App;
|
||||
using ClawdDotNet.App.Services;
|
||||
using ClawdDotNet.Web.Contracts;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace ClawdDotNet.Web.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// #683: The organisation — units, leads, moves, tool ceilings, unit-wide values. Every
|
||||
/// decision is made by <see cref="OrgAdministration"/>; this only translates.
|
||||
/// </summary>
|
||||
internal static class OrganisationEndpoints
|
||||
{
|
||||
public static void MapOrganisation(this RouteGroupBuilder api)
|
||||
{
|
||||
var org = api.MapGroup("/organisation");
|
||||
|
||||
org.MapGet("/", (AppHost host) =>
|
||||
{
|
||||
var chart = host.Organisation.Current();
|
||||
var units = chart.Units
|
||||
.OrderBy(u => chart.PathOf(u.Id), StringComparer.OrdinalIgnoreCase)
|
||||
.Select(u => new UnitDto(
|
||||
u.Id, u.Name, u.ParentId, u.LeaderAgentId, u.Goal, u.ToolCeiling,
|
||||
chart.ToolCeiling(u.Id)?.Order(StringComparer.OrdinalIgnoreCase).ToList(),
|
||||
new UnitDefaultsDto(u.Defaults.Model, u.Defaults.DailyCostUsd, u.Defaults.DailyTokens, u.Defaults.WhenExhausted),
|
||||
chart.PathOf(u.Id), chart.Depth(u.Id)))
|
||||
.ToList();
|
||||
|
||||
var agents = host.Instance.Agents
|
||||
.OrderBy(a => a.AgentId, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(a => new OrgMemberDto(
|
||||
a.AgentId, a.DisplayName, chart.UnitOf(a.AgentId)?.Id, chart.LedBy(a.AgentId) is not null,
|
||||
chart.EffectiveLeaderOf(a.AgentId)?.AgentId, a.UnitSinceUtc,
|
||||
a.Tools.Keys.Order(StringComparer.OrdinalIgnoreCase).ToList(),
|
||||
a.Budget.DailyCostUsd, a.Budget.DailyTokens, a.Model))
|
||||
.ToList();
|
||||
|
||||
return new OrganisationDto(units, agents, chart.Problems(), AgentAdministration.KnownTools);
|
||||
});
|
||||
|
||||
org.MapPost("/units", (AppHost host, UnitInputDto input) =>
|
||||
host.Organisation.CreateUnit(input.Id, input.Name, input.ParentId, input.Goal, input.Reason).ToResponse());
|
||||
|
||||
org.MapPatch("/units/{unitId}", (AppHost host, string unitId, UnitUpdateDto input) =>
|
||||
host.Organisation.UpdateUnit(unitId, input.Name, input.Goal, input.Reason).ToResponse());
|
||||
|
||||
org.MapDelete("/units/{unitId}", (AppHost host, string unitId, string? reason) =>
|
||||
host.Organisation.DeleteUnit(unitId, reason ?? "").ToResponse());
|
||||
|
||||
org.MapPut("/units/{unitId}/leader", (AppHost host, string unitId, LeaderInputDto input) =>
|
||||
host.Organisation.SetLeader(unitId, input.AgentId, input.Reason).ToResponse());
|
||||
|
||||
org.MapPost("/units/{unitId}/move", (AppHost host, string unitId, MoveInputDto input) =>
|
||||
input.Preview
|
||||
? Results.Ok(ToDto(host.Organisation.PreviewMoveUnit(unitId, input.Target)))
|
||||
: host.Organisation.MoveUnit(unitId, input.Target, input.Reason, input.DropExcessTools).ToResponse());
|
||||
|
||||
org.MapPost("/agents/{agentId}/move", (AppHost host, string agentId, MoveInputDto input) =>
|
||||
input.Preview
|
||||
? Results.Ok(ToDto(host.Organisation.PreviewMoveAgent(agentId, input.Target)))
|
||||
: host.Organisation.MoveAgent(agentId, input.Target, input.Reason, input.DropExcessTools).ToResponse());
|
||||
|
||||
org.MapPut("/units/{unitId}/ceiling", (AppHost host, string unitId, CeilingInputDto input) =>
|
||||
input.Preview
|
||||
? Results.Ok(ToDto(host.Organisation.PreviewToolCeiling(unitId, input.Tools)))
|
||||
: host.Organisation.SetToolCeiling(unitId, input.Tools, input.Reason, input.DropExcessTools).ToResponse());
|
||||
|
||||
org.MapPost("/units/{unitId}/settings/preview", (AppHost host, string unitId, SettingPreviewInputDto input) =>
|
||||
{
|
||||
if (!Enum.TryParse<UnitSetting>(input.Setting, ignoreCase: true, out var setting)
|
||||
|| !Enum.TryParse<UnitSettingMode>(input.Mode, ignoreCase: true, out var mode))
|
||||
return Results.BadRequest(new OutcomeDto(false, "Unknown setting or mode."));
|
||||
|
||||
return Results.Ok(host.Organisation.PreviewSetting(unitId, setting, input.Value, mode)
|
||||
.Select(c => new SettingChangeDto(c.AgentId, c.DisplayName, c.Current, c.Proposed, c.Outlier, c.SelectedByDefault))
|
||||
.ToList());
|
||||
});
|
||||
|
||||
org.MapPost("/units/{unitId}/settings", (AppHost host, string unitId, SettingApplyDto input) =>
|
||||
Enum.TryParse<UnitSetting>(input.Setting, ignoreCase: true, out var setting)
|
||||
? host.Organisation.ApplySetting(unitId, setting, input.Values, input.Reason, input.DefaultValue).ToResponse()
|
||||
: Results.BadRequest(new OutcomeDto(false, "Unknown setting.")));
|
||||
}
|
||||
|
||||
private static StructurePreviewDto ToDto(StructurePreview preview)
|
||||
=> new(preview.AffectedAgents, preview.ToolsLost, preview.Running, preview.Problems, preview.CanApply);
|
||||
}
|
||||
Reference in New Issue
Block a user