Finish Copilot production setup on current master

Preserve the reviewed candidate source and mainline provider behavior.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-09 10:54:11 -05:00
1 parent cc1670aa56
commit aaa65bbfbf
74 files changed
+57873 -251

No files matched your search

+28 -15
View File
@@ -1,4 +1,6 @@
import { probeAcpxClaudeInstallation, probeAcpxPiInstallation } from "../vendor/paperclip-runner/live/index.js";
import { probeCopilotExecutionTarget } from "../services/copilot-connection-probe.js";
import { HttpError } from "../errors.js";
import { probeAcpxClaudeInstallation, probeAcpxCursorInstallation, probeAcpxPiInstallation } from "../vendor/paperclip-runner/live/index.js";
import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
import { buildSandboxNpmInstallCommand } from "@paperclipai/adapter-utils";
import type { ServerAdapterModule } from "../adapters/index.js";
@@ -17,6 +19,8 @@ import {
setOverridePaused,
} from "../adapters/registry.js";
vi.mock("../services/copilot-connection-probe.js",()=>({probeCopilotExecutionTarget:vi.fn(async()=>({status:"verified",models:[{id:"gpt-5.6-luna"}]}))}));
vi.mock("../vendor/paperclip-runner/live/index.js", () => ({
probeAcpxClaudeInstallation: vi.fn(async () => undefined),
probeAcpxGrokInstallation: vi.fn(async () => undefined),
@@ -348,20 +352,29 @@ describe("server adapter registry", () => {
});
expect(probe).toHaveBeenLastCalledWith("openrouter/deepseek/deepseek-v4-flash-0731");
});
it("reports qualification-only readiness for an exact host-authorized candidate", async () => {
const key = "PAPERCLIP_RUNNER_ACPX_QUALIFICATION";
const previous = process.env[key];
process.env[key] = JSON.stringify([{ agent: "copilot", model: "exact-model" }]);
try {
const result = await requireServerAdapter("paperclip_runner").testEnvironment({
companyId: "company-1", adapterType: "paperclip_runner",
config: { provider: "acpx", acpxAgent: "copilot", model: "exact-model" },
});
expect(result).toMatchObject({ status: "warn", checks: [{ code: "acpx_candidate_qualification_only" }] });
} finally {
if (previous === undefined) delete process.env[key];
else process.env[key] = previous;
}
it("requires a saved Copilot token rather than ambient authentication",async()=>{
const prior=process.env.COPILOT_GITHUB_TOKEN;process.env.COPILOT_GITHUB_TOKEN="ambient-not-authority";
const probe=vi.mocked(probeCopilotExecutionTarget);probe.mockClear();
try{
const result=await requireServerAdapter("paperclip_runner").testEnvironment({companyId:"company-1",adapterType:"paperclip_runner",config:{provider:"acpx",acpxAgent:"copilot",model:"gpt-5.6-luna"}});
expect(result).toMatchObject({status:"fail",checks:[{code:"COPILOT_AUTH_REQUIRED"}]});expect(probe).not.toHaveBeenCalled();
}finally{if(prior===undefined)delete process.env.COPILOT_GITHUB_TOKEN;else process.env.COPILOT_GITHUB_TOKEN=prior;}
});
it.each([undefined,{kind:"remote",remoteCwd:"/workspace"}])("verifies the selected Copilot runtime and model without a prompt",async(executionTarget)=>{
const result=await requireServerAdapter("paperclip_runner").testEnvironment({companyId:"company-1",adapterType:"paperclip_runner",config:{provider:"acpx",acpxAgent:"copilot",model:"gpt-5.6-luna",env:{COPILOT_GITHUB_TOKEN:"fixture-token"}},executionTarget} as any);
expect(probeCopilotExecutionTarget).toHaveBeenLastCalledWith("fixture-token",executionTarget,"gpt-5.6-luna");
expect(result).toMatchObject({status:"pass",checks:[{code:"copilot_metadata_verified"}]});
});
it.each(["COPILOT_AUTH_REQUIRED","COPILOT_ENTITLEMENT_DENIED","COPILOT_MODEL_UNAVAILABLE","COPILOT_INSTALLATION_INVALID","COPILOT_REQUEST_FAILED"])("reports distinct Copilot failure %s",async code=>{
vi.mocked(probeCopilotExecutionTarget).mockRejectedValueOnce(new HttpError(422,"Fixture metadata failure",{code}));
const result=await requireServerAdapter("paperclip_runner").testEnvironment({companyId:"company-1",adapterType:"paperclip_runner",config:{provider:"acpx",acpxAgent:"copilot",model:"gpt-5.6-luna",env:{COPILOT_GITHUB_TOKEN:"fixture-token"}}});
expect(result).toMatchObject({status:"fail",checks:[{code}]});
});
it("sanitizes unexpected Copilot metadata failures",async()=>{
vi.mocked(probeCopilotExecutionTarget).mockRejectedValueOnce(new Error("fixture private implementation detail"));
const result=await requireServerAdapter("paperclip_runner").testEnvironment({companyId:"company-1",adapterType:"paperclip_runner",config:{provider:"acpx",acpxAgent:"copilot",model:"gpt-5.6-luna",env:{COPILOT_GITHUB_TOKEN:"fixture-token"}}});
expect(result).toMatchObject({status:"fail",checks:[{code:"COPILOT_REQUEST_FAILED",message:"Copilot metadata verification failed."}]});
expect(JSON.stringify(result)).not.toContain("private implementation detail");
});
it("wraps built-in npm runtime installs with the sandbox-aware install helper", () => {
const expectedClaudeInstall = `if ! command -v 'claude' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@anthropic-ai/claude-code")}; fi`;
@@ -389,6 +389,7 @@ describe("adapter routes", () => {
options: [
{ value: "claude", label: "Claude" },
{ value: "grok", label: "Grok Build" },
{ value: "copilot", label: "GitHub Copilot" },
],
});
expect(JSON.stringify(res.body)).not.toContain("Codex via ACPX");
@@ -32,6 +32,7 @@ const mockEnvironmentService = vi.hoisted(() => ({
releaseLease: vi.fn(),
listBoundCompanyIds: vi.fn(async () => [] as string[]),
findManagedSandboxEnvironment: vi.fn(async () => null as Record<string, unknown> | null),
findKubernetesEnvironment: vi.fn(async () => null as Record<string, unknown> | null),
}));
const mockReleaseRunLease = vi.hoisted(() => vi.fn(async () => undefined));
@@ -46,7 +47,7 @@ const mockEnvironmentRuntime = vi.hoisted(() => ({
const mockResolveEnvironmentExecutionTarget = vi.hoisted(() => vi.fn());
const mockInstanceSettingsService = vi.hoisted(() => ({
get: vi.fn(async () => ({ defaultEnvironmentId: null as string | null })),
getGeneral: vi.fn(async () => ({ censorUsernameInLogs: false })),
getGeneral: vi.fn(async () => ({ censorUsernameInLogs: false, executionMode: "local" })),
getExperimental: vi.fn(async () => ({ enableManagedSandboxOnly: false })),
}));
@@ -121,6 +122,10 @@ vi.mock("../routes/ai-connections.js", async (importOriginal) => ({
validateAiApiKey: mockValidateAiApiKey,
}));
const mockMarkAuthenticationFailed = vi.hoisted(() => vi.fn(async () => undefined));
const mockCopilotProbe = vi.hoisted(() => vi.fn(async () => ({ status: "verified", promptSent: false })));
vi.mock("../services/copilot-connection-probe.js", async importOriginal => ({
...(await importOriginal<object>()), probeCopilotExecutionTarget: mockCopilotProbe,
}));
vi.mock("../services/ai-connections.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../services/ai-connections.js")>()),
aiConnectionService: () => ({ markAuthenticationFailed: mockMarkAuthenticationFailed }),
@@ -192,6 +197,8 @@ describe("agent test-environment route", () => {
vi.resetModules();
vi.clearAllMocks();
mockInstanceSettingsService.get.mockResolvedValue({ defaultEnvironmentId: null });
mockInstanceSettingsService.getGeneral.mockResolvedValue({ censorUsernameInLogs: false, executionMode: "local" });
mockEnvironmentService.findKubernetesEnvironment.mockResolvedValue(null);
mockInstanceSettingsService.getExperimental.mockResolvedValue({ enableManagedSandboxOnly: false });
mockEnvironmentService.findManagedSandboxEnvironment.mockResolvedValue(null);
mockAccessService.decide.mockResolvedValue({
@@ -327,6 +334,41 @@ describe("agent test-environment route", () => {
expect(testEnvironmentSpy).toHaveBeenCalledWith(expect.objectContaining({ executionTarget: target }));
});
it.each([null, "33333333-3333-4333-8333-333333333333"])("verifies Copilot on forced Kubernetes before the host selection (%s)", async environmentId => {
const kubernetesId = "11111111-1111-4111-8111-111111111111";
mockInstanceSettingsService.getGeneral.mockResolvedValue({ censorUsernameInLogs: false, executionMode: "kubernetes" });
mockEnvironmentService.findKubernetesEnvironment.mockResolvedValue({ id: kubernetesId });
const target = { kind: "remote", transport: "sandbox", remoteCwd: "/workspace", providerKey: "kubernetes", runner: { execute: vi.fn() } };
mockResolveEnvironmentExecutionTarget.mockResolvedValue(target);
mockPrepareManagedAiRuntime.mockImplementation(async (_db, input) => ({
config: { ...input.config, env: { COPILOT_GITHUB_TOKEN: "private-fixture" } },
attribution: { provider: "github", method: "api_key" }, cleanup: vi.fn(),
}));
const app = await createApp();
const response = await request(app).post("/api/companies/company-1/adapters/paperclip_runner/test-environment").send({
environmentId, adapterConfig: { provider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna" },
aiConnection: { provider: "github", method: "api_key", mode: "responsible_user" },
});
expect(response.status, JSON.stringify(response.body)).toBe(200);
expect(response.body.status).toBe("pass");
expect(mockEnvironmentRuntime.acquireRunLease).toHaveBeenCalledWith(expect.objectContaining({ environment: expect.objectContaining({ id: kubernetesId }) }));
expect(mockCopilotProbe).toHaveBeenCalledExactlyOnceWith("private-fixture", target, "gpt-5.6-luna");
expect(mockReleaseRunLease).toHaveBeenCalledOnce();
});
it("refuses a Copilot test before credentials when forced Kubernetes is unavailable", async () => {
mockInstanceSettingsService.getGeneral.mockResolvedValue({ censorUsernameInLogs: false, executionMode: "kubernetes" });
const app = await createApp();
const response = await request(app).post("/api/companies/company-1/adapters/paperclip_runner/test-environment").send({
adapterConfig: { provider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna" },
aiConnection: { provider: "github", method: "api_key", mode: "responsible_user" },
});
expect(response.status).toBe(422);
expect(response.body.details.code).toBe("copilot_environment_unavailable");
expect(mockPrepareManagedAiRuntime).not.toHaveBeenCalled();
expect(mockCopilotProbe).not.toHaveBeenCalled();
});
it("checks the inherited environment's company before resolving credentials", async () => {
mockInstanceSettingsService.get.mockResolvedValue({ defaultEnvironmentId: "11111111-1111-4111-8111-111111111111" });
mockEnvironmentService.listBoundCompanyIds.mockResolvedValue(["company-2"]);
+231 -2
View File
@@ -32,6 +32,10 @@ import express from "express";
import request from "supertest";
import { aiConnectionRoutes, canInstallSharedAiConnectionForNewAgent, responsibleUserForAiRequest } from "../routes/ai-connections.js";
import { validateAiApiKey } from "../routes/ai-connections.js";
import * as copilotProbe from "../services/copilot-connection-probe.js";
import * as settingsModule from "../services/instance-settings.js";
import { agentRoutes } from "../routes/agents.js";
import type { PluginWorkerManager } from "../services/plugin-worker-manager.js";
vi.mock("../services/local-ai-browser-login.js", () => ({
startLocalBrowserLogin: () => ({ authorizationUrl: "https://auth.openai.com/codex/device", code: "ABCD-EFGHJ", abort: () => {} }),
}));
@@ -61,6 +65,219 @@ beforeAll(async () => {
afterAll(async () => { await database?.cleanup(); vi.unstubAllEnvs(); if (home) await rm(home, { recursive: true, force: true }); });
describe("managed AI connections", () => {
it.each(["create", "hire", "edit"])("refuses Copilot %s before probing or persisting when forced Kubernetes is missing", async action => {
const { errorHandler } = await import("../middleware/index.js");
const owner = `copilot-kubernetes-${action}`, id = randomUUID();
const config = { provider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna" };
const aiConnection = { provider: "github", method: "api_key", mode: "responsible_user" } as const;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
await db.insert(agents).values({ id, companyId, name: "Kubernetes fixture", adapterType: "paperclip_runner", adapterConfig: config, runtimeConfig: { aiConnection } });
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "personal", name: "Kubernetes fixture", apiKey: "github_pat_fixture_kubernetes", agentIds: [id], allAgents: false }, "github_pat_fixture_kubernetes");
await service.setDefault(companyId, owner, account.grantId);
const [company] = await db.select().from(companies).where(eq(companies.id, companyId));
await db.update(companies).set({ requireBoardApprovalForNewAgents: false }).where(eq(companies.id, companyId));
const before = await db.select({ id: agents.id }).from(agents).where(eq(agents.companyId, companyId));
const originalSettings = settingsModule.instanceSettingsService;
const settings = vi.spyOn(settingsModule, "instanceSettingsService").mockImplementation(database => {
const service = originalSettings(database);
return { ...service, getGeneral: async () => ({ ...await service.getGeneral(), executionMode: "kubernetes" as const }) };
});
const probe = vi.spyOn(copilotProbe, "probeCopilotExecutionTarget");
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: owner, companyIds: [companyId], isInstanceAdmin: true }; next(); });
app.use("/api", agentRoutes(db)); app.use(errorHandler);
try {
const changedConfig = { ...config, model: "prospective-model" };
const response = action === "edit"
? await request(app).patch(`/api/agents/${id}`).send({ adapterConfig: changedConfig })
: await request(app).post(`/api/companies/${companyId}/${action === "hire" ? "agent-hires" : "agents"}`).send({ name: "Forced Kubernetes", role: "general", adapterType: "paperclip_runner", adapterConfig: changedConfig, runtimeConfig: { aiConnection } });
expect(response.status, JSON.stringify(response.body)).toBe(422);
expect(response.body.details.code).toBe("copilot_environment_unavailable");
expect(probe).not.toHaveBeenCalled();
expect((await db.select().from(agents).where(eq(agents.id, id)))[0].adapterConfig).toEqual(config);
expect(await db.select({ id: agents.id }).from(agents).where(eq(agents.companyId, companyId))).toHaveLength(before.length);
} finally {
probe.mockRestore(); settings.mockRestore();
await db.update(companies).set({ requireBoardApprovalForNewAgents: company.requireBoardApprovalForNewAgents }).where(eq(companies.id, companyId));
}
});
it.each(["create", "hire", "edit"].flatMap(action => [true, false].map(connectionProvided => ({ action, connectionProvided }))))(
"refuses unavailable Copilot models before $action persists configuration (connection binding: $connectionProvided)", async ({ action, connectionProvided }) => {
const { unprocessable } = await import("../errors.js");
const { errorHandler } = await import("../middleware/index.js");
const owner = `copilot-model-${action}-${connectionProvided}`, id = randomUUID();
const savedConfig = { provider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna" };
const aiConnection = { provider: "github", method: "api_key", mode: "responsible_user" } as const;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
await db.insert(agents).values({ id, companyId, name: `Copilot ${action} fixture`, adapterType: "paperclip_runner", adapterConfig: savedConfig, runtimeConfig: connectionProvided ? { aiConnection } : {} });
const token = `github_pat_fixture_model_${action}`;
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "personal", name: `Model ${action}`, apiKey: token, agentIds: [id], allAgents: false }, token);
await service.setDefault(companyId, owner, account.grantId);
const [company] = await db.select().from(companies).where(eq(companies.id, companyId));
await db.update(companies).set({ requireBoardApprovalForNewAgents: false }).where(eq(companies.id, companyId));
const before = await db.select({ id: agents.id }).from(agents).where(eq(agents.companyId, companyId));
const probe = vi.spyOn(copilotProbe, "probeCopilotExecutionTarget").mockRejectedValue(unprocessable("The selected Copilot model is unavailable for this account.", { code: "COPILOT_MODEL_UNAVAILABLE" }));
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: owner, companyIds: [companyId], isInstanceAdmin: true }; next(); });
app.use("/api", agentRoutes(db)); app.use(errorHandler);
const config = { ...savedConfig, model: "unavailable-copilot-model" };
try {
const response = action === "edit"
? await request(app).patch(`/api/agents/${id}`).send({ adapterConfig: config })
: await request(app).post(`/api/companies/${companyId}/${action === "hire" ? "agent-hires" : "agents"}`).send({ name: `Unavailable ${action}`, role: "general", adapterType: "paperclip_runner", adapterConfig: config, ...(connectionProvided ? { runtimeConfig: { aiConnection } } : {}) });
expect(response.status, JSON.stringify(response.body)).toBe(422);
expect(response.body).toMatchObject({ error: "The selected Copilot model is unavailable for this account." });
expect(probe).toHaveBeenCalledExactlyOnceWith(token, null, "unavailable-copilot-model");
expect((await db.select().from(agents).where(eq(agents.id, id)))[0].adapterConfig).toEqual(savedConfig);
expect(await db.select({ id: agents.id }).from(agents).where(eq(agents.companyId, companyId))).toHaveLength(before.length);
expect(JSON.stringify(response.body)).not.toContain(token);
} finally {
probe.mockRestore();
await db.update(companies).set({ requireBoardApprovalForNewAgents: company.requireBoardApprovalForNewAgents }).where(eq(companies.id, companyId));
}
});
it("verifies the exact available Copilot model before a model-only edit with an unchanged connection", async () => {
const { errorHandler } = await import("../middleware/index.js");
const owner = "copilot-model-valid", id = randomUUID();
const aiConnection = { provider: "github", method: "api_key", mode: "responsible_user" } as const;
const config = { provider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna" };
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
await db.insert(agents).values({ id, companyId, name: "Copilot available model", adapterType: "paperclip_runner", adapterConfig: { ...config, model: "previous-model" }, runtimeConfig: { aiConnection } });
const token = "github_pat_fixture_model_valid";
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "personal", name: "Valid model", apiKey: token, agentIds: [id], allAgents: false }, token);
await service.setDefault(companyId, owner, account.grantId);
const probe = vi.spyOn(copilotProbe, "probeCopilotExecutionTarget").mockResolvedValue({ status: "verified", version: "1.0.88", profileDigest: "fixture-profile", promptSent: false, models: [{ id: config.model, label: "GPT" }] });
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: owner, companyIds: [companyId], isInstanceAdmin: true }; next(); });
app.use("/api", agentRoutes(db)); app.use(errorHandler);
try {
const response = await request(app).patch(`/api/agents/${id}`).send({ adapterConfig: config });
expect(response.status, JSON.stringify(response.body)).toBe(200);
expect(probe).toHaveBeenCalledExactlyOnceWith(token, null, config.model);
expect((await db.select().from(agents).where(eq(agents.id, id)))[0].adapterConfig).toMatchObject(config);
expect(response.body.runtimeConfig.aiConnection).toEqual(aiConnection);
} finally { probe.mockRestore(); }
});
it("passes the live sandbox worker manager to Copilot connection verification", async () => {
const pluginWorkerManager = {} as PluginWorkerManager;
const probe = vi.spyOn(copilotProbe, "probeCopilotConnection").mockResolvedValue({
status: "verified", version: "1.0.88", profileDigest: "fixture-profile",
promptSent: false, models: [{ id: "gpt-5.6-luna", label: "GPT" }],
});
const app = express();
app.use(express.json());
app.use((req, _res, next) => {
req.actor = { type: "board", source: "local_implicit", userId: "alice", companyIds: [companyId] };
next();
});
app.use("/api", aiConnectionRoutes(db, { pluginWorkerManager }));
try {
const response = await request(app).post(`/api/companies/${companyId}/ai-connections`).send({
provider: "github", method: "api_key", name: "Copilot remote route", ownership: "personal",
apiKey: "github_pat_fixture_remote", agentIds: [], allAgents: false,
});
expect(response.status).toBe(201);
expect(probe).toHaveBeenCalledWith(db, companyId, "github_pat_fixture_remote", undefined, undefined, { pluginWorkerManager });
} finally { probe.mockRestore(); }
});
it("discovers Copilot models in the selected environment with its sandbox worker", async () => {
const pluginWorkerManager = {} as PluginWorkerManager;
const [environment] = await db.insert(environments).values({ name: "Copilot model discovery", driver: "sandbox", config: { provider: "daytona" } }).returning();
const token = "github_pat_fixture_model_discovery";
const account = await service.save(companyId, "alice", { provider: "github", method: "api_key", ownership: "personal", name: "Model discovery account", apiKey: token, agentIds: [agentId], allAgents: false }, token);
await service.setDefault(companyId, "alice", account.grantId);
const probe = vi.spyOn(copilotProbe, "probeCopilotConnection").mockResolvedValue({ status: "verified", version: "1.0.88", profileDigest: "fixture-profile", promptSent: false, models: [{ id: "gpt-5.6-luna", label: "GPT" }] });
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: "alice", companyIds: [companyId] }; next(); });
app.use("/api", agentRoutes(db, { pluginWorkerManager }));
try {
const response = await request(app).get(`/api/companies/${companyId}/adapters/paperclip_runner/models`).query({ provider: "acpx", acpxAgent: "copilot", environmentId: environment.id, agentId, aiConnection: JSON.stringify({ provider: "github", method: "api_key", mode: "responsible_user" }) });
expect(response.status).toBe(200);
expect(response.headers["cache-control"]).toBe("no-store");
expect(response.body).toEqual([{ id: "gpt-5.6-luna", label: "GPT" }]);
expect(probe).toHaveBeenCalledWith(db, companyId, token, environment.id, undefined, { pluginWorkerManager });
expect(JSON.stringify(response.body)).not.toContain(token);
} finally { probe.mockRestore(); }
});
it("keeps Copilot model discovery behind shared installation and agent configuration authority", async () => {
const { errorHandler } = await import("../middleware/index.js");
const owner = "copilot-discovery-owner", audience = "copilot-discovery-audience";
await db.insert(companyMemberships).values([owner, audience].map(principalId => ({ companyId, principalId, principalType: "user", status: "active", membershipRole: "member" })));
await db.insert(principalPermissionGrants).values([owner, audience].map(principalId => ({ companyId, principalId, principalType: "user", permissionKey: "agents:create" })));
const token = "github_pat_fixture_shared_discovery";
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "shared", name: "Shared discovery", apiKey: token, agentIds: [], allAgents: false }, token);
await db.insert(connectionGrantMembers).values([owner, audience].map(subjectId => ({ companyId, grantId: account.grantId, subjectType: "user", subjectId })));
const binding = { provider: "github", method: "api_key", mode: "shared", ...account };
const probe = vi.spyOn(copilotProbe, "probeCopilotConnection").mockResolvedValue({ status: "verified", version: "1.0.88", profileDigest: "fixture-profile", promptSent: false, models: [{ id: "gpt-5.6-luna", label: "GPT" }] });
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "session", userId: String(req.headers["x-test-user"] ?? audience), companyIds: [companyId], memberships: [{ companyId, membershipRole: "member", status: "active" }] }; next(); });
app.use("/api", agentRoutes(db)); app.use(errorHandler);
const url = `/api/companies/${companyId}/adapters/paperclip_runner/models`;
const query = { provider: "acpx", acpxAgent: "copilot", aiConnection: JSON.stringify(binding) };
try {
const denied = await request(app).get(url).query(query);
expect(denied.status, JSON.stringify(denied.body)).toBe(403);
expect(denied.body.error).toContain("not permitted for this agent");
expect(probe).not.toHaveBeenCalled();
const allowed = await request(app).get(url).set("x-test-user", owner).query(query);
expect(allowed.status, JSON.stringify(allowed.body)).toBe(200);
expect(probe).toHaveBeenCalledExactlyOnceWith(db, companyId, token, null, undefined, { pluginWorkerManager: undefined });
probe.mockClear();
const foreign = await request(app).get(url).set("x-test-user", owner).query({ ...query, agentId: randomUUID() });
expect(foreign.status).toBe(404);
expect(probe).not.toHaveBeenCalled();
expect(JSON.stringify([denied.body, allowed.body, foreign.body])).not.toContain(token);
} finally { probe.mockRestore(); }
});
it("preserves repository GitHub credentials for managed non-Copilot execution", async () => {
const owner = "repo-github-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
await create(owner, owner);
for (const configured of [{ GH_TOKEN: "repo-gh-fixture", GITHUB_TOKEN: "repo-github-fixture" }, {}]) {
const runtime = await prepareManagedAiRuntime(db, { ...input, responsibleUserId: owner, config: { env: configured } });
try {
expect(runtime.config.env).toMatchObject({ COPILOT_GITHUB_TOKEN: "", ANTHROPIC_API_KEY: `fixture-${owner}` });
for (const key of ["GH_TOKEN", "GITHUB_TOKEN"] as const) {
if (key in configured) expect(runtime.config.env?.[key]).toBe(configured[key as keyof typeof configured]);
else expect(runtime.config.env).not.toHaveProperty(key);
}
} finally { await runtime.cleanup(); }
}
});
it("uses a vaulted Copilot token with GitHub attribution and refuses revoked or foreign grants", async () => {
const owner = "copilot-owner";
const token = "github_pat_fixture_copilot";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "personal", name: "Copilot", apiKey: token, agentIds: [agentId], allAgents: false }, token);
const selectedBinding = { provider: "github", method: "api_key", mode: "responsible_user" } as const;
const config = { provider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna", env: { COPILOT_GITHUB_TOKEN: "ambient-never-use", GH_TOKEN: "project-never-use" } };
const runInput = { companyId, agentId, responsibleUserId: owner, adapterType: "paperclip_runner", binding: selectedBinding, config };
const selected = await service.select({ ...runInput, userId: owner, runnerProvider: "acpx", acpxAgent: "copilot", model: config.model });
expect(await service.credential(selected)).toBe(token);
expect(JSON.stringify(await service.list(companyId, owner))).not.toContain(token);
const runtime = await prepareManagedAiRuntime(db, runInput);
try {
expect(runtime.config).toMatchObject({ provider: "acpx", acpxAgent: "copilot", model: config.model, env: { COPILOT_GITHUB_TOKEN: token, GH_TOKEN: "", GITHUB_TOKEN: "", OPENAI_API_KEY: "", ANTHROPIC_API_KEY: "" } });
expect(runtime.attribution).toMatchObject({ provider: "github", method: "api_key", connectionId: account.connectionId, grantId: account.grantId });
expect(config.env.COPILOT_GITHUB_TOKEN).toBe("ambient-never-use");
} finally { await runtime.cleanup(); }
await expect(access(runtime.home!)).rejects.toThrow();
await expect(service.select({ ...runInput, userId: "bob", runnerProvider: "acpx", acpxAgent: "copilot" })).rejects.toThrow();
await expect(service.select({ ...runInput, companyId: otherCompanyId, userId: owner, binding: { ...selectedBinding, mode: "shared", ...account }, runnerProvider: "acpx", acpxAgent: "copilot" })).rejects.toThrow();
const network = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("usage must not send credentials"));
try {
expect(await service.probeUsage(companyId, owner, account.connectionId, account.grantId)).toMatchObject({ provider: "github", status: "unsupported", limits: [] });
expect(network).not.toHaveBeenCalled();
} finally { network.mockRestore(); }
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, account.grantId));
await expect(prepareManagedAiRuntime(db, runInput)).rejects.toThrow();
});
it("marks explicit AI selection rejections without changing their HTTP contract", async () => {
const owner = "selection-blocker-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
@@ -807,12 +1024,18 @@ console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello"
await expect(access(runtime.home!)).rejects.toThrow();
});
it("preserves Google account defaults when the provider constraint migration is reapplied", async () => {
it("preserves Google and Copilot account defaults when the current provider constraint migration is reapplied", async () => {
const saved = await service.save(companyId, "bob", {
provider: "google", method: "api_key", ownership: "personal", name: "Google migration",
apiKey: "fixture", allAgents: true, agentIds: [],
}, "google-migration-key");
const migration = await readFile(new URL("../../../packages/db/src/migrations/0306_familiar_titania.sql", import.meta.url), "utf8");
await db.insert(companyMemberships).values({ companyId, principalId: "migration-copilot", principalType: "user", status: "active", membershipRole: "member" });
const copilot = await service.save(companyId, "migration-copilot", {
provider: "github", method: "api_key", ownership: "personal", name: "Copilot migration",
apiKey: "fixture", allAgents: true, agentIds: [],
}, "github_pat_fixture_migration");
await service.setDefault(companyId, "migration-copilot", copilot.grantId);
const migration = await readFile(new URL("../../../packages/db/src/migrations/0320_copilot_defaults.sql", import.meta.url), "utf8");
for (let attempt = 0; attempt < 2; attempt += 1) {
await db.transaction(async (tx) => {
for (const statement of migration.split("--> statement-breakpoint")) {
@@ -825,6 +1048,12 @@ console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello"
binding: { provider: "google", method: "api_key", mode: "responsible_user" },
});
expect(selected.grant.id).toBe(saved.grantId);
const selectedCopilot = await service.select({
companyId, agentId, userId: "migration-copilot", adapterType: "paperclip_runner",
runnerProvider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna",
binding: { provider: "github", method: "api_key", mode: "responsible_user" },
});
expect(selectedCopilot.grant.id).toBe(copilot.grantId);
});
it.each([false, true])("reports the authoritative connection-manager capability for custom grants (manager: %s)", async (manager) => {
@@ -372,7 +372,7 @@ describeEmbeddedPostgres("connectionIntentService", () => {
});
});
it("only advertises GitHub tool methods in search and setup options", async () => {
it("separates GitHub runtime discovery from tool setup options", async () => {
const service = connectionIntentService(db);
const search = await service.search(claims, "github");
const github = search.results.find((result) => result.service === "github");
@@ -383,6 +383,12 @@ describeEmbeddedPostgres("connectionIntentService", () => {
key: "mcp-key",
label: "Personal access token (advanced)",
auth: "api_key",
purpose: "tool",
}),
expect.objectContaining({
key: "copilot-token",
purpose: "ai",
setupPath: expect.stringContaining("/apps/connect?source=github"),
}),
],
}),
@@ -407,6 +413,7 @@ describeEmbeddedPostgres("connectionIntentService", () => {
expect(setup.service.methods.map((method) => method.key)).not.toContain(
"chat-agent",
);
expect(setup.service.methods.map((method) => method.key)).not.toContain("copilot-token");
});
it("serializes OAuth intent completion behind addressed-user membership revocation", async () => {
@@ -0,0 +1,101 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { environmentRuntimeService } from "../services/environment-runtime.js";
import type { PluginWorkerManager } from "../services/plugin-worker-manager.js";
const mocks = vi.hoisted(() => ({ metadata: vi.fn(), command: vi.fn(), environment: vi.fn(), bindings: vi.fn(), acquire: vi.fn(), realize: vi.fn(), release: vi.fn(), resolve: vi.fn(), general: vi.fn(), experimental: vi.fn(), defaults: vi.fn(), kubernetes: vi.fn(), managed: vi.fn() }));
vi.mock("../vendor/paperclip-runner/live/index.js", async importOriginal => ({ ...await importOriginal<object>(), probeCopilotMetadata: mocks.metadata }));
vi.mock("@paperclipai/adapter-utils/execution-target", () => ({ runAdapterExecutionTargetShellCommand: mocks.command }));
vi.mock("../middleware/logger.js", () => ({ logger: { warn: vi.fn() } }));
vi.mock("../services/environments.js", () => ({ environmentService: () => ({ getById: mocks.environment, listBoundCompanyIds: mocks.bindings, findKubernetesEnvironment: mocks.kubernetes, findManagedSandboxEnvironment: mocks.managed }) }));
vi.mock("../services/instance-settings.js", () => ({ instanceSettingsService: () => ({ get: mocks.defaults, getGeneral: mocks.general, getExperimental: mocks.experimental }) }));
vi.mock("../services/environment-runtime.js", () => ({ environmentRuntimeService: vi.fn(() => ({ acquireRunLease: mocks.acquire, realizeWorkspace: mocks.realize, getDriver: () => ({ releaseRunLease: mocks.release }) })) }));
vi.mock("../services/environment-execution-target.js", () => ({ resolveEnvironmentExecutionTarget: mocks.resolve }));
import { logger } from "../middleware/logger.js";
import { probeCopilotConnection, probeCopilotExecutionTarget } from "../services/copilot-connection-probe.js";
import { QUALIFIED_ACPX_PROFILES } from "../../../packages/paperclip-runner/src/drivers/acpx/qualified-profiles.js";
import type { Db } from "@paperclipai/db";
import type { AdapterExecutionTarget } from "@paperclipai/adapter-utils/execution-target";
const verified = { status: "verified", version: "1.0.88", profileDigest: QUALIFIED_ACPX_PROFILES.copilot.commandDigest, promptSent: false, models: [{ id: "gpt-5.6-luna", label: "GPT" }] };
const remote = { kind: "remote", remoteCwd: "/workspace" } as AdapterExecutionTarget;
beforeEach(() => {
vi.clearAllMocks(); mocks.general.mockResolvedValue({ executionMode: "local" }); mocks.experimental.mockResolvedValue({}); mocks.defaults.mockResolvedValue({ defaultEnvironmentId: null });
mocks.environment.mockResolvedValue({ id: "selected", driver: "sandbox", status: "active", config: { provider: "daytona" } }); mocks.bindings.mockResolvedValue(["company"]);
mocks.acquire.mockResolvedValue({ lease: { id: "lease", metadata: {} } }); mocks.realize.mockResolvedValue({}); mocks.release.mockResolvedValue(undefined); mocks.resolve.mockResolvedValue(remote);
mocks.command.mockResolvedValue({ timedOut: false, stdout: JSON.stringify(verified), exitCode: 0 }); mocks.metadata.mockResolvedValue(verified);
});
describe("Copilot connection verification", () => {
it.each(["ssh", "sandbox", "plugin"])("keeps an explicitly selected %s environment under managed-only execution", async driver => {
mocks.experimental.mockResolvedValue({ enableManagedSandboxOnly: true });
mocks.environment.mockResolvedValue({ id: "selected", driver, status: "active", config: { provider: "daytona" } });
expect(await probeCopilotConnection({} as Db, "company", "private-token", "selected")).toEqual(verified);
expect(mocks.managed).not.toHaveBeenCalled();
expect(mocks.resolve).toHaveBeenCalledWith(expect.objectContaining({ environment: expect.objectContaining({ id: "selected", driver }) }));
expect(mocks.metadata).not.toHaveBeenCalled();
expect(mocks.command).toHaveBeenCalledOnce();
});
it.each([null, "local"])("redirects only a local selection (%s) onto the managed environment", async selection => {
mocks.experimental.mockResolvedValue({ enableManagedSandboxOnly: true });
mocks.managed.mockResolvedValue({ id: "managed" });
mocks.environment.mockImplementation(async id => ({ id, driver: id === "local" ? "local" : "sandbox", status: "active", config: { provider: "daytona" } }));
expect(await probeCopilotConnection({} as Db, "company", "private-token", selection)).toEqual(verified);
expect(mocks.resolve).toHaveBeenCalledWith(expect.objectContaining({ environment: expect.objectContaining({ id: "managed" }) }));
expect(mocks.metadata).not.toHaveBeenCalled();
});
it("gives forced Kubernetes precedence over an explicitly selected remote and managed-only policy", async () => {
mocks.general.mockResolvedValue({ executionMode: "kubernetes" });
mocks.experimental.mockResolvedValue({ enableManagedSandboxOnly: true });
mocks.kubernetes.mockResolvedValue({ id: "kubernetes" });
mocks.environment.mockImplementation(async id => ({ id, driver: "sandbox", status: "active", config: { provider: "kubernetes" } }));
expect(await probeCopilotConnection({} as Db, "company", "private-token", "selected")).toEqual(verified);
expect(mocks.resolve).toHaveBeenCalledWith(expect.objectContaining({ environment: expect.objectContaining({ id: "kubernetes" }) }));
expect(mocks.managed).not.toHaveBeenCalled();
expect(mocks.metadata).not.toHaveBeenCalled();
});
it("probes the selected environment and releases its owned lease", async () => {
const db = {} as Db;
const pluginWorkerManager = {} as PluginWorkerManager;
expect(await probeCopilotConnection(db, "company", "private-token", "selected", "gpt-5.6-luna", { pluginWorkerManager })).toEqual(verified);
expect(environmentRuntimeService).toHaveBeenCalledWith(db, { pluginWorkerManager });
expect(mocks.metadata).not.toHaveBeenCalled(); expect(mocks.acquire).toHaveBeenCalledOnce();
expect(mocks.command.mock.calls[0]![3]).toMatchObject({ env: { COPILOT_GITHUB_TOKEN: "private-token", PAPERCLIP_COPILOT_PROBE_MODEL: "gpt-5.6-luna" }, timeoutSec: 35 });
expect(mocks.command.mock.calls[0]![2]).not.toContain("private-token");
expect(mocks.release).toHaveBeenCalledWith(expect.objectContaining({ status: "released" }));
});
it("rejects a foreign company before creating a lease or transmitting the token", async () => {
mocks.bindings.mockResolvedValue(["other-company"]);
await expect(probeCopilotConnection({} as Db, "company", "private-token", "selected")).rejects.toMatchObject({ status: 403 });
expect(mocks.command).not.toHaveBeenCalled(); expect(mocks.metadata).not.toHaveBeenCalled(); expect(mocks.acquire).not.toHaveBeenCalled();
});
it("does not fall back to host credentials when the remote runtime is missing", async () => {
mocks.command.mockResolvedValue({ timedOut: false, stdout: "", exitCode: 127 });
await expect(probeCopilotConnection({} as Db, "company", "private-token", "selected")).rejects.toMatchObject({ details: { code: "COPILOT_INSTALLATION_INVALID" } });
expect(mocks.metadata).not.toHaveBeenCalled(); expect(mocks.release).toHaveBeenCalledWith(expect.objectContaining({ status: "failed" }));
});
it("sanitizes remote errors and releases the lease on authentication failure", async () => {
mocks.command.mockResolvedValue({ timedOut: false, stdout: JSON.stringify({ status: "failed", code: "COPILOT_AUTH_REQUIRED", promptSent: false, message: "private-token" }), exitCode: 1 });
const error = await probeCopilotConnection({} as Db, "company", "private-token", "selected").catch(e => e);
expect(error.details.code).toBe("COPILOT_AUTH_REQUIRED"); expect(error.message).not.toContain("private-token"); expect(mocks.release).toHaveBeenCalledOnce();
});
it("preserves authentication classification when lease cleanup also fails", async () => {
mocks.command.mockResolvedValue({ timedOut: false, stdout: JSON.stringify({ status: "failed", code: "COPILOT_AUTH_REQUIRED", promptSent: false, message: "private-token" }), exitCode: 1 });
mocks.release.mockRejectedValue(new Error("cleanup private-token"));
const error = await probeCopilotConnection({} as Db, "company", "private-token", "selected").catch(e => e);
expect(error).toMatchObject({ status: 422, details: { code: "COPILOT_AUTH_REQUIRED" } });
expect(error.message).not.toContain("private-token");
expect(logger.warn).toHaveBeenCalledWith({ companyId: "company", environmentId: "selected", leaseId: "lease", code: "COPILOT_PROBE_CLEANUP_FAILED" }, expect.any(String));
expect(JSON.stringify(vi.mocked(logger.warn).mock.calls)).not.toContain("private-token");
expect(mocks.release).toHaveBeenCalledOnce();
});
it("does not verify a connection when its successful probe cannot clean up", async () => {
mocks.release.mockRejectedValue(new Error("cleanup private-token"));
const error = await probeCopilotConnection({} as Db, "company", "private-token", "selected").catch(e => e);
expect(error).toMatchObject({ status: 422, details: { code: "COPILOT_REQUEST_FAILED", cleanupCode: "COPILOT_PROBE_CLEANUP_FAILED" } });
expect(error.message).not.toContain("private-token");
expect(mocks.release).toHaveBeenCalledOnce();
});
it("rejects stale profile results and unavailable model selections", async () => {
mocks.command.mockResolvedValue({ timedOut: false, stdout: JSON.stringify({ ...verified, profileDigest: "stale" }), exitCode: 0 });
await expect(probeCopilotExecutionTarget("private-token", remote)).rejects.toMatchObject({ details: { code: "COPILOT_REQUEST_FAILED" } });
mocks.command.mockResolvedValue({ timedOut: false, stdout: JSON.stringify(verified), exitCode: 0 });
await expect(probeCopilotExecutionTarget("private-token", remote, "missing")).rejects.toMatchObject({ details: { code: "COPILOT_MODEL_UNAVAILABLE" } });
});
});
@@ -1,6 +1,7 @@
import express from "express";
import request from "supertest";
import { request as httpRequest } from "node:http";
import { createServer as createViteServer } from "vite";
import { afterEach, describe, expect, it } from "vitest";
import { idleAdmissionMiddleware, trackIdleRequestHandlers } from "../middleware/idle-admission.js";
import { idleWorkSnapshot, startTaskDrain, stopTaskDrain, readTaskDrain, beginIdleTrackedWork } from "../services/task-admission.js";
@@ -49,6 +50,36 @@ describe("idle admission", () => {
expect(idleWorkSnapshot().active).toBe(0);
});
it("tracks ordinary middleware with non-router stack metadata", async () => {
const server = app(), done = deferred();
const middleware = Object.assign(async (_req: express.Request, res: express.Response) => {
res.sendStatus(202); await done.promise;
}, { stack: { name: "middleware metadata" } });
server.use(middleware);
expect(() => trackIdleRequestHandlers(server)).not.toThrow();
await request(server).get("/work").expect(202);
expect(idleWorkSnapshot().active).toBe(1);
done.resolve();
await new Promise(resolve => setImmediate(resolve));
expect(idleWorkSnapshot().active).toBe(0);
});
it("supports real Vite Connect route metadata without losing async work", async () => {
const vite = await createViteServer({ configFile: false, appType: "custom",
server: { middlewareMode: true, watch: null }, optimizeDeps: { noDiscovery: true, include: [] } });
const server = app(), done = deferred();
try {
server.use(vite.middlewares);
server.post("/work", async (_req, res) => { res.sendStatus(202); await done.promise; });
expect(() => trackIdleRequestHandlers(server)).not.toThrow();
await request(server).post("/work").expect(202);
expect(idleWorkSnapshot().active).toBe(1);
done.resolve();
await new Promise(resolve => setImmediate(resolve));
expect(idleWorkSnapshot().active).toBe(0);
} finally { await vite.close(); }
});
it("tracks real Vite middleware without confusing Connect mount paths with Express routes", async () => {
const { createServer } = await import("vite");
const vite = await createServer({