Cursor Agent 32af2398d6 fix(adapters): retry delegated bot returns when wake message is gone
When a delegated bot finishes but its wake message was deleted (e.g.
thread clear), returnBotMessageOutcome used to mark botOutcomeReturnedAt
and drop the coordinator reply silently. Retry instead, and block
clearThread while completed bot_message runs still owe a return.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 06:20:47 +00:00

66 lines
1.9 KiB
TypeScript

import type { Actor } from "@rakazo/contracts";
import type { PrismaClient } from "./client.js";
export class IsolationError extends Error {
constructor(message = "Resource not found") {
super(message);
this.name = "IsolationError";
}
}
/** Unique (spaceId, userId, name) collision when renaming a bot section. */
export class BotSectionNameConflictError extends Error {
constructor(message = "Section name already used") {
super(message);
this.name = "BotSectionNameConflictError";
}
}
/** Thread clear blocked while a delegated bot run still owes its coordinator a return. */
export class ClearThreadPendingDelegatedReturnsError extends Error {
constructor(message = "Delegated bot work is still finishing") {
super(message);
this.name = "ClearThreadPendingDelegatedReturnsError";
}
}
export async function requireMembership(
prisma: PrismaClient,
userId: string,
requestedSpaceId?: string | null,
): Promise<Actor> {
const membership = await prisma.spaceMember.findFirst({
where: {
userId,
...(requestedSpaceId ? { spaceId: requestedSpaceId } : {}),
},
orderBy: [{ space: { isDefault: "desc" } }, { createdAt: "asc" }, { id: "asc" }],
include: { member: { include: { user: true } } },
});
if (!membership) {
throw new IsolationError("No personal space");
}
const settings = await prisma.deploymentSettings.findUnique({
where: { id: "default" },
});
return {
userId: membership.userId,
spaceId: membership.spaceId,
email: membership.member.user.email,
isDeploymentOwner: settings?.ownerUserId === membership.userId,
};
}
export function scoped<T extends { spaceId: string; userId?: string }>(
actor: Actor,
record: T | null,
): T {
if (!record || record.spaceId !== actor.spaceId) {
throw new IsolationError();
}
if (record.userId && record.userId !== actor.userId) {
throw new IsolationError();
}
return record;
}