mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-16 02:40:39 +09:00
fix(cursor-local): resolve sandbox agent installs from cursor bin (#5686)
> _Stacked on top of #5685 (Harden remote sandbox runtime). Diff against master includes commits from earlier PRs in the stack — review focuses on the new commit only._ ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - The cursor-local adapter wraps the Cursor Agent CLI so a Paperclip workflow can drive it inside a sandbox > - When the adapter runs in a remote sandbox, the Cursor Agent CLI installs under `$HOME/.local/bin/cursor-agent` (or wherever `$XDG_BIN_HOME` points), not on the global PATH > - The existing post-install resolution assumed `cursor-agent` would resolve via the sandbox's login shell PATH after `npm install -g`, which fails on sandboxes where the install lands in a user-prefixed directory that isn't on PATH at probe time > - This pull request resolves the agent CLI from the cursor binary's own directory (`dirname "$(command -v cursor)"`) so the install probe and execute path agree on a real binary location > - The benefit is that cursor-local works correctly on any sandbox provider where `npm install` lands in a user-prefixed directory ## What Changed - `packages/adapters/cursor-local/src/server/remote-command.ts`: resolve the cursor-agent binary from the cursor bin directory after install, instead of relying on PATH. - `packages/adapters/cursor-local/src/server/test.ts`: corresponding probe tweak. - `packages/adapters/cursor-local/src/server/test.test.ts` (new) + `remote-command.test.ts`: focused coverage that exercises the install + resolve path against a sandbox runner that places the binary in a user-prefixed directory. ## Verification - `pnpm exec vitest run --no-coverage packages/adapters/cursor-local/src/server/test.test.ts packages/adapters/cursor-local/src/server/remote-command.test.ts packages/adapters/cursor-local/src/server/execute.test.ts` All passing locally. ## Risks - Local cursor-local runs are unaffected — the resolution change only kicks in for the sandbox install path. - Low risk; isolated to one adapter. ## Model Used - Provider: Anthropic - Model: Claude Opus 4.7 (1M context) - Capabilities used: tool use (Read/Edit/Bash), no code execution beyond local repo commands ## 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 run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots — N/A, no UI change - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
b24c6909e8
commit
0fe39a2d5c
6 changed files with 315 additions and 23 deletions
|
|
@ -44,6 +44,52 @@ printf '%s\\n' ok
|
|||
}
|
||||
|
||||
describe("prepareCursorSandboxCommand", () => {
|
||||
it("prefers the Cursor installer bin directory when the default agent entrypoint is installed there", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-cursor-remote-command-cursor-bin-"));
|
||||
const systemHomeDir = path.join(root, "system-home");
|
||||
const managedHomeDir = path.join(root, "managed-home");
|
||||
const remoteWorkspace = path.join(root, "workspace");
|
||||
const cursorAgentPath = path.join(systemHomeDir, ".cursor", "bin", "agent");
|
||||
await fs.mkdir(remoteWorkspace, { recursive: true });
|
||||
await writeFakeAgent(cursorAgentPath);
|
||||
|
||||
try {
|
||||
const result = await prepareCursorSandboxCommand({
|
||||
runId: "run-remote-command-cursor-bin",
|
||||
target: {
|
||||
kind: "remote",
|
||||
transport: "sandbox",
|
||||
shellCommand: "bash",
|
||||
remoteCwd: remoteWorkspace,
|
||||
runner: createLocalSandboxRunner(),
|
||||
timeoutMs: 30_000,
|
||||
},
|
||||
command: "agent",
|
||||
cwd: remoteWorkspace,
|
||||
env: {
|
||||
HOME: managedHomeDir,
|
||||
PATH: "/usr/bin:/bin",
|
||||
},
|
||||
remoteSystemHomeDirHint: systemHomeDir,
|
||||
timeoutSec: 30,
|
||||
graceSec: 5,
|
||||
});
|
||||
|
||||
expect(result.command).toBe(cursorAgentPath);
|
||||
expect(result.preferredCommandPath).toBe(cursorAgentPath);
|
||||
expect(result.remoteSystemHomeDir).toBe(systemHomeDir);
|
||||
expect(result.addedPathEntry).toBe(path.join(systemHomeDir, ".local", "bin"));
|
||||
expect(result.env.PATH?.split(":").slice(0, 2)).toEqual([
|
||||
path.join(systemHomeDir, ".local", "bin"),
|
||||
path.join(systemHomeDir, ".cursor", "bin"),
|
||||
]);
|
||||
expect(result.env.PATH).not.toContain(path.join(managedHomeDir, ".cursor", "bin"));
|
||||
expect(result.env.PATH).not.toContain(path.join(managedHomeDir, ".local", "bin"));
|
||||
} finally {
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps probing the original sandbox home after managed HOME overrides", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-cursor-remote-command-"));
|
||||
const systemHomeDir = path.join(root, "system-home");
|
||||
|
|
@ -79,7 +125,10 @@ describe("prepareCursorSandboxCommand", () => {
|
|||
expect(result.preferredCommandPath).toBe(systemAgentPath);
|
||||
expect(result.remoteSystemHomeDir).toBe(systemHomeDir);
|
||||
expect(result.addedPathEntry).toBe(path.join(systemHomeDir, ".local", "bin"));
|
||||
expect(result.env.PATH?.split(":")[0]).toBe(path.join(systemHomeDir, ".local", "bin"));
|
||||
expect(result.env.PATH?.split(":").slice(0, 2)).toEqual([
|
||||
path.join(systemHomeDir, ".local", "bin"),
|
||||
path.join(systemHomeDir, ".cursor", "bin"),
|
||||
]);
|
||||
expect(result.env.PATH).not.toContain(path.join(managedHomeDir, ".local", "bin"));
|
||||
} finally {
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue