fix(runtime): gate cofounder checkpoints
This commit is contained in:
1 parent
7a1dd647aa
commit
43583ef30b
5 files changed
+162
-25
No files matched your search
@@ -0,0 +1,119 @@
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("electron", () => ({
|
||||
safeStorage: {
|
||||
isEncryptionAvailable: () => false,
|
||||
getSelectedStorageBackend: () => "basic_text",
|
||||
encryptString: (value: string) => Buffer.from(value, "utf8"),
|
||||
decryptString: (value: Buffer) => value.toString("utf8"),
|
||||
},
|
||||
}));
|
||||
|
||||
import { AppDatabase } from "../../src/main/database";
|
||||
import {
|
||||
handleBeforeToolCall,
|
||||
type ActiveRunContext,
|
||||
} from "../../src/main/agent-runtime/hook-handlers";
|
||||
|
||||
function seedCofounderRun(db: AppDatabase) {
|
||||
const companyId = db.company.saveCompany({
|
||||
name: "Gate Co",
|
||||
description: "Cofounder runtime gate test",
|
||||
status: "active",
|
||||
executionMode: "ai_cofounder",
|
||||
});
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: "openai-codex",
|
||||
model: "gpt-5",
|
||||
envBindingText: "",
|
||||
notes: "fixture",
|
||||
});
|
||||
const agentId = db.agents.saveAgent({
|
||||
companyId,
|
||||
name: "Researcher",
|
||||
role: "Research Lead",
|
||||
title: "Research Lead",
|
||||
department: "product",
|
||||
status: "active",
|
||||
reportsTo: null,
|
||||
connectorId: "openai-codex",
|
||||
workspaceId: null,
|
||||
model: null,
|
||||
capabilities: "Research",
|
||||
budgetMonthlyUsd: 100,
|
||||
});
|
||||
const taskId = db.tasks.saveTask({
|
||||
companyId,
|
||||
title: "Draft market plan",
|
||||
description: "Prepare the market plan after approval.",
|
||||
assigneeAgentId: agentId,
|
||||
priority: "high",
|
||||
status: "in_progress",
|
||||
requiresFilesystem: false,
|
||||
});
|
||||
const runContext: ActiveRunContext = {
|
||||
runId: "run-1",
|
||||
agentId,
|
||||
companyId,
|
||||
taskId,
|
||||
connectorId: "openai-codex",
|
||||
modelId: "gpt-5",
|
||||
taskSessionId: "session-1",
|
||||
workspaceId: null,
|
||||
workspacePath: null,
|
||||
workspaceBaseline: null,
|
||||
state: "agent_active",
|
||||
};
|
||||
return { companyId, agentId, taskId, runContext };
|
||||
}
|
||||
|
||||
describe("cofounder approval runtime gate", () => {
|
||||
let tempDir: string;
|
||||
let db: AppDatabase;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await mkdtemp(join(tmpdir(), "cofounder-gate-"));
|
||||
db = new AppDatabase(tempDir);
|
||||
db.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
db.close();
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("blocks destructive tools while a cofounder plan review is pending", () => {
|
||||
const { companyId, agentId, taskId, runContext } = seedCofounderRun(db);
|
||||
const approvalId = db.approvalsMod.requestApproval({
|
||||
companyId,
|
||||
relatedTaskId: taskId,
|
||||
requestedByAgentId: agentId,
|
||||
type: "plan_review",
|
||||
payloadSummary: "Review plan",
|
||||
impactSummary: "Human approval is required before execution.",
|
||||
payloadJson: "{}",
|
||||
});
|
||||
|
||||
const blocking = db.approvalsMod.getBlockingApproval(companyId, agentId, taskId);
|
||||
expect(blocking?.id).toBe(approvalId);
|
||||
|
||||
const result = handleBeforeToolCall(
|
||||
{
|
||||
db,
|
||||
logger: { info: () => {}, warn: () => {}, error: () => {} },
|
||||
},
|
||||
runContext,
|
||||
{
|
||||
toolName: "bash",
|
||||
toolCallId: "call-1",
|
||||
args: { command: "touch blocked.txt" },
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.block).toBe(true);
|
||||
expect(result.reason).toContain("plan_review");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user