mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-15 18:56:05 +01:00
## Thinking Path > - Paperclip manages AI agents and their work. > - Native runs must receive the files that users attach to their messages. > - Remote wake preparation returned file metadata without readable bytes. > - The historical chat reader also rejected every remote target. > - Command-backed sandboxes and SSH targets already provide a bound command transport. > - This pull request stages verified files through that transport and clears them after the turn. > - Agents can then open attached images with their normal image reader. ## Linked Issues or Issue Description Refs #19740. This change covers SSH and command-backed sandbox execution, including Daytona. Plugin Runner tasks without a command transport still need a separate versioned file-transfer contract. This PR does not close that broader issue. The open attachment-recovery PR #20778 concerns provider-session attachment journals, not input files. **What happened?** A user uploaded an image and sent it to an agent. The remote native run received its filename and size, but no readable file. Wake preparation reported `remote_workspace_staging_unsupported`. The historical read fallback also refused remote execution. **Expected behavior** Copy the exact admitted attachment into the bound remote workspace. Let the provider read the image. Preserve company, task, source-comment, size, and hash checks. Clear temporary file contents when the turn ends. **Steps to reproduce** 1. Select a native agent on a command-backed sandbox or Linux SSH environment. 2. Attach a PNG to its current task or in-app chat message. 3. Ask the agent to inspect it. 4. Check that wake preparation supplies a readable workspace-relative image path. ## What Changed - Add a remote staging owner with bounded commands, stdin transfer, hash verification, opaque paths, and safe image extensions. - Confine Linux filesystem operations through directory descriptors. Refuse symlinks and replacement files during cleanup. - Bind current wake files and authorized historical external-chat reads to the selected lease transport. - Join active writes during cancellation. Retain failed cleanup work until scope close. - Tell providers to open image files with an image-capable reader. - Handle stdin stream errors in the shared SSH transport. Failed uploads return failure instead of raising an unhandled EPIPE or reporting success. - Add database, transport, cancellation, integrity, cleanup, executor-wiring, and Linux filesystem regressions. Document support and limits. ## Verification - Prepared an isolated source checkout with `node scripts/agent-preflight.mjs --skip-rust-debug`. - Focused suites: `pnpm exec vitest run server/src/services/native-runtime/remote-attachment-staging.test.ts server/src/services/native-runtime/native-runner-file-handoff.test.ts server/src/services/native-runtime/chat-attachment-read.test.ts` — 74 passed, 4 Linux-only tests skipped. - Executor checks: `pnpm exec vitest run server/src/services/native-runtime/native-session-executor.test.ts -t 'attachment staging to the remote|stages from the authenticated|verifies frozen staged admission'` — 7 passed. - Server typecheck: `pnpm --filter @paperclipai/server typecheck`, followed by the server layered check after the final changes. - `git diff --check` passed. The local secret, PII, and private-reference diff scan found no matches. - `pnpm verify` returned exit 2 because the documentation change requires broad CI. No full local suite is claimed. - The four Linux filesystem tests were skipped locally because this macOS host has no available Docker daemon. Linux CI executed all 17 remote-staging tests successfully, including those four confinement tests: [server shard 10](https://github.com/paperclipai/paperclip/actions/runs/43102035096/job/130796646266). - SSH regression: the real pipe test reproduced two unhandled EPIPE errors before the fix, plus false success when the peer exited with code zero. The fixed SSH tests and existing argv tests pass (12 tests). The remote-staging transport suite also passes (13 tests; 4 Linux tests require CI). - `pnpm --filter @paperclipai/adapter-utils typecheck` passed after the SSH change. - Final revision `1c5a9288b8d5f267ddeac84638ff6075dbbbf8ec`: all applicable CI jobs passed, including typecheck, build, general and serialized tests, Runner checks, canary dry run, and all eight browser shards. [CI run](https://github.com/paperclipai/paperclip/actions/runs/43102035096). Linux CI also passed all 6 native SSH tests: [server shard 5](https://github.com/paperclipai/paperclip/actions/runs/43102035096/job/130796646349). - Greptile reviewed that final revision at 5/5 with no remaining findings. The review thread is resolved. GitHub reports the PR mergeable with a clean merge state. - No live model turn or deployed release is claimed. ## Risks - Remote staging requires Linux, Node, and a command transport bound to the selected lease. Plugin Runner tasks without that transport retain their unavailable result. - Each file uses bounded independent commands. Slow transports can delay preparation or cleanup. File bytes stay out of command arguments and provider-facing error messages. - An abrupt controller or remote-host failure can bypass cleanup. Ephemeral sandbox disposal remains the backstop. Retained files are never blindly overwritten or deleted. - Previous-release provider sessions and checkpoints retain their format and identity. New turns receive newly staged input files. Existing restart-admission checks still apply. No migration, runner-image change, or account-credential transfer is required. ## Model Used OpenAI `gpt-6.1-sol` through the Codex harness in T3 Code, with xhigh reasoning, repository inspection, code execution, and tests. No subagents were used. The context-window size is not exposed by this runtime. ## 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>
304 lines
14 KiB
Markdown
304 lines
14 KiB
Markdown
# 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.
|
|
|
|
## Read attached files in native runs
|
|
|
|
Native runs receive workspace-relative paths for the exact files admitted in
|
|
their current wake. On Linux SSH and command-backed sandbox targets, Paperclip
|
|
copies verified bytes through the bound lease transport before the provider
|
|
turn starts. The agent needs no Paperclip API key to open those paths. Image
|
|
inputs must be opened with the provider's image-capable file reader.
|
|
|
|
The same transport supports authorized historical external-chat reads. Their
|
|
existing conversation and source-comment checks still apply. External-chat
|
|
history tools do not grant access to unrelated tasks or in-app conversations.
|
|
Targets without a command transport still report attachment access as unavailable.
|
|
|
|
Temporary slots are isolated per staging operation. Cleanup clears the verified
|
|
file after the turn and refuses symlinks or replacement files. Abrupt controller
|
|
or remote-host failure can interrupt cleanup; sandbox disposal remains the
|
|
backstop for ephemeral environments. Retained provider sessions and checkpoints
|
|
keep their existing format and identity across this change.
|
|
|
|
## Upload files from a task
|
|
|
|
People can open the task's Artifacts tab and use the upload icon, or drop files
|
|
anywhere in the panel, including its header and existing artifact cards. Uploads
|
|
save directly to the task. They do not send a message, assign an agent, or
|
|
release a pause hold. Files from people and agents appear together in Artifacts.
|
|
|
|
Markdown imports create task documents. Repeated filenames get a new document
|
|
key instead of replacing an existing revision.
|
|
Hidden system keys and artifact-review prefixes are reserved; imports with
|
|
those filenames receive a visible upload document key. Other supported files use
|
|
the existing attachment API and its type and size limits. Failed uploads show
|
|
an inline Retry action while successful files remain available.
|
|
|
|
## 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.
|
|
|
|
`register_deliverable` stores the file on its current task. A completion report
|
|
can also cite a file on a direct child task that is done, because the child's
|
|
own completion verified that file. A parent that delegated a file to a child
|
|
links the child's file and does not register a copy. The child's file does not
|
|
count as delivery of a file that the parent's own request names.
|
|
|
|
Named JSON file outputs and explicit publication requests require the same
|
|
verified delivery evidence as other file outputs. A saved summary document does
|
|
not replace a requested downloadable JSON file. Reading JSON inputs or discussing
|
|
their contents inline does not by itself require a file attachment.
|
|
JSON output intent is recognized from a direct filename or an explicit output
|
|
destination. Ambiguous descriptions of input files joined by `to` remain text
|
|
requests; use `as`, `into`, or an explicit output file when an artifact is required.
|
|
A `Deliverable:` or `Deliverables` label also states the output. A file named
|
|
on that line, or in the list directly below it (blank lines between items are
|
|
allowed), is a requested file. This includes relative paths such as
|
|
`dist/index.html` and web page files such as `index.html` and `styles.css`.
|
|
Items that describe a repository change, pull request, commit, or branch do
|
|
not count, nor do files named as inputs (`a comparison of input.csv and
|
|
baseline.csv`), files the item forbids (`do not create report.pdf`), files to
|
|
be edited in place (`update styles.css`) unless the item also asks to publish
|
|
them, files to be written inline in the reply (`index.html and styles.css inline
|
|
in the reply`), or items that say no files are created. An inline or no-upload
|
|
instruction about another output in the same item does not cancel a named file
|
|
(`index.html and a short summary in the reply`).
|
|
|
|
The completion gate permits a narrow sentence-bound exception for one requested
|
|
file when the following sentence explicitly qualifies it, for example:
|
|
`Write memory.txt. This is personal memory, not a task deliverable.` or
|
|
`Write internal-proof.txt. This is an internal verification file, not a deliverable.`
|
|
Each qualifier applies to one file in the immediately preceding request sentence.
|
|
Separate file requests can each qualify when each has its own following qualifier.
|
|
Multiple files requested in one sentence, unqualified report requests, and explicit
|
|
attachment/download requests still need verified accessible publication evidence. Saying only “for internal verification”
|
|
does not establish this exception. Expected denied native writes in a negative
|
|
test are separately recognized as denied attempts, not requested deliverables.
|
|
|
|
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.
|
|
|
|
With workspace-relative file access, create or copy task deliverables into the
|
|
directory returned by `get_workspace.cwd` before registration. `AGENT_HOME`
|
|
holds personal instructions and memory. If registration reports a missing
|
|
workspace file, copy the file to that task directory and retry with a relative
|
|
`contentRef`.
|
|
|
|
## 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:
|
|
|
|
```sh
|
|
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_URL`
|
|
- `PAPERCLIP_API_KEY`
|
|
- `PAPERCLIP_COMPANY_ID`
|
|
- `PAPERCLIP_TASK_ID`
|
|
- `PAPERCLIP_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
|
|
|
|
Existing and newly arriving agent attachments, work products, and documents add
|
|
the task's Artifacts tab without selecting it, opening the side panel or mobile
|
|
drawer, or changing the current document/file link. If the pane is closed, the
|
|
tab is available when the user opens it. This uses stored object IDs, so it works
|
|
with either runner. Uploading a file and registering its work product counts as
|
|
one arrival. Revisions and repeated query refreshes preserve dismissed tabs and
|
|
the user's 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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
1. Generate and verify the file locally.
|
|
2. Upload it with `skills/paperclip/scripts/paperclip-upload-artifact.sh`.
|
|
3. Keep the artifact work product unless the file is incidental; pass
|
|
`--no-work-product` only for supporting files that should not be promoted.
|
|
4. Link the printed attachment URL in the final issue comment.
|
|
5. 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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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`.
|
|
|
|
The optional `executionWorkspaceId` on work-product create and update requests
|
|
must identify an execution workspace in the same company. A project workspace
|
|
ID is a different identifier and cannot be used here. Omit the field when no
|
|
execution workspace is available, or send `null` to clear an existing link.
|
|
Invalid references return `422` without changing the work product or the current
|
|
primary product.
|
|
|
|
## 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.
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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.
|
|
|
|
## Text attachment previews
|
|
|
|
In the task chat layout, select a text attachment in Artifacts, a work-product
|
|
card, or a chat attachment chip to open a named right-side tab. Reopening the
|
|
same attachment focuses its existing tab. Tabs can be switched and closed.
|
|
On mobile, the same viewer opens in the task details drawer.
|
|
|
|
Markdown work products keep their expandable review document, annotations,
|
|
revision indicator, and document links. **Open in tab** is a separate action.
|
|
Work-product cards keep **Download** as a direct original-file download.
|
|
Text file cards provide **Open in tab** beside their existing actions. CSV cards
|
|
keep this action before and after loading their data preview.
|
|
|
|
Markdown attachments offer **Rendered** and **Raw** views. Other supported text
|
|
files display literal text. Image references and diagram source remain inert;
|
|
opening a preview does not load attachment-selected media URLs. The viewer
|
|
provides a download action. Preview reads
|
|
are limited to 512 KiB; oversized, unsupported, or unavailable files show an
|
|
explicit fallback instead of attempting an unbounded render. A failed read can
|
|
be retried. Workspace files continue to use the existing workspace file viewer.
|
|
|
|
Storybook: **Tasks / Text file tabs** covers opening from Artifacts, Markdown,
|
|
plain text, empty files, oversized files, missing attachments, and a narrow panel.
|