Q88: a sign-in is saved only if a run can use and renew it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4668396f25
commit
7eef3d459f
38 files changed
+127
-398
No files matched your search
@@ -12,7 +12,7 @@ import { PROVIDER_CATALOG } from "../../src/shared/provider-catalog";
|
||||
const PROVIDER_SECRET_SAMPLES: Record<string, string> = {
|
||||
openai: "sk-live-provider",
|
||||
anthropic: "sk-live-provider",
|
||||
"claude-pro": "claude-session-token",
|
||||
"claude-pro": JSON.stringify({ access: "live-access", refresh: "live-refresh", expires: 1_900_000_000_000 }),
|
||||
xai: "sk-live-provider",
|
||||
groq: "sk-live-provider",
|
||||
mistral: "sk-live-provider",
|
||||
@@ -20,10 +20,10 @@ const PROVIDER_SECRET_SAMPLES: Record<string, string> = {
|
||||
openrouter: "sk-or-live-provider",
|
||||
kimi: "sk-live-provider",
|
||||
"kimi-cn": "sk-live-provider",
|
||||
"kimi-code": "kimi-session-token",
|
||||
"github-copilot": "ghu_live_provider",
|
||||
"google-antigravity": "antigravity-session",
|
||||
"gpt-account": "chatgpt-session",
|
||||
"kimi-code": JSON.stringify({ access: "live-access", refresh: "live-refresh", expires: 1_900_000_000_000 }),
|
||||
"github-copilot": JSON.stringify({ access: "live-access", refresh: "live-refresh", expires: 1_900_000_000_000 }),
|
||||
"google-antigravity": JSON.stringify({ access: "live-access", refresh: "live-refresh", expires: 1_900_000_000_000, projectId: "acme-project" }),
|
||||
"gpt-account": JSON.stringify({ access: "live-access", refresh: "live-refresh", expires: 1_900_000_000_000, accountId: "acct_1" }),
|
||||
"azure-openai": JSON.stringify({
|
||||
baseUrl: "https://acme.openai.azure.com",
|
||||
deployment: "gpt-5.4",
|
||||
|
||||
@@ -28,7 +28,7 @@ describe("agent model", () => {
|
||||
};
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-model-${randomUUID()}.db`));
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
const tasks: Record<string, string> = {};
|
||||
@@ -54,7 +54,7 @@ describe("agent model", () => {
|
||||
const turn = async (): Promise<AgentTurnResult> => ({ text: "done", toolCalls: [], rawResponseJson: "{}" });
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-model-az-${randomUUID()}.db`));
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
let taskId = "";
|
||||
@@ -84,7 +84,7 @@ describe("agent model", () => {
|
||||
};
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-model-pin-${randomUUID()}.db`));
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
let taskId = "";
|
||||
|
||||
@@ -60,7 +60,7 @@ function scriptedTeam(): (input: ProviderToolTurnInput) => Promise<AgentTurnResu
|
||||
describe("inter-agent reply", () => {
|
||||
test("an @mention wakes the teammate, who replies in the same thread", async () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-reply-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(scriptedTeam()));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(scriptedTeam()));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const taskExecution = new TaskExecutionService(connection.db, providers);
|
||||
const snapshot = new SnapshotService(connection.db);
|
||||
|
||||
@@ -90,7 +90,7 @@ describe("autonomous bootstrap run", () => {
|
||||
const workspacePath = join(tmpdir(), `ac-auto-ws-${randomUUID()}`);
|
||||
mkdirSync(workspacePath, { recursive: true });
|
||||
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(scriptedCeo()));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(scriptedCeo()));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const snapshot = new SnapshotService(connection.db);
|
||||
|
||||
@@ -184,7 +184,7 @@ describe("autonomous bootstrap run", () => {
|
||||
test("a goal created after onboarding reaches the CEO and runs autonomously", async () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-goal-${randomUUID()}.db`));
|
||||
// A minimal well-behaved agent: does its session, closes its task, then stops.
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(async (input: ProviderToolTurnInput) => {
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(async (input: ProviderToolTurnInput) => {
|
||||
const firstUser = input.messages.find((message) => message.role === "user");
|
||||
const taskId = firstUser && firstUser.role === "user" ? /Task id: (\S+)/.exec(firstUser.text)?.[1] : undefined;
|
||||
const alreadyClosed = input.messages.some(
|
||||
|
||||
@@ -68,7 +68,7 @@ describe("founder⇄CEO chat", () => {
|
||||
test("a founder DM wakes the CEO, who sees who is asking and replies in the pinned conversation", async () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-ceo-chat-${randomUUID()}.db`));
|
||||
const capturedWakePrompts: string[] = [];
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(scriptedCeo(capturedWakePrompts)));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(scriptedCeo(capturedWakePrompts)));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const taskExecution = new TaskExecutionService(connection.db, providers);
|
||||
const chat = new ChatService(connection.db);
|
||||
@@ -144,7 +144,7 @@ describe("founder⇄CEO chat", () => {
|
||||
|
||||
test("a CEO who answers in plain text still has the reply delivered into the founder's thread", async () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-ceo-plain-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(plainTextCeo()));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(plainTextCeo()));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const taskExecution = new TaskExecutionService(connection.db, providers);
|
||||
const chat = new ChatService(connection.db);
|
||||
|
||||
@@ -38,7 +38,7 @@ function scriptedCeoReply(): (input: ProviderToolTurnInput) => Promise<AgentTurn
|
||||
describe("channel anchor wake", () => {
|
||||
test("a plain user message in a channel wakes the most senior member, who replies", async () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-anchor-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(scriptedCeoReply()));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(scriptedCeoReply()));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const taskExecution = new TaskExecutionService(connection.db, providers);
|
||||
const chat = new ChatService(connection.db);
|
||||
|
||||
@@ -35,7 +35,7 @@ async function founderLine() {
|
||||
return { text: `Reply ${wakePrompts.length}.`, toolCalls: [], rawResponseJson: "{}" };
|
||||
};
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-chatctx-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
const chat = new ChatService(connection.db);
|
||||
@@ -154,7 +154,7 @@ describe("chat wake context", () => {
|
||||
}
|
||||
return { text: "Switching to Postgres.", toolCalls: [], rawResponseJson: "{}" };
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let ceoId = "";
|
||||
|
||||
@@ -18,7 +18,7 @@ const testVault = {
|
||||
|
||||
function company(turn: (input: ProviderToolTurnInput, db: DatabaseConnection["db"]) => AgentTurnResult) {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-goalchg-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(async (input) => turn(input, connection.db)));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(async (input) => turn(input, connection.db)));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
|
||||
@@ -77,7 +77,7 @@ describe("interactive execution lane", () => {
|
||||
}
|
||||
return turn("done");
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(script));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(script));
|
||||
providers.saveCredential({ orgId, providerId, secret: "sk-test-key", model: "gpt-5.4" });
|
||||
const taskExecution = new TaskExecutionService(connection.db, providers);
|
||||
|
||||
@@ -164,7 +164,7 @@ describe("interactive execution lane", () => {
|
||||
}
|
||||
return turn("done");
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(script));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(script));
|
||||
providers.saveCredential({ orgId, providerId, secret: "sk-test-key", model: "gpt-5.4" });
|
||||
const taskExecution = new TaskExecutionService(connection.db, providers);
|
||||
|
||||
@@ -229,7 +229,7 @@ describe("interactive execution lane", () => {
|
||||
const alwaysOverloaded = async (): Promise<AgentTurnResult> => {
|
||||
throw new ProviderHttpError("The engine is currently overloaded, please try again later", null, 529, false, "overloaded_error");
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(alwaysOverloaded));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(alwaysOverloaded));
|
||||
providers.saveCredential({ orgId, providerId, secret: "sk-test-key", model: "gpt-5.4" });
|
||||
const taskExecution = new TaskExecutionService(connection.db, providers);
|
||||
const wakeDana = () => dispatchAgentTool({ db: connection.db, organization, chat }, {
|
||||
|
||||
@@ -25,7 +25,7 @@ describe("session checkpoints", () => {
|
||||
? { text: "Audit done: all clean.", toolCalls: [], rawResponseJson: "{}" }
|
||||
: { text: "Looking into it.", toolCalls: [{ id: `c_${randomUUID()}`, name: "read_messages", arguments: {} }], rawResponseJson: "{}" };
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-checkpoint-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("run journal", () => {
|
||||
throw new Error("Provider dropped the connection mid-run");
|
||||
};
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-journal-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
|
||||
@@ -28,7 +28,7 @@ async function company(reply: (prompt: string) => AgentTurnResult) {
|
||||
return reply(prompt);
|
||||
};
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-memory-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
|
||||
@@ -22,7 +22,7 @@ const testVault = {
|
||||
|
||||
function company(turn: (input: ProviderToolTurnInput) => Promise<AgentTurnResult>) {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-stop-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
@@ -45,7 +45,7 @@ describe("stopping runs", () => {
|
||||
return { text: "Announcing.", toolCalls: [{ id: "c1", name: "send_message", arguments: { channel: "general", body: "Should never be posted." } }], rawResponseJson: "{}" };
|
||||
};
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-stop-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
@@ -116,7 +116,7 @@ describe("stopping runs", () => {
|
||||
return { text: "done", toolCalls: [], rawResponseJson: "{}" };
|
||||
};
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-deadline-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers, undefined, undefined, undefined, 200);
|
||||
let orgId = "";
|
||||
|
||||
@@ -24,7 +24,7 @@ describe("money budgets", () => {
|
||||
return { text: "Worked.", toolCalls: [], rawResponseJson: JSON.stringify({ usage: { input_tokens: 2_000_000, output_tokens: 1_000_000 } }) };
|
||||
};
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-spend-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, turn);
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, turn);
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
|
||||
@@ -25,7 +25,7 @@ function company() {
|
||||
return { text: "done", toolCalls: [], rawResponseJson: "{}" };
|
||||
};
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-stale-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
|
||||
@@ -50,7 +50,7 @@ describe("task prompt scope", () => {
|
||||
return { text: step.text, toolCalls: step.toolCalls, rawResponseJson: "{}" };
|
||||
};
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-scope-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
@@ -91,7 +91,7 @@ describe("task prompt scope", () => {
|
||||
calls += 1;
|
||||
return { text: "done", toolCalls: [], rawResponseJson: "{}" };
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
@@ -126,7 +126,7 @@ describe("task prompt scope", () => {
|
||||
test("a manual write run waits while another run is changing the same workspace", async () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-wsplock-${randomUUID()}.db`));
|
||||
const turn = async (): Promise<AgentTurnResult> => ({ text: "done", toolCalls: [], rawResponseJson: "{}" });
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
|
||||
@@ -29,7 +29,7 @@ function company() {
|
||||
prompts.push(first.text);
|
||||
return { text: "done", toolCalls: [], rawResponseJson: "{}" };
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(turn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(turn));
|
||||
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
const organization = new OrganizationService(connection.db);
|
||||
|
||||
@@ -135,7 +135,7 @@ describe("agent instruction wiring", () => {
|
||||
capturedInstructions = input.instructions ?? "";
|
||||
return { text: "done", toolCalls: [], rawResponseJson: JSON.stringify({ id: "resp-1" }) };
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(fakeToolTurn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(fakeToolTurn));
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
|
||||
let orgId = "";
|
||||
@@ -190,7 +190,7 @@ describe("agent instruction wiring", () => {
|
||||
capturedPrompt = first.text;
|
||||
return { text: "done", toolCalls: [], rawResponseJson: JSON.stringify({ id: "resp-1" }) };
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(fakeToolTurn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(fakeToolTurn));
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
let taskId = "";
|
||||
|
||||
@@ -19,7 +19,7 @@ describe("character accounting", () => {
|
||||
const toolCalls = [{ id: "c1", name: "send_message", arguments: { channel: "general", body: "Shipping today." } }];
|
||||
const turn = async (): Promise<AgentTurnResult> => ({ text: "Posting.", toolCalls, rawResponseJson: "{}" });
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-chars-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, turn);
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, turn);
|
||||
let orgId = "";
|
||||
let providerId = "";
|
||||
connection.transaction(() => {
|
||||
|
||||
@@ -21,7 +21,7 @@ const TEXT_ONLY = async (): Promise<AgentTurnResult> => ({ text: "Sure, the code
|
||||
|
||||
function harness(turn: (input: ProviderToolTurnInput) => Promise<AgentTurnResult>) {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-model-check-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, turn);
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, turn);
|
||||
let orgId = "";
|
||||
let providerId = "";
|
||||
connection.transaction(() => {
|
||||
|
||||
@@ -109,7 +109,7 @@ describe("provider errors keep their status and machine code", () => {
|
||||
test("our own monthly limit is a typed error, and a failed request keeps its status and code", async () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-codes-${randomUUID()}.db`));
|
||||
const vault = { encryptSecret: (secret: string) => Buffer.from(secret, "utf8"), decryptSecret: (ciphertext: Buffer) => ciphertext.toString("utf8") };
|
||||
const providers = new ProviderService(connection.db, providerAdapters, vault, undefined, async () => { throw new ProviderHttpError("Overloaded", null, 529, false, "overloaded_error"); });
|
||||
const providers = new ProviderService(connection.db, providerAdapters, vault, async () => { throw new ProviderHttpError("Overloaded", null, 529, false, "overloaded_error"); });
|
||||
let orgId = "";
|
||||
let providerId = "";
|
||||
connection.transaction(() => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { interpretKimiPollResponse } from "../../src/main/services/oauth/kimi-co
|
||||
import { extractAccountId } from "../../src/main/services/oauth/openai-codex-flow";
|
||||
import { readProjectId, requireAntigravityClientSecret } from "../../src/main/services/oauth/google-antigravity-flow";
|
||||
import { generatePkce } from "../../src/main/services/oauth/pkce";
|
||||
import { parseAccessToken, parseAccountToken } from "../../src/main/services/provider-auth-profiles";
|
||||
import { providerAdapters } from "../../src/main/services/provider-adapters";
|
||||
|
||||
function makeJwt(claims: Record<string, unknown>): string {
|
||||
const part = (value: unknown) => Buffer.from(JSON.stringify(value)).toString("base64url");
|
||||
@@ -112,18 +112,20 @@ describe("pkce generation", () => {
|
||||
});
|
||||
|
||||
describe("oauth credential parsing", () => {
|
||||
test("accepts a raw token", () => {
|
||||
expect(parseAccessToken("ghu_raw")).toEqual({ type: "access_token", accessToken: "ghu_raw" });
|
||||
expect(parseAccountToken("chatgpt_raw")).toEqual({ type: "account_token", accessToken: "chatgpt_raw" });
|
||||
const signIn = (extra: Record<string, unknown> = {}) => JSON.stringify({ access: "live_token", refresh: "r", expires: 1_900_000_000_000, ...extra });
|
||||
|
||||
test("a stored sign-in is accepted only with what a run needs to use and renew it", () => {
|
||||
expect(providerAdapters["github-copilot"].parseAuthProfile(signIn())).toEqual({ type: "access_token", accessToken: "live_token" });
|
||||
expect(providerAdapters["gpt-account"].parseAuthProfile(signIn({ accountId: "acct_1" }))).toEqual({ type: "account_token", accessToken: "live_token" });
|
||||
expect(providerAdapters["google-antigravity"].parseAuthProfile(signIn({ projectId: "proj-1" }))).toEqual({ type: "access_token", accessToken: "live_token" });
|
||||
});
|
||||
|
||||
test("extracts the access token from native OAuth credential JSON", () => {
|
||||
const secret = JSON.stringify({ access: "live_token", refresh: "r", expires: 123 });
|
||||
expect(parseAccessToken(secret)).toEqual({ type: "access_token", accessToken: "live_token" });
|
||||
expect(parseAccountToken(secret)).toEqual({ type: "account_token", accessToken: "live_token" });
|
||||
});
|
||||
|
||||
test("rejects credential JSON without an access token", () => {
|
||||
expect(() => parseAccessToken(JSON.stringify({ refresh: "r" }))).toThrow("missing an access token");
|
||||
test("a pasted token, or a sign-in missing its refresh token, expiry or provider extras, is refused at save time", () => {
|
||||
expect(() => providerAdapters["github-copilot"].parseAuthProfile("ghu_raw")).toThrow("Sign in to GitHub Copilot");
|
||||
expect(() => providerAdapters["claude-pro"].parseAuthProfile(JSON.stringify({ access: "a", expires: 1 }))).toThrow("missing its refresh token");
|
||||
expect(() => providerAdapters["kimi-code"].parseAuthProfile(JSON.stringify({ access: "a", refresh: "r" }))).toThrow("missing its expiry");
|
||||
expect(() => providerAdapters["gpt-account"].parseAuthProfile(signIn())).toThrow("missing its ChatGPT account id");
|
||||
expect(() => providerAdapters["google-antigravity"].parseAuthProfile(signIn())).toThrow("missing its Google Cloud project id");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -141,7 +141,7 @@ describe("provider service", () => {
|
||||
let mode: "capable" | "text" | "down" = "capable";
|
||||
const seen: string[] = [];
|
||||
const capable: (input: ProviderToolTurnInput) => Promise<AgentTurnResult> = answeringModelCheck(async () => ({ text: "", toolCalls: [], rawResponseJson: "{}" }));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, async (input) => {
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, async (input) => {
|
||||
seen.push(`${input.credential.apiKey}:${input.model}`);
|
||||
if (mode === "down") throw new Error("401 invalid x-api-key");
|
||||
return mode === "text" ? { text: "I am healthy.", toolCalls: [], rawResponseJson: "{}" } : capable(input);
|
||||
@@ -173,98 +173,27 @@ describe("provider service", () => {
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("audits provider sign-in launches with the employee actor", () => {
|
||||
test("a sign-in provider saves only a native sign-in, recorded as coming from the OAuth login", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const snapshot = new SnapshotService(connection.db);
|
||||
let launched: { providerId: string; label: string; signInUrl: string; partition: string } | null = null;
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, (input) => { launched = input; });
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault);
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
let copilotProviderId = "";
|
||||
const signIn = JSON.stringify({ access: "ghu_live_provider", refresh: "ghr_refresh", expires: 1_900_000_000_000 });
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = createOrganization(organization);
|
||||
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
copilotProviderId = providerId(connection, orgId, "github-copilot");
|
||||
});
|
||||
|
||||
expect(() => providers.launchSignInWindow({
|
||||
orgId,
|
||||
providerId: copilotProviderId,
|
||||
employeeId: "emp_missing",
|
||||
})).toThrow("Provider sign-in employee was not found.");
|
||||
|
||||
connection.transaction(() => providers.launchSignInWindow({ orgId, providerId: copilotProviderId, employeeId }));
|
||||
|
||||
const state = snapshot.load();
|
||||
expect(launched).toMatchObject({
|
||||
providerId: copilotProviderId,
|
||||
label: "GitHub Copilot",
|
||||
signInUrl: "https://github.com/login/device",
|
||||
partition: `persist:agentcompany-provider-${copilotProviderId}`,
|
||||
});
|
||||
expect(state.providerAuthSessions[0]).toMatchObject({
|
||||
orgId,
|
||||
providerId: copilotProviderId,
|
||||
providerKey: "github-copilot",
|
||||
employeeId,
|
||||
partition: `persist:agentcompany-provider-${copilotProviderId}`,
|
||||
completedAt: null,
|
||||
});
|
||||
expect(state.providerAuthSessions[0]?.state).toHaveLength(32);
|
||||
expect(state.providerAuthSessions[0]?.nonce).toHaveLength(32);
|
||||
expect(state.providerAuthSessions[0]?.codeVerifierSha256).toHaveLength(64);
|
||||
expect(state.auditEvents).toContainEqual(expect.objectContaining({
|
||||
entityType: "provider",
|
||||
entityId: copilotProviderId,
|
||||
actorType: "employee",
|
||||
actorId: employeeId,
|
||||
action: "sign_in_window_opened",
|
||||
}));
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("binds OAuth credentials to a sign-in session source", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const snapshot = new SnapshotService(connection.db);
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, () => {});
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
let copilotProviderId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = createOrganization(organization);
|
||||
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
copilotProviderId = providerId(connection, orgId, "github-copilot");
|
||||
});
|
||||
|
||||
expect(() => connection.transaction(() => providers.saveCredential({
|
||||
orgId,
|
||||
providerId: copilotProviderId,
|
||||
secret: "ghu_live_provider",
|
||||
model: "claude-opus-4-5",
|
||||
employeeId,
|
||||
}))).toThrow("Provider sign-in session is required.");
|
||||
|
||||
connection.transaction(() => {
|
||||
providers.launchSignInWindow({ orgId, providerId: copilotProviderId, employeeId });
|
||||
providers.saveCredential({
|
||||
orgId,
|
||||
providerId: copilotProviderId,
|
||||
secret: "ghu_live_provider",
|
||||
model: "claude-opus-4-5",
|
||||
employeeId,
|
||||
});
|
||||
});
|
||||
expect(() => connection.transaction(() => providers.saveCredential({ orgId, providerId: copilotProviderId, secret: "ghu_live_provider", model: "claude-opus-4-5" })))
|
||||
.toThrow("Sign in to GitHub Copilot; a pasted token cannot be renewed when it expires.");
|
||||
connection.transaction(() => providers.saveCredential({ orgId, providerId: copilotProviderId, secret: signIn, model: "claude-opus-4-5" }));
|
||||
|
||||
const state = snapshot.load();
|
||||
const profile = state.providerAuthProfiles.find((item) => item.providerId === copilotProviderId && item.active);
|
||||
const session = state.providerAuthSessions.find((item) => item.providerId === copilotProviderId);
|
||||
if (!profile || !session) throw new Error("Provider auth evidence was not created.");
|
||||
expect(profile).toMatchObject({ sourceType: "provider_sign_in_session", sourceRef: session.id });
|
||||
expect(session.completedAt).toBeTruthy();
|
||||
expect(profile).toMatchObject({ sourceType: "provider_oauth_login", sourceRef: null });
|
||||
expect(state.providers.find((item) => item.id === copilotProviderId)?.enabled).toBe(true);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ describe("monthly quota reservation", () => {
|
||||
return { text: "done", toolCalls: [], rawResponseJson: "{}" };
|
||||
};
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-quota-${randomUUID()}.db`));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, turn);
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, turn);
|
||||
let orgId = "";
|
||||
let providerId = "";
|
||||
connection.transaction(() => {
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("rate limits seen on successful calls", () => {
|
||||
});
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-ratelimit-${randomUUID()}.db`));
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, turn);
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, turn);
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
let providerId = "";
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("task execution service", () => {
|
||||
if (first?.role === "user") capturedPrompt = first.text;
|
||||
return { text: "executed:gpt-5.4", toolCalls: [], rawResponseJson: JSON.stringify({ id: "resp-task-1" }) };
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(fakeToolTurn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(fakeToolTurn));
|
||||
const skills = new SkillService(connection.db, join(tmpdir(), `agentcompany-task-skills-${randomUUID()}`));
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
const skillDir = join(tmpdir(), `agentcompany-task-skill-${randomUUID()}`);
|
||||
@@ -154,7 +154,7 @@ describe("task execution service", () => {
|
||||
toolCalls: [],
|
||||
rawResponseJson: JSON.stringify({ id: "resp-task-approved" }),
|
||||
});
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(fakeToolTurn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(fakeToolTurn));
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
@@ -293,7 +293,7 @@ describe("task execution service", () => {
|
||||
const fakeToolTurn = async (): Promise<AgentTurnResult> => {
|
||||
throw new Error("provider rejected task");
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(fakeToolTurn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(fakeToolTurn));
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
let taskId = "";
|
||||
@@ -351,7 +351,7 @@ describe("task execution service", () => {
|
||||
turns += 1;
|
||||
return { text: "done", toolCalls: [], rawResponseJson: JSON.stringify({ id: `resp-${turns}` }) };
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(fakeToolTurn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(fakeToolTurn));
|
||||
const execution = new TaskExecutionService(connection.db, providers);
|
||||
let orgId = "";
|
||||
let taskId = "";
|
||||
@@ -850,7 +850,7 @@ describe("task execution service", () => {
|
||||
const fakeToolTurn = async (): Promise<AgentTurnResult> => {
|
||||
throw new ProviderHttpError("The usage limit has been reached", null, 429, false, "usage_limit_reached");
|
||||
};
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, answeringModelCheck(fakeToolTurn));
|
||||
const providers = new ProviderService(connection.db, providerAdapters, testVault, answeringModelCheck(fakeToolTurn));
|
||||
let nowMs = Date.now();
|
||||
const execution = new TaskExecutionService(connection.db, providers, undefined, undefined, () => nowMs);
|
||||
let orgId = "";
|
||||
|
||||
Reference in new issue
Block a user