Files
PaperClipAI/ui/src/fixtures/systemNoticeFixtures.ts
T
DottaandPaperclip f572e08678 fix(recovery): stop automatic stranded-task takeovers (#11961)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The recovery service restores execution when a task loses its live
path.
> - The service retries the original agent for a limited number of
attempts.
> - The old fallback could select a manager or an executive and wake
that agent.
> - That fallback changed the effective recovery owner without a board
decision.
> - This pull request keeps the source owner and gives the exhausted
recovery decision to the board.
> - The benefit is a clear ownership rule with no automatic task
takeover.

## Linked Issues or Issue Description

Refs: #11807

Refs: #11817

**What existing behavior does this improve?**

This improves stranded-task recovery in the server and the recovery
action card in the board UI.

**Subsystem affected**

Cross-cutting: server recovery orchestration, recovery observability,
board UI, and execution documentation.

**Current behavior**

Paperclip retries the original agent for a limited number of attempts.
After the retry limit, it can select a manager, task creator, CTO, or
CEO as a recovery owner. It can then wake that substitute agent. The
source task keeps its assignee, but the automatic substitute wake
creates an implicit takeover path.

**Proposed behavior**

Paperclip keeps the limited retry path for the original agent. If
recovery is exhausted or unsafe, Paperclip creates one board-owned
source recovery action. It keeps both source assignee fields. It does
not wake a substitute agent. The board can repair, retry the original
owner, explicitly reassign, or resolve the task.

**Reason and benefit**

Source task ownership must remain stable until a person or an approved
policy changes it. The new rule removes implicit manager and executive
takeover. It also gives operators clear evidence through the
`board_escalation_no_takeover_v1` routing marker.

**Breaking changes**

Automatic recovery no longer wakes a manager or executive after the
original-agent retry limit. Existing active agent-owned recovery actions
remain visible and can resolve. Paperclip does not schedule a new
takeover wake for those legacy actions.

## What Changed

- Route exhausted and unsafe stranded recovery to a board-owned source
action.
- Preserve agent and user assignee fields during automatic escalation.
- Keep limited same-agent continuity repair and provider quota
monitoring.
- Stop new manager, creator, CTO, and CEO recovery wakes.
- Keep legacy agent-owned recovery actions readable and resolvable.
- Add the routing marker to new board escalation evidence and
observability.
- Update recovery notices, the board UI card, tests, and execution
documentation.

## Verification

- Run `pnpm -r typecheck`.
- Run `pnpm build`.
- Run `pnpm check:token-gates`.
- Run `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-process-recovery.test.ts`.
- Run `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-workspace-branch-containment.test.ts`.
- Run the focused recovery and UI Vitest files changed by this pull
request.
- Confirm that a paused or over-budget source owner creates one board
action, keeps the source assignee, and creates no substitute wake.

## Risks

- Operators must now make the final recovery decision after the
original-agent limit.
- Legacy agent-owned actions use their stored contract. This avoids a
rollout-time ownership rewrite.
- No database migration or API response shape changes are included.
- The tests cover concurrent escalation, paused and over-budget owners,
legacy actions, provider quota monitoring, and UI presentation.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex with GPT-5. The hosted exact model revision and context
window are not exposed. Reasoning, tool use, and code execution were
enabled.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-22 11:41:24 -05:00

205 lines
5.4 KiB
TypeScript

import type {
SystemNoticeMetadataSection,
SystemNoticeProps,
} from "../components/SystemNotice";
export type SystemNoticeFixture = {
id: string;
caption: string;
} & SystemNoticeProps;
const HANDOFF_METADATA: SystemNoticeMetadataSection[] = [
{
title: "Recovery owner",
rows: [
{
kind: "issue",
label: "Recovery issue",
identifier: "PAP-3440",
href: "/PAP/issues/PAP-3440",
title: "Successful run handoff missing disposition",
},
{
kind: "agent",
label: "Owner",
name: "CTO",
href: "/PAP/agents/cto",
},
{
kind: "text",
label: "Suggested action",
value: "Reassign to a recovery agent and pick a disposition.",
},
],
},
{
title: "Run evidence",
rows: [
{
kind: "run",
label: "Source run",
runId: "9cdba892-c7ca-4d93-8604-4843873b127c",
href: "/PAP/agents/codexcoder/runs/9cdba892-c7ca-4d93-8604-4843873b127c",
status: "succeeded",
},
{
kind: "run",
label: "Recovery run",
runId: "61fdb79b-8012-4676-ac71-2971830e126a",
href: "/PAP/agents/codexcoder/runs/61fdb79b-8012-4676-ac71-2971830e126a",
status: "failed",
},
{
kind: "text",
label: "Normalized cause",
value: "Run completed without issuing a disposition for an in_progress task.",
},
],
},
];
const REQUIRED_METADATA: SystemNoticeMetadataSection[] = [
{
title: "Required action",
rows: [
{
kind: "issue",
label: "Source issue",
identifier: "PAP-3440",
href: "/PAP/issues/PAP-3440",
title: "Successful run handoff missing disposition",
},
{
kind: "agent",
label: "Responsible",
name: "CodexCoder",
href: "/PAP/agents/codexcoder",
},
{
kind: "text",
label: "Next step",
value: "Pick done, blocked, or in_review and post a one-line rationale.",
},
],
},
{
title: "Run context",
rows: [
{
kind: "run",
label: "Successful run",
runId: "9cdba892-c7ca-4d93-8604-4843873b127c",
href: "/PAP/agents/codexcoder/runs/9cdba892-c7ca-4d93-8604-4843873b127c",
status: "succeeded",
},
{
kind: "code",
label: "Status before",
value: "in_progress",
},
],
},
];
const NEUTRAL_METADATA: SystemNoticeMetadataSection[] = [
{
rows: [
{
kind: "agent",
label: "Reassigned to",
name: "ClaudeFixer",
href: "/PAP/agents/claudefixer",
},
{
kind: "agent",
label: "From",
name: "CodexCoder",
href: "/PAP/agents/codexcoder",
},
{
kind: "text",
label: "Reason",
value: "Manual reassignment requested by Board.",
},
],
},
];
export const systemNoticeFixtures: readonly SystemNoticeFixture[] = [
{
id: "warning-collapsed",
caption: "Warning · collapsed (default)",
tone: "warning",
label: "System warning",
source: { label: "Paperclip", href: "/PAP/agents" },
timestamp: "2026-05-04T16:32:00.000Z",
body: "Paperclip needs a disposition before this issue can continue.",
metadata: REQUIRED_METADATA,
detailsDefaultOpen: false,
},
{
id: "warning-expanded",
caption: "Warning · expanded",
tone: "warning",
label: "System warning",
source: { label: "Paperclip", href: "/PAP/agents" },
timestamp: "2026-05-04T16:32:00.000Z",
body: "Paperclip needs a disposition before this issue can continue.",
metadata: REQUIRED_METADATA,
detailsDefaultOpen: true,
},
{
id: "danger-collapsed",
caption: "Danger · collapsed (default)",
tone: "danger",
label: "System alert",
source: { label: "Paperclip", href: "/PAP/agents" },
timestamp: "2026-05-04T16:48:00.000Z",
body: "Paperclip could not resolve this issue's missing disposition automatically. The source assignment is unchanged and a board decision is required.",
metadata: HANDOFF_METADATA,
detailsDefaultOpen: false,
},
{
id: "danger-expanded",
caption: "Danger · expanded",
tone: "danger",
label: "System alert",
source: { label: "Paperclip", href: "/PAP/agents" },
timestamp: "2026-05-04T16:48:00.000Z",
body: "Paperclip could not resolve this issue's missing disposition automatically. The source assignment is unchanged and a board decision is required.",
metadata: HANDOFF_METADATA,
detailsDefaultOpen: true,
},
{
id: "neutral-collapsed",
caption: "Neutral · collapsed (default)",
tone: "neutral",
label: "System notice",
source: { label: "Paperclip" },
timestamp: "2026-05-04T15:10:00.000Z",
body: "Reassigned to ClaudeFixer.",
metadata: NEUTRAL_METADATA,
detailsDefaultOpen: false,
},
{
id: "neutral-expanded",
caption: "Neutral · expanded",
tone: "neutral",
label: "System notice",
source: { label: "Paperclip" },
timestamp: "2026-05-04T15:10:00.000Z",
body: "Reassigned to ClaudeFixer.",
metadata: NEUTRAL_METADATA,
detailsDefaultOpen: true,
},
{
id: "warning-no-details",
caption: "Warning · no metadata (Details affordance hidden)",
tone: "warning",
label: "System warning",
source: { label: "Paperclip" },
timestamp: "2026-05-04T17:02:00.000Z",
body: "This run paused while waiting on board approval.",
},
];