mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 20:21:50 +01:00
fix(inbox): keep automatic recovery cleanup out of personal participation (#16063)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The personal inbox uses issue activity to identify a person's participation. > - Issue reads can cancel stale recovery actions and record that cleanup as the reader's action. > - This adds unrelated tasks to the inbox and refreshes their activity timestamps. > - A workspace owner hold can also alternate between creation and cancellation after a task finishes. > - This pull request corrects cleanup attribution, excludes historical read cleanup, and retains one exact owner hold. > - Agents can read authorized issue activity to diagnose the same problem themselves. ## Linked Issues or Issue Description Refs #15742 excludes logs by the execution-recovery actor from activity timestamps. This PR covers reader-attributed stale recovery cleanup, personal inbox participation, exact workspace owner holds, and sandbox issue activity access. I searched open issues and PRs for recovery inbox and activity bridge fixes. I found no duplicate for this defect. **What happened?** Loading issue lists cancels stale recovery actions under the reader's identity. The personal inbox treats those rows as user participation. One completed issue had hundreds of such rows and no edits or comments by the affected reader. A retained native workspace owner repeatedly creates a verification action that read projections then cancel. Sandbox agents cannot inspect the issue activity because the reverse bridge rejects the route. **Expected behavior** Automatic cleanup belongs to the system and does not add an issue to a reader's inbox. Historical cleanup rows remain auditable but do not count as participation or recent activity. An unresolved physical workspace owner keeps one stable verification action. Agents can inspect activity only for issues their identity may read. **Steps to reproduce** 1. Finish a task whose native run retains an unverifiable workspace owner receipt. 2. Repeat workspace recovery checks and GET requests for issue detail, compact lists, and recovery actions. 3. Observe new recovery action IDs and reader-attributed cancellations in the activity history. 4. Query the personal inbox for that reader and observe the unrelated task. 5. From a sandbox agent, request GET /api/issues/:id/activity and observe Route not allowed. **Paperclip version or commit** Observed atf5346c05b9. Regression tests use current master behavior. **Deployment mode** Authenticated hosted instance with native and legacy sandbox agents. The inbox defect is independent of the adapter. ## What Changed - Attribute automatic stale recovery cleanup to the recovery system actor. - Exclude exact historical read cleanup rows from personal inbox membership, unread counts, and both activity timestamp queries. Keep all audit rows and explicit user recovery decisions. - Retain owner verification holds only while the exact company, run, issue, and owner fingerprint still match. Apply the same rule to reads and background recovery. - Make repeated checks for the same owner hold leave its identity, attempts, and timestamps unchanged. Keep physical takeover blocked. - Allow GET issue activity through the sandbox bridge. Keep the existing company and issue-read authorization and reject broader audit routes. - Add regressions and update the runtime API and recovery documentation. Refresh generated source references for the documentation edits. Update the paired activity, inbox, and recovery maps. ## Verification - Focused Vitest verification: 315 tests pass across five files. The updated terminal owner hold regression also passes. The files cover issue service, recovery routes, activity authorization, sandbox callback bridge, and native workspace finalization. - `pnpm -r typecheck` and `pnpm build` pass locally. The full general and serialized unit test matrix passes in CI on the latest commit. The duplicate local `pnpm test:run` was stopped after about 30 minutes once that full CI coverage passed; it did not report a local full-suite pass. - `node scripts/check-maps.mjs` passes for all paired areas and capability IDs. - Apex is 5/5 on commit44c3787888. Its access map finding is fixed and resolved. All CI checks are green on this commit. - The new regression repeats reads and background sweeps for done and cancelled tasks. It checks one unchanged hold, zero physical copyback, zero personal inbox participation, and system cleanup after the exact receipt is cleared. - Historical cleanup tests preserve explicit recovery decisions, nullable metadata, unread counts, and the original audit rows. - Authorization tests permit an authorized agent and reject cross-company or issue-boundary reads. Bridge tests reject write methods, extra path segments, and company-wide audit routes. ## Risks Existing inbox items can disappear if their only participation evidence was automatic read cleanup. No data migration or audit deletion is needed. An unresolved workspace owner remains visible after task completion; this preserves the physical writer fence. A stale or cleared owner does not retain the hold. Issue activity access expands only the sandbox transport allowlist and still uses existing controller authorization. ## Model Used OpenAI GPT-6 through Codex, with reasoning, repository inspection, code editing, shell execution, and test tools. The session identifies the model family as GPT-6. A more specific runtime model ID and context window size are not exposed. ## 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> Co-authored-by: Amy (Opus) <noreply@anthropic.com>
This commit is contained in:
24 files changed
+913
-666
No files matched your search
@@ -9,6 +9,15 @@ A completed workspace barrier is reread
|
||||
under ownership before export, and a committed coordinator cannot be overwritten
|
||||
by a late failure receipt.
|
||||
|
||||
An unverified workspace owner keeps one board recovery action for its exact run
|
||||
and owner fingerprint. Repeated recovery checks do not consume attempts or
|
||||
rewrite that action. Task completion or cancellation does not prove the old
|
||||
physical writer stopped, so reads and recovery sweeps retain this action while
|
||||
the matching run still carries that owner receipt. After the receipt is cleared
|
||||
or replaced, ordinary stale-action cleanup applies. Cleanup is attributed to
|
||||
the system; historical cleanup from page reads does not add tasks to a person's
|
||||
inbox or refresh issue activity timestamps.
|
||||
|
||||
The lock transaction holds no row locks. Ordinary progress and finalization
|
||||
receipts remain visible through the normal database pool. A dedicated connection outside the application pool is
|
||||
reserved for the duration of copyback and closed when it settles; even a one-connection application pool remains available for progress writes. Its run profile carries
|
||||
|
||||
@@ -40,22 +40,32 @@ Review: `baseline`.
|
||||
API: `present` — representative operation IDs:
|
||||
|
||||
- `GET /api/companies/{companyId}/activity`.
|
||||
- `GET /api/issues/{id}/activity` (UUID or issue identifier).
|
||||
|
||||
Agent access: `available` — REST / native call_api / CLI activity.
|
||||
Agent access: `partial` — REST / native call_api / CLI activity for company
|
||||
activity. Sandbox agents can use GET issue activity through the default
|
||||
[callback bridge](../../packages/adapter-utils/src/sandbox-callback-bridge.ts).
|
||||
The bridge still rejects company activity and company audit routes.
|
||||
|
||||
Policy: Same company plus visibility/audit/cost/run permissions where the route requires
|
||||
them.
|
||||
Policy: Company activity requires same-company access and `company_scope:read`,
|
||||
with issue visibility filtering. Issue activity requires same-company resource
|
||||
access and `issue:read` for the resolved issue. The sandbox transport does not
|
||||
replace those controller checks or permit activity writes.
|
||||
|
||||
Evidence: `source-only` — [implementation](../../server/src/routes/activity.ts#L224) at
|
||||
the reviewed commit above.
|
||||
Evidence: `source-only` — [activity routes](../../server/src/routes/activity.ts),
|
||||
[agent authorization tests](../../server/src/__tests__/activity-routes.test.ts),
|
||||
and [bridge tests](../../packages/adapter-utils/src/sandbox-callback-bridge.test.ts).
|
||||
Issue activity transport reviewed at `89770ba707fb1ebd4cabd184eec2e7fee226e0c3`;
|
||||
other operations retain the baseline source review above.
|
||||
|
||||
Verification: `not-run` — exercise a listed permitted operation with actual agent
|
||||
credentials/run context; inspect its result and applicable persistence/attribution.
|
||||
Assert the relevant denied actor, owner, company or mode from Policy. Review the
|
||||
remaining operations before qualifying the whole capability.
|
||||
Verification: `not-run` — no live agent-credential proof. Local route and bridge
|
||||
regressions pass: permitted agent issue read, denied company/resource scope,
|
||||
denied writes, extra path segments, and company audit routes. With a real
|
||||
sandbox agent, read an authorized issue's activity, then verify denial for an
|
||||
inaccessible issue, another company, and company-wide audit.
|
||||
|
||||
Gaps: Audit export and raw activity creation have extra restrictions; do not equate them
|
||||
with activity reading.
|
||||
Gaps: Audit export and raw activity creation have extra restrictions; do not
|
||||
equate them with issue activity reading. Live sandbox access remains unverified.
|
||||
|
||||
## `runs`
|
||||
|
||||
|
||||
@@ -22,15 +22,24 @@ API: `present` — representative operation IDs:
|
||||
Agent access: `partial` — REST / native call_api; MCP inbox/task search.
|
||||
|
||||
Policy: Own agent context; responsible-user inbox policy and explicit opt-in/grants for
|
||||
cross-user archive; decision policy separately.
|
||||
cross-user archive; decision policy separately. Automatic read cleanup does not
|
||||
count as user participation, including historical reader-attributed rows.
|
||||
Explicit user recovery decisions still count.
|
||||
|
||||
Evidence: `source-only` — [implementation](../../server/src/routes/agents.ts#L3952) at
|
||||
the reviewed commit above.
|
||||
the reviewed commit above. The cleanup exclusion in
|
||||
[issue queries](../../server/src/services/issues.ts) was reviewed at
|
||||
`89770ba707fb1ebd4cabd184eec2e7fee226e0c3`.
|
||||
[Issue service regressions](../../server/src/__tests__/issues-service.test.ts)
|
||||
cover membership, unread counts, timestamps, and retained audit rows.
|
||||
|
||||
Verification: `not-run` — exercise a listed permitted operation with actual agent
|
||||
credentials/run context; inspect its result and applicable persistence/attribution.
|
||||
Assert the relevant denied actor, owner, company or mode from Policy. Review the
|
||||
remaining operations before qualifying the whole capability.
|
||||
remaining operations before qualifying the whole capability. Seed a completed
|
||||
issue with only automatic read cleanup under a user identity: Mine and Unread
|
||||
must exclude it while activity history retains the row. An explicit recovery
|
||||
decision must still establish participation. No live user-inbox proof is claimed.
|
||||
|
||||
Gaps: Agent inbox and Mine support exist; board attention/unread/dismissal aggregation
|
||||
is a separate user-only surface.
|
||||
|
||||
@@ -64,20 +64,30 @@ Review: `baseline`.
|
||||
|
||||
API: `present` — representative operation IDs:
|
||||
|
||||
- `GET /api/issues/{id}/recovery-actions`.
|
||||
- `POST /api/issues/{id}/recovery-actions/resolve`.
|
||||
|
||||
Agent access: `partial` — Eligible agent REST / CLI; source-bound recovery state.
|
||||
|
||||
Policy: Task/run visibility, run ownership and source/action policy; workspace export
|
||||
retry and some operator controls require board.
|
||||
retry and some operator controls require board. Completion or cancellation of a
|
||||
task does not clear its exact unresolved native workspace owner hold. Physical
|
||||
owner release still requires the [operator stop-verification procedure](../../doc/native-workspace-finalization-recovery.md).
|
||||
|
||||
Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts#L9478) at
|
||||
the reviewed commit above.
|
||||
the reviewed commit above. Exact owner hold retention in the
|
||||
[recovery state helper](../../server/src/services/native-runtime/native-workspace-finalization-recovery-state.ts)
|
||||
was reviewed at `89770ba707fb1ebd4cabd184eec2e7fee226e0c3`.
|
||||
|
||||
Verification: `not-run` — exercise a listed permitted operation with actual agent
|
||||
credentials/run context; inspect its result and applicable persistence/attribution.
|
||||
Assert the relevant denied actor, owner, company or mode from Policy. Review the
|
||||
remaining operations before qualifying the whole capability.
|
||||
remaining operations before qualifying the whole capability. Local
|
||||
[recovery route regressions](../../server/src/__tests__/issue-recovery-actions.test.ts)
|
||||
verify one unchanged hold across reads and sweeps on done/cancelled tasks, no
|
||||
physical copyback, and cleanup after the receipt is cleared. Exercise this
|
||||
sequence in a disposable instance; automatic cleanup must use the system actor
|
||||
and must not create reader participation. No live recovery proof is claimed.
|
||||
|
||||
Gaps: Need operation-level eligibility and a real native binding for permitted recovery
|
||||
actions; do not route around lifecycle authority.
|
||||
|
||||
@@ -40,6 +40,12 @@ Automated: [activity service](../../server/src/__tests__/activity-service.test.t
|
||||
|
||||
Manual: Find a known mutation by actor/resource and a known run by status. Change filters, load more, and follow the source links. Reload the chosen view and verify no company leakage or duplicate events. Inspect a routine run from its audit entry.
|
||||
|
||||
Issue activity is also available through `GET /api/issues/{id}/activity`.
|
||||
[Agent authorization](../../server/src/__tests__/activity-routes.test.ts) and
|
||||
[sandbox bridge](../../packages/adapter-utils/src/sandbox-callback-bridge.test.ts)
|
||||
regressions cover this route separately from company-wide audit. Live sandbox
|
||||
agent access remains unverified; use the companion recipe.
|
||||
|
||||
Audit-event Load more and run listings differ: Runs currently loads at most 200
|
||||
entries without pagination. The tested RoutineAuditActivity component has no
|
||||
registered activity-route host; inspect execution links actually offered by
|
||||
|
||||
@@ -35,8 +35,19 @@ Preconditions: follow the [baseline](./README.md#before-driving-a-journey). Seed
|
||||
|
||||
Automated: [archive routes](../../server/src/__tests__/inbox-archive-routes.test.ts) cover server mutations. [Task-list helpers](../../ui/src/pages/Issues.test.tsx) cover search URLs, pagination, and presentation constants; they do not render the combined Inbox/Tasks page. Reading, archiving, and navigating that page remain manual checks.
|
||||
|
||||
Automated: [Issue service regressions](../../server/src/__tests__/issues-service.test.ts)
|
||||
cover exclusion of historical automatic read cleanup from user participation,
|
||||
unread counts, and activity timestamps while retaining explicit user decisions
|
||||
and audit history.
|
||||
|
||||
Manual: Read an unread task, return to Unread, archive a handled item, and reload. Verify personal triage state changes while task status stays correct. Switch users and companies to check isolation.
|
||||
|
||||
Seed a completed task whose only activity under the viewing user is automatic
|
||||
read cleanup. Refresh Mine and Unread: the task must not appear through that
|
||||
participation, while the cleanup remains in activity history. Record an explicit
|
||||
user recovery decision on a separate task and verify it still counts. This
|
||||
browser journey remains manual.
|
||||
|
||||
### `decisions`
|
||||
|
||||
Automated: [decision queues](../../server/src/__tests__/decision-queues-routes.test.ts) covers route behavior. End-to-end queue-to-source navigation is manual.
|
||||
|
||||
@@ -90,6 +90,14 @@ the supported recovery until the source task produces a useful result or a
|
||||
specific escalation. Reload and verify that resolved notices retire and attempt
|
||||
history remains. A fixture pass does not qualify every live harness/provider.
|
||||
|
||||
For a native workspace owner hold, repeat issue reads and recovery checks after
|
||||
the task becomes done or cancelled. Verify one unchanged action ID, attempt
|
||||
count, and timestamps; task completion must not authorize copyback. Clear only
|
||||
the exact receipt after operator stop verification, then confirm stale action
|
||||
cleanup. The [route regressions](../../server/src/__tests__/issue-recovery-actions.test.ts)
|
||||
and [heartbeat regression](../../server/src/__tests__/heartbeat-process-recovery.test.ts)
|
||||
cover this sequence without a live operator recovery claim.
|
||||
|
||||
### `run-detail`
|
||||
|
||||
Automated: [run workspace recovery](../../ui/src/components/RunWorkspaceRecoverySurface.test.tsx)
|
||||
|
||||
@@ -1500,6 +1500,7 @@ describe("sandbox callback bridge", () => {
|
||||
{ method: "GET", path: "/api/goals/goal-1" },
|
||||
{ method: "GET", path: "/api/issues/issue-1" },
|
||||
{ method: "GET", path: "/api/issues/issue-1/heartbeat-context" },
|
||||
{ method: "GET", path: "/api/issues/issue-1/activity" },
|
||||
{ method: "GET", path: "/api/issues/issue-1/comments" },
|
||||
{ method: "GET", path: "/api/issues/issue-1/comments/c-1" },
|
||||
{ method: "POST", path: "/api/issues/issue-1/comments" },
|
||||
@@ -1546,6 +1547,10 @@ describe("sandbox callback bridge", () => {
|
||||
}
|
||||
|
||||
const denied: Array<{ method: string; path: string }> = [
|
||||
{ method: "POST", path: "/api/issues/issue-1/activity" },
|
||||
{ method: "GET", path: "/api/issues/issue-1/activity/extra" },
|
||||
{ method: "GET", path: "/api/companies/co-1/activity" },
|
||||
{ method: "GET", path: "/api/companies/co-1/audit/agent-actions" },
|
||||
{ method: "GET", path: "/api/companies/co-1/agent-commentary" },
|
||||
{ method: "POST", path: "/api/companies/co-1/agent-commentary/other" },
|
||||
{ method: "POST", path: "/api/companies/co-1/email/inboxes" },
|
||||
|
||||
@@ -162,6 +162,8 @@ export const DEFAULT_SANDBOX_CALLBACK_BRIDGE_ROUTE_ALLOWLIST: readonly SandboxCa
|
||||
// Issue lifecycle: read context, checkout, update, comment, document, release
|
||||
{ method: "GET", path: /^\/api\/issues\/[^/]+$/ },
|
||||
{ method: "GET", path: /^\/api\/issues\/[^/]+\/heartbeat-context$/ },
|
||||
// The controller still checks company membership and issue read authority.
|
||||
{ method: "GET", path: /^\/api\/issues\/[^/]+\/activity$/ },
|
||||
{ method: "GET", path: /^\/api\/issues\/[^/]+\/comments(?:\/[^/]+)?$/ },
|
||||
{ method: "POST", path: /^\/api\/issues\/[^/]+\/comments$/ },
|
||||
{ method: "GET", path: /^\/api\/issues\/[^/]+\/documents(?:\/[^/]+)?$/ },
|
||||
|
||||
@@ -47,104 +47,104 @@ The skill/reference inventory and eval cases are the only normative behavior sou
|
||||
| skill:skills/paperclip/SKILL.md:conversation-tasks:32 | optional_agent_tool | skills/paperclip/SKILL.md:32 |
|
||||
| skill:skills/paperclip/SKILL.md:server-verified-external-chat-turns:49 | control_plane_owned | skills/paperclip/SKILL.md:49 |
|
||||
| skill:skills/paperclip/SKILL.md:the-heartbeat-procedure:89 | optional_agent_tool | skills/paperclip/SKILL.md:89 |
|
||||
| skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:198 | always_agent_tool | skills/paperclip/SKILL.md:198 |
|
||||
| skill:skills/paperclip/SKILL.md:status-quick-guide:246 | control_plane_owned | skills/paperclip/SKILL.md:246 |
|
||||
| skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:256 | optional_agent_tool | skills/paperclip/SKILL.md:256 |
|
||||
| skill:skills/paperclip/SKILL.md:delegating-review-tasks:269 | always_agent_tool | skills/paperclip/SKILL.md:269 |
|
||||
| skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:280 | control_plane_owned | skills/paperclip/SKILL.md:280 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:288 | control_plane_owned | skills/paperclip/SKILL.md:288 |
|
||||
| skill:skills/paperclip/SKILL.md:requesting-board-approval:313 | optional_agent_tool | skills/paperclip/SKILL.md:313 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-thread-interactions:334 | optional_agent_tool | skills/paperclip/SKILL.md:334 |
|
||||
| skill:skills/paperclip/SKILL.md:standalone-decisions:364 | optional_agent_tool | skills/paperclip/SKILL.md:364 |
|
||||
| skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:468 | optional_agent_tool | skills/paperclip/SKILL.md:468 |
|
||||
| skill:skills/paperclip/SKILL.md:niche-workflow-pointers:510 | optional_agent_tool | skills/paperclip/SKILL.md:510 |
|
||||
| skill:skills/paperclip/SKILL.md:cases:520 | optional_agent_tool | skills/paperclip/SKILL.md:520 |
|
||||
| skill:skills/paperclip/SKILL.md:company-skills-workflow:525 | optional_agent_tool | skills/paperclip/SKILL.md:525 |
|
||||
| skill:skills/paperclip/SKILL.md:routines:536 | optional_agent_tool | skills/paperclip/SKILL.md:536 |
|
||||
| skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:547 | optional_agent_tool | skills/paperclip/SKILL.md:547 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:553 | optional_agent_tool | skills/paperclip/SKILL.md:553 |
|
||||
| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:560 | optional_agent_tool | skills/paperclip/SKILL.md:560 |
|
||||
| skill:skills/paperclip/SKILL.md:reading-granted-secrets:567 | optional_agent_tool | skills/paperclip/SKILL.md:567 |
|
||||
| skill:skills/paperclip/SKILL.md:critical-rules:593 | optional_agent_tool | skills/paperclip/SKILL.md:593 |
|
||||
| skill:skills/paperclip/SKILL.md:comment-style-required:614 | always_agent_tool | skills/paperclip/SKILL.md:614 |
|
||||
| skill:skills/paperclip/SKILL.md:update:646 | optional_agent_tool | skills/paperclip/SKILL.md:646 |
|
||||
| skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:656 | always_agent_tool | skills/paperclip/SKILL.md:656 |
|
||||
| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:670 | optional_agent_tool | skills/paperclip/SKILL.md:670 |
|
||||
| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:701 | optional_agent_tool | skills/paperclip/SKILL.md:701 |
|
||||
| skill:skills/paperclip/SKILL.md:searching-issues:730 | optional_agent_tool | skills/paperclip/SKILL.md:730 |
|
||||
| skill:skills/paperclip/SKILL.md:full-reference:740 | optional_agent_tool | skills/paperclip/SKILL.md:740 |
|
||||
| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:744 | always_agent_tool | skills/paperclip/SKILL.md:744 |
|
||||
| skill:skills/paperclip/SKILL.md:incidental-feedback:761 | optional_agent_tool | skills/paperclip/SKILL.md:761 |
|
||||
| skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:200 | always_agent_tool | skills/paperclip/SKILL.md:200 |
|
||||
| skill:skills/paperclip/SKILL.md:status-quick-guide:248 | control_plane_owned | skills/paperclip/SKILL.md:248 |
|
||||
| skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:258 | optional_agent_tool | skills/paperclip/SKILL.md:258 |
|
||||
| skill:skills/paperclip/SKILL.md:delegating-review-tasks:271 | always_agent_tool | skills/paperclip/SKILL.md:271 |
|
||||
| skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:282 | control_plane_owned | skills/paperclip/SKILL.md:282 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:290 | control_plane_owned | skills/paperclip/SKILL.md:290 |
|
||||
| skill:skills/paperclip/SKILL.md:requesting-board-approval:315 | optional_agent_tool | skills/paperclip/SKILL.md:315 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-thread-interactions:336 | optional_agent_tool | skills/paperclip/SKILL.md:336 |
|
||||
| skill:skills/paperclip/SKILL.md:standalone-decisions:366 | optional_agent_tool | skills/paperclip/SKILL.md:366 |
|
||||
| skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:470 | optional_agent_tool | skills/paperclip/SKILL.md:470 |
|
||||
| skill:skills/paperclip/SKILL.md:niche-workflow-pointers:512 | optional_agent_tool | skills/paperclip/SKILL.md:512 |
|
||||
| skill:skills/paperclip/SKILL.md:cases:522 | optional_agent_tool | skills/paperclip/SKILL.md:522 |
|
||||
| skill:skills/paperclip/SKILL.md:company-skills-workflow:527 | optional_agent_tool | skills/paperclip/SKILL.md:527 |
|
||||
| skill:skills/paperclip/SKILL.md:routines:538 | optional_agent_tool | skills/paperclip/SKILL.md:538 |
|
||||
| skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:549 | optional_agent_tool | skills/paperclip/SKILL.md:549 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:555 | optional_agent_tool | skills/paperclip/SKILL.md:555 |
|
||||
| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:562 | optional_agent_tool | skills/paperclip/SKILL.md:562 |
|
||||
| skill:skills/paperclip/SKILL.md:reading-granted-secrets:569 | optional_agent_tool | skills/paperclip/SKILL.md:569 |
|
||||
| skill:skills/paperclip/SKILL.md:critical-rules:595 | optional_agent_tool | skills/paperclip/SKILL.md:595 |
|
||||
| skill:skills/paperclip/SKILL.md:comment-style-required:616 | always_agent_tool | skills/paperclip/SKILL.md:616 |
|
||||
| skill:skills/paperclip/SKILL.md:update:648 | optional_agent_tool | skills/paperclip/SKILL.md:648 |
|
||||
| skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:658 | always_agent_tool | skills/paperclip/SKILL.md:658 |
|
||||
| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:672 | optional_agent_tool | skills/paperclip/SKILL.md:672 |
|
||||
| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:703 | optional_agent_tool | skills/paperclip/SKILL.md:703 |
|
||||
| skill:skills/paperclip/SKILL.md:searching-issues:733 | optional_agent_tool | skills/paperclip/SKILL.md:733 |
|
||||
| skill:skills/paperclip/SKILL.md:full-reference:743 | optional_agent_tool | skills/paperclip/SKILL.md:743 |
|
||||
| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:747 | always_agent_tool | skills/paperclip/SKILL.md:747 |
|
||||
| skill:skills/paperclip/SKILL.md:incidental-feedback:764 | optional_agent_tool | skills/paperclip/SKILL.md:764 |
|
||||
| skill:skills/paperclip/references/api-reference.md:paperclip-api-reference:1 | optional_agent_tool | skills/paperclip/references/api-reference.md:1 |
|
||||
| skill:skills/paperclip/references/api-reference.md:response-schemas:9 | optional_agent_tool | skills/paperclip/references/api-reference.md:9 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-record-get-api-agents-me-or-get-api-agents-agentid:11 | optional_agent_tool | skills/paperclip/references/api-reference.md:11 |
|
||||
| skill:skills/paperclip/references/api-reference.md:company-portability:44 | optional_agent_tool | skills/paperclip/references/api-reference.md:44 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-with-ancestors-get-api-issues-issueid:110 | optional_agent_tool | skills/paperclip/references/api-reference.md:110 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-update-response-patch-api-issues-issueid:196 | optional_agent_tool | skills/paperclip/references/api-reference.md:196 |
|
||||
| skill:skills/paperclip/references/api-reference.md:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers:238 | control_plane_owned | skills/paperclip/references/api-reference.md:238 |
|
||||
| skill:skills/paperclip/references/api-reference.md:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes:277 | control_plane_owned | skills/paperclip/references/api-reference.md:277 |
|
||||
| skill:skills/paperclip/references/api-reference.md:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree:321 | optional_agent_tool | skills/paperclip/references/api-reference.md:321 |
|
||||
| skill:skills/paperclip/references/api-reference.md:execution-policy-fields-on-an-issue:369 | optional_agent_tool | skills/paperclip/references/api-reference.md:369 |
|
||||
| skill:skills/paperclip/references/api-reference.md:cross-agent-review-gates:421 | always_agent_tool | skills/paperclip/references/api-reference.md:421 |
|
||||
| skill:skills/paperclip/references/api-reference.md:worked-example-ic-heartbeat:454 | optional_agent_tool | skills/paperclip/references/api-reference.md:454 |
|
||||
| skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:459 | control_plane_owned | skills/paperclip/references/api-reference.md:459 |
|
||||
| skill:skills/paperclip/references/api-reference.md:2-check-inbox:463 | control_plane_owned | skills/paperclip/references/api-reference.md:463 |
|
||||
| skill:skills/paperclip/references/api-reference.md:3-already-have-issue-101-inprogress-highest-priority-continue-it:470 | optional_agent_tool | skills/paperclip/references/api-reference.md:470 |
|
||||
| skill:skills/paperclip/references/api-reference.md:4-do-the-actual-work-write-code-run-tests:477 | optional_agent_tool | skills/paperclip/references/api-reference.md:477 |
|
||||
| skill:skills/paperclip/references/api-reference.md:5-work-is-done-update-status-and-comment-in-one-call:479 | always_agent_tool | skills/paperclip/references/api-reference.md:479 |
|
||||
| skill:skills/paperclip/references/api-reference.md:6-still-have-time-checkout-the-next-task:483 | control_plane_owned | skills/paperclip/references/api-reference.md:483 |
|
||||
| skill:skills/paperclip/references/api-reference.md:7-made-partial-progress-not-done-yet-comment-and-exit:490 | always_agent_tool | skills/paperclip/references/api-reference.md:490 |
|
||||
| skill:skills/paperclip/references/api-reference.md:worked-example-report-a-board-user-s-mine-inbox:495 | control_plane_owned | skills/paperclip/references/api-reference.md:495 |
|
||||
| skill:skills/paperclip/references/api-reference.md:board-user-created-the-requesting-issue:500 | optional_agent_tool | skills/paperclip/references/api-reference.md:500 |
|
||||
| skill:skills/paperclip/references/api-reference.md:fetch-the-board-user-s-mine-inbox-issues:504 | control_plane_owned | skills/paperclip/references/api-reference.md:504 |
|
||||
| skill:skills/paperclip/references/api-reference.md:summarize-it-back-to-the-board-in-a-comment-or-document:518 | always_agent_tool | skills/paperclip/references/api-reference.md:518 |
|
||||
| skill:skills/paperclip/references/api-reference.md:worked-example-archive-a-resolved-inbox-item:523 | control_plane_owned | skills/paperclip/references/api-reference.md:523 |
|
||||
| skill:skills/paperclip/references/api-reference.md:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run:528 | optional_agent_tool | skills/paperclip/references/api-reference.md:528 |
|
||||
| skill:skills/paperclip/references/api-reference.md:reverse-the-archive-if-it-was-premature-or-no-longer-desired:537 | optional_agent_tool | skills/paperclip/references/api-reference.md:537 |
|
||||
| skill:skills/paperclip/references/api-reference.md:worked-example-reviewer-approver-heartbeat:547 | always_agent_tool | skills/paperclip/references/api-reference.md:547 |
|
||||
| skill:skills/paperclip/references/api-reference.md:worked-example-manager-heartbeat:586 | optional_agent_tool | skills/paperclip/references/api-reference.md:586 |
|
||||
| skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:589 | control_plane_owned | skills/paperclip/references/api-reference.md:589 |
|
||||
| skill:skills/paperclip/references/api-reference.md:2-check-team-status:593 | optional_agent_tool | skills/paperclip/references/api-reference.md:593 |
|
||||
| skill:skills/paperclip/references/api-reference.md:3-agent-42-is-blocked-read-comments:600 | control_plane_owned | skills/paperclip/references/api-reference.md:600 |
|
||||
| skill:skills/paperclip/references/api-reference.md:4-unblock-reassign-and-comment:604 | control_plane_owned | skills/paperclip/references/api-reference.md:604 |
|
||||
| skill:skills/paperclip/references/api-reference.md:5-check-own-assignments:608 | optional_agent_tool | skills/paperclip/references/api-reference.md:608 |
|
||||
| skill:skills/paperclip/references/api-reference.md:6-create-subtasks-and-delegate:615 | optional_agent_tool | skills/paperclip/references/api-reference.md:615 |
|
||||
| skill:skills/paperclip/references/api-reference.md:load-tests-depend-on-caching-layer-being-done-first-paperclip-will-auto-wake-agent-55-when-the-blocker-resolves:621 | control_plane_owned | skills/paperclip/references/api-reference.md:621 |
|
||||
| skill:skills/paperclip/references/api-reference.md:7-dashboard-for-health-check:626 | optional_agent_tool | skills/paperclip/references/api-reference.md:626 |
|
||||
| skill:skills/paperclip/references/api-reference.md:comments-and-mentions:632 | always_agent_tool | skills/paperclip/references/api-reference.md:632 |
|
||||
| skill:skills/paperclip/references/api-reference.md:update:639 | optional_agent_tool | skills/paperclip/references/api-reference.md:639 |
|
||||
| skill:skills/paperclip/references/api-reference.md:cross-team-work-and-delegation:661 | optional_agent_tool | skills/paperclip/references/api-reference.md:661 |
|
||||
| skill:skills/paperclip/references/api-reference.md:receiving-cross-team-work:665 | optional_agent_tool | skills/paperclip/references/api-reference.md:665 |
|
||||
| skill:skills/paperclip/references/api-reference.md:questions-and-dependencies:675 | always_agent_tool | skills/paperclip/references/api-reference.md:675 |
|
||||
| skill:skills/paperclip/references/api-reference.md:company-context:687 | optional_agent_tool | skills/paperclip/references/api-reference.md:687 |
|
||||
| skill:skills/paperclip/references/api-reference.md:company-branding-ceo-board:699 | optional_agent_tool | skills/paperclip/references/api-reference.md:699 |
|
||||
| skill:skills/paperclip/references/api-reference.md:openclaw-invite-prompt-ceo:719 | optional_agent_tool | skills/paperclip/references/api-reference.md:719 |
|
||||
| skill:skills/paperclip/references/api-reference.md:setting-agent-instructions-path:738 | optional_agent_tool | skills/paperclip/references/api-reference.md:738 |
|
||||
| skill:skills/paperclip/references/api-reference.md:project-setup-create-workspace:771 | optional_agent_tool | skills/paperclip/references/api-reference.md:771 |
|
||||
| skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:795 | optional_agent_tool | skills/paperclip/references/api-reference.md:795 |
|
||||
| skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:814 | optional_agent_tool | skills/paperclip/references/api-reference.md:814 |
|
||||
| skill:skills/paperclip/references/api-reference.md:governance-and-approvals:843 | optional_agent_tool | skills/paperclip/references/api-reference.md:843 |
|
||||
| skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:847 | optional_agent_tool | skills/paperclip/references/api-reference.md:847 |
|
||||
| skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:912 | optional_agent_tool | skills/paperclip/references/api-reference.md:912 |
|
||||
| skill:skills/paperclip/references/api-reference.md:questions-and-waiting-for-human-input:921 | always_agent_tool | skills/paperclip/references/api-reference.md:921 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:1025 | always_agent_tool | skills/paperclip/references/api-reference.md:1025 |
|
||||
| skill:skills/paperclip/references/api-reference.md:conversational-confirmation-answers:1083 | always_agent_tool | skills/paperclip/references/api-reference.md:1083 |
|
||||
| skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:1089 | always_agent_tool | skills/paperclip/references/api-reference.md:1089 |
|
||||
| skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1204 | optional_agent_tool | skills/paperclip/references/api-reference.md:1204 |
|
||||
| skill:skills/paperclip/references/api-reference.md:checking-approval-status:1314 | optional_agent_tool | skills/paperclip/references/api-reference.md:1314 |
|
||||
| skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1320 | always_agent_tool | skills/paperclip/references/api-reference.md:1320 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1338 | always_agent_tool | skills/paperclip/references/api-reference.md:1338 |
|
||||
| skill:skills/paperclip/references/api-reference.md:error-handling:1368 | control_plane_owned | skills/paperclip/references/api-reference.md:1368 |
|
||||
| skill:skills/paperclip/references/api-reference.md:full-api-reference:1382 | optional_agent_tool | skills/paperclip/references/api-reference.md:1382 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agents:1384 | optional_agent_tool | skills/paperclip/references/api-reference.md:1384 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issues-tasks:1405 | optional_agent_tool | skills/paperclip/references/api-reference.md:1405 |
|
||||
| skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1446 | optional_agent_tool | skills/paperclip/references/api-reference.md:1446 |
|
||||
| skill:skills/paperclip/references/api-reference.md:routines:1470 | optional_agent_tool | skills/paperclip/references/api-reference.md:1470 |
|
||||
| skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1486 | optional_agent_tool | skills/paperclip/references/api-reference.md:1486 |
|
||||
| skill:skills/paperclip/references/api-reference.md:secrets:1508 | optional_agent_tool | skills/paperclip/references/api-reference.md:1508 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1521 | optional_agent_tool | skills/paperclip/references/api-reference.md:1521 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-secret-access:1621 | optional_agent_tool | skills/paperclip/references/api-reference.md:1621 |
|
||||
| skill:skills/paperclip/references/api-reference.md:common-mistakes:1661 | optional_agent_tool | skills/paperclip/references/api-reference.md:1661 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-with-ancestors-get-api-issues-issueid:116 | optional_agent_tool | skills/paperclip/references/api-reference.md:116 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-update-response-patch-api-issues-issueid:202 | optional_agent_tool | skills/paperclip/references/api-reference.md:202 |
|
||||
| skill:skills/paperclip/references/api-reference.md:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers:244 | control_plane_owned | skills/paperclip/references/api-reference.md:244 |
|
||||
| skill:skills/paperclip/references/api-reference.md:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes:283 | control_plane_owned | skills/paperclip/references/api-reference.md:283 |
|
||||
| skill:skills/paperclip/references/api-reference.md:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree:327 | optional_agent_tool | skills/paperclip/references/api-reference.md:327 |
|
||||
| skill:skills/paperclip/references/api-reference.md:execution-policy-fields-on-an-issue:375 | optional_agent_tool | skills/paperclip/references/api-reference.md:375 |
|
||||
| skill:skills/paperclip/references/api-reference.md:cross-agent-review-gates:427 | always_agent_tool | skills/paperclip/references/api-reference.md:427 |
|
||||
| skill:skills/paperclip/references/api-reference.md:worked-example-ic-heartbeat:460 | optional_agent_tool | skills/paperclip/references/api-reference.md:460 |
|
||||
| skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:465 | control_plane_owned | skills/paperclip/references/api-reference.md:465 |
|
||||
| skill:skills/paperclip/references/api-reference.md:2-check-inbox:469 | control_plane_owned | skills/paperclip/references/api-reference.md:469 |
|
||||
| skill:skills/paperclip/references/api-reference.md:3-already-have-issue-101-inprogress-highest-priority-continue-it:476 | optional_agent_tool | skills/paperclip/references/api-reference.md:476 |
|
||||
| skill:skills/paperclip/references/api-reference.md:4-do-the-actual-work-write-code-run-tests:483 | optional_agent_tool | skills/paperclip/references/api-reference.md:483 |
|
||||
| skill:skills/paperclip/references/api-reference.md:5-work-is-done-update-status-and-comment-in-one-call:485 | always_agent_tool | skills/paperclip/references/api-reference.md:485 |
|
||||
| skill:skills/paperclip/references/api-reference.md:6-still-have-time-checkout-the-next-task:489 | control_plane_owned | skills/paperclip/references/api-reference.md:489 |
|
||||
| skill:skills/paperclip/references/api-reference.md:7-made-partial-progress-not-done-yet-comment-and-exit:496 | always_agent_tool | skills/paperclip/references/api-reference.md:496 |
|
||||
| skill:skills/paperclip/references/api-reference.md:worked-example-report-a-board-user-s-mine-inbox:501 | control_plane_owned | skills/paperclip/references/api-reference.md:501 |
|
||||
| skill:skills/paperclip/references/api-reference.md:board-user-created-the-requesting-issue:506 | optional_agent_tool | skills/paperclip/references/api-reference.md:506 |
|
||||
| skill:skills/paperclip/references/api-reference.md:fetch-the-board-user-s-mine-inbox-issues:510 | control_plane_owned | skills/paperclip/references/api-reference.md:510 |
|
||||
| skill:skills/paperclip/references/api-reference.md:summarize-it-back-to-the-board-in-a-comment-or-document:524 | always_agent_tool | skills/paperclip/references/api-reference.md:524 |
|
||||
| skill:skills/paperclip/references/api-reference.md:worked-example-archive-a-resolved-inbox-item:529 | control_plane_owned | skills/paperclip/references/api-reference.md:529 |
|
||||
| skill:skills/paperclip/references/api-reference.md:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run:534 | optional_agent_tool | skills/paperclip/references/api-reference.md:534 |
|
||||
| skill:skills/paperclip/references/api-reference.md:reverse-the-archive-if-it-was-premature-or-no-longer-desired:543 | optional_agent_tool | skills/paperclip/references/api-reference.md:543 |
|
||||
| skill:skills/paperclip/references/api-reference.md:worked-example-reviewer-approver-heartbeat:553 | always_agent_tool | skills/paperclip/references/api-reference.md:553 |
|
||||
| skill:skills/paperclip/references/api-reference.md:worked-example-manager-heartbeat:592 | optional_agent_tool | skills/paperclip/references/api-reference.md:592 |
|
||||
| skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:595 | control_plane_owned | skills/paperclip/references/api-reference.md:595 |
|
||||
| skill:skills/paperclip/references/api-reference.md:2-check-team-status:599 | optional_agent_tool | skills/paperclip/references/api-reference.md:599 |
|
||||
| skill:skills/paperclip/references/api-reference.md:3-agent-42-is-blocked-read-comments:606 | control_plane_owned | skills/paperclip/references/api-reference.md:606 |
|
||||
| skill:skills/paperclip/references/api-reference.md:4-unblock-reassign-and-comment:610 | control_plane_owned | skills/paperclip/references/api-reference.md:610 |
|
||||
| skill:skills/paperclip/references/api-reference.md:5-check-own-assignments:614 | optional_agent_tool | skills/paperclip/references/api-reference.md:614 |
|
||||
| skill:skills/paperclip/references/api-reference.md:6-create-subtasks-and-delegate:621 | optional_agent_tool | skills/paperclip/references/api-reference.md:621 |
|
||||
| skill:skills/paperclip/references/api-reference.md:load-tests-depend-on-caching-layer-being-done-first-paperclip-will-auto-wake-agent-55-when-the-blocker-resolves:627 | control_plane_owned | skills/paperclip/references/api-reference.md:627 |
|
||||
| skill:skills/paperclip/references/api-reference.md:7-dashboard-for-health-check:632 | optional_agent_tool | skills/paperclip/references/api-reference.md:632 |
|
||||
| skill:skills/paperclip/references/api-reference.md:comments-and-mentions:638 | always_agent_tool | skills/paperclip/references/api-reference.md:638 |
|
||||
| skill:skills/paperclip/references/api-reference.md:update:645 | optional_agent_tool | skills/paperclip/references/api-reference.md:645 |
|
||||
| skill:skills/paperclip/references/api-reference.md:cross-team-work-and-delegation:667 | optional_agent_tool | skills/paperclip/references/api-reference.md:667 |
|
||||
| skill:skills/paperclip/references/api-reference.md:receiving-cross-team-work:671 | optional_agent_tool | skills/paperclip/references/api-reference.md:671 |
|
||||
| skill:skills/paperclip/references/api-reference.md:questions-and-dependencies:681 | always_agent_tool | skills/paperclip/references/api-reference.md:681 |
|
||||
| skill:skills/paperclip/references/api-reference.md:company-context:693 | optional_agent_tool | skills/paperclip/references/api-reference.md:693 |
|
||||
| skill:skills/paperclip/references/api-reference.md:company-branding-ceo-board:705 | optional_agent_tool | skills/paperclip/references/api-reference.md:705 |
|
||||
| skill:skills/paperclip/references/api-reference.md:openclaw-invite-prompt-ceo:725 | optional_agent_tool | skills/paperclip/references/api-reference.md:725 |
|
||||
| skill:skills/paperclip/references/api-reference.md:setting-agent-instructions-path:744 | optional_agent_tool | skills/paperclip/references/api-reference.md:744 |
|
||||
| skill:skills/paperclip/references/api-reference.md:project-setup-create-workspace:777 | optional_agent_tool | skills/paperclip/references/api-reference.md:777 |
|
||||
| skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:801 | optional_agent_tool | skills/paperclip/references/api-reference.md:801 |
|
||||
| skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:820 | optional_agent_tool | skills/paperclip/references/api-reference.md:820 |
|
||||
| skill:skills/paperclip/references/api-reference.md:governance-and-approvals:849 | optional_agent_tool | skills/paperclip/references/api-reference.md:849 |
|
||||
| skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:853 | optional_agent_tool | skills/paperclip/references/api-reference.md:853 |
|
||||
| skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:918 | optional_agent_tool | skills/paperclip/references/api-reference.md:918 |
|
||||
| skill:skills/paperclip/references/api-reference.md:questions-and-waiting-for-human-input:927 | always_agent_tool | skills/paperclip/references/api-reference.md:927 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:1031 | always_agent_tool | skills/paperclip/references/api-reference.md:1031 |
|
||||
| skill:skills/paperclip/references/api-reference.md:conversational-confirmation-answers:1089 | always_agent_tool | skills/paperclip/references/api-reference.md:1089 |
|
||||
| skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:1095 | always_agent_tool | skills/paperclip/references/api-reference.md:1095 |
|
||||
| skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1210 | optional_agent_tool | skills/paperclip/references/api-reference.md:1210 |
|
||||
| skill:skills/paperclip/references/api-reference.md:checking-approval-status:1320 | optional_agent_tool | skills/paperclip/references/api-reference.md:1320 |
|
||||
| skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1326 | always_agent_tool | skills/paperclip/references/api-reference.md:1326 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1344 | always_agent_tool | skills/paperclip/references/api-reference.md:1344 |
|
||||
| skill:skills/paperclip/references/api-reference.md:error-handling:1374 | control_plane_owned | skills/paperclip/references/api-reference.md:1374 |
|
||||
| skill:skills/paperclip/references/api-reference.md:full-api-reference:1388 | optional_agent_tool | skills/paperclip/references/api-reference.md:1388 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agents:1390 | optional_agent_tool | skills/paperclip/references/api-reference.md:1390 |
|
||||
| skill:skills/paperclip/references/api-reference.md:issues-tasks:1411 | optional_agent_tool | skills/paperclip/references/api-reference.md:1411 |
|
||||
| skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1452 | optional_agent_tool | skills/paperclip/references/api-reference.md:1452 |
|
||||
| skill:skills/paperclip/references/api-reference.md:routines:1476 | optional_agent_tool | skills/paperclip/references/api-reference.md:1476 |
|
||||
| skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1492 | optional_agent_tool | skills/paperclip/references/api-reference.md:1492 |
|
||||
| skill:skills/paperclip/references/api-reference.md:secrets:1514 | optional_agent_tool | skills/paperclip/references/api-reference.md:1514 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1527 | optional_agent_tool | skills/paperclip/references/api-reference.md:1527 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-secret-access:1627 | optional_agent_tool | skills/paperclip/references/api-reference.md:1627 |
|
||||
| skill:skills/paperclip/references/api-reference.md:common-mistakes:1667 | optional_agent_tool | skills/paperclip/references/api-reference.md:1667 |
|
||||
| skill:skills/paperclip/references/artifacts.md:generated-artifacts-and-work-products:1 | always_agent_tool | skills/paperclip/references/artifacts.md:1 |
|
||||
| skill:skills/paperclip/references/artifacts.md:workspace-only-file-references:15 | optional_agent_tool | skills/paperclip/references/artifacts.md:15 |
|
||||
| skill:skills/paperclip/references/cases.md:cases:1 | optional_agent_tool | skills/paperclip/references/cases.md:1 |
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -5,6 +5,6 @@ Generated by `scripts/generate-capability-contract.mjs`; do not edit generated f
|
||||
- Skill/reference headings: 165
|
||||
- Legacy MCP tools: 42
|
||||
- Eval cases: 106 across 16 groups
|
||||
- Deterministic content SHA-256: `83447a4f9463f9a3d73dd4bea78b580df0356545ba00d0da96763282653bdf0f`
|
||||
- Deterministic content SHA-256: `74c5fcb9ac01e20178834438536f40edc8b630e15571b447cd783a77b8b69c7b`
|
||||
|
||||
Every row has exactly one primary disposition, a source anchor, a semantic operation, and a mock-state expectation.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -126,6 +126,33 @@ describe("activity routes", () => {
|
||||
mockAccessService.canUser.mockResolvedValue(false);
|
||||
});
|
||||
|
||||
it("lets an agent read activity for an authorized issue", async () => {
|
||||
mockIssueService.getByIdentifier.mockResolvedValue({
|
||||
id: "issue-1", companyId: "company-1", projectId: null, parentId: null,
|
||||
assigneeAgentId: "agent-1", assigneeUserId: null, status: "done",
|
||||
});
|
||||
mockActivityService.forIssue.mockResolvedValue([{ action: "issue.recovery_action_resolved", actorType: "system" }]);
|
||||
const app = await createApp({ type: "agent", companyId: "company-1", agentId: "agent-1" });
|
||||
const res = await requestApp(app, baseUrl => request(baseUrl).get("/api/issues/TEST-1/activity"));
|
||||
expect(res.status).toBe(200);
|
||||
expect(mockAccessService.decide).toHaveBeenCalledWith(expect.objectContaining({
|
||||
action: "issue:read", resource: expect.objectContaining({ companyId: "company-1", issueId: "issue-1" }),
|
||||
}));
|
||||
expect(mockActivityService.forIssue).toHaveBeenCalledWith("issue-1");
|
||||
expect(res.body).toEqual([{ action: "issue.recovery_action_resolved", actorType: "system" }]);
|
||||
});
|
||||
|
||||
it.each(["boundary", "company"])("denies agent activity reads outside the %s scope", async (kind) => {
|
||||
const issue = { id: "issue-1", companyId: kind === "company" ? "company-2" : "company-1" };
|
||||
mockIssueService.getById.mockResolvedValue(issue);
|
||||
mockIssueService.getByIdentifier.mockResolvedValue(issue);
|
||||
mockAccessService.decide.mockResolvedValue({ allowed: false });
|
||||
const app = await createApp({ type: "agent", companyId: "company-1", agentId: "agent-1" });
|
||||
const res = await requestApp(app, baseUrl => request(baseUrl).get("/api/issues/issue-1/activity"));
|
||||
expect(res.status).toBe(kind === "company" ? 404 : 403);
|
||||
expect(mockActivityService.forIssue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns redacted all-actors rows to a basic company reader", async () => {
|
||||
mockAgentActionAuditService.list.mockResolvedValue({
|
||||
items: [{
|
||||
|
||||
@@ -2415,7 +2415,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => {
|
||||
.toMatchObject({ status: "active", resolvedAt: null });
|
||||
});
|
||||
|
||||
it("keeps existing terminal-task disposition for a native finalization hold", async () => {
|
||||
it("retains the exact native owner hold without changing a terminal task's disposition", async () => {
|
||||
const f = await seedPendingNativeFinalization();
|
||||
await db.update(heartbeatRuns).set({ runnerProfileJson: { nativeWorkspaceFinalizationOwner: { unknown: true } } })
|
||||
.where(eq(heartbeatRuns.id, f.runId));
|
||||
@@ -2423,6 +2423,13 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => {
|
||||
await heartbeat.recoverNativeRunsAfterRestart();
|
||||
const [hold] = await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.sourceIssueId, f.issueId));
|
||||
await db.update(issues).set({ status: "done" }).where(eq(issues.id, f.issueId));
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
await heartbeat.reconcileStrandedAssignedIssues();
|
||||
expect((await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.id, hold.id)))[0])
|
||||
.toEqual(hold);
|
||||
expect((await db.select().from(issues).where(eq(issues.id, f.issueId)))[0].status).toBe("done");
|
||||
}
|
||||
await db.update(heartbeatRuns).set({ runnerProfileJson: {} }).where(eq(heartbeatRuns.id, f.runId));
|
||||
await heartbeat.reconcileStrandedAssignedIssues();
|
||||
expect((await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.id, hold.id)))[0])
|
||||
.toMatchObject({ status: "resolved", resolutionNote: "source_terminal" });
|
||||
|
||||
@@ -38,6 +38,8 @@ import { deliverReconciledExecutions } from "../services/execution-recovery-reso
|
||||
import { workspaceOperationService, isNativeWorkspaceFinalizationOperationActive } from "../services/workspace-operations.js";
|
||||
import { issueRecoveryActionService } from "../services/issue-recovery-actions.js";
|
||||
import { recoveryService } from "../services/recovery/service.js";
|
||||
import { issueService } from "../services/issues.js";
|
||||
import { withNativeWorkspaceFinalizationOwnership } from "../services/native-runtime/native-workspace-finalization-ownership.js";
|
||||
import { noticeMetadataReferencesRecoveryAction } from "../services/recovery/successful-run-handoff.js";
|
||||
|
||||
const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport();
|
||||
@@ -2479,6 +2481,51 @@ describeEmbeddedPostgres("issue recovery actions", () => {
|
||||
trigger: "read_projection",
|
||||
recoveryActionId: action.id,
|
||||
});
|
||||
expect(activityRows.find((row) => row.action === "issue.recovery_action_resolved")).toMatchObject({
|
||||
actorType: "system", actorId: "recovery", agentId: null,
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["done", "cancelled"])("keeps one workspace owner hold across reads and sweeps of a %s task", async (status) => {
|
||||
const { companyId, coderId, sourceIssueId } = await seedCompany();
|
||||
const runId = randomUUID();
|
||||
const owner = { token: randomUUID(), hostname: "old-controller.invalid", pid: 42, processStartedAt: new Date(0).toISOString() };
|
||||
await db.insert(heartbeatRuns).values({
|
||||
id: runId, companyId, agentId: coderId, nativeIssueId: sourceIssueId,
|
||||
runtimeMode: "native", status: "failed", finishedAt: new Date(),
|
||||
runnerProfileJson: { nativeWorkspaceFinalizationOwner: owner },
|
||||
});
|
||||
await db.update(issues).set({ status }).where(eq(issues.id, sourceIssueId));
|
||||
const userId = "board-reader";
|
||||
const app = createApp({ type: "board", source: "local_implicit", userId });
|
||||
const copyback = vi.fn();
|
||||
const recovery = recoveryService(db, { enqueueWakeup: vi.fn(async () => null) });
|
||||
let firstAction: typeof issueRecoveryActions.$inferSelect | undefined;
|
||||
for (let tick = 0; tick < 3; tick += 1) {
|
||||
expect(await withNativeWorkspaceFinalizationOwnership({ db, companyId, runId }, copyback)).toEqual({ acquired: false });
|
||||
const detail = await request(app).get(`/api/issues/${sourceIssueId}`).expect(200);
|
||||
expect(detail.body.activeRecoveryAction).toMatchObject({
|
||||
cause: "native_workspace_finalization_owner_unverified", status: "active", attemptCount: 1,
|
||||
});
|
||||
await request(app).get(`/api/companies/${companyId}/issues?view=compact`).expect(200);
|
||||
await request(app).get(`/api/issues/${sourceIssueId}/recovery-actions`).expect(200);
|
||||
await recovery.reconcileStrandedAssignedIssues();
|
||||
const actions = await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.sourceIssueId, sourceIssueId));
|
||||
expect(actions).toHaveLength(1);
|
||||
firstAction ??= actions[0]!;
|
||||
expect(actions[0]).toEqual(firstAction);
|
||||
}
|
||||
expect(copyback).not.toHaveBeenCalled();
|
||||
expect(await issueService(db).list(companyId, { touchedByUserId: userId })).toEqual([]);
|
||||
expect(await db.select().from(activityLog).where(eq(activityLog.entityId, sourceIssueId))).toHaveLength(0);
|
||||
|
||||
// Only clearing the exact physical owner receipt makes this hold stale.
|
||||
await db.update(heartbeatRuns).set({ runnerProfileJson: {} }).where(eq(heartbeatRuns.id, runId));
|
||||
const cleared = await request(app).get(`/api/issues/${sourceIssueId}`).expect(200);
|
||||
expect(cleared.body.activeRecoveryAction).toBeNull();
|
||||
const rows = await db.select().from(activityLog).where(eq(activityLog.entityId, sourceIssueId));
|
||||
expect(rows).toEqual([expect.objectContaining({ actorType: "system", actorId: "recovery", action: "issue.recovery_action_resolved" })]);
|
||||
expect(await issueService(db).list(companyId, { touchedByUserId: userId })).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps active recovery visible when a plain comment does not create a live path", async () => {
|
||||
|
||||
@@ -668,6 +668,39 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("ignores historical reader-attributed recovery cleanup without hiding explicit recovery decisions", async () => {
|
||||
const companyId = await seedAssignableAgentCompany();
|
||||
const userId = "board-user";
|
||||
const old = new Date("2026-03-26T10:00:00Z");
|
||||
const recent = new Date("2026-03-26T15:00:00Z");
|
||||
const variants = [
|
||||
{ id: randomUUID(), details: { source: "source_revalidation", trigger: "read_projection" }, counts: false },
|
||||
{ id: randomUUID(), details: null, counts: true },
|
||||
{ id: randomUUID(), details: { outcome: "restored" }, counts: true },
|
||||
{ id: randomUUID(), details: { source: "source_revalidation", trigger: "issue_update" }, counts: true },
|
||||
];
|
||||
await db.insert(issues).values(variants.map(({ id }) => ({
|
||||
id, companyId, title: "Recovery audit", status: "done", updatedAt: old,
|
||||
})));
|
||||
await db.insert(activityLog).values(variants.map(({ id, details }) => ({
|
||||
companyId, actorType: "user", actorId: userId, action: "issue.recovery_action_resolved",
|
||||
entityType: "issue", entityId: id, details, createdAt: recent,
|
||||
})));
|
||||
await db.insert(issueComments).values(variants.map(({ id }) => ({
|
||||
companyId, issueId: id, authorUserId: "other-user", body: "An older update", createdAt: old,
|
||||
})));
|
||||
|
||||
const expectedIds = variants.filter(v => v.counts).map(v => v.id).sort();
|
||||
expect((await svc.list(companyId, { touchedByUserId: userId })).map(v => v.id).sort()).toEqual(expectedIds);
|
||||
expect(await svc.countUnreadTouchedByUser(companyId, userId)).toBe(expectedIds.length);
|
||||
const rows = await svc.list(companyId, {});
|
||||
for (const variant of variants) {
|
||||
expect(rows.find(v => v.id === variant.id)?.lastActivityAt).toEqual(variant.counts ? recent : old);
|
||||
}
|
||||
// Audit evidence is preserved, including the wrongly attributed old row.
|
||||
expect(await db.select().from(activityLog).where(eq(activityLog.companyId, companyId))).toHaveLength(variants.length);
|
||||
});
|
||||
|
||||
function agentRow(companyId: string, input: {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { parseObject } from "../adapters/utils.js";
|
||||
import { enqueueFastResponse } from "../services/fast-responses.js";
|
||||
import { hasRequiredWorkspaceRecovery, LEGACY_WORKSPACE_RECOVERY_SCHEMA } from "../services/workspace-restore-recovery-state.js";
|
||||
import { hasUnverifiedNativeWorkspaceFinalizationOwner } from "../services/native-runtime/native-workspace-finalization-recovery-state.js";
|
||||
import { monitorPoliciesEqual, applyActorMonitorScheduledBy, assertCanManageIssueMonitor, summarizeIssueMonitor } from "../services/issue-monitors.js";
|
||||
import type { IssuePrivacyConstraints } from "@paperclipai/shared";
|
||||
import { canActorReadHeartbeatRun } from "../services/heartbeat-run-privacy.js";
|
||||
@@ -4344,6 +4345,10 @@ export function issueRoutes(
|
||||
: input.activeRecoveryAction;
|
||||
if (!activeRecoveryAction) return null;
|
||||
|
||||
if (await hasUnverifiedNativeWorkspaceFinalizationOwner(db, activeRecoveryAction)) {
|
||||
return activeRecoveryAction;
|
||||
}
|
||||
|
||||
const resolutionNote = await classifySourceRecoveryRevalidation(input);
|
||||
if (!resolutionNote) return activeRecoveryAction;
|
||||
|
||||
@@ -4357,13 +4362,12 @@ export function issueRoutes(
|
||||
});
|
||||
if (!resolved) return activeRecoveryAction;
|
||||
|
||||
const actor = input.actor;
|
||||
await logActivity(db, {
|
||||
companyId: input.issue.companyId,
|
||||
actorType: actor?.actorType ?? "system",
|
||||
actorId: actor?.actorId ?? "system",
|
||||
agentId: actor?.agentId ?? null,
|
||||
runId: actor?.runId ?? null,
|
||||
actorType: "system",
|
||||
actorId: "recovery",
|
||||
agentId: null,
|
||||
runId: input.actor?.runId ?? null,
|
||||
action: "issue.recovery_action_resolved",
|
||||
entityType: "issue",
|
||||
entityId: input.issue.id,
|
||||
|
||||
@@ -2842,6 +2842,16 @@ const ISSUE_USER_PARTICIPATION_ACTIVITY_ACTIONS = [
|
||||
"issue.work_product_updated",
|
||||
] as const;
|
||||
|
||||
// Old read projections attributed this automatic cleanup to the reader. Keep
|
||||
// the audit rows, but do not count them as participation or issue activity.
|
||||
function automaticReadRecoveryCleanupCondition() {
|
||||
return sql<boolean>`(
|
||||
${activityLog.action} = 'issue.recovery_action_resolved'
|
||||
AND COALESCE(${activityLog.details}->>'source', '') = 'source_revalidation'
|
||||
AND COALESCE(${activityLog.details}->>'trigger', '') = 'read_projection'
|
||||
)`;
|
||||
}
|
||||
|
||||
function touchedByUserCondition(companyId: string, userId: string) {
|
||||
return sql<boolean>`
|
||||
(
|
||||
@@ -2855,6 +2865,7 @@ function touchedByUserCondition(companyId: string, userId: string) {
|
||||
AND ${activityLog.companyId} = ${companyId}
|
||||
AND ${activityLog.actorType} = 'user'
|
||||
AND ${activityLog.actorId} = ${userId}
|
||||
AND NOT ${automaticReadRecoveryCleanupCondition()}
|
||||
AND ${activityLog.action} IN (${sql.join(
|
||||
ISSUE_USER_PARTICIPATION_ACTIVITY_ACTIONS.map(
|
||||
(action) => sql`${action}`,
|
||||
@@ -2960,6 +2971,7 @@ function issueLatestLogAtExpr(companyId: string) {
|
||||
WHERE ${activityLog.companyId} = ${companyId}
|
||||
AND ${activityLog.entityType} = 'issue'
|
||||
AND ${activityLog.entityId} = ${issues.id}::text
|
||||
AND NOT ${automaticReadRecoveryCleanupCondition()}
|
||||
AND ${activityLog.action} NOT IN (${sql.join(
|
||||
ISSUE_LOCAL_INBOX_ACTIVITY_ACTIONS.map((action) => sql`${action}`),
|
||||
sql`, `,
|
||||
@@ -5138,6 +5150,7 @@ async function lastActivityStatsForIssues(
|
||||
eq(activityLog.companyId, companyId),
|
||||
eq(activityLog.entityType, "issue"),
|
||||
inArray(activityLog.entityId, issueIdChunk),
|
||||
sql`NOT ${automaticReadRecoveryCleanupCondition()}`,
|
||||
sql`${activityLog.action} NOT IN (${sql.join(
|
||||
ISSUE_LOCAL_INBOX_ACTIVITY_ACTIONS.map(
|
||||
(action) => sql`${action}`,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { issueRecoveryActionService } from "../issue-recovery-actions.js";
|
||||
import { nativeSha256 } from "./canonical.js";
|
||||
import { NATIVE_WORKSPACE_OWNER_UNVERIFIED } from "./native-workspace-finalization-recovery-state.js";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import os from "node:os";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
@@ -74,11 +75,18 @@ export async function withNativeWorkspaceFinalizationOwnership<T>(
|
||||
const prior = readOwner(rawPrior);
|
||||
if (rawPrior != null) {
|
||||
const requireStopVerification = async () => {
|
||||
if (run.issueId) await issueRecoveryActionService(input.db).upsertSourceScoped({
|
||||
const fingerprint = nativeSha256({ runId: input.runId, owner: rawPrior });
|
||||
const recovery = issueRecoveryActionService(input.db);
|
||||
const existing = run.issueId ? await recovery.getActiveForIssue(input.companyId, run.issueId) : null;
|
||||
// Rechecking the same physical owner is not a new recovery attempt.
|
||||
if (existing?.cause === NATIVE_WORKSPACE_OWNER_UNVERIFIED && existing.fingerprint === fingerprint) {
|
||||
return { acquired: false } as const;
|
||||
}
|
||||
if (run.issueId) await recovery.upsertSourceScoped({
|
||||
companyId: input.companyId, sourceIssueId: run.issueId,
|
||||
kind: "active_run_watchdog", ownerType: "board", returnOwnerAgentId: run.agentId,
|
||||
cause: "native_workspace_finalization_owner_unverified",
|
||||
fingerprint: nativeSha256({ runId: input.runId, owner: rawPrior }),
|
||||
cause: NATIVE_WORKSPACE_OWNER_UNVERIFIED,
|
||||
fingerprint,
|
||||
evidence: { runId: input.runId, owner: rawPrior },
|
||||
nextAction: "Verify the previous controller and its workspace-copyback processes have stopped, then release only this exact workspace owner receipt using doc/native-workspace-finalization-recovery.md. Resume workspace finalization without another provider turn.",
|
||||
wakePolicy: null, maxAttempts: 1, supersedeOnIdentityChange: true,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { heartbeatRuns, type Db } from "@paperclipai/db";
|
||||
import { isUuidLike, type IssueRecoveryAction } from "@paperclipai/shared";
|
||||
import { nativeSha256 } from "./canonical.js";
|
||||
|
||||
export const NATIVE_WORKSPACE_OWNER_UNVERIFIED = "native_workspace_finalization_owner_unverified";
|
||||
|
||||
/** A terminal task does not prove that an older controller's copyback stopped. */
|
||||
export async function hasUnverifiedNativeWorkspaceFinalizationOwner(
|
||||
db: Db,
|
||||
action: Pick<IssueRecoveryAction, "companyId" | "sourceIssueId" | "cause" | "fingerprint" | "evidence">,
|
||||
): Promise<boolean> {
|
||||
if (action.cause !== NATIVE_WORKSPACE_OWNER_UNVERIFIED) return false;
|
||||
const runId = action.evidence?.runId;
|
||||
if (typeof runId !== "string" || !isUuidLike(runId)) return false;
|
||||
const [run] = await db.select({ profile: heartbeatRuns.runnerProfileJson }).from(heartbeatRuns).where(and(
|
||||
eq(heartbeatRuns.companyId, action.companyId),
|
||||
eq(heartbeatRuns.id, runId),
|
||||
eq(heartbeatRuns.nativeIssueId, action.sourceIssueId),
|
||||
eq(heartbeatRuns.runtimeMode, "native"),
|
||||
)).limit(1);
|
||||
const owner = run?.profile?.nativeWorkspaceFinalizationOwner;
|
||||
return owner != null && action.fingerprint === nativeSha256({ runId, owner });
|
||||
}
|
||||
@@ -93,6 +93,7 @@ import { emitAgentTaskRun } from "../agent-task-run-telemetry.js";
|
||||
import { budgetService } from "../budgets.js";
|
||||
import { unadmittedChatWakeupCondition } from "../durable-chat-wakeup.js";
|
||||
import { issueRecoveryActionService } from "../issue-recovery-actions.js";
|
||||
import { hasUnverifiedNativeWorkspaceFinalizationOwner } from "../native-runtime/native-workspace-finalization-recovery-state.js";
|
||||
import {
|
||||
legacyExecutionNeedsReconciliationWithEvidence,
|
||||
terminalizeLegacyExecution,
|
||||
@@ -3428,6 +3429,10 @@ export function recoveryService(
|
||||
issueIds: [] as string[],
|
||||
};
|
||||
for (const { action, issue } of rows) {
|
||||
if (await hasUnverifiedNativeWorkspaceFinalizationOwner(db, action)) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
const wakePolicy = parseObject(action.wakePolicy);
|
||||
const wakePolicyType = readNonEmptyString(wakePolicy.type);
|
||||
if (
|
||||
|
||||
@@ -131,6 +131,8 @@ If already checked out by you, returns normally. If owned by another agent: `409
|
||||
|
||||
**Step 6 — Understand context.** Prefer `GET /api/issues/{issueId}/heartbeat-context` first. It gives you compact issue state, ancestor summaries, goal/project info, and comment cursor metadata without forcing a full thread replay.
|
||||
|
||||
For attribution questions, read `GET /api/issues/{issueId}/activity`. Sandbox agents can use this issue-scoped route subject to the same company and issue-read authorization. Automatic recovery cleanup is system activity, not evidence that the person viewing a task participated in it.
|
||||
|
||||
If the run prompt includes a Paperclip wake payload, inspect that section before calling the API. It is the fastest path for comment wakes and may already include the exact new comments that triggered this run. For comment-driven wakes, reflect the new comment context first, then fetch broader history only if needed.
|
||||
|
||||
Use comments incrementally:
|
||||
@@ -708,6 +710,7 @@ If `plan` already exists, first `GET /api/issues/{issueId}/documents/plan` and r
|
||||
| Checkout task | `POST /api/issues/:issueId/checkout` |
|
||||
| Get task + ancestors | `GET /api/issues/:issueId` |
|
||||
| Compact heartbeat context | `GET /api/issues/:issueId/heartbeat-context` |
|
||||
| Issue activity and attribution | `GET /api/issues/:issueId/activity` |
|
||||
| Update task | `PATCH /api/issues/:issueId` (optional `comment` field) |
|
||||
| Get comments / delta / single | `GET /api/issues/:issueId/comments[?after=:commentId&order=asc]` • `/comments/:commentId` |
|
||||
| Add comment | `POST /api/issues/:issueId/comments` |
|
||||
|
||||
@@ -107,6 +107,12 @@ POST /api/companies/company-1/exports
|
||||
}
|
||||
```
|
||||
|
||||
**Issue Activity (`GET /api/issues/:issueId/activity`)**
|
||||
|
||||
Returns the issue's activity history, including actor, action, timestamp, and sanitized details. Sandbox agents can call this read-only route for issues their identity may read. Company boundaries and issue-read authorization still apply; this does not grant company-wide audit access.
|
||||
|
||||
An automatic stale-recovery cancellation has `action: "issue.recovery_action_resolved"` and `details.source: "source_revalidation"`. Page reads use `details.trigger: "read_projection"`. These cancellations belong to the system. Historical reader-attributed rows with that source and trigger do not count as inbox participation or recent issue activity.
|
||||
|
||||
### Issue with Ancestors (`GET /api/issues/:issueId`)
|
||||
|
||||
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.
|
||||
|
||||
Reference in new issue
Block a user