From 73da5ae6d2aa7d336c009bdc32b1b90ff8dd0eec Mon Sep 17 00:00:00 2001 From: Christopher Mayor Date: Sat, 7 Mar 2026 12:53:22 -0800 Subject: [PATCH] [taskboard] add completion sync APIs --- README.md | 2 + app/api/sync/openclaw/route.ts | 7 ++ app/api/tasks/[id]/callback/route.ts | 30 +++++++ app/api/tasks/[id]/route.ts | 12 +++ components/tasks-client.tsx | 9 +++ lib/db.ts | 3 + lib/openclaw-sync.ts | 117 +++++++++++++++++++++++++++ lib/tasks.ts | 58 ++++++++++++- lib/types.ts | 12 +++ 9 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 app/api/sync/openclaw/route.ts create mode 100644 app/api/tasks/[id]/callback/route.ts create mode 100644 lib/openclaw-sync.ts diff --git a/README.md b/README.md index b6c3576..b8de182 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ It tracks and visualizes: - dispatch lifecycle states and SQLite audit history - OpenClaw swarm dispatch into `~/.clawdbot/active-tasks.json` - ZeroClaw webhook dispatch for `grizzley` and `ice` +- task callback API for remote completion/result sync +- OpenClaw registry sync API for swarm task state reconciliation - failure queue and dispatch history views - family-specific runtime views for OpenClaw and ZeroClaw - architecture documentation rendered directly from tracked config diff --git a/app/api/sync/openclaw/route.ts b/app/api/sync/openclaw/route.ts new file mode 100644 index 0000000..2613592 --- /dev/null +++ b/app/api/sync/openclaw/route.ts @@ -0,0 +1,7 @@ +import { NextResponse } from "next/server"; + +import { syncOpenClawTasks } from "@/lib/openclaw-sync"; + +export async function POST() { + return NextResponse.json(await syncOpenClawTasks()); +} diff --git a/app/api/tasks/[id]/callback/route.ts b/app/api/tasks/[id]/callback/route.ts new file mode 100644 index 0000000..653ce95 --- /dev/null +++ b/app/api/tasks/[id]/callback/route.ts @@ -0,0 +1,30 @@ +import { NextResponse } from "next/server"; + +import { applyTaskCallback } from "@/lib/tasks"; + +export async function POST( + request: Request, + { params }: { params: Promise<{ id: string }> }, +) { + const { id } = await params; + const numericId = Number(id); + if (!Number.isInteger(numericId) || numericId <= 0) { + return NextResponse.json({ error: "invalid_task_id" }, { status: 400 }); + } + + const payload = (await request.json()) as { + status?: "Backlog" | "Todo" | "In Progress" | "Review" | "Done"; + dispatch_state?: "planned" | "assigned" | "dispatched" | "acknowledged" | "completed" | "failed"; + summary?: string | null; + detail?: string | null; + completed_by?: string | null; + last_error?: string | null; + }; + + const updated = await applyTaskCallback(numericId, payload); + if (!updated) { + return NextResponse.json({ error: "task_not_found" }, { status: 404 }); + } + + return NextResponse.json(updated); +} diff --git a/app/api/tasks/[id]/route.ts b/app/api/tasks/[id]/route.ts index 76e3f13..acef7d5 100644 --- a/app/api/tasks/[id]/route.ts +++ b/app/api/tasks/[id]/route.ts @@ -38,6 +38,18 @@ export async function PATCH( reasoning_effort: typeof payload.reasoning_effort === "string" ? payload.reasoning_effort : undefined, model_hint: typeof payload.model_hint === "string" ? payload.model_hint : undefined, + result_summary: + payload.result_summary === null || typeof payload.result_summary === "string" + ? (payload.result_summary as never) + : undefined, + result_detail: + payload.result_detail === null || typeof payload.result_detail === "string" + ? (payload.result_detail as never) + : undefined, + completed_by: + payload.completed_by === null || typeof payload.completed_by === "string" + ? (payload.completed_by as never) + : undefined, priority: payload.priority as never, status: payload.status as never, last_dispatch_at: diff --git a/components/tasks-client.tsx b/components/tasks-client.tsx index f9a1041..8992bae 100644 --- a/components/tasks-client.tsx +++ b/components/tasks-client.tsx @@ -347,6 +347,15 @@ export function TasksClient({
{task.target_channel || "n/a"}
+ {task.result_summary ? ( +
+

Latest Result

+

{task.result_summary}

+ {task.result_detail ? ( +

{task.result_detail}

+ ) : null} +
+ ) : null}
{task.dispatch_state !== "dispatched" && task.dispatch_state !== "completed" ? (