evOo-local/packages/adapters/src/executor.test.ts
cynarAI f14b112cdd
Broker GitHub Integrations token for computer git/gh (#203)
* Broker GitHub integration token for computer git/gh

Use the Integrations GitHub MCP secret for supervisor broker sessions
so home and project computers can clone private repos without PAT prompts.
Add rakazo git/gh shims, push policy, deploy image rebuild, and tests.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* ci: retrigger product checks after runner timeout

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* ci: run product change detection on GitHub-hosted runners

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* style: format deployment-github-token

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* ci: self-hosted change detection with timeout and fail-open gate

Keep Detect product changes on evoo-ci with a 10-minute cap, continue-on-error
filtering, and a fail-open product flag when detection fails. Downstream jobs
and Product CI treat a failed or timed-out changes job as full product impact.
Add job timeouts on remaining self-hosted ci jobs.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix: continueRun tests and broker home path guard

- Skip broker workspace resolution when computer homeKey is absent
- Add shared continue-run Prisma/home/secretStore test stubs
- Update computer PATH expectation and evo-deploy command sequence
- Remove unused executor imports flagged by Biome

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* style: Biome format and import order for broker CI

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix: compose YAML, typecheck for broker stubs and GitHub hint

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* ci: retrigger Product CI after compose and typecheck fixes

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* ci: retry Product CI queue

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* ci: give self-hosted Web E2E a 45m job timeout

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* ci: extend Web E2E job timeout to 60m on self-hosted runners

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix: keep github-broker-token off the core browser barrel

Export broker HMAC helpers only from @rakazo/core/node/github-broker-token so
Vite does not pull node:crypto into the web client and break Playwright signup.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

---------

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

1885 lines
63 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 { agentSecretScopeForRun } from "./agent-environment.js";
import { withContinueRunTestDeps } from "./continue-run-test-stubs.js";
import {
appendToolCompletionAudit,
createRunExecutor,
createRunWorkspaceCheckpoint,
loadCurrentTurnImages,
runNotificationsEnabled,
selectBuiltinToolsForRun,
toolCompletionAuditPayload,
toolCompletionFromResult,
} from "./executor.js";
import {
missingTurnImagesInstruction,
settleSteeringAttachmentLoads,
} from "./executor-attachments.js";
import { threadContextForRun } from "./executor-completion.js";
import { LOCAL_API_KEY } from "./pi-local-provider.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);
});
});
const specialist = { kind: "specialist", restrictBuiltinTools: false, allowedBuiltinTools: [] };
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,
bot: specialist,
}).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,
bot: specialist,
}).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("offers delegate_task to the main bot only", () => {
const options = {
graphicalToolsAllowed: false,
groupId: "group-main",
trigger: "user",
semanticMemoryEnabled: false,
messagingChannelRun: false,
};
const names = (kind: string) =>
selectBuiltinToolsForRun({
...options,
bot: { kind, restrictBuiltinTools: false, allowedBuiltinTools: [] },
}).map((tool) => tool.name);
expect(names("main")).toContain("delegate_task");
expect(names("specialist")).not.toContain("delegate_task");
});
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,
bot: specialist,
}).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 () => []) },
user: { findUnique: vi.fn(async () => null) },
userProfile: { findUnique: vi.fn(async () => null) },
userFact: { findMany: vi.fn(async () => []) },
agentSkill: { findMany: vi.fn(async () => []) },
scratchpadItem: { findMany: vi.fn(async () => []) },
} as unknown as PrismaClient;
const executor = createRunExecutor({
...withContinueRunTestDeps({
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 },
user: { findUnique: vi.fn(async () => null) },
userProfile: { findUnique: vi.fn(async () => null) },
userFact: { findMany: vi.fn(async () => []) },
agentSkill: { findMany: vi.fn(async () => []) },
scratchpadItem: { findMany: vi.fn(async () => []) },
} as unknown as PrismaClient;
const executor = createRunExecutor({
...withContinueRunTestDeps({
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",
});
});
// One router connection serves several local models; tiers pick among them.
function tierPrisma(
bot: Record<string, unknown> | null,
tierRow: unknown,
deploymentModelKey?: string,
) {
const plaintext = serializeModelSecret({
kind: "openai_compatible",
baseUrl: "http://127.0.0.1:8080/v1",
reasoning: true,
});
const findUnique = vi.fn(async () => tierRow);
const prisma = {
bot: { findFirst: vi.fn(async () => bot) },
spaceModelTier: { findUnique },
spaceModelPreference: {
findFirst: vi.fn(async (args: { where: { credential?: { provider?: string } } }) =>
args.where.credential?.provider === "xai"
? modelPreference({
provider: "xai",
secretId: "secret-xai",
modelId: "grok-4.6",
isDefault: false,
})
: null,
),
},
userModelCredential: { findFirst: vi.fn(async () => null) },
deploymentSettings: { findUnique: vi.fn(async () => null) },
secret: {
findFirst: vi.fn(async (args: { where: { id: string } }) =>
args.where.id === "secret-router" ? { id: "secret-router", ciphertext: plaintext } : null,
),
findUnique: vi.fn(async () => null),
},
} as unknown as PrismaClient;
const executor = createRunExecutor({
prisma,
secretStore: { load: vi.fn(() => plaintext), put: vi.fn() },
deploymentModelKey,
} as unknown as Parameters<typeof createRunExecutor>[0]);
return { executor, findUnique };
}
const routerTier = {
tier: "code",
credentialId: "credential-router",
modelId: "router-code",
credential: {
id: "credential-router",
userId: "user-1",
provider: "openai-compatible",
label: "Router",
secretId: "secret-router",
supportsImages: false,
createdAt: new Date(0),
updatedAt: new Date(0),
preferences: [{ isDefault: true, modelId: "router-default" }],
},
};
it("runs a bot on the model its space maps to the bot's tier", async () => {
const { executor, findUnique } = tierPrisma(
{ modelProvider: "xai", modelId: "grok-4.6", thinkingLevel: "high", modelTier: "code" },
routerTier,
);
const model = await executor.resolveModel({
userId: "user-1",
spaceId: "ws-1",
botId: "bot-1",
});
expect(model).toMatchObject({
provider: "openai-compatible",
id: "router-code",
baseUrl: "http://127.0.0.1:8080/v1",
reasoning: true,
thinkingLevel: "high",
});
expect(findUnique).toHaveBeenCalledWith(
expect.objectContaining({
where: { spaceId_userId_tier: { spaceId: "ws-1", userId: "user-1", tier: "code" } },
}),
);
});
it("keeps the bot's own model when its tier is not mapped", async () => {
const { executor } = tierPrisma(
{ modelProvider: "xai", modelId: "grok-4.6", thinkingLevel: null, modelTier: "code" },
null,
);
await expect(
executor.resolveModel({ userId: "user-1", spaceId: "ws-1", botId: "bot-1" }),
).resolves.toMatchObject({ provider: "xai", id: "grok-4.6" });
});
it("prefers a tier the call asks for, and keeps the bot's choice when it is unmapped", async () => {
const fastTier = { ...routerTier, tier: "fast", modelId: "router-fast" };
const mapped = tierPrisma(
{ modelProvider: "xai", modelId: "grok-4.6", thinkingLevel: null, modelTier: null },
fastTier,
);
await expect(
mapped.executor.resolveModel({
userId: "user-1",
spaceId: "ws-1",
botId: "bot-1",
tier: "fast",
}),
).resolves.toMatchObject({ provider: "openai-compatible", id: "router-fast" });
expect(mapped.findUnique).toHaveBeenCalledWith(
expect.objectContaining({
where: { spaceId_userId_tier: { spaceId: "ws-1", userId: "user-1", tier: "fast" } },
}),
);
const unmapped = tierPrisma(
{ modelProvider: "xai", modelId: "grok-4.6", thinkingLevel: null, modelTier: null },
null,
);
await expect(
unmapped.executor.resolveModel({
userId: "user-1",
spaceId: "ws-1",
botId: "bot-1",
tier: "fast",
}),
).resolves.toMatchObject({ provider: "xai", id: "grok-4.6" });
});
it("resolves a helper tier and reports an unmapped one as null", async () => {
const mapped = tierPrisma(null, routerTier);
await expect(
mapped.executor.resolveTierModel({ userId: "user-1", spaceId: "ws-1" }, "code"),
).resolves.toMatchObject({
provider: "openai-compatible",
id: "router-code",
baseUrl: "http://127.0.0.1:8080/v1",
});
const unmapped = tierPrisma(null, null);
await expect(
unmapped.executor.resolveTierModel({ userId: "user-1", spaceId: "ws-1" }, "fast"),
).resolves.toBeNull();
});
it("falls back to the tiers a local-model deployment assigns", async () => {
vi.stubEnv("PI_DEFAULT_PROVIDER", "local");
vi.stubEnv("RAKAZO_LOCAL_MODELS", "strong,weak=http://127.0.0.1:1239/v1");
vi.stubEnv("RAKAZO_LOCAL_TIERS", "fast=weak,code=strong");
try {
const { executor } = tierPrisma(null, null, LOCAL_API_KEY);
await expect(
executor.resolveTierModel({ userId: "user-1", spaceId: "ws-1" }, "fast"),
).resolves.toMatchObject({ provider: "local", id: "weak", apiKey: LOCAL_API_KEY });
// A tier the deployment leaves out keeps the run's own model.
await expect(
executor.resolveTierModel({ userId: "user-1", spaceId: "ws-1" }, "standard"),
).resolves.toBeNull();
// A space's own mapping still wins over the deployment's.
const mapped = tierPrisma(null, routerTier, LOCAL_API_KEY);
await expect(
mapped.executor.resolveTierModel({ userId: "user-1", spaceId: "ws-1" }, "code"),
).resolves.toMatchObject({ provider: "openai-compatible", id: "router-code" });
} finally {
vi.unstubAllEnvs();
}
});
});