import { describe, expect, it } from "vitest"; import { canonicalNativeRuntimeContextDigest } from "../../vendor/paperclip-runner/index.js"; import { buildNativeExecutionInput } from "./native-execution-input.js"; import { rebindNativeSessionCheckpoint } from "./native-session-resume.js"; import { nativeRuntimeContextFixture } from "./runtime-context.test-fixture.js"; const companyId = "10000000-0000-4000-8000-000000000001"; const issueId = "20000000-0000-4000-8000-000000000002"; const agentId = "30000000-0000-4000-8000-000000000003"; const previousRunId = "40000000-0000-4000-8000-000000000004"; const currentRunId = "50000000-0000-4000-8000-000000000005"; const normalizedSessionId = "60000000-0000-4000-8000-000000000006"; function execution(runId: string, cwd = "/workspace") { return buildNativeExecutionInput({ companyId, runId, issue: { id: issueId, identifier: "DOT-2", title: "Test", description: null, workMode: "standard" }, taskPrompt: "Only the current turn", agentId, workspace: { id: runId, cwd, repoUrl: null, repoRef: null, branchName: null }, normalizedSessionId, provider: "codex", completionContract: { id: "70000000-0000-4000-8000-000000000007", sha256: `sha256:${"a".repeat(64)}`, schemaVersion: "paperclip.run-result.v1", contract: { revision: "1", objective: "Test session resumption", criteria: [{ id: "objective", requirement: "Answer the current turn" }], }, }, runtimeContext: nativeRuntimeContextFixture(), }); } function planningExecution(runId: string, revisionId: string) { return buildNativeExecutionInput({ companyId, runId, issue: { id: issueId, identifier: "DOT-2", title: "Test", description: null, workMode: "planning" }, taskPrompt: "Revise the plan", agentId, workspace: { id: runId, cwd: "/workspace", repoUrl: null, repoRef: null, branchName: null }, normalizedSessionId, provider: "codex", executionMode: "plan", planningContext: { documentId: "80000000-0000-4000-8000-000000000008", baseRevisionId: revisionId, baseRevisionNumber: revisionId.endsWith("9") ? 9 : 8, markdown: `Plan at ${revisionId}`, sha256: `sha256:${"b".repeat(64)}`, reviewContext: {}, }, completionContract: execution(runId).completionContract, runtimeContext: nativeRuntimeContextFixture(), }); } function previousRun(overrides: Record = {}) { return { id: previousRunId, companyId, agentId, nativeSessionId: normalizedSessionId, runnerProfileJson: { nativeExecutionInput: execution(previousRunId), sessionCheckpoint: { backendKind: "runner", driverKind: "codex_app_server", sessionId: "provider-thread-123", providerSessionId: "provider-thread-123", cursor: "42", identity: { runId: previousRunId, sessionId: normalizedSessionId, companyId, issueId, agentId }, semanticResult: { schema: "paperclip.run-result.v1", reportedWorkDisposition: "done", summary: "old" }, terminal: { schema: "paperclip.prp.terminal.v1", turnTerminalState: "completed", runTerminalState: "succeeded", reportedWorkDisposition: "done" }, activeTurnId: "old-turn", terminalTurns: [{ turnId: "old-turn", state: "completed" }], pendingRuntimeRequests: [{ requestId: "old-request" }], lineage: [{ threadId: "provider-thread-123" }], }, ...overrides, }, }; } describe("rebindNativeSessionCheckpoint", () => { it("retains provider identity but clears prior turn and event state", () => { const rebound = rebindNativeSessionCheckpoint({ previousRun: previousRun(), currentExecution: execution(currentRunId), }); expect(rebound).toMatchObject({ sessionId: "provider-thread-123", providerSessionId: "provider-thread-123", driverKind: "codex_app_server", cursor: null, semanticResult: null, terminal: null, activeTurnId: null, terminalTurns: [], pendingRuntimeRequests: [], providerRecoveryPolicy: "allow_replacement_after_resume_failure", identity: { runId: currentRunId, sessionId: normalizedSessionId, companyId, issueId, agentId }, }); }); it("allows a provider replacement only after a durable response wake", () => { const source = previousRun(); const profile = source.runnerProfileJson as Record; const checkpoint = profile.sessionCheckpoint as Record; checkpoint.semanticResult = { schema: "paperclip.run_result.v1", reportedWorkDisposition: "yielded", summary: "Waiting for a response.", continuation: { kind: "response_wake", idempotencyKey: "interaction-response:one", }, }; expect( rebindNativeSessionCheckpoint({ previousRun: source, currentExecution: execution(currentRunId), }), ).toMatchObject({ providerRecoveryPolicy: "allow_replacement_after_governed_wait", semanticResult: null, activeTurnId: null, }); }); it("refuses to resume when the workspace changes", () => { expect(rebindNativeSessionCheckpoint({ previousRun: previousRun(), currentExecution: execution(currentRunId, "/different-workspace"), })).toBeNull(); }); it("rotates when assigned context changes but permits a fresh run-scoped MCP binding", () => { const reboundCredential = execution(currentRunId); reboundCredential.runtimeContext.mcp.bindingId = "native-mcp:fresh-run"; expect(rebindNativeSessionCheckpoint({ previousRun: previousRun(), currentExecution: reboundCredential, })).not.toBeNull(); const changedAssignment = execution(currentRunId); const withoutAggregate = { prompt: changedAssignment.runtimeContext.prompt, instructions: changedAssignment.runtimeContext.instructions, skills: changedAssignment.runtimeContext.skills, mcp: { assignmentSetId: `sha256:${"1".repeat(64)}`, digest: "1".repeat(64), bindingId: "native-mcp:fresh-run", }, }; changedAssignment.runtimeContext = { ...withoutAggregate, aggregateDigest: canonicalNativeRuntimeContextDigest(withoutAggregate), }; expect(rebindNativeSessionCheckpoint({ previousRun: previousRun(), currentExecution: changedAssignment, })).toBeNull(); }); it("refuses a checkpoint whose prior-run binding was rewritten", () => { const source = previousRun(); const profile = source.runnerProfileJson as Record; const checkpoint = profile.sessionCheckpoint as Record; checkpoint.identity = { ...(checkpoint.identity as Record), runId: currentRunId }; expect(rebindNativeSessionCheckpoint({ previousRun: source, currentExecution: execution(currentRunId) })).toBeNull(); }); it("reuses plan mode across canonical revisions but never across a mode change", () => { const source = previousRun({ nativeExecutionInput: planningExecution(previousRunId, "revision-8") }); expect(rebindNativeSessionCheckpoint({ previousRun: source, currentExecution: planningExecution(currentRunId, "revision-9"), })).not.toBeNull(); expect(rebindNativeSessionCheckpoint({ previousRun: source, currentExecution: execution(currentRunId), })).toBeNull(); }); }); describe("buildNativeExecutionInput wake projection", () => { it("writes native v4 and pins every provider's complete effective configuration", () => { const common = { companyId, runId: currentRunId, issue: { id: issueId, identifier: "DOT-4", title: "Permissions", description: null, workMode: "standard" }, taskPrompt: "Verify permissions", agentId, workspace: { id: currentRunId, cwd: "/workspace", repoUrl: null, repoRef: null, branchName: null }, normalizedSessionId, completionContract: execution(currentRunId).completionContract, runtimeContext: nativeRuntimeContextFixture(), } as const; const codex = buildNativeExecutionInput({ ...common, provider: "codex", codexApprovalPolicy: "on-request", }); const opencode = buildNativeExecutionInput({ ...common, provider: "opencode", model: "openrouter/z-ai/glm-5.2", opencodePermissionMode: "ask", }); const acpx = buildNativeExecutionInput({ ...common, provider: "acpx", acpxAgent: "claude", model: "claude-sonnet-5", acpxPermissionMode: "deny-all", }); const claudeManaged = buildNativeExecutionInput({ ...common, provider: "claude_managed", model: "claude-sonnet-5", managedProfile: { profileId: "managed-profile", anthropicAgentId: "agent-remote", agentVersion: "7", environmentId: "environment-remote", betaVersion: "managed-agents-2026-04-01", }, maxSessionListCostUsd: 0.75, }); const agentCore = buildNativeExecutionInput({ ...common, provider: "aws_agentcore", model: "global.anthropic.claude-sonnet-4-6", agentCoreProfile: { profileId: "agentcore-profile", region: "us-east-1", accountId: "123456789012", harnessArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/h-1", harnessVersion: "3", endpointArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/e-1", endpointQualifier: "prod", agentRuntimeArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/r-1", memoryArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/m-1", memoryId: "m-1", invocationRoleArn: "arn:aws:iam::123456789012:role/invoke", contextBucket: "paperclip-context", contextPrefix: "runner/", contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/key-1", qualificationRevision: "aws-agentcore-harness-v1", eventExpiryDays: 90, }, maxEstimatedSessionCostUsd: 1.25, invocationLimits: { maxIterations: 8, maxOutputTokens: 4_096, timeoutSeconds: 300, }, }); const defaultOpenCode = buildNativeExecutionInput({ ...common, provider: "opencode", model: "openrouter/z-ai/glm-5.2", }); const defaultAcpx = buildNativeExecutionInput({ ...common, provider: "acpx", model: "gpt-5.6-sol", }); expect(codex).toMatchObject({ schema: "paperclip.native-execution-input.v4", provider: { kind: "codex", approvalPolicy: "on-request" }, }); expect(opencode).toMatchObject({ schema: "paperclip.native-execution-input.v4", provider: { kind: "opencode", permissionMode: "ask" }, }); expect(acpx).toMatchObject({ schema: "paperclip.native-execution-input.v4", provider: { kind: "acpx", permissionMode: "deny-all" }, }); expect(claudeManaged).toMatchObject({ session: { driverKind: "claude_managed_agents_api" }, provider: { kind: "claude_managed", managedProfile: { profileId: "managed-profile" }, maxSessionListCostUsd: 0.75, }, }); expect(agentCore).toMatchObject({ session: { driverKind: "aws_agentcore_harness_api" }, provider: { kind: "aws_agentcore", agentCoreProfile: { profileId: "agentcore-profile", eventExpiryDays: 90, }, maxEstimatedSessionCostUsd: 1.25, }, }); expect(defaultOpenCode).toMatchObject({ provider: { kind: "opencode", permissionMode: "ask" }, }); expect(defaultAcpx).toMatchObject({ provider: { kind: "acpx", agent: "codex", permissionMode: "approve-reads", }, }); expect(JSON.stringify([codex, opencode, claudeManaged, agentCore, acpx])) .not.toMatch(/OPENAI_API_KEY|ANTHROPIC_API_KEY|AWS_SECRET_ACCESS_KEY|PAPERCLIP_API_KEY/); }); it("places child completion summaries in the closed provider prompt", () => { const input = buildNativeExecutionInput({ companyId, runId: currentRunId, issue: { id: issueId, identifier: "DOT-146", title: "Finish after child handoff", description: "Use the child result.", workMode: "standard", }, taskPrompt: "Paperclip task context:\n- Issue: DOT-146", wakePayload: { reason: "issue_children_completed", issue: { id: issueId, identifier: "DOT-146", title: "Finish after child handoff", description: "Use the child result.", status: "in_progress", priority: "medium", workMode: "standard", }, childIssueSummaries: [{ id: "child-147", identifier: "DOT-147", title: "Build utility", status: "done", summary: "Created three files and passed 7/7 tests.", }], childIssueSummaryTruncated: false, checkedOutByHarness: true, }, resumedSession: true, agentId, workspace: { id: currentRunId, cwd: "/workspace", repoUrl: null, repoRef: null, branchName: null, }, normalizedSessionId, provider: "opencode", model: "openrouter/z-ai/glm-5.2", completionContract: { id: "70000000-0000-4000-8000-000000000007", sha256: `sha256:${"a".repeat(64)}`, schemaVersion: "paperclip.run-result.v1", contract: { revision: "1", objective: "Finish after the child", criteria: [{ id: "objective", requirement: "Report the child result" }], }, }, runtimeContext: nativeRuntimeContextFixture(), }); expect(input.task.prompt).toContain("## Paperclip Resume Delta"); expect(input.task.prompt).toContain("reason: issue_children_completed"); expect(input.task.prompt).toContain("DOT-147 Build utility (done)"); expect(input.task.prompt).toContain("Created three files and passed 7/7 tests."); expect(input.task.prompt).toContain("Paperclip task context:\n- Issue: DOT-146"); expect(input.task.prompt).not.toContain("Use the child result."); }); });