b18669452f
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Paperclip already separates agent adapters from execution environments, so agents can run locally, over SSH, or through sandbox providers. > - Sandbox provider plugins let Paperclip add new cloud runtimes without changing each agent adapter. > - Novita Agent Sandbox is a cloud runtime for AI agent workloads with isolated filesystems, command execution, templates, timeout controls, and pause/resume behavior. > - Paperclip currently has sandbox provider examples for Daytona and Cloudflare, but not Novita. > - This pull request adds a Novita sandbox provider plugin using the existing provider-plugin lifecycle. > - The benefit is that Paperclip users can run existing adapters such as Codex, Claude, Gemini, OpenCode, Cursor, or ACPX inside Novita Agent Sandbox environments. ## Linked Issues or Issue Description Fixes #7596 ## What Changed - Added `packages/plugins/sandbox-providers/novita` as a standalone sandbox provider plugin package. - Registered provider key `novita` with `kind: "sandbox_provider"` and `environment.drivers.register` capability. - Implemented Novita environment lifecycle hooks: validate config, probe, acquire lease, resume lease, release lease, destroy lease, realize workspace, and execute commands. - Added config support for `apiKey`, `domain`, `template`, `requestedCwd`, `timeoutMs`, `requestTimeoutMs`, `secure`, `autoPause`, and `reuseLease`. - Added README documentation for setup, configuration, and lifecycle behavior. - Added tests for manifest shape, config parsing, safe shell command wrapping, stdin delimiter safety, and env-key validation. ## Verification From `packages/plugins/sandbox-providers/novita`: - `pnpm typecheck` - `pnpm test` The tests avoid live Novita API calls and cover the provider's static contract and command-wrapping behavior. Live end-to-end verification requires a Paperclip instance with the plugin installed and a Novita API key configured as either a Paperclip secret or `NOVITA_API_KEY` in the worker environment. ## Risks - This adds a new direct dependency on the Novita Sandbox JS SDK (`novita-sandbox`). Socket/Snyk should review the package as part of normal dependency checks. - The implementation relies on Novita SDK command execution semantics; live provider behavior should be verified with a real Novita sandbox before marking the plugin production-ready. - `reuseLease` maps Paperclip release behavior to Novita `betaPause()`. If pause is unavailable for a selected template, the plugin falls back to best-effort kill during release. - Low migration risk for existing users because this is a new standalone provider plugin and does not change existing adapters or built-in providers. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI GPT-5 Codex via Codex CLI, with repository file access, shell command execution, GitHub CLI/API usage, and local TypeScript/Vitest verification. Web and local documentation context were used for Novita Sandbox SDK/API behavior. ## 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 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 - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] 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> Co-authored-by: Devin Foley <devin@paperclip.ing>
42 lines
1.4 KiB
JavaScript
42 lines
1.4 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { existsSync, mkdirSync, lstatSync, readlinkSync, rmSync, symlinkSync } from "node:fs";
|
|
import { dirname, join, relative, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = resolve(__dirname, "..");
|
|
const packageDir = process.cwd();
|
|
const sdkDir = join(repoRoot, "packages", "plugins", "sdk");
|
|
const scopeDir = join(packageDir, "node_modules", "@paperclipai");
|
|
const linkTarget = join(scopeDir, "plugin-sdk");
|
|
|
|
if (!existsSync(join(packageDir, "package.json"))) {
|
|
throw new Error(`No package.json found in plugin directory: ${packageDir}`);
|
|
}
|
|
|
|
mkdirSync(scopeDir, { recursive: true });
|
|
|
|
try {
|
|
const stat = lstatSync(linkTarget);
|
|
if (stat.isSymbolicLink()) {
|
|
const existingTarget = readlinkSync(linkTarget);
|
|
const relativeSdkDir = relative(scopeDir, sdkDir);
|
|
if (existingTarget === relativeSdkDir) {
|
|
console.log(` ✓ Local @paperclipai/plugin-sdk already linked for ${packageDir}`);
|
|
process.exit(0);
|
|
}
|
|
rmSync(linkTarget, { force: true });
|
|
} else {
|
|
console.log(" i Keeping existing installed @paperclipai/plugin-sdk directory in place");
|
|
process.exit(0);
|
|
}
|
|
} catch {
|
|
// target does not exist yet
|
|
}
|
|
|
|
const relativeSdkDir = relative(scopeDir, sdkDir);
|
|
symlinkSync(relativeSdkDir, linkTarget, "dir");
|
|
|
|
console.log(` ✓ Linked local @paperclipai/plugin-sdk for ${packageDir}`);
|