evOo-local/packages/db/src/autopilot-lines.ts
cynarAI 54658ee689
feat: autopilot progress, composer start and morning summary (#146)
The autopilot's start line counts its steps and folds open to the latest
checkpoints, and its result reads as a card. The composer's plus menu
starts an autopilot with the draft as the order and a choice of until
done, tomorrow morning or a time. Today lists the autopilots that run,
wait or ended within a day, and their step runs no longer show one by
one. Autopilot runs send no completion or failure push; only a pause
notifies.


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

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-05 04:45:49 +02:00

98 lines
3.4 KiB
TypeScript

import type { AutopilotBlock, MessageBlock } from "@rakazo/contracts";
import { addAutopilotCheckpoint, markAutopilotLinesEnded } from "@rakazo/core";
import type { Prisma } from "./client.js";
import { appendEventInTransaction } from "./events.js";
import { createThreadMessageInTransaction } from "./messages.js";
/** Write one autopilot line in the thread; it never marks the thread unread. */
export async function postAutopilotLineInTransaction(
tx: Prisma.TransactionClient,
input: { spaceId: string; threadId: string; botId: string; block: AutopilotBlock },
): Promise<{ messageId: string; seq: number }> {
const message = await createThreadMessageInTransaction(tx, {
threadId: input.threadId,
role: "bot",
blocks: [input.block],
botId: input.botId,
markUnread: false,
});
const event = await appendEventInTransaction(tx, {
spaceId: input.spaceId,
threadId: input.threadId,
botId: input.botId,
type: "thread.message.created",
payload: { messageId: message.id, role: "bot", blocks: [input.block] },
});
return { messageId: message.id, seq: event.seq };
}
/** Take the Stop and Continue controls off an autopilot's earlier lines once it is over. */
export async function endAutopilotLinesInTransaction(
tx: Prisma.TransactionClient,
input: { spaceId: string; threadId: string; autopilotId: string },
): Promise<number | null> {
const messages = await tx.message.findMany({
where: {
threadId: input.threadId,
blocks: { array_contains: [{ kind: "autopilot", autopilotId: input.autopilotId }] },
},
select: { id: true, role: true, botId: true, blocks: true },
});
let seq: number | null = null;
for (const message of messages) {
const blocks = markAutopilotLinesEnded(message.blocks as MessageBlock[], input.autopilotId);
if (!blocks || !message.botId) continue;
await tx.message.update({
where: { id: message.id },
data: { blocks: blocks as Prisma.InputJsonValue },
});
const event = await appendEventInTransaction(tx, {
spaceId: input.spaceId,
threadId: input.threadId,
botId: message.botId,
type: "thread.message.updated",
payload: { messageId: message.id, role: message.role, blocks },
});
seq = event.seq;
}
return seq;
}
/** Count a checkpoint on the autopilot's start line, so the line shows live progress. */
export async function recordAutopilotCheckpointInTransaction(
tx: Prisma.TransactionClient,
input: {
spaceId: string;
threadId: string;
messageId: string;
autopilotId: string;
checkpoint: { text: string; at: string };
steps: number;
},
): Promise<number | null> {
const message = await tx.message.findUnique({
where: { id: input.messageId },
select: { id: true, role: true, botId: true, blocks: true, threadId: true },
});
if (!message?.botId || message.threadId !== input.threadId) return null;
const blocks = addAutopilotCheckpoint(
message.blocks as MessageBlock[],
input.autopilotId,
input.checkpoint,
input.steps,
);
if (!blocks) return null;
await tx.message.update({
where: { id: message.id },
data: { blocks: blocks as Prisma.InputJsonValue },
});
const event = await appendEventInTransaction(tx, {
spaceId: input.spaceId,
threadId: input.threadId,
botId: message.botId,
type: "thread.message.updated",
payload: { messageId: message.id, role: message.role, blocks },
});
return event.seq;
}