Enforce execution-policy stage handoffs

This commit is contained in:
dotta 2026-04-08 08:05:35 -05:00
parent 9eaf72ab31
commit ec75cabcd8
8 changed files with 949 additions and 138 deletions

View file

@ -369,6 +369,252 @@ describe("codex execute", () => {
}
});
it("renders execution-stage wake instructions for reviewer and executor roles", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codex-execute-stage-wake-"));
const workspace = path.join(root, "workspace");
const commandPath = path.join(root, "codex");
const capturePath = path.join(root, "capture.json");
await fs.mkdir(workspace, { recursive: true });
await writeFakeCodexCommand(commandPath);
const previousHome = process.env.HOME;
process.env.HOME = root;
try {
const result = await execute({
runId: "run-stage-wake",
agent: {
id: "agent-1",
companyId: "company-1",
name: "Codex Coder",
adapterType: "codex_local",
adapterConfig: {},
},
runtime: {
sessionId: null,
sessionParams: null,
sessionDisplayId: null,
taskKey: null,
},
config: {
command: commandPath,
cwd: workspace,
env: {
PAPERCLIP_TEST_CAPTURE_PATH: capturePath,
},
promptTemplate: "Follow the paperclip heartbeat.",
},
context: {
issueId: "issue-1",
taskId: "issue-1",
wakeReason: "execution_review_requested",
paperclipWake: {
reason: "execution_review_requested",
issue: {
id: "issue-1",
identifier: "PAP-1207",
title: "implement the plan of PAP-1200",
status: "in_review",
priority: "medium",
},
executionStage: {
wakeRole: "reviewer",
stageId: "stage-1",
stageType: "review",
currentParticipant: { type: "agent", agentId: "qa-agent" },
returnAssignee: { type: "agent", agentId: "coder-agent" },
lastDecisionOutcome: null,
allowedActions: ["approve", "request_changes"],
},
commentIds: [],
latestCommentId: null,
comments: [],
commentWindow: {
requestedCount: 0,
includedCount: 0,
missingCount: 0,
},
truncated: false,
fallbackFetchNeeded: false,
},
},
authToken: "run-jwt-token",
onLog: async () => {},
});
expect(result.exitCode).toBe(0);
const capture = JSON.parse(await fs.readFile(capturePath, "utf8")) as CapturePayload;
expect(capture.prompt).toContain("execution wake role: reviewer");
expect(capture.prompt).toContain("You are waking as the active reviewer for this issue.");
expect(capture.prompt).toContain("Do not execute the task itself or continue executor work.");
expect(capture.prompt).toContain("allowed actions: approve, request_changes");
const executorCapturePath = path.join(root, "capture-executor.json");
const executorResult = await execute({
runId: "run-stage-wake-executor",
agent: {
id: "agent-1",
companyId: "company-1",
name: "Codex Coder",
adapterType: "codex_local",
adapterConfig: {},
},
runtime: {
sessionId: null,
sessionParams: null,
sessionDisplayId: null,
taskKey: null,
},
config: {
command: commandPath,
cwd: workspace,
env: {
PAPERCLIP_TEST_CAPTURE_PATH: executorCapturePath,
},
promptTemplate: "Follow the paperclip heartbeat.",
},
context: {
issueId: "issue-1",
taskId: "issue-1",
wakeReason: "execution_changes_requested",
paperclipWake: {
reason: "execution_changes_requested",
issue: {
id: "issue-1",
identifier: "PAP-1207",
title: "implement the plan of PAP-1200",
status: "in_progress",
priority: "medium",
},
executionStage: {
wakeRole: "executor",
stageId: "stage-1",
stageType: "review",
currentParticipant: { type: "agent", agentId: "qa-agent" },
returnAssignee: { type: "agent", agentId: "coder-agent" },
lastDecisionOutcome: "changes_requested",
allowedActions: ["address_changes", "resubmit"],
},
commentIds: [],
latestCommentId: null,
comments: [],
commentWindow: {
requestedCount: 0,
includedCount: 0,
missingCount: 0,
},
truncated: false,
fallbackFetchNeeded: false,
},
},
authToken: "run-jwt-token",
onLog: async () => {},
});
expect(executorResult.exitCode).toBe(0);
const executorCapture = JSON.parse(await fs.readFile(executorCapturePath, "utf8")) as CapturePayload;
expect(executorCapture.prompt).toContain("execution wake role: executor");
expect(executorCapture.prompt).toContain("You are waking because changes were requested in the execution workflow.");
expect(executorCapture.prompt).toContain("allowed actions: address_changes, resubmit");
} finally {
if (previousHome === undefined) delete process.env.HOME;
else process.env.HOME = previousHome;
await fs.rm(root, { recursive: true, force: true });
}
});
it("renders an issue-scoped wake prompt even when the wake has no comments yet", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codex-execute-issue-wake-"));
const workspace = path.join(root, "workspace");
const commandPath = path.join(root, "codex");
const capturePath = path.join(root, "capture.json");
await fs.mkdir(workspace, { recursive: true });
await writeFakeCodexCommand(commandPath);
const previousHome = process.env.HOME;
process.env.HOME = root;
try {
const result = await execute({
runId: "run-issue-wake",
agent: {
id: "agent-1",
companyId: "company-1",
name: "Codex Coder",
adapterType: "codex_local",
adapterConfig: {},
},
runtime: {
sessionId: null,
sessionParams: null,
sessionDisplayId: null,
taskKey: null,
},
config: {
command: commandPath,
cwd: workspace,
env: {
PAPERCLIP_TEST_CAPTURE_PATH: capturePath,
},
promptTemplate: "Follow the paperclip heartbeat.",
},
context: {
issueId: "issue-1",
taskId: "issue-1",
wakeReason: "issue_assigned",
paperclipWake: {
reason: "issue_assigned",
issue: {
id: "issue-1",
identifier: "PAP-1201",
title: "Fix gallery opening for inline images",
status: "todo",
priority: "medium",
},
commentIds: [],
latestCommentId: null,
comments: [],
commentWindow: {
requestedCount: 0,
includedCount: 0,
missingCount: 0,
},
truncated: false,
fallbackFetchNeeded: false,
},
},
authToken: "run-jwt-token",
onLog: async () => {},
});
expect(result.exitCode).toBe(0);
expect(result.errorMessage).toBeNull();
const capture = JSON.parse(await fs.readFile(capturePath, "utf8")) as CapturePayload;
expect(capture.paperclipEnvKeys).toContain("PAPERCLIP_WAKE_PAYLOAD_JSON");
expect(capture.paperclipWakePayloadJson).not.toBeNull();
expect(JSON.parse(capture.paperclipWakePayloadJson ?? "{}")).toMatchObject({
reason: "issue_assigned",
issue: {
identifier: "PAP-1201",
title: "Fix gallery opening for inline images",
status: "todo",
priority: "medium",
},
commentIds: [],
});
expect(capture.prompt).toContain("## Paperclip Wake Payload");
expect(capture.prompt).toContain("Do not switch to another issue until you have handled this wake.");
expect(capture.prompt).toContain("- issue: PAP-1201 Fix gallery opening for inline images");
expect(capture.prompt).toContain("- pending comments: 0/0");
expect(capture.prompt).toContain("- issue status: todo");
} finally {
if (previousHome === undefined) delete process.env.HOME;
else process.env.HOME = previousHome;
await fs.rm(root, { recursive: true, force: true });
}
});
it("uses a compact wake delta instead of the full heartbeat prompt when resuming a session", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codex-execute-resume-wake-"));
const workspace = path.join(root, "workspace");