feat(agent-api): POST /api/agent/cards/:cardId/comments

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Dave 2026-09-26 09:40:52 +02:00
parent a297fe5f15
commit 0e74dbdf14

View file

@ -0,0 +1,55 @@
import { getAdminPb } from "@/lib/pb/admin";
import { requireAgentToken } from "@/lib/agent/auth";
import { getOwnerUser } from "@/lib/agent/owner";
import { jsonError, jsonOk } from "@/lib/http/json";
import type { CardRecord } from "@/lib/pb/types";
/** Matches the comments collection's own `body` field limit. */
const MAX_BODY_LENGTH = 20000;
/**
* POST /api/agent/cards/:cardId/comments — post a drafted plan as a comment.
*
* The stored body is always prefixed before being written, so no caller can
* accidentally or deliberately post something indistinguishable from the
* owner's own words. This prefix is the one thing this whole feature exists
* to guarantee — do not make it conditional or configurable.
*/
export async function POST(
request: Request,
context: { params: Promise<{ cardId: string }> }
): Promise<Response> {
const denied = requireAgentToken(request);
if (denied) return denied;
const { cardId } = await context.params;
let json: unknown;
try {
json = await request.json();
} catch {
return jsonError("Invalid request body", 400);
}
const body = (json as { body?: unknown } | null)?.body;
if (typeof body !== "string" || !body.trim()) {
return jsonError("A non-empty body is required", 400);
}
const pb = await getAdminPb();
const card = await pb.collection("cards").getOne<CardRecord>(cardId).catch(() => null);
if (!card || card.archived) return jsonError("Card not found", 404);
const owner = await getOwnerUser();
const prefix = "🤖 Drafted by your agent:\n\n";
const trimmed = body.trim().slice(0, MAX_BODY_LENGTH - prefix.length);
const comment = await pb.collection("comments").create({
card: cardId,
author: owner.id,
body: `${prefix}${trimmed}`,
});
return jsonOk({ id: comment.id }, 201);
}