evOo-local/packages/adapters/src/executor.test.ts
cynarAI ff11f1d151 fix(adapters): scope agent secrets to run owner in shared Spaces (reland #47) (#70)
The executor loaded every agent secret in a Space and injected decrypted
values into shell environment variables for any member's bot run. Restore
agentSecretScopeForRun scoping accidentally dropped in 0a8a831.

Add an executor test that fails when findMany is scoped only by spaceId.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 11:22:14 +02:00

1677 lines
56 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { MessageBlock } from "@rakazo/contracts";
import { ONCE_ROUTINE_CRON } from "@rakazo/core";
import type { PrismaClient } from "@rakazo/db";
import { describe, expect, it, vi } from "vitest";
import {
appendToolCompletionAudit,
createRunExecutor,
createRunWorkspaceCheckpoint,
loadCurrentTurnImages,
runNotificationsEnabled,
selectBuiltinToolsForRun,
toolCompletionAuditPayload,
toolCompletionFromResult,
} from "./executor.js";
import { agentSecretScopeForRun } from "./agent-environment.js";
import { missingTurnImagesInstruction, settleSteeringAttachmentLoads } from "./executor-attachments.js";
import { threadContextForRun } from "./executor-completion.js";
import { serializeModelSecret } from "./pi-oauth.js";
describe("tool completion audit", () => {
it("records result metadata without persisting tool contents", () => {
const payload = toolCompletionAuditPayload({
name: "computer_observe",
executionId: "call-1",
durationMs: 12.6,
result: {
kind: "agent_tool_result",
content: [
{ type: "text", text: "Visible window" },
{ type: "image", data: "image-bytes", mimeType: "image/png" },
],
details: {
frameId: "frame-1",
capturedAt: "2026-09-07T00:00:00.000Z",
width: 1280,
height: 720,
activeWindow: { title: "Private window" },
},
},
});
expect(payload).toEqual({
name: "computer_observe",
executionId: "call-1",
durationMs: 13,
outcome: "succeeded",
contentTypes: ["text", "image"],
frameId: "frame-1",
capturedAt: "2026-09-07T00:00:00.000Z",
width: 1280,
height: 720,
});
expect(payload).not.toHaveProperty("content");
expect(payload).not.toHaveProperty("activeWindow");
});
it("does not fail the run when the audit append fails", async () => {
const append = vi.fn().mockRejectedValue(new Error("database unavailable"));
await expect(
appendToolCompletionAudit(
{ events: { append } },
{ spaceId: "space-1", threadId: "thread-1", botId: "bot-1", runId: "run-1" },
{
name: "destination.write",
executionId: "call-1",
durationMs: 4,
error: new Error("Bearer secret-token"),
},
["secret-token"],
),
).resolves.toBeUndefined();
expect(append).toHaveBeenCalledWith(
expect.objectContaining({
type: "agent.tool.completed",
payload: expect.objectContaining({
outcome: "error",
error: "Bearer [redacted]",
}),
}),
);
});
it("records rejected scripted tool results as errors", () => {
const completion = toolCompletionFromResult(
{ name: "destination.write", executionId: "call-1", durationMs: 4 },
{ error: "destination rejected the record" },
);
expect(completion).toEqual({
name: "destination.write",
executionId: "call-1",
durationMs: 4,
error: "destination rejected the record",
paused: false,
});
expect(toolCompletionAuditPayload(completion)).toMatchObject({
outcome: "error",
error: "destination rejected the record",
});
expect(completion).not.toHaveProperty("result");
});
});
describe("run workspace checkpoint", () => {
it("skips clean turns and flushes once after a mutation", async () => {
const persist = vi.fn(async () => undefined);
const checkpoint = createRunWorkspaceCheckpoint(persist);
await expect(checkpoint.flush()).resolves.toBe(false);
checkpoint.markDirty();
await expect(checkpoint.flush()).resolves.toBe(true);
await expect(checkpoint.flush()).resolves.toBe(false);
expect(persist).toHaveBeenCalledOnce();
});
it("marks materialized steering files for checkpointing", async () => {
const persist = vi.fn(async () => undefined);
const checkpoint = createRunWorkspaceCheckpoint(persist);
checkpoint.markFiles([]);
await expect(checkpoint.flush()).resolves.toBe(false);
checkpoint.markFiles([{ path: "attachments/result.txt" }]);
await expect(checkpoint.flush()).resolves.toBe(true);
});
it("keeps a failed checkpoint dirty for retry", async () => {
const persist = vi
.fn<() => Promise<void>>()
.mockRejectedValueOnce(new Error("checkpoint failed"))
.mockResolvedValueOnce(undefined);
const checkpoint = createRunWorkspaceCheckpoint(persist);
checkpoint.markDirty();
await expect(checkpoint.flush()).rejects.toThrow("checkpoint failed");
await expect(checkpoint.flush()).resolves.toBe(true);
expect(persist).toHaveBeenCalledTimes(2);
});
});
describe("run tool selection", () => {
it.each([
[false, false],
[false, true],
[true, false],
[true, true],
])("gates page browsers (%s) independently of cloud agents (%s)", (page, cloud) => {
const names = selectBuiltinToolsForRun({
graphicalToolsAllowed: false,
pageBrowserAllowed: page,
cloudAgentEnabled: cloud,
groupId: null,
trigger: "message",
semanticMemoryEnabled: false,
messagingChannelRun: false,
}).map((tool) => tool.name);
expect(names.includes("browser_snapshot")).toBe(page);
expect(names.includes("cloud_agent_status")).toBe(cloud);
expect(names).not.toContain("computer_act");
});
const toolNames = (
trigger: string,
groupId: string | null = null,
options?: { graphicalToolsAllowed?: boolean; pageBrowserAllowed?: boolean },
) =>
selectBuiltinToolsForRun({
graphicalToolsAllowed: options?.graphicalToolsAllowed ?? true,
pageBrowserAllowed: options?.pageBrowserAllowed ?? true,
groupId,
trigger,
semanticMemoryEnabled: false,
messagingChannelRun: false,
}).map((tool) => tool.name);
it("keeps page browser tools without vision, and hides them without a graphical computer", () => {
const withPage = toolNames("message", null, {
graphicalToolsAllowed: false,
pageBrowserAllowed: true,
});
expect(withPage).toEqual(
expect.arrayContaining(["browser_navigate", "browser_snapshot", "browser_act"]),
);
expect(withPage).not.toEqual(expect.arrayContaining(["computer_observe", "computer_act"]));
const withoutPage = toolNames("message", null, {
graphicalToolsAllowed: true,
pageBrowserAllowed: false,
});
expect(withoutPage).not.toEqual(
expect.arrayContaining(["browser_navigate", "browser_snapshot", "browser_act"]),
);
expect(withoutPage).toEqual(expect.arrayContaining(["computer_observe", "computer_act"]));
});
it("withholds schedule creation only from routine-triggered runs", () => {
expect(toolNames("routine")).not.toContain("schedule_create");
expect(toolNames("routine")).toEqual(
expect.arrayContaining(["schedule_list", "schedule_cancel"]),
);
expect(toolNames("user")).toContain("schedule_create");
});
it("keeps schedule tools in group chats and still blocks create on routines", () => {
expect(toolNames("user", "group-1")).toEqual(
expect.arrayContaining(["schedule_create", "schedule_list", "schedule_cancel"]),
);
expect(toolNames("routine", "group-1")).not.toContain("schedule_create");
expect(toolNames("routine", "group-1")).toEqual(
expect.arrayContaining(["schedule_list", "schedule_cancel"]),
);
});
});
describe("steering attachment hydration", () => {
it("keeps successful attachment parts when another part is unavailable", async () => {
const imageBlocks: MessageBlock[] = [
{ kind: "image", artifactId: "image-1", name: "one.png", mimeType: "image/png" },
{ kind: "image", artifactId: "image-2", name: "two.png", mimeType: "image/png" },
];
const withoutImage = await settleSteeringAttachmentLoads(
Promise.reject(new Error("image missing")),
Promise.resolve(["attachment.pdf"]),
);
expect(withoutImage).toMatchObject({ images: undefined, files: ["attachment.pdf"] });
expect(withoutImage.unavailableInstruction).toContain("do not guess its contents");
const withoutFile = await settleSteeringAttachmentLoads(
Promise.resolve(["image.png"]),
Promise.reject(new Error("file missing")),
);
expect(withoutFile).toMatchObject({ images: ["image.png"], files: [] });
expect(withoutFile.unavailableInstruction).toContain("do not guess its contents");
const partiallyHydratedImages = await loadCurrentTurnImages(
{
artifacts: { get: vi.fn(async () => new Uint8Array([1])) },
prisma: {
artifact: {
findMany: vi.fn(async () => [{ id: "image-1", storageKey: "one.png" }]),
},
},
} as never,
imageBlocks,
{
operationId: "run-1",
traceId: "run-1",
spaceId: "space-1",
userId: "user-1",
botId: "bot-1",
runId: "run-1",
signal: new AbortController().signal,
},
);
expect(partiallyHydratedImages).toHaveLength(1);
const withPartiallyMissingImages = await settleSteeringAttachmentLoads(
Promise.resolve(partiallyHydratedImages),
Promise.resolve([]),
imageBlocks,
);
expect(withPartiallyMissingImages).toMatchObject({
images: partiallyHydratedImages,
files: [],
});
expect(withPartiallyMissingImages.unavailableInstruction).toContain(
"do not guess its contents",
);
const withAllImagesMissing = await settleSteeringAttachmentLoads(
Promise.resolve(undefined),
Promise.resolve([]),
imageBlocks.slice(0, 1),
);
expect(withAllImagesMissing.unavailableInstruction).toContain("do not guess its contents");
const withoutMissingImages = await settleSteeringAttachmentLoads(
Promise.resolve(["image.png"]),
Promise.resolve([]),
imageBlocks.slice(0, 1),
);
expect(withoutMissingImages.unavailableInstruction).toBe("");
const withoutExpectedImages = await settleSteeringAttachmentLoads(
Promise.resolve(undefined),
Promise.resolve([]),
);
expect(withoutExpectedImages.unavailableInstruction).toBe("");
});
it("propagates cancellation while steering attachments settle", async () => {
const controller = new AbortController();
const cancellation = new Error("cancelled");
controller.abort();
await expect(
settleSteeringAttachmentLoads(
Promise.reject(cancellation),
Promise.resolve([]),
undefined,
controller.signal,
),
).rejects.toBe(cancellation);
});
it("treats unreadable image bytes as missing instead of failing hydration", async () => {
const blocks: MessageBlock[] = [
{ kind: "image", artifactId: "image-1", name: "one.png", mimeType: "image/png" },
{ kind: "image", artifactId: "image-2", name: "two.png", mimeType: "image/png" },
];
const images = await loadCurrentTurnImages(
{
artifacts: {
get: vi.fn(async (storageKey: string) => {
if (storageKey === "bad.png") throw new Error("read failed");
return new Uint8Array([1]);
}),
},
prisma: {
artifact: {
findMany: vi.fn(async () => [
{ id: "image-1", storageKey: "one.png" },
{ id: "image-2", storageKey: "bad.png" },
]),
},
},
} as never,
blocks,
{
operationId: "run-1",
traceId: "run-1",
spaceId: "space-1",
userId: "user-1",
botId: "bot-1",
runId: "run-1",
signal: new AbortController().signal,
},
);
expect(images).toHaveLength(1);
expect(missingTurnImagesInstruction(blocks, images)).toContain("do not guess its contents");
const settled = await settleSteeringAttachmentLoads(
Promise.resolve(images),
Promise.resolve([]),
blocks,
);
expect(settled.unavailableInstruction).toContain("do not guess its contents");
});
it("does not swallow image hydration cancellation", async () => {
const controller = new AbortController();
const cancellation = new Error("cancelled");
controller.abort(cancellation);
await expect(
loadCurrentTurnImages(
{
artifacts: { get: vi.fn(async () => Promise.reject(cancellation)) },
prisma: {
artifact: {
findMany: vi.fn(async () => [{ id: "image-1", storageKey: "one.png" }]),
},
},
} as never,
[{ kind: "image", artifactId: "image-1", name: "one.png", mimeType: "image/png" }],
{
operationId: "run-1",
traceId: "run-1",
spaceId: "space-1",
userId: "user-1",
botId: "bot-1",
runId: "run-1",
signal: controller.signal,
},
),
).rejects.toBe(cancellation);
});
it("warns when an ordinary turn expects more images than were loaded", () => {
const blocks: MessageBlock[] = [
{ kind: "image", artifactId: "image-1", name: "one.png", mimeType: "image/png" },
{ kind: "image", artifactId: "image-2", name: "two.png", mimeType: "image/png" },
];
expect(missingTurnImagesInstruction(blocks, [{ name: "one.png" } as never])).toContain(
"do not guess its contents",
);
expect(
missingTurnImagesInstruction(blocks, [
{ name: "one.png" } as never,
{ name: "two.png" } as never,
]),
).toBe("");
expect(missingTurnImagesInstruction(blocks, undefined)).toContain("do not guess its contents");
expect(missingTurnImagesInstruction(undefined, undefined)).toBe("");
});
});
function modelPreference({
provider,
secretId,
modelId,
isDefault,
}: {
provider: string;
secretId: string;
modelId: string;
isDefault: boolean;
}) {
const now = new Date("2026-08-30T00:00:00.000Z");
return {
id: `preference-${provider}`,
isDefault,
modelId,
credential: {
id: `credential-${provider}`,
userId: "user-1",
provider,
label: provider,
secretId,
createdAt: now,
updatedAt: now,
},
};
}
describe("run notification preference", () => {
it("silences direct messages but leaves group notifications enabled", async () => {
let source: { bot: { notifyOnFinish: boolean }; thread: { groupId: string | null } } | null = {
bot: { notifyOnFinish: false },
thread: { groupId: null },
};
const findFirst = vi.fn(async () => source);
const prisma = { run: { findFirst } } as unknown as PrismaClient;
await expect(
runNotificationsEnabled(prisma, {
botId: "bot-1",
threadId: "thread-1",
spaceId: "workspace-1",
userId: "user-1",
}),
).resolves.toBe(false);
expect(findFirst).toHaveBeenCalledWith({
where: {
botId: "bot-1",
threadId: "thread-1",
spaceId: "workspace-1",
userId: "user-1",
},
select: {
bot: { select: { notifyOnFinish: true } },
thread: { select: { groupId: true } },
},
});
source = { bot: { notifyOnFinish: false }, thread: { groupId: "group-1" } };
await expect(
runNotificationsEnabled(prisma, {
botId: "bot-1",
threadId: "thread-1",
spaceId: "workspace-1",
userId: "user-1",
}),
).resolves.toBe(true);
});
});
describe("createRunExecutor", () => {
it("excludes private summaries and memory tools from group messaging runs", () => {
const messages = [{ role: "user", content: "Group request" }];
expect(
threadContextForRun(
"messaging",
{
messages,
summary: "Private test detail",
historyCompactedUpToSeq: 12,
},
true,
),
).toEqual({
messages,
summary: null,
historyCompactedUpToSeq: null,
includeSemanticRecall: false,
});
const tools = selectBuiltinToolsForRun({
graphicalToolsAllowed: false,
groupId: null,
trigger: "messaging",
semanticMemoryEnabled: true,
messagingChannelRun: true,
}).map((tool) => tool.name);
expect(tools).not.toContain("recall_memory");
expect(tools).not.toContain("remember");
expect(tools).not.toContain("save_memory");
expect(tools.some((tool) => tool.startsWith("scratchpad_"))).toBe(false);
expect(tools).toContain("web_fetch");
});
it("isolates routine runs from every thread-history source", () => {
const threadContext = {
messages: [{ role: "user", content: "Create this routine" }],
summary: "The user just configured this routine.",
historyCompactedUpToSeq: 4,
};
expect(threadContextForRun("routine", threadContext, false)).toEqual({
messages: [],
summary: null,
historyCompactedUpToSeq: null,
includeSemanticRecall: false,
});
expect(threadContextForRun("user", threadContext, false)).toEqual({
...threadContext,
includeSemanticRecall: true,
});
});
it("deactivates one-shot routines after wake without scheduling another wakeup", async () => {
const scheduledAt = new Date(Date.now() - 1_000);
const enqueue = vi.fn(async () => undefined);
const cancel = vi.fn(async () => undefined);
const append = vi.fn(async () => undefined);
const updateMany = vi.fn(async () => ({ count: 1 }));
const taskCreate = vi.fn(async () => ({ id: "task-1" }));
const runCreate = vi.fn(async () => ({ id: "run-1" }));
const prisma = {
routine: {
findUnique: vi.fn(async () => ({
id: "routine-1",
spaceId: "ws-1",
botId: "bot-1",
userId: "user-1",
prompt: "say hi",
crons: [ONCE_ROUTINE_CRON],
timezone: "UTC",
active: true,
nextRunAt: scheduledAt,
threadId: "group-thread-1",
})),
},
bot: {
findUnique: vi.fn(async () => ({
id: "bot-1",
thread: { id: "thread-1" },
})),
},
thread: {
findFirst: vi.fn(async () => ({ id: "group-thread-1" })),
},
agentSkill: {
findMany: vi.fn(async () => []),
},
$transaction: vi.fn(async (callback: (tx: unknown) => Promise<unknown>) =>
callback({
routine: { updateMany },
task: { create: taskCreate },
run: { create: runCreate },
}),
),
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
jobs: { enqueue, cancel, close: vi.fn(async () => undefined) },
events: { append },
} as unknown as Parameters<typeof createRunExecutor>[0]);
await executor.wakeRoutine("routine-1", scheduledAt.toISOString());
expect(updateMany).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({ active: false, nextRunAt: null }),
}),
);
expect(cancel).toHaveBeenCalledWith("routine:routine-1");
expect(enqueue).toHaveBeenCalledTimes(1);
expect(enqueue).toHaveBeenCalledWith(expect.objectContaining({ name: "run.continue" }));
expect(taskCreate).toHaveBeenCalledWith(
expect.objectContaining({ data: expect.objectContaining({ threadId: "group-thread-1" }) }),
);
expect(runCreate).toHaveBeenCalledWith(
expect.objectContaining({ data: expect.objectContaining({ threadId: "group-thread-1" }) }),
);
expect(append).toHaveBeenCalledWith(
expect.objectContaining({
type: "routine.fired",
runId: "run-1",
threadId: "group-thread-1",
}),
);
});
it("wakes a tool-created group routine into the group thread, not the bot DM", async () => {
const scheduledAt = new Date(Date.now() - 1_000);
const taskCreate = vi.fn(async () => ({ id: "task-1" }));
const runCreate = vi.fn(async () => ({ id: "run-1" }));
const append = vi.fn(async () => undefined);
const findFirst = vi.fn(async () => ({ id: "group-thread-1" }));
const prisma = {
routine: {
findUnique: vi.fn(async () => ({
id: "routine-1",
spaceId: "ws-1",
botId: "bot-1",
userId: "user-1",
prompt: "remind the group",
crons: [ONCE_ROUTINE_CRON],
timezone: "UTC",
active: true,
nextRunAt: scheduledAt,
threadId: "group-thread-1",
})),
},
bot: {
findUnique: vi.fn(async () => ({
id: "bot-1",
thread: { id: "dm-thread-1" },
})),
},
thread: { findFirst },
agentSkill: { findMany: vi.fn(async () => []) },
$transaction: vi.fn(async (callback: (tx: unknown) => Promise<unknown>) =>
callback({
routine: { updateMany: vi.fn(async () => ({ count: 1 })) },
task: { create: taskCreate },
run: { create: runCreate },
}),
),
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
jobs: {
enqueue: vi.fn(async () => undefined),
cancel: vi.fn(async () => undefined),
close: vi.fn(async () => undefined),
},
events: { append },
} as unknown as Parameters<typeof createRunExecutor>[0]);
await executor.wakeRoutine("routine-1", scheduledAt.toISOString());
expect(findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({ id: "group-thread-1", spaceId: "ws-1" }),
}),
);
expect(taskCreate).toHaveBeenCalledWith(
expect.objectContaining({ data: expect.objectContaining({ threadId: "group-thread-1" }) }),
);
expect(runCreate).toHaveBeenCalledWith(
expect.objectContaining({ data: expect.objectContaining({ threadId: "group-thread-1" }) }),
);
expect(append).toHaveBeenCalledWith(
expect.objectContaining({ type: "routine.fired", threadId: "group-thread-1" }),
);
});
it("wakes a tool-created 1:1 routine into the bot DM thread", async () => {
const scheduledAt = new Date(Date.now() - 1_000);
const taskCreate = vi.fn(async () => ({ id: "task-1" }));
const runCreate = vi.fn(async () => ({ id: "run-1" }));
const append = vi.fn(async () => undefined);
const findFirst = vi.fn(async () => ({ id: "dm-thread-1" }));
const prisma = {
routine: {
findUnique: vi.fn(async () => ({
id: "routine-1",
spaceId: "ws-1",
botId: "bot-1",
userId: "user-1",
prompt: "remind me",
crons: [ONCE_ROUTINE_CRON],
timezone: "UTC",
active: true,
nextRunAt: scheduledAt,
threadId: "dm-thread-1",
})),
},
bot: {
findUnique: vi.fn(async () => ({
id: "bot-1",
thread: { id: "dm-thread-1" },
})),
},
thread: { findFirst },
agentSkill: { findMany: vi.fn(async () => []) },
$transaction: vi.fn(async (callback: (tx: unknown) => Promise<unknown>) =>
callback({
routine: { updateMany: vi.fn(async () => ({ count: 1 })) },
task: { create: taskCreate },
run: { create: runCreate },
}),
),
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
jobs: {
enqueue: vi.fn(async () => undefined),
cancel: vi.fn(async () => undefined),
close: vi.fn(async () => undefined),
},
events: { append },
} as unknown as Parameters<typeof createRunExecutor>[0]);
await executor.wakeRoutine("routine-1", scheduledAt.toISOString());
expect(findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({ id: "dm-thread-1", spaceId: "ws-1" }),
}),
);
expect(taskCreate).toHaveBeenCalledWith(
expect.objectContaining({ data: expect.objectContaining({ threadId: "dm-thread-1" }) }),
);
expect(runCreate).toHaveBeenCalledWith(
expect.objectContaining({ data: expect.objectContaining({ threadId: "dm-thread-1" }) }),
);
expect(append).toHaveBeenCalledWith(
expect.objectContaining({ type: "routine.fired", threadId: "dm-thread-1" }),
);
});
it("expands @skill mentions in the routine prompt at fire time", async () => {
const scheduledAt = new Date(Date.now() - 1_000);
const enqueue = vi.fn(async () => undefined);
let createdPrompt = "";
const taskCreate = vi.fn(async (args: { data: { prompt: string } }) => {
createdPrompt = args.data.prompt;
return { id: "task-1" };
});
const skillContent = `---
name: Daily standup
description: Prepare standup notes
---
1. Summarize wins.
`;
const prisma = {
routine: {
findUnique: vi.fn(async () => ({
id: "routine-1",
spaceId: "ws-1",
botId: "bot-1",
userId: "user-1",
prompt: "Run @Daily standup, then email me",
crons: [ONCE_ROUTINE_CRON],
timezone: "UTC",
active: true,
nextRunAt: scheduledAt,
})),
},
bot: {
findUnique: vi.fn(async () => ({
id: "bot-1",
thread: { id: "thread-1" },
})),
},
agentSkill: {
findMany: vi.fn(async () => [
{
id: "skill-1",
name: "Daily standup",
description: "Prepare standup notes",
content: skillContent,
source: "user",
},
]),
},
$transaction: vi.fn(async (callback: (tx: unknown) => Promise<unknown>) =>
callback({
routine: { updateMany: vi.fn(async () => ({ count: 1 })) },
task: { create: taskCreate },
run: { create: vi.fn(async () => ({ id: "run-1" })) },
}),
),
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
jobs: { enqueue, cancel: vi.fn(async () => undefined), close: vi.fn(async () => undefined) },
events: { append: vi.fn(async () => undefined) },
} as unknown as Parameters<typeof createRunExecutor>[0]);
await executor.wakeRoutine("routine-1", scheduledAt.toISOString());
expect(createdPrompt).toContain("Use skill: Daily standup");
expect(createdPrompt).toContain("Summarize wins");
expect(createdPrompt).not.toMatch(/@Daily standup/);
});
it("still continues the run when routine.fired append fails", async () => {
const scheduledAt = new Date(Date.now() - 1_000);
const enqueue = vi.fn(async () => undefined);
const cancel = vi.fn(async () => undefined);
const append = vi.fn(async () => {
throw new Error("append failed");
});
const updateMany = vi.fn(async () => ({ count: 1 }));
const prisma = {
routine: {
findUnique: vi.fn(async () => ({
id: "routine-1",
spaceId: "ws-1",
botId: "bot-1",
userId: "user-1",
prompt: "say hi",
crons: [ONCE_ROUTINE_CRON],
timezone: "UTC",
active: true,
nextRunAt: scheduledAt,
lastRunAt: null,
})),
},
bot: {
findUnique: vi.fn(async () => ({
id: "bot-1",
thread: { id: "thread-1" },
})),
},
agentSkill: {
findMany: vi.fn(async () => []),
},
$transaction: vi.fn(async (callback: (tx: unknown) => Promise<unknown>) =>
callback({
routine: { updateMany },
task: { create: vi.fn(async () => ({ id: "task-1" })) },
run: { create: vi.fn(async () => ({ id: "run-1", taskId: "task-1" })) },
}),
),
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
jobs: { enqueue, cancel, close: vi.fn(async () => undefined) },
events: { append },
} as unknown as Parameters<typeof createRunExecutor>[0]);
await expect(
executor.wakeRoutine("routine-1", scheduledAt.toISOString()),
).resolves.toBeUndefined();
expect(enqueue).toHaveBeenCalledWith(expect.objectContaining({ name: "run.continue" }));
expect(cancel).toHaveBeenCalledWith("routine:routine-1");
});
it("restores the routine claim when run.continue enqueue fails", async () => {
const scheduledAt = new Date(Date.now() - 1_000);
const previousLastRunAt = new Date(Date.now() - 60_000);
const enqueue = vi.fn(async () => {
throw new Error("enqueue failed");
});
const claimUpdateMany = vi.fn(async () => ({ count: 1 }));
const restoreUpdateMany = vi.fn(async () => ({ count: 1 }));
const deleteRunMany = vi.fn(async () => ({ count: 1 }));
const deleteTaskMany = vi.fn(async () => ({ count: 1 }));
let transactionCalls = 0;
const prisma = {
routine: {
findUnique: vi.fn(async () => ({
id: "routine-1",
spaceId: "ws-1",
botId: "bot-1",
userId: "user-1",
prompt: "say hi",
crons: [ONCE_ROUTINE_CRON],
timezone: "UTC",
active: true,
nextRunAt: scheduledAt,
lastRunAt: previousLastRunAt,
})),
},
bot: {
findUnique: vi.fn(async () => ({
id: "bot-1",
thread: { id: "thread-1" },
})),
},
agentSkill: {
findMany: vi.fn(async () => []),
},
$transaction: vi.fn(async (callback: (tx: unknown) => Promise<unknown>) => {
transactionCalls += 1;
if (transactionCalls === 1) {
return callback({
routine: { updateMany: claimUpdateMany },
task: { create: vi.fn(async () => ({ id: "task-1" })) },
run: { create: vi.fn(async () => ({ id: "run-1", taskId: "task-1" })) },
});
}
return callback({
routine: { updateMany: restoreUpdateMany },
task: { deleteMany: deleteTaskMany },
run: { deleteMany: deleteRunMany },
});
}),
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
jobs: { enqueue, cancel: vi.fn(async () => undefined), close: vi.fn(async () => undefined) },
events: { append: vi.fn(async () => undefined) },
} as unknown as Parameters<typeof createRunExecutor>[0]);
await expect(executor.wakeRoutine("routine-1", scheduledAt.toISOString())).rejects.toThrow(
"enqueue failed",
);
expect(deleteRunMany).toHaveBeenCalledWith({ where: { id: "run-1", status: "queued" } });
expect(deleteTaskMany).toHaveBeenCalledWith({ where: { id: "task-1", status: "queued" } });
expect(restoreUpdateMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({ id: "routine-1", active: false, nextRunAt: null }),
data: expect.objectContaining({
nextRunAt: scheduledAt,
active: true,
lastRunAt: previousLastRunAt,
}),
}),
);
});
it("consumes a persisted takeover checkpoint when claiming the run", async () => {
const updateMany = vi.fn(async () => ({ count: 0 }));
const prisma = {
run: {
findUnique: vi.fn(async () => ({
id: "run-1",
botId: "bot-1",
status: "queued",
checkpoint: "takeover-skipped",
leaseFence: 0,
})),
updateMany,
},
} as unknown as PrismaClient;
const executor = createRunExecutor({ prisma } as Parameters<typeof createRunExecutor>[0]);
await executor.continueRun("run-1", "worker-1");
expect(updateMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
id: "run-1",
status: "queued",
checkpoint: "takeover-skipped",
}),
data: expect.objectContaining({ checkpoint: null }),
}),
);
});
it("does not claim a waiting takeover after a concurrent release writes the checkpoint", async () => {
let row: { status: string; checkpoint: string | null } = {
status: "waiting_takeover",
checkpoint: null,
};
const matchesClaim = (
where: {
status?: string | { in: string[] };
checkpoint?: string | null;
OR?: Array<{ status?: string | { in: string[] } }>;
},
current: { status: string; checkpoint: string | null },
): boolean => {
if (typeof where.status === "string" && where.status !== current.status) return false;
if (where.status && typeof where.status === "object" && "in" in where.status) {
if (!where.status.in.includes(current.status)) return false;
}
if ("checkpoint" in where && where.checkpoint !== current.checkpoint) return false;
if (where.OR) return where.OR.some((clause) => matchesClaim(clause, current));
return true;
};
const updateMany = vi.fn(async (args: { where: Parameters<typeof matchesClaim>[0] }) => {
row = { status: "queued", checkpoint: "takeover" };
return { count: matchesClaim(args.where, row) ? 1 : 0 };
});
const prisma = {
run: {
findUnique: vi.fn(async () => ({
id: "run-1",
botId: "bot-1",
status: "waiting_takeover",
checkpoint: null,
leaseFence: 0,
})),
updateMany,
},
} as unknown as PrismaClient;
const executor = createRunExecutor({ prisma } as Parameters<typeof createRunExecutor>[0]);
await executor.continueRun("run-1", "worker-1");
expect(updateMany).toHaveBeenCalledTimes(1);
expect(updateMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
status: "waiting_takeover",
checkpoint: null,
}),
}),
);
expect(row).toEqual({ status: "queued", checkpoint: "takeover" });
});
it("restores a takeover checkpoint when a switching computer requeues the run", async () => {
const updateMany = vi.fn(
async (args: {
where: { checkpoint?: string | null | { in: string[] } };
data: { status?: string; checkpoint?: string | null };
}) => {
if (args.data.status === "leased" || args.data.status === "running") return { count: 1 };
if (args.where.checkpoint && typeof args.where.checkpoint === "object") return { count: 0 };
return { count: 1 };
},
);
const enqueue = vi.fn(async () => undefined);
const prisma = {
run: {
findUnique: vi.fn(async () => ({
id: "run-1",
botId: "bot-1",
status: "queued",
checkpoint: "takeover-skipped",
leaseFence: 0,
})),
findUniqueOrThrow: vi.fn(async () => ({ status: "leased", startedAt: null })),
updateMany,
},
bot: {
findUniqueOrThrow: vi.fn(async () => ({
computerId: "computer-1",
computerSwitching: true,
})),
},
} as unknown as PrismaClient;
const executor = createRunExecutor({ prisma, jobs: { enqueue } } as unknown as Parameters<
typeof createRunExecutor
>[0]);
await executor.continueRun("run-1", "worker-1");
expect(updateMany).toHaveBeenLastCalledWith(
expect.objectContaining({
where: expect.objectContaining({ checkpoint: null }),
data: expect.objectContaining({
status: "queued",
checkpoint: "takeover-skipped",
}),
}),
);
expect(enqueue).toHaveBeenCalledOnce();
});
it("preserves a concurrent release checkpoint when a held continue requeues", async () => {
let checkpoint: string | null = null;
const updateMany = vi.fn(
async (args: {
where: { checkpoint?: string | null | { in: string[] } };
data: { status?: string; checkpoint?: string | null };
}) => {
if (args.data.status === "leased") {
checkpoint = null;
return { count: 1 };
}
if (args.data.status === "running") {
checkpoint = "takeover";
return { count: 1 };
}
if (args.where.checkpoint && typeof args.where.checkpoint === "object") {
return { count: args.where.checkpoint.in.includes(checkpoint ?? "") ? 1 : 0 };
}
return { count: args.where.checkpoint === null && checkpoint === null ? 1 : 0 };
},
);
const enqueue = vi.fn(async () => undefined);
const prisma = {
run: {
findUnique: vi.fn(async () => ({
id: "run-1",
botId: "bot-1",
status: "waiting_takeover",
checkpoint: null,
leaseFence: 0,
})),
findUniqueOrThrow: vi.fn(async () => ({ status: "leased", startedAt: null })),
updateMany,
},
bot: {
findUniqueOrThrow: vi.fn(async () => ({
computerId: "computer-1",
computerSwitching: true,
})),
},
} as unknown as PrismaClient;
const executor = createRunExecutor({ prisma, jobs: { enqueue } } as unknown as Parameters<
typeof createRunExecutor
>[0]);
await executor.continueRun("run-1", "worker-1");
expect(checkpoint).toBe("takeover");
expect(enqueue).toHaveBeenCalledOnce();
expect(updateMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
checkpoint: { in: ["takeover", "takeover-skipped"] },
}),
data: expect.objectContaining({ status: "queued" }),
}),
);
expect(updateMany.mock.calls.some((call) => call[0].data?.status === "waiting_takeover")).toBe(
false,
);
expect(
updateMany.mock.calls.some(
(call) => call[0].data?.status === "queued" && call[0].data?.checkpoint === null,
),
).toBe(false);
});
it("returns a held takeover to waiting_takeover when the computer is switching", async () => {
const updateMany = vi.fn(
async (args: {
where: { checkpoint?: string | null | { in: string[] } };
data: { status?: string; checkpoint?: string | null };
}) => {
if (args.data.status === "leased" || args.data.status === "running") return { count: 1 };
if (args.where.checkpoint && typeof args.where.checkpoint === "object") return { count: 0 };
return { count: 1 };
},
);
const enqueue = vi.fn(async () => undefined);
const prisma = {
run: {
findUnique: vi.fn(async () => ({
id: "run-1",
botId: "bot-1",
status: "waiting_takeover",
checkpoint: null,
leaseFence: 0,
})),
findUniqueOrThrow: vi.fn(async () => ({ status: "leased", startedAt: null })),
updateMany,
},
bot: {
findUniqueOrThrow: vi.fn(async () => ({
computerId: "computer-1",
computerSwitching: true,
})),
},
} as unknown as PrismaClient;
const executor = createRunExecutor({ prisma, jobs: { enqueue } } as unknown as Parameters<
typeof createRunExecutor
>[0]);
await executor.continueRun("run-1", "worker-1");
expect(updateMany).toHaveBeenLastCalledWith(
expect.objectContaining({
where: expect.objectContaining({ checkpoint: null }),
data: expect.objectContaining({
status: "waiting_takeover",
checkpoint: null,
}),
}),
);
expect(enqueue).toHaveBeenCalledOnce();
});
it("fails a run clearly without calling the real runtime when no model is configured", async () => {
let status = "queued";
const runtimeRun = vi.fn();
const finalizeRun = vi.fn(async () => {
status = "failed";
return { continuationRunId: null };
});
const run = {
id: "run-1",
botId: "bot-1",
threadId: "thread-1",
taskId: "task-1",
userId: "user-1",
spaceId: "ws-1",
status: "queued",
trigger: "user",
routineId: null,
sourceMessageId: null,
checkpoint: null,
leaseFence: 0,
};
const botLookup = vi.fn(async (args: { select?: { computerId?: boolean } }) =>
args.select?.computerId
? { computerId: "computer-1", computerSwitching: false }
: {
id: "bot-1",
name: "Assistant",
modelProvider: null,
modelId: null,
thinkingLevel: null,
memoryScope: "isolated",
computer: { id: "computer-1", scope: "private" },
},
);
const prisma = {
run: {
findUnique: vi.fn(async () => run),
findUniqueOrThrow: vi.fn(async () => ({ status: "leased", startedAt: null })),
updateMany: vi.fn(async () => ({ count: 1 })),
},
bot: { findUniqueOrThrow: botLookup },
computer: {
findUniqueOrThrow: vi.fn(async () => ({ scope: "private", state: "running" })),
},
attempt: {
create: vi.fn(async () => ({ id: "attempt-1" })),
updateMany: vi.fn(async () => ({ count: 1 })),
},
thread: {
findUniqueOrThrow: vi.fn(async () => ({
id: "thread-1",
groupId: null,
historyCompactionSummary: null,
historyCompactedUpToSeq: null,
historyCompactionGeneration: 0,
})),
},
message: { findMany: vi.fn(async () => []) },
task: { findUniqueOrThrow: vi.fn(async () => ({ id: "task-1", prompt: "hello" })) },
connection: { findMany: vi.fn(async () => []) },
spaceModelPreference: { findFirst: vi.fn(async () => null) },
userModelCredential: { findFirst: vi.fn(async () => null) },
deploymentSettings: { findUnique: vi.fn(async () => null) },
taughtSkill: { findMany: vi.fn(async () => []) },
agentSecret: { findMany: vi.fn(async () => []) },
agentSkill: { findMany: vi.fn(async () => []) },
scratchpadItem: { findMany: vi.fn(async () => []) },
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
runtime: {
describe: () => ({ capabilities: { scripted: false } }),
run: runtimeRun,
},
memoryProviders: { resolve: vi.fn(async () => null) },
memory: { read: vi.fn(async () => ({ documents: [] })) },
events: { append: vi.fn(async () => undefined), finalizeRun },
jobs: { enqueue: vi.fn(async () => undefined) },
secrets: [],
} as unknown as Parameters<typeof createRunExecutor>[0]);
await executor.continueRun("run-1", "worker-1");
expect(status).toBe("failed");
expect(finalizeRun).toHaveBeenCalledWith(
expect.objectContaining({
outcome: "failed",
error: "Connect a model in Settings before running bots.",
}),
);
expect(runtimeRun).not.toHaveBeenCalled();
});
it("scopes agent secrets to the run owner when loading the environment", async () => {
const agentSecretFindMany = vi.fn(async () => []);
const run = {
id: "run-1",
botId: "bot-1",
threadId: "thread-1",
taskId: "task-1",
userId: "member-user",
spaceId: "shared-ws",
status: "queued",
trigger: "user",
routineId: null,
sourceMessageId: null,
checkpoint: null,
leaseFence: 0,
};
const botLookup = vi.fn(async (args: { select?: { computerId?: boolean } }) =>
args.select?.computerId
? { computerId: "computer-1", computerSwitching: false }
: {
id: "bot-1",
name: "Assistant",
modelProvider: null,
modelId: null,
thinkingLevel: null,
memoryScope: "isolated",
computer: { id: "computer-1", scope: "private" },
},
);
const prisma = {
run: {
findUnique: vi.fn(async () => run),
findUniqueOrThrow: vi.fn(async () => ({ status: "leased", startedAt: null })),
updateMany: vi.fn(async () => ({ count: 1 })),
},
bot: { findUniqueOrThrow: botLookup },
computer: {
findUniqueOrThrow: vi.fn(async () => ({ scope: "private", state: "running" })),
},
attempt: {
create: vi.fn(async () => ({ id: "attempt-1" })),
updateMany: vi.fn(async () => ({ count: 1 })),
},
thread: {
findUniqueOrThrow: vi.fn(async () => ({
id: "thread-1",
groupId: null,
historyCompactionSummary: null,
historyCompactedUpToSeq: null,
historyCompactionGeneration: 0,
})),
},
message: { findMany: vi.fn(async () => []) },
task: { findUniqueOrThrow: vi.fn(async () => ({ id: "task-1", prompt: "hello" })) },
connection: { findMany: vi.fn(async () => []) },
spaceModelPreference: { findFirst: vi.fn(async () => null) },
userModelCredential: { findFirst: vi.fn(async () => null) },
deploymentSettings: { findUnique: vi.fn(async () => null) },
taughtSkill: { findMany: vi.fn(async () => []) },
agentSecret: { findMany: agentSecretFindMany },
agentSkill: { findMany: vi.fn(async () => []) },
scratchpadItem: { findMany: vi.fn(async () => []) },
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
runtime: {
describe: () => ({ capabilities: { scripted: false } }),
run: vi.fn(),
},
memoryProviders: { resolve: vi.fn(async () => null) },
memory: { read: vi.fn(async () => ({ documents: [] })) },
events: {
append: vi.fn(async () => undefined),
finalizeRun: vi.fn(async () => ({ continuationRunId: null })),
},
jobs: { enqueue: vi.fn(async () => undefined) },
secrets: [],
} as unknown as Parameters<typeof createRunExecutor>[0]);
await executor.continueRun("run-1", "worker-1");
expect(agentSecretFindMany).toHaveBeenCalledWith(
expect.objectContaining({
where: agentSecretScopeForRun("shared-ws", "member-user"),
}),
);
});
it("resolves a per-bot model override with that provider’s credential", async () => {
const findFirst = vi.fn(
async (args: { where: { credential?: { provider?: string }; isDefault?: boolean } }) => {
if (args.where.credential?.provider === "xai") {
return modelPreference({
provider: "xai",
secretId: "secret-xai",
modelId: "grok-4.6",
isDefault: false,
});
}
if (args.where.isDefault) {
return modelPreference({
provider: "openrouter",
secretId: "secret-or",
modelId: "deepseek/deepseek-v4-flash-0731",
isDefault: true,
});
}
return null;
},
);
const prisma = {
bot: {
findFirst: vi.fn(async () => ({
modelProvider: "xai",
modelId: "grok-4.6",
thinkingLevel: "high",
})),
},
spaceModelPreference: { findFirst },
userModelCredential: { findFirst: vi.fn(async () => null) },
deploymentSettings: { findUnique: vi.fn(async () => null) },
secret: { findFirst: vi.fn(async () => null), findUnique: vi.fn(async () => null) },
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
secretStore: { load: vi.fn(), put: vi.fn() },
} as unknown as Parameters<typeof createRunExecutor>[0]);
const model = await executor.resolveModel({
userId: "user-1",
spaceId: "ws-1",
botId: "bot-1",
});
expect(model).toMatchObject({
provider: "xai",
id: "grok-4.6",
thinkingLevel: "high",
});
expect(findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({ credential: { provider: "xai" } }),
}),
);
});
it("resolves an explicit subagent model within the active user and space", async () => {
const preference = modelPreference({
provider: "xai",
secretId: "secret-xai",
modelId: "grok-4.6",
isDefault: false,
});
const findFirst = vi.fn(
async (args: { where: { credential?: { provider?: string }; modelId?: string } }) => {
if (args.where.credential?.provider !== "xai") return null;
if (args.where.modelId && args.where.modelId !== "grok-4.6") return null;
return preference;
},
);
const prisma = {
spaceModelPreference: { findFirst },
userModelCredential: { findFirst: vi.fn(async () => null) },
secret: { findFirst: vi.fn(async () => null), findUnique: vi.fn(async () => null) },
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
secretStore: { load: vi.fn(), put: vi.fn() },
} as unknown as Parameters<typeof createRunExecutor>[0]);
const model = await executor.resolveConnectedModel(
{ userId: "user-1", spaceId: "ws-1" },
"xai",
"grok-4.6",
);
expect(model).toMatchObject({ provider: "xai", id: "grok-4.6", thinkingLevel: null });
expect(findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
spaceId: "ws-1",
userId: "user-1",
modelId: "grok-4.6",
credential: { provider: "xai" },
}),
}),
);
});
it("rejects a free-form selection when the owning preference disappears", async () => {
const preference = modelPreference({
provider: "openai-compatible",
secretId: "secret-compat",
modelId: "newest-model",
isDefault: true,
});
const findFirst = vi.fn(
async (args: {
where: { credential?: { provider?: string; userId?: string }; modelId?: string };
select?: unknown;
}) => {
if (args.select) {
return args.where.modelId === "private-model" ? { id: "saved" } : null;
}
if (args.where.modelId === "private-model") return null;
if (args.where.credential?.provider === "openai-compatible") return preference;
return null;
},
);
const prisma = {
spaceModelPreference: { findFirst },
userModelCredential: { findFirst: vi.fn(async () => null) },
secret: { findFirst: vi.fn(async () => null), findUnique: vi.fn(async () => null) },
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
secretStore: { load: vi.fn(), put: vi.fn() },
} as unknown as Parameters<typeof createRunExecutor>[0]);
await expect(
executor.resolveConnectedModel(
{ userId: "user-1", spaceId: "ws-1" },
"openai-compatible",
"private-model",
),
).rejects.toThrow("Unknown model for that provider");
});
it("keeps image support for a separately enabled bot model override", async () => {
const provider = "openai-compatible";
const findFirst = vi.fn(
async (args: { where: { credential?: { provider?: string }; isDefault?: boolean } }) => {
if (args.where.credential?.provider === provider || args.where.isDefault) {
return modelPreference({
provider,
secretId: "secret-openai-compatible",
modelId: "space-model",
isDefault: Boolean(args.where.isDefault),
});
}
return null;
},
);
const plaintext = serializeModelSecret({
kind: "openai_compatible",
baseUrl: "http://127.0.0.1:8000/v1",
visionModelIds: ["bot-vision-model"],
maxImagesPerPrompt: 1,
maxTokens: 8192,
contextWindow: 65536,
});
const bot = {
modelProvider: provider,
modelId: "bot-vision-model",
thinkingLevel: null,
};
const prisma = {
bot: { findFirst: vi.fn(async () => bot) },
spaceModelPreference: { findFirst },
userModelCredential: { findFirst: vi.fn(async () => null) },
deploymentSettings: { findUnique: vi.fn(async () => null) },
secret: {
findFirst: vi.fn(async () => ({
id: "secret-openai-compatible",
ciphertext: plaintext,
})),
findUnique: vi.fn(async () => null),
},
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
secretStore: { load: vi.fn(() => plaintext), put: vi.fn() },
} as unknown as Parameters<typeof createRunExecutor>[0]);
await expect(
executor.resolveModel({ userId: "user-1", spaceId: "ws-1", botId: "bot-1" }),
).resolves.toMatchObject({
provider,
id: "bot-vision-model",
acceptsImages: true,
maxImagesPerPrompt: 1,
maxTokens: 8192,
contextWindow: 65536,
});
bot.modelId = "text-only-model";
await expect(
executor.resolveModel({ userId: "user-1", spaceId: "ws-1", botId: "bot-1" }),
).resolves.toMatchObject({
provider,
id: "text-only-model",
acceptsImages: false,
});
});
it("falls back to the Space default when the override provider has no credential", async () => {
const findFirst = vi.fn(
async (args: { where: { credential?: { provider?: string }; isDefault?: boolean } }) => {
if (args.where.credential?.provider === "xai") return null;
if (args.where.isDefault) {
return modelPreference({
provider: "openrouter",
secretId: "secret-or",
modelId: "deepseek/deepseek-v4-flash-0731",
isDefault: true,
});
}
return null;
},
);
const prisma = {
bot: {
findFirst: vi.fn(async () => ({
modelProvider: "xai",
modelId: "grok-4.6",
thinkingLevel: "high",
})),
},
spaceModelPreference: { findFirst },
userModelCredential: { findFirst: vi.fn(async () => null) },
deploymentSettings: { findUnique: vi.fn(async () => null) },
secret: { findFirst: vi.fn(async () => null), findUnique: vi.fn(async () => null) },
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
secretStore: { load: vi.fn(), put: vi.fn() },
deploymentModelKey: "deployment-openrouter-key",
} as unknown as Parameters<typeof createRunExecutor>[0]);
const model = await executor.resolveModel({
userId: "user-1",
spaceId: "ws-1",
botId: "bot-1",
});
expect(model).toMatchObject({
provider: "openrouter",
id: "deepseek/deepseek-v4-flash-0731",
// Override thinking must drop with the override provider/credential unit.
thinkingLevel: null,
});
expect(findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({ credential: { provider: "xai" } }),
}),
);
expect(findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({ isDefault: true }),
}),
);
});
it("withholds the deployment key when settings name a different provider", async () => {
const prisma = {
bot: { findFirst: vi.fn(async () => null) },
spaceModelPreference: { findFirst: vi.fn(async () => null) },
userModelCredential: { findFirst: vi.fn(async () => null) },
deploymentSettings: {
findUnique: vi.fn(async () => ({
defaultModelProvider: "anthropic",
defaultModelId: "claude-sonnet-5",
})),
},
secret: { findFirst: vi.fn(async () => null), findUnique: vi.fn(async () => null) },
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
secretStore: { load: vi.fn(), put: vi.fn() },
// PI_DEFAULT_PROVIDER is unset here, so this key belongs to OpenRouter.
deploymentModelKey: "deployment-openrouter-key",
} as unknown as Parameters<typeof createRunExecutor>[0]);
const model = await executor.resolveModel({ userId: "user-1", spaceId: "ws-1" });
expect(model.provider).toBe("anthropic");
expect(model.apiKey).toBeUndefined();
});
it("keeps per-bot thinking when using the Space default model", async () => {
const findFirst = vi.fn(async (args: { where: { isDefault?: boolean } }) => {
if (!args.where.isDefault) return null;
return modelPreference({
provider: "openrouter",
secretId: "secret-or",
modelId: "deepseek/deepseek-v4-flash-0731",
isDefault: true,
});
});
const prisma = {
bot: {
findFirst: vi.fn(async () => ({
modelProvider: null,
modelId: null,
thinkingLevel: "high",
})),
},
spaceModelPreference: { findFirst },
userModelCredential: { findFirst: vi.fn(async () => null) },
deploymentSettings: { findUnique: vi.fn(async () => null) },
secret: { findFirst: vi.fn(async () => null), findUnique: vi.fn(async () => null) },
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
secretStore: { load: vi.fn(), put: vi.fn() },
} as unknown as Parameters<typeof createRunExecutor>[0]);
const model = await executor.resolveModel({
userId: "user-1",
spaceId: "ws-1",
botId: "bot-1",
});
expect(model).toMatchObject({
provider: "openrouter",
id: "deepseek/deepseek-v4-flash-0731",
thinkingLevel: "high",
});
});
});