* feat: show routines the bot sets up from chat as one quiet line
When someone mentions something recurring in passing ("every Sunday I plan the
week"), the bot now creates the routine right away and the conversation shows a
single line with the schedule, Edit and Undo, on web and mobile.
- New `event` message block for routine lines, kept in history so later turns
know what was set up or removed.
- schedule_create reuses an active repeating routine of the same name instead
of adding a duplicate, caps a bot at 50 active schedules, and falls back to
default times when none was given.
- Removing a routine (Undo, the editor, or schedule_cancel) marks its lines
removed via the new routines.noticeMessageId column.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1
* test: give the routine notice test a complete job publisher
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1
---------
Co-authored-by: Claude <noreply@anthropic.com>
85 lines
3.1 KiB
TypeScript
85 lines
3.1 KiB
TypeScript
import type { RoutineEventBlock } from "@rakazo/contracts";
|
|
import { markRoutineEventsRemoved } from "@rakazo/core";
|
|
import type { Prisma, PrismaClient } from "./client.js";
|
|
import { appendEventInTransaction } from "./events.js";
|
|
import { createThreadMessageInTransaction } from "./messages.js";
|
|
|
|
export interface RoutineNoticeInput {
|
|
spaceId: string;
|
|
threadId: string;
|
|
/** The bot that set the routine up and writes the line. */
|
|
botId: string;
|
|
block: RoutineEventBlock;
|
|
}
|
|
|
|
/** Write the timeline line for a routine the bot set up and remember it on the routine. */
|
|
export async function postRoutineNotice(
|
|
prisma: PrismaClient,
|
|
input: RoutineNoticeInput,
|
|
): Promise<{ messageId: string; seq: number }> {
|
|
return prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
|
const message = await createThreadMessageInTransaction(tx, {
|
|
threadId: input.threadId,
|
|
role: "bot",
|
|
blocks: [input.block],
|
|
botId: input.botId,
|
|
markUnread: false,
|
|
});
|
|
await tx.routine.updateMany({
|
|
where: { id: input.block.routineId },
|
|
data: { noticeMessageId: message.id },
|
|
});
|
|
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 };
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mark every line about a removed routine in the thread of its latest line. Returns the thread and
|
|
* event seq to notify, or null when the routine had no line or nothing changed.
|
|
*/
|
|
export async function markRoutineNoticesRemoved(
|
|
prisma: PrismaClient,
|
|
routine: { id: string; botId: string; noticeMessageId: string | null },
|
|
): Promise<{ threadId: string; seq: number } | null> {
|
|
if (!routine.noticeMessageId) return null;
|
|
return prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
|
const notice = await tx.message.findUnique({
|
|
where: { id: routine.noticeMessageId! },
|
|
select: { threadId: true, thread: { select: { spaceId: true } } },
|
|
});
|
|
if (!notice) return null;
|
|
const messages = await tx.message.findMany({
|
|
where: {
|
|
threadId: notice.threadId,
|
|
blocks: { array_contains: [{ kind: "event", routineId: routine.id }] },
|
|
},
|
|
select: { id: true, role: true, botId: true, blocks: true },
|
|
});
|
|
let seq: number | null = null;
|
|
for (const message of messages) {
|
|
const blocks = markRoutineEventsRemoved(message.blocks as RoutineEventBlock[], routine.id);
|
|
if (!blocks) continue;
|
|
await tx.message.update({
|
|
where: { id: message.id },
|
|
data: { blocks: blocks as Prisma.InputJsonValue },
|
|
});
|
|
const event = await appendEventInTransaction(tx, {
|
|
spaceId: notice.thread.spaceId,
|
|
threadId: notice.threadId,
|
|
botId: message.botId ?? routine.botId,
|
|
type: "thread.message.updated",
|
|
payload: { messageId: message.id, role: message.role, blocks },
|
|
});
|
|
seq = event.seq;
|
|
}
|
|
return seq === null ? null : { threadId: notice.threadId, seq };
|
|
});
|
|
}
|