From 83e13db8fa52b40fea34534f7237c231d0874c23 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Mon, 12 Oct 2026 19:25:15 -0700 Subject: [PATCH 1/4] 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 at f5346c05b923a1eb1d15d04d8e08b993a7c4d097. 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 commit 44c3787888c2efb689543c9f0ca3345175df7bb4. 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 Co-authored-by: Amy (Opus) --- doc/native-workspace-finalization-recovery.md | 9 + maps/agent-access-map/activity.md | 32 +- maps/agent-access-map/inbox-search.md | 15 +- maps/agent-access-map/recovery.md | 16 +- maps/feature-map/activity.md | 6 + maps/feature-map/inbox-search.md | 11 + maps/feature-map/recovery.md | 8 + .../src/sandbox-callback-bridge.test.ts | 5 + .../src/sandbox-callback-bridge.ts | 2 + .../docs/capability-contract.md | 188 ++--- .../generated/capability/capabilities.yaml | 404 +++++------ .../capability/capability-contract.md | 2 +- .../spec/capability/capabilities.yaml | 686 +++++++++--------- server/src/__tests__/activity-routes.test.ts | 27 + .../heartbeat-process-recovery.test.ts | 9 +- .../__tests__/issue-recovery-actions.test.ts | 47 ++ server/src/__tests__/issues-service.test.ts | 33 + server/src/routes/issues.ts | 14 +- server/src/services/issues.ts | 13 + ...native-workspace-finalization-ownership.ts | 14 +- ...e-workspace-finalization-recovery-state.ts | 24 + server/src/services/recovery/service.ts | 5 + skills/paperclip/SKILL.md | 3 + skills/paperclip/references/api-reference.md | 6 + 24 files changed, 913 insertions(+), 666 deletions(-) create mode 100644 server/src/services/native-runtime/native-workspace-finalization-recovery-state.ts diff --git a/doc/native-workspace-finalization-recovery.md b/doc/native-workspace-finalization-recovery.md index 2be7f1b67db..01201315417 100644 --- a/doc/native-workspace-finalization-recovery.md +++ b/doc/native-workspace-finalization-recovery.md @@ -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 diff --git a/maps/agent-access-map/activity.md b/maps/agent-access-map/activity.md index b5d2041289c..40d61378fdd 100644 --- a/maps/agent-access-map/activity.md +++ b/maps/agent-access-map/activity.md @@ -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` diff --git a/maps/agent-access-map/inbox-search.md b/maps/agent-access-map/inbox-search.md index 2c079231dd9..84ada459d12 100644 --- a/maps/agent-access-map/inbox-search.md +++ b/maps/agent-access-map/inbox-search.md @@ -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. diff --git a/maps/agent-access-map/recovery.md b/maps/agent-access-map/recovery.md index a6a12a3f1c0..db98fb8ef76 100644 --- a/maps/agent-access-map/recovery.md +++ b/maps/agent-access-map/recovery.md @@ -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. diff --git a/maps/feature-map/activity.md b/maps/feature-map/activity.md index 0a80901ddfa..49f9489906f 100644 --- a/maps/feature-map/activity.md +++ b/maps/feature-map/activity.md @@ -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 diff --git a/maps/feature-map/inbox-search.md b/maps/feature-map/inbox-search.md index ac3570b5c38..14df9295962 100644 --- a/maps/feature-map/inbox-search.md +++ b/maps/feature-map/inbox-search.md @@ -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. diff --git a/maps/feature-map/recovery.md b/maps/feature-map/recovery.md index 0236b1e4dec..3cd71abaffc 100644 --- a/maps/feature-map/recovery.md +++ b/maps/feature-map/recovery.md @@ -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) diff --git a/packages/adapter-utils/src/sandbox-callback-bridge.test.ts b/packages/adapter-utils/src/sandbox-callback-bridge.test.ts index c81f162ea32..e7e639bd0a8 100644 --- a/packages/adapter-utils/src/sandbox-callback-bridge.test.ts +++ b/packages/adapter-utils/src/sandbox-callback-bridge.test.ts @@ -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" }, diff --git a/packages/adapter-utils/src/sandbox-callback-bridge.ts b/packages/adapter-utils/src/sandbox-callback-bridge.ts index 64bf04a3940..6a392f51842 100644 --- a/packages/adapter-utils/src/sandbox-callback-bridge.ts +++ b/packages/adapter-utils/src/sandbox-callback-bridge.ts @@ -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(?:\/[^/]+)?$/ }, diff --git a/packages/paperclip-runner/docs/capability-contract.md b/packages/paperclip-runner/docs/capability-contract.md index ba41e93259b..1f71df88072 100644 --- a/packages/paperclip-runner/docs/capability-contract.md +++ b/packages/paperclip-runner/docs/capability-contract.md @@ -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 | diff --git a/packages/paperclip-runner/generated/capability/capabilities.yaml b/packages/paperclip-runner/generated/capability/capabilities.yaml index 4c0ccdb432f..8814ba1da39 100644 --- a/packages/paperclip-runner/generated/capability/capabilities.yaml +++ b/packages/paperclip-runner/generated/capability/capabilities.yaml @@ -56,252 +56,252 @@ "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:198", + "id": "skill:skills/paperclip/SKILL.md:200", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L198:generated-artifacts-and-work-products", + "sourceAnchor": "skills/paperclip/SKILL.md#L200:generated-artifacts-and-work-products", "heading": "Generated Artifacts and Work Products", "primaryDisposition": "always_agent_tool", "semanticOperation": "register_deliverable", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:246", + "id": "skill:skills/paperclip/SKILL.md:248", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L246:status-quick-guide", + "sourceAnchor": "skills/paperclip/SKILL.md#L248:status-quick-guide", "heading": "Status Quick Guide", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:256", + "id": "skill:skills/paperclip/SKILL.md:258", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L256:monitors-and-watchers-say-only-what-you-actually-scheduled", + "sourceAnchor": "skills/paperclip/SKILL.md#L258:monitors-and-watchers-say-only-what-you-actually-scheduled", "heading": "Monitors and Watchers (say only what you actually scheduled)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:269", + "id": "skill:skills/paperclip/SKILL.md:271", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L269:delegating-review-tasks", + "sourceAnchor": "skills/paperclip/SKILL.md#L271:delegating-review-tasks", "heading": "Delegating review tasks", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:280", + "id": "skill:skills/paperclip/SKILL.md:282", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L280:managing-a-user-s-inbox", + "sourceAnchor": "skills/paperclip/SKILL.md#L282:managing-a-user-s-inbox", "heading": "Managing A User's Inbox", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:288", + "id": "skill:skills/paperclip/SKILL.md:290", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L288:issue-dependencies-blockers", + "sourceAnchor": "skills/paperclip/SKILL.md#L290:issue-dependencies-blockers", "heading": "Issue Dependencies (Blockers)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:313", + "id": "skill:skills/paperclip/SKILL.md:315", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L313:requesting-board-approval", + "sourceAnchor": "skills/paperclip/SKILL.md#L315:requesting-board-approval", "heading": "Requesting Board Approval", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:334", + "id": "skill:skills/paperclip/SKILL.md:336", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L334:issue-thread-interactions", + "sourceAnchor": "skills/paperclip/SKILL.md#L336:issue-thread-interactions", "heading": "Issue-Thread Interactions", "primaryDisposition": "always_agent_tool", "semanticOperation": "request_human_input", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:364", + "id": "skill:skills/paperclip/SKILL.md:366", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L364:standalone-decisions", + "sourceAnchor": "skills/paperclip/SKILL.md#L366:standalone-decisions", "heading": "Standalone Decisions", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:468", + "id": "skill:skills/paperclip/SKILL.md:470", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L468:mcp-tool-approval-gates", + "sourceAnchor": "skills/paperclip/SKILL.md#L470:mcp-tool-approval-gates", "heading": "MCP Tool Approval Gates", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:510", + "id": "skill:skills/paperclip/SKILL.md:512", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L510:niche-workflow-pointers", + "sourceAnchor": "skills/paperclip/SKILL.md#L512:niche-workflow-pointers", "heading": "Niche Workflow Pointers", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:520", + "id": "skill:skills/paperclip/SKILL.md:522", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L520:cases", + "sourceAnchor": "skills/paperclip/SKILL.md#L522:cases", "heading": "Cases", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:525", + "id": "skill:skills/paperclip/SKILL.md:527", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L525:company-skills-workflow", + "sourceAnchor": "skills/paperclip/SKILL.md#L527:company-skills-workflow", "heading": "Company Skills Workflow", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:536", + "id": "skill:skills/paperclip/SKILL.md:538", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L536:routines", + "sourceAnchor": "skills/paperclip/SKILL.md#L538:routines", "heading": "Routines", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:547", + "id": "skill:skills/paperclip/SKILL.md:549", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L547:task-files-and-workspace-choices", + "sourceAnchor": "skills/paperclip/SKILL.md#L549:task-files-and-workspace-choices", "heading": "Task Files and Workspace Choices", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:553", + "id": "skill:skills/paperclip/SKILL.md:555", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L553:issue-workspace-runtime-controls", + "sourceAnchor": "skills/paperclip/SKILL.md#L555:issue-workspace-runtime-controls", "heading": "Issue Workspace Runtime Controls", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:560", + "id": "skill:skills/paperclip/SKILL.md:562", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L560:proposing-credentials-safely", + "sourceAnchor": "skills/paperclip/SKILL.md#L562:proposing-credentials-safely", "heading": "Proposing Credentials Safely", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:567", + "id": "skill:skills/paperclip/SKILL.md:569", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L567:reading-granted-secrets", + "sourceAnchor": "skills/paperclip/SKILL.md#L569:reading-granted-secrets", "heading": "Reading Granted Secrets", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:593", + "id": "skill:skills/paperclip/SKILL.md:595", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L593:critical-rules", + "sourceAnchor": "skills/paperclip/SKILL.md#L595:critical-rules", "heading": "Critical Rules", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:614", + "id": "skill:skills/paperclip/SKILL.md:616", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L614:comment-style-required", + "sourceAnchor": "skills/paperclip/SKILL.md#L616:comment-style-required", "heading": "Comment Style (Required)", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:646", + "id": "skill:skills/paperclip/SKILL.md:648", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L646:update", + "sourceAnchor": "skills/paperclip/SKILL.md#L648:update", "heading": "Update", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:656", + "id": "skill:skills/paperclip/SKILL.md:658", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L656:task-documents-and-deliverables", + "sourceAnchor": "skills/paperclip/SKILL.md#L658:task-documents-and-deliverables", "heading": "Task Documents and Deliverables", "primaryDisposition": "always_agent_tool", "semanticOperation": "write_document", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:670", + "id": "skill:skills/paperclip/SKILL.md:672", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L670:planning-required-when-planning-requested", + "sourceAnchor": "skills/paperclip/SKILL.md#L672:planning-required-when-planning-requested", "heading": "Planning (Required when planning requested)", "primaryDisposition": "always_agent_tool", "semanticOperation": "write_document", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:701", + "id": "skill:skills/paperclip/SKILL.md:703", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L701:key-endpoints-hot-routes", + "sourceAnchor": "skills/paperclip/SKILL.md#L703:key-endpoints-hot-routes", "heading": "Key Endpoints (Hot Routes)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:730", + "id": "skill:skills/paperclip/SKILL.md:733", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L730:searching-issues", + "sourceAnchor": "skills/paperclip/SKILL.md#L733:searching-issues", "heading": "Searching Issues", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:740", + "id": "skill:skills/paperclip/SKILL.md:743", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L740:full-reference", + "sourceAnchor": "skills/paperclip/SKILL.md#L743:full-reference", "heading": "Full Reference", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:744", + "id": "skill:skills/paperclip/SKILL.md:747", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L744:conversational-confirmation-answers", + "sourceAnchor": "skills/paperclip/SKILL.md#L747:conversational-confirmation-answers", "heading": "Conversational confirmation answers", "primaryDisposition": "always_agent_tool", "semanticOperation": "call_api", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:761", + "id": "skill:skills/paperclip/SKILL.md:764", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L761:incidental-feedback", + "sourceAnchor": "skills/paperclip/SKILL.md#L764:incidental-feedback", "heading": "Incidental feedback", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", @@ -344,603 +344,603 @@ "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:110", + "id": "skill:skills/paperclip/references/api-reference.md:116", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L110:issue-with-ancestors-get-api-issues-issueid", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L116:issue-with-ancestors-get-api-issues-issueid", "heading": "Issue with Ancestors (`GET /api/issues/:issueId`)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:196", + "id": "skill:skills/paperclip/references/api-reference.md:202", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L196:issue-update-response-patch-api-issues-issueid", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L202:issue-update-response-patch-api-issues-issueid", "heading": "Issue Update Response (`PATCH /api/issues/:issueId`)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:238", + "id": "skill:skills/paperclip/references/api-reference.md:244", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L238:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L244:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers", "heading": "Blocker Diagnostics (`GET /api/issues/:issueId/diagnostics/blockers`)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:277", + "id": "skill:skills/paperclip/references/api-reference.md:283", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L277:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L283:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes", "heading": "Wake Diagnostics (`GET /api/issues/:issueId/diagnostics/wakes`)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:321", + "id": "skill:skills/paperclip/references/api-reference.md:327", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L321:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L327:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree", "heading": "Subtree Diagnostics (`GET /api/issues/:issueId/diagnostics/subtree`)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:369", + "id": "skill:skills/paperclip/references/api-reference.md:375", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L369:execution-policy-fields-on-an-issue", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L375:execution-policy-fields-on-an-issue", "heading": "Execution Policy Fields On An Issue", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:421", + "id": "skill:skills/paperclip/references/api-reference.md:427", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L421:cross-agent-review-gates", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L427:cross-agent-review-gates", "heading": "Cross-Agent Review Gates", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:454", + "id": "skill:skills/paperclip/references/api-reference.md:460", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L454:worked-example-ic-heartbeat", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L460:worked-example-ic-heartbeat", "heading": "Worked Example: IC Heartbeat", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:459", + "id": "skill:skills/paperclip/references/api-reference.md:465", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L459:1-identity-skip-if-already-in-context", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L465:1-identity-skip-if-already-in-context", "heading": "1. Identity (skip if already in context)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:463", + "id": "skill:skills/paperclip/references/api-reference.md:469", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L463:2-check-inbox", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L469:2-check-inbox", "heading": "2. Check inbox", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:470", + "id": "skill:skills/paperclip/references/api-reference.md:476", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L470:3-already-have-issue-101-in-progress-highest-priority-continue-it", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L476:3-already-have-issue-101-in-progress-highest-priority-continue-it", "heading": "3. Already have issue-101 in_progress (highest priority). Continue it.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:477", + "id": "skill:skills/paperclip/references/api-reference.md:483", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L477:4-do-the-actual-work-write-code-run-tests", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L483:4-do-the-actual-work-write-code-run-tests", "heading": "4. Do the actual work (write code, run tests)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:479", + "id": "skill:skills/paperclip/references/api-reference.md:485", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L479:5-work-is-done-update-status-and-comment-in-one-call", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L485:5-work-is-done-update-status-and-comment-in-one-call", "heading": "5. Work is done. Update status and comment in one call.", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:483", + "id": "skill:skills/paperclip/references/api-reference.md:489", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L483:6-still-have-time-checkout-the-next-task", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L489:6-still-have-time-checkout-the-next-task", "heading": "6. Still have time. Checkout the next task.", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:490", + "id": "skill:skills/paperclip/references/api-reference.md:496", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L490:7-made-partial-progress-not-done-yet-comment-and-exit", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L496:7-made-partial-progress-not-done-yet-comment-and-exit", "heading": "7. Made partial progress, not done yet. Comment and exit.", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:495", + "id": "skill:skills/paperclip/references/api-reference.md:501", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L495:worked-example-report-a-board-user-s-mine-inbox", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L501:worked-example-report-a-board-user-s-mine-inbox", "heading": "Worked Example: Report A Board User's Mine Inbox", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:500", + "id": "skill:skills/paperclip/references/api-reference.md:506", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L500:board-user-created-the-requesting-issue", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L506:board-user-created-the-requesting-issue", "heading": "Board user created the requesting issue.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:504", + "id": "skill:skills/paperclip/references/api-reference.md:510", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L504:fetch-the-board-user-s-mine-inbox-issues", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L510:fetch-the-board-user-s-mine-inbox-issues", "heading": "Fetch the board user's Mine inbox issues.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:518", + "id": "skill:skills/paperclip/references/api-reference.md:524", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L518:summarize-it-back-to-the-board-in-a-comment-or-document", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L524:summarize-it-back-to-the-board-in-a-comment-or-document", "heading": "Summarize it back to the board in a comment or document.", "primaryDisposition": "always_agent_tool", "semanticOperation": "write_document", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:523", + "id": "skill:skills/paperclip/references/api-reference.md:529", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L523:worked-example-archive-a-resolved-inbox-item", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L529:worked-example-archive-a-resolved-inbox-item", "heading": "Worked Example: Archive A Resolved Inbox Item", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:528", + "id": "skill:skills/paperclip/references/api-reference.md:534", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L528:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L534:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run", "heading": "The responsible user's id is resolved from the authenticated agent run.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:537", + "id": "skill:skills/paperclip/references/api-reference.md:543", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L537:reverse-the-archive-if-it-was-premature-or-no-longer-desired", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L543:reverse-the-archive-if-it-was-premature-or-no-longer-desired", "heading": "Reverse the archive if it was premature or no longer desired.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:547", + "id": "skill:skills/paperclip/references/api-reference.md:553", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L547:worked-example-reviewer-approver-heartbeat", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L553:worked-example-reviewer-approver-heartbeat", "heading": "Worked Example: Reviewer / Approver Heartbeat", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:586", + "id": "skill:skills/paperclip/references/api-reference.md:592", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L586:worked-example-manager-heartbeat", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L592:worked-example-manager-heartbeat", "heading": "Worked Example: Manager Heartbeat", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:589", + "id": "skill:skills/paperclip/references/api-reference.md:595", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L589:1-identity-skip-if-already-in-context", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L595:1-identity-skip-if-already-in-context", "heading": "1. Identity (skip if already in context)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:593", + "id": "skill:skills/paperclip/references/api-reference.md:599", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L593:2-check-team-status", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L599:2-check-team-status", "heading": "2. Check team status", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:600", + "id": "skill:skills/paperclip/references/api-reference.md:606", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L600:3-agent-42-is-blocked-read-comments", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L606:3-agent-42-is-blocked-read-comments", "heading": "3. Agent-42 is blocked. Read comments.", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:604", + "id": "skill:skills/paperclip/references/api-reference.md:610", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L604:4-unblock-reassign-and-comment", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L610:4-unblock-reassign-and-comment", "heading": "4. Unblock: reassign and comment.", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:608", + "id": "skill:skills/paperclip/references/api-reference.md:614", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L608:5-check-own-assignments", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L614:5-check-own-assignments", "heading": "5. Check own assignments.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, - { - "id": "skill:skills/paperclip/references/api-reference.md:615", - "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L615:6-create-subtasks-and-delegate", - "heading": "6. Create subtasks and delegate.", - "primaryDisposition": "optional_agent_tool", - "semanticOperation": "scoped_discovery", - "expectedMockState": "operation_result" - }, { "id": "skill:skills/paperclip/references/api-reference.md:621", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L621:load-tests-depend-on-caching-layer-being-done-first-paperclip-will-auto-wake-agent-55-when-the-blocker-resolves", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L621:6-create-subtasks-and-delegate", + "heading": "6. Create subtasks and delegate.", + "primaryDisposition": "optional_agent_tool", + "semanticOperation": "scoped_discovery", + "expectedMockState": "operation_result" + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:627", + "kind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L627:load-tests-depend-on-caching-layer-being-done-first-paperclip-will-auto-wake-agent-55-when-the-blocker-resolves", "heading": "^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:626", + "id": "skill:skills/paperclip/references/api-reference.md:632", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L626:7-dashboard-for-health-check", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L632:7-dashboard-for-health-check", "heading": "7. Dashboard for health check.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:632", + "id": "skill:skills/paperclip/references/api-reference.md:638", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L632:comments-and-mentions", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L638:comments-and-mentions", "heading": "Comments and @-mentions", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:639", + "id": "skill:skills/paperclip/references/api-reference.md:645", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L639:update", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L645:update", "heading": "Update", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:661", + "id": "skill:skills/paperclip/references/api-reference.md:667", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L661:cross-team-work-and-delegation", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L667:cross-team-work-and-delegation", "heading": "Cross-Team Work and Delegation", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:665", + "id": "skill:skills/paperclip/references/api-reference.md:671", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L665:receiving-cross-team-work", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L671:receiving-cross-team-work", "heading": "Receiving cross-team work", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:675", + "id": "skill:skills/paperclip/references/api-reference.md:681", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L675:questions-and-dependencies", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L681:questions-and-dependencies", "heading": "Questions and dependencies", "primaryDisposition": "always_agent_tool", "semanticOperation": "request_human_input", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:687", + "id": "skill:skills/paperclip/references/api-reference.md:693", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L687:company-context", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L693:company-context", "heading": "Company Context", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:699", + "id": "skill:skills/paperclip/references/api-reference.md:705", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L699:company-branding-ceo-board", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L705:company-branding-ceo-board", "heading": "Company Branding (CEO / Board)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:719", + "id": "skill:skills/paperclip/references/api-reference.md:725", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L719:openclaw-invite-prompt-ceo", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L725:openclaw-invite-prompt-ceo", "heading": "OpenClaw Invite Prompt (CEO)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:738", + "id": "skill:skills/paperclip/references/api-reference.md:744", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L738:setting-agent-instructions-path", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L744:setting-agent-instructions-path", "heading": "Setting Agent Instructions Path", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:771", + "id": "skill:skills/paperclip/references/api-reference.md:777", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L771:project-setup-create-workspace", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L777:project-setup-create-workspace", "heading": "Project Setup (Create + Workspace)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:795", + "id": "skill:skills/paperclip/references/api-reference.md:801", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L795:option-a-one-call-create-with-workspace", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L801:option-a-one-call-create-with-workspace", "heading": "Option A: One-call create with workspace", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:814", + "id": "skill:skills/paperclip/references/api-reference.md:820", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L814:option-b-two-calls-project-first-then-workspace", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L820:option-b-two-calls-project-first-then-workspace", "heading": "Option B: Two calls (project first, then workspace)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:843", + "id": "skill:skills/paperclip/references/api-reference.md:849", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L843:governance-and-approvals", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L849:governance-and-approvals", "heading": "Governance and Approvals", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:847", + "id": "skill:skills/paperclip/references/api-reference.md:853", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L847:requesting-a-hire-management-only", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L853:requesting-a-hire-management-only", "heading": "Requesting a hire (management only)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:912", + "id": "skill:skills/paperclip/references/api-reference.md:918", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L912:ceo-strategy-approval", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L918:ceo-strategy-approval", "heading": "CEO strategy approval", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:921", + "id": "skill:skills/paperclip/references/api-reference.md:927", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L921:questions-and-waiting-for-human-input", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L927:questions-and-waiting-for-human-input", "heading": "Questions and waiting for human input", "primaryDisposition": "always_agent_tool", "semanticOperation": "request_human_input", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1025", + "id": "skill:skills/paperclip/references/api-reference.md:1031", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1025:issue-thread-confirmations", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1031:issue-thread-confirmations", "heading": "Issue-thread confirmations", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1083", + "id": "skill:skills/paperclip/references/api-reference.md:1089", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1083:conversational-confirmation-answers", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1089:conversational-confirmation-answers", "heading": "Conversational confirmation answers", "primaryDisposition": "always_agent_tool", "semanticOperation": "call_api", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1089", + "id": "skill:skills/paperclip/references/api-reference.md:1095", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1089:checkbox-confirmations", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1095:checkbox-confirmations", "heading": "Checkbox confirmations", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1204", + "id": "skill:skills/paperclip/references/api-reference.md:1210", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1204:item-verdict-requests", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1210:item-verdict-requests", "heading": "Item verdict requests", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, - { - "id": "skill:skills/paperclip/references/api-reference.md:1314", - "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1314:checking-approval-status", - "heading": "Checking approval status", - "primaryDisposition": "optional_agent_tool", - "semanticOperation": "scoped_discovery", - "expectedMockState": "operation_result" - }, { "id": "skill:skills/paperclip/references/api-reference.md:1320", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1320:approval-follow-up-requesting-agent", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1320:checking-approval-status", + "heading": "Checking approval status", + "primaryDisposition": "optional_agent_tool", + "semanticOperation": "scoped_discovery", + "expectedMockState": "operation_result" + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:1326", + "kind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1326:approval-follow-up-requesting-agent", "heading": "Approval follow-up (requesting agent)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1338", + "id": "skill:skills/paperclip/references/api-reference.md:1344", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1338:issue-lifecycle", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1344:issue-lifecycle", "heading": "Issue Lifecycle", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1368", + "id": "skill:skills/paperclip/references/api-reference.md:1374", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1368:error-handling", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1374:error-handling", "heading": "Error Handling", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1382", + "id": "skill:skills/paperclip/references/api-reference.md:1388", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1382:full-api-reference", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1388:full-api-reference", "heading": "Full API Reference", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1384", + "id": "skill:skills/paperclip/references/api-reference.md:1390", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1384:agents", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1390:agents", "heading": "Agents", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1405", + "id": "skill:skills/paperclip/references/api-reference.md:1411", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1405:issues-tasks", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1411:issues-tasks", "heading": "Issues (Tasks)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1446", + "id": "skill:skills/paperclip/references/api-reference.md:1452", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1446:companies-projects-goals", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1452:companies-projects-goals", "heading": "Companies, Projects, Goals", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1470", + "id": "skill:skills/paperclip/references/api-reference.md:1476", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1470:routines", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1476:routines", "heading": "Routines", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1486", + "id": "skill:skills/paperclip/references/api-reference.md:1492", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1486:approvals-costs-activity-dashboard", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1492:approvals-costs-activity-dashboard", "heading": "Approvals, Costs, Activity, Dashboard", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1508", + "id": "skill:skills/paperclip/references/api-reference.md:1514", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1508:secrets", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1514:secrets", "heading": "Secrets", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1521", + "id": "skill:skills/paperclip/references/api-reference.md:1527", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1521:agent-secret-proposals", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1527:agent-secret-proposals", "heading": "Agent secret proposals", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1574", + "id": "skill:skills/paperclip/references/api-reference.md:1580", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1574:re-bind-an-existing-secret-under-a-new-path-no-secret-id", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1580:re-bind-an-existing-secret-under-a-new-path-no-secret-id", "heading": "Re-bind an existing secret under a new path (no secret ID)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1621", + "id": "skill:skills/paperclip/references/api-reference.md:1627", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1621:agent-secret-access", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1627:agent-secret-access", "heading": "Agent secret access", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1661", + "id": "skill:skills/paperclip/references/api-reference.md:1667", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1661:common-mistakes", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1667:common-mistakes", "heading": "Common Mistakes", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", diff --git a/packages/paperclip-runner/generated/capability/capability-contract.md b/packages/paperclip-runner/generated/capability/capability-contract.md index 6c6665b696a..d8493676bb2 100644 --- a/packages/paperclip-runner/generated/capability/capability-contract.md +++ b/packages/paperclip-runner/generated/capability/capability-contract.md @@ -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. diff --git a/packages/paperclip-runner/spec/capability/capabilities.yaml b/packages/paperclip-runner/spec/capability/capabilities.yaml index 7899ff444d0..a3bc4ddadac 100644 --- a/packages/paperclip-runner/spec/capability/capabilities.yaml +++ b/packages/paperclip-runner/spec/capability/capabilities.yaml @@ -105,9 +105,9 @@ ] }, { - "id": "skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:198", + "id": "skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:200", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:198", + "sourceAnchor": "skills/paperclip/SKILL.md:200", "title": "Generated Artifacts and Work Products", "expectedSemantics": "Skill guidance headed “Generated Artifacts and Work Products”.", "primaryDisposition": "always_agent_tool", @@ -116,13 +116,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:198" + "skill:skills/paperclip/SKILL.md:200" ] }, { - "id": "skill:skills/paperclip/SKILL.md:status-quick-guide:246", + "id": "skill:skills/paperclip/SKILL.md:status-quick-guide:248", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:246", + "sourceAnchor": "skills/paperclip/SKILL.md:248", "title": "Status Quick Guide", "expectedSemantics": "Skill guidance headed “Status Quick Guide”.", "primaryDisposition": "control_plane_owned", @@ -131,13 +131,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:246" + "skill:skills/paperclip/SKILL.md:248" ] }, { - "id": "skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:256", + "id": "skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:258", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:256", + "sourceAnchor": "skills/paperclip/SKILL.md:258", "title": "Monitors and Watchers (say only what you actually scheduled)", "expectedSemantics": "Skill guidance headed “Monitors and Watchers (say only what you actually scheduled)”.", "primaryDisposition": "optional_agent_tool", @@ -146,13 +146,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:256" + "skill:skills/paperclip/SKILL.md:258" ] }, { - "id": "skill:skills/paperclip/SKILL.md:delegating-review-tasks:269", + "id": "skill:skills/paperclip/SKILL.md:delegating-review-tasks:271", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:269", + "sourceAnchor": "skills/paperclip/SKILL.md:271", "title": "Delegating review tasks", "expectedSemantics": "Skill guidance headed “Delegating review tasks”.", "primaryDisposition": "always_agent_tool", @@ -161,13 +161,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:269" + "skill:skills/paperclip/SKILL.md:271" ] }, { - "id": "skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:280", + "id": "skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:282", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:280", + "sourceAnchor": "skills/paperclip/SKILL.md:282", "title": "Managing A User's Inbox", "expectedSemantics": "Skill guidance headed “Managing A User's Inbox”.", "primaryDisposition": "control_plane_owned", @@ -176,13 +176,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:280" + "skill:skills/paperclip/SKILL.md:282" ] }, { - "id": "skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:288", + "id": "skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:290", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:288", + "sourceAnchor": "skills/paperclip/SKILL.md:290", "title": "Issue Dependencies (Blockers)", "expectedSemantics": "Skill guidance headed “Issue Dependencies (Blockers)”.", "primaryDisposition": "control_plane_owned", @@ -191,13 +191,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:288" + "skill:skills/paperclip/SKILL.md:290" ] }, { - "id": "skill:skills/paperclip/SKILL.md:requesting-board-approval:313", + "id": "skill:skills/paperclip/SKILL.md:requesting-board-approval:315", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:313", + "sourceAnchor": "skills/paperclip/SKILL.md:315", "title": "Requesting Board Approval", "expectedSemantics": "Skill guidance headed “Requesting Board Approval”.", "primaryDisposition": "optional_agent_tool", @@ -206,13 +206,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:313" + "skill:skills/paperclip/SKILL.md:315" ] }, { - "id": "skill:skills/paperclip/SKILL.md:issue-thread-interactions:334", + "id": "skill:skills/paperclip/SKILL.md:issue-thread-interactions:336", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:334", + "sourceAnchor": "skills/paperclip/SKILL.md:336", "title": "Issue-Thread Interactions", "expectedSemantics": "Skill guidance headed “Issue-Thread Interactions”.", "primaryDisposition": "optional_agent_tool", @@ -221,13 +221,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:334" + "skill:skills/paperclip/SKILL.md:336" ] }, { - "id": "skill:skills/paperclip/SKILL.md:standalone-decisions:364", + "id": "skill:skills/paperclip/SKILL.md:standalone-decisions:366", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:364", + "sourceAnchor": "skills/paperclip/SKILL.md:366", "title": "Standalone Decisions", "expectedSemantics": "Skill guidance headed “Standalone Decisions”.", "primaryDisposition": "optional_agent_tool", @@ -236,13 +236,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:364" + "skill:skills/paperclip/SKILL.md:366" ] }, { - "id": "skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:468", + "id": "skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:470", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:468", + "sourceAnchor": "skills/paperclip/SKILL.md:470", "title": "MCP Tool Approval Gates", "expectedSemantics": "Skill guidance headed “MCP Tool Approval Gates”.", "primaryDisposition": "optional_agent_tool", @@ -251,13 +251,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:468" + "skill:skills/paperclip/SKILL.md:470" ] }, { - "id": "skill:skills/paperclip/SKILL.md:niche-workflow-pointers:510", + "id": "skill:skills/paperclip/SKILL.md:niche-workflow-pointers:512", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:510", + "sourceAnchor": "skills/paperclip/SKILL.md:512", "title": "Niche Workflow Pointers", "expectedSemantics": "Skill guidance headed “Niche Workflow Pointers”.", "primaryDisposition": "optional_agent_tool", @@ -266,13 +266,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:510" + "skill:skills/paperclip/SKILL.md:512" ] }, { - "id": "skill:skills/paperclip/SKILL.md:cases:520", + "id": "skill:skills/paperclip/SKILL.md:cases:522", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:520", + "sourceAnchor": "skills/paperclip/SKILL.md:522", "title": "Cases", "expectedSemantics": "Skill guidance headed “Cases”.", "primaryDisposition": "optional_agent_tool", @@ -281,13 +281,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:520" + "skill:skills/paperclip/SKILL.md:522" ] }, { - "id": "skill:skills/paperclip/SKILL.md:company-skills-workflow:525", + "id": "skill:skills/paperclip/SKILL.md:company-skills-workflow:527", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:525", + "sourceAnchor": "skills/paperclip/SKILL.md:527", "title": "Company Skills Workflow", "expectedSemantics": "Skill guidance headed “Company Skills Workflow”.", "primaryDisposition": "optional_agent_tool", @@ -296,13 +296,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:525" + "skill:skills/paperclip/SKILL.md:527" ] }, { - "id": "skill:skills/paperclip/SKILL.md:routines:536", + "id": "skill:skills/paperclip/SKILL.md:routines:538", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:536", + "sourceAnchor": "skills/paperclip/SKILL.md:538", "title": "Routines", "expectedSemantics": "Skill guidance headed “Routines”.", "primaryDisposition": "optional_agent_tool", @@ -311,13 +311,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:536" + "skill:skills/paperclip/SKILL.md:538" ] }, { - "id": "skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:547", + "id": "skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:549", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:547", + "sourceAnchor": "skills/paperclip/SKILL.md:549", "title": "Task Files and Workspace Choices", "expectedSemantics": "Skill guidance headed “Task Files and Workspace Choices”.", "primaryDisposition": "optional_agent_tool", @@ -326,13 +326,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:547" + "skill:skills/paperclip/SKILL.md:549" ] }, { - "id": "skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:553", + "id": "skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:555", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:553", + "sourceAnchor": "skills/paperclip/SKILL.md:555", "title": "Issue Workspace Runtime Controls", "expectedSemantics": "Skill guidance headed “Issue Workspace Runtime Controls”.", "primaryDisposition": "optional_agent_tool", @@ -341,13 +341,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:553" + "skill:skills/paperclip/SKILL.md:555" ] }, { - "id": "skill:skills/paperclip/SKILL.md:proposing-credentials-safely:560", + "id": "skill:skills/paperclip/SKILL.md:proposing-credentials-safely:562", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:560", + "sourceAnchor": "skills/paperclip/SKILL.md:562", "title": "Proposing Credentials Safely", "expectedSemantics": "Skill guidance headed “Proposing Credentials Safely”.", "primaryDisposition": "optional_agent_tool", @@ -356,13 +356,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:560" + "skill:skills/paperclip/SKILL.md:562" ] }, { - "id": "skill:skills/paperclip/SKILL.md:reading-granted-secrets:567", + "id": "skill:skills/paperclip/SKILL.md:reading-granted-secrets:569", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:567", + "sourceAnchor": "skills/paperclip/SKILL.md:569", "title": "Reading Granted Secrets", "expectedSemantics": "Skill guidance headed “Reading Granted Secrets”.", "primaryDisposition": "optional_agent_tool", @@ -371,13 +371,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:567" + "skill:skills/paperclip/SKILL.md:569" ] }, { - "id": "skill:skills/paperclip/SKILL.md:critical-rules:593", + "id": "skill:skills/paperclip/SKILL.md:critical-rules:595", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:593", + "sourceAnchor": "skills/paperclip/SKILL.md:595", "title": "Critical Rules", "expectedSemantics": "Skill guidance headed “Critical Rules”.", "primaryDisposition": "optional_agent_tool", @@ -386,13 +386,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:593" + "skill:skills/paperclip/SKILL.md:595" ] }, { - "id": "skill:skills/paperclip/SKILL.md:comment-style-required:614", + "id": "skill:skills/paperclip/SKILL.md:comment-style-required:616", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:614", + "sourceAnchor": "skills/paperclip/SKILL.md:616", "title": "Comment Style (Required)", "expectedSemantics": "Skill guidance headed “Comment Style (Required)”.", "primaryDisposition": "always_agent_tool", @@ -401,13 +401,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:614" + "skill:skills/paperclip/SKILL.md:616" ] }, { - "id": "skill:skills/paperclip/SKILL.md:update:646", + "id": "skill:skills/paperclip/SKILL.md:update:648", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:646", + "sourceAnchor": "skills/paperclip/SKILL.md:648", "title": "Update", "expectedSemantics": "Skill guidance headed “Update”.", "primaryDisposition": "optional_agent_tool", @@ -416,13 +416,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:646" + "skill:skills/paperclip/SKILL.md:648" ] }, { - "id": "skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:656", + "id": "skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:658", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:656", + "sourceAnchor": "skills/paperclip/SKILL.md:658", "title": "Task Documents and Deliverables", "expectedSemantics": "Skill guidance headed “Task Documents and Deliverables”.", "primaryDisposition": "always_agent_tool", @@ -431,13 +431,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:656" + "skill:skills/paperclip/SKILL.md:658" ] }, { - "id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:670", + "id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:672", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:670", + "sourceAnchor": "skills/paperclip/SKILL.md:672", "title": "Planning (Required when planning requested)", "expectedSemantics": "Skill guidance headed “Planning (Required when planning requested)”.", "primaryDisposition": "optional_agent_tool", @@ -446,13 +446,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:670" + "skill:skills/paperclip/SKILL.md:672" ] }, { - "id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:701", + "id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:703", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:701", + "sourceAnchor": "skills/paperclip/SKILL.md:703", "title": "Key Endpoints (Hot Routes)", "expectedSemantics": "Skill guidance headed “Key Endpoints (Hot Routes)”.", "primaryDisposition": "optional_agent_tool", @@ -461,13 +461,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:701" + "skill:skills/paperclip/SKILL.md:703" ] }, { - "id": "skill:skills/paperclip/SKILL.md:searching-issues:730", + "id": "skill:skills/paperclip/SKILL.md:searching-issues:733", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:730", + "sourceAnchor": "skills/paperclip/SKILL.md:733", "title": "Searching Issues", "expectedSemantics": "Skill guidance headed “Searching Issues”.", "primaryDisposition": "optional_agent_tool", @@ -476,13 +476,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:730" + "skill:skills/paperclip/SKILL.md:733" ] }, { - "id": "skill:skills/paperclip/SKILL.md:full-reference:740", + "id": "skill:skills/paperclip/SKILL.md:full-reference:743", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:740", + "sourceAnchor": "skills/paperclip/SKILL.md:743", "title": "Full Reference", "expectedSemantics": "Skill guidance headed “Full Reference”.", "primaryDisposition": "optional_agent_tool", @@ -491,13 +491,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:740" + "skill:skills/paperclip/SKILL.md:743" ] }, { - "id": "skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:744", + "id": "skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:747", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:744", + "sourceAnchor": "skills/paperclip/SKILL.md:747", "title": "Conversational confirmation answers", "expectedSemantics": "Skill guidance headed “Conversational confirmation answers”.", "primaryDisposition": "always_agent_tool", @@ -506,13 +506,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:744" + "skill:skills/paperclip/SKILL.md:747" ] }, { - "id": "skill:skills/paperclip/SKILL.md:incidental-feedback:761", + "id": "skill:skills/paperclip/SKILL.md:incidental-feedback:764", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:761", + "sourceAnchor": "skills/paperclip/SKILL.md:764", "title": "Incidental feedback", "expectedSemantics": "Skill guidance headed “Incidental feedback”.", "primaryDisposition": "optional_agent_tool", @@ -521,7 +521,7 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:761" + "skill:skills/paperclip/SKILL.md:764" ] }, { @@ -585,9 +585,9 @@ ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:issue-with-ancestors-get-api-issues-issueid:110", + "id": "skill:skills/paperclip/references/api-reference.md:issue-with-ancestors-get-api-issues-issueid:116", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:110", + "sourceAnchor": "skills/paperclip/references/api-reference.md:116", "title": "Issue with Ancestors (`GET /api/issues/:issueId`)", "expectedSemantics": "Skill guidance headed “Issue with Ancestors (`GET /api/issues/:issueId`)”.", "primaryDisposition": "optional_agent_tool", @@ -596,13 +596,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:110" + "skill:skills/paperclip/references/api-reference.md:116" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:issue-update-response-patch-api-issues-issueid:196", + "id": "skill:skills/paperclip/references/api-reference.md:issue-update-response-patch-api-issues-issueid:202", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:196", + "sourceAnchor": "skills/paperclip/references/api-reference.md:202", "title": "Issue Update Response (`PATCH /api/issues/:issueId`)", "expectedSemantics": "Skill guidance headed “Issue Update Response (`PATCH /api/issues/:issueId`)”.", "primaryDisposition": "optional_agent_tool", @@ -611,13 +611,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:196" + "skill:skills/paperclip/references/api-reference.md:202" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers:238", + "id": "skill:skills/paperclip/references/api-reference.md:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers:244", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:238", + "sourceAnchor": "skills/paperclip/references/api-reference.md:244", "title": "Blocker Diagnostics (`GET /api/issues/:issueId/diagnostics/blockers`)", "expectedSemantics": "Skill guidance headed “Blocker Diagnostics (`GET /api/issues/:issueId/diagnostics/blockers`)”.", "primaryDisposition": "control_plane_owned", @@ -626,13 +626,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:238" + "skill:skills/paperclip/references/api-reference.md:244" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes:277", + "id": "skill:skills/paperclip/references/api-reference.md:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes:283", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:277", + "sourceAnchor": "skills/paperclip/references/api-reference.md:283", "title": "Wake Diagnostics (`GET /api/issues/:issueId/diagnostics/wakes`)", "expectedSemantics": "Skill guidance headed “Wake Diagnostics (`GET /api/issues/:issueId/diagnostics/wakes`)”.", "primaryDisposition": "control_plane_owned", @@ -641,13 +641,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:277" + "skill:skills/paperclip/references/api-reference.md:283" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree:321", + "id": "skill:skills/paperclip/references/api-reference.md:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree:327", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:321", + "sourceAnchor": "skills/paperclip/references/api-reference.md:327", "title": "Subtree Diagnostics (`GET /api/issues/:issueId/diagnostics/subtree`)", "expectedSemantics": "Skill guidance headed “Subtree Diagnostics (`GET /api/issues/:issueId/diagnostics/subtree`)”.", "primaryDisposition": "optional_agent_tool", @@ -656,13 +656,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:321" + "skill:skills/paperclip/references/api-reference.md:327" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:execution-policy-fields-on-an-issue:369", + "id": "skill:skills/paperclip/references/api-reference.md:execution-policy-fields-on-an-issue:375", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:369", + "sourceAnchor": "skills/paperclip/references/api-reference.md:375", "title": "Execution Policy Fields On An Issue", "expectedSemantics": "Skill guidance headed “Execution Policy Fields On An Issue”.", "primaryDisposition": "optional_agent_tool", @@ -671,13 +671,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:369" + "skill:skills/paperclip/references/api-reference.md:375" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:cross-agent-review-gates:421", + "id": "skill:skills/paperclip/references/api-reference.md:cross-agent-review-gates:427", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:421", + "sourceAnchor": "skills/paperclip/references/api-reference.md:427", "title": "Cross-Agent Review Gates", "expectedSemantics": "Skill guidance headed “Cross-Agent Review Gates”.", "primaryDisposition": "always_agent_tool", @@ -686,13 +686,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:421" + "skill:skills/paperclip/references/api-reference.md:427" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:worked-example-ic-heartbeat:454", + "id": "skill:skills/paperclip/references/api-reference.md:worked-example-ic-heartbeat:460", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:454", + "sourceAnchor": "skills/paperclip/references/api-reference.md:460", "title": "Worked Example: IC Heartbeat", "expectedSemantics": "Skill guidance headed “Worked Example: IC Heartbeat”.", "primaryDisposition": "optional_agent_tool", @@ -701,13 +701,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:454" + "skill:skills/paperclip/references/api-reference.md:460" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:459", + "id": "skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:465", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:459", + "sourceAnchor": "skills/paperclip/references/api-reference.md:465", "title": "1. Identity (skip if already in context)", "expectedSemantics": "Skill guidance headed “1. Identity (skip if already in context)”.", "primaryDisposition": "control_plane_owned", @@ -716,13 +716,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:459" + "skill:skills/paperclip/references/api-reference.md:465" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:2-check-inbox:463", + "id": "skill:skills/paperclip/references/api-reference.md:2-check-inbox:469", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:463", + "sourceAnchor": "skills/paperclip/references/api-reference.md:469", "title": "2. Check inbox", "expectedSemantics": "Skill guidance headed “2. Check inbox”.", "primaryDisposition": "control_plane_owned", @@ -731,13 +731,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:463" + "skill:skills/paperclip/references/api-reference.md:469" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:3-already-have-issue-101-inprogress-highest-priority-continue-it:470", + "id": "skill:skills/paperclip/references/api-reference.md:3-already-have-issue-101-inprogress-highest-priority-continue-it:476", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:470", + "sourceAnchor": "skills/paperclip/references/api-reference.md:476", "title": "3. Already have issue-101 in_progress (highest priority). Continue it.", "expectedSemantics": "Skill guidance headed “3. Already have issue-101 in_progress (highest priority). Continue it.”.", "primaryDisposition": "optional_agent_tool", @@ -746,13 +746,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:470" + "skill:skills/paperclip/references/api-reference.md:476" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:4-do-the-actual-work-write-code-run-tests:477", + "id": "skill:skills/paperclip/references/api-reference.md:4-do-the-actual-work-write-code-run-tests:483", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:477", + "sourceAnchor": "skills/paperclip/references/api-reference.md:483", "title": "4. Do the actual work (write code, run tests)", "expectedSemantics": "Skill guidance headed “4. Do the actual work (write code, run tests)”.", "primaryDisposition": "optional_agent_tool", @@ -760,44 +760,44 @@ "assertionClasses": [ "control_plane_invariant" ], - "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:477" - ] - }, - { - "id": "skill:skills/paperclip/references/api-reference.md:5-work-is-done-update-status-and-comment-in-one-call:479", - "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:479", - "title": "5. Work is done. Update status and comment in one call.", - "expectedSemantics": "Skill guidance headed “5. Work is done. Update status and comment in one call.”.", - "primaryDisposition": "always_agent_tool", - "requiredGrants": [], - "assertionClasses": [ - "control_plane_invariant" - ], - "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:479" - ] - }, - { - "id": "skill:skills/paperclip/references/api-reference.md:6-still-have-time-checkout-the-next-task:483", - "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:483", - "title": "6. Still have time. Checkout the next task.", - "expectedSemantics": "Skill guidance headed “6. Still have time. Checkout the next task.”.", - "primaryDisposition": "control_plane_owned", - "requiredGrants": [], - "assertionClasses": [ - "control_plane_invariant" - ], "evidenceIds": [ "skill:skills/paperclip/references/api-reference.md:483" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:7-made-partial-progress-not-done-yet-comment-and-exit:490", + "id": "skill:skills/paperclip/references/api-reference.md:5-work-is-done-update-status-and-comment-in-one-call:485", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:490", + "sourceAnchor": "skills/paperclip/references/api-reference.md:485", + "title": "5. Work is done. Update status and comment in one call.", + "expectedSemantics": "Skill guidance headed “5. Work is done. Update status and comment in one call.”.", + "primaryDisposition": "always_agent_tool", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/references/api-reference.md:485" + ] + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:6-still-have-time-checkout-the-next-task:489", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md:489", + "title": "6. Still have time. Checkout the next task.", + "expectedSemantics": "Skill guidance headed “6. Still have time. Checkout the next task.”.", + "primaryDisposition": "control_plane_owned", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/references/api-reference.md:489" + ] + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:7-made-partial-progress-not-done-yet-comment-and-exit:496", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md:496", "title": "7. Made partial progress, not done yet. Comment and exit.", "expectedSemantics": "Skill guidance headed “7. Made partial progress, not done yet. Comment and exit.”.", "primaryDisposition": "always_agent_tool", @@ -806,13 +806,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:490" + "skill:skills/paperclip/references/api-reference.md:496" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:worked-example-report-a-board-user-s-mine-inbox:495", + "id": "skill:skills/paperclip/references/api-reference.md:worked-example-report-a-board-user-s-mine-inbox:501", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:495", + "sourceAnchor": "skills/paperclip/references/api-reference.md:501", "title": "Worked Example: Report A Board User's Mine Inbox", "expectedSemantics": "Skill guidance headed “Worked Example: Report A Board User's Mine Inbox”.", "primaryDisposition": "control_plane_owned", @@ -821,13 +821,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:495" + "skill:skills/paperclip/references/api-reference.md:501" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:board-user-created-the-requesting-issue:500", + "id": "skill:skills/paperclip/references/api-reference.md:board-user-created-the-requesting-issue:506", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:500", + "sourceAnchor": "skills/paperclip/references/api-reference.md:506", "title": "Board user created the requesting issue.", "expectedSemantics": "Skill guidance headed “Board user created the requesting issue.”.", "primaryDisposition": "optional_agent_tool", @@ -836,13 +836,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:500" + "skill:skills/paperclip/references/api-reference.md:506" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:fetch-the-board-user-s-mine-inbox-issues:504", + "id": "skill:skills/paperclip/references/api-reference.md:fetch-the-board-user-s-mine-inbox-issues:510", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:504", + "sourceAnchor": "skills/paperclip/references/api-reference.md:510", "title": "Fetch the board user's Mine inbox issues.", "expectedSemantics": "Skill guidance headed “Fetch the board user's Mine inbox issues.”.", "primaryDisposition": "control_plane_owned", @@ -851,13 +851,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:504" + "skill:skills/paperclip/references/api-reference.md:510" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:summarize-it-back-to-the-board-in-a-comment-or-document:518", + "id": "skill:skills/paperclip/references/api-reference.md:summarize-it-back-to-the-board-in-a-comment-or-document:524", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:518", + "sourceAnchor": "skills/paperclip/references/api-reference.md:524", "title": "Summarize it back to the board in a comment or document.", "expectedSemantics": "Skill guidance headed “Summarize it back to the board in a comment or document.”.", "primaryDisposition": "always_agent_tool", @@ -866,13 +866,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:518" + "skill:skills/paperclip/references/api-reference.md:524" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:worked-example-archive-a-resolved-inbox-item:523", + "id": "skill:skills/paperclip/references/api-reference.md:worked-example-archive-a-resolved-inbox-item:529", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:523", + "sourceAnchor": "skills/paperclip/references/api-reference.md:529", "title": "Worked Example: Archive A Resolved Inbox Item", "expectedSemantics": "Skill guidance headed “Worked Example: Archive A Resolved Inbox Item”.", "primaryDisposition": "control_plane_owned", @@ -881,13 +881,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:523" + "skill:skills/paperclip/references/api-reference.md:529" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run:528", + "id": "skill:skills/paperclip/references/api-reference.md:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run:534", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:528", + "sourceAnchor": "skills/paperclip/references/api-reference.md:534", "title": "The responsible user's id is resolved from the authenticated agent run.", "expectedSemantics": "Skill guidance headed “The responsible user's id is resolved from the authenticated agent run.”.", "primaryDisposition": "optional_agent_tool", @@ -896,13 +896,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:528" + "skill:skills/paperclip/references/api-reference.md:534" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:reverse-the-archive-if-it-was-premature-or-no-longer-desired:537", + "id": "skill:skills/paperclip/references/api-reference.md:reverse-the-archive-if-it-was-premature-or-no-longer-desired:543", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:537", + "sourceAnchor": "skills/paperclip/references/api-reference.md:543", "title": "Reverse the archive if it was premature or no longer desired.", "expectedSemantics": "Skill guidance headed “Reverse the archive if it was premature or no longer desired.”.", "primaryDisposition": "optional_agent_tool", @@ -911,13 +911,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:537" + "skill:skills/paperclip/references/api-reference.md:543" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:worked-example-reviewer-approver-heartbeat:547", + "id": "skill:skills/paperclip/references/api-reference.md:worked-example-reviewer-approver-heartbeat:553", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:547", + "sourceAnchor": "skills/paperclip/references/api-reference.md:553", "title": "Worked Example: Reviewer / Approver Heartbeat", "expectedSemantics": "Skill guidance headed “Worked Example: Reviewer / Approver Heartbeat”.", "primaryDisposition": "always_agent_tool", @@ -926,13 +926,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:547" + "skill:skills/paperclip/references/api-reference.md:553" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:worked-example-manager-heartbeat:586", + "id": "skill:skills/paperclip/references/api-reference.md:worked-example-manager-heartbeat:592", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:586", + "sourceAnchor": "skills/paperclip/references/api-reference.md:592", "title": "Worked Example: Manager Heartbeat", "expectedSemantics": "Skill guidance headed “Worked Example: Manager Heartbeat”.", "primaryDisposition": "optional_agent_tool", @@ -941,13 +941,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:586" + "skill:skills/paperclip/references/api-reference.md:592" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:589", + "id": "skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:595", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:589", + "sourceAnchor": "skills/paperclip/references/api-reference.md:595", "title": "1. Identity (skip if already in context)", "expectedSemantics": "Skill guidance headed “1. Identity (skip if already in context)”.", "primaryDisposition": "control_plane_owned", @@ -956,13 +956,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:589" + "skill:skills/paperclip/references/api-reference.md:595" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:2-check-team-status:593", + "id": "skill:skills/paperclip/references/api-reference.md:2-check-team-status:599", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:593", + "sourceAnchor": "skills/paperclip/references/api-reference.md:599", "title": "2. Check team status", "expectedSemantics": "Skill guidance headed “2. Check team status”.", "primaryDisposition": "optional_agent_tool", @@ -971,13 +971,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:593" + "skill:skills/paperclip/references/api-reference.md:599" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:3-agent-42-is-blocked-read-comments:600", + "id": "skill:skills/paperclip/references/api-reference.md:3-agent-42-is-blocked-read-comments:606", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:600", + "sourceAnchor": "skills/paperclip/references/api-reference.md:606", "title": "3. Agent-42 is blocked. Read comments.", "expectedSemantics": "Skill guidance headed “3. Agent-42 is blocked. Read comments.”.", "primaryDisposition": "control_plane_owned", @@ -986,13 +986,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:600" + "skill:skills/paperclip/references/api-reference.md:606" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:4-unblock-reassign-and-comment:604", + "id": "skill:skills/paperclip/references/api-reference.md:4-unblock-reassign-and-comment:610", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:604", + "sourceAnchor": "skills/paperclip/references/api-reference.md:610", "title": "4. Unblock: reassign and comment.", "expectedSemantics": "Skill guidance headed “4. Unblock: reassign and comment.”.", "primaryDisposition": "control_plane_owned", @@ -1001,13 +1001,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:604" + "skill:skills/paperclip/references/api-reference.md:610" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:5-check-own-assignments:608", + "id": "skill:skills/paperclip/references/api-reference.md:5-check-own-assignments:614", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:608", + "sourceAnchor": "skills/paperclip/references/api-reference.md:614", "title": "5. Check own assignments.", "expectedSemantics": "Skill guidance headed “5. Check own assignments.”.", "primaryDisposition": "optional_agent_tool", @@ -1016,13 +1016,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:608" + "skill:skills/paperclip/references/api-reference.md:614" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:6-create-subtasks-and-delegate:615", + "id": "skill:skills/paperclip/references/api-reference.md:6-create-subtasks-and-delegate:621", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:615", + "sourceAnchor": "skills/paperclip/references/api-reference.md:621", "title": "6. Create subtasks and delegate.", "expectedSemantics": "Skill guidance headed “6. Create subtasks and delegate.”.", "primaryDisposition": "optional_agent_tool", @@ -1030,47 +1030,32 @@ "assertionClasses": [ "control_plane_invariant" ], - "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:615" - ] - }, - { - "id": "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", - "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:621", - "title": "^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.", - "expectedSemantics": "Skill guidance headed “^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.”.", - "primaryDisposition": "control_plane_owned", - "requiredGrants": [], - "assertionClasses": [ - "control_plane_invariant" - ], "evidenceIds": [ "skill:skills/paperclip/references/api-reference.md:621" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:7-dashboard-for-health-check:626", + "id": "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", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:626", - "title": "7. Dashboard for health check.", - "expectedSemantics": "Skill guidance headed “7. Dashboard for health check.”.", - "primaryDisposition": "optional_agent_tool", + "sourceAnchor": "skills/paperclip/references/api-reference.md:627", + "title": "^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.", + "expectedSemantics": "Skill guidance headed “^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.”.", + "primaryDisposition": "control_plane_owned", "requiredGrants": [], "assertionClasses": [ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:626" + "skill:skills/paperclip/references/api-reference.md:627" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:comments-and-mentions:632", + "id": "skill:skills/paperclip/references/api-reference.md:7-dashboard-for-health-check:632", "sourceKind": "skill_heading", "sourceAnchor": "skills/paperclip/references/api-reference.md:632", - "title": "Comments and @-mentions", - "expectedSemantics": "Skill guidance headed “Comments and @-mentions”.", - "primaryDisposition": "always_agent_tool", + "title": "7. Dashboard for health check.", + "expectedSemantics": "Skill guidance headed “7. Dashboard for health check.”.", + "primaryDisposition": "optional_agent_tool", "requiredGrants": [], "assertionClasses": [ "control_plane_invariant" @@ -1080,9 +1065,24 @@ ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:update:639", + "id": "skill:skills/paperclip/references/api-reference.md:comments-and-mentions:638", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:639", + "sourceAnchor": "skills/paperclip/references/api-reference.md:638", + "title": "Comments and @-mentions", + "expectedSemantics": "Skill guidance headed “Comments and @-mentions”.", + "primaryDisposition": "always_agent_tool", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/references/api-reference.md:638" + ] + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:update:645", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md:645", "title": "Update", "expectedSemantics": "Skill guidance headed “Update”.", "primaryDisposition": "optional_agent_tool", @@ -1091,13 +1091,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:639" + "skill:skills/paperclip/references/api-reference.md:645" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:cross-team-work-and-delegation:661", + "id": "skill:skills/paperclip/references/api-reference.md:cross-team-work-and-delegation:667", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:661", + "sourceAnchor": "skills/paperclip/references/api-reference.md:667", "title": "Cross-Team Work and Delegation", "expectedSemantics": "Skill guidance headed “Cross-Team Work and Delegation”.", "primaryDisposition": "optional_agent_tool", @@ -1106,13 +1106,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:661" + "skill:skills/paperclip/references/api-reference.md:667" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:receiving-cross-team-work:665", + "id": "skill:skills/paperclip/references/api-reference.md:receiving-cross-team-work:671", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:665", + "sourceAnchor": "skills/paperclip/references/api-reference.md:671", "title": "Receiving cross-team work", "expectedSemantics": "Skill guidance headed “Receiving cross-team work”.", "primaryDisposition": "optional_agent_tool", @@ -1121,13 +1121,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:665" + "skill:skills/paperclip/references/api-reference.md:671" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:questions-and-dependencies:675", + "id": "skill:skills/paperclip/references/api-reference.md:questions-and-dependencies:681", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:675", + "sourceAnchor": "skills/paperclip/references/api-reference.md:681", "title": "Questions and dependencies", "expectedSemantics": "Skill guidance headed “Questions and dependencies”.", "primaryDisposition": "always_agent_tool", @@ -1136,13 +1136,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:675" + "skill:skills/paperclip/references/api-reference.md:681" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:company-context:687", + "id": "skill:skills/paperclip/references/api-reference.md:company-context:693", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:687", + "sourceAnchor": "skills/paperclip/references/api-reference.md:693", "title": "Company Context", "expectedSemantics": "Skill guidance headed “Company Context”.", "primaryDisposition": "optional_agent_tool", @@ -1151,13 +1151,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:687" + "skill:skills/paperclip/references/api-reference.md:693" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:company-branding-ceo-board:699", + "id": "skill:skills/paperclip/references/api-reference.md:company-branding-ceo-board:705", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:699", + "sourceAnchor": "skills/paperclip/references/api-reference.md:705", "title": "Company Branding (CEO / Board)", "expectedSemantics": "Skill guidance headed “Company Branding (CEO / Board)”.", "primaryDisposition": "optional_agent_tool", @@ -1166,13 +1166,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:699" + "skill:skills/paperclip/references/api-reference.md:705" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:openclaw-invite-prompt-ceo:719", + "id": "skill:skills/paperclip/references/api-reference.md:openclaw-invite-prompt-ceo:725", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:719", + "sourceAnchor": "skills/paperclip/references/api-reference.md:725", "title": "OpenClaw Invite Prompt (CEO)", "expectedSemantics": "Skill guidance headed “OpenClaw Invite Prompt (CEO)”.", "primaryDisposition": "optional_agent_tool", @@ -1181,13 +1181,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:719" + "skill:skills/paperclip/references/api-reference.md:725" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:setting-agent-instructions-path:738", + "id": "skill:skills/paperclip/references/api-reference.md:setting-agent-instructions-path:744", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:738", + "sourceAnchor": "skills/paperclip/references/api-reference.md:744", "title": "Setting Agent Instructions Path", "expectedSemantics": "Skill guidance headed “Setting Agent Instructions Path”.", "primaryDisposition": "optional_agent_tool", @@ -1196,13 +1196,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:738" + "skill:skills/paperclip/references/api-reference.md:744" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:project-setup-create-workspace:771", + "id": "skill:skills/paperclip/references/api-reference.md:project-setup-create-workspace:777", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:771", + "sourceAnchor": "skills/paperclip/references/api-reference.md:777", "title": "Project Setup (Create + Workspace)", "expectedSemantics": "Skill guidance headed “Project Setup (Create + Workspace)”.", "primaryDisposition": "optional_agent_tool", @@ -1211,13 +1211,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:771" + "skill:skills/paperclip/references/api-reference.md:777" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:795", + "id": "skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:801", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:795", + "sourceAnchor": "skills/paperclip/references/api-reference.md:801", "title": "Option A: One-call create with workspace", "expectedSemantics": "Skill guidance headed “Option A: One-call create with workspace”.", "primaryDisposition": "optional_agent_tool", @@ -1226,13 +1226,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:795" + "skill:skills/paperclip/references/api-reference.md:801" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:814", + "id": "skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:820", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:814", + "sourceAnchor": "skills/paperclip/references/api-reference.md:820", "title": "Option B: Two calls (project first, then workspace)", "expectedSemantics": "Skill guidance headed “Option B: Two calls (project first, then workspace)”.", "primaryDisposition": "optional_agent_tool", @@ -1241,13 +1241,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:814" + "skill:skills/paperclip/references/api-reference.md:820" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:governance-and-approvals:843", + "id": "skill:skills/paperclip/references/api-reference.md:governance-and-approvals:849", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:843", + "sourceAnchor": "skills/paperclip/references/api-reference.md:849", "title": "Governance and Approvals", "expectedSemantics": "Skill guidance headed “Governance and Approvals”.", "primaryDisposition": "optional_agent_tool", @@ -1256,13 +1256,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:843" + "skill:skills/paperclip/references/api-reference.md:849" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:847", + "id": "skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:853", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:847", + "sourceAnchor": "skills/paperclip/references/api-reference.md:853", "title": "Requesting a hire (management only)", "expectedSemantics": "Skill guidance headed “Requesting a hire (management only)”.", "primaryDisposition": "optional_agent_tool", @@ -1271,13 +1271,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:847" + "skill:skills/paperclip/references/api-reference.md:853" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:912", + "id": "skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:918", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:912", + "sourceAnchor": "skills/paperclip/references/api-reference.md:918", "title": "CEO strategy approval", "expectedSemantics": "Skill guidance headed “CEO strategy approval”.", "primaryDisposition": "optional_agent_tool", @@ -1286,13 +1286,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:912" + "skill:skills/paperclip/references/api-reference.md:918" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:questions-and-waiting-for-human-input:921", + "id": "skill:skills/paperclip/references/api-reference.md:questions-and-waiting-for-human-input:927", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:921", + "sourceAnchor": "skills/paperclip/references/api-reference.md:927", "title": "Questions and waiting for human input", "expectedSemantics": "Skill guidance headed “Questions and waiting for human input”.", "primaryDisposition": "always_agent_tool", @@ -1301,13 +1301,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:921" + "skill:skills/paperclip/references/api-reference.md:927" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:1025", + "id": "skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:1031", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1025", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1031", "title": "Issue-thread confirmations", "expectedSemantics": "Skill guidance headed “Issue-thread confirmations”.", "primaryDisposition": "always_agent_tool", @@ -1316,30 +1316,15 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1025" + "skill:skills/paperclip/references/api-reference.md:1031" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:conversational-confirmation-answers:1083", - "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1083", - "title": "Conversational confirmation answers", - "expectedSemantics": "Skill guidance headed “Conversational confirmation answers”.", - "primaryDisposition": "always_agent_tool", - "requiredGrants": [], - "assertionClasses": [ - "control_plane_invariant" - ], - "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1083" - ] - }, - { - "id": "skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:1089", + "id": "skill:skills/paperclip/references/api-reference.md:conversational-confirmation-answers:1089", "sourceKind": "skill_heading", "sourceAnchor": "skills/paperclip/references/api-reference.md:1089", - "title": "Checkbox confirmations", - "expectedSemantics": "Skill guidance headed “Checkbox confirmations”.", + "title": "Conversational confirmation answers", + "expectedSemantics": "Skill guidance headed “Conversational confirmation answers”.", "primaryDisposition": "always_agent_tool", "requiredGrants": [], "assertionClasses": [ @@ -1350,9 +1335,24 @@ ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1204", + "id": "skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:1095", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1204", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1095", + "title": "Checkbox confirmations", + "expectedSemantics": "Skill guidance headed “Checkbox confirmations”.", + "primaryDisposition": "always_agent_tool", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/references/api-reference.md:1095" + ] + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1210", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1210", "title": "Item verdict requests", "expectedSemantics": "Skill guidance headed “Item verdict requests”.", "primaryDisposition": "optional_agent_tool", @@ -1361,13 +1361,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1204" + "skill:skills/paperclip/references/api-reference.md:1210" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:checking-approval-status:1314", + "id": "skill:skills/paperclip/references/api-reference.md:checking-approval-status:1320", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1314", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1320", "title": "Checking approval status", "expectedSemantics": "Skill guidance headed “Checking approval status”.", "primaryDisposition": "optional_agent_tool", @@ -1375,29 +1375,29 @@ "assertionClasses": [ "control_plane_invariant" ], - "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1314" - ] - }, - { - "id": "skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1320", - "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1320", - "title": "Approval follow-up (requesting agent)", - "expectedSemantics": "Skill guidance headed “Approval follow-up (requesting agent)”.", - "primaryDisposition": "always_agent_tool", - "requiredGrants": [], - "assertionClasses": [ - "control_plane_invariant" - ], "evidenceIds": [ "skill:skills/paperclip/references/api-reference.md:1320" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1338", + "id": "skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1326", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1338", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1326", + "title": "Approval follow-up (requesting agent)", + "expectedSemantics": "Skill guidance headed “Approval follow-up (requesting agent)”.", + "primaryDisposition": "always_agent_tool", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/references/api-reference.md:1326" + ] + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1344", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1344", "title": "Issue Lifecycle", "expectedSemantics": "Skill guidance headed “Issue Lifecycle”.", "primaryDisposition": "always_agent_tool", @@ -1406,13 +1406,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1338" + "skill:skills/paperclip/references/api-reference.md:1344" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:error-handling:1368", + "id": "skill:skills/paperclip/references/api-reference.md:error-handling:1374", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1368", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1374", "title": "Error Handling", "expectedSemantics": "Skill guidance headed “Error Handling”.", "primaryDisposition": "control_plane_owned", @@ -1421,13 +1421,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1368" + "skill:skills/paperclip/references/api-reference.md:1374" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:full-api-reference:1382", + "id": "skill:skills/paperclip/references/api-reference.md:full-api-reference:1388", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1382", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1388", "title": "Full API Reference", "expectedSemantics": "Skill guidance headed “Full API Reference”.", "primaryDisposition": "optional_agent_tool", @@ -1436,13 +1436,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1382" + "skill:skills/paperclip/references/api-reference.md:1388" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:agents:1384", + "id": "skill:skills/paperclip/references/api-reference.md:agents:1390", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1384", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1390", "title": "Agents", "expectedSemantics": "Skill guidance headed “Agents”.", "primaryDisposition": "optional_agent_tool", @@ -1451,13 +1451,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1384" + "skill:skills/paperclip/references/api-reference.md:1390" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:issues-tasks:1405", + "id": "skill:skills/paperclip/references/api-reference.md:issues-tasks:1411", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1405", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1411", "title": "Issues (Tasks)", "expectedSemantics": "Skill guidance headed “Issues (Tasks)”.", "primaryDisposition": "optional_agent_tool", @@ -1466,13 +1466,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1405" + "skill:skills/paperclip/references/api-reference.md:1411" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1446", + "id": "skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1452", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1446", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1452", "title": "Companies, Projects, Goals", "expectedSemantics": "Skill guidance headed “Companies, Projects, Goals”.", "primaryDisposition": "optional_agent_tool", @@ -1481,13 +1481,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1446" + "skill:skills/paperclip/references/api-reference.md:1452" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:routines:1470", + "id": "skill:skills/paperclip/references/api-reference.md:routines:1476", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1470", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1476", "title": "Routines", "expectedSemantics": "Skill guidance headed “Routines”.", "primaryDisposition": "optional_agent_tool", @@ -1496,13 +1496,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1470" + "skill:skills/paperclip/references/api-reference.md:1476" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1486", + "id": "skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1492", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1486", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1492", "title": "Approvals, Costs, Activity, Dashboard", "expectedSemantics": "Skill guidance headed “Approvals, Costs, Activity, Dashboard”.", "primaryDisposition": "optional_agent_tool", @@ -1511,13 +1511,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1486" + "skill:skills/paperclip/references/api-reference.md:1492" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:secrets:1508", + "id": "skill:skills/paperclip/references/api-reference.md:secrets:1514", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1508", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1514", "title": "Secrets", "expectedSemantics": "Skill guidance headed “Secrets”.", "primaryDisposition": "optional_agent_tool", @@ -1526,13 +1526,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1508" + "skill:skills/paperclip/references/api-reference.md:1514" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1521", + "id": "skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1527", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1521", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1527", "title": "Agent secret proposals", "expectedSemantics": "Skill guidance headed “Agent secret proposals”.", "primaryDisposition": "optional_agent_tool", @@ -1541,13 +1541,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1521" + "skill:skills/paperclip/references/api-reference.md:1527" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:agent-secret-access:1621", + "id": "skill:skills/paperclip/references/api-reference.md:agent-secret-access:1627", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1621", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1627", "title": "Agent secret access", "expectedSemantics": "Skill guidance headed “Agent secret access”.", "primaryDisposition": "optional_agent_tool", @@ -1556,13 +1556,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1621" + "skill:skills/paperclip/references/api-reference.md:1627" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:common-mistakes:1661", + "id": "skill:skills/paperclip/references/api-reference.md:common-mistakes:1667", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1661", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1667", "title": "Common Mistakes", "expectedSemantics": "Skill guidance headed “Common Mistakes”.", "primaryDisposition": "optional_agent_tool", @@ -1571,7 +1571,7 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1661" + "skill:skills/paperclip/references/api-reference.md:1667" ] }, { diff --git a/server/src/__tests__/activity-routes.test.ts b/server/src/__tests__/activity-routes.test.ts index 76671447903..d2090f9e5a1 100644 --- a/server/src/__tests__/activity-routes.test.ts +++ b/server/src/__tests__/activity-routes.test.ts @@ -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: [{ diff --git a/server/src/__tests__/heartbeat-process-recovery.test.ts b/server/src/__tests__/heartbeat-process-recovery.test.ts index f9ce2234e52..50b97e82246 100644 --- a/server/src/__tests__/heartbeat-process-recovery.test.ts +++ b/server/src/__tests__/heartbeat-process-recovery.test.ts @@ -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" }); diff --git a/server/src/__tests__/issue-recovery-actions.test.ts b/server/src/__tests__/issue-recovery-actions.test.ts index dd814866b58..05e96216f61 100644 --- a/server/src/__tests__/issue-recovery-actions.test.ts +++ b/server/src/__tests__/issue-recovery-actions.test.ts @@ -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 () => { diff --git a/server/src/__tests__/issues-service.test.ts b/server/src/__tests__/issues-service.test.ts index 535990d7fd1..4475e075122 100644 --- a/server/src/__tests__/issues-service.test.ts +++ b/server/src/__tests__/issues-service.test.ts @@ -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; diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 13c5e848f6a..3ecbbdab36f 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -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, diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index 35a606cf7d5..e34121a99fa 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -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`( + ${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` ( @@ -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}`, diff --git a/server/src/services/native-runtime/native-workspace-finalization-ownership.ts b/server/src/services/native-runtime/native-workspace-finalization-ownership.ts index 84227de0049..fcbff05fdee 100644 --- a/server/src/services/native-runtime/native-workspace-finalization-ownership.ts +++ b/server/src/services/native-runtime/native-workspace-finalization-ownership.ts @@ -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( 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, diff --git a/server/src/services/native-runtime/native-workspace-finalization-recovery-state.ts b/server/src/services/native-runtime/native-workspace-finalization-recovery-state.ts new file mode 100644 index 00000000000..71bc4f426c5 --- /dev/null +++ b/server/src/services/native-runtime/native-workspace-finalization-recovery-state.ts @@ -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, +): Promise { + 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 }); +} diff --git a/server/src/services/recovery/service.ts b/server/src/services/recovery/service.ts index 0a45ab1619b..e4c0f0eacc9 100644 --- a/server/src/services/recovery/service.ts +++ b/server/src/services/recovery/service.ts @@ -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 ( diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index a4f0f95dd06..aa492179608 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -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` | diff --git a/skills/paperclip/references/api-reference.md b/skills/paperclip/references/api-reference.md index 596e0648e20..72e12f6a189 100644 --- a/skills/paperclip/references/api-reference.md +++ b/skills/paperclip/references/api-reference.md @@ -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. From 56a0bf0d492006a0aa2d0b99bfe7d5b86f657f3a Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Mon, 12 Oct 2026 19:32:33 -0700 Subject: [PATCH 2/4] Support test Cloud connector identities (#16033) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Its Cloud connector uses an environment-bound identity to call a hosted broker. > - The identity parser and enrollment flow accept development, staging, and production. > - A separate test broker needs its own environment identity. > - This pull request accepts test identities and maps the test broker to that environment. > - Known hosted brokers still reject identities from other environments. ## Linked Issues or Issue Description **What existing behavior does this improve?** Cloud connector configuration and self-host enrollment. **Current behavior** The app rejects an identity with environment `test`. Self-host enrollment infers development for the test broker. **Proposed behavior** Accept `test` identities at `https://test.paperclip.app`. Infer test during self-host enrollment. Reject development, staging, and production identities at that broker. Continue to reject test identities at the production and staging brokers. **Reason and benefit** Branch testing needs a separate broker so test identities and requests stay in their selected environment. The existing identity, enrollment, and signature flow can provide that isolation with a new allowed environment value. **Breaking changes** Existing environment values and broker checks keep their current behavior. Older app versions cannot use test connector identities. ## What Changed - Add `test` to managed and self-hosted connector environment types and validation, and to the board API enrollment status type. - Infer test from its hosted broker and enforce the matching environment. - Test enrollment persistence, signed request environment/audience claims, and broker mismatch rejection. - Document test broker configuration in the development guide. ## Verification - Connector, enrollment, and board API-client suites passed: 63 tests across three files. Board UI typecheck and token gates passed after synchronizing its enrollment response type. - `pnpm -r typecheck` and `pnpm build` passed, including after rebasing onto current master. - `pnpm test:run` completed its general server stage: 17,867 passed, 105 skipped, and 9 failed across 5 files. The runner stops at that failing stage, so it did not run subsequent workspace/serialized stages locally. All nine failures also reproduce on the unchanged base commit in a separate checkout. Five skill-related cases resolve an unrelated ancestor skills directory; two workspace cases accept a remote main branch that their fixture expects to be absent; two permission-cleanup cases return macOS `ENOTEMPTY` instead of the expected permission error. - Current-head GitHub CI passed, including full general and serialized test suites, typecheck, build, runner checks, and browser e2e. Greptile is 5/5 with no unresolved comments. - `git diff --check` and Gitleaks passed. ## Risks The new environment value is additive. The test broker must support test identities before use. This change preserves existing signature handling, managed identity overrides, and rejection of mismatched identities at known hosted brokers. A local full-suite pass is not claimed; the nine failures above also occur without this change. ## Model Used OpenAI Codex, GPT-6, with reasoning and tool execution. The exact serving model ID and context window are not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `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 - [ ] 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 Co-authored-by: Dotta <34892728+cryppadotta@users.noreply.github.com> --- doc/DEVELOPING.md | 8 +++++ ...perclip-cloud-connector-enrollment.test.ts | 29 +++++++++++++++++++ .../paperclip-cloud-connector-enrollment.ts | 9 ++++-- .../paperclip-cloud-connector.test.ts | 18 ++++++++++++ .../src/services/paperclip-cloud-connector.ts | 5 ++-- ui/src/api/tools.ts | 2 +- 6 files changed, 65 insertions(+), 6 deletions(-) diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 0241771ebf5..36b4cdc2b7e 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -2034,6 +2034,14 @@ pnpm secrets:migrate-inline-env --apply # apply migration ## Internal Connection Token Brokers +Paperclip Cloud connector identities support `development`, `test`, `staging`, +and `production`. To enroll a self-hosted instance against the test broker, set +`PAPERCLIP_CLOUD_CONNECTOR_BASE_URL=https://test.paperclip.app`; its environment +is inferred as `test`. Managed instances also receive +`PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT=test`. Standard Cloud broker hosts reject +identities for another environment, and enrollment keys remain local to the +selected broker. + Connection token exchanges carry a resolved parent credential, so their URLs are public-only by default even in local/private deployments. To intentionally use an internal broker, configure an exact hostname allowlist: diff --git a/server/src/services/paperclip-cloud-connector-enrollment.test.ts b/server/src/services/paperclip-cloud-connector-enrollment.test.ts index 6443eafef32..8d731f426ba 100644 --- a/server/src/services/paperclip-cloud-connector-enrollment.test.ts +++ b/server/src/services/paperclip-cloud-connector-enrollment.test.ts @@ -193,6 +193,35 @@ describe("Paperclip Cloud self-host enrollment", () => { expect(paperclipCloudConnectorEnrollmentStatus({ PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: "https://my-staging.paperclip.app", })).toMatchObject({ environment: "staging" }); + expect(paperclipCloudConnectorEnrollmentStatus({ + PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: "https://test.paperclip.app", + })).toMatchObject({ environment: "test" }); + }); + + it.each(["development", "staging", "production"])("rejects %s identity at the test broker", (environment) => { + expect(() => paperclipCloudConnectorEnrollmentStatus({ + PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: "https://test.paperclip.app", + PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT: environment, + })).toThrow(/broker and environment do not match/); + }); + + it("persists test enrollment identity and signs requests for the test broker", async () => { + const origin = "https://tenant.test.paperclip.app"; + const request = vi.fn(async (input: string | URL | Request, init?: RequestInit) => { + const body = JSON.parse(String(init?.body)); + if (String(input).endsWith("/enrollments")) { + expect(body).toMatchObject({ environment: "test", origin }); + return Response.json({ enrollmentId: "enroll-test", verificationUrl: "https://test.paperclip.app/connections/enroll?id=enroll-test", expiresAt: new Date(Date.now() + 60_000).toISOString() }, { status: 201 }); + } + const claims = JSON.parse(Buffer.from(body.request.split(".")[1], "base64url").toString("utf8")); + expect(claims).toMatchObject({ env: "test", aud: "https://test.paperclip.app/v1/connector/enrollment-claims" }); + return Response.json({ id: loadPaperclipCloudConnectorIdentity()!.instanceId, environment: "test", origins: [origin] }); + }); + const env = { PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: "https://test.paperclip.app" }; + await startPaperclipCloudConnectorEnrollment({ origin, env, request: request as typeof fetch }); + const pending = loadPaperclipCloudConnectorIdentity()!; + await completePaperclipCloudConnectorEnrollment({ enrollmentId: "enroll-test", approvalCode: "approval-code", state: pending.pending!.returnState, env, request: request as typeof fetch }); + expect(paperclipCloudConnectorConfigFromEnv(env)).toMatchObject({ environment: "test", baseUrl: "https://test.paperclip.app", instanceId: pending.instanceId }); }); it("rotates a non-active identity instead of mixing enrollment targets", async () => { diff --git a/server/src/services/paperclip-cloud-connector-enrollment.ts b/server/src/services/paperclip-cloud-connector-enrollment.ts index ba5de762ba0..0fbbd9755c7 100644 --- a/server/src/services/paperclip-cloud-connector-enrollment.ts +++ b/server/src/services/paperclip-cloud-connector-enrollment.ts @@ -17,7 +17,7 @@ const IDENTITY_VERSION = 1; const ENROLLMENT_FILE = "paperclip-cloud-connector.json"; const REQUEST_TYPE = "paperclip-cloud-connector-request+jwt"; -export type LocalConnectorEnvironment = "development" | "staging" | "production"; +export type LocalConnectorEnvironment = "development" | "test" | "staging" | "production"; type PendingEnrollment = { enrollmentId: string; @@ -350,10 +350,13 @@ function connectorEnvironment( ? "production" : host === "my-staging.paperclip.app" ? "staging" - : "development"; + : host === "test.paperclip.app" + ? "test" + : "development"; const value = env.PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT?.trim() || fallback || inferred; if (!isEnvironment(value)) throw new Error("Paperclip Cloud connector environment is invalid"); if ((host === "my.paperclip.app" && value !== "production") + || (host === "test.paperclip.app" && value !== "test") || (host === "my-staging.paperclip.app" && value !== "staging")) { throw new Error("Paperclip Cloud connector broker and environment do not match"); } @@ -390,7 +393,7 @@ function hasManagedConnectorIdentityOverride(env: NodeJS.ProcessEnv): boolean { } function isEnvironment(value: unknown): value is LocalConnectorEnvironment { - return value === "development" || value === "staging" || value === "production"; + return value === "development" || value === "test" || value === "staging" || value === "production"; } function normalizeBrokerOrigin(value: string): string { diff --git a/server/src/services/paperclip-cloud-connector.test.ts b/server/src/services/paperclip-cloud-connector.test.ts index a2a0b37abe0..999236c6c54 100644 --- a/server/src/services/paperclip-cloud-connector.test.ts +++ b/server/src/services/paperclip-cloud-connector.test.ts @@ -55,6 +55,24 @@ function config() { } describe("Paperclip Cloud connector", () => { + it("accepts a managed test identity and rejects other environments at its broker", () => { + const identity = config().config; + const env = { + PAPERCLIP_CLOUD_CONNECTOR_INSTANCE_ID: identity.instanceId, + PAPERCLIP_CLOUD_CONNECTOR_SIGN_PRIVATE_KEY: identity.signPrivateKey, + PAPERCLIP_CLOUD_CONNECTOR_SEAL_PRIVATE_KEY: identity.sealPrivateKey, + PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: "https://test.paperclip.app", + PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT: "test", + }; + expect(paperclipCloudConnectorConfigFromEnv(env)).toMatchObject({ environment: "test", baseUrl: env.PAPERCLIP_CLOUD_CONNECTOR_BASE_URL }); + for (const environment of ["development", "staging", "production"]) { + expect(() => paperclipCloudConnectorConfigFromEnv({ ...env, PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT: environment })).toThrow(/broker and environment do not match/); + } + for (const baseUrl of ["https://my.paperclip.app", "https://my-staging.paperclip.app"]) { + expect(() => paperclipCloudConnectorConfigFromEnv({ ...env, PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: baseUrl })).toThrow(/broker and environment do not match/); + } + }); + async function rejection(response: Response) { const connector = createPaperclipCloudConnector({ config: config().config, diff --git a/server/src/services/paperclip-cloud-connector.ts b/server/src/services/paperclip-cloud-connector.ts index 241ff937b57..3f330292dab 100644 --- a/server/src/services/paperclip-cloud-connector.ts +++ b/server/src/services/paperclip-cloud-connector.ts @@ -32,7 +32,7 @@ export const GMAIL_CONNECTOR_SCOPES = [ ] as const; export { GOOGLE_WORKSPACE_CONNECTOR_PROFILES }; -export type PaperclipCloudConnectorEnvironment = "development" | "staging" | "production"; +export type PaperclipCloudConnectorEnvironment = "development" | "test" | "staging" | "production"; export type PaperclipCloudConnectorOperation = "status" | "session" | "claim" | "refresh" | "revoke" | "webhook-bind" | "event-lease" | "event-ack" | "github-app"; export type PaperclipCloudConnectorProfileId = GoogleWorkspaceConnectorProfileId | GitHubConnectorProfileId | AsanaConnectorProfileId | "slack.manager" | "github.bot"; export type PaperclipCloudConnectorProvider = "google" | "github" | "asana" | "slack"; @@ -232,7 +232,7 @@ export function paperclipCloudConnectorConfigFromEnv( if (values.some((value) => !value)) { throw new PaperclipCloudConnectorError("Paperclip Cloud connector configuration is incomplete", "CONNECTOR_CONFIG_INCOMPLETE"); } - if (environment !== "development" && environment !== "staging" && environment !== "production") { + if (environment !== "development" && environment !== "test" && environment !== "staging" && environment !== "production") { throw new PaperclipCloudConnectorError("Paperclip Cloud connector environment is invalid", "CONNECTOR_CONFIG_INVALID"); } const parsedBaseUrl = new URL(baseUrl); @@ -244,6 +244,7 @@ export function paperclipCloudConnectorConfigFromEnv( } const brokerHost = parsedBaseUrl.hostname.toLowerCase(); if ((brokerHost === "my.paperclip.app" && environment !== "production") + || (brokerHost === "test.paperclip.app" && environment !== "test") || (brokerHost === "my-staging.paperclip.app" && environment !== "staging")) { throw new PaperclipCloudConnectorError( "Paperclip Cloud connector broker and environment do not match", diff --git a/ui/src/api/tools.ts b/ui/src/api/tools.ts index 43cb010466d..1702478e54d 100644 --- a/ui/src/api/tools.ts +++ b/ui/src/api/tools.ts @@ -106,7 +106,7 @@ export type CloudConnectorEnrollmentStatus = { status: "not_configured" | "unenrolled" | "pending" | "active" | "suspended" | "unverified"; brokerBaseUrl: string; instanceId: string | null; - environment: "development" | "staging" | "production"; + environment: "development" | "test" | "staging" | "production"; origins: string[]; verificationUrl?: string; expiresAt?: string; From 7d2bb5aaabf633eb41c132e0f18fae820e303c09 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Mon, 12 Oct 2026 19:43:04 -0700 Subject: [PATCH 3/4] docs(release): stable notes for the 2026.1012.1-beta.0 soak (v2026.1015.0) (#16053) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The release process promotes a soaked beta to stable. The stable GitHub Release body comes from `releases/beta/v.md` on `master` > - The `draft_stable_notes` job pushed a commit-log skeleton to this branch when `2026.1012.1-beta.0` published today (workflow run 38311557959) > - The skeleton is grouped commit subjects. It is not in release-notes voice and it does not tell a self-hoster what to do before an upgrade > - This pull request replaces the skeleton with curated notes. Each claim traces to a pull request in `v2026.1012.0..1b4c8038` > - The benefit is that the earliest stable promotion (v2026.1015.0) finds finished notes on `master` on time ## Linked Issues or Issue Description **Issue type** Missing content **Where is the issue?** `releases/beta/v2026.1012.1-beta.0.md` — the stable-notes file for the `2026.1012.1-beta.0` soak. It exists only on this machine-owned branch and only as the auto-generated skeleton. **What's wrong?** The stable promotion reads this file from `master` and publishes it as the GitHub Release body. Until this branch merges, the stable preflight has no notes to resolve. The skeleton lists raw commit subjects with nested PR summaries. It does not call out the three changes that need operator attention before the upgrade. **Suggested fix** Merge the curated notes so the notes invariant holds for the v2026.1015.0 promotion. Correct the `> Released:` date in a follow-up if the promotion date slips. ## What Changed - Replaced the skeleton in `releases/beta/v2026.1012.1-beta.0.md` with curated stable notes for v2026.1015.0 in the same layout as `releases/beta/v2026.1006.0-beta.0.md`: overview, Breaking Changes, Highlights, Fixes, Improvements, Upgrade Guide, Contributors - Breaking Changes lists the expanded Operator role preset (#15377), GitHub review bots behind a new default-off experimental setting (#15416), and budget policies that hold on unpriced usage by default (#14997) - Upgrade Guide enumerates migrations `0306` through `0333` with one-phrase effects, the three new environment variables with defaults, the three new default-off experimental settings, the new agent identity key variables for custom adapters, and the private-task API behavior - Release and CI internals, smoke specs, canary tooling, Paperclip Cloud work, heartbeat refactors, dependency bumps, and test-only changes are left out. Features already described in the v2026.1012.0 notes are not repeated ## Verification - Beta publish is complete: npm dist-tag `beta` is `2026.1012.1-beta.0`, tag `beta/v2026.1012.1-beta.0` points at `1b4c803816e82b0265c8422e1a804327a7ed8c54`, and the `publish_beta`, `smoke_beta`, and `draft_stable_notes` jobs in run 38311557959 all succeeded - `git rev-list --count v2026.1012.0..1b4c803816e82b0265c8422e1a804327a7ed8c54` returns 200, which matches the overview and Contributors section - `git shortlog -sn --no-merges` over the same range shows 6 human authors after the two bot accounts are excluded. The two external handles were read from the GitHub commit authors, not guessed from names - `./scripts/release.sh stable --date 2026-10-15 --print-version` returns `2026.1015.0` - Every `#NNNN` link in the file resolves to a pull request inside the range. Migration numbers, setting keys, env var names, and defaults were checked in the code on the source commit, not in commit subjects - The file contains no internal ticket ids or instance-local links - The compare view for this branch shows one added file and no source changes ## Risks - Low risk: a single markdown file, no source changes. If the promotion date slips past 2026-10-15, the `> Released:` line and H1 need a one-line update before the stable dispatch. The beta-keyed filename makes that re-date harmless ## Model Used - Claude (Anthropic), model ID `claude-fable-5-1` (Claude Fable 5.1), extended thinking enabled, tool use via Claude Code ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: devinfoley <139239+devinfoley@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 --- releases/beta/v2026.1012.1-beta.0.md | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 releases/beta/v2026.1012.1-beta.0.md diff --git a/releases/beta/v2026.1012.1-beta.0.md b/releases/beta/v2026.1012.1-beta.0.md new file mode 100644 index 00000000000..66ede73ab84 --- /dev/null +++ b/releases/beta/v2026.1012.1-beta.0.md @@ -0,0 +1,65 @@ +# Paperclip v2026.1015.0 + +> Released: 2026-10-15 + +Paperclip v2026.1015.0 carries 200 commits, promoted from `2026.1012.1-beta.0`. The headline is private work: a task or project can now be marked private and shared with named people and agents; everyone else cannot open it, find it in task lists or search, read its run transcripts, or reach it through a plugin. Around that: agents can run through your own model gateway (OpenRouter, Amazon Bedrock, Google Gemini, or any OpenAI Responses, Anthropic Messages, or Chat Completions endpoint), Slack bots set themselves up through Slack's Manifest API, the GitHub review bot gets a two-screen setup and a real **Paperclip Review** status check, Costs becomes an exact ledger that also shows monthly subscription fees, and connected assistants can edit tasks, upload files, and configure agents over MCP. Underneath, OpenAI Dot and Pi 1.0 join the experimental Runner, native OpenCode streams as it types, each agent gets a persistent cryptographic identity, and idle instances stop polling the database every second. + +## Breaking Changes + +- **Operators can now edit the company.** The Operator role preset grows from one grant (`tasks:assign`) to fourteen: agent creation and configuration, skills, environments, invitations, task assignment, pipelines, connections, tool management and use, and audit reads. Existing Operators pick up the missing grants through the normal startup seeding; custom grant scopes are kept. Join approval and member-permission management stay out of the preset, and inviting an Admin now requires join approval while inviting an Owner also requires member-permission management. Review your member roles if Operators were meant to be read-mostly. ([#15377](https://github.com/paperclipai/paperclip/pull/15377)) +- **GitHub review bots move behind their own experimental setting.** The **GitHub Code Review Bot** catalog entry, its setup, and its management screens appear only while **Settings → Experimental → GitHub review bots** (`enableGitHubReviewBots`, off by default) is on; existing bots keep running while it is off. The old setup-method and delegated-agent screens are gone, and per-person automatic-run flags are replaced by author include/exclude filters (legacy `people[].automaticReviews` values are read but ignored). ([#15416](https://github.com/paperclipai/paperclip/pull/15416)) +- **Budgets hold on unpriced usage by default.** Each budget policy gains an unpriced-usage setting that defaults to `block`: while a run's usage has no reliable price, the policy counts as at its limit until the charge is resolved through the audited cost adjustment flow. Set the policy to `allow` to keep running on unpriced usage. ([#14997](https://github.com/paperclipai/paperclip/pull/14997)) + +## Highlights + +- **Private tasks and projects** — Mark a task private when you create it, or make a project private with its own member list; a personal private project is created for you on first use. Private work is readable only by its responsible user, creator, current assignee, named grantees, and private-project members, and that rule applies to task lists and task counts, search, activity, run detail, transcripts, events, and logs, documents, attachments, work products, and every plugin read. Two surfaces stay company-wide on purpose: dashboard and sidebar totals may count private work without exposing a title or identifier, and the company run list shows a locked row for a private task's run with only the agent, status, timing, token, and cost fields, so budget oversight keeps working. Privacy flows down to children; sharing a child never exposes its parent or siblings. Owners and admins do not see private tasks by default and must request `?breakGlass=true`, which writes an audit row and a warning notice on the task. Agents' access is intersected with the run's responsible user, so reusing an agent does not carry another person's permissions. ([#14717](https://github.com/paperclipai/paperclip/pull/14717), [#10633](https://github.com/paperclipai/paperclip/pull/10633), [#14718](https://github.com/paperclipai/paperclip/pull/14718)) +- **Bring your own model gateway** — Connectors gains rows for OpenRouter, Amazon Bedrock, Google Gemini, and generic Responses API, Messages API, Chat Completions API, and local endpoints. URLs, protocol, region, and credentials live on the connection; the agent's Harness / Runtime page picks a compatible **Connection** from a dropdown and the model picker accepts manual IDs. Codex (legacy and Runner) routes to OpenRouter or custom Responses endpoints, Claude (legacy and Runner) to OpenRouter, custom Messages endpoints, or Bedrock, and OpenCode and Hermes to OpenRouter or custom Chat Completions endpoints. OpenRouter connections without an explicit model list load OpenRouter's public catalog. ([#14970](https://github.com/paperclipai/paperclip/pull/14970), [#15341](https://github.com/paperclipai/paperclip/pull/15341)) +- **Slack bots set themselves up** — New Slack bot drafts default to automatic setup: paste a temporary App configuration token, Paperclip creates the app through Slack's Manifest API, installs it through OAuth, links your Slack account, uploads the agent's avatar, and verifies delivery in a four-screen wizard with no Slack CLI involved ([#15413](https://github.com/paperclipai/paperclip/pull/15413)). A new **Require at-mention** setting stops thread replies from waking the agent without an explicit mention ([#15612](https://github.com/paperclipai/paperclip/pull/15612)); a follow-up sent after a Slack Stop starts a fresh turn instead of stalling ([#15737](https://github.com/paperclipai/paperclip/pull/15737)); and Slack agents now know the saved slash command people use to link their accounts, link every task they create, and report results and review requests back in the originating thread ([#15576](https://github.com/paperclipai/paperclip/pull/15576), [#15579](https://github.com/paperclipai/paperclip/pull/15579), [#15611](https://github.com/paperclipai/paperclip/pull/15611)). +- **A two-screen GitHub review bot** — Setup is now Choose agent, then Connect GitHub under a personal account or organization; GitHub asks for creation and installation consent, and Paperclip stores the App secrets locally, imports repository access, and verifies delivery. Each accepted request gets one editable App-owned progress comment, and review assessments publish a **Paperclip Review** check for the reviewed commit with a configurable passing score (1–5) that branch protection can require. A self-hosted instance needs one Paperclip Cloud approval before its first managed GitHub connection, because Cloud hosts the fixed OAuth callback and signed webhook inbox; tokens are sealed to your instance. ([#15416](https://github.com/paperclipai/paperclip/pull/15416)) +- **Costs you can reconcile** — Accounting is now a durable, exact ledger: usage receipts are fsynced and replayed after failures, retries settle one charge, amounts keep decimal cents, and every event is marked reported, estimated, or unpriced instead of silently zero ([#14991](https://github.com/paperclipai/paperclip/pull/14991), [#14997](https://github.com/paperclipai/paperclip/pull/14997)). The Costs page reports each connected ChatGPT or Claude subscription's monthly fee once beside API spend, lets you enter the real price, and keeps ambiguous plans unpriced ([#15634](https://github.com/paperclipai/paperclip/pull/15634)); quota reads are scoped to authorized company accounts ([#14994](https://github.com/paperclipai/paperclip/pull/14994)); cost gaps are listed by agent with links to the affected runs, the model the runtime actually selected is recorded, and a retry appears only when saved evidence is ready ([#15948](https://github.com/paperclipai/paperclip/pull/15948)); and historical pricing gaps show as a neutral note under the totals rather than a red error ([#15459](https://github.com/paperclipai/paperclip/pull/15459)). +- **Assistants that can do more over MCP** — A connected Claude Code, Codex, or other MCP assistant can now edit tasks, finish or block them, write documents and revisions, register deliverables, upload files through expiring tickets, and, with a separately consented configuration scope, manage agent settings and instructions, projects and repositories, and skills ([#15380](https://github.com/paperclipai/paperclip/pull/15380)). Connections are named by owner and client ("Dotta's Codex connection") with the authorizing person's avatar, revoked rows disappear, and the copied invitation is shorter ([#15411](https://github.com/paperclipai/paperclip/pull/15411)). +- **Sixteen more apps in the catalog** — Gauge ([#15596](https://github.com/paperclipai/paperclip/pull/15596)), Superagent ([#15394](https://github.com/paperclipai/paperclip/pull/15394)), Telem.AI web search ([#15379](https://github.com/paperclipai/paperclip/pull/15379)), Enterpret ([#13906](https://github.com/paperclipai/paperclip/pull/13906)), Calendly, Exa, Firecrawl, GSC Wizard, Parallel Search, Tavily, and Windsor.ai ([#15621](https://github.com/paperclipai/paperclip/pull/15621)), and Bitly, Context7, Hugging Face, and Resend ([#15686](https://github.com/paperclipai/paperclip/pull/15686)) join as verified hosted MCP connections. Sentry sign-in discovers OAuth through its MCP resource metadata, so setup no longer asks for a client ID and secret ([#15837](https://github.com/paperclipai/paperclip/pull/15837)), and OAuth grants now record whether each scope came from the provider or from Paperclip's request ([#14059](https://github.com/paperclipai/paperclip/pull/14059)). +- **New Runner providers** — OpenAI Dot joins as a standalone experimental agent choice: pair your Dot with a one-use code, and the Runner delivers assignments over MCP Events while Dot uses its own tools and reports back ([#15402](https://github.com/paperclipai/paperclip/pull/15402), [#15414](https://github.com/paperclipai/paperclip/pull/15414), [#15581](https://github.com/paperclipai/paperclip/pull/15581), [#15734](https://github.com/paperclipai/paperclip/pull/15734)). Pi 1.0 runs through the experimental Runner with explicit provider and model selection ([#14921](https://github.com/paperclipai/paperclip/pull/14921)), and the Cursor Runner integration is consolidated onto mainline recovery behavior with a public install path ([#15075](https://github.com/paperclipai/paperclip/pull/15075)). +- **Codex and OpenCode that just start** — Native Codex uses the installed Codex CLI and accepts usable older or newer versions, so a clean npm install no longer needs a global command ([#15555](https://github.com/paperclipai/paperclip/pull/15555)); a model that the sandbox's Codex CLI is too old for is swapped for a compatible one with a visible warning instead of failing every turn ([#15399](https://github.com/paperclipai/paperclip/pull/15399), [#15519](https://github.com/paperclipai/paperclip/pull/15519)); saved runs survive server upgrades ([#15518](https://github.com/paperclipai/paperclip/pull/15518)); and task environment variables reach later turns and tool commands ([#15451](https://github.com/paperclipai/paperclip/pull/15451)). Native OpenCode streams text as it arrives ([#15708](https://github.com/paperclipai/paperclip/pull/15708)), starts without the postinstall script that package managers skip ([#15702](https://github.com/paperclipai/paperclip/pull/15702)), and selects the bundled Linux assets for remote runs without path overrides ([#15721](https://github.com/paperclipai/paperclip/pull/15721)). +- **Reports you can open on the task** — HTML attachments and workspace `.html` files render in an opaque-origin sandbox with view-source and download controls ([#15447](https://github.com/paperclipai/paperclip/pull/15447)), and CSV files open as tables with row numbers, sticky headers, and record counts ([#15445](https://github.com/paperclipai/paperclip/pull/15445)). +- **Clearer task provenance and waits** — A follow-up task an agent creates while working on a task now defaults to that task as its parent, and the task page shows "Created from by " ([#15762](https://github.com/paperclipai/paperclip/pull/15762)); native agents can schedule their own one-shot task monitor and yield until it fires ([#15446](https://github.com/paperclipai/paperclip/pull/15446)); the monitor card gets an X with a confirmation ([#15735](https://github.com/paperclipai/paperclip/pull/15735)); and a saved pull request stays visible in Properties with its review request and a **Check status** action even when the provider lookup fails ([#15375](https://github.com/paperclipai/paperclip/pull/15375)). +- **Your primary agent** — Each person gets one primary agent per company, shown with a crown on the roster and used as the fallback for new tasks and Chat when there is no recent choice; the first agent you create becomes primary automatically and can be changed on the agent profile ([#15470](https://github.com/paperclipai/paperclip/pull/15470)). +- **Company decision models and fast responses** — Company Settings can bind one shared OpenAI or OpenRouter API connection for small typed model decisions, with permission checks, budget reservations, and a Costs → Decisions history ([#15473](https://github.com/paperclipai/paperclip/pull/15473)). On top of it, **Experimental fast responses** posts a short acknowledgement comment within three seconds of a human message while the agent starts its normal run, in tasks, Agent Chat, and Slack and GitHub threads ([#15748](https://github.com/paperclipai/paperclip/pull/15748)). + +## Fixes + +- **Workspaces and sandboxes** — A transient sandbox copy-back failure is retried up to three times before the task is held for repair, and remaining holds explain what happened in plain language ([#15920](https://github.com/paperclipai/paperclip/pull/15920)); a remote sandbox whose required copy-back failed is kept, with its files, behind an explicit repair decision instead of being deleted by cleanup ([#15479](https://github.com/paperclipai/paperclip/pull/15479), [#15790](https://github.com/paperclipai/paperclip/pull/15790)); server shutdown waits for a legacy adapter's workspace export before releasing its sandbox lease ([#15857](https://github.com/paperclipai/paperclip/pull/15857)); a reused saved worktree keeps its original repository when project settings change ([#15528](https://github.com/paperclipai/paperclip/pull/15528)); an accepted native result is no longer mistaken for lost work by the orphan reaper after a restart ([#15718](https://github.com/paperclipai/paperclip/pull/15718)); OpenCode runs in the realized workspace via `--dir` ([#11438](https://github.com/paperclipai/paperclip/pull/11438)); Daytona OpenCode sessions keep their deadlines ([#15905](https://github.com/paperclipai/paperclip/pull/15905)); and OpenCode timeouts no longer claim the configured deadline expired ([#15789](https://github.com/paperclipai/paperclip/pull/15789)). +- **Runs, recovery, and questions** — A retry no longer burns itself while a stopped run's environment is still cleaning up, and settled repair instructions stop reappearing in later wakes ([#15463](https://github.com/paperclipai/paperclip/pull/15463)); a review wait whose automatic repair also leaves no wake path now records a board decision instead of sitting silently ([#15764](https://github.com/paperclipai/paperclip/pull/15764)); a parent is woken again when a child finishes requested revisions ([#15372](https://github.com/paperclipai/paperclip/pull/15372)); native runs resume cleanly after connection approval decisions with final usage retained ([#15471](https://github.com/paperclipai/paperclip/pull/15471), [#15420](https://github.com/paperclipai/paperclip/pull/15420), [#15514](https://github.com/paperclipai/paperclip/pull/15514)); a pending tool request expires on its own when the user has already connected the service in Apps ([#15856](https://github.com/paperclipai/paperclip/pull/15856)); agents stop re-asking questions that moved into feed history ([#15392](https://github.com/paperclipai/paperclip/pull/15392)); archiving a task during its completion no longer deadlocks ([#15615](https://github.com/paperclipai/paperclip/pull/15615)); and a task policy with a monitor but no stages no longer crashes the task page ([#15593](https://github.com/paperclipai/paperclip/pull/15593)). +- **Adapters** — Claude CLI sessions resume across per-run agent file copies ([#15437](https://github.com/paperclipai/paperclip/pull/15437)); Hermes gets managed instructions through its system overlay instead of a copy in every resumed user turn ([#15439](https://github.com/paperclipai/paperclip/pull/15439)); and external agents such as Dot are told to use their own tools when a Paperclip app is missing ([#15734](https://github.com/paperclipai/paperclip/pull/15734)). +- **Connections and MCP** — One oversized or malformed MCP reply fails only that call instead of locking the whole connection in an error state ([#15462](https://github.com/paperclipai/paperclip/pull/15462)); remote MCP callbacks keep an explicitly saved empty agent selection, token exchange sends `Accept: application/json`, and saved hidden-provider drafts resume from Finish setup ([#15686](https://github.com/paperclipai/paperclip/pull/15686)); Airtable, Calendly, and other verified providers get the registration and form fixes found in live testing ([#15621](https://github.com/paperclipai/paperclip/pull/15621)); and the sidebar keeps its last plugin items quiet when the server is unreachable ([#15588](https://github.com/paperclipai/paperclip/pull/15588)). + +## Improvements + +- Hiring, approval, pause, resume, and termination run through a durable agent lifecycle with explicit states: proposed hires stay pending until approval, approved hires are prepared and have their harness configuration verified before work, required plugins get callbacks at each step, and the agent page shows lifecycle progress with a retry action ([#15631](https://github.com/paperclipai/paperclip/pull/15631), [#15391](https://github.com/paperclipai/paperclip/pull/15391)); plugins can consume a durable resource lifecycle journal with acknowledgments, seeded with a one-time baseline of existing agents and projects, and project archive and restore now write journal events ([#15343](https://github.com/paperclipai/paperclip/pull/15343), [#15371](https://github.com/paperclipai/paperclip/pull/15371)). +- Every agent gets a persistent Ed25519 keypair, created on hire or on its next managed run, encrypted at rest and handed to managed processes as `PAPERCLIP_AGENT_KEY_ID`, `PAPERCLIP_AGENT_PUBLIC_KEY`, and `PAPERCLIP_AGENT_PRIVATE_KEY`; the public key appears on the agent overview ([#15352](https://github.com/paperclipai/paperclip/pull/15352)). +- Agents can file complaints and suggestions about their tooling into the instance's `agent_commentary` table through new `complain` and `suggestion-box` skills or native `submit_complaint` and `submit_suggestion` tools, keeping that feedback out of task threads ([#15367](https://github.com/paperclipai/paperclip/pull/15367)). +- Idle instances go quiet: chat delivery, provider action, Slack sync, email, Browser Use cleanup, and five server delivery queues now wake from committed writes and known deadlines instead of scanning the database every second or three ([#15625](https://github.com/paperclipai/paperclip/pull/15625), [#15750](https://github.com/paperclipai/paperclip/pull/15750), [#15757](https://github.com/paperclipai/paperclip/pull/15757), [#15802](https://github.com/paperclipai/paperclip/pull/15802), [#15808](https://github.com/paperclipai/paperclip/pull/15808), [#15822](https://github.com/paperclipai/paperclip/pull/15822)); hosts that park idle instances get an idle-sleep safety report that fences accepted work, drains plugin workers, and treats finished logins, consumed retries, and verified delivery history as history rather than pending work ([#15522](https://github.com/paperclipai/paperclip/pull/15522), [#15599](https://github.com/paperclipai/paperclip/pull/15599), [#15761](https://github.com/paperclipai/paperclip/pull/15761), [#15851](https://github.com/paperclipai/paperclip/pull/15851), [#15897](https://github.com/paperclipai/paperclip/pull/15897), [#15907](https://github.com/paperclipai/paperclip/pull/15907)), with an opt-in final backup checkpoint before sleep ([#15629](https://github.com/paperclipai/paperclip/pull/15629)). +- Task notifications show the actor's name and avatar and hide your own actions ([#15695](https://github.com/paperclipai/paperclip/pull/15695)); other people's comments in task chat appear on the left with their name and avatar, and only yours are on the right in blue ([#15494](https://github.com/paperclipai/paperclip/pull/15494)); an agent blocked by an AI connection its responsible user cannot use shows an expanded error naming the agent and connection with a link to its AI settings ([#15741](https://github.com/paperclipai/paperclip/pull/15741)); and a teammate without the agent's required personal AI account can connect their own from inside the chat and resume the message ([#15376](https://github.com/paperclipai/paperclip/pull/15376)). +- The task composer keeps its model label steady while the effort slider moves and the slider is larger ([#15444](https://github.com/paperclipai/paperclip/pull/15444)), nested selectors scroll with the wheel and touch ([#15374](https://github.com/paperclipai/paperclip/pull/15374)), the project picker always offers **No project** ([#15382](https://github.com/paperclipai/paperclip/pull/15382)), routine descriptions keep code-block pastes in the code block and gain a copy control ([#15773](https://github.com/paperclipai/paperclip/pull/15773)), confirmation cards stack the prompt above the target pill and hide unlabeled machine keys ([#15895](https://github.com/paperclipai/paperclip/pull/15895)), chat connection and recovery cards share one surface ([#15771](https://github.com/paperclipai/paperclip/pull/15771)), the Slack chat-or-tools chooser gets icons ([#15574](https://github.com/paperclipai/paperclip/pull/15574)), the Workspace filter leaves the task and inbox filter menus ([#15788](https://github.com/paperclipai/paperclip/pull/15788)), and the star and leave buttons leave the agent index ([#15400](https://github.com/paperclipai/paperclip/pull/15400)). +- Tasks whose starting branch does not exist in the repository show a branch repair card that offers the remote's default branch or a typed branch and retries through the normal admission gates ([#15726](https://github.com/paperclipai/paperclip/pull/15726)); a GitHub reference the instance's credentials cannot see is reported as inaccessible and re-checked instead of frozen as "Not found" ([#15906](https://github.com/paperclipai/paperclip/pull/15906)); and the Paperclip skill ships its own issue-update helper and honors a harness checkout instead of demanding a second one ([#15554](https://github.com/paperclipai/paperclip/pull/15554), [#15548](https://github.com/paperclipai/paperclip/pull/15548)). +- Workspace jobs that exit non-zero return `422 workspace_job_failed` with the job's own output instead of a 500 ([#15941](https://github.com/paperclipai/paperclip/pull/15941)); custom source launchers get the native Runner WebSocket transport from the shared HTTP server constructor ([#15682](https://github.com/paperclipai/paperclip/pull/15682)); a refused local Hermes Gateway connection explains which host to check ([#15520](https://github.com/paperclipai/paperclip/pull/15520)); and pi-local's unavailable-model error no longer dumps the model catalog ([#13628](https://github.com/paperclipai/paperclip/pull/13628)). +- Failure reports carry bounded evidence for Daytona acquisition timeouts and provider state, workspace base-ref resolution, sandbox transfer and sync-out errors, orphan process loss, ACP bridge terminal frames, Git worktree inspection, and stackless browser rejections ([#15946](https://github.com/paperclipai/paperclip/pull/15946), [#15736](https://github.com/paperclipai/paperclip/pull/15736), [#15719](https://github.com/paperclipai/paperclip/pull/15719), [#15637](https://github.com/paperclipai/paperclip/pull/15637), [#15481](https://github.com/paperclipai/paperclip/pull/15481), [#15475](https://github.com/paperclipai/paperclip/pull/15475), [#15478](https://github.com/paperclipai/paperclip/pull/15478), [#15525](https://github.com/paperclipai/paperclip/pull/15525), [#15940](https://github.com/paperclipai/paperclip/pull/15940)), while proven configuration blockers such as a missing user secret, an unchosen AI account, a credential not shared with the responsible user, a local-directory project with a worktree policy, or an external service outage stay out of Sentry ([#15472](https://github.com/paperclipai/paperclip/pull/15472), [#15618](https://github.com/paperclipai/paperclip/pull/15618), [#15714](https://github.com/paperclipai/paperclip/pull/15714), [#15521](https://github.com/paperclipai/paperclip/pull/15521), [#15590](https://github.com/paperclipai/paperclip/pull/15590)). + +## Upgrade Guide + +- **Migrations `0306`–`0333`** run on upgrade: `0306` allows `google` in the AI account-default provider checks; `0307` rewrites the lifecycle-event action constraint; `0308` adds `agent_identity_keys`; `0309` adds `plugin_lifecycle_acknowledgments`, indexes the lifecycle journal, and seeds a one-time baseline event per existing agent and project; `0310` adds `agent_commentary`; `0311` adds MCP attachment upload and file ticket tables; `0312` adds accounting receipt, reservation, adjustment, invoice, and provider snapshot tables, widens cost columns to `numeric`, and adds the budget unpriced-usage policy (default `block`); `0313` adds `issue_access_grants` and `project_access_members`, adds `visibility` to tasks and projects (default `open`), and backfills privacy parent edges and run bindings in 1,000-row batches; `0314` binds historical inline draft images to their first task; `0315` adds `user_company_preferences` and backfills a primary agent from history; `0316` adds decision model tables; `0317` adds OpenAI Dot binding, mailbox, and assignment tables; `0318` adds `chat_slack_registrations`; `0319` makes Dot agent refresh tokens non-expiring; `0320` adds agent lifecycle columns and derives `lifecycle_state` from each agent's current status; `0321` adds `chat_endpoints.require_at_mention`; `0322` adds AI subscription account and price tables; `0323` adds GitHub registration owner columns; `0324`–`0330` add the experimental voice session tables; `0331` adds fast-response tables and `issue_comments.origin`; `0332` adds a question-history index concurrently; `0333` adds cost data-quality indexes. `0313` and `0314` run outside a single transaction and commit each batch, so an interrupted upgrade leaves them pending and safe to rerun; expect them to take longer on instances with large run histories. +- **New environment variables**, none required: `PAPERCLIP_ISSUE_PRIVACY_MODE` (default `enforce`; `shadow` logs would-deny decisions without enforcing, `off` disables the predicate) ([#10633](https://github.com/paperclipai/paperclip/pull/10633)); `PAPERCLIP_DB_BACKUP_IDLE_CHECKPOINT_ENABLED=1` opts an instance with automatic backups into taking a final verified backup before idle sleep (off by default) ([#15629](https://github.com/paperclipai/paperclip/pull/15629)); `PAPERCLIP_RESPONSIBLE_USER_AUTHZ_CACHE_TTL_MS` bounds the responsible-user permission snapshot (default `5000`) ([#10633](https://github.com/paperclipai/paperclip/pull/10633)). +- **New experimental settings**, all off by default: **GitHub review bots** (`enableGitHubReviewBots`) shows GitHub bot setup and management ([#15416](https://github.com/paperclipai/paperclip/pull/15416)); **OpenAI Dot** (`enableOpenAiDot`) adds Dot as an agent choice and requires **Assistant connections (MCP)**, authenticated sign-in, and an HTTPS `PAPERCLIP_PUBLIC_URL` ([#15414](https://github.com/paperclipai/paperclip/pull/15414)); **Experimental fast responses** (`enableFastResponses`) reveals Company Settings → Connections and allows acknowledgement comments ([#15748](https://github.com/paperclipai/paperclip/pull/15748)). Decision model setup lives under Company Settings → General until fast responses are on ([#15473](https://github.com/paperclipai/paperclip/pull/15473)). +- **Review Operator members** ([#15377](https://github.com/paperclipai/paperclip/pull/15377)): Operators now hold company editing, invitation, connection, tool, and audit grants after startup seeding. Demote members who should not configure agents or connect accounts. +- **Budget policies and unpriced usage** ([#14997](https://github.com/paperclipai/paperclip/pull/14997)): existing policies get `unpricedUsagePolicy: block`. If your agents run on providers whose prices Paperclip cannot determine, set the policy to `allow` or resolve unpriced events through the cost adjustment flow so the budget does not hold them. +- **Custom adapters and managed processes** ([#15352](https://github.com/paperclipai/paperclip/pull/15352)): managed runs now receive `PAPERCLIP_AGENT_KEY_ID`, `PAPERCLIP_AGENT_PUBLIC_KEY`, and `PAPERCLIP_AGENT_PRIVATE_KEY`; the key values are multiline PEM strings and the private key must not be logged. The wrapping key is `PAPERCLIP_SECRETS_MASTER_KEY` or the local secrets master-key file; a wrong master key fails the run rather than regenerating the keypair. +- **Private-task callers** ([#10633](https://github.com/paperclipai/paperclip/pull/10633)): direct reads of a private task by a non-member return `404`; owners and admins must pass `?breakGlass=true` for an audited emergency read; plugins are non-member principals and see no private content. +- **Slack and GitHub drafts**: existing Slack bot drafts without a saved setup method stay on manual setup ([#15413](https://github.com/paperclipai/paperclip/pull/15413)); existing GitHub bots, direct webhooks, and manual existing-App recovery keep working, but their settings are hidden until **GitHub review bots** is enabled ([#15416](https://github.com/paperclipai/paperclip/pull/15416)). +- **Harness pins** are unchanged for Codex, Claude, and OpenCode. Native Codex now prefers the Codex CLI installed with the server package and tolerates version drift; a sandbox image whose Codex CLI is too old for the selected model is switched to a compatible model with a warning ([#15555](https://github.com/paperclipai/paperclip/pull/15555), [#15519](https://github.com/paperclipai/paperclip/pull/15519)). +- No other configuration or API changes require action. + +## Contributors + +This release has 200 commits from 6 contributors. Thank you to everyone who contributed to this release! + +@lorenzozanee, @lusoris From e168512f7bdbb6af6ea01954c6d7ed80b91e903e Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:49:47 -0500 Subject: [PATCH 4/4] perf(chat): stop polling failed run retries (#15871) Stop one-second scans of failed chat-run retries. Wake the existing retry queue after transaction commits and at saved retry or stale-claim deadlines. Preserve claim fences, authorization, recovery, and idempotent agent admission. Resolve conflicts with the current chat-service extraction by applying the retry signals and shared deadline predicate in the failed-run retry module. Verified with pnpm typecheck, pnpm build, all 1,328 chat and coordinator regression tests, and current-head CI. Greptile reviewed the final head at 5/5 with no unresolved threads. Co-Authored-By: Paperclip --- ...-instance-sleep-and-scheduler-inventory.md | 9 +- .../app-private-hostname-gate.test.ts | 1 + server/src/__tests__/chat-action-work.test.ts | 5 +- .../chat-channels.integration.test.ts | 96 +++++++++++++++++++ server/src/services/chat-action-work.ts | 24 +++-- server/src/services/chat-channels.ts | 5 +- .../chat-channels/failed-run-retry.ts | 71 ++++++-------- .../src/services/chat-work-notifications.ts | 1 + .../services/delivery-work-notifications.ts | 1 + 9 files changed, 157 insertions(+), 56 deletions(-) diff --git a/doc/plans/2026-10-08-instance-sleep-and-scheduler-inventory.md b/doc/plans/2026-10-08-instance-sleep-and-scheduler-inventory.md index e40716791f3..8177461f43b 100644 --- a/doc/plans/2026-10-08-instance-sleep-and-scheduler-inventory.md +++ b/doc/plans/2026-10-08-instance-sleep-and-scheduler-inventory.md @@ -444,8 +444,13 @@ Longer polling intervals alone do not complete an item. running sessions and pauses without rescheduling until an endpoint change. Tests exercise real producers with deferred fast paths disabled, retry and startup recovery, stale-send quarantine, and zero scans after settlement. - Remaining **1s** maintenance: provider runtimes, Telegram maintenance, failed - chat-run retries, run milestone/wake-notice projection, GitHub operation + Failed chat-run retries now also use commit wakes and durable deadlines from + existing action rows. Issued retries honor `retryAt`; processing claims wait + for both stale-claim expiry and any saved retry time. Startup recovers unfinished + requests. Claim and settlement writes signal in their own short transactions, + with agent wakeup outside those transactions. Empty and terminal queues disarm. + Remaining **1s** maintenance: provider runtimes, Telegram maintenance, + run milestone/wake-notice projection, GitHub operation recovery, and Teams protocol maintenance. GitHub pre-ingress recovery must still inspect provider history: a lost callback has no local queue row. This slice does **not** yet make the whole chat subsystem quiet. Independent diff --git a/server/src/__tests__/app-private-hostname-gate.test.ts b/server/src/__tests__/app-private-hostname-gate.test.ts index 8f087241913..1c46075d2aa 100644 --- a/server/src/__tests__/app-private-hostname-gate.test.ts +++ b/server/src/__tests__/app-private-hostname-gate.test.ts @@ -110,6 +110,7 @@ describe("createChatReconciliationCoordinator", () => { const service = readFileSync(new URL("../services/chat-channels.ts", import.meta.url), "utf8"); const actionMaintenance = service.slice(service.indexOf("async function processPendingChatMaintenance("), service.indexOf("async function processPendingDeliveries(")); + expect(actionMaintenance).not.toContain("processFailedChatRunRetries("); expect(actionMaintenance).not.toContain("processPendingProviderEffects("); expect(actionMaintenance).not.toContain("processPendingGitHubWebhookIngress("); }); diff --git a/server/src/__tests__/chat-action-work.test.ts b/server/src/__tests__/chat-action-work.test.ts index b96541e79ee..294715f922f 100644 --- a/server/src/__tests__/chat-action-work.test.ts +++ b/server/src/__tests__/chat-action-work.test.ts @@ -19,6 +19,7 @@ function setup() { const coordinator = createDeliveryWorkCoordinator({ owner, canRun: enabled, canReconcile: enabled, onError }); coordinators.push(coordinator); const service = { + processFailedChatRunRetries: vi.fn(async () => {}), processPendingProviderEffects: vi.fn(async () => {}), processPendingGitHubWebhookIngress: vi.fn(async () => {}), processPendingSlackSessionSyncs: vi.fn(async () => {}), @@ -30,7 +31,7 @@ function setup() { nextChatActionAt: vi.fn(async (): Promise => null), nextVerificationMessageAt: vi.fn(async (): Promise => null), }; - const runs = [service.processPendingProviderEffects, service.processPendingGitHubWebhookIngress, service.processPendingSlackSessionSyncs, service.processPendingSlackBoardMessages, service.processPendingSlackTaskStarts, + const runs = [service.processFailedChatRunRetries, service.processPendingProviderEffects, service.processPendingGitHubWebhookIngress, service.processPendingSlackSessionSyncs, service.processPendingSlackBoardMessages, service.processPendingSlackTaskStarts, service.processPendingReceiptReactions, service.processPendingSlackSessionStops, service.processPendingVerificationMessages]; return { owner, service, runs, coordinator, enabled, onError, start: () => registerChatActionWork(coordinator, service, enabled) }; } @@ -40,7 +41,7 @@ describe("chat action scheduling", () => { await s.start().ready; await vi.advanceTimersByTimeAsync(3_600_000); for (const run of s.runs) expect(run).toHaveBeenCalledTimes(1); - expect(s.service.nextChatActionAt).toHaveBeenCalledTimes(7); + expect(s.service.nextChatActionAt).toHaveBeenCalledTimes(8); expect(s.service.nextVerificationMessageAt).toHaveBeenCalledTimes(1); expect(s.coordinator.nextWakeAt()).toBeNull(); expect(vi.getTimerCount()).toBe(0); diff --git a/server/src/__tests__/chat-channels.integration.test.ts b/server/src/__tests__/chat-channels.integration.test.ts index a550acedf8b..d8c86374466 100644 --- a/server/src/__tests__/chat-channels.integration.test.ts +++ b/server/src/__tests__/chat-channels.integration.test.ts @@ -1,3 +1,4 @@ +import { idleWorkSnapshot } from "../services/task-admission.js"; import * as activityLogModule from "../services/activity-log.js"; import { chatSlackManagerGrants, chatSlackRegistrations, toolOauthStates } from "@paperclipai/db"; import type { PaperclipCloudConnector, SealedConnectorCredentials } from "../services/paperclip-cloud-connector.js"; @@ -33026,6 +33027,26 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { await set("delivery_unknown"); expect(await f.service.nextChatActionAt(kind)).toBeNull(); expect(await due()).toBe(false); + } else if (kind === "failed_run_retry") { + expect(await due()).toBe(false); + await set("issued"); + expect(await due()).toBe(true); + await set("issued", { retryAt: later.toISOString() }); + expect(await f.service.nextChatActionAt(kind)).toBe(later.getTime()); + expect(await due()).toBe(false); + await set("processing"); + expect(await f.service.nextChatActionAt(kind)).toBe(at.getTime() + 60_000); + expect(await due()).toBe(false); + // Both the claim lease and retry time must expire before replay. + await set("processing", { retryAt: later.toISOString() }); + expect(await f.service.nextChatActionAt(kind)).toBe(later.getTime()); + await set("processing", { retryAt: at.toISOString() }); + expect(await f.service.nextChatActionAt(kind)).toBe(at.getTime() + 60_000); + for (const status of ["failed", "cancelled", "processed"]) { + await set(status, { retryable: true, retryAt: at.toISOString() }); + expect(await f.service.nextChatActionAt(kind)).toBeNull(); + expect(await due()).toBe(false); + } } else if (kind === "slack_session_sync") { expect(await due()).toBe(true); await set("received", { retryAt: later.toISOString() }); @@ -59779,6 +59800,81 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { }, ); + it.each(["commit", "retry", "startup", "stale_claim", "rollback"] as const)( + "drains failed chat retries from durable work and becomes idle: %s", + async (mode) => { + const context = await failedChatRetryFixture("telegram"); + const onError = vi.fn(); + const coordinator = createDeliveryWorkCoordinator({ owner: db, canRun: () => true, onError }); + const input = { companyId: context.fixture.companyId, issueId: context.issue.id, + agentId: context.fixture.assignedAgentId, failedRunId: context.runId, initiatedByUserId: "owner-user" }; + const stage = () => db.transaction(tx => context.service.prepareFailedChatRunRetry(tx, input)); + context.wakeup.mockClear(); + try { + let actionId: string; + if (mode === "startup" || mode === "stale_claim") { + ({ actionId } = await stage()); + if (mode === "stale_claim") await db.update(chatActions).set({ status: "processing", + updatedAt: new Date(Date.now() - 120_000) }).where(eq(chatActions.id, actionId)); + await registerChatActionWork(coordinator, context.service, () => true).ready; + } else { + await registerChatActionWork(coordinator, context.service, () => true).ready; + if (mode === "rollback") { + await expect(db.transaction(async tx => { + await context.service.prepareFailedChatRunRetry(tx, input); + throw new Error("rollback retry intent"); + })).rejects.toThrow("rollback retry intent"); + await vi.waitFor(() => { + expect(coordinator.nextWakeAt()).toBeNull(); + expect(idleWorkSnapshot().active).toBe(0); + }, { timeout: 10_000 }); + expect(context.wakeup).not.toHaveBeenCalled(); + } + if (mode === "retry") { + let failedAt = 0; + context.wakeup.mockImplementationOnce(async () => { + failedAt = Date.now(); + throw new Error("temporary admission outage"); + }).mockImplementationOnce(async () => { + expect(Date.now() - failedAt).toBeGreaterThanOrEqual(250); + return { accepted: true }; + }); + } + ({ actionId } = await db.transaction(async tx => { + const staged = await tx.transaction(nested => context.service.prepareFailedChatRunRetry(nested, input)); + // A nested savepoint must not wake the worker before the outer commit. + await new Promise(resolve => setTimeout(resolve, 25)); + expect(context.wakeup).not.toHaveBeenCalled(); + return staged; + })); + } + await vi.waitFor(async () => { + const [action] = await db.select().from(chatActions).where(eq(chatActions.id, actionId)); + expect(action?.status).toBe("processed"); + expect(action?.result.attemptCount).toBe(mode === "retry" ? 2 : 1); + expect(coordinator.nextWakeAt()).toBeNull(); + }, { timeout: 10_000 }); + expect(context.wakeup).toHaveBeenCalledTimes(mode === "retry" ? 2 : 1); + const receipts = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, actionId)); + expect(receipts).toHaveLength(1); + expect(receipts[0]?.payload).toMatchObject({ retryOfRunId: context.runId, + wakeCommentIds: [context.action.payload.commentId] }); + // A running worker has no scheduled deadline. Wait for its final scan + // and any commit-driven follow-up before measuring the quiet period. + await vi.waitFor(() => { + expect(coordinator.nextWakeAt()).toBeNull(); + expect(idleWorkSnapshot().active).toBe(0); + }, { timeout: 10_000 }); + const scans = vi.spyOn(db, "select"); + try { + await new Promise(resolve => setTimeout(resolve, 1200)); + expect(scans).not.toHaveBeenCalled(); + } finally { scans.mockRestore(); } + expect(onError).not.toHaveBeenCalled(); + } finally { await coordinator.stop(); await context.service.shutdown(); } + }, + ); + it.each(["slack", "telegram"] as const)( "retries the exact failed %s chat input with a new idempotent admission", async (provider) => { diff --git a/server/src/services/chat-action-work.ts b/server/src/services/chat-action-work.ts index f794cc6a0c2..6d1847526ed 100644 --- a/server/src/services/chat-action-work.ts +++ b/server/src/services/chat-action-work.ts @@ -8,6 +8,7 @@ import { registerChatQueueWork } from "./chat-delivery-work.js"; export const SLACK_COMMAND_POST_STALE_MS = 60_000; export const SLACK_COMMAND_EXPLICIT_RETRY_STALE_MS = 5 * 60_000; export const SLACK_COMMAND_ADMISSION_STALE_MS = 60_000; +export const FAILED_RUN_RETRY_STALE_MS = 60_000; export const PROVIDER_EFFECT_STALE_MS = 60_000; /** The dispatch predicate and scheduler use the same eligibility and deadlines. @@ -15,6 +16,9 @@ export const PROVIDER_EFFECT_STALE_MS = 60_000; export function chatActionDeadline(kind: ScheduledChatAction) { const updated = sql`extract(epoch from ${chatActions.updatedAt}) * 1000`; const retry = sql`coalesce(extract(epoch from (${chatActions.result}->>'retryAt')::timestamptz) * 1000, 0)`; + if (kind === "failed_run_retry") return sql`case + when ${chatActions.status} = 'issued' then ${retry} + when ${chatActions.status} = 'processing' then greatest(${updated} + ${FAILED_RUN_RETRY_STALE_MS}, ${retry}) end`; if (kind === "slack_board_message") return sql`case when ${chatActions.status} = 'received' and ${chatEndpoints.status} not in ('paused', 'attention') then 0::numeric end`; if (kind === "slash_task_start") return sql`case @@ -36,14 +40,16 @@ export function chatActionDeadline(kind: ScheduledChatAction) { function pendingChatAction(kind: ScheduledChatAction) { // Filter history before the endpoint join and aggregate. Failed actions // only remain live when the saved result permits another attempt. - const status = kind === "slack_board_message" - ? eq(chatActions.status, "received") - : kind === "slash_task_start" - ? inArray(chatActions.status, ["queued", "provider_confirmed", "admitting", "resolving", "validating"]) - : kind === "slack_session_sync" - ? inArray(chatActions.status, ["received", "processing"]) - : or(inArray(chatActions.status, ["received", "processing"]), - and(eq(chatActions.status, "failed"), sql`${chatActions.result}->>'retryable' = 'true'`)); + const status = kind === "failed_run_retry" + ? inArray(chatActions.status, ["issued", "processing"]) + : kind === "slack_board_message" + ? eq(chatActions.status, "received") + : kind === "slash_task_start" + ? inArray(chatActions.status, ["queued", "provider_confirmed", "admitting", "resolving", "validating"]) + : kind === "slack_session_sync" + ? inArray(chatActions.status, ["received", "processing"]) + : or(inArray(chatActions.status, ["received", "processing"]), + and(eq(chatActions.status, "failed"), sql`${chatActions.result}->>'retryable' = 'true'`)); return and(eq(chatActions.kind, kind), status); } @@ -60,6 +66,7 @@ export async function nextChatActionAt(db: Db, kind: ScheduledChatAction): Promi } type ChatActionService = { + processFailedChatRunRetries(): Promise; processPendingProviderEffects(): Promise; processPendingGitHubWebhookIngress(): Promise; processPendingSlackSessionSyncs(): Promise; @@ -78,6 +85,7 @@ export function registerChatActionWork( canRun: () => boolean, ) { const actions = [ + { kind: "failed_run_retry", run: service.processFailedChatRunRetries }, { kind: "provider_effect", run: service.processPendingProviderEffects }, { kind: "github_webhook_ingress", run: service.processPendingGitHubWebhookIngress }, { kind: "slack_session_sync", run: service.processPendingSlackSessionSyncs }, diff --git a/server/src/services/chat-channels.ts b/server/src/services/chat-channels.ts index 2bd74bbb9d6..7716efe06aa 100644 --- a/server/src/services/chat-channels.ts +++ b/server/src/services/chat-channels.ts @@ -5729,7 +5729,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { lifecycleRuntimeFence, isShuttingDown: () => shuttingDown, wakeup: (...args) => options.heartbeat.wakeup(...args), - staleProcessingMs: DELIVERY_PROCESSING_STALE_MS, }); const { stageTeamsBoardFileIntent, deriveTeamsFileTransferAuthority } = createTeamsFileAuthority({ runtimeGeneration, credentialFingerprint, runtime, runtimeContexts, localRuntimeEpoch, runtimeVersions, lockCurrentPrincipalAuthorization, lifecycleRuntimeFence, assertFailedRetryReceipt, inboundWakeScope, failedChatRetrySource, isExplicitOperatorPublication, retryCommentIds }); async function authorizeRetainedChatSourcePublication( @@ -15429,7 +15428,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { async function processPendingChatMaintenance(limit = 25) { return Promise.allSettled([ processPendingTelegramMaintenance(limit), - processFailedChatRunRetries(limit), ]); } @@ -15440,7 +15438,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { // so a crashed processing claim is quarantined before the delivery worker // could otherwise replay it. const actionRecovery = onlyDeliveryId || !maintenance ? null : Promise.allSettled([ - processPendingChatMaintenance(limit), processPendingGitHubWebhookIngress(limit), + processPendingChatMaintenance(limit), processFailedChatRunRetries(limit), + processPendingGitHubWebhookIngress(limit), processPendingProviderEffects(limit), processPendingSlackBoardMessages(limit), processPendingSlackTaskStarts(limit), processPendingReceiptReactions(limit), processPendingSlackSessionStops(limit), slackRegistration.processPendingVerificationMessages(limit), diff --git a/server/src/services/chat-channels/failed-run-retry.ts b/server/src/services/chat-channels/failed-run-retry.ts index c0b8d43988f..ae041ef9251 100644 --- a/server/src/services/chat-channels/failed-run-retry.ts +++ b/server/src/services/chat-channels/failed-run-retry.ts @@ -1,5 +1,6 @@ import { continuingTeamsFilePublication, type ContinuingTeamsFileTransfer } from "./microsoft-teams/file-transfer-source.js"; -import { notifyChatPublicationWork } from "../chat-work-notifications.js"; +import { notifyChatActionWork, notifyChatPublicationWork } from "../chat-work-notifications.js"; +import { FAILED_RUN_RETRY_STALE_MS, dueChatAction } from "../chat-action-work.js"; import { createHash, @@ -13,7 +14,6 @@ import { isNotNull, isNull, like, - lte, ne, notInArray, or, @@ -104,7 +104,6 @@ type ChatFailedRunRetryOptions = { lifecycleRuntimeFence: (delivery: DeliveryRow) => RetryRuntimeFence | null; isShuttingDown: () => boolean; wakeup: IssueAssignmentWakeupDeps["wakeup"]; - staleProcessingMs: number; }; export function createChatFailedRunRetry(db: Db, options: ChatFailedRunRetryOptions) { @@ -115,7 +114,6 @@ export function createChatFailedRunRetry(db: Db, options: ChatFailedRunRetryOpti lifecycleRuntimeFence, isShuttingDown, wakeup, - staleProcessingMs: DELIVERY_PROCESSING_STALE_MS, } = options; @@ -1265,6 +1263,7 @@ export function createChatFailedRunRetry(db: Db, options: ChatFailedRunRetryOpti .limit(1); if (busy || !input.initiatedByUserId) throw failedChatRetryDenied(); const now = new Date(); + await notifyChatActionWork(tx, "failed_run_retry"); const [intent] = await tx .insert(chatActions) .values({ @@ -1508,36 +1507,42 @@ export function createChatFailedRunRetry(db: Db, options: ChatFailedRunRetryOpti !["issued", "processing"].includes(candidate.status) || (candidate.status === "processing" && candidate.updatedAt.getTime() > - now.getTime() - DELIVERY_PROCESSING_STALE_MS) || + now.getTime() - FAILED_RUN_RETRY_STALE_MS) || (typeof candidate.result?.retryAt === "string" && Date.parse(candidate.result.retryAt) > now.getTime()) ) return failedChatRetryReceipt(candidate); - const [claimed] = await db - .update(chatActions) - .set({ status: "processing", updatedAt: now }) - .where( - and( - eq(chatActions.id, candidate.id), - eq(chatActions.status, candidate.status), - eq(chatActions.updatedAt, candidate.updatedAt), - ), - ) - .returning(); - if (!claimed) return failedChatRetryReceipt(candidate); - const attemptCount = Number(candidate.result?.attemptCount ?? 0) + 1; - const settle = async (status: string, result: Record) => { - const [current] = await db + const [claimed] = await db.transaction(async (tx) => { + await notifyChatActionWork(tx, "failed_run_retry"); + return tx .update(chatActions) - .set({ status, result, updatedAt: new Date() }) + .set({ status: "processing", updatedAt: now }) .where( and( - eq(chatActions.id, claimed.id), - eq(chatActions.status, "processing"), - eq(chatActions.updatedAt, now), + eq(chatActions.id, candidate.id), + eq(chatActions.status, candidate.status), + eq(chatActions.updatedAt, candidate.updatedAt), ), ) .returning(); + }); + if (!claimed) return failedChatRetryReceipt(candidate); + const attemptCount = Number(candidate.result?.attemptCount ?? 0) + 1; + const settle = async (status: string, result: Record) => { + const [current] = await db.transaction(async (tx) => { + await notifyChatActionWork(tx, "failed_run_retry"); + return tx + .update(chatActions) + .set({ status, result, updatedAt: new Date() }) + .where( + and( + eq(chatActions.id, claimed.id), + eq(chatActions.status, "processing"), + eq(chatActions.updatedAt, now), + ), + ) + .returning(); + }); return failedChatRetryReceipt(current ?? claimed); }; try { @@ -1669,26 +1674,10 @@ export function createChatFailedRunRetry(db: Db, options: ChatFailedRunRetryOpti async function processFailedChatRunRetries(limit = 20) { if (isShuttingDown()) return 0; - const now = new Date(); const candidates = await db .select({ id: chatActions.id }) .from(chatActions) - .where( - and( - eq(chatActions.kind, "failed_run_retry"), - or( - eq(chatActions.status, "issued"), - and( - eq(chatActions.status, "processing"), - lte( - chatActions.updatedAt, - new Date(now.getTime() - DELIVERY_PROCESSING_STALE_MS), - ), - ), - ), - sql`(${chatActions.result}->>'retryAt' is null or (${chatActions.result}->>'retryAt')::timestamptz <= ${now.toISOString()}::timestamptz)`, - ), - ) + .where(dueChatAction("failed_run_retry")) .orderBy(asc(chatActions.createdAt), asc(chatActions.id)) .limit(Math.max(1, Math.min(50, limit))); for (const candidate of candidates) diff --git a/server/src/services/chat-work-notifications.ts b/server/src/services/chat-work-notifications.ts index f76ac13c72e..16e2ac75135 100644 --- a/server/src/services/chat-work-notifications.ts +++ b/server/src/services/chat-work-notifications.ts @@ -13,6 +13,7 @@ export async function notifyChatPublicationWork(tx: object): Promise { export const CHAT_ACTION_QUEUES = { provider_effect: DELIVERY_QUEUES.chatProviderEffects, github_webhook_ingress: DELIVERY_QUEUES.chatGitHubIngress, + failed_run_retry: DELIVERY_QUEUES.chatFailedRunRetries, slack_session_sync: DELIVERY_QUEUES.chatSessionSyncs, slack_board_message: DELIVERY_QUEUES.chatBoardMessages, slash_task_start: DELIVERY_QUEUES.chatTaskStarts, diff --git a/server/src/services/delivery-work-notifications.ts b/server/src/services/delivery-work-notifications.ts index fdee30932f8..ab4ba5fe257 100644 --- a/server/src/services/delivery-work-notifications.ts +++ b/server/src/services/delivery-work-notifications.ts @@ -8,6 +8,7 @@ export const DELIVERY_QUEUES = { chatSessionStops: "chat-session-stops", chatProviderEffects: "chat-provider-effects", chatGitHubIngress: "chat-github-ingress", + chatFailedRunRetries: "chat-failed-run-retries", chatSessionSyncs: "chat-session-syncs", chatVerification: "chat-verification", chatInbound: "chat-inbound",