using ClawdDotNet.Core.Organisation; namespace ClawdDotNet.Core.Tasks; /// /// #684: One board per unit. With 50 agents one board for everything is worthless as an /// overview — the test instance had 159 tasks with seven agents — and a bookkeeping task /// could slow down the creation of a marketing task through the duplicate check. /// /// The board of a task is not stored, it follows from the organisation: the unit /// of the agent it is assigned to, or — for a task for a human or a fresh run — the unit of /// the agent that created it. So open tasks move with an agent that moves (Organisation- /// Konzept, section 3), and nothing has to be migrated. A task that belongs to no unit is on /// the board of the top level. /// /// Who sees which board: an agent its own unit's; a lead also the boards of every /// unit below the one it leads; everyone the tasks they created themselves, so a task handed /// up to the lead stays in view of whoever handed it up. A task across units is a delegation /// to the other unit's lead — the address book decides that, not this. /// /// Without units nothing changes: there is one board, and everyone sees it. /// public static class TaskBoard { /// The unit whose board the task is on; null for the top level. public static string? UnitOf(TaskItem task, OrgChart org) { var assigned = TaskAssignee.AgentId(task.Assignee); if (assigned.Length > 0 && org.Agent(assigned) is not null) return org.UnitOf(assigned)?.Id; if (!string.IsNullOrWhiteSpace(task.CreatedBy) && org.Agent(task.CreatedBy) is not null) return org.UnitOf(task.CreatedBy)?.Id; return null; } /// /// What an agent may see — null for "everything": without units, without an /// organisation (outside the engine), or for an agent the organisation does not know. /// public static Func? VisibleTo(OrgChart? org, string agentId) { if (org is not { HasUnits: true } || org.Agent(agentId) is null) return null; var boards = BoardsOf(org, agentId); var topLevel = org.UnitOf(agentId) is null; return task => string.Equals(task.CreatedBy, agentId, StringComparison.OrdinalIgnoreCase) || (UnitOf(task, org) is { } unit ? boards.Contains(unit) : topLevel); } /// The units whose boards an agent sees: its own, and all below the unit it leads. public static IReadOnlySet BoardsOf(OrgChart org, string agentId) { var boards = new HashSet(StringComparer.OrdinalIgnoreCase); if (org.UnitOf(agentId) is { } own) boards.Add(own.Id); if (org.LedBy(agentId) is { } led) { var seen = new HashSet(StringComparer.OrdinalIgnoreCase); var pending = new Stack(); pending.Push(led.Id); while (pending.TryPop(out var unitId)) { if (!seen.Add(unitId)) continue; // a broken tree must not hang this boards.Add(unitId); foreach (var child in org.Children(unitId)) pending.Push(child.Id); } } return boards; } }