mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-11 14:10:50 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Container images include a movable provider pack for agent execution. > - pnpm executable wrappers can contain the temporary build directory. > - New optional Copilot packages add wrappers that the build does not replace. > - This pull request gives those installed wrappers relative executable paths. > - Image publication can finish while the existing path check remains enforced. ## Linked Issues or Issue Description Refs #15572 and #15560. The small portability loop comes from cryppadotta's larger Copilot runtime PR #15560. This separate fix repairs image publication without waiting for that feature's qualification and runtime changes. That PR can remove its duplicate loop after this lands. **What happened?** The standard Docker build stops with `Provider pack shim copilot-linux-x64 retains its temporary build path`. The failure occurred before and after #15522. See [the failed master build](https://github.com/paperclipai/paperclip/actions/runs/37802065316). **Expected behavior** Installed native Copilot wrappers resolve their pinned executable after the provider pack moves. Optional packages that are absent do not gain a command. The builder still rejects wrappers with temporary paths. **Steps to reproduce** 1. Run the provider-pack build from the affected master revision on Linux x64. 2. Let `pnpm deploy --prod` install the optional Copilot package. 3. The wrapper scan rejects its temporary `NODE_PATH`. **Paperclip version or commit** Master `3367b75ccce34d02f355cda1f1ed3fe0b34cf93d`. **Deployment mode** Docker and provider-pack builds. ## What Changed - Replace installed Copilot platform wrappers with relative native executable launchers. - Move the existing executable wrapper writer into an importable helper. Retain the same launch behavior for Node, Claude and OpenCode. - Test relocation, argument handling, exit status, absent optional packages and missing executable packages. Register the tests in the existing Runner test preparation command. - Hash the helper in Daytona image identity and prove that helper changes invalidate the image cache. - Document the packaging rule. Keep dependencies, provider qualification and the final temporary-path check unchanged. ## Verification - 17 Node packaging tests passed across the new wrapper tests, provider-pack release tests and candidate selection tests. - Six existing bundled remote-provider-pack tests passed using the Runner Vitest configuration. - Nine Daytona image identity tests and Runner E2E typecheck passed after the cache-input correction. - `pnpm -r typecheck`: passed. - `pnpm build`: passed. - Real `pnpm deploy --prod` reproduction on macOS ARM64: the original Copilot wrapper contained the temporary path. After the rewrite and directory relocation, the actual executable returned Copilot CLI 1.0.88 with exit code zero using only `/usr/bin:/bin` in `PATH`. - Syntax checks, `git diff --check` and the pre-push secret scan passed. - The full local `pnpm test:run` reproduced the same five skill/connector fixture failures observed earlier in this workspace. It was stopped after current-head clean-checkout CI passed; later local phases were not run. This local run is not claimed as passing. Focused packaging tests, workspace typecheck/build and all hosted CI passed. - [Hosted Docker verification passed](https://github.com/paperclipai/paperclip/actions/runs/37804902678): Linux AMD64 and ARM64 image builds, multi-architecture publication and the process-reaping smoke check. This run tested `779d94989c54d9abbeba0838194c951186af66a6`; the only later changes are Daytona cache identity and its regression test. Provider-pack build code is identical. Local Docker did not respond within the bounded probe. - Current head `db2c19e270b8d5a7bab5db39d3de0e4761cf57c3`: 54 successful checks, two conditional skips, no failures and no merge conflicts. Apex is 5/5 with no unresolved comments. ## Risks The helper uses each installed package's exported executable. An installed wrapper with a missing package still fails the build. This change does not execute Copilot during image construction, alter dependency pins, change runtime admission or weaken the temporary-path check. ## Model Used OpenAI GPT-6 via Codex, with reasoning, repository inspection, tool use and code execution. The exact serving model identifier and context window are not exposed in this session. ## 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 and contains no internal Paperclip ticket id or instance-derived details - [x] I have run focused tests locally and they pass; the full local-suite limitation is documented above - [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>
60 lines
2.8 KiB
JavaScript
60 lines
2.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { spawnSync } from "node:child_process";
|
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import test from "node:test";
|
|
import { writePortableCopilotShims } from "./provider-pack-executable-shims.mjs";
|
|
|
|
function fixture(t) {
|
|
const root = mkdtempSync(join(tmpdir(), "provider-shims-"));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const pack = join(root, "temporary pack");
|
|
mkdirSync(join(pack, "node_modules", ".bin"), { recursive: true });
|
|
writeFileSync(join(pack, "package.json"), '{"type":"module"}');
|
|
return { root, pack };
|
|
}
|
|
|
|
for (const platform of ["darwin-arm64", "darwin-x64", "linux-x64"]) {
|
|
test(`relocates the installed ${platform} Copilot command and preserves arguments and exit status`, (t) => {
|
|
const { root, pack } = fixture(t);
|
|
const name = `copilot-${platform}`;
|
|
const installed = join(pack, "node_modules", "@github", name);
|
|
mkdirSync(installed, { recursive: true });
|
|
writeFileSync(join(installed, "package.json"), JSON.stringify({
|
|
name: `@github/${name}`, exports: { ".": "./copilot" }, bin: { [name]: "copilot" },
|
|
}));
|
|
// A provider-free executable proves relocation without invoking a real CLI.
|
|
writeFileSync(join(installed, "copilot"), '#!/bin/sh\nprintf "%s\\n" "$@"\nexit 17\n', { mode: 0o755 });
|
|
const shim = join(pack, "node_modules", ".bin", name);
|
|
writeFileSync(shim, `#!/bin/sh\nexport NODE_PATH="${pack}/node_modules"\nexit 99\n`);
|
|
|
|
writePortableCopilotShims(pack);
|
|
assert.equal(readFileSync(shim, "utf8").includes(root), false);
|
|
const relocated = join(root, "relocated pack");
|
|
renameSync(pack, relocated);
|
|
const args = ["argument with spaces", "", "$(not-a-command)"];
|
|
const result = spawnSync(join(relocated, "node_modules", ".bin", name), args, {
|
|
cwd: root, env: { PATH: "/usr/bin:/bin" }, encoding: "utf8", timeout: 5_000,
|
|
});
|
|
assert.equal(result.error, undefined);
|
|
assert.equal(result.status, 17);
|
|
assert.equal(result.stdout, args.join("\n") + "\n");
|
|
assert.equal(result.stderr, "");
|
|
});
|
|
}
|
|
|
|
test("does not create commands for absent optional platform packages", (t) => {
|
|
const { pack } = fixture(t);
|
|
writePortableCopilotShims(pack);
|
|
for (const platform of ["darwin-arm64", "darwin-x64", "linux-x64"]) {
|
|
assert.equal(existsSync(join(pack, "node_modules", ".bin", `copilot-${platform}`)), false);
|
|
}
|
|
});
|
|
|
|
test("fails when an installed command has no matching package", (t) => {
|
|
const { pack } = fixture(t);
|
|
writeFileSync(join(pack, "node_modules", ".bin", "copilot-linux-x64"), "invalid old shim");
|
|
assert.throws(() => writePortableCopilotShims(pack), { code: "MODULE_NOT_FOUND" });
|
|
});
|