evOo-local/packages/db/src/card-messages.ts
cynarAI 6fd136e944
feat: suggestions the user accepts or dismisses with one tap (#142)
Bots can offer an exact tool call as a card. Nothing runs until the user
taps Accept; the call then runs as its own approved run of the bot it was
meant for, and the card shows the outcome. Open suggestions also wait in
the inbox and expire after a week.


Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-05 02:19:31 +02:00

38 lines
1.8 KiB
TypeScript

import type { CardBlock, MessageBlock } from "@rakazo/contracts";
import type { Prisma } from "./client.js";
import { appendEventInTransaction } from "./events.js";
/**
* Change one card in a bot message and tell the thread. The thread row is locked so two taps on
* the same card apply one after the other. `change` returns null to leave the card as it is;
* the result is null when the message, the card or a change is missing.
*/
export async function updateCardInTransaction(
tx: Prisma.TransactionClient,
input: { threadId: string; messageId: string; cardId: string },
change: (card: CardBlock) => CardBlock | null,
): Promise<{ seq: number; card: CardBlock } | null> {
await tx.$queryRaw`SELECT id FROM threads WHERE id = ${input.threadId} FOR UPDATE`;
const message = await tx.message.findFirst({
where: { id: input.messageId, threadId: input.threadId, role: "bot" },
select: { id: true, blocks: true, botId: true, thread: { select: { spaceId: true } } },
});
if (!message?.botId) return null;
const blocks = (Array.isArray(message.blocks) ? message.blocks : []) as MessageBlock[];
const index = blocks.findIndex((block) => block.kind === "card" && block.id === input.cardId);
const card = blocks[index];
if (card?.kind !== "card") return null;
const next = change(card);
if (!next) return null;
const updated = blocks.map((block, position) => (position === index ? next : block));
await tx.message.update({ where: { id: message.id }, data: { blocks: updated } });
const event = await appendEventInTransaction(tx, {
spaceId: message.thread.spaceId,
threadId: input.threadId,
botId: message.botId,
type: "thread.message.updated",
payload: { messageId: message.id, role: "bot", blocks: updated },
});
return { seq: event.seq, card: next };
}