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; /// /// #683: The organisation — units, leads, moves, tool ceilings, unit-wide values. Every /// decision is made by ; this only translates. /// 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(input.Setting, ignoreCase: true, out var setting) || !Enum.TryParse(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(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); }