mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 08:22:20 +02:00
Bind Daytona candidate image identity to selected provider assets
Refresh the reviewed resolved dependency digest and fix ACP selector typing. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
588dd00af1
commit
a3a71ebdb1
5 files changed
+37
-3
No files matched your search
@@ -28,7 +28,7 @@ COPY cli/package.json ./cli/package.json
|
||||
# The complete resolved lock (including transitive integrity hashes) is reviewed.
|
||||
# Reject registry-time drift BEFORE installing packages or running lifecycle code.
|
||||
# Refresh this digest together with source/provider dependency changes.
|
||||
ARG PAPERCLIP_RUNNER_LOCK_SHA256=d7d96cf0d98cf0946f6195e29ba173b03711a947a1c38f312b67cda56c254c22
|
||||
ARG PAPERCLIP_RUNNER_LOCK_SHA256=3b2842ff53812112cf2eb59efa0664e10630d5f7a7586813fdd07741131486f9
|
||||
RUN pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile \
|
||||
&& printf '%s pnpm-lock.yaml\n' "${PAPERCLIP_RUNNER_LOCK_SHA256}" > /tmp/provider-lock.sha256 \
|
||||
&& sha256sum -c /tmp/provider-lock.sha256 \
|
||||
|
||||
@@ -90,6 +90,10 @@ A pack with candidate assets does not qualify or enable that provider.
|
||||
|
||||
For an isolated Linux x64 Daytona qualification image, pass
|
||||
`--build-arg PAPERCLIP_RUNNER_CANDIDATE_PROVIDERS=cursor` with the normal build arguments.
|
||||
Compute its content ID with the same selection:
|
||||
`pnpm --silent test:e2e:runner:image-id --candidate-providers=cursor`.
|
||||
Candidate assets and the default pack have distinct image identities. Never reuse
|
||||
the default image's content ID for a candidate build.
|
||||
Use each provider branch's recorded version and explicit model. Keep credentials
|
||||
out of images. Paid qualification requires bound provider and Daytona credentials,
|
||||
inspectable spend, and the shared $100 ceiling recorded in the capability report.
|
||||
|
||||
@@ -34,6 +34,7 @@ export const DAYTONA_IMAGE_INPUT_PATHS = [
|
||||
"packages/paperclip-runner/runner/crates",
|
||||
"packages/paperclip-runner/scripts/acpx-sidecar-contract.mjs",
|
||||
"packages/paperclip-runner/scripts/build-provider-pack.mjs",
|
||||
"packages/paperclip-runner/scripts/candidate-provider-pack.mjs",
|
||||
"packages/paperclip-runner/scripts/build-verified-provider-entrypoints.mjs",
|
||||
"packages/paperclip-runner/scripts/generate-acpx-sidecar-contract.mjs",
|
||||
"packages/paperclip-runner/scripts/generate-protocol-schema-module.mjs",
|
||||
@@ -65,6 +66,17 @@ export interface DaytonaImageContentOptions {
|
||||
platform?: string;
|
||||
baseImages?: readonly string[];
|
||||
frontendDigest?: string;
|
||||
/** Must match the Docker PAPERCLIP_RUNNER_CANDIDATE_PROVIDERS build argument. */
|
||||
candidateProviders?: readonly string[];
|
||||
}
|
||||
|
||||
export function normalizedDaytonaCandidateProviders(values: readonly string[]): string[] {
|
||||
const selected = [...values].sort();
|
||||
if (selected.some(value => !["cursor", "copilot", "pi"].includes(value))
|
||||
|| new Set(selected).size !== selected.length) {
|
||||
throw new Error("Daytona candidate providers must be distinct known ACP profiles");
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
function compareNames(left: string, right: string): number {
|
||||
@@ -262,6 +274,9 @@ export async function computeDaytonaImageContentId(
|
||||
for (const baseImage of await resolveBaseImages(root, options.baseImages)) {
|
||||
updateRecord(hash, "base-image", baseImage);
|
||||
}
|
||||
for (const candidate of normalizedDaytonaCandidateProviders(options.candidateProviders ?? [])) {
|
||||
updateRecord(hash, "candidate-provider", candidate);
|
||||
}
|
||||
for (const inputPath of inputPaths) {
|
||||
await hashEntry(hash, root, inputPath);
|
||||
}
|
||||
@@ -270,7 +285,13 @@ export async function computeDaytonaImageContentId(
|
||||
|
||||
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : null;
|
||||
if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
|
||||
computeDaytonaImageContentId()
|
||||
const arguments_ = process.argv.slice(2);
|
||||
const candidateFlag = arguments_[0];
|
||||
if (arguments_.length > 1 || (candidateFlag !== undefined && !candidateFlag.startsWith("--candidate-providers="))) {
|
||||
throw new Error("Expected only --candidate-providers=cursor,copilot,pi");
|
||||
}
|
||||
const candidateProviders = candidateFlag?.slice("--candidate-providers=".length).split(",").filter(Boolean) ?? [];
|
||||
computeDaytonaImageContentId({ candidateProviders })
|
||||
.then((contentId) => process.stdout.write(`${contentId}\n`))
|
||||
.catch((error: unknown) => {
|
||||
process.stderr.write(
|
||||
|
||||
@@ -166,6 +166,7 @@ describe("runner E2E Daytona image contract", () => {
|
||||
"patches",
|
||||
"packages/paperclip-eval-kernel/src",
|
||||
"packages/paperclip-runner/package.json",
|
||||
"packages/paperclip-runner/scripts/candidate-provider-pack.mjs",
|
||||
"packages/paperclip-runner/runner/crates",
|
||||
"packages/paperclip-runner/src",
|
||||
]) {
|
||||
@@ -242,6 +243,14 @@ describe("runner E2E Daytona image contract", () => {
|
||||
'pub const VERSION: &str = "one";\n',
|
||||
);
|
||||
const baseline = await computeDaytonaImageContentId(options);
|
||||
const candidate = await computeDaytonaImageContentId({ ...options, candidateProviders: ["pi"] });
|
||||
expect(candidate).not.toBe(baseline);
|
||||
expect(await computeDaytonaImageContentId({ ...options, candidateProviders: ["copilot", "pi"] }))
|
||||
.toBe(await computeDaytonaImageContentId({ ...options, candidateProviders: ["pi", "copilot"] }));
|
||||
await expect(computeDaytonaImageContentId({ ...options, candidateProviders: ["pi", "pi"] }))
|
||||
.rejects.toThrow("distinct known");
|
||||
await expect(computeDaytonaImageContentId({ ...options, candidateProviders: ["unknown"] }))
|
||||
.rejects.toThrow("distinct known");
|
||||
expect(
|
||||
await computeDaytonaImageContentId({
|
||||
...options,
|
||||
|
||||
@@ -240,7 +240,7 @@ export function CodexLocalConfigFields({
|
||||
{runnerManaged && runnerProvider === "acpx" && (
|
||||
<Field configSection="adapter" label="ACP agent" hint="Cursor, GitHub Copilot, and Pi are awaiting local and Daytona qualification.">
|
||||
<select className={inputClass}
|
||||
value={isCreate ? values!.adapterSchemaValues?.acpxAgent ?? "claude" : eff("adapterConfig", "acpxAgent", config.acpxAgent ?? "claude")}
|
||||
value={String(isCreate ? values!.adapterSchemaValues?.acpxAgent ?? "claude" : eff("adapterConfig", "acpxAgent", config.acpxAgent ?? "claude"))}
|
||||
onChange={(event) => {
|
||||
const profile = PAPERCLIP_RUNNER_ACPX_PROFILES.find(entry => entry.value === event.target.value);
|
||||
if (!profile?.qualified) return;
|
||||
|
||||
Reference in new issue
Block a user