Commit Graph
2289 Commits
Author SHA1 Message Date
Devin FoleyandPaperclip 17901d74ba Fix onboarding account changes after returning from Review (#19557)
Save changed onboarding connections on the existing agent after validation.
Select the chosen personal provider account and serialize default changes so
an abandoned save cannot override a later choice after Back. Preserve runner,
environment, and unrelated agent settings. Reject unsupported managed-account
to legacy-secret switches with an actionable Connect error.

Verified with 327 component tests, three backend credential-selection tests,
nine affected browser tests, typecheck, build, token gates, all CI jobs, and
Greptile 5/5 with no unresolved comments.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-11-06 11:55:29 -08:00
TonioandPaperclip c650f36007 fix(ui): refine agent profiles, identity editing, and list actions (#19535)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent pages expose identity files, reporting relationships, and
runtime settings.
> - Several controls had inconsistent spacing, weak navigation, or
misleading save feedback.
> - Empty task-row detail slots also reduced the title width before
truncation.
> - This pull request fixes those behaviors and aligns the reviewed UI
details.
> - The result is clearer agent editing and more readable task and agent
lists.

## Linked Issues or Issue Description

**What happened?**
Agent headers and file controls had inconsistent layout. The Identity
footer appeared without edits and did not provide clear save
confirmation. File names could wrap or omit the Markdown suffix. Task
titles truncated while empty detail slots occupied the row.

**Expected behavior**
Keep headers and actions aligned. Reveal the Identity footer only for
changes or save feedback. Use valid Markdown file names and actual file
dates. Let titles use available space.

**Steps to reproduce**
1. Open an agent overview, then its Identity tab.
2. Edit, undo, save, copy, and add an instruction file.
3. Resize the file panel and inspect file names and date labels.
4. Hover an agent row and inspect the Chat action and tooltip.
5. Open a task list with no recovery or external details and inspect
title width.

Related: #18716 is the prior UI bundle. #16934 and #14483 concern role
editing; this change does not add a role picker.

## What Changed

- Refine the character header, inline title and name editing, crown
position, runtime shortcut, and Chat/New task actions.
- Align profile metadata and manager selection; hide empty direct
reports.
- Refine Identity panels, Markdown filename validation, file-row hover,
truncation, Paper Mono typography, and copy feedback.
- Add a viewport-bound save footer with dirty, saving, and saved states.
Keep undo/discard separate from successful saves.
- Return recorded creation or modification dates for instruction files.
Reuse the active file transaction for metadata reads to avoid exhausting
the small connection pool.
- Reveal the agent-row Chat icon and label on hover, with a standard
delayed tooltip.
- Collapse empty task-row details so titles can use the available width.

## Verification

- Browser checks covered the local overview and Identity flows, narrow
file panels, successful file creation/update, undo/discard, header
navigation, row hover, tooltip delayed-open state, and full task titles.
- Focused instruction, save-footer, date, font-asset, and task-row tests
passed during development. After review, 55 file/instruction tests
passed, including cold restoration with recorded and unknown dates;
server typecheck and 18 immediate-save response tests also passed.
- Full `pnpm -r typecheck` and `pnpm build` passed after rebase onto
current master. The complete `pnpm test:run` attempt hit a 15-second
timeout in the existing chat Retry HTTP integration test; that case
passed in isolation (1 test passed). A second full attempt hit timeouts
in two other chat integration cases (GitHub webhook draining and
delivery retry budget); both passed in isolation without increasing
timeouts. All 55 remote checks passed on f31405a730, including the full
test matrix, build, typecheck, and final verification gate.
- `pnpm check:token-gates`, `node scripts/check-maps.mjs`, and `git diff
--check` passed on the submitted revision.

## Risks

- File dates are optional. Managed files require recorded dates because
restored projections have synthetic filesystem timestamps. External
files can use modification time; unknown dates are omitted.
- Save-footer timing depends on animation completion; reduced-motion and
failed-save paths have tests.
- Global Paper Mono typography can change text widths. Browser checks
covered the edited surfaces.
- No schema migration or new execution permissions are introduced.

## Model Used

OpenAI Codex, GPT-6, with repository tools, code execution, and browser
verification. The exact serving model ID and context-window size 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 references)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run the relevant tests locally and they pass; full-run
timeout limitations and successful isolated reruns are documented above
- [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-11-06 11:14:56 -08:00
Devin FoleyandPaperclip fd4b197f83 fix(ui): hide runner selection during agent setup (#19517)
## Thinking Path

> - Paperclip lets people manage AI agents for work.
> - Agent setup connects a harness, model, account, and computer.
> - The system already selects a qualified runner for a new agent.
> - Setup still asks users to select that internal runner.
> - The runner dropdown can be the only field in Advanced.
> - This PR removes the dropdown and hides an empty Advanced section.
> - Users keep their saved execution settings and see fewer setup controls.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Agent creation, onboarding, built-in agent setup, company import, and agent configuration.

**Subsystem affected**

The UI, setup error messages, tests, and Storybook fixtures.

**Current behavior**

Users can select a runner from a dropdown. Some setup screens place only that dropdown in Advanced. Errors can direct users to that control.

**Proposed behavior**

Remove runner dropdowns from setup and configuration. Use the existing automatic selection for new agents. Retain saved runner settings. Show Advanced only when other fields remain.

**Reason and benefit**

Users can configure agents without an extra internal execution choice. Setup also avoids an empty Advanced section.

**Breaking changes**

Users can no longer select a runner through these UI dropdowns. The API and CLI retain their runner options. Existing agents keep their saved execution settings.

**Additional context**

Related: [Default new agents to the qualified runner for their harness](https://github.com/paperclipai/paperclip/pull/15422). This PR changes the existing setup UI. It adds no harness or runtime default. The master merge retains the custom gateway placement from [#19510](https://github.com/paperclipai/paperclip/pull/19510).

## What Changed

- Remove the shared runner dropdown and its setup/configuration callers.
- Hide Advanced when it has no remaining fields. Keep command and argument settings where supported. Preserve custom gateway setup under Advanced on the Claude and OpenAI API key paths.
- Keep setup failures visible and keep the retry action outside the removed control.
- Update errors and documentation that referred to the removed dropdown.
- Update component tests and Storybook fixtures for the new UI states.
- Make Legacy first-task evals resume an API-configured setup record. Record that path separately from fresh automatic onboarding.

## Verification

- Latest master integration (`187c0f6d6e5`): 276 onboarding/provider-connection tests passed across six files. UI typecheck and build passed. Token gates, query-error rendering checks, and the Storybook catalog check passed. The conflict resolution retains master's bounded connection tests and cancellation while removing the obsolete runner instruction. The first test command used the wrong working directory and failed before collecting tests; the corrected invocation from `ui/` passed.
- Earlier branch verification passed: full workspace build and typecheck, 1,037 focused UI/service tests, 49 heartbeat provider contract tests, eval typecheck, and 30 map/catalog tests.
- The full local `pnpm test:run` run passed all six chat integration shards (1,931 tests). It was stopped for the master merge before the complete suite finished. The [complete CI run](https://github.com/paperclipai/paperclip/actions/runs/42264662642) passed on the previous head `72e1699`.
- Current head `a097c8369d079c839e78661a2206cfa62c5210e0`: [full CI](https://github.com/paperclipai/paperclip/actions/runs/42273168268) passed. PR checks total 55 passed and 3 skipped, with none failed or pending. Greptile reviewed this head at 5/5 with no actionable findings or unresolved review threads.
- Live browser/provider evals have not run. The Legacy eval change has no live qualification claim.

## Risks

- Removing the dropdown changes the setup UI. Existing API and CLI runner choices remain available.
- Conditional Advanced sections must retain valid command and argument fields.
- Legacy first-task evals now prove setup resumption. They do not prove the fresh-agent automatic default.
- No schema change or data migration is included.

## Model Used

OpenAI `gpt-6.1-sol`, through Codex in T3 Code. Reasoning effort: `xhigh`. The model used file editing, shell commands, and test tools. The harness does not expose the context window size.

## 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-11-06 10:58:42 -08:00
Devin FoleyandPaperclip e3d8f42e19 fix(ui): recover stalled onboarding connection checks (#19505)
Bound the complete onboarding connection check to two minutes, abort its
requests on timeout, and ignore late results. Keep the saved account
available for retry and resume its matching unfinished setup after refresh.

Verified 242 focused tests, workspace typecheck and build, and the complete
hosted CI check set. Greptile is 5/5 with no unresolved review comments.
Local full-UI timing failure and interrupted CI runner attempts remain
documented in the PR evidence.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-11-06 10:31:18 -08:00
DottaandClaude Opus 5.5 a6c2280630 fix(ui): dock the announcement card in the sidebar so it never covers the task thread (#19511)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A new user lands on their first task after onboarding. The task
thread and the composer are the core of that screen.
> - The app shows an optional in-app announcement (now "Introducing
Connectors"). On desktop the card floats at the bottom-left, 22rem wide,
but the sidebar is only 240px wide.
> - In the Nov 5 cloud onboarding test, the tester said: "onboarding
popup blocks core real estate".
> - At 1280x800 the card covers the left part of the composer. At
1440x900 and 1280x800 it covers the task thread area and the sidebar
account menu.
> - This pull request docks the card inside the desktop sidebar column,
in normal layout flow, above the account menu.
> - The benefit is that the card stays visible and dismissible, but it
can never cover the thread or the composer.

## Linked Issues or Issue Description

**What happened?**
On a new company's first task, the "Introducing Connectors" announcement
card floats over the task thread. At 1280x800 it covers the left part of
the composer.

**Steps to reproduce**
1. Create a new company and open its first task at 1280x800.
2. Wait 3 seconds for the announcement card to show.
3. See the card on top of the composer and the account menu.

**Expected behavior**
Optional announcements do not cover the task thread or the composer.

**Evidence**
The staging test company (Claude subscription path) has a local
dismissal for `2026-09-14-connectors`. The live feed at
`pages.paperclip.ing/announcements/v1/current.json` serves that
announcement. No other first-use overlay showed on the first task or the
dashboard. Toasts and the task-detail error notice are transient and are
not first-use popups.

## What Changed

- `AnnouncementWell` has a `placement` prop: `sidebar` (in-flow, no
`fixed`) or `floating` (the earlier fixed placement).
- `Layout` renders the `sidebar` placement inside the desktop
`SidebarShell`, above the account menu. It renders the `floating`
placement only on mobile, where it stays above the bottom nav.
- While the desktop sidebar is collapsed to a rail or closed, the card
waits. It shows again when the sidebar is expanded.
- In the sidebar, the card is capped at half the window height
(`--announcement-sidebar-max-height: 50dvh`) and scrolls. It uses the
existing compact image height token, so the sidebar navigation keeps
most of its height.
- New e2e spec `tests/e2e/announcement-placement.spec.ts`. It makes a
new company with a team lead and a first task. It serves the live
connectors announcement through the app's announcement API. At 1440x900
and 1280x800 it checks that the card does not overlap the composer or
`#main-content`, that the card is fully in the viewport and dismissible,
and that it does not overlap the account menu trigger and the Account
dialog opens.
- Unit test for the two placements in `AnnouncementWell.test.tsx`.

**Product decision for Dotta:** this PR does not hide announcements on
first use. Another option is to hold all announcements until the user
has used the product for some time (for example, after the first task is
done or after the first day). That is a one-line gate in
`AnnouncementWell`'s `enabled` condition, if we want it.

## Verification

Red on `master` (819420a60a), same spec, before the fix:

```
[1/1] [chromium] › tests/e2e/announcement-placement.spec.ts › Announcement placement on a new company's first task › the announcement does not cover the task thread or composer
    Error: 1440x900: announcement overlaps task thread
    Expected: 0
    Received: 42904
    Error: 1280x800: announcement overlaps composer
    Expected: 0
    Received: 8568
    Error: 1280x800: announcement overlaps task thread
    Expected: 0
    Received: 42904
  1 failed
```

The spec also checks the account menu (requested by the N-13
connectors-upsell fix). Red on `master` with the same spec:

```
    Error: 1440x900: announcement overlaps task thread
    Received: 42904
    Error: 1440x900: announcement overlaps account menu
    Received: 5312
    Error: locator.click: Test ended.   (the card intercepts the "Open account menu" click)
```

Green after the fix:

```
[1/1] [chromium] › tests/e2e/announcement-placement.spec.ts › Announcement placement on a new company's first task › the announcement does not cover the task thread or composer
  1 passed (45.0s)
```

Review follow-up (Greptile/Ponytail: a long announcement in a 208px
sidebar had no height cap). A second spec uses a 400-character
description, a 208px sidebar, and a 1280x720 window. Red without the
cap:

```
    Error: docked card keeps at most half the window height
    Received:    732
    Error: expect(locator).toBeInViewport() failed   (sidebar "Tasks" link)
    Received: viewport ratio 0
    Error: expect(locator).toBeInViewport() failed   (account menu)
    Received: viewport ratio 0
```

The spec sets the sidebar width key for both the classic and the
Coordinator workspace, and asserts the rendered width is 208px.

Green with the cap (both specs):

```
  2 passed (57.8s)
```

Neighbouring suites:

```
cd ui && npx vitest run src/components/AnnouncementWell.test.tsx src/components/AnnouncementCard src/hooks/useAnnouncement.test.tsx src/components/Layout.test.tsx
 Test Files  4 passed (4)
      Tests  57 passed (57)
```

- `pnpm check:token-gates`: All gates clean.
- `pnpm --filter @paperclipai/ui typecheck`: pass.
- Screenshots: the spec writes `announcement-1440x900.png` and
`announcement-1280x800.png` to its test output. Before: the card (352px
wide) sits over the composer at x 296–364 and over the account menu.
After: the card is 216px wide inside the sidebar, above the account
menu. The thread and composer are clear.

Run it:

```
npx playwright test --config tests/e2e/playwright.config.ts tests/e2e/announcement-placement.spec.ts
```

## Risks

- Low risk. The card now takes vertical space in the sidebar while it
shows. The sidebar navigation scrolls as before. The card goes away when
the user dismisses it.
- Users who keep the desktop sidebar collapsed do not see the
announcement until they expand the sidebar.
- Mobile behavior does not change.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, in Claude Code with
tool use (shell, browser, Playwright).

## 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:19:46 -06:00
Devin FoleyandPaperclip b85557a122 Fix OpenAI subscription waiting labels (#19521)
Show “Waiting for auth…” while OpenAI subscription sign-in completes in onboarding and the shared provider connection screen. Preserve Claude’s code-entry label and align connection previews and existing regression expectations.

Verified 246 component tests, workspace typechecks, the full production build, token gates, and all 54 applicable CI checks. Greptile scored the change 5/5 with no unresolved comments.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-11-06 10:12:12 -08:00
DottaandClaude Opus 5.5 1485d3de1b fix(ui): hold announcements until the onboarding first task is finished (#19514)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A new user's first session is the onboarding first task. The agent
asks its onboarding questions in that task thread.
> - The app also shows an optional in-app announcement. The live feed
now serves "Introducing Connectors" (`2026-09-14-connectors`).
> - The announcement well only waits while the onboarding wizard dialog
is open. It shows 3 seconds after the user lands on the first task.
> - In the Nov 5 cloud onboarding test (Claude API path), the tester
said: "Don't show the connectors upsell until the second session. It's
blocking the user flyout and potentially the onboarding questions."
> - This pull request holds announcements until the company's onboarding
first task is done or cancelled.
> - The benefit is that a new user sees the onboarding questions and the
account menu without an upsell on top of them.

## Linked Issues or Issue Description

Refs #19511 (places the card in the desktop sidebar, above the account
menu. That PR keeps the card off the account menu after onboarding. This
PR adds the first-session gate that #19511 leaves open.)

**What happened?**
On a new company's onboarding first task, the "Introducing Connectors"
card shows 3 seconds after the page loads. At 1440x900 the card (352x346
px, bottom-left, z-40) covers the sidebar account menu trigger and the
left edge of the task composer.

**Expected behavior**
Optional announcements do not show in the first session, while the
onboarding first task is open.

**Steps to reproduce**
1. Create a new company through onboarding. The wizard creates the
onboarding first task.
2. Open the first task at 1440x900 and wait 3 seconds.
3. See the announcement card on top of the account menu and the
composer.

**Paperclip version or commit**
`master` at 819420a60a.

## What Changed

- `AnnouncementWell` reads the company's onboarding first task
(`originKind=onboarding_first_task`, `limit=1`) with React Query under
`["issues", companyId, "onboarding-first-task"]`. Issue live-update
invalidation of `["issues", companyId]` refreshes it.
- The well stays disabled while that task is not `done` or `cancelled`.
While the read is loading or after it fails, the optional card stays
hidden. A company that never had an onboarding first task is not gated.
- "First session" uses the existing `origin_kind` marker. There is no
schema change.
- New e2e spec `tests/e2e/announcement-first-session.spec.ts` with a
mocked feed that copies the live connectors entry. It checks that the
card is absent while the onboarding first task is open. Then it marks
the task done on the same page and checks that the card appears without
a reload (a positive control for the feed mock and the live refresh
path).
- New unit test `AnnouncementWell.onboarding.test.tsx`. It covers open,
finished, no first task, read failure, and the open-to-done transition
after `["issues", companyId]` is invalidated.
`AnnouncementWell.test.tsx` now gives the well a `QueryClient` with a
finished onboarding state.

## Verification

Red on `master` (819420a60a), before the fix. Vitest:

```
cd ui && npx vitest run src/components/AnnouncementWell.onboarding.test.tsx
     × stays hidden while the onboarding first task is open
     × stays hidden when onboarding state cannot be read
      Tests  2 failed | 1 passed (3)
AssertionError: expected true to be false
```

Red on `master`, Playwright at 1440x900 (an earlier draft of the spec
also had a placement check, recorded here as evidence for the flyout
part of the report):

```
✘ announcement-first-session.spec.ts › a brand-new company with its onboarding task open does not show the upsell
    Error: expect(locator).toHaveCount(expected) failed
    Locator:  getByRole('complementary', { name: 'Paperclip announcements' })
    Expected: 0
    Received: 1
✘ … where the upsell shows, it does not cover the account menu or its flyout
    Error: well {"x":12,"y":542,"width":352,"height":346} covers account trigger {"x":20,"y":856,"width":166,"height":36}
```

Green after the fix, merged with current `master` (f7070d68b5), with the
in-page positive control:

```
✓ announcement-first-session.spec.ts › a brand-new company with its onboarding task open does not show the upsell (10.1s)
  1 passed

cd ui && npx vitest run src/components/AnnouncementWell.onboarding.test.tsx src/components/AnnouncementWell.test.tsx
      Tests  6 passed (6)

cd ui && npx vitest run src/components/AnnouncementWell.onboarding.test.tsx src/components/AnnouncementWell.test.tsx \
  src/components/AnnouncementCard.test.tsx src/hooks/useAnnouncement.test.tsx src/components/SidebarAccountMenu.test.tsx src/components/Layout.test.tsx
 Test Files  6 passed (6)
      Tests  95 passed (95)
```

- `pnpm check:token-gates`: All gates clean.
- UI `tsc --noEmit`: no errors in app code. The only errors are the
git-ignored, generated Storybook catalog files.
- CI note: the first CI run had three jobs fail with "The self-hosted
runner lost communication with the server" (server tests 12/12, Runner
vitest package, e2e shard 5/8). These are infra flakes. I reran them.
- Local e2e note: two runs could not start the server because embedded
PostgreSQL ports were in use by parallel e2e runs on the same machine. A
rerun passed. This is not related to the change.

Before (master): the card covers the account menu and the composer on
the onboarding first task.

![before: onboarding first
task](https://raw.githubusercontent.com/paperclipai/paperclip/c97519868225b0c206cf95fa3fb7150287c88116/n13-before-onboarding-task.png)

After: no card on the onboarding first task. The account menu and the
composer are clear.

![after: onboarding first
task](https://raw.githubusercontent.com/paperclipai/paperclip/c97519868225b0c206cf95fa3fb7150287c88116/n13-after-onboarding-task.png)

## Risks

- Low risk. The change only hides optional content.
- A user who never finishes or cancels the onboarding first task does
not see announcements in that company. This is the intended trade-off
for "not in the first session". Closing the task shows them again.
- The well makes one extra small issues read (`limit=1`) per company. It
is cached for 60 seconds.
- `AnnouncementWell.tsx` also changes in #19511. Whichever PR lands
second resolves a small conflict in the `enabled` condition and the
`<aside>` placement.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits, Playwright, Vitest).

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:00:50 -06:00
DottaandClaude Opus 5.5 19239c11d1 fix(onboarding): move Use a custom gateway under Advanced on the API key step (#19510)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - New users connect a model in the onboarding wizard's "Connect a
model" step.
> - On the Claude API and Codex API paths, the step shows an API key
card.
> - The "Use a custom gateway" button sat directly under the key field,
inside that card, with no gap.
> - A tester reported that it hugs the field and that it belongs under
Advanced.
> - A custom gateway is an alternative route, not the next step after
the key.
> - This pull request moves the button into the step's existing Advanced
disclosure.
> - The benefit is a cleaner key card, and the gateway route stays one
click away.

## Linked Issues or Issue Description

No public issue exists. A manual new-user onboarding test on a staging
cloud workspace found this bug. Related: #17253 added the custom gateway
route.

**What happened?**
On the onboarding "Connect a model" step, API key path (Claude API and
Codex/OpenAI API), the "Use a custom gateway" button is shown at the top
level, directly against the API key text field.

**Steps to reproduce**
1. Start onboarding and reach "Connect a model".
2. Choose "Use API key instead", then pick Claude (or OpenAI).
3. See "Use a custom gateway" directly under the "Enter API key here"
field.

**Expected behavior**
The custom gateway control is in the Advanced section of the step. It is
hidden until Advanced is opened.

Source: manual new-user onboarding test on Paperclip Cloud staging (item
N-16, P3, Claude API path).

## What Changed

- `ui/src/components/OnboardingWizard.tsx`: render
`OnboardingGatewaySetup` inside the connect step's existing `Advanced`
`<details>` (above the runner select), not inside the API key card. It
keeps the same gates as the card it left: the card is live and mounted,
no connect progress, API credential mode, a created company, and a
Claude or Codex adapter.
- `ui/src/components/onboarding/OnboardingGatewaySetup.tsx`: accept an
optional `className`. The wizard passes `-ml-3` so the ghost button's
label lines up with the Advanced summary.
- The gateway dialog and its save/select-model/connect flow are
unchanged.
- `ui/src/components/OnboardingWizard.test.tsx`: new test for the Claude
API and Codex API paths. It asserts the button is in a closed `Advanced`
disclosure, outside the key field's card, and that the gateway dialog
opens after Advanced is expanded.

## Verification

Red on unchanged source (`npx vitest run
src/components/OnboardingWizard.test.tsx -t "custom gateway"`):

```
 ❯ src/components/OnboardingWizard.test.tsx (212 tests | 2 failed | 208 skipped)
       × keeps Use a custom gateway in the closed Advanced disclosure on the Claude API key step
       × keeps Use a custom gateway in the closed Advanced disclosure on the Codex API key step
AssertionError: Use a custom gateway should live inside the Advanced disclosure: expected null to be truthy
 Test Files  1 failed (1)
      Tests  2 failed | 2 passed | 208 skipped (212)
```

Green after the fix (same command):

```
 Test Files  1 passed (1)
      Tests  4 passed | 208 skipped (212)
```

Neighbouring suites (`OnboardingWizard.test.tsx`,
`ai-connections/AiProviderSetup.test.tsx`, `components/onboarding/*`),
including the existing end-to-end custom gateway save/test/hire test:

```
 Test Files  8 passed (8)
      Tests  270 passed (270)
```

- `pnpm check:token-gates`: all gates clean.
- `tsc --noEmit` for `ui`: no new errors. The only errors are the
existing unbuilt `@paperclipai/plugin-sdk/ui` import in
`PluginOrganizationSwitcher`.

Screenshots (Storybook `Onboarding/Agent arc › Connect · Claude API
key`):

| Before (master) | After (Advanced closed) | After (Advanced open) |
| --- | --- | --- |
|
![before](https://raw.githubusercontent.com/paperclipai/paperclip/1dae9c09f9a4070c66cd313dbb64baf1636c54d0/n16-before.jpg)
| ![after
collapsed](https://raw.githubusercontent.com/paperclipai/paperclip/1dae9c09f9a4070c66cd313dbb64baf1636c54d0/n16-after-collapsed.jpg)
| ![after
expanded](https://raw.githubusercontent.com/paperclipai/paperclip/1dae9c09f9a4070c66cd313dbb64baf1636c54d0/n16-after-expanded.jpg)
|

The red "This item doesn't exist or was moved." line in the screenshots
comes from the story's runner fixture. It is present on master too and
is not related.

## Risks

- Low risk. This is a layout move of one control. The gateway flow and
its gates are unchanged.
- The control is now one click deeper. This is the intended outcome of
the report.
- After a gateway is saved, "Use a different API key" stays in the card,
so a user can still leave the gateway route without opening Advanced.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edits, browser for Storybook screenshots).

## 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:42:21 -06:00
DottaandClaude Opus 5.5 63e550b338 fix(ui): keep the audit hash out of the tool approval card (#19509)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents call connected-app tools (for example GitHub
`create_repository`) through the tool gateway, and a write call waits
for a human approval card
> - The pending approval card showed an "Arguments and audit hash"
disclosure. The disclosure printed the 64-character SHA-256 of the
reviewed arguments
> - A new user in onboarding testing called this "a long hash string in
the review approval card" that is confusing and unapproachable
> - The hash identifies the exact arguments for audit. The approver does
not need to read it to decide
> - This pull request removes the raw hash from the card text and keeps
it behind a "Copy audit hash" action
> - The benefit is a plain approval card, with the audit value still
available to people who need it

## Linked Issues or Issue Description

**What happened?**
On the pending tool-approval card, the "Arguments and audit hash"
disclosure printed the raw SHA-256 of the reviewed arguments (for
example `8ca5312877235a…`, 64 hex characters) under the arguments JSON.

**Expected behavior**
The approval card uses plain language. Internal identifiers do not show
as card text. A person who needs the audit hash can still get it.

**Steps to reproduce**
1. Let an agent call a write tool on a connected app (for example GitHub
`create_repository`).
2. Open the pending "Approve tool action" card ("Review request").
3. Expand "Arguments and audit hash". The raw 64-character hash shows
under the arguments.

**Paperclip version or commit**
Current `master` (819420a60a) and the Paperclip Cloud staging build used
for onboarding tests.

## What Changed

- New `ToolActionAuditHashCopy` button
(`ui/src/components/ToolActionAuditHashCopy.tsx`). It copies the
arguments hash to the clipboard through the shared `useCopyAction` hook,
and shows "Copied" or "Couldn’t copy".
- `IssueThreadInteractionCard` (`RequestToolActionCard`): the disclosure
is now "Arguments". The raw hash `<p>` is replaced by the copy button.
- `TaskChatCompactInteractionCard`: the same change in both places that
printed `toolAction.argumentsHash`.
- If the clipboard is blocked, the button shows "Couldn’t copy" and the
hash then shows as selectable text, so the audit value stays reachable
(review feedback).
- Tests: a new regression test renders the review flow with the real
staging payload shape (GitHub `create_repository`, raw gallery tool id,
64-hex hash) and asserts that no `/[0-9a-f]{12,}/i` text shows before or
after the disclosure opens, and that the copy action writes the hash.
The existing long-proposal test now asserts that the hash is not in the
disclosure text. `ToolActionAuditHashCopy.test.tsx` covers copy success
and the blocked-clipboard fallback. The clipboard mock is restored after
the test.

## Verification

Red on `master` (new test only, before the fix):

```
 FAIL  src/components/task-chat/TaskChatInteractionCard.test.tsx > TaskChatInteractionCard > keeps raw tool ids and the audit hash out of the tool approval card text
AssertionError: expected 'Gcreate_repository · Name: brainrot-s…' not to match /[0-9a-f]{12,}/i
+ Received:
"Gcreate_repository · Name: brainrot-storefront-prototype · Private: trueArguments and audit hash{\"name\":\"brainrot-storefront-prototype\",\"private\":true}8ca5312877235a4c0f6e9b1d2a7c3e5f9081b2c4d6e8fa1b3c5d7e9f0a2b4c6dDeclineApprove & run"
 Test Files  1 failed (1)
      Tests  1 failed | 32 skipped (33)
```

Green after the fix (same test):

```
 Test Files  1 passed (1)
      Tests  1 passed | 32 skipped (33)
```

Neighbouring suites (`ui/src/components/task-chat`,
`IssueThreadInteractionCard.test.tsx`, `AttentionQueueRow.test.tsx`,
`transcript/RunTranscriptView.test.tsx`), after rebase on current
`master`:

```
 Test Files  62 passed (62)
      Tests  1047 passed (1047)
```

After the review fixes (`ToolActionAuditHashCopy.test.tsx`, `task-chat`,
`IssueThreadInteractionCard.test.tsx`):

```
 Test Files  61 passed (61)
      Tests  1004 passed (1004)
```

- `pnpm check:token-gates`: all gates clean.
- `tsc --noEmit` on `ui`: no errors in the changed files.
- Visual check in Storybook story `Issue Thread Interactions / Tool
Action Pending Write`, disclosure open:
- Before: the summary reads "Arguments and audit hash" and a mono line
`sha256:9f2c1a7be4d0c8a3` shows under the JSON.
- After: the summary reads "Arguments". A small ghost "Copy audit hash"
button shows under the JSON. No hash text shows.

## Risks

- Low risk. UI copy and layout change on one card type only. The server
payload, the approval decision, and the stored hash do not change.
- A person who read the hash off the card must now use the copy button.

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

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`), in Claude Code with
tool use (shell, file edits, browser). Default context window and
reasoning mode.

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:39:44 -06:00
DottaandClaude Opus 5.5 f5de197536 fix(ui): keep a busy-workspace wait as a quiet status, not a notice (#19507)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The task page tells the user what an agent is doing and what needs
their attention
> - When another task uses the same workspace, the agent waits and
Paperclip starts the work automatically
> - A tester reported that the "Waiting for workspace" notice is not
helpful, because the user cannot do anything about it
> - On `master`, the task page shows this wait four times: a pinned top
banner, a conversation banner, the composer strip and the Workspace
property row
> - This pull request removes the two banners and keeps the two quiet
status surfaces
> - The benefit is that a transient system wait no longer looks like
something that needs the user

## Linked Issues or Issue Description

Refs #19369

**What happened?**
During new-user onboarding tests on Paperclip Cloud staging (Claude
subscription path), the tester saw a "Waiting for workspace" notice and
reported: "this isn't helpful and the user can't do anything about it."

**Steps to reproduce**
1. Start work on a task while another task uses the same workspace.
2. Open the waiting task.
3. See the pinned "Waiting for workspace" banner under the title and a
second "Waiting for workspace" banner in the conversation.

**Expected behavior**
A busy workspace is a transient system wait with no user action. It
shows only as a quiet status on the task. It does not show as a banner
or notice.

**Investigation**
- No server inbox, Needs-you item or activity entry is created for a
workspace wait. The live-updates toast already skips `workspace_busy`
(`LiveUpdatesProvider.tsx`), and the chat thread already hides the
cancelled admission attempt.
- The remaining notices are UI banners: `IssueMonitorBanner` (pinned top
banner) and `IssueScheduledRetryCard` (conversation banner).
- #19369 stopped showing resource waits as failed attempts, but it kept
these banners.

## What Changed

- `IssueMonitorBanner`: render nothing for a workspace wait. The
composer strip still shows "Waiting for workspace" and "You can keep
sending instructions while the agent waits."
- `IssueScheduledRetryCard`: render nothing for `workspace_busy`. The
Workspace property row still shows "Waiting for workspace".
- `DESIGN.md`: add the rule that a busy-workspace wait is a quiet status
only, never a pinned banner, conversation notice or toast.
- Tests: replace the old "shows the banner" assertions with N-14
regression tests.

## Verification

Red on `master` (`819420a60a`), test change only:

```
cd ui && npx vitest run src/components/IssueMonitorBanner.test.tsx src/components/IssueScheduledRetryCard.test.tsx
     × does not raise a pinned banner for a workspace wait the user cannot act on (N-14) 7ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯
 FAIL  src/components/IssueMonitorBanner.test.tsx > IssueMonitorBanner / IssueMonitorComposerStrip rendering > does not raise a pinned banner for a workspace wait the user cannot act on (N-14)
AssertionError: expected 'Waiting for workspaceAnother task is …' to be '' // Object.is equality
 FAIL  src/components/IssueScheduledRetryCard.test.tsx > IssueScheduledRetryCard > raises no conversation notice for workspace contention the user cannot act on (N-14)
AssertionError: expected 'Waiting for workspaceAnother task is …' to be '' // Object.is equality
      Tests  2 failed | 32 passed (34)
```

Green after the fix, same command:

```
 Test Files  2 passed (2)
      Tests  34 passed (34)
```

Neighbouring suites:

```
cd ui && npx vitest run src/components/TaskChatThread.test.tsx src/pages/IssueDetail src/components/issue-properties src/lib/issue-monitor
 Test Files  7 passed (7)
      Tests  687 passed | 1 skipped (688)
```

- `pnpm check:token-gates`: all gates clean.
- `tsc --noEmit -p ui`: no errors in the changed files. Local errors
come only from unbuilt workspace packages (`@paperclipai/plugin-sdk/ui`,
Storybook feature map). CI typecheck covers the full build.

## Risks

- Low risk. UI-only change. Two banners no longer render for
`workspace_busy`. Other scheduled-retry reasons (failures, pool
exhaustion, continuations) are unchanged.
- A long workspace wait now has no prominent notice. No actionable next
step exists in the current data (the API does not name the task that
holds the workspace). A persist-past-threshold escalation with a link to
that task can follow when the data exists.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edit, browser for read-only staging evidence).

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:30:10 -06:00
DottaandPaperclip 952ecdbb39 feat(ui): show assignee avatars in task lists (#18607)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - People use task lists to see who owns each task.
> - The shared task table shows an assignee name without a picture.
> - A picture helps people find an agent or person when they scan the
list.
> - This pull request shows the existing avatar beside the assignee
name.
> - The table uses the same image and fallback components as other
identity views.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The Assignee column in the shared task table.

**Subsystem affected**

`ui/` — the task table and its page data.

**Current behavior**

The column shows only an assignee name. People must read each name to
find an owner.

**Proposed behavior**

Show the agent avatar or human profile image beside the name when the
Assignee column is visible. Use the existing fallback if an image is
missing. Show no avatar for an unassigned task.

**Reason and benefit**

People can use the same identity pictures throughout the app. This helps
them scan task ownership.

**Breaking changes**

None. The API, task assignment, name sorting, and filters keep their
existing contracts.

**Additional context**

Related work: #2422 adds agent avatar URL support. It does not add
avatars to this table. #13319 aligns icons in task properties. This
change uses the existing identity components.

## What Changed

- Pass agent appearance and human profile images from existing company
data to the task table.
- Keep the avatar visible beside a long name as the name scrolls on
hover or focus.
- Respect the Assignee column setting and keep unassigned rows without
avatars.
- Load human photos only from Paperclip asset URLs. Show initials for
external profile URLs without an image request.
- Add tests with the real task row for agent and human images, image
fallbacks, unassigned tasks, and the Columns control.
- Use the shared human label when a directory entry is missing. The
local operator shows Board. Other missing users show the short user ID.
- Update Storybook data, design guidance, and the task-list verification
steps.

## Verification

- Current head: `c49fee5c9b`. The source-tested merge is
`586873fe47921b250ab0de18371d25632f46cff0`, which integrates master
`53645a0104`. The last commit removes only the inherited avatar
paragraph from the feature map under the current map-scope rule. Map
checks pass again. No history rewrite or duplicate feature PR.
- Reproduced the old voice-report CI failure before integration. The
unchanged test on `7f1d137a` passed on an unloaded local machine. With a
controlled 3,250 ms delay in its first local provider fixture and a
15-second wait, the old polling failed: expected 105 report requests,
received 315. This confirms the fake clock crossed refresh deadlines
during database work.
- Master already fixes this exact failure through #18628 (`d26628e35a`).
The fixed polling passes with the identical delay and wait budget. The
exact session IDs, 105 requests, one attempt per report, bounded
reconstruction, restart, and quiet assertions remain. The temporary
delay is removed.
- Final committed code: all 15 voice-report tests and 46
scheduler/coordinator tests pass against disposable embedded PostgreSQL
on macOS arm64. No external provider call is used.
- All 154 tests pass across eight focused UI suites: TaskList,
TaskList.assignees, CoordinatorTasks, IssueRow, AgentAvatar,
useTaskListCollection, useTaskListColumnPreferences, and TaskChatBubble.
- Three merge conflicts retained both current behavior and the avatar
feature: current sorting and column preferences, unread state, assignee
photos, and shared human labels. The focused tests cover those
contracts.
- UI TypeScript, UI build, token gates, map structure, Storybook catalog
generation, and whitespace checks pass. Storybook output is generated
and ignored.
- The original server shard failure remains recorded: [voice-report CI
failure](https://github.com/paperclipai/paperclip/actions/runs/41472540522/job/125369899753),
expected zero idle holds but received two. Older attempts also had
attention-retention and sandbox-runtime timeouts; their causes were not
established in those attempts. Fresh CI must qualify the merged head.
- Previous review findings were fixed with red/green coverage: missing
avatar tests, shared human labels, and arbitrary human image URLs.
Current code still limits human photos to Paperclip asset URLs; external
URLs use initials and start no image preload.
- Prior browser checks covered agent and human images, fallbacks,
unassigned rows, hidden columns, and long-name keyboard focus. Those
checks used production components with fixture data and do not prove
live backend behavior. No new browser or hosted task action was
performed for this CI repair.
- Current head `c49fee5c9b`: Greptile and Paperclip review both give 5/5
with no findings. The initial CI attempt passed the original server
shard and all other completed test suites. The workspaces-a 2/2 job was
interrupted by a runner shutdown signal ([job
log](https://github.com/paperclipai/paperclip/actions/runs/42250845315/job/128006963628));
it has no complete test result. A single bounded retry of that shard and
its dependent verification has been requested after the initial workflow
ended. No source change is based on that interruption.

- Final-head qualification: `c49fee5c9b75b695d2894933bbcbe0896aab9148`
passes all 55 applicable CI checks (three skipped, one neutral review).
[CI
result](https://github.com/paperclipai/paperclip/actions/runs/42250845315).
Greptile gives 5/5 with no open findings. Hosted task recovery remains
pending verified rollout.

## Risks

- Low risk. The change adds identity pictures through existing UI
components and existing company data.
- Human profile images hosted outside Paperclip show initials in this
table. This matches the existing task-message avatar rule.
- A long name has less width beside its avatar. The existing hover and
focus reveal keeps the name readable.
- Current master changes table sorting and preferences. Merge resolution
retains these contracts and their tests. Fresh CI and review must pass
before merge.

## Model Used

OpenAI GPT-6 through Codex assisted with code and verification. The
runtime did not expose an exact model ID or context-window size. The
agent used code execution and repository tools.

## 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 —
current head `c49fee5c9b`; the map-scope finding is fixed and its thread
is resolved
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-06 11:01:02 -06:00
Nicky LeachandClaude Opus 5.5 21a1e78852 feat(environments): add managed plugin environment rows (#19490)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A managed instance gets its instance-level environments from the
`PAPERCLIP_MANAGED_CONFIG` document
> - That document cannot declare a plugin environment yet, so a managed
instance cannot get a plugin-provided environment without manual setup
> - The full change is split into three pull requests. This first one
adds the plugin environment rows and their write floor, and nothing
calls them yet
> - The second pull request lets the managed config declare plugin
entries and provisions them at boot. The third sets the instance default
once and advertises the new managed-config capability
> - The benefit is a smaller review for each step, and no instance
changes behavior until the last step lands

## Linked Issues or Issue Description

**Subsystem affected**
Server environments (managed-config environment provisioning,
environment routes) and the board UI's adapter Test target.

**Problem or motivation**
A managed instance cannot get a plugin environment from its managed
config. Company settings cannot create one either. So each managed
instance that needs one requires manual setup.

**Proposed solution**
Over three pull requests, let an `environments` entry in the managed
config declare `driver: "plugin"`. Boot then provisions one locked row
for each plugin entry, and makes one of them the instance default once
on new instances. This pull request adds only the row layer: create or
adopt a marked row, archive it, find it, and keep tenants from editing
it.

**Alternatives considered**
Manual setup on each instance, and a new control-plane endpoint. Both
need a step outside the deploy. The managed config already delivers the
sandbox entry.

**Roadmap alignment**
This extends the existing managed-config `environments` section, which
already provisions the managed sandbox. It adds no new product surface.

## What Changed

- New `server/src/services/managed-plugin-environments.ts`, used through
new `environmentService` methods (`ensureManagedPluginEnvironment`,
`archiveManagedPluginEnvironment`, `findManagedPluginEnvironment`):
- The ensure validates the driver config through
`normalizeEnvironmentConfig` and
`validatePluginEnvironmentDriverConfig`, the same path an
operator-created plugin environment takes.
- It marks the row `managedByPaperclip: true` and
`managedPluginEnvironment: "<pluginKey>:<driverKey>"`.
- It adopts an existing unmarked row for the same plugin and driver
instead of adding a second one, so a hand-made row keeps its agents.
- A row that already matches gets no write. Changes write activity log
entries.
- Archive sets an active marked row to `archived`. The next ensure
reactivates it.
- Migration `0437`: unique index
`environments_managed_plugin_environment_idx` on `metadata ->>
'managedPluginEnvironment'`. This is the only schema change of the three
pull requests. Snapshot `0432` is pruned to keep the five newest.
- `server/src/routes/environments.ts`:
`isPlatformProvisionedEnvironment` also covers the new marker (write
floor and redaction). `managedPluginEnvironment` is a reserved marker
key, so a client cannot stamp it on its own row. Nothing provisions
marked rows yet, so the marker-clear escape hatch stays open for them.
The next pull request refuses it while the managed config declares the
row's entry.
- `ui/src/lib/adapter-test-environment.ts`:
`resolveManagedSandboxEnvironmentId` requires `driver === "sandbox"`.
The plugin row also carries `managedByPaperclip`, so the old "not local"
check could take it for the managed sandbox.

## Verification

- `cd server && npx vitest run
src/__tests__/managed-plugin-environments.test.ts` (embedded Postgres):
- The ensure creates the marked row through the plugin validator. A
second ensure writes nothing and logs nothing.
- A hand-made row for the same plugin and driver is adopted. It keeps
its id and its agents.
  - Archive, then ensure, reactivates the row.
- The ensure refuses while the plugin has no running worker, and refuses
a name that another environment holds.
  - The unique index allows one row per marker.
- `npx vitest run src/__tests__/environment-routes.test.ts`: on a
cloud-managed instance, edit, env-var edit, status change and delete of
a marked plugin row are refused, including for instance admins. The
marker alone keeps the floor. A client cannot stamp the marker. The
marker clear is allowed.
- `npx vitest run src/__tests__/environment-service.test.ts`: passes.
- `cd ui && npx vitest run src/lib/adapter-test-environment.test.ts`:
the plugin row is never taken for the managed sandbox. Under
managed-sandbox-only, an agent on the sandbox tests in the sandbox, and
an agent on the plugin environment tests there.
- `pnpm --filter @paperclipai/server typecheck`, `pnpm --filter
@paperclipai/ui typecheck` and `pnpm --filter @paperclipai/db
check:migrations` pass.
- I did not run the full `pnpm test:run`, `pnpm -r typecheck` or `pnpm
build`. CI covers them. `pnpm verify` asks for broader verification
because of the migration.

## Risks

- Migration: one new partial unique index on a small table. No row
carries the marker before this change, so the index cannot fail on
existing data.
- No caller uses the new ensure yet, so no instance changes behavior.
- The UI change affects only lists that hold a non-sandbox row with
`managedByPaperclip`. Today that is only the local row, which the old
check already excluded.

> 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

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, through Claude Code
with tool use (file edits, shell, test runs).

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:52:43 +00:00
DottaandPaperclip eb5c24b61c perf(chat): speed up cached returns and preserve bottom follow (#19452)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent Chat shows saved messages and native run output in one
conversation.
> - Reopening a chat rebuilt terminal transcripts and waited for network
hydration.
> - A queued scroll event could also disable bottom-follow after a
cached editor increased the content height.
> - This pull request reuses terminal history and preserves
bottom-follow through that event order.
> - The benefit is a faster cached return that opens at the latest
reply.

## Linked Issues or Issue Description

**What happened?**

Returning to an agent chat could leave its latest reply below the
viewport. Saved native transcripts also held the first reveal behind
repeated network reads.

**Expected behavior**

A new chat navigation opens at the latest reply. A reader who scrolls up
keeps their position. Back restores the earlier reading anchor. Saved
history can render while the normal catch-up read runs.

**Steps to reproduce**

1. Open an agent chat with saved native runs and task question cards.
2. Open Tasks, then reopen the same agent from Recents.
3. Observe the wait and the distance from the latest reply.

**Paperclip version or commit**

Reproduced on `523818ea9bf81f96abf4e6b73e81c2ce87518c07`. Rebased onto
master `2f9d3d43c3`.

**Deployment mode**

Built from source with production UI assets and an isolated synthetic
database.

Related work: #18358 added recent-window chat loading. #18461 deferred
completed transcripts for compact task opens. Open PR #14667 changes the
reveal gate for task threads. This PR keeps that gate and caches
hydrated terminal native history across visits.

## What Changed

- Retain successful terminal native history in the application query
cache. Keys include company, run, status and projection.
- Render matching saved history immediately and continue the normal
cursor-based catch-up read.
- Preserve bottom-follow when content grows before a queued scroll event
is delivered. Upward gestures and Back anchors retain priority.
- Add regression coverage for remounts, catch-up, cache clearing,
projection changes, cached question editors and scroll event ordering.
- Repair two browser assertions: use a final 422 failure for explicit
history retry, and accept saved task-view query parameters.
- Document the local before/after comparison, including unchanged paths.

## Verification

- Focused transcript and scroll suites: 51 tests passed after the review
fixes, including a 300-run cache stability case.
- Full UI suite on the rebased head: 896 files passed; 11,257 tests
passed and one skipped.
- Final review-fixed production UI: all 24 desktop/mobile chat journeys
passed.
- Final head: `pnpm -r typecheck`, `pnpm build` and `pnpm
check:token-gates` passed. The complete GitHub CI test matrix, E2E,
build and release checks passed for `4429a9aab4`.
- The local repo-wide test command was stopped before performance
sampling. Local acceptance used the full UI suite and the chat browser
rig; CI completed the full repository matrix.
- A parallel local build initially read a partially regenerated runner
module while typecheck was emitting files. After typecheck finished, the
serial full build passed. No runner source change was required.
- The first browser run exposed the retry fixture's synthetic 500 being
retried by the shared query policy. A final 422 response now verifies
explicit retry. The Back assertion also now accepts the task
collection's saved view query.
- Re-measured the final reviewed build against the original baseline in
five alternating pairs. The same retained 600-message/300-run fixture,
Chromium at 1440×900 and 150 ms simulated latency produced cached-return
medians of 580 ms (568–594) before and 155 ms (130–161) after. Median
paired saving: 433 ms. All five pairs improved. A separate loopback
asset host kept the manual test-drive build unchanged.
- The initial full comparison found no reliable change to loopback
cached returns, cold opens or uncached in-app entry. These are local
results, not deployed staging measurements.
- Greptile identified cache-expiry row loss and missing older output
after a cached projection switch. Regression cases reproduce those
paths. Mounted history now retains restored rows, and each projection
retains its confirmed snapshot. The fresh review on commit `4429a9aab4`
is 5/5 with no open findings.
- One browser run overlapped a local rebuild and received a root-page
500. The server log records the missing generated index at that time.
The run also timed out waiting for wheel acknowledgement after older
rows had loaded; that input-acknowledgement cause was not established.
After the build completed, the full serial run passed all 24 cases.
- Run the opt-in browser rig with
`tests/perf/agent-chat/playwright.config.ts` against a disposable
loopback instance. See `tests/perf/agent-chat/README.md`.

## Risks

- Cached history must not cross company, status or projection
boundaries. Regression tests cover those cache misses and cache
clearing.
- Terminal output can receive late events. Cached history still performs
catch-up reads, and retry, reconnect and mode changes retain their
reads.
- Programmatic scroll events can race with a reader gesture. Tests cover
upward intent, delayed media and Back restoration.
- This change does not improve cold-start latency. No schema, server
authorization or exported data changes.

## Model Used

OpenAI Codex using GPT-6. The runtime does not expose a more specific
model ID or context-window size. Used reasoning, shell execution,
repository editing, tests and browser profiling.

## 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-11-06 09:38:40 -06:00
Nicky LeachandClaude Opus 5.5 83c6832ccb fix(build): typecheck package, Playwright, and e2e test files in CI (#18839)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Runner, plugin, and Playwright tests verify how those agents run,
connect, and appear in the board
> - Most package tsconfigs exclude tests, and Vitest strips types, so a
type error in those tests fails nothing
> - The tests drifted to 803 real type errors, and some of those errors
hid assertions that checked nothing
> - paperclipai/paperclip#18739 and paperclipai/paperclip#18738 check
the server test tree and four test projects, but not these files
> - This pull request adds a required check for these test files, fixes
every error, and fails CI if a test file is in no program
> - The benefit is that a type error in any test file outside
`server/src` now fails CI

## Linked Issues or Issue Description

Refs #18739
Refs #18738

**What happened?**

517 test files were in no program that required CI checks. A type error
in those files did not fail CI.

**Expected behavior**

CI fails on a type error in any test file.

**Steps to reproduce**

Add `export const value: number = "text";` to a test file under
`packages/paperclip-runner/src` on master. CI stays green, because
`tsconfig.json` excludes test files and Vitest strips types.

**Measured gap**

Each row is a program with its tests included, on one thread. File and
error counts are for head `7d4b5e907a`. Memory peaks were measured
before the two latest merges of master.

| Group | Test files | Real errors | Peak |
| --- | ---: | ---: | ---: |
| `paperclip-runner` Node source | 290 | 644 | 1.09 GiB |
| `paperclip-runner` surfaces | 9 | 12 | 0.42 GiB |
| `paperclip-runner` devtools and examples | 14 | 0 | 0.68 GiB |
| `sandbox-providers/kubernetes` | 21 | 65 | 0.44 GiB |
| 8 other sandbox providers and `bridge-template` | 32 | 4 | 0.34 GiB |
| `plugins/sdk` | 22 | 15 | 0.41 GiB |
| `plugin-workspace-diff` | 5 | 0 | 0.37 GiB |
| `plugin-orchestration-smoke-example` | 1 | 1 | 0.15 GiB |
| `kv-demo-mcp-server` | 3 | 0 | 0.13 GiB |
| `packages/db` scripts | 1 | 0 | 1.01 GiB |
| 8 Playwright suites under `tests/` | 52 | 25 | 1.72 GiB |
| `tests/runner-acceptance` | 3 | 0 | 0.58 GiB |
| `tests/e2e`, through layer declarations | 63 | 37 | at most 1.59 GiB |
| `ui` `AttentionInteractionResolver.test.tsx` | 1 | 0 | n/a |
| **Total** | **517** | **803** | |

27 of the 803 errors came from master while this pull request was open.
Each merge of master brought new test code that no check had read.

Errors that were config errors, not real errors:

- One TS2688 for each provider, because the root install does not
install provider dependencies.
- 9 in the Runner Node program (TS7016 and TS6059).
- 2 in `tsconfig.browser.json`. TypeScript 7 removed `baseUrl`, so that
config was invalid.
- 213 in the Playwright suites (`@/*` aliases and Vite types).
- About 60 in `tests/e2e` (import extensions and aliases).

`packages/plugins/create-paperclip-plugin` has no gap. Its tsconfig
excludes its test, but `cli/tsconfig.json` reaches that test through an
import. `server/test-baselines` also has no gap, because
`tests/lifecycle-baseline` reads it.

## What Changed

- Added `scripts/typecheck-test-programs.mjs` and its tests. The
`Typecheck + Release Registry` job runs it. It checks each program in
`PROGRAMS` on one thread.
- The script fails if a tracked `*.test.ts(x)` or `*.spec.ts(x)` file
outside `server/src` is in no program. The compiler file list decides
this, because `include` drops `foo.test.tsx` when `foo.test.ts` exists.
- Added a `tsconfig.test.json` to each uncovered package, and
`tests/tsconfig.playwright.json`.
- Added `tests/e2e` to `PROJECTS` in
`scripts/typecheck-test-projects.mjs`, with a `tsconfig.json` and a
`tsconfig.layers.json`.
- Added `scripts/typecheck-sandbox-providers`. It is a small workspace
that installs provider SDKs from one lockfile. I did not add a lockfile
inside each provider, because
`scripts/build-standalone-public-packages.mjs` uses a frozen install
when one exists, and that changes what a release resolves.
- Corrected `packages/paperclip-runner/tsconfig.browser.json` for
TypeScript 7.
- Set `rootDir` to `..` in
`packages/paperclip-runner/tsconfig.test.json`, because one Runner test
imports from `packages/adapter-utils/src`. The program does not emit
files, so this changes no build output.
- Moved the Kubernetes provider from `vitest ^3.2.4` to `^4.1.8`, the
version the other providers use. Vitest 3 requires `tinypool ^1.1.1`,
which has two critical advisories (GHSA-85c8-ppgw-ccpr and
GHSA-5gmw-xhrv-c9v3). The first patched version is 2.1.2. Its tests have
the same result on both versions: 189 passed, and the same 12
`file-sync` tests failed.
- Moved the Kubernetes provider from `zod ^3.24.2` to `^4.6.6`, and gave
its two `z.record` calls a key schema. This is the only production
change, and it stays in this pull request. See "Production change for
review".
- Renamed `AttentionInteractionResolver.test.ts` to
`AttentionInteractionResolver.replace.test.ts`, so TypeScript reads the
`.test.tsx` beside it.
- Fixed all 803 errors, with changes to test files and test support
only. The fix for `contracts/native-execution.test.ts` is the same as
the fix in paperclipai/paperclip#18739, so those two changes merge
cleanly.
- Updated `doc/DEVELOPING.md`.

This pull request does not edit `server/scripts/typecheck-layers.mjs`.
paperclipai/paperclip#18739 and paperclipai/paperclip#18738 have both
merged, and this branch includes them.

### Production change for review

The new lockfile made dependency review flag `zod@3.25.76` as a
high-severity advisory (GHSA-mrhx-c7xc-m3w5). The first patched version
is 4.6.6, so 3.x has no fix. The published Kubernetes plugin uses zod to
validate operator config. The zod 3 source does not compile against zod
4, so a typecheck-only override cannot fix this.

The existing tests do not prove this change, because they also passed
with the invalid one-argument `z.record`. So I parsed 45 inputs with the
same schema on zod 3.25.76 and zod 4.6.7:

| Result | Inputs | Detail |
| --- | ---: | --- |
| Identical | 23 | Includes defaults, valid config, and a `__proto__`
key |
| Same outcome, new error wording | 21 | For example, `Invalid url` is
now `Invalid URL`. `validateConfig` shows this text to operators. |
| Different outcome | 1 | `jobTtlSecondsAfterFinished: 2 ** 60` was
valid and is now rejected. The new limit is `Number.MAX_SAFE_INTEGER`. |

I did not test this against a real cluster.

The decision is to keep this change in this pull request. Since then,
paperclipai/paperclip#17364 has moved the rest of the repository to zod
4.6.x, so the Kubernetes plugin now matches it.

### Behavior change for review

`reducedMotion` under `use` is not a Playwright option. It had no effect
in 11 configs and 2 `test.use` calls, so those suites ran with
`no-preference`. The options are now under `contextOptions`, so reduced
motion now takes effect. `app-shell-design.spec.ts` asserts a real
transition, so it now asks for `no-preference` itself.

The baseline manifest has `snapshotCount: 0`, so this invalidates no
published baseline. A frozen trial reference PNG can differ.

### Findings

These are product questions. I did not change them, and I did not hide
them with casts:

1. **`PrpEvent` and its schema disagree.** The TypeScript type in
`src/protocol/replay-contract.ts` requires `normalizedSessionId`. The
`required` list in `protocol/schemas/event.schema.json` does not include
it. `validatePrpEvent` accepts events that the type forbids.
2. **`requestCheckboxConfirmation` is stricter than its runtime.** It
uses the output type, so plugin authors must pass `continuationPolicy`
and `allowDeclineReason`, although the schema gives them defaults.
`z.input` would fix this.
3. **`RetainedRunnerdMaintenanceInput.backend.kind` is a free string.**
The quarantine domain comes from `descriptor.kind`, but the release uses
`proof.backend.kind`. If they differ,
`completeRetainedNativeSessionCleanup` returns 0 and the quarantine
stays. The production caller passes `"runner"` today, so this is not a
live bug.
4. **`CodexHarnessSession.close(input?)` is optional, but the contract
requires `{ reason }`.**

These are tests that verified nothing. I corrected them, and they pass:

- `backends/native-instruction-measurement.test.ts` filtered on
`placement === "core"`, which is not a valid placement. It measured an
empty tool list. It now measures 14 tools.
- `drivers/acpx/codex-acpx-driver.test.ts` checked for `usage.updated`,
which is not an event type.
- The `hostFixture()` helper in `drivers/acpx/runtime-host.test.ts`
ignored two overrides, so
`expect(prepareSandbox).not.toHaveBeenCalled()` could not fail.
- `live/live-session.test.ts` set `agent: "pi"` before a spread that
overwrote it.
- `native-distribution-integrity.test.ts` passed an AbortSignal where
`openCommand` expects `{ signal }`.

These are gaps that this pull request does not close:

- **The root lockfile still has some old zod versions.**
paperclipai/paperclip#17364 moved the direct zod dependencies to 4.6.x.
`pnpm-lock.yaml` still resolves `zod@3.25.76`, `4.1.11`, and `4.4.3`,
which are below the patched 4.6.6.
`packages/paperclip-runner/scripts/pi-distribution/package.json` pins
`zod` to `3.25.76`, and some agent SDKs resolve `4.4.3`. Dependency
review does not flag them, because this pull request does not add them.
- **Dead GitHub coverage.** `chat-adapters-ui-providers.spec.ts`
returned early for GitHub, so four later `provider === "github"`
branches never ran. I removed them. They included the private key file
and paste match, and the reconnect flow. Nothing asserts those behaviors
in that spec now.
- **Provider tests do not run in CI.** The root `vitest.config.ts` lists
only Daytona. The other 8 providers, `bridge-template` included, are
typechecked now, but CI does not run their tests.
- **One `packages/db` test never runs.**
`scripts/clean-poisoned-claude-sessions.test.ts` is outside the
`include` of `packages/db/vitest.config.ts`.
- **`plugin-orchestration-smoke-example` does not collect.**
`@paperclipai/shared` is not linked in its `node_modules`. It passes 3
of 3 with an alias.

## Verification

Run these commands:

```sh
node scripts/typecheck-test-programs.mjs --low-memory
pnpm --filter @paperclipai/server typecheck
node scripts/typecheck-test-projects.mjs --low-memory --reuse-declarations
node --test scripts/typecheck-test-programs.test.mjs
```

Results on macOS (arm64), Node 26.12. CI uses Node 24, and its
`Typecheck + Release Registry` job also passes. At head `7d4b5e907a`,
the test programs, `tests/e2e`, and the script tests pass again with 0
errors. The times and peaks below come from the run before the two
latest merges of master:

| Check | Result |
| --- | --- |
| Test programs, `--low-memory` | Exit 0, 0 errors, 42 seconds, peak
1.78 GiB |
| Server layers, low memory | Exit 0, 0 errors, 38 seconds, peak 3.41
GiB |
| Test projects, including `tests/e2e` | Exit 0, 0 errors, 13 seconds,
peak 1.59 GiB |
| Script tests | 12 passed |
| Injected type error in each of 23 places | Each one failed its program
with TS2322 |
| Coverage check | 0 unchecked files, 10 seconds |

I did not run this in a 4 GiB container. The peaks above are `maximum
resident set size` for the largest single process.

**Runtime check of the fixes.** Types do not prove that a test still
works, so I ran the suites before the two latest merges of master:

| Suite | Result |
| --- | --- |
| Runner, full suite | 4,885 passed, 3 failed, 13 skipped |
| Sandbox providers, 10 packages | 939 passed, 12 failed, 36 skipped |
| Plugin SDK | 200 passed |
| `kv-demo-mcp-server` | 12 passed |
| Renamed UI test and its sibling | 4 passed |

All 15 failures exist without these fixes or come from the local
machine:

- **3 Cursor Cloud timeouts.** `artifact-delivery.test.ts` has 1 and
`cursor-cloud-driver.test.ts` has 2. They time out in a full local run,
and all 133 tests in those files with `codex-security-config.test.ts`
pass when run alone. They fail in the same way with those two files at
their unedited versions.
- **12 Kubernetes `file-sync.test.ts` failures.** They fail in the same
way before and after the fixes. The first failure is that
`/proc/self/fd` does not exist on macOS.

After each of the two latest merges of master, I ran only the Runner
test files that I changed:

| Merge | Result |
| --- | --- |
| `885c249cb9`, 8 files | 534 passed |
| `7d4b5e907a`, 7 files | 672 passed, 16 failed |

The 16 failures are in the two `source-reconciliation` test files. They
fail in the same way with master's unedited versions of those files. 15
of them fail with `ACPX sidecar exited at initialize`, so I think my
local build of the Rust and sidecar binaries is out of date. I did not
rebuild them to check. CI builds them from scratch, and all Runner jobs
pass in CI at `7d4b5e907a`.

**Not run:**

- `tests/e2e` and the 8 Playwright suites need a server, so most of them
have only compile checks. `playwright test --list` loads 308 tests from
the main e2e config.
- `packages/plugins/sandbox-providers/kubernetes/test/integration` needs
a cluster, so it has only compile checks.
- I did not run the full `pnpm test:run` or root `pnpm build`. Required
CI runs them.

**Reduced motion.** I ran 6 motion-sensitive Storybook specs in system
Chrome with a temporary config, once with `reduce` and once with
`no-preference`. Both runs had 45 passed and the same 5 failed. So
reduced motion did not change those results. The 5 failures do not
depend on motion. At least one comes from the browser, because system
Chrome prints `oklch(55.6% 0 0)` where the test expects `oklch(0.556 0
0)`. I did not run the other Storybook specs. The `storybook-visual`
label runs that workflow.

## Risks

- **A new lockfile to maintain.** A change to a provider `package.json`
now fails the typecheck job until someone refreshes
`scripts/typecheck-sandbox-providers/pnpm-lock.yaml`. The failure
message gives the command.
- **The job has a new install.** The step downloads provider SDKs, so it
depends on the npm registry. It uses `--frozen-lockfile` and
`--ignore-scripts`.
- **The local install replaces provider `node_modules`.** A contributor
with a standalone provider install must reinstall that provider after
running the script.
- **Reduced motion can change Storybook results.** See "Behavior change
for review". That workflow is not part of required CI.
- **Kubernetes plugin validation changed.** A release of this plugin
ships zod 4. Error wording changes, and very large integers are now
rejected. See "Production change for review".
- **Master moves fast.** Each merge of master brought new test code with
type errors, 27 so far. After this merges, the check reports those
errors in the pull request that adds them.

## Model Used

- Provider: Anthropic Claude, through Claude Code
- Model: Claude Opus 5.5 (`claude-opus-5-5`)
- Capabilities: tool use, code execution, and subagents for the type
fixes in separate files

## 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)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived
details. The app generated the branch name.
- [ ] I have run tests locally and they pass. See the failures explained
in Verification.
- [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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 07:33:59 -08:00
DottaandPaperclip 0b8578055a fix: restore trailing spaces in queued steering messages (#19456)
## Thinking Path

> - Paperclip lets people manage AI agents and give them more
instructions during work.
> - The task composer sends those instructions through the shared
Markdown editor.
> - The editor writes a final text space as the HTML entity `&#x20;`.
> - The composer trims spaces before it submits a message. An entity
survives this step.
> - Queued messages can therefore contain extra text that the person did
not type.
> - This pull request restores final prose spaces at the editor output
boundary.
> - The queue and the agent receive clean message text. Code and escaped
examples keep their source.

## Linked Issues or Issue Description

**What happened?**

A message that ends with a space can contain a final `&#x20;` when it
enters the steering queue. The shared editor exports this entity, and
normal message trimming does not remove it.

**Expected behavior**

The editor must return final prose spaces as spaces. Code samples and
escaped literal entities must keep their exact text.

**Steps to reproduce**

1. Open a task while its agent works.
2. Type a steering message with a space at the end.
3. Submit the message to the queue.
4. Inspect its stored Markdown. Before this fix, it can end with
`&#x20;`.

**Paperclip version or commit**

Reproduced from `70d23612dd` with the installed Markdown serializer. The
serializer writes `Steer the current turn ` as `Steer the current
turn&#x20;` plus a newline.

Searches for related trailing-space and steering-entity PRs found no
duplicate.

## What Changed

- Restore trailing space entities only in the final Markdown text node.
- Apply this step before the editor removes its other transport escapes.
- Keep code, raw HTML, escaped entities, links, internal entities, and
hard breaks unchanged.
- Limit the fix to an entity at the end of the message. Keep entities
before internal newlines or closing emphasis markers. Replacing those
entities with spaces can create hard breaks or remove bold and italic
formatting. Add tests for these limits.
- Add editor regression cases and focused Markdown cases. Keep the fix
in the shared editor; no queue API change is needed.

## Verification

- TDD: the new editor test failed in 3 cases before the fix. It received
literal entities instead of spaces. All 6 cases pass after the fix.
- All 353 focused editor, Markdown, composer, and queue tests pass. This
includes 10 new cases for internal newlines, emphasis, and a final space
after these cases.
- `pnpm check:token-gates` and `git diff --check` pass.
- UI typecheck and UI production build pass.
- Repository typecheck and build could not complete locally because
Cargo is not installed. The full test command was started, then stopped
after confirming its native Runner cases also need Cargo. No complete
local full-suite verdict is claimed. Current-head
[CI](https://github.com/paperclipai/paperclip/actions/runs/42229743296)
passes for `350af775850664226d6630072cb8e08732e2a218`. This includes
typecheck, build, tests, and E2E checks.
-
[Greptile](https://github.com/paperclipai/paperclip/pull/19456#issuecomment-6686109030)
and [Paperclip
review](https://github.com/paperclipai/paperclip/pull/19456#issuecomment-6686063906)
each score the current commit 5/5. No review threads remain open.
- Checked the design-review dependency claim against the current commit.
[ui/package.json already declares the
parser](https://github.com/paperclipai/paperclip/blob/350af775850664226d6630072cb8e08732e2a218/ui/package.json#L71).
The existing underscore helper uses the same parser. Six direct checks
also pass for GFM strikethrough, task lists, URLs, and tables.
- No visual style changed. No database or API contract changed. The
helper comment documents the storage rule.

## Risks

- Low risk. The change only restores encoded spaces at the end of the
document. Internal line endings and spaces inside emphasis are out of
scope. The tests keep these entities unchanged to preserve Markdown
formatting. Parser source positions protect code and HTML. Escape pairs
protect literal entity examples.
- Existing stored messages are not rewritten. They use the corrected
output after an editor change.

## Model Used

- OpenAI Codex, GPT-6. GPT-6.1 Sol (`gpt-6.1-sol`) assisted with the
review follow-up. The runtime does not expose the context-window size.
Used reasoning, repository tools, code execution, and test execution.

## 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-11-06 09:06:25 -06:00
DottaandPaperclip b8e69c6dd0 fix(ui): prevent layout shift when usage limits load (#19463)
Reserve the usage-limit property row during the first runtime-state request and replace its skeleton in place.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-11-06 08:34:38 -06:00
DottaandPaperclip 4696ff4990 fix: reserve agent In Progress display for active runs (#19453)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task status must tell operators whether an agent is working or
waiting.
> - A task can retain its in-progress lifecycle status after its run
stops.
> - The shared read projection only handled scheduled monitoring waits.
> - The UI also ignored other projected statuses for in-progress tasks.
> - This pull request requires an active task run for the In Progress
display.
> - Operators can see waits without changing execution or recovery
authority.

## Linked Issues or Issue Description

**What happened?**

An agent task still showed In Progress after its run ended with an
access failure and left a pending connection request. Stale execution
locks and saved lifecycle state could show the same false activity.

**Expected behavior**

An agent task shows In Progress only with a task-linked queued or
running run. Otherwise, it shows Blocked for recovery, In Review for a
pending response or review, Monitoring for a scheduled check, or Todo
for remaining work. Human ownership keeps its selected status.

**Steps to reproduce**

1. Create an agent-owned task with an in-progress lifecycle status.
2. End its run and retain a pending human request.
3. Read the task, its summary, and search results. Before this change,
they still show In Progress.

## What Changed

- Derive visible activity from company-scoped, task-linked runs in the
existing shared SQL projection.
- Project inactive tasks to existing waiting or todo states. Preserve
lifecycle status, locks, admission, and recovery.
- Make the shared UI resolver use the projection while preserving
explicit and optimistic status changes.
- Cover stopped runs, stale locks, human waits, company isolation,
monitoring, filters, counts, search, and UI labels.
- Document the presentation rule in the product and execution contracts.

## Verification

- Red before the server fix: 7 new regression cases failed because they
returned in_progress. The 10 existing cases passed.
- Green after the server fix: 20 cases passed in
issue-display-status.test.ts, using embedded Postgres and real task
APIs/search. Seven nearby active-question cases also passed.
- Review regression: dismissed requests, historical questions, and
returned review work each failed before the follow-up fix. All three now
pass using the existing active-interaction rule and pending review
state.
- Red before the UI fix: 4 label cases failed. Five additional safety
cases caught stale projections hiding explicit transitions.
- Green after the UI fix: 43 tests passed across
task-status-presentation, issue-filters, and issue-detail-subissues.
- pnpm -r typecheck passed. All UI token gates and synchronization
checks passed.
- pnpm build passed. Server typecheck and build passed again after the
review fixes.
- Full CI passed on 45c4ababc5, including
typecheck, build, general/serialized tests, Runner tests, and browser
checks. The final rollup has 55 successful checks, one neutral check,
and three configured skips.
- The local pnpm test:run command passed all 1,931 chat integration
tests. Its remaining duplicate full-suite run was stopped once full CI
passed on the same commit. This is not a claim that the complete local
command finished.
- Greptile reviewed the final commit at 5/5. Both prior findings have
regression coverage and all review threads are resolved.
- Live acceptance at commit 45c4ababc5: started a fresh isolated local
instance, created an inactive agent task, and saved a pending human
request. API reads changed from Todo to In Review. An initial readiness
probe ran before startup finished; the doctor then verified the exact
serving revision before acceptance. Chrome showed In Review in the task
header, breadcrumb, and sidebar. Stored lifecycle status remained
in_progress. The instance was stopped after verification.
- The reported stopped state was inspected in the live UI. The fix is
not deployed there. Local API tests reproduce the failure shape without
replaying the real deployment task.

## Risks

- This changes visible status and display-status filter membership for
inactive agent tasks. Stored lifecycle filters keep their existing
meaning.
- The projection uses existing indexes on task runs, recovery actions,
and interactions. It adds no worker, timer, schema change, or execution
action.
- A queued run counts as active admission. Human-owned tasks retain
explicit ownership status.

## Model Used

- OpenAI GPT-6 through Codex, with reasoning, repository tools, and code
execution. A more specific model ID and context-window size 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
- [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-11-06 07:59:38 -06:00
DottaandPaperclip 70d23612dd fix: offer Interrupt when queued-message steering is unavailable (#19450)
## Thinking Path

> - Paperclip lets people manage work performed by AI agents.
> - Task chat saves follow-up messages while an agent runs.
> - A native runner can accept Steer only after an active provider turn
exists.
> - During preparation, or with an unsupported provider, the queue
showed a disabled Steer button and no delivery action.
> - The server also rejected Interrupt for ordinary native queues.
> - This change offers an explicit Interrupt fallback and uses the
existing cancellation and admission path.
> - People can send their saved messages while Stop confirmation,
cleanup, and execution gates remain enforced.

## Linked Issues or Issue Description

Related: #15865 covers mixed legacy queues. #15030 covers steering
acknowledgements. This change covers native queues without available
steering.

**What happened?**

A queued message had a disabled Steer button while the native runner was
still preparing. Clicking it did nothing. The server returned HTTP 409
for an explicit Interrupt request against the same native queue.

**Expected behavior**

Offer Steer when the provider can accept same-turn input. Otherwise
offer Interrupt, labeled with its actual effect. Preserve saved messages
until the previous execution stops and normal admission accepts the
successor.

**Steps to reproduce**

1. Start a task with the Paperclip Runner adapter.
2. Submit a follow-up while the run is preparing, before its provider
turn opens.
3. Observe the disabled Steer button above the composer.
4. With this change, click Interrupt. The existing Stop path cancels the
previous execution and submits the saved queue through normal admission.

**Paperclip version or commit**

Reproduced locally on master at 523818ea9b. The branch is rebased on
2f9d3d43c3.

## What Changed

- Offer Interrupt for unavailable or unsupported native steering when
the parent supplies an interrupt callback.
- Keep Steer for an available provider turn.
- Accept explicit Interrupt for a deferred native queue and record its
run target.
- Bind native continuation to the exact Interrupt queue, run, actor, and
cancellation receipt. This lets one click send the messages after Stop
completes.
- Add component, task-thread, real PostgreSQL route, and continuation
authorization regressions.
- Close the route fixture database pool before disposing its PostgreSQL
instance.
- Document the fallback and its Stop and admission requirements.

## Verification

- Red: two component cases failed because Interrupt was absent. Two
route cases returned HTTP 409, `This queue does not support
interruption`, before the repair.
- Red: stronger delivery assertions also caught `send_predates_stop`
after the first repair. The admission fix covers both the freshness
exception and later eligibility check.
- Green: both route regressions now wait for preparation settlement,
then deliver both saved messages exactly once without a second click.
The complete route suite passes 119 tests. The complete continuation
suite passes 376 tests, including wrong actor, queue, target, and
missing-receipt cases.
- UI: task-thread and queued-message suites pass 394 tests, with one
existing skip. The issue-page suite also passes (the earlier three-suite
run passed 644 tests before the final three UI cases).
- Token gates and `git diff --check` pass.
- Full workspace build and low-memory typecheck pass. The initial
typecheck caught a missing `membershipRole` in an unrelated fixture; the
rebase picked up the correction already on master, and the complete
typecheck then passed.
- The full local `pnpm test:run` passed all six chat shards (1,931
tests). It was stopped during general-server validation after the final
commit passed all PR CI gates. The complete local suite is not claimed
as a pass.
- Earlier route-suite teardown timed out with an open database pool.
Closing that pool fixed teardown; the complete route suite then passed.
- All final-head CI gates pass: 52 successful checks and two intentional
skips. Greptile is 5/5 on the same head with no unresolved review
threads.

## Risks

Interrupt stops the previous execution and starts a successor; it is a
different action from same-turn steering. The UI labels that action
explicitly. Cleanup or execution gates can keep the messages queued.
Existing company access, board permissions, queue revision, run target,
cancellation proof, and admission checks remain in place. No schema
migration is needed.

## Model Used

OpenAI Codex (GPT-6), with reasoning, tool use, and code execution. The
exact model ID and context window are not exposed by this client.

## 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-11-06 07:11:38 -06:00
Dotta 1cd1e8c598 fix(ui): place task message copy control left of the timestamp (#19451)
## Thinking Path

> - Paperclip is the open source app people use to manage agents at
work.
> - Task threads show a time below each message.
> - Own messages align with the right edge.
> - The copy control added in #19442 sits to the right of the time.
> - This shifts the time inward even when the control is hidden.
> - This pull request moves the control to the left of the time.
> - The time stays at the outside edge.

## Linked Issues or Issue Description

Refs #19442.

**What happened?**
The copy control occupies space to the right of the time on own
messages.

**Expected behavior**
The copy control sits to the left of the time. The time stays at the
right edge.

**Steps to reproduce**
Open a task with your own sent message. Move the pointer over its
footer.

## What Changed

- Move TaskChatBubbleActions before the time in the own-message footer.
- Keep the layout change inside TaskChatBubble. No shared contract
change is needed.

## Verification

- All 36 TaskChatBubble tests pass.
- Design-token gates pass.
- UI typecheck and production build pass.
- Browser layout checks pass on desktop and touch: copy is left of the
time, the time ends at the bubble edge, and the time does not move on
hover.
- Clipboard contents and copied feedback pass.
- Screenshots cover idle, hover, copied feedback, and touch using the
production component in the existing Storybook fixture.
- Current-head CI passed: build, typecheck, selected general tests, all
eight e2e shards, policy, security, and aggregate verification.
- Greptile and Paperclip reviews score the current commit 5/5. No open
findings or review threads remain.
- The first Greptile review requested an order assertion. The existing
copy test now checks that the control precedes the time and the time is
the last footer element. All 36 component tests pass. The review thread
is resolved.
- Initial test and Storybook commands used the wrong working directory
or argument form. Corrected commands run successfully.
- Local typecheck and Storybook initially could not resolve
mdast-util-from-markdown. A frozen dependency install restored the
missing package. Typecheck, build, and browser checks then passed. An
early browser retry reached the server before it was ready; the
ready-server run passed.

## Risks

- Low risk. This change only moves the existing control before the time.

## Model Used

- OpenAI GPT-6 Codex. Exact deployed model ID and context window are not
exposed in this runtime. The agent used reasoning, tools, and code
execution.

## 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
- [x] My branch name describes the change and contains no internal
ticket id or instance details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable (existing copy
tests cover unchanged behavior; browser checks cover position)
- [x] I have updated relevant documentation to reflect my changes (the
behavior is described here)
- [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
2026-11-06 07:07:04 -06:00
DottaandPaperclip 2f9d3d43c3 feat(ui): copy own task messages as Markdown (#19442)
## Thinking Path

> - Paperclip is the open source app people use to manage agents at
work.
> - The task thread shows human messages and agent replies.
> - Users can copy agent replies with an existing control.
> - Users need the same control for their own messages.
> - This pull request adds that control beside the message time.
> - Users can copy the original Markdown and attachment links.

## Linked Issues or Issue Description

**What existing behavior does this improve?**
Copy a message from the task thread.

**Subsystem affected**
ui/ — React board UI.

**Current behavior**
Your own messages have no copy control beside the time.

**Proposed behavior**
Move the pointer over the time to show a copy icon. Select it to copy
the original Markdown. Keyboard focus also shows the icon. Touch screens
show it at all times.

**Reason and benefit**
Users can reuse their message text without losing Markdown or attachment
links.

**Breaking changes**
None.

## What Changed

- Reuse TaskChatBubbleActions in the footer of your sent messages.
- Keep the timestamp and iMessage source label.
- Show the control on hover or keyboard focus. Keep it visible on touch
screens and small screens.
- Respect the reduced motion setting.
- Test original Markdown, attachment links, copy feedback, other
authors, and pending messages.
- Add the required membership role to the existing routine-user test
fixture so UI typecheck can pass.
- Keep this behavior in TaskChatBubble. No shared contract change is
needed.

## Verification

- 45 focused component, artifact, and clipboard tests pass after rebase
onto master.
- The routine-user fixture tests pass: 9 tests.
- UI typecheck, UI production build, design-token gates, and browser
checks pass.
- Five screenshots cover idle, hover, copied feedback, keyboard focus,
and touch states. The existing Storybook own-message story uses the
production component with sample content.
- Browser checks verify clipboard contents and reduced motion.
- GitHub PR CI passed for d46d2890c4:
build, typecheck, selected general tests, all eight e2e shards, policy,
security, and aggregate verification. Storybook visual and out-of-scope
suites were skipped by CI.
- Greptile and Paperclip reviews both scored the current commit 5/5 with
no actionable findings. No open review threads.
- Full repository typecheck and build stop because Cargo is missing in
the local environment. Earlier full tests reached resource limits. No
full-suite pass is claimed.
- The first typecheck after rebase found stale local dependencies and a
missing membershipRole in a routine-user test fixture. A frozen
dependency install resolved the package errors. This PR adds the
required fixture field and the later UI typecheck passes.
- Browser states to verify: idle, hover, keyboard focus, copied
feedback, touch, and reduced motion.

## Risks

- Low risk. The change adds a footer control for sent messages owned by
the viewer.
- The control uses the existing clipboard fallback and error feedback.

## Model Used

- OpenAI GPT-6 Codex. Exact deployed model ID and context window are not
exposed in this runtime. The agent used reasoning, tools, and code
execution.

## 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
- [x] My branch name describes the change and contains no internal
ticket id or instance details
- [x] I have run focused tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes (the
behavior is described here)
- [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-11-06 06:35:16 -06:00
DottaandClaude Opus 5.5 e9999071f3 fix(ui): store intraword underscores as typed, without the editor's escapes (#19350)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - People write task requests in the Markdown editor, and the stored
text becomes the agent's prompt
> - MDXEditor exports every literal underscore as `\_`, even inside
words such as `E2E_ASK_12` or `snake_case`
> - Agents then see backslashes the person never typed, and sometimes
repeat them in their answers
> - This pull request removes only the intraword escape when the editor
hands text back, as is already done for `\>` and `\<`
> - The benefit is that identifiers, filenames and exact strings reach
agents exactly as typed

## Linked Issues or Issue Description

**What happened?**
In QA staging smoke (launch 3108, task QA-671), a person created a task
through the UI that asked for the exact reply
`E2E_ASK_12_journey_1793913565974_507b01b8`. The stored description, and
the agent's prompt, contained `E2E\_ASK\_12\_journey\_…`. The agent's
`paperclip_finish` summary was unescaped, but its final answer copied
the escaped form. The posted reply therefore did not equal the requested
text, though the rendered Markdown looks the same. Earlier attempts
passed only when the model happened to unescape.

**Expected behavior**
Text the person typed is stored as typed. An underscore inside a word
never needs an escape, because CommonMark never treats it as emphasis.

**Steps to reproduce**
1. Type `snake_case` in any Markdown editor field, such as a new task
description or the chat composer.
2. Read the stored value through the API.
3. Before this change it is `snake\_case`.

**Paperclip version or commit**
master at da3084a05d.

## What Changed

- New `ui/src/lib/underscore-markdown.ts`:
`unescapeIntrawordUnderscores` parses the text with
`mdast-util-from-markdown`, collects the source ranges of `inlineCode`
and `code` nodes, and turns `\_` back into `_` only outside those
ranges. It only changes a run of escaped underscores between two letters
or digits (`snake\_case`, `foo\_\_bar`). Text it can't parse is left
unchanged.
- These stay exactly as the editor wrote them: code spans, and fenced or
indented code blocks at any blockquote or list depth; escapes at a word
boundary (`\_\_init\_\_`, which would otherwise render as strong
emphasis); escaped backslashes (`\\_`).
- `MarkdownEditor.tsx`: `toStoredMarkdown` applies the helper after the
existing `\>` and `\<` handling, which is unchanged.
- `ui/package.json`: `mdast-util-from-markdown` becomes a direct
dependency. It was already installed transitively at 2.0.3. Per the
lockfile policy, `pnpm-lock.yaml` is not committed.
- Tests:
- Unit cases cover lists, blockquotes, list-indented fences (including
Greptile's case), inline code, escaped and unmatched backticks,
`foo\_\_bar`, `__init__` and escaped backslashes.
- Two editor tests: the original intraword test, and a round trip of
prose plus code.
- Negative control: when the code ranges are removed, the code
byte-identity tests fail.

## Verification

- `npx vitest run src/components/task-chat src/components/MarkdownEditor
src/lib` in `ui/`: 277 files, 3314 tests passed.
- `pnpm check:token-gates`: all gates clean. `vite build` passed. No
type errors in the changed files.
- An earlier CI Build failure on this PR was `npm error notarget No
matching version found for @microsoft/teams.api@2.2.0`, in the
bundled-server prefetch. That version is published now, and the failure
is unrelated to this change.
- Not verified live on QA yet. QA only uses this after its stacks are
rolled to a build that contains it.

## Risks

- Low risk. Only escapes that cannot change rendering are removed, and
only outside parsed code.
- Stored values written before this change keep their escapes until the
text is edited again.
- Parsing adds a small cost per editor change, and only when the text
contains `\_`.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, browser, code editing). Extended reasoning was used.

## Checklist

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 06:18:47 -06:00
Nicky LeachandClaude Opus 5.5 832ede93ed fix(apps): stop the Connectors page from pegging the CPU on managed instances (#19380)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Connectors page (`/apps`) merges native connectors with about
1,600 apps from the Composio and Arcade catalogs
> - #19328 made some native connectors yield to aggregator sign-in on
managed instances. To decide, it checks whether any aggregator serves
each native row
> - That check scans the whole catalog. It runs once per native row for
every catalog app, so one pass makes about 1,600 × 110 × 1,600 name
comparisons
> - The pass also re-runs on every render, because a memo dependency is
a `Map` rebuilt in the component body. Aggregator polling keeps renders
coming
> - On a managed instance, the page pegs a CPU core and does not become
usable. Self-hosted instances skip the branch, and the tests used one or
two catalog entries, so nothing caught it
> - This pull request makes the check a set lookup, keeps the memo
inputs stable, and adds a test at shipped catalog scale plus a general
review rule for rendering large collections
> - The benefit is that `/apps` loads normally on managed instances
again, and similar regressions fail a test or get flagged in review

## Linked Issues or Issue Description

Refs #19328
Refs #19247

**What happened?**
On a managed instance, opening `/apps` pegs the CPU at 100%, and the
page is very slow or unresponsive.

**Steps to reproduce**
1. Open a managed instance (`useCloudInstance()?.managed === true`).
2. Go to `/apps`.
3. The tab holds a CPU core at 100%.

**Expected behavior**
The Connectors page renders in well under a second, and it does not
re-run catalog work on unrelated renders.

**Root cause**
In `ui/src/pages/apps/Browse.tsx`, `yieldsToAggregators()` calls
`hasAggregatorRoute()`. That function scans every aggregator catalog
entry with regex normalization. The `managedCatalog` loop calls
`yieldsToAggregators()` for every native row, for each of about 1,600
catalog apps. A Node benchmark with the shipped catalog data measured
about 214 s per pass on a managed instance and 40 ms self-hosted. The
`rows` memo depended on `upstreamAppsBySlug` and `gallery`, which were
rebuilt on every render, so the memo never reused its result.

## What Changed

- Index every aggregator identity once (`managedCatalogIdentities`), so
the aggregator route check is one `Set` lookup.
- Cache each slug's yield decision for the pass. Rows hold their final
connections and chat endpoints by then, so the answer cannot change.
- Normalize native row identities once before matching them against
catalog apps.
- Memoize `gallery`, the aggregator snapshots, `upstreamAppsBySlug` and
`managedCatalog`. A small `useShallowStableItems` helper keeps the
per-gateway `useQueries` data stable, so the `rows` memo runs only when
connector data changes.
- Match catalog apps to native rows through an identity-to-row index
instead of scanning the native rows for each catalog app. The earliest
row among an app's alias lookups is the row the old ordered scan found.
- Add a test that loads the shipped aggregator catalog and every native
connector in managed-instance mode, with a saved Composio gateway and
account. It counts `aggregatorAppIdentity` calls. The full load must
stay under 20× the catalog name count (today about 5×). An unchanged
refetch and a search must not rebuild the rows, and a changed account
must still appear. Above the limit the counter throws, so a regression
fails in milliseconds instead of blocking the test worker.
- Add `rules/ui-render-cost.mdc`, a general rule for `ui/src/**` code
that renders large collections: no nested scans, stable memo
dependencies, and a realistic-size test with a deterministic work
counter when a change touches such a path. Register it in
`.greptile/files.json`.

## Verification

- `cd ui && npx vitest run src/pages/apps/Browse.test.tsx`: 100 of 100
pass.
- With the previous `Browse.tsx` and the new test, the new test fails in
70 ms with "Connector catalog work exceeded its budget".
- Measured on the new code: a full managed-instance load makes 25,810
identity calls for 5,095 catalog names. A search makes 0.
- `node scripts/run-tsc.mjs -b` in `ui/`: no new errors. The only errors
are in `PluginOrganizationSwitcher`, because `@paperclipai/plugin-sdk`
is not built in a fresh worktree. They are unrelated to this change.
- `pnpm check:token-gates`: all gates clean.
- I did not check this in a browser on a managed instance. A local
self-hosted instance does not take that branch.

## Risks

- Low. The row-building logic gives the same results. The existing
managed-instance Slack, Asana and Google tests still pass.
- `useShallowStableItems` writes a ref during render. In the worst case,
a concurrent render does one extra memo pass. It cannot return stale
data, because it compares every item by identity.
- The new rule applies to all `ui/src/**` reviews. Its body limits it to
large or unbounded collections, to keep review noise low.

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

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with
tool use (shell, file editing, test execution).

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 06:00:31 -06:00
DottaandPaperclip 21d2a577bb feat: show and change routine responsible users (#19128)
## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - Routines create tasks for repeated work.
> - Each routine has a responsible person.
> - The Overview did not show this person or permit a change.
> - People need to see who will be responsible for automatic tasks.
> - This pull request adds a small name picker beside Default agent.
> - Authorized people can adopt a routine or transfer it to an active
company member.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The routine Overview and the routine update API.

**Subsystem affected**

UI, server, and shared contracts.

**Current behavior**

The routine stores a responsible user. The Overview does not show that
person. The update API cannot change that person.

**Proposed behavior**

Show Responsible user beside Default agent. Click the name to search
active company members. Save only the new person and the current
revision ID.

**Reason and benefit**

People can see who owns future automatic tasks. An authorized person can
adopt a routine without making a new routine.

**Breaking changes**

The detail response adds an optional actor-specific field. The update
API adds an optional responsible user ID. Existing requests keep their
current behavior.

Related: #17368 covers local Board ownership migration. This change adds
the routine control and explicit transfer.

## What Changed

- Show a quiet, searchable person picker beside Default agent.
- Explain that automatic tasks use this person. Manual runs use the
person who starts them.
- Permit human operators with `tasks:assign`, instance administrators,
and local operators to transfer responsibility.
- Check active company membership inside the update transaction.
- Save a revision and record the old and new person in the activity log.
- Keep existing task responsibility and ownership during revision
restore.
- Show errors for failed saves and stale revisions.
- Add tests for permissions, company boundaries, transfers, inheritance,
and picker states.
- Keep the picker disabled until saved data is refreshed. Refresh the
activity list after a transfer.
- Capture the actual previous person under the update row lock for the
activity record.
- Use shared query error handling and retain cached people during a
connection failure.

## Verification

- Focused checks cover 145 tests in 5 files. The first post-fix run
passed 144 tests. One existing Fireflies route test timed out during the
first import while the UI typecheck also ran. The same test passed in
the pre-fix run. The isolated route suite then passed all 21 tests with
no source change.
- The new tests reproduce slow detail refreshes, activity refresh, and
two concurrent transfers without a base revision. The concurrency test
uses PostgreSQL row locks.
- UI and server TypeScript checks passed at commit `1df9587eb`.
- The UI production build passed for the updated head.
- The branch includes master through `26391707f`.
- Design token gates, query-error policy, and Git whitespace checks
passed. The query-error policy reports zero findings.
- Local browser checks passed on desktop and mobile for the initial
feature commit. Search, selection, reload persistence, and revision
creation passed. No page errors or horizontal overflow were found. The
follow-up does not change the normal layout.
- Earlier full local checks could not finish: Cargo was absent, and
concurrent full test/build checks exceeded the 8 GiB memory limit. These
attempts are not passes.
- The first CI run failed on two error-rendering policy findings. That
failure is fixed locally. The three findings from the first Greptile
review are fixed and their threads are resolved.
- Greptile reviewed `1df9587eb2d3e034c2769b1f6955ac52323c95bb` and
scored it 5/5. It confirms all three prior findings are addressed. There
are no open review threads.
- [CI attempt
1](https://github.com/paperclipai/paperclip/actions/runs/42046369415/attempts/1)
passed 53 checks. The server 8/12 shard failed only because the existing
`history-http` runtime fixture did not become ready within 10 seconds
(99 refused connections). The fixture and runtime source are unchanged
from the merged master. That case passed locally on the same PR head
with no source change. Its full 75-test suite also passed locally. The
cause of the CI failure remains unknown.
- [CI attempt
2](https://github.com/paperclipai/paperclip/actions/runs/42046369415/attempts/2)
passed on `1df9587eb`: 54 successful checks, 3 skipped, and 1 neutral.
No failed or pending checks remained. The original fixture-readiness
failure remains recorded above.
- A later review found that viewers with an explicit `tasks:assign`
grant could see an editable picker even though writes were denied.
Commit `8b95ee707` now mirrors active-membership and viewer restrictions
in the GET capability flag. Instance-admin and local-operator behavior
is preserved.
- Three membership regressions failed on the incorrect editable flag
before the fix. After the fix, all 43 routine-route and company-access
cases passed (`--testTimeout 60000` accommodated this sandbox's cold
route imports; no repository timeout was changed). The first pre-fix run
timed out during the cold import in one case; the second pre-fix run
reproduced all three assertion failures.
- Layered server typechecking and whitespace checks passed after the
permission fix.
- [Fresh
CI](https://github.com/paperclipai/paperclip/actions/runs/42057752313)
passed on `8b95ee7075417527687b4a86cbaf63ba87408364` without a retry.
Greptile reviewed this exact head, scored it 5/5, and added no new
findings. All four review threads are resolved. The security status is
also successful.
- The separate Paperclip Review check is still queued because its
assigned review worker requires runtime recovery. This is the remaining
check before completion; the CI workflow itself passed.

## Risks

- A transfer changes the person used for future automatic tasks.
Existing tasks keep their current person.
- Only people can transfer responsibility. The server checks the
permission again on every write.
- A stale revision rejects the save. The user must reload before
retrying.
- No database migration is required.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, tool use, and code
execution. The exact runtime model ID and context window are not exposed
to 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
- [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 (CI passed; separate Paperclip
Review check pending)
- [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-11-06 05:47:30 -06:00
DottaandClaude Opus 5.5 a4bb059f59 fix(heartbeat): retry transient sandbox provider lease refusals and explain the block (#19412)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A task runs in an execution environment. A managed sandbox
environment gets a lease from a sandbox provider (for example Daytona)
before the agent starts.
> - When the provider answered sandbox create with a temporary error
(HTTP 502), the run failed as the generic `setup_failed`.
> - `setup_failed` is the fail-closed code for unknown setup errors. It
gets no automatic retry, so the task went to `blocked` at once. The task
showed only "Couldn't finish this attempt".
> - A user had to find and press Retry. The Retry then worked, which
shows that the failure was temporary.
> - This pull request classifies temporary provider refusals during
lease acquisition from structured fields. It retries them within the
existing bounded bootstrap budget, and it explains the block when the
retries are used up.
> - The benefit is that a short provider outage no longer stops a task.
When the outage continues, the task shows the cause and a Retry that
works.

## Linked Issues or Issue Description

**What happened?**
A task was assigned to an agent that uses a managed Daytona sandbox
environment. Sandbox creation returned `HTTP 502`, and the cleanup of
the provisional sandbox could not be confirmed. The run failed with
`setup_failed` and the error `Failed to acquire lease for environment
"Paperclip Sandbox" (sandbox): Daytona sandbox creation failed (HTTP
502); cleanup could not be confirmed for paperclip-create-…`. Three
seconds later the task was `blocked`. No automatic retry was scheduled.
The task showed no specific reason. A manual Retry started a new run
that completed normally.

**Expected behavior**
A temporary provider error (HTTP 5xx, 429, timeout) before the agent
starts must retry automatically with bounded backoff. The task must show
why it waits. If the retries are used up, the task must show the cause,
a next action, and a Retry that works on the first click.

**Steps to reproduce**
1. Configure an agent with a sandbox environment.
2. Make the provider return HTTP 502 on sandbox create.
3. Assign a task to the agent.
4. See that the run fails with `setup_failed` and the task goes to
`blocked` with no retry.

**Paperclip version or commit**
`master` at `612da1e8e0`.

## What Changed

- Plugin SDK: an `environmentAcquireLease` error now carries a closed
diagnostic envelope (HTTP status or a closed errno code) across the
worker RPC. It takes the status of the create call only, not of the
compensating cleanup lookup. The existing creation-cleanup ownership
envelope is unchanged. New module: `environment-acquire-error.ts`. The
resume diagnostic helpers are shared.
- Server: new `EnvironmentProviderUnavailableError` and code
`environment_provider_unavailable`. The environment run orchestrator
sets it as the cause of `lease_acquire_failed` only when structured
evidence shows a transient status (408, 425, 429, 500, 502, 503, 504, or
a closed connection errno). It never parses message text. Saved-sandbox
resume and stopped-worker refusals keep their own codes.
- Setup failure (`failRunSetup`): a fresh, pre-dispatch run records
`environment_provider_unavailable` with a `providerFailure` (phase
`startup`, retryability `transient`). The run uses the bounded bootstrap
retry (30 s, then 60 s). An accepted interaction continuation keeps its
own continuation retry, as for `plugin_worker_unavailable`.
- Recovery: the new code is registered with the other bootstrap codes.
It is not sent to the generic continuation retry. It does not create an
execution-reconciliation hold, because no provider work started. On
exhaustion the stranded notice has the title "Sandbox provider was
temporarily unavailable" and a specific next action.
- UI: `ProviderFailureNotice` labels the `sandbox` provider as "Sandbox
provider". The task notice shows the provider failure, "Paperclip will
retry automatically" while a retry is scheduled, and Retry after
exhaustion.
- Docs: `doc/execution-semantics.md` describes the policy and the
sandbox ownership rule.

## Verification

Red before the fix, green after the fix:

- `server/src/__tests__/heartbeat-process-recovery.test.ts` › "transient
sandbox provider refusal during lease acquisition":
- "retries automatically and runs exactly one successful successor
without user action": the real heartbeat. The lease runtime returns a
Daytona 502 once, then delegates. Before the fix: one `failed` run and
no successor. After the fix: one `scheduled_retry` successor with no
hold and a `retry_scheduled` presentation. After promotion, exactly one
successor succeeds.
- "explains the block with a working Retry once automatic retries are
used up": three refusals. The task is `blocked` with a "Sandbox provider
was temporarily unavailable" notice. The presentation is `{ state:
"failed", canRetry: true, providerFailure: { httpStatus: 502 } }`, and
no execution-reconciliation hold exists. One `requestTaskRetry` is
accepted, and its run succeeds.
- `server/src/services/heartbeat/run-completion.test.ts` › "routes a …
sandbox provider lease refusal through bounded bootstrap recovery": the
real orchestrator wrapping. Only the fresh, structured 502 is retried. A
message-only 502, a 400, a run after provider dispatch, and a retained
native run stay `setup_failed`.
- `packages/plugins/sdk/tests/environment-acquire-error.test.ts`: the
real worker RPC. The test checks the create status (not the 404 cleanup
lookup), the errno, no provider text, the unchanged cleanup envelope,
and that forged envelopes are rejected.

Commands:

```sh
pnpm --filter @paperclipai/plugin-sdk exec vitest run tests/environment-acquire-error.test.ts tests/environment-creation-cleanup.test.ts tests/environment-resume-error.test.ts
cd server && npx vitest run src/__tests__/heartbeat-process-recovery.test.ts src/services/recovery/ src/services/heartbeat/run-completion.test.ts src/services/heartbeat/retries.test.ts src/services/legacy-execution-recovery.test.ts src/services/task-interruption-presentation.test.ts src/services/task-retry.test.ts src/__tests__/environment-runtime.test.ts src/__tests__/heartbeat-retry-scheduling.test.ts
cd packages/plugins/sandbox-providers/daytona && npx vitest run
pnpm --filter @paperclipai/plugin-sdk typecheck && pnpm --filter @paperclipai/server typecheck
```

I did not run a live Daytona 502 on staging. Re-check on staging after
deploy (see Risks).

## Risks

- Sandbox leaks or double creation: each retry is a new run with a new
durable creation record and a new provider name
(`paperclip-create-<operationId>`). The failed attempt stays owned by
its own record, and the existing creation reconciler settles it. A retry
never reuses or abandons it. While a provisional sandbox waits for
reconciliation, up to two more sandboxes can be created for the same
task. This is the same as a manual Retry today.
- Plugins built with an older SDK do not send the envelope. Their
refusals stay `setup_failed`, which is the current behavior.
- An in-process error with `status`/`statusCode` 500–504 or a connection
errno during lease acquisition is now retried twice. The budget is
bounded and shared with the other bootstrap codes.
- An RPC timeout on the host side is not classified by this change.

> 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

- Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, used through
Claude Code as an agent with tool use (shell, file edits, test runs).
Extended reasoning was on.

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 00:08:44 -06:00
DottaandClaude Opus 5.5 6ca7b151f7 feat: compact an agent's context from the task Properties panel (#19378)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A task's Properties panel shows the assignee agent's context window
(paperclipai/paperclip#19273)
> - The board can see that the context is nearly full, but it cannot act
on it
> - A full context makes the agent compact at an unplanned time, often
in the middle of work
> - This pull request adds "Compact now" to the Context hover card
> - The benefit is that the board can compact the agent's context
between runs, at a time it chooses

## Linked Issues or Issue Description

Refs paperclipai/paperclip#19273

**Problem**
The Context row shows how full the agent's context window is. The board
cannot ask the agent to compact it.

**Proposed solution**
A "Compact now" button in the Context hover card. It runs the harness's
own compaction: Codex `thread/compact/start`, and Claude's `/compact`
command through ACP.

**Alternatives considered**
A separate PRP `session.compact` command. Both harnesses run compaction
as a turn, so a compaction turn keeps the existing durable turn binding,
interruption, and recovery. A separate command needs a second turn
lifecycle.

## What Changed

- UI: the Context hover card shows Compact now with three states:
available, Compacting…, and unavailable with a reason (agent mid-run,
harness unsupported, task closed). The Storybook story shows each state.
- Server: `POST /api/issues/:id/context/compact` (board only,
`decideIssueAccess` `issue:mutate`, activity log
`issue.context_compaction_requested`). It queues a short run marked by
`contextSnapshot.contextCompactionRequestId`. `GET
/issues/:id/context-usage` returns the `compaction` state.
- Server: a compaction run does no task work. The native status arbiter
preserves the task status. The lifecycle skips handoff, liveness
continuation, review-path disposition and the issue comment, as it does
for goal-control runs.
- Runner TS: `startTurn({ compact: true })`. The Codex harness session
sends `thread/compact/start`. The runnerd transport maps it to PRP
`turn.start` with `compact: true` (Codex) or `text: "/compact"`
(Claude/ACPX). The native runtime option `sessionCompaction` requires a
resumed session and settles with a synthesized yielded result.
- Runner Rust: `turn.start` accepts `compact: true` for Codex.
`CodexProvider::start_compaction_turn` sends `thread/compact/start` and
binds the turn from the `turn/started` that follows the empty response.
- Fix (from #19273): Codex emitted `context.compacted` on both the start
and the end of one compaction item. It now emits it once, and the count
is distinct by `compactionId`.
- Fix (from #19273): the conversation event view dropped
`context.compacted`, so a finished run never showed the divider. The
view now keeps compactions, plus context readings for runs that
compacted. A finished run's compactions show as thread dividers.
- Fix: the Codex harness stamps before/after sizes on the compaction
event. Codex reports the compacted size before the compaction item ends,
so the old neighbour-based guess showed the wrong size.

## Verification

- `pnpm -r typecheck`, `pnpm build`, `pnpm check:token-gates`, `node
scripts/storybook-catalog.mjs`: pass.
- New tests: `server/src/__tests__/context-compaction.test.ts`,
`status-arbiter.test.ts`, `issue-context-usage.test.ts` (duplicate
compaction counts once), `ui/.../context-usage-row.test.tsx`,
`native-session-runtime.test.ts` (context compaction), Codex driver and
runnerd transport tests, Rust
`codex_compaction_turn_binds_the_turn_reported_after_its_empty_response`.
Rust `codex_provider` suite: 115 passed.
- `pnpm test:run` locally: one failure in
`chat-channels.integration.test.ts`. It reads
`.claude/skills/slack/SKILL.md` from the main checkout, which is not
present in this worktree. It is not related to this change.
- Live, local worktree instance, Codex agent on the Paperclip runner:
clicked Compact now. The run succeeded. Context went from 35k to 9.4k
tokens, and later 9.4k to 9.2k. The task stayed In Review. The thread
shows "Context compacted · 9.4k → 9.2k tokens" and the meter dropped.
- Live Claude proof: pending. The local Claude agent needs an Anthropic
API key. claude-agent-acp handles `/compact` as an echo-less turn, and
the runner already maps its "Compacting completed." notice to
`context.compacted`.

## Risks

- A compaction run resumes the agent's session. If the session cannot
resume, the run fails with `native_context_compaction_unavailable` and
the task is unchanged.
- Compact now is not available while the agent runs on the task, because
compaction interrupts the active turn.
- The conversation view now returns context readings for runs that
compacted. Runs that never compacted return the same events as before.
- An old runner binary ignores `compact` and rejects the `turn.start`
because it has no text. It does not start an ordinary turn.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, through Claude Code
with tool use and code execution.

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:07:12 -06:00
f8cb4957dc fix(ui): default Tasks to All tasks with a neutral sort header (#19393)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Tasks page helps people inspect work across their company.
> - A new viewer currently starts in Mine and can miss other company
tasks.
> - The task table also shows an active sort header before the viewer
selects a sort.
> - This pull request starts new viewers in All tasks and keeps the
default sort header neutral.
> - People see company work in newest-update order and keep their saved
view choices.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The initial task view and the task table's default sort state.

**Subsystem affected**

ui/ — React board UI.

**Current behavior**

Tasks opens Mine when the viewer has no saved view. The table highlights
Last updated even when the viewer has not selected a column sort.

**Proposed behavior**

Tasks opens All tasks when no saved view exists. The table keeps
newest-updated tasks first without an active header or arrow. A column
selection or an explicit sort URL activates the selected header.

**Reason and benefit**

New viewers can inspect company work immediately. The header
distinguishes default ordering from an explicit sort choice.

**Breaking changes**

The first-visit view changes from Mine to All tasks. Saved views and
explicit view URLs keep priority. Existing Inbox and Needs attention
preferences retain their meaning. The change does not alter task access
or database contracts.

**Additional context**

Refs #19363. This change builds on the grouped task menu from that
merged pull request. It uses the existing menu and design tokens.

## What Changed

- Default the Coordinator and combined task surfaces to All tasks.
- Preserve saved personal views and migrate previous picker choices.
- Keep implicit Last updated descending order separate from active
header state.
- Test default ordering, explicit sorting, reset behavior, view
selection, and preference migration.
- Update the product and implementation docs.

## Verification

- Passed 118 focused tests across task routing, table sorting, views,
and collection preferences.
- Passed `pnpm check:token-gates`, `pnpm -r typecheck`, and `pnpm
build`.
- Full `pnpm test:run` is running. Its result will be updated here.
- Greptile passed the current head with 5/5 and no findings or open
review threads.
- Browser check: All tasks uses the grouped My work and Organization
menu. Last updated has no active highlight by default. Selecting the
header adds the sort URL and active direction. Removing the explicit
sort restores the neutral header.
- Manual check: Open Tasks with no saved task view. Confirm All tasks
opens. Select Mine, leave Tasks, and return. Confirm Mine remains
selected.

## Risks

- The new default shows more accessible company tasks on the first
visit. Saved view choices remain intact.
- Task tables share the sort-state change. Explicit sort links and
column clicks retain active styling. Default row order stays
newest-updated first.
- No schema, API, permission, or execution changes.

## Model Used

OpenAI Codex, GPT-6 family. This session does not expose the exact model
ID or context-window size. Used reasoning, code editing, terminal
commands, and browser verification.

## 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
- [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: Scott Tong <scott@scottsmbpm5max.lan>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-05 18:44:59 -08:00
Devin FoleyandPaperclip 8f79a32898 fix(connections): keep agent access consistent through setup and permission changes (#19352)
Task-hosted tool setup kept requester-only defaults, and audience edits could leave runtime installations restricted after permission bindings were widened. Large provider catalogs could also fail before creating an access card.

Use the normal agent audience defaults for new tool setup, preserve explicit selections through verified OAuth and draft resumption, and synchronize permission bindings and runtime installations atomically. Enforce agent-edit authority and preserve existing access during additive account reuse.

Validation: 915 focused regression tests, typechecks, and builds passed. All 54 CI checks passed and Greptile Apex scored the reviewed head 5/5 with no unresolved threads. Broad local test failures, recoveries, and incomplete heartbeat coverage are documented in the PR.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-11-05 17:38:32 -08:00
Devin FoleyandClaude Opus 5.5 434d224c3a fix(ui): retry stale chunk reloads while a deploy settles (#19361)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI loads each page as a lazy chunk. After a deploy, an
open tab can ask for a chunk from its old build.
> - #17349 added a recovery: the app reloads the page one time when a
chunk fails to load. A loop guard permits only one reload in five
minutes.
> - Staging error monitoring still reports `Failed to fetch dynamically
imported module` from builds that include #17349. The events come 1–10
seconds after page load, and 1–10 minutes after page load.
> - During a deploy, the old server and the new server can both answer
for some seconds. The reloaded page can then miss a chunk again. Also, a
second deploy can occur inside the five-minute window. In both cases the
guard stops the recovery, and the user sees the error page. One more
reload a few seconds later would recover.
> - #18518 keeps old chunks available after a deploy, but only on
deployments that enable shared UI assets. Other deployments still depend
on the reload recovery.
> - This pull request permits up to three reloads in the guard window.
The reloads wait 0 s, 2 s and 5 s, so that the deploy can settle. After
the last reload, the error page shows and reports as before.
> - The benefit is that a stale tab recovers through a normal deploy
without an error page and without error-monitoring noise. A chunk that
stays broken still shows the error after a small, fixed number of
reloads.

## Linked Issues or Issue Description

Refs #17349
Refs #18518

**What happened?**
A browser tab stayed open during a staging deploy. A lazy page chunk
failed to load with `TypeError: Failed to fetch dynamically imported
module`. The route error boundary showed "This page hit an error" and
reported the error. The release included the one-time reload from
#17349. The reload did not occur because the loop guard had already used
its single reload in the last five minutes.

**Expected behavior**
The app reloads again after a short wait while the deploy settles, and
the page loads. The error page shows only when the chunk stays broken.

**Steps to reproduce**
1. Open a board page on a deployment without shared UI assets.
2. Deploy a new build. While the old and new servers both answer, go to
a page that was not loaded yet.
3. The chunk fails, and the page reloads one time.
4. The reloaded page gets its HTML from one server and a chunk request
goes to the other server. The chunk fails again.
5. The guard stops the recovery, and the route error page shows.

**Paperclip version or commit**
`master` at 5ac05871b5

## What Changed

- `ui/src/lib/chunk-load-recovery.ts`: The recovery permits up to three
reloads in the five-minute guard window. The reloads wait 0 s, 2 s and 5
s (`RELOAD_DELAYS_MS`). After the third reload, the recovery returns
`false`, and the boundary shows and reports the error as before.
- Storage stays compatible with older builds.
`paperclip:chunk-load-reload-at` still holds only the bare start time of
the window, so an older build keeps its one-reload guard during a
deploy. The retry count is in a separate key,
`paperclip:chunk-load-reload-attempts` (`<window start>:<attempts>`).
The count applies only to the matching window. A window that an older
build started counts as one reload.
- A delayed retry keeps its timer. The new
`cancelPendingChunkLoadReload()` stops a retry that has not started.
`RouteErrorBoundary` calls it when the user moves to another route, and
when the boundary unmounts. A working page is therefore not reloaded
later.
- The abandonment timer starts after the delay, so a delayed reload gets
the full 10 s before the boundary reports.
- A reload that does not leave the document (for example, the user
cancels it) is not tried again from the same document. This keeps the
behavior from #17349.
- Tests: unit tests for delayed retries, the retry limit, compatibility
with older builds, a stale retry count, and cancellation. Boundary tests
start from a delayed attempt and cancel it on navigation. The e2e spec
for a lazy CSS asset that stays broken now expects 4 page loads before
the error page.

## Verification

- `cd ui && npx vitest run src/lib/chunk-load-recovery.test.ts
src/components/RouteErrorBoundary.test.tsx
src/components/AppErrorBoundary.test.tsx`: 3 files, 32 tests pass.
- Red before the fix: with the new tests, the first PR head fails 5
tests (older-build key, cancellation, cancel on navigation). The
original `master` code fails the retry tests. With this head, all pass.
- `tsc --noEmit -p ui/tsconfig.json` shows no errors in the changed
files.
- `tests/e2e/chunk-load-recovery.spec.ts` runs in CI.
- Manual check: on a deployment without shared UI assets, open a page,
deploy, and go to a page that was not loaded. The page reloads, and it
loads after the deploy settles. If you move to another page during the
wait, the page does not reload.

## Risks

- Low risk. The change affects only the chunk-load recovery path.
- A chunk that stays broken now causes up to three reloads (about 7 s of
waits in total) before the error page shows. Before, it caused one
reload. The limit stays fixed in each five-minute window, so a reload
loop cannot occur, also when old and new builds alternate.
- A retry that the `vite:preloadError` listener starts is not tied to a
route, so navigation does not cancel it. This matches the behavior
before this change, where that reload occurred at once.
- No server, data, or contract changes.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) by Anthropic, used through Claude
Code with tool use (shell, file edits and GitHub connector). Standard
context window, with reasoning.

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:21:08 -08:00
4550253dc9 feat(ui): rename Needs you to Inbox and move Mine into Tasks (#19363)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Coordinator gives people navigation for conversations, tasks, and
decisions.
> - The decision entry is called Needs you, while a separate Inbox
remains under More.
> - Coordinator's personal task filter does not retain the full behavior
of the original Mine view.
> - This pull request names the decision entry Inbox and hosts the
original personal work views inside Tasks.
> - People keep their triage tools and saved choices without moving
between two task destinations.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Coordinator navigation and the personal Mine view in Tasks.

**Subsystem affected**

UI navigation, task view routing, task collection preferences, and
browser coverage.

**Current behavior**

Needs you opens the decision feed. More contains another Inbox
destination. The default Coordinator task filter matches task creators
and assignees. It does not use the full personal participation rules,
archive behavior, shortcuts, grouping, or saved columns of the original
Mine view. The organization task table also resets its column choices on
reload.

**Proposed behavior**

Inbox opens the same decision feed with the inbox icon. Tasks defaults
to Mine and remembers a person's selected view per company. Mine uses
the existing personal Inbox implementation and preferences. It retains
read/unread title weights, keyboard navigation, read/unread shortcuts,
archive and undo, mobile swipe-to-archive, grouping, and saved columns.
Old Inbox links open the matching task view. The organization task table
saves its columns and order per person and company.

**Reason and benefit**

People get Chat, Tasks, and Inbox as distinct navigation entries.
Personal work remains a view within Tasks without losing the existing
triage behavior.

**Breaking changes**

Coordinator no longer offers the previous Inbox destination under More.
Old Inbox URLs redirect into Tasks and retain query parameters and
fragments. The Needs you label becomes Inbox; its decision feed, count,
history, and resolution paths stay in place. Classic retains its
separate optional combined-list setting.

**Additional context**

Refs: #19360, the workspace-defaults PR now integrated from master.
Refs: #10620, related Inbox attention ordering. Duplicate searches found
no open PR for this navigation and Mine integration. Roadmap review
found no separate new execution feature in this scope.

## What Changed

- Rename Coordinator's desktop and mobile decision entry and page to
Inbox. Use the inbox icon. Remove the old Inbox item under More. Clarify
that the combined-list experiment applies only to Classic.
- Add one Coordinator task-view router. Host Mine, Unread, Recent,
Blocked, and Everything through the existing Inbox owner. Keep
organization views in the task table and board. Normalize legacy
filtered URLs before mounting a collection so clearing the last filter
preserves the current layout and local filters.
- Reuse the existing Mine API, participation, read state, archive cache,
undo, keyboard, mobile gesture, grouping, and preference paths. Existing
personal column choices carry over.
- Save organization task-table columns and order per person and company.
Keep controls usable if browser storage fails.
- Update navigation tests, routing and persistence regressions,
exact-run retry browser coverage for each workspace variant, product
documentation, and the affected Storybook notes. Keep existing story
IDs.

## Verification

- Preference, routing, navigation, task table, and inherited Inbox
regression tests pass in focused runs. After integrating master and
addressing the review finding, all 187 focused tests, UI typecheck, and
token gates pass.
- Full build and repository typecheck pass after integrating master. The
full UI suite previously passed: 894 files, 11,169 tests passed, and one
skipped. All 14 desktop/mobile triage and exact-run retry browser cases,
including a fresh production UI build, pass after integrating master.
- The full repository test suite was not run locally for this UI-only
change; full PR CI is running.
- Browser checks use a fresh isolated local instance and a deterministic
process agent for real run-attributed unread activity. They exercise
legacy URLs, title weights, saved grouping and columns, read/unread
shortcuts, archive/undo, and a real mobile touch gesture.
- Earlier browser fixtures were corrected: own activity is read, agent
comments need valid run attribution, and undo uses `u` after the archive
refresh completes. The Inbox content heading is scoped separately from
the layout heading.
- The final settings wording correction passes all 63 settings tests.
- The [next CI browser
run](https://github.com/paperclipai/paperclip/actions/runs/42137377960/job/127629100535)
caught an old URL assertion for denied run retries. The test now pins
each workspace variant and covers Coordinator Tasks explicitly. The old
expectation fails deterministically for Coordinator; all 12 retry cases
pass with the corrected destination, preserving exact run identity and
truthful queued, deferred, and denied feedback.
- Token gates, map checks, and Storybook catalog checks pass. All 30
map/catalog script tests pass. Storybook play tests were not run.
- Greptile reviewed [the final
commit](https://github.com/paperclipai/paperclip/commit/5f2f3789b83af259f694f379afb769465688fb72)
at **5/5** with no new actionable findings. Its sort-only link finding
is fixed and resolved: three cases fail before the fix and pass after
it. `sortField` and `sortDir` keep organization links on the
organization table. [Current-head
CI](https://github.com/paperclipai/paperclip/actions/runs/42139668774)
is running.
- [CI exposed a routing
race](https://github.com/paperclipai/paperclip/actions/runs/42135293421/job/127621981587)
when removing the last label from a legacy Everything link. Four
deterministic regressions failed before the fix and now pass:
collections only mount after URL normalization. The existing end-to-end
component cases confirm local filters, board layout, fragments, and
history survive the change.
- The [initial server shard 4
run](https://github.com/paperclipai/paperclip/actions/runs/42131163087/job/127608751620)
failed an unchanged monitor-drain idleness assertion: two active
operations remained instead of zero. The complete scheduling suite
passes locally (54 tests). One isolated CI retry passed, including its
dependent verification gate. The original timing failure's root cause is
not established.

## Risks

- Mine includes the existing tasks, approvals, failed runs, and join
requests, preserving the original personal participation and attention
rules.
- Shared URLs with organization filters or table sort parameters still
open the organization task collection. Task back-links retain their
original fragments.
- Saved preferences are browser-local. Mine keeps its existing storage
keys; the organization table and selected Coordinator view use
person/company scopes.
- No database or server API contract changes are included. Existing
company access, personal participation, and decision permissions retain
their owners.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools, code
execution, and browser automation. The exact serving revision and
context-window size 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
- [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
- [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: Scott Tong <scott@scottsmbpm5max.lan>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-05 17:07:24 -08:00
f2593b7905 feat: make Coordinator and Agent Chat standard and speed up navigation (#19360)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - People enter that work through the workspace, agent conversations,
and tasks.
> - Coordinator and Agent Chat still require experimental enrollment.
> - Task link prefetches do not warm the compact queries used by the
default task view. Chat also waits for a request when its navigation
list already holds the conversation.
> - This pull request makes Coordinator and Agent Chat standard and
reuses the reads needed for navigation.
> - People can choose Classic workspace in Experimental settings. Cached
work can appear while fresh data loads.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Workspace selection, Agent Chat availability, and navigation between
Chat, Tasks, and task details.

**Subsystem affected**

Cross-cutting: UI, workspace preferences, instance settings, and the
database migration.

**Current behavior**

People must enable Coordinator and Agent Chat in Experimental settings.
Opening a task can repeat requests because prefetch warms the full
detail and comment queries, while the default view reads compact task
data and a recent comment window. Opening Chat waits for a detail
request even when the current person's conversation list contains it.

**Proposed behavior**

Coordinator is the standard workspace for everyone. Experimental
settings offer Classic workspace, off by default. Old Coordinator
experiment values do not select Classic. Agent Chat is available to
everyone, including instances with an old saved or managed off value.
Task link intent warms the queries used by the mounted view. Chat can
use the current person's company-scoped conversation list during
refresh.

**Reason and benefit**

People can use the standard workspace without setup. Repeat navigation
can show cached content while the server refreshes it.

**Breaking changes**

Existing Coordinator experiment and Agent Chat off values no longer
disable these surfaces. Classic requires an explicit new opt-in. The
workspace API keeps its existing response shape and audit checks. The
migration retains legacy values and adds a separate Classic preference.
Execution permissions, company access, and chat privacy checks still
apply.

**Additional context**

Roadmap check: this graduates existing surfaces and improves their
navigation. It does not add a new agent execution capability. Duplicate
searches found no open Coordinator workspace PR. Related server cache
work, Refs: #17828, addresses compact issue-list invalidation; this PR
addresses client navigation queries.

## What Changed

- Add a default-off Classic workspace preference. Derive the existing
workspace API response from that choice. Keep personal persistence,
account isolation, and audited writes in the workspace preference
service.
- Replace the Coordinator experiment control with Classic workspace. Use
Coordinator while preferences load. Update the existing settings
examples and navigation recipe.
- Retire the Agent Chat experiment control. Ignore old stored and
managed off values. Retain the compatibility key and the existing
execution and access checks.
- Reuse account- and company-scoped chat list data with its original
freshness timestamp.
- Share compact task and recent-comment query options between prefetch
and the task view. Keep full-history prefetch for Classic tasks and
expanded history.
- Add upgrade, replay, cache, account isolation, and browser switching
regressions. Update product, database, development, and roadmap
documentation.

## Verification

- Targeted preference, settings, migration, cache, and UI regression
tests passed during implementation. The cache tests hold refresh
requests pending and check that content still renders. They also check
account isolation and stale refresh behavior.
- A real browser test verified fresh Coordinator defaults, Classic
opt-in persistence, and return to Coordinator. It used an isolated
source checkout with fresh data on macOS.
- Token, migration numbering and safety, map, and Storybook catalog
checks pass after the rebase.
- Repository typecheck and build pass. Typecheck and token gates also
pass after review repairs. The broad local run passed all six chat
integration shards (1,931 tests). It then found the host-CLI-dependent
recovery fixture described below. Its remaining duplicate work was
stopped after the full current-head CI matrix passed; this is not a
claim that the broad local command completed green.
- Review repairs passed 419 UI/migration fixture tests, 19 Coordinator
navigation tests, and all 214 tests in the five affected server suites.
The 24 Chat/workspace browser tests pass, including standard Coordinator
switching and explicit Classic persistence.
- The broader browser repair run passed 85 tests. It retained two failed
assertions and skipped five later serial cases. The affected files then
passed all 11 tests after the fixes: reopen the desktop panel after
reload and open More for Audit and Connectors. This includes the five
serial cases that had not run.
- The broad local suite exposed a recovery fixture that executed the
host Codex CLI during a simulated remote probe. On a host with Codex
0.154.0, model fallback broke its fixed-model assertion. The fake
sandbox now reports Codex 0.160.0. All 20 selected recovery/provider
compatibility cases pass after the repair.
- A CLI company import/export suite timed out during server startup in
the second CI run. The port fallback is intentional in that fixture. No
root cause was established. That suite passes on the final commit in CI.
This records recovery, not a claimed product fix for the earlier
timeout.
- The first CI run exposed remaining assumptions about the retired flags
and Classic UI. The repair commit updates those fixtures and navigation
steps. It also restores the missing enabled Cases destination in
Coordinator. Earlier local recovery attempts used the wrong fixture
notification and advanced fake time during live execution; the corrected
tests use the subscribed recovery queue and wall time during execution.
- Greptile reviewed commit `98972b030d4505d838427a2fca6b196b8cad6d47` at
**5/5** with no new comments. The original finding is resolved.
- Current-head CI is green: **56 successful checks**, with three checks
skipped by workflow filters. This includes typecheck, build, the
complete Vitest matrix, all eight browser shards, Runner verification,
release checks, and security checks. [Final CI
run](https://github.com/paperclipai/paperclip/actions/runs/42132651414).
- Typecheck caught missing `pid` and `startedAt` fields in the simulated
command result. The fixture now returns the complete process contract,
and the server test-layer typecheck passes.
- Storybook catalog alignment was checked. Storybook play tests were not
run. Hosted latency and cold bundle loading were not measured.

## Risks

- The new default changes the workspace for people who previously left
the Coordinator experiment off. They can opt into Classic in
Experimental settings.
- The additive migration must run before the new server code. It is safe
to replay and preserves explicit Classic choices.
- Cached content can be stale during background refresh. Existing query
invalidation and source timestamps still control freshness.
- These changes reduce avoidable loading waits. They do not establish a
measured improvement in hosted request latency or initial bundle
download time.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools, code
execution, and browser automation. The exact serving revision and
context-window size 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
- [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: Scott Tong <scott@scottsmbpm5max.lan>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-05 16:28:25 -08:00
Devin FoleyandPaperclip 5ac05871b5 fix(onboarding): unify sign-in actions and recover failed logins (#19336)
## Thinking Path

> - Paperclip lets people create agents and connect their AI accounts.
> - Onboarding uses the shared login panel to connect Claude and OpenAI
accounts.
> - The sign-in link did not update the footer, so the footer could open
sign-in again.
> - A failed or expired sign-in also left onboarding without a retry
action.
> - This pull request gives both sign-in controls one action and makes
failed attempts recoverable.
> - Users can return with a code or start a fresh attempt while keeping
their setup.

## Linked Issues or Issue Description

**What happened?**

The onboarding instruction link opens Claude but leaves the footer
offering Sign in to Claude. If a code is entered without a paste event,
the footer opens provider sign-in again. A failed code, expired session,
or failed sandbox request can also leave an error with no retry control.
Component tests reproduce these failures.

This PR addresses that explicit footer action. It does not establish a
fix for unsolicited navigation of the original Paperclip tab to Claude
before code entry. That reported hosted-Cloud behavior remains under
investigation and needs browser-level evidence.

**Expected behavior**

The link and footer should use the same sign-in action. Both should open
one provider tab and put the footer into a disabled Waiting for code…
state. The code field should stay available. Failed or expired attempts
should show a clear message and an enabled Retry sign-in button.

**Steps to reproduce**

1. Choose Claude during onboarding with no saved subscription login.
2. Open Sign in to Claude from the instruction link.
3. Return and enter the authorization code without a paste event, such
as by typing it.
4. Press the footer action. Before this fix, it opens Claude again.

**Paperclip version or commit**

Reproduced in component tests against master at 31bf6e4482.

**Deployment mode**

The fixed footer defect is reproduced in the hosted onboarding component
path. The regression tests use the real wizard and login panel with
simulated API responses. No live Claude authorization was performed.

Related merged work: #13193 added immediate paste feedback; #12921 kept
the pasted code visible. Neither handles this footer path. The duplicate
search found no open PR for this defect.

## What Changed

- Use one handler for the sign-in link and footer. Opening either enters
Waiting for code… and disables the footer. The code field stays
available. The inline link can reopen the same provider URL if the
provider tab was closed.
- Submit an entered Claude code from the footer or Enter. Preserve
automatic submission on paste and block duplicate submissions.
- Replace raw red onboarding errors with a short failure or expiry
message. Hide the stale sign-in link and old code field.
- Add Retry sign-in in the onboarding footer. The login panel owns
recovery: release the old session, recheck the owner’s active session,
then resume it or start a fresh attempt. Clear the old code and keep the
onboarding choices.
- Pause polling during recovery. Treat an already-removed session as
released, but keep other cancellation failures retryable. Keep missing
resumed sessions recoverable.
- Give shared-card hosts without a retry footer an inline Retry sign-in
action.
- Add coverage for both sign-in controls, reopening the current provider
URL, failed codes, startup, lookup, prompt, status, expiry, missing
sessions, completion, cancellation, resume, and hosts without a retry
footer.
- Add failed and expired Claude sign-in stories to the production
onboarding suite. Their scoped fixtures support draft-agent saves,
cancellation, and a fresh second attempt.

The login panel owns code validation, session lifecycle, and retry. The
wizard owns the footer and opens the provider tab. Optional action
callbacks let each host own its controls; existing panel hosts retain
their session behavior.

## Verification

- The original four link and footer regressions failed before the
initial fix.
- `pnpm -r typecheck`: passed across the workspace. UI typecheck was
repeated after the final UI changes.
- From `ui/`, `pnpm exec vitest run --config vitest.config.ts
src/components/OnboardingWizard.test.tsx
src/components/AgentConfigForm.render.test.tsx
src/components/AdapterLoginChrome.test.tsx`: 405 tests passed.
- Component checks confirm that both controls open the same URL once,
disable the waiting footer, and leave the code field usable. Retry waits
for cancellation, clears the old code, and submits the next code to the
new session. Repeated clicks do not create duplicate attempts. Back
still prevents a late failure or success from reopening the card or
hiring.
- `pnpm check:token-gates` and `git diff --check`: passed.
- `pnpm build`: passed across the workspace before the final shared-card
fallback. The UI build was repeated after that change.
- Full local `pnpm test:run` was not repeated for this revision. The
earlier local run on `11fca74` passed all six chat integration shards
(1,931 tests), then was stopped after that head’s complete CI plan
passed. It did not finish locally.
- CI is pending on the updated head. Greptile reviewed
`876142d3877cdfe63e8438d00b605f4b66aded05` and scored it 5/5 with no new
actionable findings. All review threads are resolved.
- The earlier review of `4283b1c` scored 4/5 and found that the link
could not reopen a closed provider tab. That finding was fixed in
`876142d`. The handler accepts the waiting state while the footer stays
disabled. Regression assertions cover reopening the same URL without
starting another session.
- Storybook browser inspection used the real wizard with simulated APIs
in dark mode at 760 × 900. Both new story play functions reached their
completed state. Clicking Retry in the expired story restored an empty
code field and the second attempt’s authorization URL. No provider page
was opened.
- New story IDs: `onboarding-agent-arc--claude-sign-in-failed` and
`onboarding-agent-arc--claude-sign-in-expired`. Run `pnpm storybook` and
select Agent arc → Claude Sign In Failed or Claude Sign In Expired.
- Storybook catalog validation and its 30 node tests passed. This proves
the story structure and the local simulated interaction; it does not
prove a live provider exchange or Linux visual-baseline parity.
- No live provider sign-in or deployed browser acceptance was run. The
original unsolicited main-tab navigation report remains unverified.

Earlier CI evidence remains recorded: on `11fca74`, server shard 2/12
failed in the unchanged `remote-workspace-migration.test.ts`
orphan-index fixture. Creating its Git worktree lock file returned
`ENOENT` before the behavior under test ran. The test and migration
implementation have no diff in this PR. The focused local test passed (1
passed, 124 skipped). The same-head CI rerun and aggregate verification
passed on attempt 2. The cause of that initial failure was not
established.

Current-head CI failure retained: on `876142d`, server shard 11/12
failed in the unchanged `status-card-evaluation-work.test.ts` test
“coalesces one hundred committed source writes without delaying another
company's saved deadline.” The clock assertion at line 384 finished 610
ms after the saved deadline, against a limit of less than 500 ms. There
are no server changes in this PR. The focused local test passed (1
passed, 35 skipped). The aggregate verification job failed because
`general_tests` failed; it reported no separate test defect. The first
shard rerun request was rejected while the workflow was still running.
After the workflow completed, GitHub accepted a rerun of server shard
11/12 and its dependent verification job on the same head. That rerun is
pending. The cause of the initial timing overrun is not established; a
passing local test does not explain it. [Initial failed
job](https://github.com/paperclipai/paperclip/actions/runs/42123655875/job/127583449952).

## Risks

- The onboarding footer now tracks sign-in, code submission, and retry.
The ordinary paste flow stays automatic; typed codes still need an
explicit action.
- Retry cancels the current attempt before starting another one. A
cancellation failure keeps Retry available and prevents a second start.
- The failure card uses short, fixed text instead of raw provider
errors. Standalone configuration panels retain their existing error
presentation.
- No API, credential storage, authorization, schema, or deployment
changes.

## Model Used

OpenAI Codex, `gpt-6.1-sol`, xhigh reasoning. Used repository
inspection, code editing, and local command execution. Context window
size is not exposed by the harness.

## 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
- [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-11-05 15:10:50 -08:00
DottaandClaude Opus 5.5 bf10bc695a feat(task): show the agent's context window in task properties (#19273)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run through a harness (Codex app-server, or Claude through
ACPX), and the runner turns harness events into Paperclip events
> - Both harnesses report how full the model's context window is. Codex
sends `modelContextWindow` with its token usage. Claude sends `used` and
`size` in ACP `usage_update`
> - Paperclip dropped both values. The task view could not show how
close an agent is to compaction, and a compaction left no visible mark
in the task chat
> - This pull request carries the context-window occupancy through the
runner, stores it with the existing usage events, and shows it as a
"Context" row in the task Properties panel
> - It also shows a "Context compacted · 230k → 38k tokens" divider in
the task chat when the agent compacts its context
> - The benefit is that operators can see when a long task is near its
context limit and when the agent lost detail to a compaction

## Linked Issues or Issue Description

Refs #19252 (same pattern: a canonical harness event turned into
task-chat UI). Refs #19267 (status notices; it turns Claude's compaction
banner into a notice, and this PR reads that notice too).

**What existing behavior does this improve?**
The task view shows no context-window information, and a compaction is
invisible in the task chat.

**Subsystem affected**
Runner usage normalization (Rust and TypeScript transport), the PRP
usage schema, a read-only issue API, the task Properties panel and the
task-chat transcript.

**Current behavior**
Codex `modelContextWindow` and ACP `used`/`size` are dropped in the
runner. `context.compacted` always has null sizes, Claude compactions
emit no `context.compacted` event at all, and the live runner turn hides
every marker except "interrupted".

**Proposed behavior**
`usage.reported` carries an optional `context {usedTokens,
windowTokens}`. The task Properties panel shows a Context row ("10% of
258k") with a hover breakdown (tokens used, input and cached input,
output, cost, compactions). The transcript shows a compaction divider,
with sizes from the usage before and after the compaction.

**Reason and benefit**
Operators can see when a long task is close to its context limit, and
they can see where the agent's earlier context was summarized.

**Breaking changes**
None. The new schema field is optional, and the new endpoint is
read-only.

## What Changed

- PRP `usage.schema.json`: add an optional `context {usedTokens,
windowTokens}` object. Regenerate the schema bundle and the protocol
manifest.
- Rust `provider_events.rs`: add `codex_context_usage` (last request
`totalTokens` and `modelContextWindow`) and `acpx_context_usage` (ACP
`used`/`size`). ACPX `usage_update` now sets `context`.
- Rust `provider_backend.rs`: Codex `thread/tokenUsage/updated` sets
`context` on live (non-historical) usage.
- Rust `provider_events.rs`: emit `context.compacted` for the completed
`context_compaction` provider notice that #19267 produces from Claude's
compaction banner. Only this path marks a Claude compaction, so the
server counts each compaction once.
- `runnerd-codex-transport.ts`: `rehydrateRunnerdUsageNotification`
keeps a valid `context` on `tokenUsage`, so it reaches the stored
`item.completed` usage item.
- Server: new `GET /api/issues/:id/context-usage` (issue read access is
required) and `issueContextUsage` service. It reads only the assignee's
current session (the session of its newest usage report), and returns
that session's latest context reading, non-zero totals, cost, and the
count of compactions across all of the session's runs. Added to OpenAPI.
Shared type `IssueContextUsage`.
- DB: migration 0432 adds two partial indexes on `heartbeat_run_events`,
built concurrently. One indexes usage items by run and time. The other
indexes `context.compacted` events by company and session. `EXPLAIN`
shows that both lookups use them.
- UI: new `ContextUsageMeter` (bar, percentage, hover card) and
`IssueContextUsageRow` in the Execution section of Properties. The row
refreshes every 8 s while a run is active, and once more when the run
ends.
- UI transcript: `native-run-events.ts` maps `context.compacted` to a
`context` activity entry with pre/post sizes from neighbouring usage
items. `transcript-adapter.ts` turns it into a new `context_compacted`
marker variant. The live runner turn keeps that marker as its own
timeline row.
- Storybook: `task-context-window.stories.tsx` (Properties panel in a
task, all states, hover breakdowns, divider).

## Verification

- `cargo test -p paperclip-runner-core --lib provider_events` (new
tests: ACPX context, Codex context, banner compaction, notice
compaction)
- `pnpm --filter @paperclipai/paperclip-runner exec vitest run
src/live/runnerd-codex-transport.test.ts`
- `cd ui && npx vitest run src/components/ContextUsageMeter.test.tsx
src/components/transcript src/components/task-chat
src/components/issue-properties`
- `cd server && npx vitest run
src/__tests__/issue-context-usage.test.ts` (embedded Postgres)
- `node scripts/typecheck-low-memory.mjs`, `pnpm check:token-gates`,
`pnpm --filter @paperclipai/paperclip-runner run check:protocol-types`
- Live check on a local worktree instance through the runnerd path:
- A Codex `gpt-5.5` task showed "10% of 258k" (27k of 258k, input 77k
with 49k cached, output 1.1k).
  - A Claude Sonnet 4.6 task showed "17% of 200k" (34k of 200k).
- Storybook: Tasks and conversations / … / Task chat / Context window.
- Not verified live: a real compaction. The divider is covered by unit
tests through the same projector path the task chat uses.

## Risks

- The schema field is optional, and older runners omit it. The UI
renders nothing until a harness reports a window.
- Migration 0432 builds two partial indexes concurrently on
`heartbeat_run_events`, which is a large table. The indexes cover only
usage items and compaction events, so they stay small. They add a little
write overhead to those events.
- Claude in-progress usage rows still record zero tokens with `requests:
1`. The final turn usage is correct. This PR does not change accounting.
- A "Compact now" action is out of scope. It needs a compaction-only run
that does not produce a run result. It will come in a follow-up.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits, in-app browser for Storybook and
the live instance).

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:54:27 -06:00
DottaandClaude Opus 5.5 4ece2107b8 fix(connections): offer aggregator Slack sign-in on Cloud instead of a customer OAuth app (#19328)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Connectors let people give agents tools, such as Slack, from the
Connectors catalog or from an agent's inline "Connect" card
> - On Paperclip Cloud, the only Slack path is "Slack needs its own
OAuth app". The user must create a Slack app, paste a callback URL, and
enter a Client ID and secret
> - Managed Slack chat setup exists, but it is behind the experimental
Chat connectors setting, which is off on Cloud. Composio's and Arcade's
managed Slack sign-in exists, but the native Slack row hides it
> - In an onboarding test on Cloud, a tester got this card from the
onboarding agent, spent 7 minutes on it, and declined it. No Slack
connection was made
> - This pull request follows the existing Google Workspace pattern: on
Cloud, a native connector whose every setup path needs a
customer-created OAuth app yields new setup to the aggregators' managed
sign-in, in the catalog and in agent connection discovery
> - The benefit is that Cloud users can connect Slack with a sign-in
instead of creating a Slack app

## Linked Issues or Issue Description

**What happened?**
On a Paperclip Cloud instance, the onboarding agent asked the user to
connect Slack. Connectors > Slack > Connect showed "Slack needs its own
OAuth app. Slack does not let Paperclip register itself automatically,
so this connector needs an OAuth app you create." The form asks for a
callback URL, a Client ID and a Client secret. It has no link to Slack
app settings. The method description said "Use the provider-hosted
connection for the quickest setup." The tester gave up. The original
notes said that the cloud connector asked for an HTTPS server. That is
not correct: no HTTPS or deployment-mode check is in this path. The real
blocker is the customer OAuth app requirement.

**Expected behavior**
Slack can be connected from the supported Cloud flow without setup steps
that only an operator can do.

**Steps to reproduce**
1. On a Cloud instance with Chat connectors off (the Cloud default),
open Connectors and search for "Slack".
2. Click Connect on Slack. The "needs its own OAuth app" form opens.
3. Or, let an agent call `connections_search("Slack")`. It returns
native `slack` first, and its card opens the same form.

**Additional context**
Evidence: the Slack gallery entry on the Cloud instance had
`ownershipAvailability` `platform_shared: false`, `platform_provisioned:
false`, `customer: true`. `GET
/api/companies/:id/chat-slack/setup-options` returned 403 "Chat
connectors are disabled".

## What Changed

- `packages/shared`: add `nativeConnectorRequiresCustomerOAuthApp(app,
settings)`. It is true when every available setup path of a native
connector needs a customer-created OAuth app: no Paperclip-managed
client, no dynamic client registration, and no enabled chat setup.
- `ui/src/pages/apps/Browse.tsx`: on Cloud, such a connector yields to
aggregators, the same as the built-in Google connectors. The catalog
shows the "Connect through Composio or Arcade" row, and its Connect
button opens the provider choice. A saved native connection, a chat
endpoint, Chat connectors on, a self-hosted instance, or no aggregator
route keeps the native row.
- `server/src/services/connection-intents.ts` and new
`connection-native-yield.ts`: agent `connections_search` uses the same
rule. On Cloud, it does not prefer native Slack for new setup, so the
agent asks the provider-choice question (Composio, Arcade or Zapier).
The rule applies the advertised Paperclip Cloud connector profiles, so
apps with a managed Cloud client (for example Asana) do not yield. If
the profiles cannot be read, the native connector stays.
- `slack.json`: correct the Slack tool method copy, which said "quickest
setup". Add a "register" console link to `https://api.slack.com/apps`,
so the form now shows "Open Slack app settings".
- Tests in all three layers.

## Verification

Red/green. I committed the tests first (`2386fd83df`) and ran them
against unchanged source code.

**Red (before the fix):**

```
server › aggregator connection fallback › Slack on Paperclip Cloud
× offers aggregator Slack routes while native Slack only supports a customer-created OAuth app
AssertionError: expected [ 'slack', 'google-calendar', …(7) ] to deeply equal [ 'via:composio:slack', …(2) ]
+   "slack", "google-calendar", "google-docs", ...
Tests  1 failed | 3 passed | 45 skipped (49)

ui › Connectors landing page
× offers aggregator Slack sign-in on Cloud while the built-in Slack connector needs a customer OAuth app
AssertionError: expected 'SlackSend and read messages in your t…' to contain 'Connect through Composio or Arcade.'
Received: "SlackSend and read messages in your team's channels.Connect"
Tests  1 failed | 5 passed | 92 skipped (98)

shared › Slack tool connection guidance
× describes the customer-created Slack app honestly and links to Slack app settings
AssertionError: expected 'Use the provider-hosted connection fo…' not to match /quickest/i
Tests  1 failed | 3 passed (4)
```

**Green (after the fix):**

```
✓ server › Slack on Paperclip Cloud › offers aggregator Slack routes while native Slack only supports a customer-created OAuth app
✓ server › Slack on Paperclip Cloud › keeps native Slack when Chat connectors provide its own setup path
✓ server › Slack on Paperclip Cloud › keeps native Slack for a company that already saved a Slack connection
✓ server › keeps native Slack on self-hosted instances, where operators register their own Slack app
✓ ui › offers aggregator Slack sign-in on Cloud while the built-in Slack connector needs a customer OAuth app
✓ ui › keeps the built-in Slack connector when Chat connectors offer its setup path
✓ ui › keeps the built-in Slack connector when the instance is self-hosted
✓ ui › keeps a saved built-in Slack connection on its native row on Cloud
✓ shared › describes the customer-created Slack app honestly and links to Slack app settings
✓ shared › flags Slack only while its chat setup is unavailable
✓ shared › does not flag connectors with a managed, dynamic-registration, or credential path
```

Neighbouring suites:
- `packages/shared` (all): 98 files, 1060 tests passed
- `ui` `src/pages/apps` + `src/features/connections`: 54 files, 980
tests passed
- `server` `connection-aggregator-fallback` 49/49,
`connection-intents-service`, `native-connection-baseline-fixture`,
`runner-connection-eval-fixtures` passed. `satisfied-connection-intents`
52/52 passed when run alone. It had a suite-setup error only when three
embedded-Postgres runs went in parallel.
- `pnpm check:token-gates`: clean. `tsc --noEmit` shows no errors in
changed files. Local runs show unrelated errors because the
`paperclip-runner` workspace package is not built. CI runs the full
typecheck.

Still needs a live Slack workspace to prove:
- Complete Slack sign-in through Composio or Arcade on a Cloud instance,
and an agent tool call to Slack.
- An agent run on Cloud that asks the provider-choice question for Slack
and then connects through the selected aggregator.

## Risks

- Behaviour change on Cloud only, for native connectors whose every path
needs a customer-created OAuth app and that an aggregator serves. Today
that is Slack (when Chat connectors are off), and Box and Xero if the
aggregator catalog has them. On Cloud, these move to the aggregator
route for new setup, the same as the built-in Google connectors. Saved
connections keep their native row and discovery.
- Cloud users who want their own Slack app can still open
`/apps/connect?source=slack`, but the catalog row no longer links to it
while the rule applies.
- Self-hosted instances keep the native connector unchanged.
- Follow-up, not in this PR: enable managed Slack chat on Cloud (Chat
connectors Cloud default and `PAPERCLIP_SLACK_MANAGED_SETUP_ENABLED`).
This is a flag and operations change. It needs the Cloud Slack broker to
be proven first. When it is enabled, this rule stops yielding Slack
automatically.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, browser inspection of a staging instance, file
editing).

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:12:45 -06:00
Devin FoleyandPaperclip c3550cfb38 fix(daytona): send archive chunks in bounded windows (#19339)
## Thinking Path

> - Paperclip manages agents and saves their work when execution stops.
> - The Daytona plugin saves an archive through a terminal when disk
capacity prevents the normal archive or download copy.
> - That fallback sent one 40 KiB chunk and waited for its host write
acknowledgement.
> - Network delay then applied to every small chunk. A large archive
could exceed the recovery deadline even while bytes were moving safely.
> - This pull request sends a bounded window of chunks and still
acknowledges every completed host write.
> - The benefit is faster recovery over delayed connections, with
bounded memory and the existing size, space and completion checks.

## Linked Issues or Issue Description

Refs #19034, which added the capacity fallback.

**What happened?**

The archive fallback waits for one network round trip after each 40 KiB
chunk. A saved workspace archive can be much larger than that chunk.
Repeating a timed-out transfer does not remove this throughput limit.

**Expected behavior**

Allow a small amount of data to travel before waiting for
acknowledgements. Keep memory bounded, write bytes in order, and stop
after any write or protocol failure.

**Steps to reproduce**

1. Run the real Bash archive sender against a host that delays each
acknowledgement by 400 ms.
2. Transfer 1 MiB plus a partial final chunk with a four-second
deadline.
3. The original implementation times out. The bounded-window
implementation completes and preserves the exact source bytes.

**Paperclip version or commit**

The failing regression uses the original implementation from
`6a66c09f7f7`.

## What Changed

- Send at most sixteen 40 KiB chunks before collecting their
acknowledgements. Drain the final partial window before the completion
receipt.
- Serialize host writes and reject more than sixteen pending frames.
Stop queued writes on the first failure.
- Keep per-frame size checks, total archive limits, the 1 GiB host space
reserve, the transfer deadline and bounded teardown.
- Add a real Bash regression with delayed acknowledgements, plus
overflow and failed-write coverage. Update the plugin documentation.
- Update the agent-creator test fixture for the required directory
membership role introduced by #15183, which otherwise breaks merged CI
typechecking.

## Verification

- Failing first: the final real-Bash regression fails against the
original source with `Daytona archive stream timed out`. It passes with
the change. The empty-stream case passes too.
- Standalone Daytona package `pnpm test`: 578 passed, 8 skipped. The
skips are the existing opt-in live tests.
- Standalone package `pnpm typecheck` and `pnpm build`: passed.
- `pnpm test src/file-sync-capacity.live.test.ts` with a staging Daytona
key: 2 passed on isolated test sandboxes. This covers actual terminal
transport, stop/start, archive and file-copy capacity failures, exact
saved hashes, safe links, cleanup and a second export. The tests removed
their own sandboxes.
- The production dependency assumption is bounded: at most 640 KiB of
decoded payloads can wait for host writes. The parser also has a bounded
encoded buffer. No background work or idle timer is added.
- Initial hosted CI failed with TS2741 in
`ui/src/lib/agent-creator.test.ts` after #15183 made `membershipRole`
required. Rebased onto that change and added the nullable field to the
fixture. UI `pnpm typecheck`, all 4 existing creator-label tests, and
`pnpm check:token-gates` now pass locally. Hosted CI subsequently passed
on the updated head.
- `git diff --check` and a local secret and PII scan: passed.
- Full repository checks were not repeated locally for this standalone
plugin change. Hosted verification at
`bf6b3cb6a622a9523c72b86fdfe616555c8f3ef8`: all 53 check runs passed,
with the 2 opt-in Storybook checks skipped; both required gates (`ci /
verify`, `ci / e2e`) passed. Greptile is 5/5 at this exact head, with no
review threads or merge conflicts. The separate non-required
`security/snyk (cryppadotta)` status remains pending; it is not claimed
as passed.

## Risks

- A burst can queue up to sixteen chunks instead of one. The receiver
enforces this bound and acknowledges a chunk only after its ordered host
write.
- The timeout budget is unchanged. A connection that cannot finish
within that budget can still fail. This change does not retry a provider
turn or change file-recovery authority.
- Source confinement, archive validation, successful producer completion
and publication rules are unchanged. No schema migration.

## Model Used

- OpenAI `gpt-6.1-sol` with xhigh reasoning, in Codex through T3 Code.
Tool use included source edits, real Bash and filesystem tests, and
isolated Daytona tests. The runtime does not expose its context window.

## 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-11-05 13:12:11 -08:00
DottaandClaude Opus 5.5 00c7738513 fix(task-chat): keep messages sendable after a declined connection request (#19331)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The task chat composer shows a queue of saved input that waits for
the agent's next turn
> - When a person declines a connection request (for example Slack), the
server queues a read-only receipt so the agent learns the outcome
> - The composer showed that receipt with its raw JSON receipt in hover
text, and the receipt hid the person's next message
> - The next message could not be edited, discarded, or sent, so the
chat looked stuck after a simple "Decline"
> - This pull request shows a readable status for receipts and keeps the
person's own later messages visible and actionable
> - The benefit is that declining a connection resolves cleanly and the
conversation continues

## Linked Issues or Issue Description

**What happened?**
During an onboarding test on staging, a person declined the agent's
"Connect Slack" request. A queued steering item then stayed above the
composer. It could not be edited. Its hover text showed raw JSON.
Sending the next message produced an error.

**Steps to reproduce**
1. Give a task to a Paperclip runner agent whose run is active or waits
on a busy workspace.
2. Let the agent request a connection, then decline it.
3. See the queued item "Rejected: Connect Slack". Hover it: the title is
the full JSON receipt.
4. Send another message. The message never appears in the queue. Its row
controls stay disabled. Send or Interrupt on the receipt queue returns
`409 This queue does not support interruption`.

**Expected behavior**
A declined connection request resolves cleanly. It shows a readable
status. The next message can be sent.

**Evidence (staging wake diagnostics for the test task)**
- 17:52:09: the decline wake for the Slack request. It stayed deferred,
was never claimed, and was skipped at 18:21.
- 17:52:10: a later retry wake for the plan-approval continuation.
- `GET /issues/:id/queued-comments` projects the oldest matching wake.
From 17:52 to 18:21 the composer showed the Slack receipt.

Refs: related to the separate plan-approval queued-message fix, which
changes the recovery sweep. This PR only touches the queue projection
and the receipt label.

## What Changed

- `server/src/services/queued-interaction-response.ts`: add
`queuedInteractionSummary()`. A receipt now has one readable line, for
example `Declined: Slack connection`, `Connected: GitHub connection`, or
`Accepted: Build the app`. The entry `source.summary` carries it. The
steering body for the agent still includes the typed JSON receipt.
- `server/src/routes/issues.ts` (`findQueuedCommentWake`): a queued
user-message wake now takes precedence over a receipt wake. A receipt
shows only when it is the only queued input. The receipt wake itself is
unchanged and is still delivered to the agent.
- `server/src/modules/wake-queue/adapters/postgres.ts`
(`findExistingDeferredWake`): skip interaction-receipt wakes when a new
message picks the deferred wake it merges into. Before, the receipt was
the oldest deferred wake, the merge was skipped, and every later message
started its own wake. Only the first message then appeared in the queue.
Receipts still never absorb user input.
- `packages/shared/src/types/issue.ts`: add the optional `summary` field
to `IssueQueuedCommentEntry.source`.
- `ui/src/components/task-chat/TaskChatQueuedMessages.tsx`: add
`queuedMessageLabel()`. The row text, the hover title, and all
aria-labels use the summary. They never use the raw steering body.

## Verification

Red/green tests were written first and run against the unchanged code.

New tests:
- `server/src/__tests__/issue-queued-comments-routes.test.ts`
- `shows a declined connection receipt as a readable status, not its
JSON`
- `keeps the next message visible and actionable after a declined
connection (running run | stopped run)`: declines Slack, posts the next
message, and asserts that the queue lists that message with
`canEdit`/`canDiscard`, that the discard works, and that the receipt
wake is still pending for the agent.
- `queues every later message together after a declined connection
(legacy run | stopped run)`: two messages after a decline both appear in
one editable queue, and the receipt wake keeps no message ids.
- `ui/src/components/task-chat/TaskChatQueuedMessages.test.tsx`
- `shows a declined connection request as readable text, never its raw
JSON receipt (BUG-08)`

**Red (unchanged code):**
```
server: × shows a declined connection receipt as a readable status, not its JSON
        AssertionError: expected { kind: 'interaction', …(3) } to match object { …, summary: 'Declined: Slack connection' }
server: × keeps the next message visible and actionable after a declined connection (running run)
server: × keeps the next message visible and actionable after a declined connection (stopped run)
        AssertionError: ["Declined: Slack connection"]: expected undefined to match object { canEdit: true, canDiscard: true }
        (the queue lists only the receipt; the new message is missing)
        Tests  3 failed | 85 skipped (88)
ui:     × shows a declined connection request as readable text, never its raw JSON receipt (BUG-08)
        AssertionError: expected 'Reorder queued message: Rejected: Con…' not to contain '{'
        Tests  1 failed | 23 skipped (24)
```
An exploratory probe on the unchanged code also showed the send error on
this path: `POST /queued-comments/interrupt` on the receipt queue with a
running native run returns `409 {"error":"This queue does not support
interruption"}`. The exact UI text that the tester saw was not
available.

Second commit (review follow-up), unchanged `findExistingDeferredWake`:
```
server: × queues every later message together after a declined connection (legacy run)
        AssertionError: expected [ Array(1) ] to deeply equal [ …(2) ]
        Tests  1 failed | 1 passed | 88 skipped (90)
```
With the change: `Tests  2 passed | 88 skipped (90)`.

**Green (with this change):**
```
pnpm --filter server exec vitest run src/__tests__/issue-queued-comments-routes.test.ts
  Test Files  1 passed (1)   Tests  90 passed (90)
server: heartbeat-comment-wake-batching + src/modules/wake-queue
  Test Files  11 passed      Tests  322 passed (322)
server: retry-scheduling, wake-diagnostics, question-response-delivery, comment-wake-batching, dot-runner, responsible-user-invariant, stale-queue-invalidation, queued-comments routes
  Test Files  8 passed       Tests  357 passed (357)
pnpm --filter ui exec vitest run src/components/task-chat/TaskChatQueuedMessages.test.tsx src/lib/issue-queued-comment-queue.test.ts
  Test Files  2 passed (2)   Tests  31 passed (31)
server: question-response-delivery, connection-intents-service, satisfied-connection-intents
  Test Files  3 passed       Tests  199 passed (199)
```
A first full run of the route file hit a 30s `beforeAll` timeout while
embedded Postgres started on a loaded machine. One new stopped-run case
was also flaky, because the agent could dispatch the new message before
the assertion. I fixed the flake: the test now occupies the agent on
another task (`maxConcurrentRuns: 1`), which is the existing pattern in
this file. I confirmed again that this test fails without the route fix.

Typecheck: `pnpm --filter @paperclipai/shared typecheck`, `pnpm --filter
@paperclipai/server typecheck`, and `pnpm --filter @paperclipai/ui
typecheck` pass. `pnpm check:token-gates`: all gates clean. I did not
run the full `pnpm test:run` or `pnpm build` locally. CI runs them.

## Risks

- Behavior change: when a receipt and a user message wait at the same
time, the composer now shows the message queue. The receipt is no longer
shown there until it is the only queued input. The receipt wake and its
delivery do not change. The resolved card in the thread still shows the
outcome.
- The first line of the steering body for connection receipts changes
from `Rejected: Connect Slack` to `Declined: Slack connection`. The JSON
receipt for the agent does not change.
- Out of scope (unchanged on master, reproduced without any receipt):
while a native run is running, each new message gets its own queued run,
and the queue shows only the first one. Messages that join a
`scheduled_retry` run's context also do not appear in the queue
projection.
- Overlap: the plan-approval queued-message fix (BUG-03) changes the
recovery sweep, not this code. Both fixes touch the same symptom area.
They do not edit the same functions.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, browser inspection of staging, test execution).

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 21:03:37 +00:00
DottaandClaude Opus 5.5 cad3f8ae09 fix(task-chat): keep a yielded turn's streamed reply visible (#19324)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task chat shows each native runner turn while it streams, while
it settles, and after it settles.
> - During the onboarding interview, the agent streams a reply, posts a
question card, and ends its turn as `yielded`.
> - The live tail hides that reply when the card's pending interaction
arrives. The settled turn also hides it, because a yielded turn never
fills the final-response slot.
> - The user sees the reply appear and then disappear. The reply is
still in run history, so this is a rendering regression, not data loss.
> - This pull request hides only the control-plane wait stub of a
yielded turn. It keeps prose that the agent wrote for the user.
> - The benefit is a stable conversation while responses stream and
finish.

## Linked Issues or Issue Description

**Bug description**
During a hands-on onboarding test on staging, interview messages in the
first-task chat appeared and then disappeared.

**Steps to reproduce**
1. Start onboarding and pick "Interview me and propose a plan".
2. Watch the agent's turn. It streams a reply that describes the
question card, for example "I've posted a question card on this task
that asks four things...".
3. The question card appears. The reply disappears and does not come
back after the run settles or after a reload.

**Expected behavior**
The conversation stays stable while responses stream and finish.

**Related**
Refs #18977. That PR changes server-side final-response selection for
native standing conversations and leaves yielded waits and cards in
control. This PR changes only how the task chat renders a yielded turn's
authored reply. The two PRs do not overlap.

**Evidence and root cause**
- Persisted history is complete. The run's events contain the
`agentMessage` `item.completed` with `channel: "final"` and the full
reply text. The `run.result.accepted` result has
`reportedWorkDisposition: "yielded"` with a `response_wake`
continuation. Its summary is different from the reply.
- The rendered thread does not contain that reply, also after "More
messages" expands the history.
- Classification: rendering/replacement, not persistence. Three gates
drop the reply. `TaskChatThread` sets `suppressFinal` on the live tail
when a pending interaction for that run exists. `TaskChatRunnerTurn`
then clears the latched final. `paperclipRunnerFinalResponse` and the
settled-turn path (`!sourceYielded`) return no final response for
yielded turns.
- Those gates were added on purpose so the mechanical "Waiting for
<card>." prose that some providers echo does not show as an answer. This
change keeps that behavior.

## What Changed

- `transcript-adapter.ts`: add `isYieldedWaitStub`. A wait stub is text
that echoes the yielded result summary, or a single-line "Waiting for
..." text. For a yielded turn, `paperclipRunnerFinalResponse` now
returns the last authored final-channel message. It still returns
nothing for a wait stub. New `yielded` and `waitSummary` options let
callers state the yield from run metadata.
- `TaskChatRunnerTurn.tsx`: `suppressFinal` hides only a wait stub. It
no longer hides the agent's authored reply. A new `waitSummary` prop
carries the accepted summary.
- `TaskChatThread.tsx`: a settled yielded turn renders the authored
reply once, in the last populated segment. The reply also counts as a
native response for the "no user-facing response" marker and for
progress de-duplication. The live tail passes the accepted summary to
the runner turn.
- Review round 1 (commit c134d72620): a run that posted a question card
stays out of the accepted response-wake path while the card is pending
and after it is answered. Before, an answered card let the wait summary
replace the reply. The backward scan now skips wait stubs, so a stub
echoed after the reply does not hide it. The accepted response-wake
summary still has precedence for runs that posted no card. The existing
contract test for that still passes.
- Review round 2 (commit 4f300fbb3e): when a steering anchor splits a
yielded turn, the reply renders in the segment that contains it. The
settled final-response choice moves from a nested ternary into a helper.
`TaskChatRunnerTurn`'s `suppressFinal` prop is renamed
`awaitingContinuation`.
- Tests: a `TaskChatThread` red/green test feeds native run events
through the three states: live stream, live with the pending card, and
settled as yielded. An adapter unit test covers authored prose versus
the two wait-stub forms.

## Verification

Red (current `master` source with the new test). The reply disappears
while the run is still live, at the moment the question card arrives:

```
 RUN  v5.0.3 /Users/dotta/paperclipai/paperclip/.claude/worktrees/fix-bug02-vanishing-messages-3e49c5/ui
 ❯ src/components/TaskChatThread.test.tsx (365 tests | 1 failed | 364 skipped) 664ms
   ❯ TaskChatThread runtime transcript selection (169)
     × keeps a streamed interview reply visible after its run yields to a question card 662ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
 FAIL  src/components/TaskChatThread.test.tsx > TaskChatThread runtime transcript selection > keeps a streamed interview reply visible after its run yields to a question card
AssertionError: expected 'Unanswered questionWhat does your org…' to contain 'I\'ve posted a question card'
Expected: "I've posted a question card"
Received: "Unanswered questionWhat does your organization do?RunnerWorking for 103767m 18sThinkingWhat does your organization do?What does your organization do?YesNoOther CancelSubmit answers"
 ❯ src/components/TaskChatThread.test.tsx:3215:35
    3213|       linkedRuns: [linkedRun("running")],
    3214|     });
    3215|     expect(container.textContent).toContain("I've posted a question ca…
       |                                   ^
    3216|
    3217|     // 3. Settled: the run yielded to the question card it posted.
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
 Test Files  1 failed (1)
      Tests  1 failed | 364 skipped (365)
   Start at  13:26:47
   Duration  31.59s (transform 71%, import 19%, environment 8%, tests 2%)
```

The first red run failed one step later, at the settled step (before I
added the live-with-card step). Both steps fail on `master`:

```
AssertionError: expected 'RunnerWorked for 30sUnanswered questi…' to contain 'I\'ve posted a question card'
Received: "RunnerWorked for 30sUnanswered questionWhat does your organization do?...Submit answers"
```

Green (same test, with the fix):

```
 Test Files  2 passed (2)
      Tests  2 passed | 479 skipped (481)
```

Neighbouring suites (`TaskChatThread*`, all of
`src/components/task-chat/`, `IssueChatThread`, `issue-chat-messages`):

```
 Test Files  59 passed (59)
      Tests  1411 passed | 1 skipped (1412)
```

This includes the existing tests "keeps a yielded question turn out of
the final-response slot", "keeps a yielded run-result and provider wait
prose out of the durable final slot", and every "renders only an
authorized terminal response-wake answer" mode. They still pass, so wait
stubs stay hidden.


Review round 1 red/green. I added a fourth test step that answers the
card. With the old pending-only check, that step fails:

```
× keeps a streamed interview reply visible after its run yields to a question card
Received: "RunnerWorked for 30sWaiting for user answers on the interview question card.Questions answered..."
```

With c134d72620, the step passes. The neighbouring suites pass again:
`Test Files 59 passed (59)`, `Tests 1411 passed | 1 skipped (1412)`.

Review round 2 red/green. A new steered-yielded test fails with the old
last-segment placement (`expected 163 to be less than 52`: the reply
rendered after the steering message). It passes with the fix.
Neighbouring suites, now including `design-sandbox`: `Test Files 61
passed (61)`, `Tests 1426 passed | 1 skipped (1427)`.

Other checks:
- `node scripts/check-token-gates.mjs`: all gates clean. The change has
no styling edits.
- `ui` `tsc --noEmit`: no errors in the changed files. The remaining
errors are already on `master` in this worktree: missing generated
storybook catalog JSON, and workspace packages that are not built.
- I did not run the full `pnpm -r typecheck`, `pnpm test:run`, or `pnpm
build` locally. CI runs them.

## Risks

- A yielded turn can now show a final reply above its question card.
This is the text that the user already saw while the turn streamed. The
text now stays instead of flashing away.
- The wait-stub check is heuristic. A one-line authored reply that
starts with "Waiting for" stays hidden, as before. A multi-line provider
stub that does not match the summary now shows.
- No data, API, or schema changes.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, in Claude Code with
tool use (shell, file edit, browser inspection of the staging run
history).

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:00:50 -06:00
b929ad43de fix: keep dependency waits neutral and explain GitHub access failures (#19315)
## Thinking Path

> - Paperclip manages agent work and shows users what can move it
forward.
> - Task status and tool approval cards describe different parts of that
work.
> - A queued dependency wait has no provider attempt to retry.
> - GitHub can reject an approved action while other connection tools
still work.
> - These cases showed a false Retry banner and an unhelpful provider
error.
> - This pull request keeps dependency waits neutral and gives GitHub
access failures a direct repair path.

## Linked Issues or Issue Description

Related: #19268 preserves pending Retry preparation and current failure
causes.

**What happened?**

A queued wake cancelled by the dependency gate appeared as a failed
attempt with Retry. A GitHub MCP permission denial appeared as raw HTTP
error text after an approved repository action.

**Expected behavior**

Keep the task's dependency state. Show GitHub access failures with a
clear explanation and a link to the selected connection's Permissions
page. Preserve actual failures, provider details, and approval gates.

**Steps to reproduce**

1. Queue a wake, then leave its task dependent on unfinished work before
dispatch.
2. Open the task after the dependency gate cancels that wake.
3. Separately approve a GitHub action whose MCP result contains `403
Resource not accessible by integration`.
4. Reopen the saved tool approval.

## What Changed

- Recognize both queues' unstarted dependency-wait receipts in
cancellation provenance and task projections. No dispatch or recovery
gate changes.
- Classify GitHub's specific permission error after MCP result
normalization. Keep result validation, the raw diagnostic, and working
tools available.
- Apply the same display code to both historical GitHub error codes.
Identify the provider from company-scoped connection metadata, including
renamed apps, without rewriting stored decisions.
- Show a token-based inline access card with **Manage GitHub access**
and collapsed **Error details**.
- Give agents trusted recovery steps after an approved GitHub action
fails. Keep provider text in the untrusted result and prevent automatic
replay.
- Add regressions, a production-component Storybook case, and
execution/connector documentation.

## Verification

- Red before the fix: cancellation provenance, dependency-wait
projection, the real queue cancellation, approved MCP failure
classification, saved interaction projection, and the error card
assertions failed at the expected behavior.
- Green focused tests: queue cancellation, one ready-task executor,
approval-before-dispatch, one failed GitHub request, connection reuse,
retained provider diagnostics, saved interaction reads, task list/detail
projection and redaction.
- UI: 106 component tests passed. Token gates and Storybook catalog
checks passed.
- `node scripts/typecheck-low-memory.mjs`: passed.
- Local source instance: verified a saved dependency wait stays neutral
after reload; a saved GitHub error shows repair guidance, expands its
original error, and opens the correct connection Permissions page.
PostgreSQL and HTTP APIs were real. The GitHub denial was a controlled
MCP fixture; no account or provider permission changed.
- `pnpm build`: passed for all workspace packages, the Runner binary,
server, CLI and UI. The server build and all server typecheck layers
also passed after the review repair.
- Additional safety checks passed for realistic repository arguments,
active recovery, failed Retry preparation, and bounded provider
diagnostics.
- Review repair: 45 pure tests and 7 PostgreSQL cases passed. These
cover both historical error codes, renamed apps, and
company/provider/application identity boundaries. A second local browser
check verified the renamed app, retained error detail, and correct
Permissions destination.
- Final review regressions: the dispatcher and metadata cases failed
before the repair. Afterward, 25 focused cases and 59
projection/identity cases passed, including connection-key precedence.
The native wait and app-only GitHub source also passed a local browser
check.
- The historical read adds at most one connection query per interaction
read batch. It selects IDs and the provider key only and runs only for
candidate permission failures. It adds no provider requests.
- Full local `pnpm test:run` stopped at the chat suite's `afterAll`
cleanup timeout. All 268 assertions in that group and the three prior
chat groups passed. The failure is retained here; it is not counted as a
full-suite pass. CI passed on `e8a692e1e2` after one workspace-startup
rerun. Fresh CI is pending for the combined head. Its first workspace-a
attempt timed out in the CLI company import/export test's child-server
startup wait; 4,968 UI tests and 287 CLI tests passed in that job. The
same shard passed on the preceding commit. The workspace shard passed on
its one rerun. All other test, build and browser jobs passed. The [first
failure](https://github.com/paperclipai/paperclip/actions/runs/42093823928/job/127478869941)
is retained in the Actions history. Greptile reviewed the preceding head
at 5/5. Fresh review is pending for the combined head.

## Risks

The GitHub classifier matches the documented permission-denial phrase,
not every 403. Rate limits, missing resources and other providers retain
their existing error behavior. Historical normalization affects display
only. A dependency cancellation needs an unstarted run and a supported
queue receipt; current user gates, Retry preparation and recovery remain
authoritative. No schema migration or credential changes.

## Model Used

OpenAI GPT-6, through the Codex agent. The exact runtime model ID and
context-window size are not exposed in this session. Used reasoning,
repository tools, TypeScript, PostgreSQL, MCP fixtures and browser
verification. The parallel agent-recovery commit records Anthropic
Claude Opus 5.5 as co-author.

## 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 described the issue in-PR using the bug template
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change
- [x] I have run the focused tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation
- [x] I have considered and documented risks
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open findings
- [x] I will address reviewer comments before requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:55:34 +00:00
DottaandClaude Opus 5.5 22281d4b3a feat(task-chat): approve harness-proposed plans (Codex plan mode, Claude ExitPlanMode) (#19291)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run through the native runner. Codex and Claude Code each
have their own plan mode, and each produces a proposed-plan document.
> - Paperclip already has a planning work mode, an issue `plan`
document, and a `request_confirmation` approval gate for plans.
> - These parts were not connected. The Codex proposed-plan item was
transcript text only. The server path never put Codex into plan mode.
Claude was never put into plan mode, and its ExitPlanMode plan was
dropped. A planning run could close its task as done with no approval.
> - This pull request turns a harness's own proposed plan into the
issue's Plan document and the existing approval gate, for Codex and
Claude.
> - The benefit is that users approve or send back the agent's real plan
from the task chat. Approve resumes the agent. Request changes sends the
reason back to the agent.

## Linked Issues or Issue Description

Refs #19252

**Subsystem affected**
Native runner provider events (Codex and ACPX Claude), the runner Codex
provider (Rust), server native plan synchronization, and the task chat
UI.

**Problem or motivation**
Codex and Claude both produce a proposed-plan document in their native
plan mode. Paperclip did not use it. In live tests on the server path, a
Codex planning run was not in Codex plan mode, saved its own
`implementation-plan` document, and marked the task done without
approval. Claude planning runs were not in Claude plan mode, and the
ExitPlanMode plan text never reached Paperclip.

**Proposed solution**
Add a canonical `plan.proposed` event for a harness's proposed plan. Put
Codex and Claude into their native plan mode for planning runs. Save a
completed proposal as the issue `plan` document with the existing
`request_confirmation` gate. Agents can also propose a plan on a
standard task; the work mode stays standard.

**Alternatives considered**
Reuse `plan.updated`. Its schema is the within-turn checklist and is not
the durable Plan document, so a separate event keeps the two clear.
Stronger prompt text without native plan mode. Live tests showed that
the model kept its own document workflow.

**Roadmap alignment**
This reuses the issue plan document, planning work mode and approval
gate that already exist. It adds one canonical provider event.

## What Changed

- `packages/paperclip-runner/protocol/schemas/*`: add the
`plan.proposed` event (`paperclip.plan.proposed.v1`: `proposalId`,
`status`, `markdown` up to 60k characters, `source`). Regenerate the
schema bundle, validators and manifest.
- `packages/paperclip-runner/src/provider-events.ts`: map the Codex
`plan` item and a `<proposed_plan>` block in a completed Codex message
to `plan.proposed`. Add `claudePlanProposalEvent`.
-
`packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs`:
send Codex's native `collaborationMode: plan` on `turn/start` for a
read-only (planning) Codex run. Before this change the server path never
sent it.
- `runner-core/src/acpx_event_payload.rs`: admit `plan.proposed` as ACPX
rich display activity.
- `drivers/acpx/codex-runtime-adapter.ts`: set Claude Code to native
plan mode for a read-only run. Capture Claude's ExitPlanMode plan from
its permission request, record it, and decline the native "Ready to
code?" prompt.
- `drivers/acpx/codex-acpx-driver.ts`, `cli/acpx-runtime-sidecar.ts`:
emit the captured proposal as a rich event. Complete that turn instead
of reporting it as cancelled.
- `backends/codex-native-backend.ts`: per-harness plan-mode
instructions, and a short opt-in hint for Claude on standard tasks.
- `server/src/services/native-runtime/native-session-executor.ts`:
synchronize a completed `plan.proposed` into the `plan` document and a
`request_confirmation` in planning and standard mode. Do not change the
issue status during the run (that revoked the run's lease). The run
finalizer turns the pending confirmation into the review wait.
- `server/src/services/heartbeat-run-summary.ts`: keep a
`<proposed_plan>` block out of the posted reply.
- UI: new `plan_proposal` provider family. The task chat shows a live
"Writing plan" card while a proposal streams and a one-line record when
it completes. Resolved plan reviews keep a receipt ("Approved Plan v1",
"Requested changes to Plan v1: reason"). Plan previews skip section
headings.
- Storybook: `Tasks and conversations/.../Task chat/Proposed plan
approval` shows the journey.
- `doc/SPEC-implementation.md`: describe harness-proposed plan approval.

## Verification

- `cargo test -p paperclip-runner-core --lib` (new:
`read_only_codex_turns_use_native_plan_collaboration_mode`)
- `cd packages/paperclip-runner && npx vitest run
src/provider-events.test.ts
src/drivers/acpx/codex-runtime-adapter.test.ts`
- `cd server && npx vitest run
src/__tests__/native-plan-proposal-sync.test.ts
src/__tests__/heartbeat-run-summary.test.ts`
- `cd ui && npx vitest run
src/components/transcript/native-run-events.test.ts
src/components/task-chat`
- Live test on a local worktree instance with real harnesses:
- Codex (`gpt-5.6-sol`), planning task: Codex asked one question, then
produced a native plan item. The task showed Plan v1 with Approve plan /
Request changes and moved to in review. Approve changed the work mode to
standard. Codex then implemented the plan and its tests passed.
- Codex, Request changes with a reason: Codex woke in planning mode and
produced Plan v2 that includes the change.
- Claude (`claude-opus-5-5`), planning task: Claude was in plan mode.
ExitPlanMode became Plan v1 and the turn completed. Approve resumed
Claude in standard mode, and Claude implemented the plan.
- Claude, standard task that asks for a plan first: Claude stopped
before code changes and asked for plan approval. The work mode stayed
standard.
- Storybook: open `Proposed plan approval` and check each stage.

## Risks

- Codex planning runs now really use Codex plan mode on the server path.
Codex can ask a clarifying question before it proposes. This matches
Codex plan mode.
- Claude planning runs now use Claude plan mode. A declined ExitPlanMode
ends the Claude turn; the runner reports it as completed. Claude's tool
row for that request records a rejection from Claude's side.
- Codex does not stream the proposed plan on the server path yet, so the
live card shows a placeholder until the plan completes.
- A `<proposed_plan>` block in any completed Codex message on a standard
task now asks for approval. This is the documented opt-in path.

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

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`), via Claude Code with
tool use (shell, file edits, browser). Long context. No extended
thinking mode was used.

## Checklist

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:53:33 +00:00
Svetlana ZolotenkovaandPaperclip 1b0f78100d fix(access): return membershipRole in the company user directory (#15183)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents often check a human approval. The check must confirm the role
of the approving user in the company.
> - An agent can read the company user directory. The directory removes
the membership role from the server record.
> - The admin member list requires `users:manage_permissions`. An
approval check must not need that permission.
> - This pull request returns `membershipRole` in the existing user
directory and keeps all access limits.
> - The benefit is that agents can verify an approver's role without
admin permissions.

## Linked Issues or Issue Description

**What happened?**

`GET /api/companies/{companyId}/user-directory` returns active users
without `membershipRole`. An agent that checks the authority of an
approving user cannot tell an `owner` from other roles. `/members`
requires `users:manage_permissions`.

**Expected behavior**

The directory returns the exact `membershipRole` from
`companyMemberships`. A missing role stays `null`. Reading this field
does not require admin permissions. Member management keeps its current
limits.

**Steps to reproduce**

1. Use a company with an active `owner` user and an active user without
a role.
2. Call `/user-directory` with an agent key.
3. Before this change, neither record has `membershipRole`. After this
change, the field is `owner` and `null`.
4. The same agent still cannot read `/members` or change a member.

## What Changed

- Add `membershipRole` to the select and result of
`loadCompanyUserDirectory`.
- Test the exact `owner`/`null` values, agent read access, denied admin
read and change, denied other company, and denied unauthenticated
request.
- Keep the existing `assertCompanyAccess` and the company, user type,
and active filters.
- The client type includes a nullable `membershipRole`. The management
test rejects only `users:manage_permissions` and checks the exact calls.
The first review findings are fixed in a0d1ad3.

## Verification

- `pnpm --filter @paperclipai/server typecheck`: pass.
- `pnpm --filter @paperclipai/server build`: pass.
- `pnpm --filter @paperclipai/ui typecheck`: pass.
- `pnpm exec vitest run
server/src/__tests__/company-user-directory-route.test.ts
server/src/__tests__/access-service.test.ts
server/src/__tests__/access-routes-hidden-floor.test.ts`: 16 passed, 17
skipped. `ui/src/lib/company-members.test.ts` is added.
- The regression test fails on the old `access.ts` because
`membershipRole` is missing. It passes after the change.
- `git diff --check`: pass.

## Risks

The directory response gets one more field. There are no migrations. The
role comes directly from the server record, with no `owner` fallback.
User management permissions do not change.

## Model Used

OpenAI Codex, GPT-6, with shell tools, code editing, and test runs. The
environment does not expose the exact model identifier.

## 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-11-05 14:43:02 -06:00
DottaandClaude Opus 5.5 d807887534 fix(ui): use one status glyph mapping for every task view (#19329)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task status is the main signal on every task surface: sidebar rows,
the task header, the status menu, lists, the inbox, pills and chat
cards.
> - One task state rendered different status icons in different views. A
blocked task that waits on an active sub-task showed the blue "in queue"
glyph in the task header. The status menu, the chat chip and the sidebar
rows showed the red "Blocked" glyph for the same task.
> - The cause: each view passed its own subset of the presentation
fields to `StatusIcon`. Many views dropped `blockerAttention` and some
dropped `externalConversationState`. The status menu rendered its
current row from the bare status. The summaries API did not return
`blockerAttention`, so the recent-task sidebar could not know the state.
> - This pull request puts the state-to-glyph/label mapping in one
resolver and makes every task view pass all of its fields.
> - The benefit is that the same state uses the same icon and label
everywhere.

## Linked Issues or Issue Description

No public issue. Found in a hands-on onboarding test on a staging cloud
workspace.

**What happened?**

The sidebar and the task pane/status menu showed different status icons
for the same state. On the test task (blocked, waiting on an active
sub-task), the task header and the status button showed the blue "in
queue" glyph. The selected "Blocked" row in the status menu showed the
red blocked glyph. The coordinator sidebar "Recents" and project rows
also showed the red blocked glyph, and the chat "Subtask created" chip
showed a red "Blocked" chip.

**Expected behavior**

The same underlying state uses the same icon and label everywhere.

**Steps to reproduce**

1. Create a task with a sub-task. Block the parent on the sub-task, and
let the sub-task's agent run (the parent is "blocked, covered").
2. Open the parent task. The header shows the blue "in queue" glyph.
3. Open the status menu. The current "Blocked" row shows the red glyph.
4. With the coordinator sidebar on, look at the task in Recents or in
its expanded project. It shows the red glyph.

**Paperclip version or commit**

`master` at 03eb49053f.

## What Changed

- Add `ui/src/lib/task-status-presentation.ts`.
`resolveTaskStatusPresentation()` is the one mapping from `status`,
`displayStatus`, `externalConversationState` and `blockerAttention` to
glyph, label and accessible copy. `taskStatusIconProps()` picks all four
fields from a task record.
- `StatusIcon` and `IssueStatusBadge` use the resolver. The status menu
draws the current row with the trigger's presentation (in queue, idle,
monitoring).
- `IssueStatusBadge` now accepts `blockerAttention`.
`taskStatusVar.in_queue` maps to the existing in-progress chip hue, the
same as the existing `--status-task-icon-in_queue` icon token.
- Task call sites spread `taskStatusIconProps(task)`: coordinator
sidebar recents and project rows, classic recent tasks, lists, rows,
columns, inbox, blocked inbox, my issues, dashboard, properties,
relation pills, search, sibling navigation, chat chips, case summaries,
status cards and user profile. Step/check statuses in protocol cards
stay unchanged because they are not task statuses.
- `GET /api/companies/:companyId/issues/summaries` returns
`blockerAttention` for blocked rows (at most five IDs, through the
existing `listBlockerAttention` service). `IssueSummary` gets the
optional field.
- Recent-task snapshots store and refresh `blockerAttention`. Sidebar
project rows request the `rows` projection, which includes blocker state
(the `index` projection skips it).
- Storybook: the Foundations/Status Language story gets a "Same state,
every view" section.

## Verification

Red on `master` (tests written first, then run against the unchanged
code):

```
cd ui && npx vitest run src/components/StatusIcon.picker.test.tsx src/components/CoordinatorSidebar.test.tsx src/lib/recent-tasks.test.ts
     × keeps the covered-blocker state so the sidebar shows the same queued glyph as the task pane (BUG-07)
     × marks the current row with the same glyph as the trigger for a covered-blocked task
     × marks the current row with the same glyph as the trigger for a waiting review and a monitored task
     × shows a covered-blocked recent task with the same queued glyph and label as the task pane
     × shows project tasks with their full status presentation
 FAIL  StatusIcon.picker.test.tsx > ... covered-blocked task
AssertionError: expected 'var(--status-task-icon-blocked)' to be 'var(--status-task-icon-in_queue)'
 FAIL  StatusIcon.picker.test.tsx > ... waiting review and a monitored task
AssertionError: expected 'var(--status-task-icon-in_review)' to be 'var(--status-task-icon-backlog)'
 FAIL  CoordinatorSidebar.test.tsx > ... covered-blocked recent task ...
AssertionError: expected 'var(--status-task-icon-blocked)' to be 'var(--status-task-icon-in_queue)'
 FAIL  CoordinatorSidebar.test.tsx > ... project tasks with their full status presentation
AssertionError: expected "vi.fn()" to be called with arguments: [ 'company', ObjectContaining{…} ]
 FAIL  recent-tasks.test.ts > ... keeps the covered-blocker state ...
AssertionError: expected undefined to deeply equal { state: 'covered', …(7) }
      Tests  5 failed | 32 passed (37)

cd server && npx vitest run src/__tests__/issue-summaries-routes.test.ts
     × projects the covered-blocker state so every task view picks the same queued glyph (BUG-07)
AssertionError: expected undefined to match object { state: 'covered', …(2) }
      Tests  1 failed | 14 passed (15)
```

Green with this change (same commands):

```
 Test Files  3 passed (3)
      Tests  37 passed (37)
 Test Files  1 passed (1)        # server summaries route
      Tests  15 passed (15)
```

Neighbouring and broader checks:

- `StatusIcon`, `StatusBadge`, `StatusGlyph`, picker, sidebar and
recent-task suites: 73/73 pass.
- Full UI Vitest run: 11106 pass, 1 skipped. Three `IssuesList.test.tsx`
tests timed out at 5 s under full-suite load. The same file passes 73/73
when run alone.
- `tsc --noEmit` for `ui` and `server`: no errors in changed files.
Remaining errors are pre-existing (unbuilt
`@paperclipai/paperclip-runner`, generated Storybook catalog).
- `pnpm check:token-gates`: all gates clean.

Screenshots. The staging task before the fix: the status button is blue,
the current "Blocked" row in the menu is red.

| Staging task (before) | Staging status menu (before) |
| --- | --- |
| ![staging
before](https://raw.githubusercontent.com/paperclipai/paperclip/75e148aa1b100400d406d952e66cfac6c4afa5c3/before-staging-onb1.jpg)
| ![staging menu
before](https://raw.githubusercontent.com/paperclipai/paperclip/75e148aa1b100400d406d952e66cfac6c4afa5c3/before-staging-onb1-menu.png)
|

Storybook "Same state, every view" (Foundations/Status Language). Before
uses the `master` `StatusIcon`/`StatusBadge`; after uses this branch.

| Before (master) | After (this PR) |
| --- | --- |
| ![storybook
before](https://raw.githubusercontent.com/paperclipai/paperclip/75e148aa1b100400d406d952e66cfac6c4afa5c3/before-storybook-menu.jpg)
| ![storybook
after](https://raw.githubusercontent.com/paperclipai/paperclip/75e148aa1b100400d406d952e66cfac6c4afa5c3/after-storybook-menu.jpg)
|

Before: the row glyph is blue, but the chip and the current menu row are
red. After: the row glyph, the chip and the current menu row are all the
blue "in queue" glyph.

Review follow-up (commits 1d3a1a0a9e, d5c798ec53): blocker state on
summaries, search and case responses is glyph-only (state, reason,
counts). Blocker titles and IDs are never returned; a sampled identifier
stays only when the actor can read that blocker. Producer-level tests
failed before the follow-up and pass after it
(\`task-created-items.test.tsx\`,
\`CoordinatorTaskCommitment.test.tsx\`,
\`company-search-service.test.ts\`, \`cases-routes.test.ts\`,
private-blocker case in \`issue-summaries-routes.test.ts\`). Before
merge, the branch was test-merged locally with current \`master\`:
typecheck clean, 1095 UI and 80 server tests in the affected suites
pass.

## Risks

- Low risk. Presentation only. Status mutations still use the canonical
`status`.
- The summaries endpoint now runs the blocker-attention query for
blocked rows. The endpoint accepts at most five IDs and runs the query
only when a row is blocked.
- Sidebar project rows use the `rows` projection instead of `index`.
This adds the blocker and review attention reads for at most 12 rows,
and only when a person expands a project.
- `IssueStatusBadge` shows the blue "in queue" chip for covered-blocked
tasks where callers pass `blockerAttention`. The label stays "Blocked"
and the full reason is in the tooltip.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`, Anthropic), through Claude Code
with tool use (shell, file edits, browser). Default context window.

## 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:42:11 -06:00
DottaandClaude Opus 5.5 1c165e9f1e fix(ui): bound the queued-message strip so it scrolls instead of covering the conversation (#19321)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - In task chat, a user can queue messages while an agent run is
active. The queued messages show in a strip above the composer, with
Steer, Interrupt, Discard and Edit actions.
> - An onboarding test on staging reported that the steering strip
clipped and covered nearby UI.
> - The strip adds one 44px row for each queued message and has no
height limit. With several queued messages it takes most of the thread.
In the Coordinator layout the dock is absolutely positioned at the
bottom of the thread, so the strip grows up over the conversation.
> - This pull request puts the rows in a bounded list that scrolls. The
status and error lines stay below the list.
> - The benefit is that the conversation stays readable at desktop and
narrow widths, and every queued message stays reachable.

## Linked Issues or Issue Description

**What happened?**
With several queued messages, the queued/steering strip above the task
composer grew without a limit. It covered the transcript and nearby
components. At a 1024x600 window, 8 queued messages made the strip 353px
tall in a 555px thread.

**Expected behavior**
The strip stays readable and inside the available space at normal and
narrow widths. Extra queued messages scroll inside the strip.

**Steps to reproduce**
1. Open a task with an active run in the Coordinator workspace.
2. Queue 6 or more messages.
3. See that the strip covers most of the conversation.

**Paperclip version or commit**

`master` at 03eb49053f. Also seen on a staging cloud deployment during
an onboarding test (BUG-04, P2).

## What Changed

- `TaskChatQueuedMessages`: wrap the sortable rows in a list with
`max-h-(--tc-queued-list-max-h) overflow-y-auto overscroll-contain`.
Status and error lines stay outside the list, so they are always
visible.
- `ui/src/index.css`: new token `--tc-queued-list-max-h:
min(calc(var(--spacing) * 40), 28dvh)`. This is about 3.5 rows, and less
on short windows.
- New Storybook story `coordinator-dock-queued-messages`: the production
`TaskChatComposerDock`, queued strip and composer inside the Coordinator
thread layout, with 8 long queued messages (light, dark, phone).
- New Playwright spec
`tests/storybook-visual/queued-messages-dock-layout.spec.ts`: measures
the dock and strip against the header, thread and viewport at desktop,
desktop dark, short desktop (1024x600), very short desktop (1024x420)
and phone sizes. It also checks that the last queued Steer action is
reachable by scrolling, and that the latest transcript message is not
under the dock. A separate test drags the first queued row below the
third and checks the new order inside the scrolling list.

## Verification

Red first (spec run on unchanged `master` code, Storybook dev server):

```
STORYBOOK_TEST_BASE_URL=http://localhost:6016 npx playwright test --config tests/storybook-visual/playwright.config.ts --project=stories tests/storybook-visual/queued-messages-dock-layout.spec.ts --retries=0 --workers=1
  ✘  1 … queued messages stay bounded inside the composer dock (desktop)
  ✘  2 … queued messages stay bounded inside the composer dock (desktop dark)
  ✘  3 … queued messages stay bounded inside the composer dock (short desktop window)
  ✘  4 … queued messages stay bounded inside the composer dock (narrow phone)
    Error: queued strip covers the conversation
    Expected: <= 286
    Received:    353
    …
    Error: queued strip covers the conversation
    Expected: <= 222
    Received:    353
  4 failed
```

Green after the fix (same command):

```
  ✓  1 … queued messages stay bounded inside the composer dock (desktop) (14.8s)
  ✓  2 … queued messages stay bounded inside the composer dock (desktop dark) (3.3s)
  ✓  3 … queued messages stay bounded inside the composer dock (short desktop window) (2.1s)
  ✓  4 … queued messages stay bounded inside the composer dock (narrow phone) (2.3s)
  4 passed (25.7s)
```

Second round, after review feedback that a fixed 160px cap does not
shrink on very short windows. Red with the fixed `max-h-40` cap:

```
  ✓  1 … (desktop)
  ✓  2 … (desktop dark)
  ✓  3 … (short desktop window)
  ✘  4 … (very short desktop window)
  ✓  5 … (narrow phone)
  ✓  6 … queued messages still reorder by drag inside the scrolling list
    Error: queued strip covers the conversation
    Expected: <= 150
    Received:    161
  1 failed
  5 passed (18.6s)
```

Green with the `--tc-queued-list-max-h` token:

```
  ✓  1 … (desktop) (6.2s)
  ✓  2 … (desktop dark) (1.9s)
  ✓  3 … (short desktop window) (3.3s)
  ✓  4 … (very short desktop window) (1.3s)
  ✓  5 … (narrow phone) (1.4s)
  ✓  6 … queued messages still reorder by drag inside the scrolling list (1.5s)
  6 passed (16.1s)
```

Measured layout (strip height / dock top in a thread):

| Case | Before | After |
| --- | --- | --- |
| 1245x760 dark | strip 353px, dock top 272 | strip 161px, dock top 464
|
| 1024x600 | strip 353px, dock top 112 | strip 161px, dock top 304 |
| 390x700 phone | strip 353px, dock top 228 | strip 161px, dock top 420
|

Neighbouring suites and gates:

- `cd ui && npx vitest run src/components/task-chat/`: 55 files, 884
tests passed (includes `TaskChatQueuedMessages.test.tsx`).
- `pnpm check:token-gates`: All gates clean.
- `node scripts/storybook-catalog.mjs`: in sync.

Screenshots (1024x600, then 390x700; taken with the first-round 160px
cap, which is the same height at these sizes):

| Before | After |
| --- | --- |
| ![before short
desktop](https://raw.githubusercontent.com/paperclipai/paperclip/0cb0f9537f4ec3f226ff974ee0f895cccebb706b/before-short-desktop.png)
| ![after short
desktop](https://raw.githubusercontent.com/paperclipai/paperclip/0cb0f9537f4ec3f226ff974ee0f895cccebb706b/after-short-desktop.png)
|
| ![before
phone](https://raw.githubusercontent.com/paperclipai/paperclip/0cb0f9537f4ec3f226ff974ee0f895cccebb706b/before-phone.png)
| ![after
phone](https://raw.githubusercontent.com/paperclipai/paperclip/0cb0f9537f4ec3f226ff974ee0f895cccebb706b/after-phone.png)
|

## Risks

- Low risk. The change is one wrapper element with token-scale Tailwind
classes.
- Drag to reorder now happens inside a scroll container. The new spec
covers a drag reorder. dnd-kit auto-scrolls the container during a drag.
A dragged row can be clipped at the list edge while it moves, as before
at the strip edge.
- The original report mentioned a "black bar". The original screenshot
was not available. I reproduced the overlap and the unbounded growth. I
did not see a separate black bar in light or dark theme. The row action
menu uses a Radix portal, so it does not clip.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use
(shell, browser, Playwright).

## 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:37:24 -06:00
DottaandClaude Opus 5.5 5d9f6c56c2 fix(ui): stop calling setup-draft agents "paused manually" (#19322)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The task chat shows a notice when the assigned agent is paused
> - A new agent is stored as `status: "paused"` with `pauseReason:
"setup"` while its setup runs
> - The notice treated every pause reason except budget, import and
system as a manual pause
> - So a user who just created their first agent saw "It was paused
manually." and a "Resume agent" button
> - This pull request shows the real setup state and the correct
recovery action
> - The benefit is that new users do not think that they paused their
agent by mistake

## Linked Issues or Issue Description

**What happened?**
During an onboarding test on a staging cloud workspace, the first agent
showed a "paused manually" warning right after it was created. Work
continued later. The agent activity log shows `agent.identity.created`,
several `agent.updated` events while setup ran, then
`agent.hire_created`. It shows no pause or resume event. The agent was
never paused by an operator. It was a setup draft (`pauseReason:
"setup"`).

**Steps to reproduce**
1. Create a first agent in the onboarding flow.
2. Open the task that is assigned to the agent while the agent setup is
not finished.
3. See "<agent> is paused. New runs will not start until the agent is
resumed. It was paused manually." with a "Resume agent" button.

**Expected behavior**
The notice shows that the agent is still being set up. When the draft
waits for the user, the notice links to the agent page to finish setup.
A real pause keeps a clear "Resume agent" action.

## What Changed

- `IssueAssigneePausedNotice` now handles `pauseReason: "setup"`
separately. It shows "<agent> is still being set up." with an info tone.
- While the lifecycle state is `preparing`, `verifying` or `pausing`,
the notice says that runs start automatically when setup finishes. It
shows no action.
- When the setup draft is parked (`lifecycleState: "paused"`), the
notice shows a "Finish setup" link to the agent page, where the existing
"Finish setup" button lives.
- The notice never offers "Resume agent" for a setup draft.
- `company_archived` pauses now say "It was paused because its company
is archived." They offer no resume, because restoring the company
resumes these agents.
- Only `pauseReason: "manual"` says "It was paused manually." An unknown
or null reason shows no reason sentence.
- Added tests for the setup-draft states and the company-archive pause.

## Verification

Red, on current `master` code with the new tests (`cd ui && npx vitest
run src/components/IssueChatThread.test.tsx -t
IssueAssigneePausedNotice`):

```
     × shows a setup-draft agent (preparing) as still being set up, not manually paused 35ms
     × shows a setup-draft agent (paused) as still being set up, not manually paused 11ms
     × explains a company-archive pause instead of calling it manual 12ms
AssertionError: expected 'CEO is paused.New runs will not start…' not to contain 'paused manually'
      Tests  3 failed | 5 passed | 100 skipped (108)
```

Green, same command after the fix:

```
      Tests  8 passed | 100 skipped (108)
```

Neighbouring suites (`npx vitest run
src/components/IssueChatThread.test.tsx
src/components/TaskChatThread.test.tsx`):

```
 Test Files  2 passed (2)
      Tests  471 passed | 1 skipped (472)
```

- `node scripts/check-token-gates.mjs`: all gates clean.
- `tsc --noEmit -p ui`: no errors in the changed files. The local run
shows errors only from unbuilt workspace packages
(`@paperclipai/plugin-sdk/ui` dist and the generated storybook feature
map). CI builds these packages.
- I did not run the full `pnpm -r typecheck` / `pnpm test:run` / `pnpm
build`. CI runs them.

## Risks

- Low risk. The change is UI-only and limited to one notice component.
- A null or unknown pause reason no longer says "manually". It still
says that the agent is paused and still offers "Resume agent".

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edits, browser inspection of the staging
workspace).

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:37:11 -06:00
DottaandClaude Opus 5.5 2575b941d0 feat(task-chat): show hook and auto-review decisions as governance rows (#19266)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run on harnesses such as Codex and Claude, and the task chat
shows what each run did
> - Harnesses make automatic decisions: hooks block or allow tool calls,
and Codex auto-review approves or denies actions
> - Today Paperclip drops these decisions or shows them as generic rows.
Codex `safety.review.*` always says `decision: "unknown"`, the native
Rust runner drops `hook/*` and `item/autoApprovalReview/*`, and
claude-agent-acp drops Claude hook messages
> - An operator cannot see why an agent changed course or which action
ran without a person's approval. This conflicts with Paperclip's
approval-gate model
> - This pull request records each hook and review decision with its
reason and shows it as a small governance row in the task chat
> - The benefit is that automatic decisions become auditable, and a
blocked action explains itself without opening run logs

## Linked Issues or Issue Description

Refs #19252 (precedent: canonical `plan.updated` events rendered as a
task-chat card).
Builds on #19259 (ACP `_meta` passthrough from ACPX), now merged.

**Subsystem affected**
Runner provider events (Rust native Codex normalizer, TS drivers, ACPX
sidecar), the claude-agent-acp patch, the PRP provider-event schema, and
the task chat activity UI.

**Problem or motivation**
Hook and auto-review outcomes reach Paperclip but are lost or flattened.
Codex reviews always record an unknown decision. Native Codex runs drop
hook and review notifications in the Rust normalizer. Claude hook
messages never leave the adapter. Codex reuses one `run.id` for every
call of a hook, and the id contains a host path.

**Proposed solution**
Map the real outcome, decision, action, risk and reason into additive
optional fields on `paperclip.hook.v1` and `paperclip.safety.review.v1`.
Forward Claude SDK hook messages. Render small grey rows: a stopped
action is tinted and shows its reason, an approved review folds into the
command it approved as an "auto-approved" mark, and routine hook passes
stay out of activity summaries.

**Alternatives considered**
Separate rows for every approval. This duplicated each approved command,
so approvals fold into the command row instead.

## What Changed

- Runner (Rust): new `governance_events.rs`.
`normalize_codex_notification` now maps `hook/started|completed` and
`item/autoApprovalReview/started|completed`. Each hook call gets its own
id (a digest of the run id and start time), so no host path is stored.
- Runner (TS): new `governance-events.ts` with the same mapping for the
TS Codex driver, and the Claude hook mapping. The ACPX sidecar publishes
Claude hook updates as schema-validated `hook.*` rich events.
- claude-agent-acp patch: turns on `includeHookEvents` and forwards
`hook_started` / `hook_response` on
`session_info_update._meta.claudeCode.hook`, with bounded output.
- Protocol: additive optional fields. Hook records get `outcome`,
`message`, `source` and `toolName`, and the `blocked` and `stopped`
statuses. Review records get `actionKind`, `action`, `riskLevel`,
`userConsent` (the reviewer's user-authorization level; keys that end in
"authorization" are redacted as secrets), `rationale` and `decidedBy`,
and the `timed_out` and `aborted` decisions. Regenerated the manifest
and validators.
- Fixed: the hook `blocking` flag read `executionMode === "blocking"`,
but Codex sends `sync` / `async`.
- UI: `native-run-events` keeps the full hook and review payloads.
`governance-presentation.ts` writes the row labels.
`fold-auto-reviews.ts` attaches an approved review to its command row.
The rows show the tint, the reason line and the "auto-approved" mark.
Activity summaries lead with a block or denial.
- Storybook: "Task chat / Governance rows" shows every outcome, a Codex
run, a Claude run and the live line.
- Docs: added a governance activity section to
`packages/paperclip-runner/docs/protocol-compatibility.md`.

## Verification

- `pnpm --filter @paperclipai/paperclip-runner exec vitest run
src/governance-events.test.ts src/provider-events.test.ts
src/drivers/codex src/drivers/acpx src/cli`. 2305 tests pass. This
includes a replay of real Codex 0.160 notifications from a session with
hooks and auto-review
(`test-fixtures/governance/codex-0.160-hooks-auto-review.json`,
sanitized).
- `cargo test --release -p paperclip-runner-core`. The full suite
passes, and `governance_events::tests` replays the same fixture.
- `cd ui && npx vitest run`. 11,047 tests pass.
- `node scripts/typecheck-low-memory.mjs`, `pnpm check:token-gates`, and
the runner contract checks (`check:protocol-manifest`,
`check:protocol-types`, `check:capability-contract`,
`check:protocol-coverage`, replay goldens, contract fingerprints) pass.
- Live test on a local isolated instance. A Codex agent and a Claude
agent each used a preconfigured home with a PreToolUse hook that blocks
edits to `0001_init.sql`, plus UserPromptSubmit and PostToolUse hooks.
Both runs showed "PreToolUse hook blocked a tool call" (Codex) or
"PreToolUse hook blocked Bash" (Claude), with the hook's message. They
also showed "UserPromptSubmit hook added context", and the group summary
started with the block.
- Auto-review cannot run in Paperclip today, because the native Codex
runner only allows `approvalPolicy: never`. The review mapping and the
approval fold are proven against real Codex 0.160 app-server output
(fixture above) and in Storybook.

## Risks

- Low risk to execution. These records are display-only and carry no
authority. All new schema fields are optional, so older records stay
valid and older UIs ignore the new fields.
- The `auto-approved` fold depends on `targetItemId` matching the
command's execution id. Real Codex 0.160 data confirms that it matches.
When there is no match, the review stays as its own row.
- In Paperclip's isolated homes, hooks do not run, because the runner
never copies hooks into an isolated home. Rows appear only for agents
with a preconfigured home, or with future managed hooks.
- The claude-agent-acp patch changes. The ACPX profile check still
passes, because the patch is Paperclip's ACPX layer and not the upstream
release.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use
(shell, browser and file editing) and extended reasoning.

## Checklist

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:35:57 -06:00
DottaandClaude Opus 5.5 dd889e6a0b feat: show the assignee's provider usage limit in task properties (#19270)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run on the native Runner through the Codex app-server and the
Claude ACP harness
> - Both harnesses report the provider account's rate-limit windows:
Codex sends `account/rateLimits/updated`, and Claude sends its SDK
`rate_limit_event` as `usage_update._meta["_claude/rateLimit"]`
> - Paperclip dropped both signals, so an operator could not see that an
agent was near or past its provider limit
> - This pull request normalizes both signals into one account-scoped
snapshot, keeps the latest snapshot per agent, and shows it as one
"Usage limit" line in the task properties pane
> - The benefit is that an operator sees "5h 78% · resets 15:40" (amber
above 80%, "Limited until …" when the provider refuses work, "using
credits" when a used-up window falls back to credits) next to the
assignee, and can click it for every window

## Linked Issues or Issue Description

Refs #19246
Refs #19259

**Is your feature request related to a problem?**
Codex and Claude report provider rate-limit windows during a run.
Paperclip dropped them: the Codex notification fell through the Rust
normalizer with no branch, and ACPX dropped Claude's `_meta`. Operators
could not see how close an agent was to its provider limit.

**Describe the solution you'd like**
Store the latest reported windows per agent and show one compact line in
the task properties pane, with a popover that lists every window.

**Describe alternatives you've considered**
A full gauge in the agent list and the run header. A design review chose
one property line with a details popover instead.

## What Changed

- Runner (Rust, the runnerd path): new `provider_rate_limits.rs`. Codex
`account/rateLimits/updated` becomes a `provider.notice.recorded` event
(category `provider.rate_limits`, scope `account`). `CodexProviderState`
merges sparse updates and emits a notice only when the windows change.
ACPX `usage_update` with a sanitized `rateLimit` field also emits the
notice.
- Runner (sidecar): `acpx-runtime-sidecar.ts` forwards only `status`,
`rateLimitType`, `utilization` and `resetsAt` from
`_meta["_claude/rateLimit"]`. This needs the ACPX `_meta` passthrough
from #19259. Until that merges, Claude emits nothing.
- Runner (TypeScript in-process drivers): the same mapping in
`codex-session-notifications.ts` and `codex-acpx-driver.ts` through
`drivers/provider-rate-limits.ts`.
- Shared: `provider-rate-limits.ts` holds the snapshot type, the parser,
the merge, and `summarizeProviderRateLimits` (fullest current window,
expiry by reset time, limited versus using credits).
- Server: `agent-provider-rate-limits.ts` merges each notice into
`agent_runtime_state.state_json.providerRateLimits`, scoped by company
and agent. A session reset keeps the snapshot. No migration.
- UI: `AgentRateLimitPropertyValue` adds a "Usage limit" row under the
assignee in `IssueProperties`. Transcripts treat the notice as run-log
only. A run status event refreshes the assignee's runtime state.
- Storybook: `Tasks and conversations/Task creation and lifecycle/Usage
limit property`.
- Docs: `doc/run-log-events.md` describes the notice and its storage.

## Verification

- `cd packages/paperclip-runner/runner && cargo test -p
paperclip-runner-core --lib rate_limit` and `--test
acpx_provider_events`: sparse Codex merge, dedupe, reached window,
checkpoint round trip, Claude utilization and rejection, notice shape.
- `cd packages/paperclip-runner && npx vitest run
src/drivers/provider-rate-limits.test.ts src/drivers/codex`
- `cd packages/shared && npx vitest run
src/provider-rate-limits.test.ts`: summary, expiry, limited, using
credits, parse and merge.
- `cd server && npx vitest run
src/__tests__/agent-provider-rate-limits.test.ts`: create and merge,
out-of-order snapshot, company isolation, unrelated notices, session
reset keeps the snapshot.
- `cd ui && npx vitest run src/components/transcript
src/components/issue-properties src/context/LiveUpdatesProvider`
- `pnpm -r typecheck` and `pnpm check:token-gates` pass.
- Live test on a local worktree instance: a native Codex agent on a
ChatGPT subscription login ran a real task. The runner recorded one
`provider.rate_limits` notice. The server stored it. The task properties
pane showed "7d 100% · using credits" (this account's weekly window was
used up and had credits, and the run succeeded). The transcript showed
no extra row.
- Not verified live: Claude. The local Claude CLI was not signed in, and
the Claude path also needs #19259. The Rust and sidecar paths have
tests.
- Not run locally: the full `pnpm test:run` and `pnpm build`. CI runs
them.

## Risks

- Low risk. The snapshot is display data. It does not hold or start
work. The existing provider-limit wait (#19246) still reads typed run
failures.
- Codex can send a rate-limit update often. The runner emits a notice
only when the windows change, so run-log growth stays small.
- With API-key auth, the harnesses report no windows, so no row appears.
This is the intended result.
- `CodexProviderState` gains two optional serde fields with defaults, so
older checkpoints still load.

> 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

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, through Claude Code
with tool use (shell, file edits, browser) and extended reasoning.

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:26:51 +00:00
DottaandClaude Opus 5.5 86a9d3ec6c feat(task-chat): tool rows that say what ran and how it went (#19265)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task chat shows each tool call an agent makes as a short
activity row.
> - Real Claude runs showed Bash rows named "Terminal" with no command
and Write rows stuck at "Preparing file…". No row showed an exit code or
a duration, and a tool refused by policy looked like any other failure.
> - Codex already reported exit codes, durations and parsed command
actions, but the UI dropped them, and every Codex read looked like a
shell command.
> - This pull request gives both harnesses one row grammar: a verb icon,
the real subject, an exit chip and a duration.
> - The benefit is that a reviewer can see what ran and how it went
without opening each row.

## Linked Issues or Issue Description

Refs #19259 (this branch includes its `_meta` passthrough commit; merge
#19259 first or together).

**Problem or motivation**
Claude tool rows hide what ran: the runner keeps the first ACP title
("Terminal", "Preparing file…") and drops the refined one. `exitCode`
and `durationMs` are null on every Claude tool event. The UI ignores
`tool.execution.progressed`, `exitCode` and `durationMs` for every
harness.

**Proposed solution**
Read real tool facts in the runner (Claude `_meta.claudeCode`, raw input
and result text; Codex `commandActions`), carry them in the canonical
tool event, and render one row grammar in the task chat.

**Alternatives considered**
Enable Claude's `terminal_output` client capability to get
`terminal_exit`. That changes how command output arrives, and Claude's
own exit code is derived from the error flag anyway, so this PR reads
the error flag and the "Exit code N" text instead.

## What Changed

- **Runner (TS):** `createAcpxToolEventNormalizer` keeps the latest
concrete title instead of the first placeholder. It merges tool facts
across the lifecycle and measures the duration. New
`acpxToolDetailFromRuntimeEvent` reads the native tool name, the command
or pattern, the description, the exit code and a "Permission denied"
reason. Exit 0 is inferred only for Claude `Bash`, where a non-zero exit
is always a failed result.
- **ACPX sidecar:** reads the facts before `rawInput` is dropped and
sends a bounded `toolDetail`. A file path argument becomes an attested
workspace-relative location.
- **runner-core (Rust):** validates `toolDetail`.
`normalize_acpx_tool_call` uses the native name, exit code, duration,
subject, description and blocked reason. Codex `commandExecution` gets
its operation, a single-file target and a subject from `commandActions`.
The shell wrapper is removed, and a single search reads as `"TODO" in
src`.
- **Protocol:** `toolExecution` gains the optional fields `subject`,
`description` and `blockedReason`. Generated validators and the manifest
are regenerated.
- **UI:**
- `nativeRunEventsToTranscript` handles `tool.execution.progressed` and
passes a canonical `detail` on `tool_call` entries.
- The task-chat row shows `$ command`, a path or a pattern, a red `exit
N` chip (exit 0 only for commands) and a duration (0 is hidden).
  - Three or more consecutive reads fold into "Read N files a, b, c +1".
  - A refused tool shows "Blocked" with its reason.
- **Storybook:** `Tasks/Tool rows` compares the data recorded today with
the new runner data for Claude and Codex.

## Verification

- `cd packages/paperclip-runner && npx vitest run
src/provider-events.test.ts src/cli src/drivers/acpx` (1,957 pass)
- `cd packages/paperclip-runner/runner && cargo test -p
paperclip-runner-core --lib provider_events`
- `cd ui && npx vitest run src/components/transcript
src/components/task-chat` (973 pass)
- `pnpm check:token-gates`, `pnpm --filter paperclip-runner build`
(includes the protocol coverage and replay-golden checks)
- Live runs on a local instance with real harnesses:
- Codex (gpt-5.6-sol): the rows read `$ node --test` with `exit 1` and
then `exit 0`, plus real durations. A three-file `sed` shows as one
command and does not claim a single file.
- Claude (claude-sonnet-5-5 through ACPX): the rows show `Bash` and
`Edit` instead of "Terminal" and "Preparing file…". Commands show their
real text, `exit 0` and durations, and edits show `Edited temp.js`.
- Not seen live: a real Claude `Write`, a folded read run and a Claude
policy denial. Unit and Rust tests cover them.

## Risks

- The tool-execution schema gains only optional fields, so older events
stay valid. Older runs keep rendering with what they recorded.
- Commands now cross the sidecar boundary as `subject` (bounded to 4,000
characters). Codex already recorded commands as the tool name, and
server redaction still applies.
- The tool lifecycle now keeps the latest concrete title. A provider
that retitles a call to something less useful would show the newer
title.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use,
code execution and browser automation.

## Checklist

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:57:04 -06:00
DottaandClaude Opus 5.5 1eff07f29e feat(task-chat): show Claude subagents as a collapsible tree in the runner turn (#19264)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task chat shows what an agent does in a run. The Paperclip
Runner sends this activity as canonical provider events.
> - Claude runs subagents through its Agent (Task) tool. A run that
delegates showed a confusing burst of unrelated tool calls.
> - The subagent data was already sent, but it was lost: ACPX dropped
`_meta`, the hardened sidecar dropped `rawInput`, and async subagents
report "launched" as the tool result.
> - This pull request maps each Agent call to a canonical `delegation`
event, links each child tool to its subagent, and shows subagents as a
collapsible card or tree.
> - The benefit is that operators can see what each subagent was asked,
what it is doing now, which tools it used, and what it returned.

## Linked Issues or Issue Description

Refs #19252 (the docked plan card used the same canonical-event pattern)
Refs #19259 (ACPX `_meta` passthrough, merged). This PR builds on it.

**What happened?**
When a Claude agent used its Agent tool, the task chat showed one
generic "Task" row and then the subagents' tool calls as flat rows in
the parent turn. The subagent type, prompt, progress and result were not
visible.

**Expected behavior**
Each subagent shows as its own card with its type, description, prompt,
live progress summary, nested tool rows and result. Subagents that start
together show as one tree with status dots.

**Steps to reproduce**
1. Assign a Claude (Paperclip Runner, ACPX) agent a task that asks it to
launch three Explore subagents in parallel.
2. Open the task while the run is live.
3. Before this change, the subagents' tool calls appear flat in the
parent turn.

## What Changed

- **claude-agent-acp patch:** Under the isolated runner context, the
patch turns on the SDK option `agentProgressSummaries`. It forwards each
subagent's `task_progress` summary and its terminal state and summary
onto the spawning tool call as `_meta.claudeCode.subagentProgress` and
`subagentState`. Native subagent sessions stay off, because ACPX cannot
follow child ACP sessions.
- **Runner:** The new file `claude-subagent-lineage.ts` tracks each
Agent/Task call across ACP update deltas. It maps the call to
`delegation.started|updated|completed` events and stamps child tools
with `parentItemId`. An async subagent stays running after its "Async
agent launched" result until its own state settles.
- **Sidecar path:** `acpx-runtime-sidecar.ts` reads the lineage before
it drops `_meta` and `rawInput`. Delegations go out as schema-checked
rich events. Child tools carry `parentToolCallId`, which Rust
(`acpx_event_payload.rs`, `provider_events.rs`) maps to `parentItemId`.
- **Contracts:** The provider-event schema adds optional `prompt` and
`result` to delegation children and optional `parentItemId` to tool
executions. The protocol modules are regenerated. ACPX Claude now
advertises the `delegation` event family.
- **UI:** `native-run-events.ts` keeps delegation payloads and the tool
parent link. `buildTurnTimelineRows` groups adjacent delegations into a
new `subagent_group` row, nests child tool rows (plain and
provider-activity shapes), and hides the spawning call's own tool row.
The new `TaskChatSubagentCard.tsx` renders the card and tree. A new size
token caps the result height.
- **Storybook:** The new story `Tasks and conversations/…/Task
chat/Subagents` has 7 states.

## Verification

- `pnpm --filter @paperclipai/paperclip-runner exec vitest run src/cli
src/drivers/acpx src/provider-events.test.ts
src/claude-subagent-lineage.test.ts src/protocol` (2069 passed)
- `cargo test --release --locked -p paperclip-runner-core --lib` in
`packages/paperclip-runner/runner` (488 passed, including the new
parent-link test)
- `node --test test/protocol-contract.test.mjs
test/acpx-sidecar-contract.test.mjs
test/acpx-codex-package-contract.test.mjs
test/acpx-rich-extensions-package-contract.test.mjs` (48 passed)
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/task-chat src/components/transcript`, including the new
`subagent-timeline.test.ts`
- `check:protocol-types`, `check:protocol-manifest`,
`check:acpx-profiles` and `pnpm check:token-gates` pass.
- **Live test:** A local test-drive instance ran a Claude Opus 5.5 agent
on the Paperclip Runner (hardened sidecar path). The agent ran real
tasks that launched three parallel Explore subagents. The live view
showed "Running 3 subagents · 2 done" with a progress summary. The
settled view showed "Ran 3 subagents", each with its prompt, nested
tools and its own result text.

## Risks

- `agentProgressSummaries` makes Claude Code generate a short progress
summary for each running subagent about every 30 seconds. This adds a
small model cost to runs that delegate.
- The adapter patch reads `task_notification` and terminal
`task_updated` messages. A future claude-agent-acp version can change
these. The patch hunks then fail to apply, and the dependency bump must
update them.
- If a delegation event is lost, child tools stay at the top level as
before. Nothing is hidden without a matching delegation.
- Codex is not changed. Codex subagents need `multi_agent`, which
Paperclip keeps off on purpose. Changing that is a separate policy
decision.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use
(shell, file edits, browser automation). It did the investigation,
implementation, tests and live test.

## 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: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:58:35 +00:00
DottaandClaude Opus 5.5 d361f92071 feat(task-chat): show harness status notices as chips, out of the answer (#19267)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run through the native runner. The runner turns harness
events from Codex and Claude into canonical provider events, and the
task chat shows them.
> - Harnesses report their own lifecycle: context compaction, retries,
model fallback, and warnings. Paperclip lost or mangled these reports.
> - Claude (claude-agent-acp 0.73) sends compaction, model-fallback and
warning banners as ordinary message text, so "Compacting..." ended up
inside the agent's answer and the issue comment.
> - Codex sends `error` with the text in `error.message` and a
`willRetry` flag. Paperclip read `params.message`, so every Codex error
said "Provider notice", and every retry looked fatal.
> - This pull request keeps these reports out of the answer and shows
them as small status chips between the turn's activity.
> - The benefit is that users can see why an agent paused, retried or
forgot earlier context, and the answer text stays clean.

## Linked Issues or Issue Description

**Subsystem affected**
Native runner provider-event mapping (TypeScript driver, ACPX sidecar
and Rust runner-core) and the task chat transcript.

**Problem or motivation**
Claude status banners are mixed into assistant answer text. Codex error
notices lose their message and their retry state. The task chat hid all
of these notices, and settled turns stripped every provider notice.

**Proposed solution**
Classify Claude banners as provider notices at the ACPX boundary. Map
Codex `TurnError` fields and `willRetry` correctly. Render status
notices as inline chips. A retry chip fades after later provider work. A
final error shows the real provider message.

**Alternatives considered**
Patch claude-agent-acp to tag banners in `_meta`. ACPX keeps only
`origin`, `kind` and `source` from `_meta`, and other sessions own the
ACPX `_meta` passthrough work. Banner chunks already have no
`messageId`, and model output always has one, so the classification
needs no adapter patch.

**Roadmap alignment**
This makes existing harness signals visible. It adds no new protocol
schema. It uses the existing `paperclip.provider.notice.v1` `category`
and `details` fields.

## What Changed

- Codex `error` notifications (TypeScript `provider-events.ts` and Rust
`runner-core/src/provider_events.rs`): read `error.message`, map
`willRetry: true` to a recoverable `provider_retry` notice, keep
category `error` for a final error, and add the `CodexErrorInfo` kind,
HTTP status, and the "Reconnecting... n/m" attempt count as notice
details. Retries in one turn share one notice id.
- New `claude-status-banners.ts`: classifies the claude-agent-acp
compaction, model-fallback and SDK informational banners. A chunk must
have no `messageId` and match a known banner exactly.
- ACPX sidecar and the TypeScript ACPX driver emit these banners as
`provider_notice` events instead of answer text. Compaction start and
end share one notice key and carry a `Phase` detail.
- Rust runner-core accepts an optional `noticeKey` and bounded `details`
on ACPX `provider_notice` events.
- UI: new `TaskChatStatusNotice` chip and
`status-notice-presentation.ts`. Status notices become top-level
timeline rows, render as chips wherever protocol cards render, and stay
visible in settled (historical) turns. Generic warnings stay hidden as
before.
- New tokens `--tc-status-notice-warning-*` in `ui/src/index.css`.
- Storybook story `Tasks and conversations/.../Task chat/Status notices`
with eight states.
- `doc/run-log-events.md`: describes the status notice categories.

## Verification

- `pnpm --filter @paperclipai/ui exec vitest run
src/components/task-chat src/components/transcript`
- `pnpm --filter @paperclipai/paperclip-runner exec vitest run
src/provider-events.test.ts
src/drivers/acpx/claude-status-banners.test.ts
src/drivers/acpx/codex-acpx-driver.test.ts
src/cli/acpx-runtime-sidecar.test.ts`
- `cargo test -p paperclip-runner-core --lib provider_events` and
`--test acpx_provider_events`
- `pnpm check:token-gates`
- Storybook: open
`product-task-chat-status-notices--all-states-gallery`.
- Live test-drive on an isolated worktree instance:
- Codex (gpt-5.6-sol, app-server 0.160): a local pass-through proxy in
front of the real OpenAI API cut the first two response streams. Codex
ran its own retries. The chat showed an amber "Reconnecting" chip live,
then "Recovered after retry" once work continued. With every stream cut,
Codex made five retries and then sent a final error with the real
message.
- Claude (claude-haiku-5-5 through claude-agent-acp 0.73): a long
reading task triggered Claude's own auto-compaction twice. The chat
showed "Compacting context…" live and "Context compacted" afterwards.
The final answer and the issue comment contained no compaction text.
- Not verified live: model fallback and Claude informational banners.
They need a model refusal or a blocking hook. Unit tests cover them.

## Risks

- Low to medium. A Claude model message that is exactly a banner text
and has no `messageId` would become a notice. Model output from
claude-agent-acp always carries a `messageId`.
- Older stored Codex `error` notices (category `error`, not recoverable)
now render as a red row instead of being hidden.
- Claude retries (`api_retry`) are still not visible. claude-agent-acp
reports them only to clients that negotiate session-failure records, and
ACPX drops `_meta`. This is a follow-up that depends on the ACPX `_meta`
passthrough work.
- On a failed Codex turn the thread collapses to a summary, so the red
row does not show there. The existing task banner already shows the real
provider message.
- Some local suites failed on this machine. They do not touch the
changed code: Cursor Cloud staging-custody tests, the ACPX Claude
dependency-graph pin test (a timeout), and the Rust `task_memory`
monitor tests. CI is the reference for these.

> 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

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use,
code execution and browser automation. Default context window. Extended
reasoning was not 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
- [ ] 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

Refs paperclipai/paperclip#19252 (plan card precedent).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:38:57 +00:00
Devin FoleyandPaperclip 088f85c5fe fix: rename the managed environment to Paperclip Sandbox (#19296)
## Thinking Path

> - Paperclip manages AI agents and their execution environments.
> - Managed installations supply a default sandbox through managed
configuration.
> - Most selectors show the configured environment name.
> - A fallback label, two errors, and examples still use Paperclip
Computer.
> - This pull request changes that stock copy to Paperclip Sandbox.
> - Existing and custom environment names continue to come from the
stored row.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The managed environment label in agent settings and the errors shown
when the managed sandbox is unavailable.

**Subsystem affected**

The agent configuration UI, runner selection, and environment
reconciliation tests.

**Current behavior**

Fallback copy and current examples call the managed sandbox Paperclip
Computer. The actual environment name is already configurable.

**Proposed behavior**

Use Paperclip Sandbox for stock copy and current examples. Keep
configured names unchanged in selectors, including legacy names and
custom names.

**Reason and benefit**

The name identifies this environment as a managed sandbox and removes
ambiguity.

**Breaking changes**

The displayed fallback and error text change. Error codes, execution
behavior, and environment identity remain the same. No migration is
needed.

**Additional context**

No duplicate rename issue or pull request was found. This is a copy
update, with no new roadmap feature.

## What Changed

- Update the managed fallback label and two unavailable-sandbox error
messages.
- Update current test and Storybook fixtures without changing story IDs
or behavior.
- Test configured names, fallback copy, and the unavailable-sandbox
message.
- Add a PostgreSQL regression test for a rename that retains the
environment ID, instance default, agent binding, environment variables,
and lease.

## Verification

- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- Focused coverage: 956 cases across nine changed suites. The new
fixture initially tried to insert an existing singleton; its corrected
upsert passed with all 52 environment-service cases. The updated runner
suite also passed all 243 cases.
- The two UI suites passed all 189 cases, including configured
legacy/custom names and the missing-default fallback.
- `pnpm check:token-gates` passed.
- `node scripts/storybook-catalog.mjs --check` passed. The catalog is
structural evidence only.
- `node --test scripts/check-maps.test.mjs
scripts/storybook-catalog.test.mjs
scripts/storybook-feature-index.test.mjs` passed.
- Full `pnpm test:run` is in progress. No live provider or browser
journey is claimed by these tests.
- CI passed on the current head after one server-shard rerun. The first
attempt hit a 10 ms deadline assertion in the unchanged status-card
timing test; its isolated local run and the complete rerun shard passed.
The original failure and rerun are retained in the PR comment.
- Reviewed the diff for secrets, personal data, and run artifacts.

## Risks

Low risk: runtime edits change three display strings. Managed
reconciliation is unchanged. The regression test exercises the existing
rename against PostgreSQL. The configured name still controls normal
display, so this code change alone does not rename existing database
rows. A managed configuration update uses the existing reconciliation
path. Roll back the copy by reverting this commit.

## Model Used

OpenAI GPT-6.1 Sol (`gpt-6.1-sol`) through Codex in T3 Code, xhigh
reasoning, with repository tools and code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used, version, and capabilities
- [x] I have checked ROADMAP.md; this copy update does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs
- [x] I have described the issue in the PR with the enhancement template
fields
- [x] No internal or instance-local issue references
- [x] The branch name describes the change and contains no internal
ticket ID
- [x] Focused tests pass locally
- [x] Relevant tests were added or updated
- [x] Relevant copy and examples were updated; no API or schema
documentation changed
- [x] Risks are documented
- [x] All CI gates passed on the current head, including the server
11/12 rerun and aggregate verify
- [ ] Full local test suite completed (still running; six chat shards
passed)
- [x] Greptile is 5/5 on the current head with no unresolved findings
- [x] I will address all reviewer comments before requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-05 10:23:43 -08:00
DottaandClaude Opus 5.5 cb0b7525e0 feat(task-chat): warn at the top of a run when an MCP server failed to start (#19289)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Each agent run shows its activity in the task chat. The Paperclip
runner turns harness events into that activity.
> - Codex reports `mcpServer/startupStatus/updated` when an MCP server
fails to start. The runner dropped this event at the notification
allowlist and in the Rust normalizer.
> - When an MCP server fails, the agent runs without its tools. The
agent then says "I could not reach the server", and the user cannot see
why.
> - This pull request maps a failed MCP start to a user-facing provider
notice and shows it as one warning line at the top of the run.
> - The benefit is that the cause of a "cannot reach my tools" answer is
visible next to that answer.

## Linked Issues or Issue Description

Refs #19252 (the plan card, which set the pattern for promoting a
dropped harness signal to a dedicated task-chat surface).

**Problem description**
Codex sends `mcpServer/startupStatus/updated` (`name`, `status`,
`error`, `failureReason`) during `thread/start`. The TypeScript
allowlist (`codex-thread-normalization.ts`) and the Rust normalizer
(`runner-core/src/provider_events.rs`) both dropped it. A run with a
broken MCP server showed no sign of the failure. The agent only said
that it could not reach the tools.

**Proposed solution**
Map `status: "failed"` to `provider.notice.recorded` with `category:
"mcp_server_startup_failed"`, `severity: "warning"`, `scope: "session"`
and `userActionable: true`. Show these notices as one run-health line
under the runner header: "github MCP server failed to start: No such
file or directory (os error 2)".

**Alternatives considered**
An inline notice row in the timeline. It scrolls away and sits among
tool rows. A run-level line next to the header is easier to connect to
the agent's answer.

## What Changed

- Runner (Rust, production Codex path): `normalize_codex_notification`
maps a failed `mcpServer/startupStatus/updated` to a
`provider.notice.recorded` notice. `starting`, `ready` and `cancelled`
stay out of the transcript. The new helper removes the Codex wrapper
text ("MCP client for \`x\` failed to start: MCP startup failed:") so
that the line shows the real reason. `reauthenticationRequired` becomes
"needs re-authentication".
- Runner (TypeScript): the same mapping in
`canonicalProviderEventsFromCodex`, and the method is added to
`isSupportedCodexNotificationMethod`.
- Server: the `view=conversation` run-event query keeps
`mcp_server_startup_failed` notices. A settled run can then still show
the warning. Other notices stay out of that view.
- UI: new `TaskChatRunHealthBanner`. `partitionRunHealthItems` takes MCP
failures out of the run's items, with one line per server where the
latest report wins. `TaskChatRunnerTurn` (live runs) and `TaskChatTurn`
(settled, historical and merged turns) show it under the header.
`coalesceSettledTurns` merges the failures when it merges turns.
- Storybook: `Tasks and conversations/…/Task chat/Run health` shows one
failure, several failures, a finished run and a healthy run, all through
the production runner turn.

## Verification

- `pnpm --filter @paperclipai/paperclip-runner exec vitest run
src/provider-events.test.ts src/drivers/codex src/protocol`: 508 tests
pass.
- `cargo test --release -p paperclip-runner-core --lib --
provider_events`: 20 tests pass.
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/task-chat src/components/transcript
src/components/TaskChatThread.test.tsx`: 1340 tests pass, including the
new `TaskChatRunHealthBanner.test.tsx` (persisted event to rendered
banner, deduplication, settled historical turn, merged turns).
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/task-cold-open.test.ts`: the conversation view keeps the
MCP notice and still excludes other notices.
- `pnpm check:token-gates` is clean. `node
scripts/typecheck-low-memory.mjs` passes.
- Live test on a local instance with Codex 0.160: a Codex agent with a
preconfigured Codex home whose `config.toml` has `[mcp_servers.github]
command = "github-mcp-server-not-installed"`. Codex reported the failure
during `thread/start`. The persisted event had the summary "github MCP
server failed to start: No such file or directory (os error 2)". The
banner showed while the run was live. It also showed on the settled turn
after "More run output", above the agent's answer that it could not
reach the GitHub MCP server.

## Risks

- Low risk. The change adds one notice category. It does not change how
existing notices map or render.
- A settled run whose transcript is deferred on a cold open (a
successful run with a posted agent comment) shows the warning only after
"More run output". This is the same as all other run activity under the
current compact history.
- Claude does not report MCP startup failures over ACP.
`claude-agent-acp` reads MCP status only for OAuth. This change is
therefore Codex-only.
- Provisional task titles from provider session names are not part of
this change. Codex app-server does not generate thread names (only its
TUI does). Claude generates its title after the turn ends, and ACPX
drops the title text.

> 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

- Claude Opus 5.5 (`claude-opus-5-5`), through Claude Code with tool use
(shell, file edits, browser automation for Storybook and the live test).

## 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
- [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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:10:51 +00:00
DottaandPaperclip b400f76647 feat(ui): submit task composers with Enter by default (#19272)
Use Enter to submit task replies and new tasks, with Shift+Enter for a new line. Add an experimental preference to require Command/Ctrl+Enter.

Preserve autocomplete selection and IME confirmation, and retain the modifier shortcut until saved settings load.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-11-05 11:40:18 -06:00