Besprechung als eigenes Mittel: Termin, Tagesordnung, eine Runde je Teilnehmer, Protokoll (#686)
Aufgabentyp meeting mit Teilnehmerliste (Frontmatter, SQLite, Taskboard-Werkzeug, WebUI). MeetingRunner: jeder Teilnehmer einmal in Reihenfolge, Beitraege gerahmt als Daten des Verfassers, danach schliesst die Leitung mit Ergebnis und Aufgaben. Protokoll schreibt die Instanz in den Bereich der Einheit der Leitung. Teilnehmer nur aus dem Adressbuch der Leitung, hoechstens 12. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2df2fc476f
commit
f2bf8b5b59
@@ -93,8 +93,8 @@ public sealed class TaskboardTool : IAgentTool
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["work", "approval", "human_input"],
|
||||
"description": "Type of task. Default 'work'."
|
||||
"enum": ["work", "approval", "human_input", "meeting"],
|
||||
"description": "Type of task. Default 'work'. 'meeting': the assignee chairs, each of 'participants' gets one turn with the agenda (body) and the contributions so far, then the chair writes the result and creates the tasks for it. Minutes are kept in the chair's unit workspace."
|
||||
},
|
||||
"priority": {
|
||||
"type": "integer",
|
||||
@@ -134,6 +134,11 @@ public sealed class TaskboardTool : IAgentTool
|
||||
"items": { "type": "string" },
|
||||
"description": "IDs of tasks that must be completed first."
|
||||
},
|
||||
"participants": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Meeting only: agent IDs in the order they speak. Only agents the chair can address; the chair itself is not listed."
|
||||
},
|
||||
"onlyWhenMarketOpen": {
|
||||
"type": "boolean",
|
||||
"description": "Only trigger schedule when market is open."
|
||||
@@ -328,6 +333,12 @@ public sealed class TaskboardTool : IAgentTool
|
||||
if (!TryReadFill(input, when, null, out var fillBy, out var estimate, out var fillError))
|
||||
return ToolResult.Fail(fillError!);
|
||||
|
||||
var type = TaskText.Type(GetString(input, "type"));
|
||||
var participants = GetStringList(input, "participants");
|
||||
var assignee = GetString(input, "assignee")?.Trim() is { Length: > 0 } chosen ? chosen : TaskAssignee.Human;
|
||||
if (MeetingProblem(type, participants, assignee, context) is { } meetingProblem)
|
||||
return ToolResult.Fail(meetingProblem);
|
||||
|
||||
// #674: The same finding a second time is not a second task. It is recorded on the
|
||||
// existing one — so that later one can see how often and for how long.
|
||||
if (GetBool(input, "force") != true)
|
||||
@@ -361,9 +372,10 @@ public sealed class TaskboardTool : IAgentTool
|
||||
Title = title.Trim(),
|
||||
Body = GetString(input, "body")?.Trim() ?? "",
|
||||
Status = TaskItemStatus.Todo,
|
||||
Type = TaskText.Type(GetString(input, "type")),
|
||||
Type = type,
|
||||
Priority = GetInt(input, "priority") ?? 3,
|
||||
Assignee = GetString(input, "assignee")?.Trim() is { Length: > 0 } a ? a : TaskAssignee.Human,
|
||||
Assignee = assignee,
|
||||
Participants = type == TaskItemType.Meeting ? participants : [],
|
||||
When = when,
|
||||
RequireApproval = GetBool(input, "require_approval") ?? false,
|
||||
Acceptance = GetString(input, "acceptance")?.Trim() ?? "",
|
||||
@@ -386,6 +398,20 @@ public sealed class TaskboardTool : IAgentTool
|
||||
$"Priority: {created.Priority}{FormatWhen(created.When)}" + Hinweis(created));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #686: A meeting needs an agent in the chair and participants the chair can address — the
|
||||
/// circle comes from the structure, not from whoever writes the invitation.
|
||||
/// </summary>
|
||||
private static string? MeetingProblem(TaskItemType type, IReadOnlyList<string> participants, string assignee, AgentToolContext context)
|
||||
{
|
||||
if (type != TaskItemType.Meeting)
|
||||
return participants.Count > 0 ? "'participants' only belongs to a meeting (type 'meeting')." : null;
|
||||
var chair = TaskAssignee.AgentId(assignee);
|
||||
if (string.IsNullOrWhiteSpace(chair))
|
||||
return "A meeting needs an agent in the chair: set 'assignee' to '@new:<agent>'.";
|
||||
return MeetingRunner.Refusal(participants, chair, context.Organisation);
|
||||
}
|
||||
|
||||
private static async Task<ToolResult> ListAsync(
|
||||
JsonElement input, TaskboardService board, Func<TaskItem, bool>? visible, CancellationToken ct)
|
||||
{
|
||||
@@ -441,6 +467,15 @@ public sealed class TaskboardTool : IAgentTool
|
||||
if (!TryReadFill(input, hasWhen ? when : existing?.When, existing, out var fillBy, out var estimate, out var fillError))
|
||||
return ToolResult.Fail(fillError!);
|
||||
|
||||
if (existing is not null)
|
||||
{
|
||||
var type = input.TryGetProperty("type", out _) ? TaskText.Type(GetString(input, "type")) : existing.Type;
|
||||
var participants = input.TryGetProperty("participants", out _) ? GetStringList(input, "participants") : existing.Participants;
|
||||
var assignee = GetString(input, "assignee")?.Trim() ?? existing.Assignee;
|
||||
if (MeetingProblem(type, participants, assignee, context) is { } meetingProblem)
|
||||
return ToolResult.Fail(meetingProblem);
|
||||
}
|
||||
|
||||
var result = await board.UpdateAsync(id.Trim(), current => current with
|
||||
{
|
||||
Title = GetString(input, "title")?.Trim() ?? current.Title,
|
||||
@@ -452,6 +487,7 @@ public sealed class TaskboardTool : IAgentTool
|
||||
RequireApproval = GetBool(input, "require_approval") ?? current.RequireApproval,
|
||||
Acceptance = GetString(input, "acceptance")?.Trim() ?? current.Acceptance,
|
||||
BlockedBy = input.TryGetProperty("blocked_by", out _) ? GetStringList(input, "blocked_by") : current.BlockedBy,
|
||||
Participants = input.TryGetProperty("participants", out _) ? GetStringList(input, "participants") : current.Participants,
|
||||
OnlyWhenMarketOpen = GetBool(input, "onlyWhenMarketOpen") ?? current.OnlyWhenMarketOpen,
|
||||
ParentId = parent ?? current.ParentId,
|
||||
FillBy = fillBy,
|
||||
@@ -539,6 +575,8 @@ public sealed class TaskboardTool : IAgentTool
|
||||
block.AppendLine($" Arose from: {origin}");
|
||||
if (task.BlockedBy.Count > 0)
|
||||
block.AppendLine($" Waiting for: {string.Join(", ", task.BlockedBy)}");
|
||||
if (task.Type == TaskItemType.Meeting)
|
||||
block.AppendLine($" Meeting with: {string.Join(", ", task.Participants)} (chair: {task.Assignee})");
|
||||
// #717: Fill work waits for room, not for a time — say so, or it looks forgotten.
|
||||
if (task.IsFillWork)
|
||||
block.AppendLine($" Fill work: runs when the budget has room, at the latest by {task.FillBy:yyyy-MM-dd HH:mm} UTC");
|
||||
|
||||
Reference in New Issue
Block a user