V18 / 8d896b8a86e5
KanbanOperations.cs
Lilith/Core/Kanban/KanbanOperations.cs
Unchanged from the preceding versionCS
using Lilith.Core.Commands;
using Lilith.Core.Projects;
namespace Lilith.Core.Kanban;
internal static class KanbanOperations
{
internal static readonly string[] Names = ["kanban_prepare_operation", "kanban_status_operation"];
internal static Task<string> ExecuteAsync(ProviderClient provider, string name, IReadOnlyDictionary<string, string> inputs, CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
var session = provider.Kanban ?? throw new InvalidOperationException("Kanban is not configured.");
string Get(string key) => inputs.GetValueOrDefault(key, "");
var result = name switch
{
"kanban_prepare_operation" => session.Prepare(Get("name"), Get("kind"), Get("prd"), Get("architecture"),
int.TryParse(Get("repair_limit"), out int limit) ? limit : throw new ArgumentException("repair_limit must be an integer.")),
"kanban_status_operation" => session.Store.Get(Get("id")),
_ => throw new ArgumentException("Unknown Kanban operation.")
};
return Task.FromResult(ProjectJson.Serialize(new { result.Id, result.State, result.Message, board = session.Dashboard.PublicUrl,
viewer = "Use the host /kanban command to open the authenticated viewer. Its control token is not supplied to model calls.",
command = result.State == "waiting_plan" ? $"/kanban-approve {result.Id} {result.Revision}" : $"/kanban-status {result.Id}" }));
}
}
public static class KanbanCommands
{
public static void Register(ChatCommandRegistry commands, Func<ProviderClient> provider)
{
KanbanSession Session() => provider().Kanban ?? throw new InvalidOperationException("Kanban unavailable.");
commands.Register("kanban", "Show the local board dashboard and saved runs.", c => { c.RequireNoArguments(); return Session().Dashboard.Url + "\n" + ProjectJson.Serialize(Session().Store.List().Select(b => new { b.Id, b.Name, b.State, b.Message })); });
commands.Register("kanban-status", "Inspect one board and all task details.", c => System.Text.Json.JsonSerializer.Serialize(Session().Store.Get(c.Arguments.Trim()), KanbanStore.Json));
commands.Register("kanban-pause", "Pause before the next subagent; return to chat when paused.", c => Session().Pause(c.Arguments.Trim()));
commands.Register("kanban-resume", "Explicitly resume one approved board; never grants code execution.", c => Session().Resume(c.Arguments.Trim()));
commands.RegisterAsync("kanban-cancel", "Cancel a run while retaining private drafts and evidence.", async c => new(true, await Session().CancelAsync(c.Arguments.Trim(),c.CancellationToken)));
commands.Register("kanban-approve", "Approve the exact reviewed board: ID REVISION.", c =>
{
var args = c.Arguments.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (args.Length != 2 || !long.TryParse(args[1], out long revision)) throw new ArgumentException("Use /kanban-approve ID REVISION.");
return Session().Approve(args[0], revision);
});
commands.RegisterAsync("kanban-test", "Explicit execution decision for this board's project; up to ten runtime rounds.", async c => new(true, await provider().TestKanbanAsync(c.Arguments.Trim(), c.CancellationToken)));
commands.RegisterAsync("kanban-publish", "Prepare separate exact publication for this qualified board.", async c => new(true, await provider().PublishKanbanAsync(c.Arguments.Trim(), c.CancellationToken)));
commands.Register("kanban-publication-review", "Read the complete exact publication proposal for a board.", c => Session().ReadPublication(c.Arguments.Trim()));
commands.RegisterAsync("kanban-publication-approve", "User approval of a board's already prepared exact workspace proposal.", async c => new(true, await provider().ApproveKanbanPublicationAsync(c.Arguments.Trim(), c.CancellationToken)));
commands.RegisterAsync("kanban-wait", "Wait for the current worker to pause or reach its next host decision; no permissions granted.", async c =>
{ c.RequireNoArguments(); while (Session().IsWorking) await Task.Delay(200, c.CancellationToken); return new(true, "Kanban reached a host decision or stopped."); });
KanbanBoard Pending(string state)
{
var boards = Session().Store.List().Where(b => b.State == state).ToArray();
return boards.Length == 1 ? boards[0] : throw new InvalidOperationException("Expected exactly one " + state + " board. Use an explicit ID.");
}
commands.Register("kanban-approve-pending", "Scripted user decision: approve the single pending source plan, not code execution.", c => { c.RequireNoArguments(); var b = Pending("waiting_plan"); return Session().Approve(b.Id,b.Revision); });
commands.RegisterAsync("kanban-test-pending", "Explicit scripted user execution decision for the single pending Kanban project.", async c => { c.RequireNoArguments(); return new(true, await provider().TestKanbanAsync(Pending("waiting_test").Id,c.CancellationToken)); });
commands.RegisterAsync("kanban-publish-pending", "Prepare exact publication for the single qualified board; does not approve workspace writes.", async c => { c.RequireNoArguments(); return new(true, await provider().PublishKanbanAsync(Pending("waiting_publication").Id,c.CancellationToken)); });
commands.RegisterAsync("kanban-publication-approve-pending", "Scripted user decision approving the single prepared exact board publication.", async c => { c.RequireNoArguments(); return new(true, await provider().ApproveKanbanPublicationAsync(Pending("waiting_publication").Id,c.CancellationToken)); });
}
}