Entscheidungsinstanz an der Spitze: Mensch, Agent oder Mensch zuerst mit Frist (#690)

- cases.top in instance.json: Human | Agent | HumanFirst, agentId, humanFirstHours
- TopRole: der entscheidende Agent steht in keiner Einheit, liest keine Fremdinhalte
  und hat nur Taskboard, Escalation, Memory, FileRW, AgentComm; geprueft beim Setzen,
  bei jeder Zustellung und vor jeder Entscheidung
- CaseDesk: was bisher an der Spitze beim Menschen landete, geht nach Einstellung an
  den Agenten; Tagesdeckel und Frist gehen weiter an den Menschen. handle verwehrt,
  forward geht an den Menschen. Mensch zuerst: Uebergabe nach Ablauf, eigene Frist.
  Der Mensch kann einen Fall des Agenten selbst entscheiden.
- Beschluesse des Agenten: ByTopAgent, zum Lesen vorgelegt; jeder Beschluss laesst
  sich mit Begruendung zuruecknehmen und bindet dann niemanden mehr
- Briefing fuer den Agenten an der Spitze, auch ohne Einheiten
- OrgAdministration.SetTop mit Audit; Werkzeugsperre und Verschiebesperre
- WebUI Vorgaenge: Reiter Spitze, Lesen/Zuruecknehmen, Zaehler; i18n en/de
- Tests: TopRoleTests (15), OrgAdministrationTests (+2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-29 09:27:26 +02:00
co-authored by Claude Opus 5.5
parent e22edb418a
commit e8964e3c74
16 changed files with 1020 additions and 26 deletions
@@ -1,4 +1,5 @@
using ClawdDotNet.App.Services;
using ClawdDotNet.Core.Cases;
using ClawdDotNet.Core.Config;
using Shouldly;
@@ -249,4 +250,47 @@ public sealed class OrgAdministrationTests : IDisposable
Find("newbie").Budget.DailyCostUsd.ShouldBe(7);
Find("newbie").UnitId.ShouldBe("amazon");
}
// ═══════════════════ the top (#690) ═══════════════════
private void MakeDecider(string id)
{
_test.Host.AgentAdmin.SetToolEnabled(id, "WebFetch", false).Ok.ShouldBeTrue();
_test.Host.AgentAdmin.SetToolEnabled(id, "Database", false).Ok.ShouldBeTrue();
}
[Fact]
public void Only_an_agent_that_fits_can_take_the_top()
{
BuildMarketing();
Org.SetTop(TopMode.Agent, null).Ok.ShouldBeFalse("an agent at the top needs a name");
Org.SetTop(TopMode.Agent, "bookkeeper").Message.ShouldContain("Database");
Org.SetTop(TopMode.Agent, "marketing-lead").Message.ShouldContain("belongs to a unit");
MakeDecider("bookkeeper");
var set = Org.SetTop(TopMode.HumanFirst, "bookkeeper", 3, "decisions at night");
set.Ok.ShouldBeTrue(set.Message);
var top = _test.Host.Instance.Cases.Top;
top.Mode.ShouldBe(TopMode.HumanFirst);
top.AgentId.ShouldBe("bookkeeper");
top.HumanFirstHours.ShouldBe(3);
Org.SetTop(TopMode.Human, null).Ok.ShouldBeTrue();
_test.Host.Instance.Cases.Top.AgentId.ShouldBeNull();
}
[Fact]
public void The_deciding_agent_cannot_be_given_outside_tools_nor_moved_into_a_unit()
{
BuildMarketing();
MakeDecider("bookkeeper");
Org.SetTop(TopMode.Agent, "bookkeeper").Ok.ShouldBeTrue();
_test.Host.AgentAdmin.SetToolEnabled("bookkeeper", "Mail", true).Message.ShouldContain("deciding agent");
_test.Host.AgentAdmin.SetToolEnabled("bookkeeper", "Memory", true).Ok.ShouldBeTrue();
Org.MoveAgent("bookkeeper", "marketing", "reorg").Ok.ShouldBeFalse();
Find("bookkeeper").UnitId.ShouldBeNull();
}
}