diff --git a/packages/adapters/src/executor.test.ts b/packages/adapters/src/executor.test.ts index 38ce1f7..d35d63c 100644 --- a/packages/adapters/src/executor.test.ts +++ b/packages/adapters/src/executor.test.ts @@ -12,6 +12,7 @@ import { 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"; @@ -1253,6 +1254,94 @@ description: Prepare standup notes 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[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 } }) => { diff --git a/packages/adapters/src/executor.ts b/packages/adapters/src/executor.ts index 3121e7c..4164e0e 100644 --- a/packages/adapters/src/executor.ts +++ b/packages/adapters/src/executor.ts @@ -108,6 +108,7 @@ import { respondAgentConnection, } from "./agent-connections.js"; import { + agentSecretScopeForRun, decryptAgentEnvironment, formatAgentEnvironmentInstruction, redactAgentCommandResult, @@ -1026,7 +1027,7 @@ export function createRunExecutor(deps: ExecutorDeps) { userId: run.userId, }), deps.prisma.agentSecret.findMany({ - where: { spaceId: run.spaceId }, + where: agentSecretScopeForRun(run.spaceId, run.userId), select: { name: true, secret: { select: { id: true, ciphertext: true } },