mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 12:04:41 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Paperclip Runner controls provider processes, permissions and task completion. > - Copilot needs its pinned distribution, native message identities and accepted semantic completion receipts. > - The release must preserve saved-token access and task lifecycle guarantees. > - This pull request will keep the current completion and recovery fixes while adding the Copilot runtime. > - The benefit is a reviewable part of the Copilot production release. ## Linked Issues or Issue Description Refs #14434. Supersedes the runtime work in #15560. This is the first PR in the stack; #15703, #15705 and #15706 follow. Related alternative adapter work: #15881. This change uses the shared Runner path. ## What Changed - Pin Copilot CLI 1.0.88 and bind the integrated implementation as profile45. - Keep native instruction, permission, receipt, process ownership and cancellation boundaries. - Preserve current master completion continuation and remote verification behavior. Other provider release identities stay unchanged. ## Verification Final reviewed source [`4f3f1af807d2ed21adabb04c2f27f4df9c4bc8d5`](https://github.com/paperclipai/paperclip/commit/4f3f1af807d2ed21adabb04c2f27f4df9c4bc8d5) includes master `cde944ed75648adfb569e86133adc8b118b55dd6`. The combined four-part PR tree matches it except for the CI-owned lockfile. Every prior public PR head remains an ancestor. Released master migrations remain unchanged; Copilot adds migration361. No unrelated provider is enabled. Copilot CLI1.0.88 and profile47 retain digest `sha256:55044aa235e88b3fe36ec8a556ee1e3382590870aab907e4462ef7d64af49cea`. The pending qualification marker is removed. The [qualification record](https://github.com/paperclipai/paperclip/blob/4f3f1af807d2ed21adabb04c2f27f4df9c4bc8d5/doc/plans/2026-10-20-copilot-qualification-v47.json) reports **20/20 Product cells,154 assertions;7/7 protocol cases,46 assertions** against frozen installed application `c2616370800f5f6df45246b0890ba35a7cd2faab`. Both environments use the exact authenticated model `gpt-5.6-luna`. Every passing Product case confirms cleanup. Automatic behavior retries are disabled. Original failed attempts remain recorded. The ordinary installed remote journey passed saved-token selection, authenticated model discovery, normal UI save/reopen, metadata-only Test, task completion, accepted receipt and an exact19-byte browser download. An open model catalog caused no new discovery requests during117seconds. Actual installed runtime smokes passed on macOS ARM64, macOS x64 under Rosetta and native Linux x64 on Daytona. Raw token browser entry was not exercised; the encrypted connection was created through the normal API. The original Daytona file attempt timed out during delayed semantic dispatch. Its precise stalled layer remains unresolved. Independent ordinary delivery and the other19 cells passed before one instrumented recheck passed8/8 with unchanged source,prompt,assertions and deadlines. No causal latency repair is claimed. After current-master reconciliation,129 connection tests,59 runner tests,72 shared eval tests and88 recovery/workspace tests pass. Migration generation/safety, unchanged profile identity and UI token gates pass. Prior required CI was green. Fresh exact-head required CI and E2E are green for all four updated heads. Each has a fresh Greptile5/5 review and zero unresolved review threads. The [final normal-package cloud build](https://github.com/paperclipai/paperclip/actions/runs/39414239704) is running. Recursive typecheck,full CI test shards,build and all8 browser shards pass. These checks cover the final source; no additional local full-suite pass is claimed. The [frozen image build](https://github.com/paperclipai/paperclip/actions/runs/39334401638) and [original normal package build](https://github.com/paperclipai/paperclip/actions/runs/39334407471) passed. The exact image is published and independently retrieved anonymously. No separate full-image scan is claimed. All18 original normal archives were scanned with zero selected credentials. Docker builds run in the cloud. Provider USD remains unknown and GitHub-attributed. **Qualification is complete on the recorded frozen build. Normal protected merge, automatic master canary publication and final shipped-package local/Daytona canaries remain. No production deployment is claimed.** ## Risks Shared Runner behavior can affect active tasks. Exact profile admission rejects stale runtime identities. The final campaign and installed canaries remain release gates. ## Model Used OpenAI Codex, based on GPT-6. The exact serving model ID and context window are not exposed in this task. Used for code changes, review and tool-assisted verification. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [ ] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
820 lines
42 KiB
TypeScript
820 lines
42 KiB
TypeScript
import { nativeTaskConstraints } from "../backends/runtime-context.js";
|
|
import { readFileSync } from "node:fs";
|
|
import { createHash } from "node:crypto";
|
|
import { QUALIFIED_ACPX_PROFILES } from "../drivers/acpx/qualified-profiles.js";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { buildNativeModelEnvelope, parseNativeExecutionInput, NATIVE_EXECUTION_INPUT_SCHEMA, NATIVE_EXECUTION_INPUT_SCHEMA_V6, NATIVE_EXECUTION_INPUT_SCHEMA_V7, NATIVE_EXECUTION_INPUT_SCHEMA_V9, type NativeExecutionInputV1 } from "./native-execution.js";
|
|
import {
|
|
NATIVE_RUNTIME_ASSET_SCHEMA,
|
|
PAPERCLIP_EXECUTION_PROMPT,
|
|
PAPERCLIP_EXECUTION_PROMPT_REVISION,
|
|
canonicalNativeRuntimeContextDigest,
|
|
composeNativeSystemInstructions,
|
|
nativeRuntimePromptDigest,
|
|
} from "./runtime-context.js";
|
|
|
|
const input: NativeExecutionInputV1 = {
|
|
schema: "paperclip.native-execution-input.v1",
|
|
binding: {
|
|
companyId: "company-1",
|
|
runId: "run-1",
|
|
issueId: "issue-1",
|
|
agentId: "agent-1",
|
|
executionWorkspaceId: "workspace-1",
|
|
},
|
|
task: {
|
|
identifier: "PAP-1",
|
|
title: "Safe task",
|
|
description: null,
|
|
prompt: "# PAP-1: Safe task\n\nPlease address the latest comment.",
|
|
workMode: "standard",
|
|
},
|
|
workspace: { cwd: "/safe/workspace", repoUrl: null, repoRef: null, branchName: null },
|
|
session: {
|
|
normalizedSessionId: null,
|
|
driverKind: "codex_app_server",
|
|
protocolVersion: 1,
|
|
lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null },
|
|
},
|
|
provider: { kind: "codex", model: null },
|
|
completionContract: {
|
|
id: "contract-1",
|
|
sha256: "abc123",
|
|
schemaVersion: "paperclip.completion-contract.v1",
|
|
contract: {
|
|
revision: "1",
|
|
objective: "Complete the safe task.",
|
|
criteria: [{ id: "objective", requirement: "The task is complete." }],
|
|
},
|
|
},
|
|
interactionResponses: [],
|
|
credentialBindings: [{
|
|
bindingId: "opaque-binding",
|
|
service: "github",
|
|
destination: "github.com",
|
|
expiresAt: null,
|
|
displayName: "GitHub",
|
|
}],
|
|
};
|
|
|
|
describe("NativeExecutionInputV1", () => {
|
|
it.each(["paperclip.native-execution-input.v3", "paperclip.native-execution-input.v4", NATIVE_EXECUTION_INPUT_SCHEMA])(
|
|
"preserves a saved %s execution through recovery parsing",
|
|
(schema) => {
|
|
const digest = "0".repeat(64);
|
|
const text = "Saved system instructions absent from the current release.";
|
|
const context = {
|
|
prompt: { revision: "saved-prompt-before-upgrade", text, digest: createHash("sha256").update(text).digest("hex") },
|
|
instructions: {
|
|
entryPath: "AGENTS.md",
|
|
bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/instructions", fileCount: 1, totalBytes: 42 },
|
|
},
|
|
skills: [],
|
|
mcp: { assignmentSetId: "none", digest, bindingId: null },
|
|
};
|
|
const persisted = JSON.parse(JSON.stringify({
|
|
...input,
|
|
schema,
|
|
provider: schema === "paperclip.native-execution-input.v3" ? input.provider : { ...input.provider, approvalPolicy: "never" },
|
|
executionMode: "default",
|
|
planningContext: null,
|
|
runtimeContext: { ...context, aggregateDigest: canonicalNativeRuntimeContextDigest(context) },
|
|
}));
|
|
const recovered = parseNativeExecutionInput(persisted);
|
|
expect(recovered.runtimeContext).toEqual(persisted.runtimeContext);
|
|
expect(parseNativeExecutionInput(recovered)).toEqual(recovered);
|
|
},
|
|
);
|
|
|
|
it("parses v3 immutable runtime context without changing the model task envelope", () => {
|
|
const digest = "0".repeat(64);
|
|
const context = {
|
|
prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() },
|
|
instructions: {
|
|
entryPath: "AGENTS.md",
|
|
bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/instructions", fileCount: 2, totalBytes: 42 },
|
|
},
|
|
skills: [{
|
|
key: "company/research",
|
|
runtimeName: "research",
|
|
versionId: "version-1",
|
|
bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/skills/research", fileCount: 2, totalBytes: 42 },
|
|
}],
|
|
mcp: { assignmentSetId: "sha256:test", digest, bindingId: "native-mcp:run-1" },
|
|
} as const;
|
|
const preconfigured = {
|
|
...input, schema: "paperclip.native-execution-input.v7", homeMode: "preconfigured",
|
|
provider: { kind: "codex", model: null, approvalPolicy: "never" },
|
|
executionMode: "default", planningContext: null,
|
|
runtimeContext: { ...context, aggregateDigest: canonicalNativeRuntimeContextDigest(context) },
|
|
};
|
|
expect(parseNativeExecutionInput(preconfigured)).toMatchObject({ homeMode: "preconfigured" });
|
|
for (const agent of ["claude", "codex"]) {
|
|
expect(() => parseNativeExecutionInput({ ...preconfigured, executionMode: "plan",
|
|
provider: { kind: "acpx", agent, permissionMode: "approve-all" } })).toThrow("preconfigured ACPX read-only execution is not supported");
|
|
}
|
|
for (const permissionMode of ["approve-reads", "approve-paperclip", "deny-all"]) {
|
|
expect(() => parseNativeExecutionInput({ ...preconfigured, provider: { kind: "acpx", agent: "codex", permissionMode } })).toThrow("requires ACPX approve-all");
|
|
}
|
|
expect(() => parseNativeExecutionInput({ ...preconfigured, provider: { kind: "acpx", agent: "codex", permissionMode: "approve-all" } })).toThrow("cannot protect Runner state");
|
|
expect(buildNativeModelEnvelope(parseNativeExecutionInput(preconfigured)).schema).toBe("paperclip.native-model-envelope.v3");
|
|
expect(() => parseNativeExecutionInput({ ...preconfigured, homeMode: "other" })).toThrow("homeMode");
|
|
expect(() => parseNativeExecutionInput({ ...preconfigured, provider: { kind: "opencode", model: "provider/model", permissionMode: "allow" } })).toThrow("supports native Codex");
|
|
expect(() => parseNativeExecutionInput({ ...preconfigured, schema: "paperclip.native-execution-input.v5" })).toThrow("homeMode");
|
|
const parsed = parseNativeExecutionInput({
|
|
...input,
|
|
schema: "paperclip.native-execution-input.v3",
|
|
executionMode: "default",
|
|
planningContext: null,
|
|
runtimeContext: { ...context, aggregateDigest: canonicalNativeRuntimeContextDigest(context) },
|
|
});
|
|
expect(parsed.schema).toBe("paperclip.native-execution-input.v3");
|
|
const envelope = buildNativeModelEnvelope(parsed);
|
|
expect(envelope.task).toEqual(buildNativeModelEnvelope(input).task);
|
|
expect(envelope.completionContract).toEqual(buildNativeModelEnvelope(input).completionContract);
|
|
expect(JSON.stringify(envelope)).not.toContain("runtimeContext");
|
|
expect(JSON.stringify(envelope)).not.toContain(PAPERCLIP_EXECUTION_PROMPT);
|
|
expect(composeNativeSystemInstructions(parsed.runtimeContext, "Follow sibling.md")).toBe(
|
|
`${PAPERCLIP_EXECUTION_PROMPT}\n\nFollow sibling.md\n\nRead-only instruction sibling root: /runtime/instructions`,
|
|
);
|
|
const agentFilesContext = {
|
|
...parsed.runtimeContext,
|
|
instructions: {
|
|
...parsed.runtimeContext.instructions,
|
|
workingCopy: { kind: "agent_files" as const, rootPath: "/runtime/current-agent-copy", entryPath: "AGENTS.md" },
|
|
},
|
|
};
|
|
const agentFilesInstructions = composeNativeSystemInstructions(agentFilesContext, "Preserve my personal notes.");
|
|
expect(agentFilesInstructions).toContain("AGENT_HOME) is /runtime/current-agent-copy.");
|
|
expect(agentFilesInstructions).toContain("its absolute path may change between turns");
|
|
expect(agentFilesInstructions).toContain("use the current $AGENT_HOME environment variable instead of an absolute agent-directory path from an earlier turn");
|
|
expect(agentFilesInstructions).toContain("All supported files and subfolders there are restored across tasks and sessions");
|
|
expect(agentFilesInstructions).toContain("Write task deliverables in the task working directory");
|
|
expect(agentFilesInstructions).toContain("Preserve my personal notes.");
|
|
expect(agentFilesInstructions).toMatch(/Read-only instruction sibling root: \/runtime\/instructions$/);
|
|
expect(composeNativeSystemInstructions(parsed.runtimeContext, "Follow sibling.md")).not.toContain("$AGENT_HOME");
|
|
expect(canonicalNativeRuntimeContextDigest({
|
|
...context,
|
|
mcp: { ...context.mcp, bindingId: "native-mcp:run-2" },
|
|
})).toBe(parsed.runtimeContext.aggregateDigest);
|
|
const current = parseNativeExecutionInput({
|
|
...parsed,
|
|
schema: "paperclip.native-execution-input.v4",
|
|
provider: { kind: "codex", model: null, approvalPolicy: "on-request" },
|
|
});
|
|
const withDelta = parseNativeExecutionInput({ ...current, continuationPrompt: '{"messages":[{"authorType":"user","body":"Just this new comment"}]}' });
|
|
// No checkpoint / failed provider recovery must retain full bootstrap input.
|
|
expect(buildNativeModelEnvelope(withDelta)).toEqual(buildNativeModelEnvelope(current));
|
|
const delta = buildNativeModelEnvelope(withDelta, { resumedSession: true });
|
|
expect(delta).toEqual({
|
|
schema: "paperclip.native-continuation.v1",
|
|
events: '{"messages":[{"authorType":"user","body":"Just this new comment"}]}',
|
|
completion: { revision: "1", criterionIds: ["objective"],
|
|
instruction: "Before ending this turn, obtain one accepted paperclip_finish or paperclip_block result. Earlier reports belong to earlier turns; a final message alone does not complete this turn." },
|
|
});
|
|
expect(JSON.stringify(delta)).not.toContain(input.task.title);
|
|
expect(JSON.stringify(delta)).not.toContain(input.completionContract.contract.objective);
|
|
expect(JSON.stringify(delta)).not.toContain("opaque-binding");
|
|
const withGuidance = parseNativeExecutionInput({ ...current, initialCommunicationGuidance: "Saved Slack instructions" });
|
|
expect(buildNativeModelEnvelope(withGuidance).task.prompt).toBe(`Saved Slack instructions\n\n${current.task.prompt}`);
|
|
// Structured question/approval resumes may use a full envelope rather than
|
|
// a delta. They still must not repeat initial communication instructions.
|
|
expect(buildNativeModelEnvelope(withGuidance, { resumedSession: true })).toEqual(buildNativeModelEnvelope(current));
|
|
expect(buildNativeModelEnvelope(parseNativeExecutionInput({ ...withGuidance, continuationPrompt: "new message" }), { resumedSession: true })).not.toHaveProperty("task");
|
|
expect(buildNativeModelEnvelope(withGuidance).task.prompt.match(/Saved Slack instructions/g)).toHaveLength(1);
|
|
expect(current).toMatchObject({
|
|
schema: "paperclip.native-execution-input.v4",
|
|
provider: { kind: "codex", approvalPolicy: "on-request" },
|
|
});
|
|
const withEffort = parseNativeExecutionInput({
|
|
...current,
|
|
schema: NATIVE_EXECUTION_INPUT_SCHEMA,
|
|
provider: { kind: "codex", model: "gpt-6-astra", approvalPolicy: "on-request", reasoningEffort: "ultra" },
|
|
});
|
|
expect(withEffort.provider).toMatchObject({ kind: "codex", reasoningEffort: "ultra" });
|
|
expect(parseNativeExecutionInput(withEffort)).toEqual(withEffort);
|
|
expect(() => parseNativeExecutionInput({
|
|
...withEffort,
|
|
provider: { kind: "codex", model: "gpt-6-astra", approvalPolicy: "on-request", reasoningEffort: "impossible" },
|
|
})).toThrow("reasoningEffort");
|
|
expect(() => parseNativeExecutionInput({
|
|
...current,
|
|
provider: { kind: "codex", model: null, approvalPolicy: "on-request", reasoningEffort: "ultra" },
|
|
})).toThrow("input.provider");
|
|
expect(() => parseNativeExecutionInput({
|
|
...parsed,
|
|
schema: "paperclip.native-execution-input.v4",
|
|
provider: { kind: "codex", model: null, approvalPolicy: "sometimes" },
|
|
})).toThrow("approvalPolicy");
|
|
});
|
|
|
|
it("rejects runtime-context traversal and aggregate digest drift", () => {
|
|
const digest = "0".repeat(64);
|
|
const context = {
|
|
prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() },
|
|
instructions: {
|
|
entryPath: "../AGENTS.md",
|
|
bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/instructions", fileCount: 1, totalBytes: 1 },
|
|
},
|
|
skills: [],
|
|
mcp: { assignmentSetId: "none", digest, bindingId: null },
|
|
aggregateDigest: digest,
|
|
} as const;
|
|
expect(() => parseNativeExecutionInput({ ...input, schema: "paperclip.native-execution-input.v3", executionMode: "default", planningContext: null, runtimeContext: context })).toThrow("bundle root");
|
|
expect(() => parseNativeExecutionInput({
|
|
...input,
|
|
schema: "paperclip.native-execution-input.v3",
|
|
executionMode: "default",
|
|
planningContext: null,
|
|
runtimeContext: { ...context, instructions: { ...context.instructions, entryPath: "AGENTS.md" } },
|
|
})).toThrow("aggregateDigest");
|
|
});
|
|
|
|
it("builds a model envelope without authority or credential bindings", () => {
|
|
const parsed = parseNativeExecutionInput(input);
|
|
const model = buildNativeModelEnvelope(parsed);
|
|
const serialized = JSON.stringify(model);
|
|
expect(serialized).not.toContain("company-1");
|
|
expect(serialized).not.toContain("run-1");
|
|
expect(serialized).not.toContain("opaque-binding");
|
|
expect(model.task.title).toBe("Safe task");
|
|
expect(model.task.prompt).toContain("latest comment");
|
|
});
|
|
|
|
it("rejects unknown context or environment escape hatches", () => {
|
|
expect(() => parseNativeExecutionInput({ ...input, context: { secret: "canary" } })).toThrow(
|
|
"unknown field context",
|
|
);
|
|
expect(() => parseNativeExecutionInput({
|
|
...input,
|
|
workspace: { ...input.workspace, env: { PAPERCLIP_API_KEY: "canary" } },
|
|
})).toThrow("unknown field env");
|
|
});
|
|
|
|
it("accepts a persisted OpenCode driver/model pair and rejects mismatches", () => {
|
|
const opencode = parseNativeExecutionInput({
|
|
...input,
|
|
session: { ...input.session, driverKind: "opencode_server" },
|
|
provider: { kind: "opencode", model: "openrouter/deepseek/deepseek-v4-flash-0731" },
|
|
});
|
|
expect(opencode.provider).toEqual({
|
|
kind: "opencode",
|
|
model: "openrouter/deepseek/deepseek-v4-flash-0731",
|
|
});
|
|
expect(parseNativeExecutionInput(opencode)).toEqual(opencode);
|
|
expect(() => parseNativeExecutionInput({
|
|
...input,
|
|
session: { ...input.session, driverKind: "opencode_server" },
|
|
provider: { kind: "codex", model: null },
|
|
})).toThrow("does not match");
|
|
});
|
|
|
|
it("deserializes pre-provider Codex state as Codex", () => {
|
|
const legacy = structuredClone(input) as Record<string, unknown>;
|
|
delete legacy.provider;
|
|
expect(parseNativeExecutionInput(legacy).provider).toEqual({
|
|
kind: "codex",
|
|
model: null,
|
|
});
|
|
expect(parseNativeExecutionInput(parseNativeExecutionInput(legacy))).toEqual(
|
|
parseNativeExecutionInput(legacy),
|
|
);
|
|
});
|
|
|
|
it("accepts an immutable Claude Managed Agent profile and rejects driver or beta drift", () => {
|
|
const claudeManaged = {
|
|
...input,
|
|
session: { ...input.session, driverKind: "claude_managed_agents_api" },
|
|
provider: {
|
|
kind: "claude_managed",
|
|
model: "claude-sonnet-5",
|
|
managedProfile: {
|
|
profileId: "managed-profile-1",
|
|
anthropicAgentId: "agent_01",
|
|
agentVersion: "3",
|
|
environmentId: "environment_01",
|
|
betaVersion: "managed-agents-2026-04-01",
|
|
},
|
|
maxSessionListCostUsd: 1,
|
|
},
|
|
} as const;
|
|
const parsed = parseNativeExecutionInput(claudeManaged);
|
|
expect(parsed.provider).toEqual(claudeManaged.provider);
|
|
expect(buildNativeModelEnvelope(parsed).workspace).toBeNull();
|
|
expect(() => parseNativeExecutionInput({
|
|
...claudeManaged,
|
|
session: { ...claudeManaged.session, driverKind: "codex_app_server" },
|
|
})).toThrow("does not match");
|
|
expect(() => parseNativeExecutionInput({
|
|
...claudeManaged,
|
|
provider: {
|
|
...claudeManaged.provider,
|
|
managedProfile: { ...claudeManaged.provider.managedProfile, betaVersion: "future-beta" },
|
|
},
|
|
})).toThrow("betaVersion");
|
|
});
|
|
|
|
it("accepts a closed AWS AgentCore Harness snapshot and rejects drift or unsafe limits", () => {
|
|
const awsAgentCore = {
|
|
...input,
|
|
session: { ...input.session, driverKind: "aws_agentcore_harness_api" },
|
|
provider: {
|
|
kind: "aws_agentcore",
|
|
model: "global.anthropic.claude-sonnet-4-6",
|
|
agentCoreProfile: {
|
|
profileId: "agentcore-development",
|
|
region: "us-east-1",
|
|
accountId: "123456789012",
|
|
harnessArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/harness-1",
|
|
harnessVersion: "3",
|
|
endpointArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness-endpoint/harness-1/paperclip",
|
|
endpointQualifier: "paperclip",
|
|
agentRuntimeArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/runtime-1",
|
|
memoryArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/memory-1",
|
|
memoryId: "memory-1",
|
|
invocationRoleArn: "arn:aws:iam::123456789012:role/paperclip-agentcore-runner",
|
|
contextBucket: "paperclip-agentcore-context",
|
|
contextPrefix: "paperclip/runtime",
|
|
contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/test",
|
|
qualificationRevision: "aws-agentcore-harness-context-v2",
|
|
eventExpiryDays: 90,
|
|
},
|
|
maxEstimatedSessionCostUsd: 1,
|
|
invocationLimits: { maxIterations: 8, maxOutputTokens: 4096, timeoutSeconds: 300 },
|
|
},
|
|
} as const;
|
|
const parsed = parseNativeExecutionInput(awsAgentCore);
|
|
expect(parsed.provider).toEqual(awsAgentCore.provider);
|
|
expect(buildNativeModelEnvelope(parsed).workspace).toBeNull();
|
|
expect(JSON.stringify(parsed)).not.toContain("AWS_SECRET_ACCESS_KEY");
|
|
expect(() => parseNativeExecutionInput({
|
|
...awsAgentCore,
|
|
session: { ...awsAgentCore.session, driverKind: "codex_app_server" },
|
|
})).toThrow("does not match");
|
|
expect(() => parseNativeExecutionInput({
|
|
...awsAgentCore,
|
|
provider: { ...awsAgentCore.provider, invocationLimits: { ...awsAgentCore.provider.invocationLimits, maxIterations: 9 } },
|
|
})).toThrow("maxIterations");
|
|
expect(() => parseNativeExecutionInput({
|
|
...awsAgentCore,
|
|
provider: { ...awsAgentCore.provider, agentCoreProfile: { ...awsAgentCore.provider.agentCoreProfile, eventExpiryDays: 30 } },
|
|
})).toThrow("eventExpiryDays");
|
|
});
|
|
|
|
it.each([
|
|
[17, "sha256:a1d976c437cb736c9cce8ebe8b8baf59e571761a8d00e8b1885e72dd906d8f21"],
|
|
[18, "sha256:9d3e7d8269f1a0af94616552dfc69671932688b81dbbd5bab9ef356b3a9cbccb"],
|
|
] as const)("decodes a persisted Pi profile-%s run without rewriting its identity", (agentProfileVersion, commandDigest) => {
|
|
const { permissionPolicy: _permissionPolicy, ...nativeProfile } = QUALIFIED_ACPX_PROFILES.pi;
|
|
const profile = { ...nativeProfile, agentProfileVersion, commandDigest };
|
|
const persisted = { ...input, session: { ...input.session, driverKind: "acpx_runtime" },
|
|
provider: { kind: "acpx", agent: "pi", model: "openrouter/deepseek/deepseek-v4-flash-0731",
|
|
piThinkingLevel: "low", permissionPolicy: "interactive", profile } };
|
|
const parsed = parseNativeExecutionInput(persisted);
|
|
expect(parsed.provider).toEqual(persisted.provider);
|
|
expect(parseNativeExecutionInput(parsed)).toEqual(parsed);
|
|
});
|
|
|
|
it.each([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] as const)("accepts only a closed ACPX profile matching the driver and agent at profile version %s", (agentProfileVersion) => {
|
|
const provider = {
|
|
kind: "acpx",
|
|
agent: "pi",
|
|
model: "openrouter/deepseek/deepseek-v4-flash-0731",
|
|
permissionPolicy: "interactive",
|
|
profile: {
|
|
driverKind: "acpx_runtime",
|
|
protocolVersion: 1,
|
|
acpxVersion: "0.13.1",
|
|
agent: "pi",
|
|
agentProfileVersion,
|
|
agentServerPackage: "pi-acp",
|
|
agentServerVersion: "0.0.33",
|
|
agentRuntimePackage: "@earendil-works/pi-coding-agent",
|
|
agentRuntimeVersion: "1.0.0",
|
|
commandDigest: "sha256:24ff73fda6e3c76ddce2d359a79f5c4b8f292eb290e4d2ab85aac94676b2c2dc",
|
|
},
|
|
} as const;
|
|
const parsed = parseNativeExecutionInput({
|
|
...input,
|
|
session: { ...input.session, driverKind: "acpx_runtime" },
|
|
provider,
|
|
});
|
|
expect(parsed.provider).toEqual({
|
|
kind: "acpx",
|
|
agent: "pi",
|
|
model: "openrouter/deepseek/deepseek-v4-flash-0731",
|
|
permissionPolicy: "interactive",
|
|
profile: provider.profile,
|
|
});
|
|
expect(parseNativeExecutionInput(parsed)).toEqual(parsed);
|
|
for (const unsupportedVersion of [0, Math.max(16, QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion) + 1, 1.5, "14", null]) {
|
|
expect(() => parseNativeExecutionInput({
|
|
...input,
|
|
session: { ...input.session, driverKind: "acpx_runtime" },
|
|
provider: { ...provider, piThinkingLevel: "low", profile: { ...provider.profile, agentProfileVersion: unsupportedVersion } },
|
|
})).toThrow("qualified ACPX v1 profile");
|
|
}
|
|
expect(buildNativeModelEnvelope(parsed).workspace).toEqual({ cwd: "/safe/workspace" });
|
|
expect(() => parseNativeExecutionInput({
|
|
...input,
|
|
session: { ...input.session, driverKind: "acpx_runtime" },
|
|
provider: { ...provider, profile: { ...provider.profile, agent: "claude" } },
|
|
})).toThrow("qualified ACPX v1 profile");
|
|
expect(() => parseNativeExecutionInput({
|
|
...input,
|
|
session: { ...input.session, driverKind: "opencode_server" },
|
|
provider,
|
|
})).toThrow("does not match");
|
|
});
|
|
|
|
it.each([17, 43, 44] as const)("decodes saved Copilot v%s input after the current source declaration advances", (version) => {
|
|
const historical = JSON.parse(readFileSync(new URL(`../../test/fixtures/copilot-profile-v${version}-identity.json`, import.meta.url), "utf8"));
|
|
const { permissionPolicy, qualificationStatus: _status, ...currentProfile } = QUALIFIED_ACPX_PROFILES.copilot;
|
|
const provider = { kind: "acpx", agent: "copilot", model: "explicit-provider-model", permissionPolicy,
|
|
profile: { ...currentProfile, agentProfileVersion: version, commandDigest: historical.commandDigest } };
|
|
const parsed = parseNativeExecutionInput({ ...input, session: { ...input.session, driverKind: "acpx_runtime" }, provider });
|
|
expect(parsed.provider).toEqual(provider);
|
|
expect(parseNativeExecutionInput(parsed)).toEqual(parsed);
|
|
});
|
|
|
|
it.each([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40] as const)("decodes closed Copilot profile history at version %s without admitting a future version", (agentProfileVersion) => {
|
|
const { qualificationModel: _qualificationModel, reportedModelId: _reportedModelId,
|
|
permissionPolicy, qualificationStatus: _qualificationStatus, modelPolicy: _modelPolicy,
|
|
...snapshot } = QUALIFIED_ACPX_PROFILES.copilot;
|
|
const provider = {
|
|
kind: "acpx", agent: "copilot", model: "gpt-5.6-luna", permissionPolicy,
|
|
profile: { ...snapshot, agentProfileVersion },
|
|
} as const;
|
|
const value = { ...input, session: { ...input.session, driverKind: "acpx_runtime" }, provider };
|
|
const parsed = parseNativeExecutionInput(value);
|
|
expect(parsed.provider).toEqual(provider);
|
|
expect(parseNativeExecutionInput(parsed)).toEqual(parsed);
|
|
for (const unsupportedVersion of [0, QUALIFIED_ACPX_PROFILES.copilot.agentProfileVersion + 1, 1.5, "34", null]) {
|
|
expect(() => parseNativeExecutionInput({
|
|
...value, provider: { ...provider, profile: { ...provider.profile, agentProfileVersion: unsupportedVersion } },
|
|
})).toThrow("qualified ACPX v1 profile");
|
|
}
|
|
expect(() => parseNativeExecutionInput({
|
|
...value, provider: { ...provider, profile: { ...provider.profile, agent: "pi" } },
|
|
})).toThrow("qualified ACPX v1 profile");
|
|
expect(() => parseNativeExecutionInput({
|
|
...value, session: { ...value.session, driverKind: "opencode_server" },
|
|
})).toThrow("does not match");
|
|
});
|
|
it.each(Object.values(QUALIFIED_ACPX_PROFILES))(
|
|
"admits the current $agent profile through the native execution boundary",
|
|
(declaration) => {
|
|
const { qualificationModel, reportedModelId: _reported, permissionPolicy,
|
|
modelPolicy: _modelPolicy, qualificationStatus: _status, ...profile } = declaration;
|
|
const provider = {
|
|
kind: "acpx", agent: declaration.agent,
|
|
model: qualificationModel || "explicit-provider-model",
|
|
permissionPolicy, profile,
|
|
...(declaration.agent === "pi" ? { piThinkingLevel: "low" } : {}),
|
|
};
|
|
const parsed = parseNativeExecutionInput({
|
|
...input,
|
|
session: { ...input.session, driverKind: "acpx_runtime" },
|
|
provider,
|
|
});
|
|
expect(parsed.provider).toEqual(provider);
|
|
expect(parseNativeExecutionInput(parsed)).toEqual(parsed);
|
|
},
|
|
);
|
|
|
|
it("defaults legacy lifecycle state to per-turn and validates warm timeouts", () => {
|
|
const legacy = structuredClone(input) as Record<string, unknown>;
|
|
delete (legacy.session as Record<string, unknown>).lifecyclePolicy;
|
|
expect(parseNativeExecutionInput(legacy).session.lifecyclePolicy).toEqual({
|
|
mode: "per_turn",
|
|
idleTimeoutMs: null,
|
|
});
|
|
expect(parseNativeExecutionInput({
|
|
...input,
|
|
session: {
|
|
...input.session,
|
|
lifecyclePolicy: { mode: "warm", idleTimeoutMs: 300_000 },
|
|
},
|
|
}).session.lifecyclePolicy).toEqual({ mode: "warm", idleTimeoutMs: 300_000 });
|
|
expect(() => parseNativeExecutionInput({
|
|
...input,
|
|
session: {
|
|
...input.session,
|
|
lifecyclePolicy: { mode: "warm", idleTimeoutMs: 0 },
|
|
},
|
|
})).toThrow("positive integer");
|
|
expect(() => parseNativeExecutionInput({
|
|
...input,
|
|
session: {
|
|
...input.session,
|
|
lifecyclePolicy: { mode: "warm", idleTimeoutMs: 86_400_001 },
|
|
},
|
|
})).toThrow("no greater than 86400000");
|
|
});
|
|
});
|
|
|
|
describe("NativeExecutionInputV2 planning", () => {
|
|
const planning = {
|
|
...input,
|
|
schema: "paperclip.native-execution-input.v2",
|
|
executionMode: "plan",
|
|
task: { ...input.task, workMode: "planning" },
|
|
planningContext: {
|
|
documentId: "document-1",
|
|
baseRevisionId: "revision-3",
|
|
baseRevisionNumber: 3,
|
|
markdown: "# Existing plan",
|
|
sha256: "plan-digest",
|
|
reviewContext: { threads: [{ id: "annotation-1" }] },
|
|
},
|
|
} as const;
|
|
|
|
it("round-trips native plan mode and its pinned canonical revision into model context", () => {
|
|
const parsed = parseNativeExecutionInput(planning);
|
|
expect(parsed).toMatchObject({ schema: "paperclip.native-execution-input.v2", executionMode: "plan" });
|
|
expect(buildNativeModelEnvelope(parsed)).toMatchObject({
|
|
schema: "paperclip.native-model-envelope.v2",
|
|
executionMode: "plan",
|
|
planningContext: { baseRevisionId: "revision-3", baseRevisionNumber: 3 },
|
|
});
|
|
});
|
|
|
|
it("fails closed when plan mode omits its pinned Paperclip context", () => {
|
|
expect(() => parseNativeExecutionInput({ ...planning, planningContext: null }))
|
|
.toThrow("planningContext is required");
|
|
expect(() => parseNativeExecutionInput({
|
|
...planning,
|
|
task: { ...planning.task, workMode: "standard" },
|
|
})).toThrow("requires planning work mode");
|
|
});
|
|
|
|
it("allows an accepted planning issue to continue in fresh default execution mode", () => {
|
|
expect(parseNativeExecutionInput({
|
|
...planning,
|
|
executionMode: "default",
|
|
planningContext: null,
|
|
})).toMatchObject({ task: { workMode: "planning" }, executionMode: "default" });
|
|
});
|
|
});
|
|
|
|
describe("NativeExecutionInputV2 ask mode", () => {
|
|
it("round-trips ask mode as a default execution without planning context", () => {
|
|
const parsed = parseNativeExecutionInput({
|
|
...input,
|
|
schema: "paperclip.native-execution-input.v2",
|
|
executionMode: "default",
|
|
task: { ...input.task, workMode: "ask" },
|
|
planningContext: null,
|
|
});
|
|
expect(parsed).toMatchObject({
|
|
schema: "paperclip.native-execution-input.v2",
|
|
executionMode: "default",
|
|
task: { workMode: "ask" },
|
|
});
|
|
expect(buildNativeModelEnvelope(parsed)).toMatchObject({
|
|
schema: "paperclip.native-model-envelope.v2",
|
|
task: { workMode: "ask" },
|
|
executionMode: "default",
|
|
planningContext: null,
|
|
});
|
|
});
|
|
|
|
it("rejects plan execution for ask mode", () => {
|
|
expect(() => parseNativeExecutionInput({
|
|
...input,
|
|
schema: "paperclip.native-execution-input.v2",
|
|
executionMode: "plan",
|
|
task: { ...input.task, workMode: "ask" },
|
|
planningContext: {
|
|
documentId: null,
|
|
baseRevisionId: null,
|
|
baseRevisionNumber: 0,
|
|
markdown: "",
|
|
sha256: "digest",
|
|
reviewContext: {},
|
|
},
|
|
})).toThrow("plan execution mode requires planning work mode");
|
|
});
|
|
});
|
|
|
|
|
|
describe("native task context ownership", () => {
|
|
function currentInput() {
|
|
const digest = "0".repeat(64);
|
|
const context = {
|
|
prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() },
|
|
instructions: { entryPath: "AGENTS.md", bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/instructions", fileCount: 1, totalBytes: 1 } },
|
|
skills: [],
|
|
mcp: { assignmentSetId: "none", digest, bindingId: null },
|
|
} as const;
|
|
return parseNativeExecutionInput({
|
|
...input,
|
|
schema: NATIVE_EXECUTION_INPUT_SCHEMA,
|
|
task: { ...input.task, description: "Use $assigned-skill. Repeat this. Repeat this.", prompt: "# PAP-1\n\nIssue description:\nUse $assigned-skill. Repeat this. Repeat this." },
|
|
provider: { kind: "codex", model: null, approvalPolicy: "never" },
|
|
executionMode: "default",
|
|
planningContext: null,
|
|
runtimeContext: { ...context, aggregateDigest: canonicalNativeRuntimeContextDigest(context) },
|
|
});
|
|
}
|
|
|
|
const { qualificationModel: _copilotQualificationModel, reportedModelId: _copilotReportedModelId,
|
|
permissionPolicy: _copilotPermissionPolicy, qualificationStatus: _copilotQualificationStatus, modelPolicy: _copilotModelPolicy,
|
|
...copilotSnapshot } = QUALIFIED_ACPX_PROFILES.copilot;
|
|
|
|
it.each([
|
|
{ driverKind: "acpx_runtime", provider: { kind: "acpx", agent: "copilot", model: "gpt-5.6-luna", permissionMode: "deny-all", profile: copilotSnapshot } },
|
|
{ driverKind: "opencode_server", provider: { kind: "opencode", model: "openrouter/deepseek/deepseek-v4-flash-0731", permissionMode: "deny" } },
|
|
{ driverKind: "acpx_runtime", provider: {
|
|
kind: "acpx", agent: "pi", model: "openrouter/deepseek/deepseek-v4-flash-0731", permissionMode: "deny-all",
|
|
profile: { driverKind: "acpx_runtime", protocolVersion: 1, acpxVersion: "0.13.1", agent: "pi", agentProfileVersion: 1,
|
|
agentServerPackage: "pi-acp", agentServerVersion: "0.0.33", agentRuntimePackage: "@earendil-works/pi-coding-agent",
|
|
agentRuntimeVersion: "0.84.2", commandDigest: `sha256:${"a".repeat(64)}` },
|
|
} },
|
|
{ driverKind: "openai_dot_mcp", provider: {
|
|
kind: "openai_dot", model: null,
|
|
binding: { bindingId: "dot-binding", bindingGeneration: 1, companyId: input.binding.companyId, agentId: input.binding.agentId,
|
|
acceptByUnixMs: 1_000, expiresAtUnixMs: 2_000 },
|
|
} },
|
|
])("preserves an unregistered saved prompt for $provider.kind", ({ driverKind, provider }) => {
|
|
const current = currentInput();
|
|
if (!("runtimeContext" in current)) throw new Error("Expected a runtime context");
|
|
const text = "Saved instructions absent from the current release.";
|
|
const context = { ...current.runtimeContext,
|
|
prompt: { revision: "saved-prompt-before-upgrade", text, digest: createHash("sha256").update(text).digest("hex") } };
|
|
context.aggregateDigest = canonicalNativeRuntimeContextDigest(context);
|
|
const persisted = JSON.parse(JSON.stringify({
|
|
...current, provider, session: { ...current.session, driverKind }, runtimeContext: context,
|
|
...(provider.kind === "openai_dot" ? {
|
|
schema: NATIVE_EXECUTION_INPUT_SCHEMA_V6,
|
|
workspace: { access: "none", cwd: null, repoUrl: null, repoRef: null, branchName: null },
|
|
credentialBindings: [],
|
|
} : {}),
|
|
}));
|
|
const recovered = parseNativeExecutionInput(persisted);
|
|
expect(recovered.runtimeContext).toEqual(context);
|
|
expect(recovered.provider).toEqual(provider);
|
|
expect(recovered.session.driverKind).toBe(driverKind);
|
|
expect(parseNativeExecutionInput(recovered)).toEqual(recovered);
|
|
});
|
|
|
|
it("carries an opaque provider mode without a vendor restriction and fences obsolete field names", () => {
|
|
const current = currentInput();
|
|
const provider = {
|
|
kind: "acpx", agent: "codex", model: "gpt-5.6-sol", permissionMode: "approve-all", mode: "architect",
|
|
profile: { driverKind: "acpx_runtime", protocolVersion: 1, acpxVersion: "0.13.1", agent: "codex", agentProfileVersion: 3,
|
|
agentServerPackage: "@agentclientprotocol/codex-acp", agentServerVersion: "1.6.2",
|
|
agentRuntimePackage: "@openai/codex", agentRuntimeVersion: "0.160.0", commandDigest: `sha256:${"a".repeat(64)}` },
|
|
};
|
|
const value = { ...current, session: { ...current.session, driverKind: "acpx_runtime" }, provider };
|
|
const parsed = parseNativeExecutionInput(value);
|
|
expect(parsed.provider).toMatchObject({ agent: "codex", mode: "architect" });
|
|
expect(parseNativeExecutionInput(parsed)).toEqual(parsed);
|
|
expect(JSON.stringify(buildNativeModelEnvelope(parsed))).not.toContain("architect");
|
|
for (const mode of [null, 1, "", " ", "x".repeat(241), "plan\0", "plan\n"]) {
|
|
expect(() => parseNativeExecutionInput({ ...value, provider: { ...provider, mode } })).toThrow(/provider.mode/);
|
|
}
|
|
const { mode: _mode, ...withoutMode } = provider;
|
|
expect(() => parseNativeExecutionInput({ ...value, provider: { ...withoutMode, cursorMode: "plan" } })).toThrow(/input.provider/);
|
|
});
|
|
|
|
it.each([
|
|
["v4", "paperclip.native-execution-input.v4", "paperclip.native-model-envelope.v2"],
|
|
["v5", NATIVE_EXECUTION_INPUT_SCHEMA, "paperclip.native-model-envelope.v3"],
|
|
] as const)("applies communication guidance once for fresh %s input and never on resume", (_label, schema, envelopeSchema) => {
|
|
const guidance = "Saved Slack instructions";
|
|
const v5 = currentInput();
|
|
const source = {
|
|
kind: "description" as const,
|
|
id: v5.binding.issueId,
|
|
revision: createHash("sha256").update(v5.task.description!).digest("hex"),
|
|
};
|
|
const contract = {
|
|
...v5.completionContract.contract,
|
|
criteria: [{ id: "objective", requirement: v5.task.description! }],
|
|
};
|
|
const parsed = parseNativeExecutionInput({
|
|
...v5,
|
|
schema,
|
|
initialCommunicationGuidance: guidance,
|
|
...(schema === NATIVE_EXECUTION_INPUT_SCHEMA ? {
|
|
completionContract: { ...v5.completionContract, contract },
|
|
completionSources: {
|
|
promptSha256: createHash("sha256").update(v5.task.prompt).digest("hex"),
|
|
contractRevision: contract.revision,
|
|
criteria: [{ id: "objective", source }],
|
|
},
|
|
} : {}),
|
|
});
|
|
const fresh = buildNativeModelEnvelope(parsed);
|
|
expect(fresh.schema).toBe(envelopeSchema);
|
|
expect(fresh.task.prompt).toBe(`${guidance}\n\n${parsed.task.prompt}`);
|
|
expect(fresh.task.prompt.match(/Saved Slack instructions/g)).toHaveLength(1);
|
|
if (schema === NATIVE_EXECUTION_INPUT_SCHEMA) {
|
|
expect(fresh.task).not.toHaveProperty("description");
|
|
expect(fresh.completionContract.criteria).toEqual([
|
|
{ id: "objective", source: { ...source, location: "task.prompt" } },
|
|
]);
|
|
} else {
|
|
expect(fresh.task).toHaveProperty("description", parsed.task.description);
|
|
}
|
|
|
|
const fullResume = buildNativeModelEnvelope(parsed, { resumedSession: true });
|
|
expect(fullResume.schema).toBe(envelopeSchema);
|
|
expect(fullResume.task.prompt).toBe(parsed.task.prompt);
|
|
expect(fullResume.task.prompt).not.toContain(guidance);
|
|
|
|
const compactResume = buildNativeModelEnvelope(
|
|
parseNativeExecutionInput({ ...parsed, continuationPrompt: "new message" }),
|
|
{ resumedSession: true },
|
|
);
|
|
expect(compactResume).toMatchObject({ schema: "paperclip.native-continuation.v1" });
|
|
expect(JSON.stringify(compactResume)).not.toContain(guidance);
|
|
});
|
|
|
|
it("selects model fields without losing the internal skill-selection description", () => {
|
|
const execution = currentInput();
|
|
const envelope = buildNativeModelEnvelope(execution);
|
|
expect(envelope.task).toEqual({
|
|
identifier: execution.task.identifier,
|
|
title: execution.task.title,
|
|
prompt: execution.task.prompt,
|
|
workMode: execution.task.workMode,
|
|
});
|
|
expect(execution.task.description).toBe("Use $assigned-skill. Repeat this. Repeat this.");
|
|
expect(envelope.task.prompt).toContain("Repeat this. Repeat this.");
|
|
expect(envelope.completionContract).toEqual(execution.completionContract.contract);
|
|
});
|
|
|
|
it("references only verified criterion sources and preserves independent requirements", () => {
|
|
const execution = currentInput();
|
|
const source = { kind: "description", id: execution.binding.issueId, revision: createHash("sha256").update(execution.task.description!).digest("hex") };
|
|
const contract = { ...execution.completionContract.contract, criteria: [
|
|
{ id: "objective", requirement: execution.task.description! },
|
|
{ id: "independent", requirement: "Also report a measured result." },
|
|
] };
|
|
const completeInput = parseNativeExecutionInput({
|
|
...execution,
|
|
completionContract: { ...execution.completionContract, contract },
|
|
completionSources: { promptSha256: createHash("sha256").update(execution.task.prompt).digest("hex"), contractRevision: contract.revision, criteria: [{ id: "objective", source }] },
|
|
});
|
|
const before = structuredClone(completeInput.completionContract);
|
|
const envelope = buildNativeModelEnvelope(completeInput);
|
|
expect(envelope.completionContract.criteria).toEqual([
|
|
{ id: "objective", source: { ...source, location: "task.prompt" } },
|
|
{ id: "independent", requirement: "Also report a measured result." },
|
|
]);
|
|
expect(completeInput.completionContract).toEqual(before);
|
|
// A different prompt or contract revision invalidates the projection, never the saved contract.
|
|
for (const stale of [
|
|
{ ...completeInput, task: { ...completeInput.task, prompt: "Recovery without the original brief" } },
|
|
{ ...completeInput, completionContract: { ...completeInput.completionContract, contract: { ...contract, revision: "new" } } },
|
|
]) {
|
|
expect(buildNativeModelEnvelope(stale).completionContract).toEqual(stale.completionContract.contract);
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
describe("Claude effort input v9", () => {
|
|
const { permissionPolicy: _permission, qualificationModel: _model, reportedModelId: _reported, ...profile } = QUALIFIED_ACPX_PROFILES.claude;
|
|
const digest = "0".repeat(64);
|
|
const context = { prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() },
|
|
instructions: { entryPath: "AGENTS.md", bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/instructions", fileCount: 1, totalBytes: 1 } },
|
|
skills: [], mcp: { assignmentSetId: "none", digest, bindingId: null } };
|
|
const current = { ...input, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V9, homeMode: "isolated", executionMode: "default", planningContext: null,
|
|
runtimeContext: { ...context, aggregateDigest: canonicalNativeRuntimeContextDigest(context) },
|
|
session: { ...input.session, driverKind: "acpx_runtime" },
|
|
provider: { kind: "acpx", agent: "claude", model: "claude-opus-5-5", permissionMode: "approve-all", profile } };
|
|
it.each(["low", "medium", "high", "xhigh", "max"])("round trips explicit %s and retains the compact model envelope", claudeEffort => {
|
|
const parsed = parseNativeExecutionInput({ ...current, provider: { ...current.provider, claudeEffort } });
|
|
expect(parsed).toMatchObject({ schema: NATIVE_EXECUTION_INPUT_SCHEMA_V9, provider: { claudeEffort } });
|
|
expect(buildNativeModelEnvelope(parsed).schema).toBe("paperclip.native-model-envelope.v3");
|
|
expect(JSON.stringify(buildNativeModelEnvelope(parsed))).not.toContain("claudeEffort");
|
|
});
|
|
it("directs answered Claude v9 questions to the actual compact envelope on fresh and resumed turns", () => {
|
|
const response = { interactionId: "answer-1", kind: "ask_user_questions", response: {
|
|
status: "answered", result: { version: 1, answers: [{ questionId: "destination", optionIds: ["workspace"], otherText: "Use the task workspace." }] },
|
|
} };
|
|
const parsed = parseNativeExecutionInput({ ...current, provider: { ...current.provider, claudeEffort: "high" }, interactionResponses: [response] });
|
|
const constraint = nativeTaskConstraints(parsed).find(value => value.includes("already authoritatively answered"));
|
|
expect(constraint).toContain("interactionResponses[0].response.result.answers");
|
|
expect(constraint).not.toContain("message.interactionResponses");
|
|
expect(constraint).not.toContain("Use the task workspace.");
|
|
for (const envelope of [buildNativeModelEnvelope(parsed), buildNativeModelEnvelope(parsed, { resumedSession: true })]) {
|
|
expect(envelope).toHaveProperty("interactionResponses.0.response.result.answers", response.response.result.answers);
|
|
expect(envelope).not.toHaveProperty("message");
|
|
}
|
|
});
|
|
it("keeps historical readers closed and rejects aliases and foreign effort", () => {
|
|
const { homeMode: _homeMode, ...historical } = current;
|
|
for (const schema of ["paperclip.native-execution-input.v4", NATIVE_EXECUTION_INPUT_SCHEMA]) {
|
|
expect(() => parseNativeExecutionInput({ ...historical, schema, provider: { ...current.provider, claudeEffort: "high" } })).toThrow("claudeEffort");
|
|
expect(parseNativeExecutionInput({ ...historical, schema }).schema).toBe(schema);
|
|
}
|
|
expect(() => parseNativeExecutionInput({ ...current, provider: { ...current.provider, claudeEffort: "ultra" } })).toThrow("Claude effort");
|
|
expect(() => parseNativeExecutionInput({ ...current, provider: { ...current.provider, agent: "codex", profile: QUALIFIED_ACPX_PROFILES.codex, claudeEffort: "high" } })).toThrow("Claude effort");
|
|
});
|
|
});
|