mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 21:59:38 +01:00
## Thinking Path > - Paperclip manages AI agents and their work. > - Native agents open input images with a file reader. > - Plugin Runner tasks use an authenticated Runner connection. > - Runner needs a way to fetch an authorized file from the tenant app. > - The tenant grants access to the exact attachment for the active run. > - Runner downloads and verifies it before it returns a local path. ## Linked Issues or Issue Description Refs #19740. Builds on the attachment authorization and command transport added in #20939. ## What Changed - Add the PRP v3 `runner.attachment.download` command for native workspace providers. - Issue short-lived grants for exact authorized wake and historical attachments. Recheck the active run, source, size, and SHA-256 at the tenant endpoint. - Download current-wake inputs before provider dispatch. Download historical files when the authorized chat reader asks for them. Preserve SSH and sandbox command staging as a fallback. - Run HTTPS transfers off the Runner control loop. Report completion with one PRP event per transfer. Drop pending results on cancellation and clear files at turn settlement, authority rotation, and restart. - Grant restricted Codex sessions read-only access to the attachment directory. ## Verification - 69 targeted server attachment tests passed. They cover wake and historical downloads, authorization failures, and command fallback. - 22 focused historical attachment tests passed, including grant redemption under a held database lock. - Six focused Rust attachment tests passed, including a source-pinned Codex sandbox check. The sandbox could read the staged file but could not modify it or read adjacent Runner state. - The real Runner transport test confirmed one durable command and one completion event for a transfer. Restart-continuation tests confirmed fresh attachment paths appear before dispatch. - Server and Runner TypeScript checks, Rust formatting, and PRP generated schema checks passed. - Required GitHub CI passed on `7a33752bd7`. [CI run](https://github.com/paperclipai/paperclip/actions/runs/43122967837) - Greptile scored the same revision 5/5 with no unresolved review threads. [Review](https://github.com/paperclipai/paperclip/pull/20956#issuecomment-6808582134) ## Risks - Saved provider and Runner identities keep their existing formats. A previous Runner falls back to command staging where the target supports it. A restart continuation refreshes temporary attachment paths before dispatch. - Plugin Runner tasks require a compatible Runner ROM and outbound HTTPS access to the configured tenant origin. - Live agent-computer qualification remains a release check. ## Model Used OpenAI GPT-6 through Codex, with repository inspection, shell tools, and local test execution. Exact variant and context window are unavailable in this session. ## 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: #123` / `Closes #123` / `Refs #123` or (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links - [x] My branch name describes the change - [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>
525 lines
12 KiB
JSON
525 lines
12 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://paperclip.dev/schemas/prp/v3/command.schema.json",
|
|
"title": "PRP v3 runner command",
|
|
"type": "object",
|
|
"required": [
|
|
"schema",
|
|
"commandId",
|
|
"controllerSeq",
|
|
"type",
|
|
"issuedAt",
|
|
"payload"
|
|
],
|
|
"properties": {
|
|
"schema": {
|
|
"const": "paperclip.prp.command.v3"
|
|
},
|
|
"commandId": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 160
|
|
},
|
|
"controllerSeq": {
|
|
"type": "integer",
|
|
"minimum": 1
|
|
},
|
|
"type": {
|
|
"enum": [
|
|
"run.prepare",
|
|
"run.attach",
|
|
"session.open",
|
|
"turn.start",
|
|
"turn.steer",
|
|
"turn.interrupt",
|
|
"turn.stop",
|
|
"request.resolve",
|
|
"interaction.receipt",
|
|
"semantic_tool.result",
|
|
"session.snapshot",
|
|
"session.close",
|
|
"session.budget.increase",
|
|
"session.destroy",
|
|
"run.cancel",
|
|
"runner.drain",
|
|
"runner.suspend",
|
|
"runner.shutdown",
|
|
"session.goal.get",
|
|
"session.goal.set",
|
|
"session.goal.clear",
|
|
"external_provider.operation",
|
|
"runner.attachment.download"
|
|
]
|
|
},
|
|
"issuedAt": {
|
|
"type": "string",
|
|
"format": "date-time"
|
|
},
|
|
"deadlineAt": {
|
|
"type": "string",
|
|
"format": "date-time"
|
|
},
|
|
"precondition": {
|
|
"type": "object",
|
|
"properties": {
|
|
"runnerState": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"runState": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"sessionState": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"activeTurnId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
}
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"payload": {
|
|
"type": "object",
|
|
"additionalProperties": true
|
|
}
|
|
},
|
|
"allOf": [
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"type": {
|
|
"const": "session.goal.set"
|
|
}
|
|
},
|
|
"type": "object"
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"type": "object",
|
|
"properties": {
|
|
"requestId": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 160
|
|
},
|
|
"objective": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 4000
|
|
},
|
|
"status": {
|
|
"enum": [
|
|
"active",
|
|
"paused"
|
|
]
|
|
},
|
|
"tokenBudget": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"minimum": 1
|
|
}
|
|
},
|
|
"additionalProperties": true
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"type": {
|
|
"const": "external_provider.operation"
|
|
}
|
|
},
|
|
"type": "object"
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"type": "object",
|
|
"required": [
|
|
"requestId",
|
|
"bindingId",
|
|
"runId",
|
|
"normalizedSessionId",
|
|
"turnId",
|
|
"bindingGeneration",
|
|
"assignmentRevision",
|
|
"digest",
|
|
"action",
|
|
"input"
|
|
],
|
|
"properties": {
|
|
"requestId": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 240
|
|
},
|
|
"bindingId": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 240
|
|
},
|
|
"runId": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 240
|
|
},
|
|
"normalizedSessionId": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 240
|
|
},
|
|
"turnId": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 240
|
|
},
|
|
"bindingGeneration": {
|
|
"type": "integer",
|
|
"minimum": 1
|
|
},
|
|
"assignmentRevision": {
|
|
"type": "integer",
|
|
"minimum": 1
|
|
},
|
|
"digest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[0-9a-f]{64}$"
|
|
},
|
|
"action": {
|
|
"enum": [
|
|
"accept",
|
|
"tool",
|
|
"progress",
|
|
"finish",
|
|
"renew",
|
|
"request_user_input",
|
|
"consume_input"
|
|
]
|
|
},
|
|
"input": {
|
|
"type": "object"
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"type": {
|
|
"const": "external_provider.operation"
|
|
},
|
|
"payload": {
|
|
"properties": {
|
|
"action": {
|
|
"const": "accept"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"properties": {
|
|
"input": {
|
|
"type": "object",
|
|
"properties": {},
|
|
"required": [],
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"type": {
|
|
"const": "external_provider.operation"
|
|
},
|
|
"payload": {
|
|
"properties": {
|
|
"action": {
|
|
"const": "tool"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"properties": {
|
|
"input": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 160
|
|
},
|
|
"arguments": {
|
|
"type": "object"
|
|
}
|
|
},
|
|
"required": [
|
|
"name",
|
|
"arguments"
|
|
],
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"type": {
|
|
"const": "external_provider.operation"
|
|
},
|
|
"payload": {
|
|
"properties": {
|
|
"action": {
|
|
"const": "progress"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"properties": {
|
|
"input": {
|
|
"type": "object",
|
|
"properties": {
|
|
"text": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 12000
|
|
}
|
|
},
|
|
"required": [
|
|
"text"
|
|
],
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"type": {
|
|
"const": "external_provider.operation"
|
|
},
|
|
"payload": {
|
|
"properties": {
|
|
"action": {
|
|
"const": "finish"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"properties": {
|
|
"input": {
|
|
"type": "object",
|
|
"properties": {
|
|
"result": {
|
|
"type": "object"
|
|
}
|
|
},
|
|
"required": [
|
|
"result"
|
|
],
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"type": {
|
|
"const": "external_provider.operation"
|
|
},
|
|
"payload": {
|
|
"properties": {
|
|
"action": {
|
|
"const": "renew"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"properties": {
|
|
"input": {
|
|
"type": "object",
|
|
"properties": {
|
|
"expiresAtUnixMs": {
|
|
"type": "integer",
|
|
"minimum": 1
|
|
}
|
|
},
|
|
"required": [
|
|
"expiresAtUnixMs"
|
|
],
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"type": {
|
|
"const": "external_provider.operation"
|
|
},
|
|
"payload": {
|
|
"properties": {
|
|
"action": {
|
|
"const": "request_user_input"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"properties": {
|
|
"input": {
|
|
"type": "object",
|
|
"properties": {
|
|
"requestId": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 240
|
|
},
|
|
"questionSet": {
|
|
"$ref": "https://paperclip.dev/schemas/prp/v1/question-set.schema.json"
|
|
}
|
|
},
|
|
"required": [
|
|
"requestId",
|
|
"questionSet"
|
|
],
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"type": {
|
|
"const": "external_provider.operation"
|
|
},
|
|
"payload": {
|
|
"properties": {
|
|
"action": {
|
|
"const": "consume_input"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"properties": {
|
|
"input": {
|
|
"type": "object",
|
|
"properties": {
|
|
"requestId": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 240
|
|
},
|
|
"inputDigest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[0-9a-f]{64}$"
|
|
}
|
|
},
|
|
"required": [
|
|
"requestId",
|
|
"inputDigest"
|
|
],
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
}
|
|
],
|
|
"additionalProperties": true
|
|
}
|