Merge pull request #65 from cynarAI/cursor/teaching-takeover-clobber-5453
fix(api): block takeover while teaching on sibling Team Computer bot
This commit is contained in:
commit
fbf9a6cd03
@ -172,7 +172,11 @@ import {
|
|||||||
readServerUpdateStatus,
|
readServerUpdateStatus,
|
||||||
UpdaterProxyError,
|
UpdaterProxyError,
|
||||||
} from "./server-update.js";
|
} from "./server-update.js";
|
||||||
import { assertTeachingSendAllowed, createTaughtSkillsService } from "./taught-skills.js";
|
import {
|
||||||
|
assertTeachingSendAllowed,
|
||||||
|
assertTeachingTakeoverAllowed,
|
||||||
|
createTaughtSkillsService,
|
||||||
|
} from "./taught-skills.js";
|
||||||
import {
|
import {
|
||||||
isPeerRun,
|
isPeerRun,
|
||||||
loadAllMessages,
|
loadAllMessages,
|
||||||
@ -1809,6 +1813,12 @@ export function createRouter(deps: RouterDeps) {
|
|||||||
if (!bot.computer?.providerRef || bot.computer.state !== "running") {
|
if (!bot.computer?.providerRef || bot.computer.state !== "running") {
|
||||||
throw new ORPCError("BAD_REQUEST", { message: "computer must be running" });
|
throw new ORPCError("BAD_REQUEST", { message: "computer must be running" });
|
||||||
}
|
}
|
||||||
|
await assertTeachingTakeoverAllowed(
|
||||||
|
deps.prisma,
|
||||||
|
context.actor,
|
||||||
|
input.botId,
|
||||||
|
bot.computer.id,
|
||||||
|
);
|
||||||
if (hasActiveComputerControl(bot.computer) && bot.computer.controlBotId === bot.id) {
|
if (hasActiveComputerControl(bot.computer) && bot.computer.controlBotId === bot.id) {
|
||||||
await bindWaitingTakeoverToControl(deps, {
|
await bindWaitingTakeoverToControl(deps, {
|
||||||
spaceId: context.actor.spaceId,
|
spaceId: context.actor.spaceId,
|
||||||
|
|||||||
@ -110,6 +110,27 @@ export async function assertTeachingSendAllowed(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function assertTeachingTakeoverAllowed(
|
||||||
|
prisma: PrismaClient,
|
||||||
|
actor: Actor,
|
||||||
|
botId: string,
|
||||||
|
computerId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const teachingElsewhere = await prisma.taughtSkill.findFirst({
|
||||||
|
where: {
|
||||||
|
spaceId: actor.spaceId,
|
||||||
|
userId: actor.userId,
|
||||||
|
status: "recording",
|
||||||
|
botId: { not: botId },
|
||||||
|
bot: { computerId },
|
||||||
|
},
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (teachingElsewhere) {
|
||||||
|
throw new ORPCError("CONFLICT", { message: "Stop teaching first" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function cancelActiveRuns(
|
async function cancelActiveRuns(
|
||||||
deps: TaughtSkillsDeps,
|
deps: TaughtSkillsDeps,
|
||||||
_actor: Actor,
|
_actor: Actor,
|
||||||
|
|||||||
@ -694,6 +694,46 @@ describeWithDatabase("API authorization and resource isolation", () => {
|
|||||||
expect(ownerCrossBot.content).toBe(secret);
|
expect(ownerCrossBot.content).toBe(secret);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("blocks Team Computer takeover on another bot while teaching is recording", async () => {
|
||||||
|
const cookie = await signup(app, `teach-takeover-${stamp}@rakazo.test`, "Teach Takeover");
|
||||||
|
const teacher = await rpc<Bot>(app, cookie, "bots/create", botInput("Teacher"));
|
||||||
|
const sibling = await rpc<Bot>(app, cookie, "bots/create", botInput("Sibling"));
|
||||||
|
const [teacherRow, siblingRow] = await Promise.all([
|
||||||
|
handles.prisma.bot.findUniqueOrThrow({ where: { id: teacher.id } }),
|
||||||
|
handles.prisma.bot.findUniqueOrThrow({ where: { id: sibling.id } }),
|
||||||
|
]);
|
||||||
|
expect(teacherRow.computerId).toBe(siblingRow.computerId);
|
||||||
|
|
||||||
|
await rpc(app, cookie, "computer/boot", { botId: teacher.id });
|
||||||
|
const skill = await rpc<{ id: string; status: string }>(app, cookie, "skills/start", {
|
||||||
|
botId: teacher.id,
|
||||||
|
goal: "Demonstrate export",
|
||||||
|
});
|
||||||
|
expect(skill.status).toBe("recording");
|
||||||
|
|
||||||
|
const blockedTakeover = await raw(app, cookie, "computer/takeover", { botId: sibling.id });
|
||||||
|
expect(blockedTakeover.status).toBe(409);
|
||||||
|
expect(await blockedTakeover.text()).toMatch(/Stop teaching first/i);
|
||||||
|
|
||||||
|
await rpc(app, cookie, "computer/input", {
|
||||||
|
botId: teacher.id,
|
||||||
|
kind: "key",
|
||||||
|
payload: { key: "x" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const stopped = await rpc<{ status: string }>(app, cookie, "skills/stop", {
|
||||||
|
skillId: skill.id,
|
||||||
|
});
|
||||||
|
expect(stopped.status).toBe("draft");
|
||||||
|
|
||||||
|
const computer = await handles.prisma.computer.findUniqueOrThrow({
|
||||||
|
where: { id: teacherRow.computerId! },
|
||||||
|
select: { controlHolder: true, controlBotId: true },
|
||||||
|
});
|
||||||
|
expect(computer.controlHolder).toBe("bot");
|
||||||
|
expect(computer.controlBotId).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it("scopes Team Computer export to the exporting member's bot workspace in a shared Space", async () => {
|
it("scopes Team Computer export to the exporting member's bot workspace in a shared Space", async () => {
|
||||||
const owner = await signup(app, `export-owner-${stamp}@rakazo.test`, "Export Owner");
|
const owner = await signup(app, `export-owner-${stamp}@rakazo.test`, "Export Owner");
|
||||||
const member = await signup(app, `export-member-${stamp}@rakazo.test`, "Export Member");
|
const member = await signup(app, `export-member-${stamp}@rakazo.test`, "Export Member");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user