## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Agents must publish generated files so users can inspect their results after a sandbox stops. > - Legacy sandbox bridges blocked attachment listing and could not carry multipart binary uploads through the queue transport. > - The native runner has a separate verified file registration path that needs the same durable result. > - This pull request repairs legacy binary transport, makes native download receipts explicit, and reveals new outputs in the task Artifacts tab. > - Users can open generated files from either runner without a transport flag change. ## Linked Issues or Issue Description Refs #13355 for the existing native file publication path. Related filename fixes: #2615 and #4788. Related sandbox persistence work: #13376. This change repairs attachment delivery through the existing API; it does not add workspace persistence. **What happened?** The upload helper first lists task attachments to avoid duplicates. Both legacy bridge allowlists rejected that GET request with 403. A direct multipart upload also failed: the queue bridge accepted only JSON, excluded attachment uploads, and converted bytes to UTF-8 text. Enabling HTTP/2 alone did not fix the missing listing route. These failures occurred before attachment storage. **Expected behavior** Both runners can publish a workspace file, register its work product, bind it to a response, and return a working download. The file stays accessible after sandbox deletion. A new output opens the task Artifacts tab. The agent receives accurate errors and decides how to retry or report a failure. **Steps to reproduce** 1. Run a legacy agent in Daytona with the duplex bridge disabled. 2. Invoke the bundled upload helper with Bash on a PNG or PDF. 3. Repeat with the duplex bridge enabled. 4. Register the same file through the native runner with generic API tools disabled. 5. Retry registration, delete the sandbox, and compare the downloaded bytes with the original file. **Paperclip version or commit** The failing baseline was `f2c5e54dc`. This branch is rebased onto `6cfe4acff`. **Deployment mode** Source checkout with a local API and real isolated Daytona sandboxes. ## What Changed - Allow authenticated attachment listing, upload, and content download through both legacy bridge transports. - Add optional base64 body encoding to queue envelopes. Preserve the existing UTF-8 contract when the encoding field is absent. Decode binary bodies before forwarding them. - Preserve multipart headers. Bound raw bytes, encoded envelopes, and in-flight reservations. Retain timeout and uncertain-write behavior. - Preserve helper deduplication and return structured uncertain-write failures. Document explicit Bash invocation in live skills. - Add attachment IDs and content/download paths to native registration receipts. Reuse verified local and remote file reads, attachment storage, work-product registration, and response binding. - Preserve Unicode upload filenames and provide a valid Content-Disposition header. - Open the task Artifacts tab when new stored outputs arrive, including a closed desktop panel or mobile drawer. Deduplicate upload and registration events by object ID. Preserve manual selection on refetches, edits, and panel remounts. - Remove task artifact filters, the company Artifacts footer link, and the unassigned group heading and timestamp. ### Screenshot  This is the local display fixture. The image was generated separately and published through the attachment and work-product APIs. ## Verification - Post-rebase `pnpm -r typecheck` and `pnpm build` pass. - The post-rebase local `pnpm test:run` passed 12,369 tests before one existing conversation reset test timed out; all 33 tests in that suite pass when rerun with isolated test configuration. The aggregate command stopped before its remaining groups. GitHub runs the complete suite in separate shards. - All [GitHub verification checks](https://github.com/paperclipai/paperclip/actions/runs/35017893350) pass on `b66ac276dd3d5fc738a22ecea783400106a494d4`: 32 successful checks and two configured skips. The native-session recovery assertion initially raced its fire-and-forget Sentry report; all 13 tests pass locally, and the same-commit CI rerun passes all 170 suites (3,079 tests). - Live post-rebase Daytona: all three file-delivery tests pass. They cover the real Bash helper with the queue bridge, the helper with HTTP/2, and native `register_deliverable` with generic API tools disabled. - Daytona cases cover PNG/PDF bytes, spaced and Unicode names, duplicate registration, response binding, authorization controls, and byte-for-byte downloads after sandbox deletion. - Local focused coverage includes transfer bounds, malformed encoding, interrupted transfers, remote path containment, and native file verification. The attachment route suite passes all 32 tests, including an eight-case filename-header matrix for Unicode and special characters, inline and forced downloads, and full and partial responses. - Browser verification confirms image previews, persisted downloads, automatic Artifacts selection, and preserved manual selection after edits and reloads. Desktop/mobile component coverage passes. The latest UI cleanup passes its 10 affected tests and token gates. - Coverage limit: the Daytona tests call the real helper and native registration path directly. They do not replay a complete model-led image-generation task through the browser. Live command (requires a configured Daytona credential): ```sh PAPERCLIP_FILE_DELIVERY_DAYTONA=1 pnpm exec vitest run server/src/__tests__/file-delivery-bridges.test.ts ``` ## Risks - Binary queue bodies use more memory because base64 adds encoding overhead. Transfer and process limits must remain aligned. - An interrupted write can have an unknown result. The bridge reports this state and preserves stable retry identities. - New artifacts intentionally change the active task tab. Existing history and repeated updates must not take focus again. - Transport flag defaults, server authorization, frozen skill snapshots, and completion policies remain unchanged. No schema migration is required. ## Model Used OpenAI Codex, based on GPT-6, with reasoning, repository tools, code execution, and browser testing. The runtime does not expose a more specific model ID or context-window size. ## 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 (e.g. `docs/...`, `fix/...`) 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>
7.6 KiB
Agent Artifact Upload Workflow
Generated files that a board user or reviewer should inspect as deliverables must be attached to the Paperclip issue before the agent chooses a final disposition. A local workspace path is not enough, because cloud users and reviewers often cannot access the agent's disk.
Native runner
When register_deliverable is available, use it for files in the bound local or
remote workspace. Supply a workspace-relative contentRef, basename filename,
contentType, exact byteSize and SHA-256, title, and a stable idempotencyKey.
The tool verifies the file, stores an attachment and artifact work product, and
binds it to the response. Generic API tools and a legacy API key are unnecessary.
Wait for the receipt. It includes attachmentId, contentPath, and
downloadPath, along with the existing command, revision, entity references,
and disposition. Reuse the original key after an ambiguous result. A receipt
confirms storage and response binding in Paperclip; it does not confirm delivery
to an external chat provider. If registration fails, use the returned error to
resolve the failure or explain the limitation; do not describe a workspace path
as an uploaded file.
Legacy adapters
Use Bash to run the helper bundled with the Paperclip skill from the repo root; installed skill files may not retain executable permissions:
bash skills/paperclip/scripts/paperclip-upload-artifact.sh path/to/output.webm \
--title "Walkthrough render" \
--summary "Rendered walkthrough for review"
The helper uses the authenticated Paperclip API from the current heartbeat environment:
PAPERCLIP_API_URLPAPERCLIP_API_KEYPAPERCLIP_COMPANY_IDPAPERCLIP_TASK_IDPAPERCLIP_RUN_ID
It uploads the file to
POST /api/companies/{companyId}/issues/{issueId}/attachments and creates an
artifact work product on POST /api/issues/{issueId}/work-products by default.
The command prints issue-safe markdown links for the final task comment.
Task artifact presentation
While a task is open, a new agent attachment, work product, or document opens the task's Artifacts tab and reveals the side panel (or mobile drawer). This uses stored object IDs, so it works with either runner. Uploading a file and registering its work product counts as one arrival. Existing history, revisions, and repeated query refreshes preserve the user's tab selection. Plans retain their existing Plan-tab behavior; unregistered user input attachments remain in the conversation.
Uploaded Artifacts vs Workspace Files
Use uploaded artifacts for deliverables: videos, PDFs, screenshots, archives,
reports, rendered HTML, or any file the board should inspect without needing the
agent's checkout. Attachment-backed artifact work products set type to
artifact and provider to paperclip, with metadata canonicalized from the
uploaded attachmentId.
Use workspace_file metadata only for important files that intentionally remain
in a project or execution workspace, such as source files, committed markdown
plans, or generated files whose meaning depends on the checkout. Workspace-only
references are useful signposts, but they are not durable uploads.
Expected work product metadata shape:
{
"resourceRef": {
"kind": "workspace_file",
"issueId": "<issue-id>",
"workspaceKind": "execution_workspace",
"workspaceId": "<execution-workspace-id>",
"relativePath": "doc/plans/example.md",
"line": 1,
"column": 1,
"displayPath": "doc/plans/example.md:1:1"
}
}
workspaceKind is execution_workspace or project_workspace. line and
column are optional. relativePath must be relative to that workspace root;
do not store host-local absolute paths as workspace references.
Workspace file links resolve only inside registered Paperclip workspaces. The
default target is the current issue's execution workspace first, then its
project workspace. A link may target another same-company project workspace only
when it carries both that projectId and workspaceId. Paperclip does not
resolve arbitrary machine-wide filesystem paths, absolute host paths, home
paths, or relative paths that escape the selected workspace.
Completion Pattern
When a task produces a user-inspectable deliverable file:
- Generate and verify the file locally.
- Upload it with
skills/paperclip/scripts/paperclip-upload-artifact.sh. - Keep the artifact work product unless the file is incidental; pass
--no-work-productonly for supporting files that should not be promoted. - Link the printed attachment URL in the final issue comment.
- Then set the final issue status.
For a response that is explicitly intended for an external chat conversation,
also pass each intended file with paperclipai issue comment --attachment-id <id>. Paperclip binds only those exact uploaded files to that comment; other
task attachments remain internal.
Final comments should name and link the uploaded artifact or work product, not just the local filesystem path. For workspace-only files, include the work product title and recorded relative path. Local paths can be included as diagnostic context, but they cannot be the only access path. Browse/search is a fallback for recovering workspace files when the issue link or chip is not available, not the preferred way to deliver files to users.
Video Examples
Upload an .mp4 render:
bash skills/paperclip/scripts/paperclip-upload-artifact.sh dist/demo.mp4 \
--title "Demo video render" \
--summary "MP4 render for board review"
Upload a .webm render:
bash skills/paperclip/scripts/paperclip-upload-artifact.sh out/walkthrough.webm \
--title "Walkthrough video" \
--summary "WebM walkthrough render"
The helper detects .mp4, .webm, and .mov content types. If a renderer uses
an unusual extension, pass the MIME type explicitly:
bash skills/paperclip/scripts/paperclip-upload-artifact.sh render.bin \
--title "Demo video render" \
--content-type video/mp4
Direct API Pattern
If the helper is unavailable, use the same API shape:
curl -sS -X POST \
"$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/issues/$PAPERCLIP_TASK_ID/attachments" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-F 'file=@"dist/demo.mp4";type=video/mp4'
Then create a work product when the uploaded file is the deliverable:
curl -sS -X POST \
"$PAPERCLIP_API_URL/api/issues/$PAPERCLIP_TASK_ID/work-products" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-H "Content-Type: application/json" \
--data-binary @artifact-work-product.json
Use type: "artifact", provider: "paperclip", and metadata containing the
uploaded attachmentId. The server canonicalizes contentType, byteSize,
contentPath, openPath, downloadPath, and originalFilename.
Verification
The file-delivery integration suite runs the real helper through queue and HTTP/2 gateways against a disposable API, database, and storage. It also tests native registration with generic API tools disabled, duplicate retries, Unicode filenames, company isolation, and downloads after deleting the workspace.
pnpm exec vitest run server/src/__tests__/file-delivery-bridges.test.ts
To run the same suite on disposable Daytona sandboxes, install the standalone
Daytona plugin's dependencies and set DAYTONA_API_KEY in the test process:
PAPERCLIP_FILE_DELIVERY_DAYTONA=1 pnpm exec vitest run server/src/__tests__/file-delivery-bridges.test.ts
The live fixture deletes each sandbox before checking that its attachments remain downloadable from Paperclip. It does not run unless explicitly enabled.