mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 01:50:13 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents run through the native runner. Codex and Claude Code each have their own plan mode, and each produces a proposed-plan document. > - Paperclip already has a planning work mode, an issue `plan` document, and a `request_confirmation` approval gate for plans. > - These parts were not connected. The Codex proposed-plan item was transcript text only. The server path never put Codex into plan mode. Claude was never put into plan mode, and its ExitPlanMode plan was dropped. A planning run could close its task as done with no approval. > - This pull request turns a harness's own proposed plan into the issue's Plan document and the existing approval gate, for Codex and Claude. > - The benefit is that users approve or send back the agent's real plan from the task chat. Approve resumes the agent. Request changes sends the reason back to the agent. ## Linked Issues or Issue Description Refs #19252 **Subsystem affected** Native runner provider events (Codex and ACPX Claude), the runner Codex provider (Rust), server native plan synchronization, and the task chat UI. **Problem or motivation** Codex and Claude both produce a proposed-plan document in their native plan mode. Paperclip did not use it. In live tests on the server path, a Codex planning run was not in Codex plan mode, saved its own `implementation-plan` document, and marked the task done without approval. Claude planning runs were not in Claude plan mode, and the ExitPlanMode plan text never reached Paperclip. **Proposed solution** Add a canonical `plan.proposed` event for a harness's proposed plan. Put Codex and Claude into their native plan mode for planning runs. Save a completed proposal as the issue `plan` document with the existing `request_confirmation` gate. Agents can also propose a plan on a standard task; the work mode stays standard. **Alternatives considered** Reuse `plan.updated`. Its schema is the within-turn checklist and is not the durable Plan document, so a separate event keeps the two clear. Stronger prompt text without native plan mode. Live tests showed that the model kept its own document workflow. **Roadmap alignment** This reuses the issue plan document, planning work mode and approval gate that already exist. It adds one canonical provider event. ## What Changed - `packages/paperclip-runner/protocol/schemas/*`: add the `plan.proposed` event (`paperclip.plan.proposed.v1`: `proposalId`, `status`, `markdown` up to 60k characters, `source`). Regenerate the schema bundle, validators and manifest. - `packages/paperclip-runner/src/provider-events.ts`: map the Codex `plan` item and a `<proposed_plan>` block in a completed Codex message to `plan.proposed`. Add `claudePlanProposalEvent`. - `packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs`: send Codex's native `collaborationMode: plan` on `turn/start` for a read-only (planning) Codex run. Before this change the server path never sent it. - `runner-core/src/acpx_event_payload.rs`: admit `plan.proposed` as ACPX rich display activity. - `drivers/acpx/codex-runtime-adapter.ts`: set Claude Code to native plan mode for a read-only run. Capture Claude's ExitPlanMode plan from its permission request, record it, and decline the native "Ready to code?" prompt. - `drivers/acpx/codex-acpx-driver.ts`, `cli/acpx-runtime-sidecar.ts`: emit the captured proposal as a rich event. Complete that turn instead of reporting it as cancelled. - `backends/codex-native-backend.ts`: per-harness plan-mode instructions, and a short opt-in hint for Claude on standard tasks. - `server/src/services/native-runtime/native-session-executor.ts`: synchronize a completed `plan.proposed` into the `plan` document and a `request_confirmation` in planning and standard mode. Do not change the issue status during the run (that revoked the run's lease). The run finalizer turns the pending confirmation into the review wait. - `server/src/services/heartbeat-run-summary.ts`: keep a `<proposed_plan>` block out of the posted reply. - UI: new `plan_proposal` provider family. The task chat shows a live "Writing plan" card while a proposal streams and a one-line record when it completes. Resolved plan reviews keep a receipt ("Approved Plan v1", "Requested changes to Plan v1: reason"). Plan previews skip section headings. - Storybook: `Tasks and conversations/.../Task chat/Proposed plan approval` shows the journey. - `doc/SPEC-implementation.md`: describe harness-proposed plan approval. ## Verification - `cargo test -p paperclip-runner-core --lib` (new: `read_only_codex_turns_use_native_plan_collaboration_mode`) - `cd packages/paperclip-runner && npx vitest run src/provider-events.test.ts src/drivers/acpx/codex-runtime-adapter.test.ts` - `cd server && npx vitest run src/__tests__/native-plan-proposal-sync.test.ts src/__tests__/heartbeat-run-summary.test.ts` - `cd ui && npx vitest run src/components/transcript/native-run-events.test.ts src/components/task-chat` - Live test on a local worktree instance with real harnesses: - Codex (`gpt-5.6-sol`), planning task: Codex asked one question, then produced a native plan item. The task showed Plan v1 with Approve plan / Request changes and moved to in review. Approve changed the work mode to standard. Codex then implemented the plan and its tests passed. - Codex, Request changes with a reason: Codex woke in planning mode and produced Plan v2 that includes the change. - Claude (`claude-opus-5-5`), planning task: Claude was in plan mode. ExitPlanMode became Plan v1 and the turn completed. Approve resumed Claude in standard mode, and Claude implemented the plan. - Claude, standard task that asks for a plan first: Claude stopped before code changes and asked for plan approval. The work mode stayed standard. - Storybook: open `Proposed plan approval` and check each stage. ## Risks - Codex planning runs now really use Codex plan mode on the server path. Codex can ask a clarifying question before it proposes. This matches Codex plan mode. - Claude planning runs now use Claude plan mode. A declined ExitPlanMode ends the Claude turn; the runner reports it as completed. Claude's tool row for that request records a rejection from Claude's side. - Codex does not stream the proposed plan on the server path yet, so the live card shows a placeholder until the plan completes. - A `<proposed_plan>` block in any completed Codex message on a standard task now asks for approval. This is the documented opt-in path. > 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 - Anthropic Claude Opus 5.5 (`claude-opus-5-5`), via Claude Code with tool use (shell, file edits, browser). Long context. No extended thinking mode was used. ## 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 - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [ ] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
238 lines
13 KiB
JSON
238 lines
13 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://paperclip.dev/schemas/prp/v1/event.schema.json",
|
|
"title": "PRP native event",
|
|
"type": "object",
|
|
"required": [
|
|
"schema",
|
|
"sourceEventId",
|
|
"sourceSeq",
|
|
"sourceInstanceId",
|
|
"sourceKind",
|
|
"runId",
|
|
"eventType",
|
|
"schemaVersion",
|
|
"priority",
|
|
"emittedAt",
|
|
"payload"
|
|
],
|
|
"properties": {
|
|
"schema": { "const": "paperclip.prp.event.v1" },
|
|
"sourceEventId": { "type": "string", "minLength": 1, "maxLength": 160 },
|
|
"sourceSeq": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 },
|
|
"sourceInstanceId": { "type": "string", "minLength": 1, "maxLength": 160 },
|
|
"sourceKind": { "enum": ["runner", "control_plane"] },
|
|
"runId": { "type": "string", "minLength": 1, "maxLength": 160 },
|
|
"normalizedSessionId": { "type": "string", "minLength": 1, "maxLength": 160 },
|
|
"turnId": { "type": "string", "minLength": 1, "maxLength": 240 },
|
|
"itemId": { "type": "string", "minLength": 1, "maxLength": 240 },
|
|
"eventType": {
|
|
"enum": [
|
|
"runner.connected", "runner.reconnected", "runner.reconciled", "runner.disconnected", "runner.draining", "runner.backpressure", "runner.suspending", "runner.suspended", "runner.stopped", "runner.diagnostic",
|
|
"runtime.phase.changed", "sandbox.metric", "workspace.ready", "workspace.change.updated", "workspace.diff.recorded", "workspace.file.referenced",
|
|
"auth.credentials.updated",
|
|
"runtime.activity.recorded",
|
|
"harness.starting", "harness.ready", "harness.exited", "harness.diagnostic",
|
|
"plan.updated", "plan.proposed",
|
|
"tool.execution.started", "tool.execution.progressed", "tool.execution.completed",
|
|
"research.started", "research.progressed", "research.completed",
|
|
"delegation.started", "delegation.updated", "delegation.completed",
|
|
"model.route.changed", "model.verification.updated", "context.compacted",
|
|
"artifact.viewed", "artifact.generated", "review.mode.changed",
|
|
"hook.started", "hook.completed", "memory.citation.referenced",
|
|
"safety.review.started", "safety.review.completed", "terminal.input.sent",
|
|
"wait.started", "wait.completed", "provider.notice.recorded",
|
|
"session.starting", "session.started", "session.resuming", "session.resumed", "session.reconciled", "session.updated", "session.closed", "session.failed",
|
|
"turn.submitted", "turn.accepted", "turn.started", "turn.completed", "turn.failed", "turn.interrupted", "turn.cancelled",
|
|
"item.started", "item.delta", "item.completed", "item.failed", "usage.reported",
|
|
"semantic_tool.input", "semantic_tool.result", "semantic_tool.reconciled",
|
|
"mcp_app.discovered", "mcp_app.resource.resolved", "mcp_app.initializing", "mcp_app.ready", "mcp_app.tool_input", "mcp_app.tool_result", "mcp_app.action.requested", "mcp_app.action.resolved", "mcp_app.host_context.changed", "mcp_app.failed", "mcp_app.teardown",
|
|
"runtime_request.created", "runtime_request.resolved", "runtime_request.expired", "runtime_request.cancelled",
|
|
"interaction.request.proposed", "interaction.request.materialized", "interaction.request.rejected", "interaction.response.progressed", "interaction.response.resolved", "interaction.response.delivered",
|
|
"run.attached", "run.detached", "run.result.proposed", "run.result.accepted", "run.result.rejected",
|
|
"attention.request.proposed", "attention.request.routed", "attention.request.resolved", "attention.request.expired", "attention.request.superseded",
|
|
"work.assessment.recorded", "issue.status.decision.recorded", "issue.status.decision.applied", "issue.status.decision.rejected", "issue.status.decision.superseded",
|
|
"run.terminal"
|
|
]
|
|
},
|
|
"schemaVersion": { "const": 1 },
|
|
"priority": { "enum": [0, 1, 2] },
|
|
"emittedAt": { "type": "string", "format": "date-time" },
|
|
"observedAt": { "type": "string", "format": "date-time" },
|
|
"payload": {
|
|
"type": "object",
|
|
"properties": {
|
|
"channel": { "enum": ["progress", "final", "summary", "detail", "unknown"] },
|
|
"providerPhase": { "type": "string" },
|
|
"providerMethod": { "type": "string" }
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"debug": { "type": "object", "additionalProperties": true }
|
|
},
|
|
"allOf": [
|
|
{ "if": { "properties": { "eventType": { "const": "auth.credentials.updated" } } }, "then": { "properties": { "sourceKind": { "const": "runner" }, "priority": { "const": 0 }, "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/auth-credentials.schema.json" } } } },
|
|
{ "if": { "properties": { "eventType": { "const": "plan.updated" } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/plan" } } } },
|
|
{ "if": { "properties": { "eventType": { "const": "plan.proposed" } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/planProposal" } } } },
|
|
{ "if": { "properties": { "eventType": { "enum": ["tool.execution.started", "tool.execution.progressed", "tool.execution.completed"] } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/toolExecution" } } } },
|
|
{ "if": { "properties": { "eventType": { "enum": ["research.started", "research.progressed", "research.completed"] } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/research" } } } },
|
|
{ "if": { "properties": { "eventType": { "enum": ["delegation.started", "delegation.updated", "delegation.completed"] } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/delegation" } } } },
|
|
{ "if": { "properties": { "eventType": { "const": "model.route.changed" } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/modelRoute" } } } },
|
|
{ "if": { "properties": { "eventType": { "const": "model.verification.updated" } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/modelVerification" } } } },
|
|
{ "if": { "properties": { "eventType": { "const": "context.compacted" } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/contextCompacted" } } } },
|
|
{ "if": { "properties": { "eventType": { "const": "artifact.viewed" } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/artifactViewed" } } } },
|
|
{ "if": { "properties": { "eventType": { "const": "artifact.generated" } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/artifactGenerated" } } } },
|
|
{ "if": { "properties": { "eventType": { "const": "review.mode.changed" } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/reviewMode" } } } },
|
|
{ "if": { "properties": { "eventType": { "enum": ["hook.started", "hook.completed"] } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/hook" } } } },
|
|
{ "if": { "properties": { "eventType": { "const": "memory.citation.referenced" } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/memoryCitation" } } } },
|
|
{ "if": { "properties": { "eventType": { "enum": ["safety.review.started", "safety.review.completed"] } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/safetyReview" } } } },
|
|
{ "if": { "properties": { "eventType": { "const": "terminal.input.sent" } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/terminalInput" } } } },
|
|
{ "if": { "properties": { "eventType": { "enum": ["wait.started", "wait.completed"] } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/wait" } } } },
|
|
{ "if": { "properties": { "eventType": { "const": "provider.notice.recorded" } } }, "then": { "properties": { "payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/provider-event.schema.json#/$defs/providerNotice" } } } },
|
|
{
|
|
"if": { "properties": { "eventType": { "const": "workspace.file.referenced" } } },
|
|
"then": {
|
|
"properties": {
|
|
"payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/workspace-file-reference.schema.json" }
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"if": { "properties": { "eventType": { "enum": ["workspace.change.updated", "workspace.diff.recorded"] } } },
|
|
"then": {
|
|
"properties": {
|
|
"payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json" }
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"if": { "properties": { "eventType": { "const": "workspace.diff.recorded" } } },
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"allOf": [
|
|
{ "$ref": "https://paperclip.dev/schemas/prp/v1/workspace-diff.schema.json" },
|
|
{ "type": "object", "properties": { "complete": { "const": true } } }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"if": { "properties": { "eventType": { "enum": ["semantic_tool.input", "mcp_app.tool_input"] } } },
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"type": "object",
|
|
"properties": {
|
|
"semantic_tool": {
|
|
"allOf": [
|
|
{ "$ref": "https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json" },
|
|
{ "type": "object", "properties": { "phase": { "const": "input" } } }
|
|
]
|
|
}
|
|
},
|
|
"additionalProperties": true
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"if": { "properties": { "eventType": { "enum": ["semantic_tool.result", "mcp_app.tool_result"] } } },
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"type": "object",
|
|
"properties": {
|
|
"semantic_tool": {
|
|
"allOf": [
|
|
{ "$ref": "https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json" },
|
|
{ "type": "object", "properties": { "phase": { "const": "result" } } }
|
|
]
|
|
}
|
|
},
|
|
"additionalProperties": true
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"if": { "properties": { "eventType": { "const": "semantic_tool.reconciled" } } },
|
|
"then": {
|
|
"properties": {
|
|
"payload": {
|
|
"type": "object",
|
|
"properties": {
|
|
"semantic_tool": {
|
|
"allOf": [
|
|
{ "$ref": "https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json" },
|
|
{ "type": "object", "properties": { "phase": { "const": "reconciled" } } }
|
|
]
|
|
}
|
|
},
|
|
"additionalProperties": true
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"if": { "properties": { "eventType": { "const": "run.terminal" } } },
|
|
"then": {
|
|
"properties": {
|
|
"payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/terminal.schema.json" }
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"if": { "properties": { "eventType": { "const": "run.result.proposed" } } },
|
|
"then": {
|
|
"properties": {
|
|
"payload": { "$ref": "https://paperclip.dev/schemas/prp/v1/result.schema.json" }
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"eventType": {
|
|
"const": "runtime.activity.recorded"
|
|
}
|
|
}
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"sourceKind": {
|
|
"const": "runner"
|
|
},
|
|
"priority": {
|
|
"const": 1
|
|
},
|
|
"payload": {
|
|
"type": "object",
|
|
"required": [
|
|
"namespace",
|
|
"activityKey"
|
|
],
|
|
"properties": {
|
|
"namespace": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 160,
|
|
"pattern": "^[^\\u0000-\\u001f\\u007f]+$"
|
|
},
|
|
"activityKey": {
|
|
"type": "string",
|
|
"minLength": 1,
|
|
"maxLength": 160,
|
|
"pattern": "^[^\\u0000-\\u001f\\u007f]+$"
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"additionalProperties": true
|
|
}
|