mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 10:30:37 +09:00
Enforce execution-policy stage handoffs
This commit is contained in:
parent
9eaf72ab31
commit
ec75cabcd8
8 changed files with 949 additions and 138 deletions
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue