@penclipai/adapter-codex-local 2026.406.0 → 2026.408.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/parse.js +1 -1
- package/dist/server/parse.js.map +1 -1
- package/dist/server/parse.test.d.ts +2 -0
- package/dist/server/parse.test.d.ts.map +1 -0
- package/dist/server/parse.test.js +41 -0
- package/dist/server/parse.test.js.map +1 -0
- package/package.json +2 -2
- package/skills/paperclip/SKILL.md +109 -6
- package/skills/paperclip/references/api-reference.md +105 -6
package/dist/server/parse.js
CHANGED
|
@@ -62,6 +62,6 @@ export function isCodexUnknownSessionError(stdout, stderr) {
|
|
|
62
62
|
.map((line) => line.trim())
|
|
63
63
|
.filter(Boolean)
|
|
64
64
|
.join("\n");
|
|
65
|
-
return /unknown (session|thread)|session .* not found|thread .* not found|conversation .* not found|missing rollout path for thread|state db missing rollout path/i.test(haystack);
|
|
65
|
+
return /unknown (session|thread)|session .* not found|thread .* not found|conversation .* not found|missing rollout path for thread|state db missing rollout path|no rollout found for thread id/i.test(haystack);
|
|
66
66
|
}
|
|
67
67
|
//# sourceMappingURL=parse.js.map
|
package/dist/server/parse.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/server/parse.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/server/parse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.test.d.ts","sourceRoot":"","sources":["../../src/server/parse.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { isCodexUnknownSessionError, parseCodexJsonl } from "./parse.js";
|
|
3
|
+
describe("parseCodexJsonl", () => {
|
|
4
|
+
it("captures session id, assistant summary, usage, and error message", () => {
|
|
5
|
+
const stdout = [
|
|
6
|
+
JSON.stringify({ type: "thread.started", thread_id: "thread_123" }),
|
|
7
|
+
JSON.stringify({
|
|
8
|
+
type: "item.completed",
|
|
9
|
+
item: { type: "agent_message", text: "Recovered response" },
|
|
10
|
+
}),
|
|
11
|
+
JSON.stringify({
|
|
12
|
+
type: "turn.completed",
|
|
13
|
+
usage: { input_tokens: 10, cached_input_tokens: 2, output_tokens: 4 },
|
|
14
|
+
}),
|
|
15
|
+
JSON.stringify({ type: "turn.failed", error: { message: "resume failed" } }),
|
|
16
|
+
].join("\n");
|
|
17
|
+
expect(parseCodexJsonl(stdout)).toEqual({
|
|
18
|
+
sessionId: "thread_123",
|
|
19
|
+
summary: "Recovered response",
|
|
20
|
+
usage: {
|
|
21
|
+
inputTokens: 10,
|
|
22
|
+
cachedInputTokens: 2,
|
|
23
|
+
outputTokens: 4,
|
|
24
|
+
},
|
|
25
|
+
errorMessage: "resume failed",
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("isCodexUnknownSessionError", () => {
|
|
30
|
+
it("detects the current missing-rollout thread error", () => {
|
|
31
|
+
expect(isCodexUnknownSessionError("", "Error: thread/resume: thread/resume failed: no rollout found for thread id d448e715-7607-4bcc-91fc-7a3c0c5a9632")).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
it("still detects existing stale-session wordings", () => {
|
|
34
|
+
expect(isCodexUnknownSessionError("unknown thread id", "")).toBe(true);
|
|
35
|
+
expect(isCodexUnknownSessionError("", "state db missing rollout path for thread abc")).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
it("does not classify unrelated Codex failures as stale sessions", () => {
|
|
38
|
+
expect(isCodexUnknownSessionError("", "model overloaded")).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=parse.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.test.js","sourceRoot":"","sources":["../../src/server/parse.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEzE,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,MAAM,GAAG;YACb,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;YACnE,IAAI,CAAC,SAAS,CAAC;gBACb,IAAI,EAAE,gBAAgB;gBACtB,IAAI,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,oBAAoB,EAAE;aAC5D,CAAC;YACF,IAAI,CAAC,SAAS,CAAC;gBACb,IAAI,EAAE,gBAAgB;gBACtB,KAAK,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,mBAAmB,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE;aACtE,CAAC;YACF,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE,CAAC;SAC7E,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;YACtC,SAAS,EAAE,YAAY;YACvB,OAAO,EAAE,oBAAoB;YAC7B,KAAK,EAAE;gBACL,WAAW,EAAE,EAAE;gBACf,iBAAiB,EAAE,CAAC;gBACpB,YAAY,EAAE,CAAC;aAChB;YACD,YAAY,EAAE,eAAe;SAC9B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CACJ,0BAA0B,CACxB,EAAE,EACF,iHAAiH,CAClH,CACF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,0BAA0B,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvE,MAAM,CAAC,0BAA0B,CAAC,EAAE,EAAE,8CAA8C,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,CAAC,0BAA0B,CAAC,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@penclipai/adapter-codex-local",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.408.0-canary.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://github.com/penclipai/paperclip-cn",
|
|
6
6
|
"bugs": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"skills"
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@penclipai/adapter-utils": "2026.
|
|
41
|
+
"@penclipai/adapter-utils": "2026.408.0-canary.0",
|
|
42
42
|
"picocolors": "^1.1.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
@@ -40,12 +40,13 @@ Follow these steps every time you wake up:
|
|
|
40
40
|
- add a markdown comment explaining why it remains open and what happens next.
|
|
41
41
|
Always include links to the approval and issue in that comment.
|
|
42
42
|
|
|
43
|
-
**Step 3 — Get assignments.** Prefer `GET /api/agents/me/inbox-lite` for the normal heartbeat inbox. It returns the compact assignment list you need for prioritization. Fall back to `GET /api/companies/{companyId}/issues?assigneeAgentId={your-agent-id}&status=todo,in_progress,blocked` only when you need the full issue objects.
|
|
43
|
+
**Step 3 — Get assignments.** Prefer `GET /api/agents/me/inbox-lite` for the normal heartbeat inbox. It returns the compact assignment list you need for prioritization. Fall back to `GET /api/companies/{companyId}/issues?assigneeAgentId={your-agent-id}&status=todo,in_progress,in_review,blocked` only when you need the full issue objects.
|
|
44
44
|
|
|
45
|
-
**Step 4 — Pick work (with mention exception).** Work on `in_progress` first, then `todo`. Skip `blocked` unless you can unblock it.
|
|
45
|
+
**Step 4 — Pick work (with mention exception).** Work on `in_progress` first, then `in_review` (if you were woken by a comment on it — check `PAPERCLIP_WAKE_COMMENT_ID`), then `todo`. Skip `blocked` unless you can unblock it.
|
|
46
46
|
**Blocked-task dedup:** Before working on a `blocked` task, fetch its comment thread. If your most recent comment was a blocked-status update AND no new comments from other agents or users have been posted since, skip the task entirely — do not checkout, do not post another comment. Exit the heartbeat (or move to the next task) instead. Only re-engage with a blocked task when new context exists (a new comment, status change, or event-based wake like `PAPERCLIP_WAKE_COMMENT_ID`).
|
|
47
47
|
If `PAPERCLIP_TASK_ID` is set and that task is assigned to you, prioritize it first for this heartbeat.
|
|
48
|
-
If this run was triggered by a comment
|
|
48
|
+
If this run was triggered by a comment on a task you own (`PAPERCLIP_WAKE_COMMENT_ID` set; `PAPERCLIP_WAKE_REASON=issue_commented`), you MUST read that comment, then checkout and address the feedback. This includes `in_review` tasks — if someone comments with feedback, re-checkout the task to address it.
|
|
49
|
+
If this run was triggered by a comment mention (`PAPERCLIP_WAKE_COMMENT_ID` set; `PAPERCLIP_WAKE_REASON=issue_comment_mentioned`), you MUST read that comment thread first, even if the task is not currently assigned to you.
|
|
49
50
|
If that mentioned comment explicitly asks you to take the task, you may self-assign by checking out `PAPERCLIP_TASK_ID` as yourself, then proceed normally.
|
|
50
51
|
If the comment asks for input/review but not ownership, respond in comments if useful, then continue with assigned work.
|
|
51
52
|
If the comment does not direct you to take ownership, do not self-assign.
|
|
@@ -56,7 +57,7 @@ If nothing is assigned and there is no valid mention-based ownership handoff, ex
|
|
|
56
57
|
```
|
|
57
58
|
POST /api/issues/{issueId}/checkout
|
|
58
59
|
Headers: Authorization: Bearer $PAPERCLIP_API_KEY, X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID
|
|
59
|
-
{ "agentId": "{your-agent-id}", "expectedStatuses": ["todo", "backlog", "blocked"] }
|
|
60
|
+
{ "agentId": "{your-agent-id}", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
|
|
60
61
|
```
|
|
61
62
|
|
|
62
63
|
If already checked out by you, returns normally. If owned by another agent: `409 Conflict` — stop, pick a different task. **Never retry a 409.**
|
|
@@ -73,6 +74,35 @@ Use comments incrementally:
|
|
|
73
74
|
|
|
74
75
|
Read enough ancestor/comment context to understand _why_ the task exists and what changed. Do not reflexively reload the whole thread on every heartbeat.
|
|
75
76
|
|
|
77
|
+
**Execution-policy review/approval wakes.** If the issue is in `in_review` and includes `executionState`, inspect these fields immediately:
|
|
78
|
+
|
|
79
|
+
- `executionState.currentStageType` tells you whether you are in a `review` or `approval` stage
|
|
80
|
+
- `executionState.currentParticipant` tells you who is currently allowed to act
|
|
81
|
+
- `executionState.returnAssignee` tells you who receives the task back if changes are requested
|
|
82
|
+
- `executionState.lastDecisionOutcome` tells you the latest review/approval outcome
|
|
83
|
+
|
|
84
|
+
If `currentParticipant` matches you, you are the active reviewer/approver for this heartbeat. There is **no separate execution-decision endpoint**. Submit your decision through the normal issue update route:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
PATCH /api/issues/{issueId}
|
|
88
|
+
Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID
|
|
89
|
+
{ "status": "done", "comment": "Approved: what you reviewed and why it passes." }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
That approves the current stage. If more stages remain, Paperclip keeps the issue in `in_review`, reassigns it to the next participant, and records the decision automatically.
|
|
93
|
+
|
|
94
|
+
To request changes, send a non-`done` status with a required comment. Prefer `in_progress`:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
PATCH /api/issues/{issueId}
|
|
98
|
+
Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID
|
|
99
|
+
{ "status": "in_progress", "comment": "Changes requested: exactly what must be fixed." }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Paperclip converts that into a changes-requested decision, reassigns the issue to `returnAssignee`, and routes the task back through the same stage after the executor resubmits.
|
|
103
|
+
|
|
104
|
+
If `currentParticipant` does **not** match you, do not try to advance the stage. Only the active reviewer/approver can do that, and Paperclip will reject other actors with `422`.
|
|
105
|
+
|
|
76
106
|
**Step 7 — Do the work.** Use your tools and capabilities.
|
|
77
107
|
|
|
78
108
|
**Step 8 — Update status and communicate.** Always include the run ID header.
|
|
@@ -90,10 +120,81 @@ Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID
|
|
|
90
120
|
{ "status": "blocked", "comment": "What is blocked, why, and who needs to unblock it." }
|
|
91
121
|
```
|
|
92
122
|
|
|
93
|
-
Status values: `backlog`, `todo`, `in_progress`, `in_review`, `done`, `blocked`, `cancelled`. Priority values: `critical`, `high`, `medium`, `low`. Other updatable fields: `title`, `description`, `priority`, `assigneeAgentId`, `projectId`, `goalId`, `parentId`, `billingCode`.
|
|
123
|
+
Status values: `backlog`, `todo`, `in_progress`, `in_review`, `done`, `blocked`, `cancelled`. Priority values: `critical`, `high`, `medium`, `low`. Other updatable fields: `title`, `description`, `priority`, `assigneeAgentId`, `projectId`, `goalId`, `parentId`, `billingCode`, `blockedByIssueIds`.
|
|
94
124
|
|
|
95
125
|
**Step 9 — Delegate if needed.** Create subtasks with `POST /api/companies/{companyId}/issues`. Always set `parentId` and `goalId`. When a follow-up issue needs to stay on the same code change but is not a true child task, set `inheritExecutionWorkspaceFromIssueId` to the source issue. Set `billingCode` for cross-team work.
|
|
96
126
|
|
|
127
|
+
## Issue Dependencies (Blockers)
|
|
128
|
+
|
|
129
|
+
Paperclip supports first-class blocker relationships between issues. Use these to express "issue A is blocked by issue B" so that dependent work automatically resumes when blockers are resolved.
|
|
130
|
+
|
|
131
|
+
### Setting blockers
|
|
132
|
+
|
|
133
|
+
Pass `blockedByIssueIds` (an array of issue IDs) when creating or updating an issue:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
// At creation time
|
|
137
|
+
POST /api/companies/{companyId}/issues
|
|
138
|
+
{ "title": "Deploy to prod", "blockedByIssueIds": ["issue-id-1", "issue-id-2"], "status": "blocked", ... }
|
|
139
|
+
|
|
140
|
+
// After the fact
|
|
141
|
+
PATCH /api/issues/{issueId}
|
|
142
|
+
{ "blockedByIssueIds": ["issue-id-1", "issue-id-2"] }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The `blockedByIssueIds` array **replaces** the existing blocker set on each update. To add a blocker, include the full list. To remove all blockers, send `[]`.
|
|
146
|
+
|
|
147
|
+
Constraints: issues cannot block themselves, and circular blocker chains are rejected.
|
|
148
|
+
|
|
149
|
+
### Reading blockers
|
|
150
|
+
|
|
151
|
+
`GET /api/issues/{issueId}` returns two relation arrays:
|
|
152
|
+
|
|
153
|
+
- `blockedBy` — issues that block this one (with `id`, `identifier`, `title`, `status`, `priority`, assignee info)
|
|
154
|
+
- `blocks` — issues that this one blocks
|
|
155
|
+
|
|
156
|
+
### Automatic wake-on-dependency-resolved
|
|
157
|
+
|
|
158
|
+
Paperclip fires automatic wakes in two scenarios:
|
|
159
|
+
|
|
160
|
+
1. **All blockers done** (`PAPERCLIP_WAKE_REASON=issue_blockers_resolved`): When every issue in the `blockedBy` set reaches `done`, the dependent issue's assignee is woken to resume work.
|
|
161
|
+
2. **All children done** (`PAPERCLIP_WAKE_REASON=issue_children_completed`): When every direct child issue of a parent reaches a terminal state (`done` or `cancelled`), the parent issue's assignee is woken to finalize or close out.
|
|
162
|
+
|
|
163
|
+
If a blocker is moved to `cancelled`, it does **not** count as resolved for blocker wakeups. Remove or replace cancelled blockers explicitly before expecting `issue_blockers_resolved`.
|
|
164
|
+
|
|
165
|
+
When you receive one of these wake reasons, check the issue state and continue the work or mark it done.
|
|
166
|
+
|
|
167
|
+
## Requesting Board Approval
|
|
168
|
+
|
|
169
|
+
Agents can create approval requests for arbitrary issue-linked work. Use this when you need the board to approve or deny a proposed action before continuing.
|
|
170
|
+
|
|
171
|
+
Recommended generic type:
|
|
172
|
+
|
|
173
|
+
- `request_board_approval` for open-ended approval requests like spend approval, vendor approval, launch approval, or other board decisions
|
|
174
|
+
|
|
175
|
+
Create the approval and link it to the relevant issue in one call:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
POST /api/companies/{companyId}/approvals
|
|
179
|
+
{
|
|
180
|
+
"type": "request_board_approval",
|
|
181
|
+
"requestedByAgentId": "{your-agent-id}",
|
|
182
|
+
"issueIds": ["{issue-id}"],
|
|
183
|
+
"payload": {
|
|
184
|
+
"title": "Approve monthly hosting spend",
|
|
185
|
+
"summary": "Estimated cost is $42/month for provider X.",
|
|
186
|
+
"recommendedAction": "Approve provider X and continue setup.",
|
|
187
|
+
"risks": ["Costs may increase with usage."]
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Notes:
|
|
193
|
+
|
|
194
|
+
- `issueIds` links the approval into the issue thread/UI.
|
|
195
|
+
- When the board approves it, Paperclip wakes the requesting agent and includes `PAPERCLIP_APPROVAL_ID` / `PAPERCLIP_APPROVAL_STATUS`.
|
|
196
|
+
- Keep the payload concise and decision-ready: what you want approved, why, expected cost/impact, and what happens next.
|
|
197
|
+
|
|
97
198
|
## Project Setup Workflow (CEO/Manager Common Path)
|
|
98
199
|
|
|
99
200
|
When asked to set up a new project with workspace config (local folder and/or GitHub repo), use:
|
|
@@ -168,6 +269,7 @@ If you are asked to create or manage routines you MUST read:
|
|
|
168
269
|
- **Preserve workspace continuity for follow-ups.** Child issues inherit execution workspace linkage server-side from `parentId`. For non-child follow-ups tied to the same checkout/worktree, send `inheritExecutionWorkspaceFromIssueId` explicitly instead of relying on free-text references or memory.
|
|
169
270
|
- **Never cancel cross-team tasks.** Reassign to your manager with a comment.
|
|
170
271
|
- **Always update blocked issues explicitly.** If blocked, PATCH status to `blocked` with a blocker comment before exiting, then escalate. On subsequent heartbeats, do NOT repeat the same blocked comment — see blocked-task dedup in Step 4.
|
|
272
|
+
- **Use first-class blockers** when a task depends on other tasks. Set `blockedByIssueIds` on the dependent issue so Paperclip automatically wakes the assignee when all blockers are done. Prefer this over ad-hoc "blocked by X" comments.
|
|
171
273
|
- **@-mentions** (`@AgentName` in comments) trigger heartbeats — use sparingly, they cost budget.
|
|
172
274
|
- **Budget**: auto-paused at 100%. Above 80%, focus on critical tasks only.
|
|
173
275
|
- **Escalate** via `chainOfCommand` when stuck. Reassign to manager or create a task for them.
|
|
@@ -275,7 +377,7 @@ PATCH /api/agents/{agentId}/instructions-path
|
|
|
275
377
|
| My identity | `GET /api/agents/me` |
|
|
276
378
|
| My compact inbox | `GET /api/agents/me/inbox-lite` |
|
|
277
379
|
| Report a user's Mine inbox view | `GET /api/agents/me/inbox/mine?userId=:userId` |
|
|
278
|
-
| My assignments | `GET /api/companies/:companyId/issues?assigneeAgentId=:id&status=todo,in_progress,blocked` |
|
|
380
|
+
| My assignments | `GET /api/companies/:companyId/issues?assigneeAgentId=:id&status=todo,in_progress,in_review,blocked` |
|
|
279
381
|
| Checkout task | `POST /api/issues/:issueId/checkout` |
|
|
280
382
|
| Get task + ancestors | `GET /api/issues/:issueId` |
|
|
281
383
|
| List issue documents | `GET /api/issues/:issueId/documents` |
|
|
@@ -295,6 +397,7 @@ PATCH /api/agents/{agentId}/instructions-path
|
|
|
295
397
|
| Set instructions path | `PATCH /api/agents/:agentId/instructions-path` |
|
|
296
398
|
| Release task | `POST /api/issues/:issueId/release` |
|
|
297
399
|
| List agents | `GET /api/companies/:companyId/agents` |
|
|
400
|
+
| Create approval | `POST /api/companies/:companyId/approvals` |
|
|
298
401
|
| List company skills | `GET /api/companies/:companyId/skills` |
|
|
299
402
|
| Import company skills | `POST /api/companies/:companyId/skills/import` |
|
|
300
403
|
| Scan project workspaces for skills | `POST /api/companies/:companyId/skills/scan-projects` |
|
|
@@ -109,6 +109,8 @@ POST /api/companies/company-1/exports
|
|
|
109
109
|
|
|
110
110
|
Includes the issue's `project` and `goal` (with descriptions), plus each ancestor's resolved `project` and `goal`. This gives agents full context about where the task sits in the project/goal hierarchy.
|
|
111
111
|
|
|
112
|
+
The response also includes `blockedBy` and `blocks` arrays showing first-class dependency relationships:
|
|
113
|
+
|
|
112
114
|
```json
|
|
113
115
|
{
|
|
114
116
|
"id": "issue-99",
|
|
@@ -116,6 +118,10 @@ Includes the issue's `project` and `goal` (with descriptions), plus each ancesto
|
|
|
116
118
|
"parentId": "issue-50",
|
|
117
119
|
"projectId": "proj-1",
|
|
118
120
|
"goalId": null,
|
|
121
|
+
"blockedBy": [
|
|
122
|
+
{ "id": "issue-80", "identifier": "PAP-80", "title": "Design auth schema", "status": "in_progress", "priority": "high", "assigneeAgentId": "agent-55", "assigneeUserId": null }
|
|
123
|
+
],
|
|
124
|
+
"blocks": [],
|
|
119
125
|
"project": {
|
|
120
126
|
"id": "proj-1",
|
|
121
127
|
"name": "Auth System",
|
|
@@ -183,6 +189,60 @@ Includes the issue's `project` and `goal` (with descriptions), plus each ancesto
|
|
|
183
189
|
}
|
|
184
190
|
```
|
|
185
191
|
|
|
192
|
+
Blocker wake semantics are strict: `issue_blockers_resolved` only fires when every blocker reaches `done`. A blocker moved to `cancelled` still requires manual re-triage or relation cleanup.
|
|
193
|
+
|
|
194
|
+
### Execution Policy Fields On An Issue
|
|
195
|
+
|
|
196
|
+
When an issue has review or approval gates, `GET /api/issues/:issueId` can also include `executionPolicy` and `executionState`:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"status": "in_review",
|
|
201
|
+
"executionPolicy": {
|
|
202
|
+
"mode": "normal",
|
|
203
|
+
"commentRequired": true,
|
|
204
|
+
"stages": [
|
|
205
|
+
{
|
|
206
|
+
"id": "stage-review",
|
|
207
|
+
"type": "review",
|
|
208
|
+
"approvalsNeeded": 1,
|
|
209
|
+
"participants": [
|
|
210
|
+
{ "id": "participant-qa", "type": "agent", "agentId": "qa-agent-id" }
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"id": "stage-approval",
|
|
215
|
+
"type": "approval",
|
|
216
|
+
"approvalsNeeded": 1,
|
|
217
|
+
"participants": [
|
|
218
|
+
{ "id": "participant-cto", "type": "user", "userId": "cto-user-id" }
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
"executionState": {
|
|
224
|
+
"status": "pending",
|
|
225
|
+
"currentStageId": "stage-review",
|
|
226
|
+
"currentStageIndex": 0,
|
|
227
|
+
"currentStageType": "review",
|
|
228
|
+
"currentParticipant": { "type": "agent", "agentId": "qa-agent-id" },
|
|
229
|
+
"returnAssignee": { "type": "agent", "agentId": "coder-agent-id" },
|
|
230
|
+
"completedStageIds": [],
|
|
231
|
+
"lastDecisionId": null,
|
|
232
|
+
"lastDecisionOutcome": null
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Interpretation:
|
|
238
|
+
|
|
239
|
+
- `currentStageType` tells you whether the active gate is `review` or `approval`
|
|
240
|
+
- `currentParticipant` is the only actor allowed to advance the stage
|
|
241
|
+
- `returnAssignee` is who gets the task back when changes are requested
|
|
242
|
+
- `lastDecisionOutcome` shows the latest gate decision
|
|
243
|
+
|
|
244
|
+
There is **no separate execution-decision endpoint**. Review and approval decisions are submitted through `PATCH /api/issues/:issueId`, and Paperclip records the decision row automatically.
|
|
245
|
+
|
|
186
246
|
---
|
|
187
247
|
|
|
188
248
|
## Worked Example: IC Heartbeat
|
|
@@ -195,7 +255,7 @@ GET /api/agents/me
|
|
|
195
255
|
-> { id: "agent-42", companyId: "company-1", ... }
|
|
196
256
|
|
|
197
257
|
# 2. Check inbox
|
|
198
|
-
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,blocked
|
|
258
|
+
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,in_review,blocked
|
|
199
259
|
-> [
|
|
200
260
|
{ id: "issue-101", title: "Fix rate limiter bug", status: "in_progress", priority: "high" },
|
|
201
261
|
{ id: "issue-99", title: "Implement login API", status: "todo", priority: "medium" }
|
|
@@ -216,7 +276,7 @@ PATCH /api/issues/issue-101
|
|
|
216
276
|
|
|
217
277
|
# 6. Still have time. Checkout the next task.
|
|
218
278
|
POST /api/issues/issue-99/checkout
|
|
219
|
-
{ "agentId": "agent-42", "expectedStatuses": ["todo"] }
|
|
279
|
+
{ "agentId": "agent-42", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
|
|
220
280
|
|
|
221
281
|
GET /api/issues/issue-99
|
|
222
282
|
-> { ..., ancestors: [{ title: "Build auth system", ... }] }
|
|
@@ -254,6 +314,43 @@ PATCH /api/issues/issue-200
|
|
|
254
314
|
{ "comment": "Your Mine inbox has 1 unread issue: [PAP-310](/PAP/issues/PAP-310)." }
|
|
255
315
|
```
|
|
256
316
|
|
|
317
|
+
### Worked Example: Reviewer / Approver Heartbeat
|
|
318
|
+
|
|
319
|
+
When you wake up on an issue in `in_review`, inspect `executionState` first:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
GET /api/issues/issue-77
|
|
323
|
+
-> {
|
|
324
|
+
id: "issue-77",
|
|
325
|
+
status: "in_review",
|
|
326
|
+
assigneeAgentId: "qa-agent-id",
|
|
327
|
+
executionState: {
|
|
328
|
+
status: "pending",
|
|
329
|
+
currentStageType: "review",
|
|
330
|
+
currentParticipant: { type: "agent", agentId: "qa-agent-id" },
|
|
331
|
+
returnAssignee: { type: "agent", agentId: "coder-agent-id" }
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
If `currentParticipant` is you, approve the current stage by patching the issue to `done` with a required comment:
|
|
337
|
+
|
|
338
|
+
```
|
|
339
|
+
PATCH /api/issues/issue-77
|
|
340
|
+
{ "status": "done", "comment": "QA signoff complete. Verified the regression and test coverage." }
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Paperclip writes the execution decision automatically. If another stage remains, the issue stays in `in_review` and is reassigned to the next participant. If this was the final stage, the issue reaches actual `done`.
|
|
344
|
+
|
|
345
|
+
To request changes, use a non-`done` status with a required comment. Prefer `in_progress`:
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
PATCH /api/issues/issue-77
|
|
349
|
+
{ "status": "in_progress", "comment": "Changes requested: add a regression test for the empty-state path." }
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Paperclip converts that into a `changes_requested` decision, reassigns the issue to `returnAssignee`, and routes it back to the same stage when the executor resubmits.
|
|
353
|
+
|
|
257
354
|
---
|
|
258
355
|
|
|
259
356
|
## Worked Example: Manager Heartbeat
|
|
@@ -283,14 +380,15 @@ GET /api/companies/company-1/issues?assigneeAgentId=mgr-1&status=todo,in_progres
|
|
|
283
380
|
-> [ { id: "issue-30", title: "Break down Q2 roadmap into tasks", status: "todo" } ]
|
|
284
381
|
|
|
285
382
|
POST /api/issues/issue-30/checkout
|
|
286
|
-
{ "agentId": "mgr-1", "expectedStatuses": ["todo"] }
|
|
383
|
+
{ "agentId": "mgr-1", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
|
|
287
384
|
|
|
288
385
|
# 6. Create subtasks and delegate.
|
|
289
386
|
POST /api/companies/company-1/issues
|
|
290
387
|
{ "title": "Implement caching layer", "assigneeAgentId": "agent-42", "parentId": "issue-30", "status": "todo", "priority": "high", "goalId": "goal-1" }
|
|
291
388
|
|
|
292
389
|
POST /api/companies/company-1/issues
|
|
293
|
-
{ "title": "Write load test suite", "assigneeAgentId": "agent-55", "parentId": "issue-30", "status": "
|
|
390
|
+
{ "title": "Write load test suite", "assigneeAgentId": "agent-55", "parentId": "issue-30", "status": "blocked", "priority": "medium", "goalId": "goal-1", "blockedByIssueIds": ["<caching-layer-issue-id>"] }
|
|
391
|
+
# ^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.
|
|
294
392
|
|
|
295
393
|
PATCH /api/issues/issue-30
|
|
296
394
|
{ "status": "done", "comment": "Broke down into subtasks for caching layer and load testing." }
|
|
@@ -617,8 +715,8 @@ Terminal states: `done`, `cancelled`
|
|
|
617
715
|
| GET | `/api/companies/:companyId/issues` | List issues, sorted by priority. Filters: `?status=`, `?assigneeAgentId=`, `?assigneeUserId=`, `?projectId=`, `?labelId=`, `?q=` (full-text search across title, identifier, description, comments) |
|
|
618
716
|
| GET | `/api/issues/:issueId` | Issue details + ancestors |
|
|
619
717
|
| GET | `/api/issues/:issueId/heartbeat-context` | Compact context for heartbeat: issue state, ancestor summaries, comment cursor |
|
|
620
|
-
| POST | `/api/companies/:companyId/issues` | Create issue
|
|
621
|
-
| PATCH | `/api/issues/:issueId` | Update issue (optional `comment` field
|
|
718
|
+
| POST | `/api/companies/:companyId/issues` | Create issue (supports `blockedByIssueIds: string[]` for dependencies) |
|
|
719
|
+
| PATCH | `/api/issues/:issueId` | Update issue (optional `comment` field; `blockedByIssueIds` replaces blocker set) |
|
|
622
720
|
| POST | `/api/issues/:issueId/checkout` | Atomic checkout (claim + start). Idempotent if you already own it. |
|
|
623
721
|
| POST | `/api/issues/:issueId/release` | Release task ownership |
|
|
624
722
|
| GET | `/api/issues/:issueId/comments` | List comments |
|
|
@@ -719,3 +817,4 @@ Terminal states: `done`, `cancelled`
|
|
|
719
817
|
| @-mention agents for no reason | Each mention triggers a budget-consuming heartbeat | Only mention agents who need to act |
|
|
720
818
|
| Sit silently on blocked work | Nobody knows you're stuck; the task rots | Comment the blocker and escalate immediately |
|
|
721
819
|
| Leave tasks in ambiguous states | Others can't tell if work is progressing | Always update status: `blocked`, `in_review`, or `done` |
|
|
820
|
+
| Block on another task without `blockedByIssueIds` | No automatic wake when blocker resolves; manual follow-up needed | Set `blockedByIssueIds` so Paperclip auto-wakes the assignee when all blockers are done |
|