mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-06 21:05:21 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The adapter/runtime layer has to move files into sandboxes safely and efficiently > - The current sync-in path needs a provider-delegable seam so providers can use their native upload transport when available > - The upload contract also needs ordered post-upload commands so extracted content can be finalized fail-fast after transfer > - The fallback path still has to preserve current behavior when the provider does not expose native sync verbs > - This pull request adds the contract and runtime seam for provider-delegable sync-in, plus the single-stream collapse flag > - The benefit is fewer round trips, a cleaner provider-owned upload path, and a compatible fallback for existing runners ## Linked Issues or Issue Description No public GitHub issue exists for this change. The underlying feature request is described below in the repository's feature-request format. ### Problem or motivation Paperclip needs a sync-in path that lets each provider choose the best available upload transport instead of forcing the harness to orchestrate uploads the same way every time. The runtime also needs a way to describe ordered post-upload commands so providers can finalize extracted content fail-fast after transfer. ### Proposed solution Extend the sync contract with ordered post-upload commands, forward that contract through the plugin and environment runtime layers, and make client syncIn always available. When a provider advertises native sync verbs, the client should delegate to that transport; otherwise it should fall back to the existing tarball/write/extract behavior and then run the post-upload commands in order. ### Alternatives considered Keeping upload orchestration entirely host-side would avoid a contract change, but it would block provider-specific transport optimizations and keep the harness responsible for a path the provider can do more efficiently. A separate post-upload API would add another surface without improving the existing sync flow. ### Roadmap alignment This work aligns with the broader runtime and adapter roadmap because it improves provider integration without changing the external product model. It is an additive contract change that preserves backward compatibility for providers that do not expose native sync verbs. ### Additional context The fallback path still needs to preserve existing observable behavior, including command ordering, cwd confinement, and fail-fast execution. The single-stream progress flag is part of the same transport improvement so smaller writes can collapse to a single round trip when the runner supports it. ## What Changed - Added ordered `postUploadCommands` support to the sync operation contract and SDK mirror. - Plumbed the sync-in contract through the plugin and environment runtime layers. - Implemented a runtime client `syncIn` path that delegates to native provider transport when available, otherwise uses the generic tarball/write/extract fallback. - Preserved fail-fast execution of ordered post-upload commands in the fallback path. - Flipped the sandbox runner's single-stream stdin progress flag to collapse small `writeFile` operations to a single round trip. - Added and updated tests for contract forwarding, fallback behavior, cwd rejection, fail-fast behavior, and single-stream collapse. ## Verification - `pnpm --filter @paperclipai/plugin-sdk exec vitest run protocol.postupload.test.ts` - `pnpm --filter @paperclipai/plugin-sdk exec vitest run environment-sync-negotiation.test.ts` - `pnpm --filter @paperclipai/adapter-utils exec vitest run command-managed-runtime.test.ts` - `pnpm --filter @paperclipai/server exec vitest run environment-execution-target.test.ts` - Local typecheck and targeted suite runs reported in the handoff passed before PR creation. ## Risks - The new fallback path could diverge from the previous inline upload behavior if the tarball/extract contract changes. - Provider-native sync handling may expose provider-specific edge cases if a runner advertises sync verbs but does not fully honor the contract. - The single-stream flag changes transport behavior for small uploads, so regressions would likely show up as round-trip or upload failures. ## Model Used OpenAI Codex (GPT-5, tool-using coding agent). ## 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 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>
46 lines
1.9 KiB
TypeScript
46 lines
1.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import type { PluginPostUploadCommand, PluginSyncOperation } from "../src/protocol.js";
|
|
|
|
// Phase 1 contract test (PAP-3222 / PAP-3159 #2+#4): the sync operation carries
|
|
// an OPTIONAL, ordered `postUploadCommands` array whose absence is
|
|
// indistinguishable from today's behavior. The field is a structured control
|
|
// command — `{ command; cwd?; timeoutMs? }` — never free interpolation, and per
|
|
// Security Condition C1 may be authored ONLY by Paperclip/adapter code.
|
|
describe("PluginSyncOperation.postUploadCommands", () => {
|
|
it("test_plugin_sync_operation_carries_ordered_post_upload_commands", () => {
|
|
const commands: PluginPostUploadCommand[] = [
|
|
{ command: "tar -xf /runtime/asset.tar -C /runtime/asset" },
|
|
{ command: "merge-auth /runtime/asset", cwd: "/runtime/asset", timeoutMs: 30_000 },
|
|
];
|
|
const operation: PluginSyncOperation = {
|
|
operationId: "sync-op-1",
|
|
files: [
|
|
{ sourcePath: "/host/asset", targetPath: "/runtime/asset", kind: "directory" },
|
|
],
|
|
postUploadCommands: commands,
|
|
};
|
|
|
|
// The field is present, is the SAME ordered array we supplied, and preserves
|
|
// order (no reordering, no rewriting of the opaque command strings).
|
|
expect(operation.postUploadCommands).toBeDefined();
|
|
expect(operation.postUploadCommands).toHaveLength(2);
|
|
expect(operation.postUploadCommands?.[0]?.command).toBe(
|
|
"tar -xf /runtime/asset.tar -C /runtime/asset",
|
|
);
|
|
expect(operation.postUploadCommands?.[1]).toEqual({
|
|
command: "merge-auth /runtime/asset",
|
|
cwd: "/runtime/asset",
|
|
timeoutMs: 30_000,
|
|
});
|
|
});
|
|
|
|
it("absent postUploadCommands is undefined (backward compatible)", () => {
|
|
const operation: PluginSyncOperation = {
|
|
operationId: "sync-op-2",
|
|
files: [{ sourcePath: "/host/a", targetPath: "/runtime/a", kind: "directory" }],
|
|
};
|
|
expect(operation.postUploadCommands).toBeUndefined();
|
|
});
|
|
});
|