Commit Graph

4193 Commits

Author SHA1 Message Date
Devin Foley 0044fa8904
Let tenants edit env vars on managed sandbox environments; add managed-sandbox-only mode (#11200)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Environments give each agent run an execution target: the local
host, SSH, or a sandbox provider
> - A managed deployment can provision one platform-managed sandbox
environment through the `PAPERCLIP_MANAGED_CONFIG` `environments`
section
> - That row is fully locked today. A tenant cannot add environment
variables for their agents. There is also no way to hide local execution
— run selection falls back to the local row
> - A platform that manages the sandbox for its tenants needs both: the
tenant adds env vars (and nothing else), and local execution is neither
visible nor reachable
> - This pull request opens exactly one tenant edit (env vars) on the
managed sandbox row, and adds an `enableManagedSandboxOnly` mode that
hides local and makes run selection fail closed
> - The benefit is a complete managed-sandbox experience with no change
for self-hosted instances

## Linked Issues or Issue Description

**Subsystem affected**

Environments (managed sandbox provisioning, environment routes, run
environment selection) and the environments UI.

**Problem or motivation**

Platform-provisioned sandbox environments
(`metadata.managedByPaperclip`) reject every write on cloud-managed
instances. Agents often need environment variables inside their sandbox.
The tenant has no way to set them on the managed row. Separately, an
operator cannot remove local execution: the environment list always
shows the local row, and run selection falls back to it when no default
is set.

**Proposed solution**

Allow an envVars-only PATCH on the managed sandbox row, and echo those
env vars back for editing. Add a managed-tier feature
(`enableManagedSandboxOnly`) that hides the local environment from all
read surfaces and redirects local-landing run selection to the managed
sandbox environment, failing closed when it is unavailable.

**Alternatives considered**

UI-only hiding of the local row. This was rejected: it does not stop a
run from resolving to local, so it is presentation without enforcement.
Full unlock of the managed row was also rejected: name, driver, and
config stay platform-owned so boot reconciliation cannot fight tenant
edits.

## What Changed

- `server/src/routes/environments.ts`: the platform-provisioned write
floor admits an envVars-only PATCH on the generalized managed sandbox
row (sandbox driver, `managedByPaperclip`, not legacy kubernetes-marker
rows). Name, driver, config, status, metadata, and DELETE stay rejected.
The read floor stops blanking env vars on that row; credential-shaped
config keys stay redacted for every actor. Legacy kubernetes-marker rows
keep the full floor.
- Same file: under `enableManagedSandboxOnly`, the environments list and
the by-id read omit the local row for every actor, including instance
admins.
- `server/src/services/execution-workspace-policy.ts`:
`resolveExecutionWorkspaceEnvironmentId` gains the managed-sandbox-only
inputs. A selection that lands on the local environment is redirected to
the managed sandbox environment. With no active managed row it throws
`ManagedSandboxUnavailableError` — never local. Non-local selections
(ssh, user-created sandboxes) are untouched.
- `server/src/services/heartbeat.ts`: the run path reads the flag, looks
up the managed row (`findManagedSandboxEnvironment`, new read-only
finder in `environments.ts`), and passes both to the resolver. Mirrors
the forced-kubernetes precedent, which keeps precedence when both
regimes are on.
- `server/src/services/managed-environments.ts`: after a successful
reconcile, the instance default environment moves to the managed sandbox
row when the current default is unset, local, or dangling. A
tenant-chosen custom environment is never overridden.
- `packages/shared`: new `enableManagedSandboxOnly` key (schema default
false, catalog tier `managed`, cloudDefault false, selfHostedDefault
false) and the matching interface field.
- UI: managed rows show a "Managed by Paperclip" lock badge; editing one
opens a dedicated env-vars-only editor that sends the one PATCH shape
the server admits (the old full form failed with a 403 on save). New
`ui/src/lib/managed-sandbox-environment.ts` mirrors the local filter for
cached lists (applied in the project picker; the agent picker already
excluded local). The experimental settings page gains the toggle at its
alphabetical card position. `environmentsApi.update` now declares the
`envVars` field it already sent.
- Tests: environment route floor coverage (envVars-only accepted, mixed
bodies rejected, legacy rows still blanked and locked, local hidden and
404 under the flag, self-hosted unchanged), an embedded-postgres service
test pinning that boot reconciliation never touches tenant env vars,
resolver redirect/fail-closed cases, managed-environments
default-stamping cases, and UI lib/settings tests.

## Verification

- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/environment-routes.test.ts
src/__tests__/environment-service.test.ts
src/__tests__/execution-workspace-policy.test.ts
src/services/managed-environments.test.ts` — all pass.
- `pnpm --filter @paperclipai/shared exec vitest run` — 425 pass
(catalog/schema default parity is pinned by an existing test).
- `pnpm --filter @paperclipai/ui exec tsc --noEmit` and the affected UI
suites (CompanyEnvironments, InstanceExperimentalSettings incl.
card-order test, new lib test) — all pass.
- Full workspace `pnpm test`: 3,414 passed. 17 files report failures on
this machine; the identical 17 fail on a clean `origin/master` worktree
in the same environment (git-worktree/skills/embedded-postgres
environment dependencies and plugin-SDK zero-test collections). One
additional file (`issue-monitor-scheduler.test.ts`) failed one
timing-sensitive test in one of two full-suite runs and passes 7/7 in
isolation on this branch — a flake in a domain this diff does not touch.
The branch introduces no new failures.
- Self-hosted zero-delta: every new behavior is gated on the
cloud-managed instance check or the new flag, which defaults to false in
schema and catalog; pinned by the "does not floor platform-marked rows
on self-hosted instances" and flag-off tests.

## Risks

- Behavior is opt-in twice over: the write-floor exception applies only
to rows the managed-config provisioner stamps, and the hiding/forcing
applies only when `enableManagedSandboxOnly` is on (default false
everywhere). Self-hosted instances see no change.
- The env-vars echo is scoped to the generalized managed sandbox row;
legacy kubernetes-marker rows keep the blanket floor because
pre-generalization builds may have written platform values there.
- Fail-closed run selection means a managed instance with the flag on
and an archived managed row (provider plugin down) refuses runs with a
precise error instead of running locally. That is the intended posture.

## Model Used

Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use
via Claude Code CLI.

## 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
2026-08-11 11:40:57 -07:00
Dotta b847e8b6f6
perf(server): reduce issue detail request overhead (#10414)
## Thinking Path

> - Paperclip is the open source control plane people use to coordinate
AI-agent work
> - Opening an issue fans out into several authenticated issue-detail
reads, so repeated work on that path directly affects perceived latency
> - Those reads repeated issue and authorization lookups, returned full
private JSON even when unchanged, and performed non-critical bookkeeping
writes on the request path
> - Interaction reads also performed lifecycle writes even though `GET`
must be read-only
> - This pull request adds request-scoped reuse, private conditional
responses, read-only interaction access, and bounded write debouncing
without crossing actor, request, or company boundaries
> - The result is less database, serialization, logging, and
response-body work while preserving authorization and interaction
lifecycle invariants

## Linked Issues or Issue Description

This is the server-only latency phase. Related work is tracked
separately in #10415 (aggregate view), #10416 (warm navigation, merged
into the base), and #10463 (bundle split). This pull request
intentionally excludes those scopes.

**What happened?**

Opening an issue detail view caused avoidable server costs: repeated
issue and authorization reads within one request, full private JSON
responses when a representation was unchanged, writes during
interaction-list reads, production debug transport setup, and immediate
bookkeeping writes for cloud tenant activity and board-key usage.

**Expected behavior**

All successful JSON `GET /api/issues/:id/*` responses should support
strong private ETags and `304 Not Modified`. Repeated work may be reused
only within the current request. `GET /interactions` must not modify
stored interactions. Non-critical activity timestamps may be debounced
without weakening authentication or stale instance-admin cleanup.

**Steps to reproduce**

1. Start Paperclip in local development or self-hosted server mode.
2. Open one issue and request its detail subresources with the same
authenticated actor.
3. Repeat a successful JSON request with its `ETag` in `If-None-Match`.
4. Observe `304 Not Modified`, no interaction writes from `GET
/interactions`, and unchanged authorization boundaries.

**Deployment mode / installation**

- Local development or self-hosted server
- Built from source
- Core server behavior; not adapter-specific

## What Changed

- Added strong ETags and `Cache-Control: private, must-revalidate` to
successful JSON reads under `/api/issues/:id/*`, including
standards-compliant `If-None-Match` handling.
- Added request-scoped promise memoization for issue and authorization
lookups; no authorization result survives the request.
- Made `GET /interactions` read-only, moved supersession and
terminal-state handling to mutation paths, and prevented plugin callers
from accepting or rejecting interactions after an issue closes.
- Removed the production debug-file logger transport while preserving
development formatting.
- Debounced cloud-tenant activity and board-key `lastUsedAt`
persistence, while keeping stale instance-admin deletion unconditional
and authentication checks per request.
- Added focused tests for ETags, request isolation, authorization
lifecycle behavior, interaction invariants, logger configuration, and
retry-safe debounce behavior.

## Verification

- `pnpm exec vitest run server/src/__tests__/private-json-etag.test.ts
server/src/__tests__/issue-thread-interaction-routes.test.ts` — 2 files,
23 tests passed.
- Focused Vitest run covering request memoization, authorization,
interactions, plugin orchestration, logger, cloud tenant, board auth,
and issue services — 9 files, 264 tests passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `git diff --check origin/master...HEAD` — passed.
- Scope guardrails: 21 changed files under `server/src`; no lockfile,
workflow, migration, UI, aggregate-view, or bundle-split changes.

## Risks

- Strong ETags hash each successful serialized JSON response. This adds
a small CPU cost but avoids transferring unchanged bodies.
- Debounced bookkeeping timestamps can lag by the bounded debounce
interval. They are non-critical usage metadata; authentication still
runs per request, and stale instance-admin deletion remains
unconditional.
- Legacy pending interactions on terminal issues are projected as
expired by reads and are finalized only by mutation paths. The stored
record remains unchanged on `GET` by design.
- No database schema or migration changes are included.

> This is a focused performance correction and does not duplicate a
planned core feature in `ROADMAP.md`.

## Model Used

OpenAI Codex using `gpt-5.3-codex` for the initial implementation and
`gpt-5.6-sol` for isolation, verification, and PR preparation, with
reasoning, repository tool use, code execution, and GitHub CLI access.
The runtimes did not expose authoritative context-window sizes.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Dev Agent <dev@paperclip.ing>
2026-08-11 14:39:23 -04:00
Dotta 145d86911b
Remove decision training UI (#11225)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Decisions desk shows work that needs an operator response.
> - It also exposed decision-training actions and a separate training
library.
> - Paperclip does not plan to use these training surfaces now.
> - Keeping inactive controls makes the Decisions workflow harder to
scan.
> - This pull request removes the training UI and keeps the backend
snapshot contract unchanged.
> - The benefit is a smaller and clearer Decisions workflow without a
data migration.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The Decisions desk currently exposes training controls, training state,
and a separate training library route.

**Subsystem affected**

`ui/` — React and Vite board UI.

**Current behavior**

Operators can open a training library from the Decisions toolbar. They
can also mark a decision for training from rows and inspect the result
in a drawer.

**Proposed behavior**

Remove the training controls, badges, drawer, library pages, and routes
from the Decisions UI. Keep the server APIs and stored training examples
unchanged.

**Reason and benefit**

The product does not plan to use decision training now. Removing the
unused surfaces reduces Decisions UI noise and avoids presenting a
workflow that operators should not use.

**Breaking changes**

The `/decisions/training` UI routes are no longer registered. Existing
server endpoints and stored decision-training data remain compatible.

## What Changed

- Removed decision-training controls and state from Decisions toolbars,
rows, queue pages, and shelves.
- Removed the training drawer, library, inspector, API client, helpers,
query keys, and routes.
- Added route and row regressions that assert training UI does not
return.
- Updated the Decisions Storybook description to match the available
controls.

## Verification

- `pnpm exec vitest run ui/src/App.test.tsx
ui/src/components/AttentionQueueRow.test.tsx` — 32 tests passed.
- `pnpm check:token-gates` — all gates clean.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — server and UI partitions passed. The CLI partition
had one environment-only failure because this agent runtime injects
static AWS credentials. The exact CLI file passed all 8 tests when those
credential variables were unset.
- Searched `ui/src` and `ui/storybook` for the removed training routes,
drawer, library, badges, and actions. Only negative regression
assertions remain.

## Risks

- Low implementation risk. This change deletes UI-only entry points and
does not change the database or server APIs.
- Saved training-page URLs no longer render a board route. This is the
intended behavior.

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

## Model Used

- OpenAI Codex, model `gpt-5.6-sol`, with `xhigh` reasoning. The runtime
did not expose the context-window size. The agent used repository tools,
shell execution, and automated tests.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-11 14:23:57 -04:00
Dotta 45dfb183b6
fix(ui): add undo action to inbox archive toast (#11220)
## Thinking Path

> - Paperclip helps operators supervise AI-agent work.
> - The Mine inbox keeps tasks that need an operator's attention in one
place.
> - Operators can archive a task from its detail page after they finish
triage.
> - That action is easy to select accidentally and did not offer
immediate recovery.
> - This pull request adds Undo to the archive success toast and keeps
inbox caches consistent.
> - The benefit is fast recovery without searching for or reopening the
task.

## Linked Issues or Issue Description

**What happened?**

Archiving a task from the Mine inbox removed it and showed a success
toast with no recovery action.

**Expected behavior**

The success toast should offer Undo. Selecting Undo should restore the
task through the existing unarchive API while preserving a consistent
inbox view.

**Steps to reproduce**

1. Open a task from the Mine inbox.
2. Select the archive action.
3. Observe that the task leaves the inbox and the success toast has no
Undo action.

**Paperclip version or commit**

Reproduced on `master` before this change.

**Deployment mode**

Local dev, built from source.

Related prior work: #9931 and #10668.

## What Changed

- Add an Undo action to the successful inbox archive toast.
- Optimistically restore the task in captured inbox query caches before
the unarchive request completes.
- Cancel in-flight inbox fetches and clear the local archive guard so
stale responses cannot hide the restored task.
- Reapply the archive guard and remove the cached task if the unarchive
request fails.
- Add regression tests for successful Undo, the in-flight cache race,
and failed Undo rollback behavior.

## Verification

- `pnpm exec vitest run ui/src/pages/IssueDetail.test.tsx
ui/src/lib/inboxArchiveCache.test.ts` — 51 tests passed on the final
head.
- `pnpm check:token-gates` — passed.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — the general server and UI groups passed. One CLI
doctor assertion detected injected host AWS credentials and passed all 8
tests with those unrelated variables unset. A task-watchdog scheduler
test also passed all 18 tests in isolation after one full-suite timing
failure.

## Risks

Low risk. The change uses the existing unarchive endpoint and inbox
cache helpers. Undo failure returns the task to its archived state,
shows an error toast, and invalidates the inbox queries for server
reconciliation.

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

## Model Used

OpenAI Codex, model ID GPT-5. The service manages the context window.
Reasoning, tool use, and code execution were enabled.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-11 14:13:06 -04:00
Dotta 7734f4b32d
fix(ui): keep slash autocomplete scrollable in dialogs (#11222)
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->

## Thinking Path

> - Paperclip helps operators manage AI-agent companies.
> - Operators create tasks and comments through shared rich-text
editors.
> - These editors show slash-command and mention matches in a floating
menu.
> - Modal dialogs treat that body-level menu as outside content and
cancel its wheel and touch movement.
> - This pull request keeps scroll events inside the floating menu and
preserves native scrolling.
> - The benefit is that operators can reach every match with a mouse
wheel, a trackpad, or a touch screen.

## Linked Issues or Issue Description

No public GitHub issue exists for this bug.

**What happened?**

Slash-command and mention menus could contain more matches than their
visible height. When an editor was inside a modal dialog, the modal
scroll lock canceled wheel and touch movement on the body-level menu
portal. Operators could not scroll to later matches.

**Expected behavior**

The autocomplete menu must scroll with a mouse wheel, a two-finger
trackpad gesture, and a vertical touch gesture. Keyboard selection and
normal editor behavior must stay unchanged.

**Steps to reproduce**

1. Open a task or comment editor inside a modal dialog.
2. Enter a slash command or mention query that has more matches than the
menu can show.
3. Try to scroll the menu with a wheel, trackpad, or touch gesture.

**Paperclip version or commit**

`7ea2068ef8` on `master`.

**Deployment mode**

Local development UI built from source.

## What Changed

- Keep wheel and touch movement inside the shared autocomplete menu
portal.
- Add vertical overscroll containment while preserving native momentum
scrolling.
- Add a regression test that mounts the real dialog and verifies that
wheel and touch movement stay uncanceled.

## Verification

- `pnpm --dir ui exec vitest run src/components/MarkdownEditor.test.tsx`
- `pnpm check:token-gates`
- `pnpm -r typecheck`
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm test:run`
- `pnpm build`

The two AWS variables are omitted from the full test command because
this agent runtime injects static AWS credentials. One unrelated CLI
doctor test correctly warns when those credentials are present. The CI
environment does not inject them.

## Risks

- Low risk. Event propagation stops only on the open autocomplete menu
portal.
- Ancestor listeners no longer receive wheel or touch movement from that
menu. Native menu scrolling and option-level touch handling still
receive the events.

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

## Model Used

OpenAI Codex with model ID `gpt-5`. The deployment suffix and
context-window size are not exposed to the agent. The model used agentic
reasoning, repository tools, GitHub tools, and local 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-11 13:59:57 -04:00
Dotta 3e1ea39ff3
fix(inbox): honor saved policy for explicit targets (#11221)
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->

## Thinking Path

> - Paperclip is the open source control plane people use to manage
AI-agent companies and their work
> - Each user can let agents tidy that user's Mine inbox
> - The profile control saves either an open policy or an agent
allowlist
> - Explicit inbox archive requests checked only the separate
`inbox:manage` grant
> - This made the saved profile control ineffective for explicit user
targets
> - This pull request makes authorization honor the target user's saved
policy
> - The benefit is that the UI control and the API now enforce the same
user choice

## Linked Issues or Issue Description

**What happened?**

An agent received `403 inbox_cross_user_grant_required` when it archived
an issue with an explicit `userId`. The denial occurred even when that
user had enabled inbox management for the agent in Profile Settings. The
authorization service checked only `principal_permission_grants` for
explicit targets and ignored the saved user inbox policy.

**Expected behavior**

An explicit target is allowed when the target user saved an `open`
policy or an allowlist that contains the agent. An unsaved default-open
policy must remain limited to the responsible-user path. A scoped
`inbox:manage` grant must remain an administrative override.

**Steps to reproduce**

1. Save an inbox-agent allowlist for a user.
2. Include the acting agent in that allowlist.
3. Call `POST /api/issues/{issueId}/inbox-archive` with that user's
explicit `userId`.
4. Observe the incorrect `403 inbox_cross_user_grant_required` response
on the previous implementation.

**Paperclip version or commit**

Reproduced on `7ea2068ef8`.

**Deployment mode**

Self-hosted server.

**Installation method**

Built from source with pnpm.

**Agent adapter(s) involved**

Not adapter-specific. This is a core authorization bug.

**Database mode**

External Postgres in production. The regression tests use embedded
PostgreSQL.

**Access context**

Agent bearer authentication.

Related foundations: #9658 and #9724.

## What Changed

- Read the target user's saved inbox-agent policy before the
explicit-target decision.
- Allow saved `open` policies and matching allowlists for explicit
targets.
- Keep unsaved implicit-open policies responsible-user-only.
- Keep scoped `inbox:manage` grants as administrative overrides.
- Add service and route regressions for allow, deny, archive, unarchive,
and audit metadata.
- Update the implementation contract and agent-facing inbox API
guidance.

## Verification

- `pnpm exec vitest run
server/src/__tests__/authorization-service.test.ts
server/src/__tests__/inbox-archive-routes.test.ts` — 66 tests passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `git diff --check origin/master...HEAD` — passed.

## Risks

- Low risk. The change is limited to explicit inbox targets with a saved
policy.
- A missing policy row still denies explicit cross-user access.
- A non-matching allowlist and a disabled policy still deny access
unless a scoped administrative grant applies.

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

## Model Used

- OpenAI Codex based on GPT-5. The runtime did not expose the exact
model build or context-window size. The agent used reasoning, repository
tools, code execution, and focused 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-08-11 13:23:03 -04:00
Devin Foley 71e9d6bb0b
feat(release): candidate-branch beta builds and the release checklist (#11209)
> Follow-up to #11208 (merged): rebased onto master and ready for
review.

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The release channels promote artifacts along canary → nightly → beta
→ stable, with the happy path being promotion of an existing build
> - When one or two targeted fixes are needed before a beta or stable,
the only options today are waiting for the next nightly or absorbing a
whole day of unrelated master changes
> - The channel model was designed with an escape hatch for exactly
this: short-lived candidate branches carrying only cherry-picked fixes
> - This pull request implements candidate-branch beta builds with full
verification, documents the stable fix path through the
soak-justification gate, and adds the release captain's checklist
> - The benefit is that a surgical fix can ship forward without either
delay or blast radius, with its provenance recorded

## Linked Issues or Issue Description

Refs #11008 — completes the fix-path half of the channel model
introduced there.

**Subsystem affected**

Release automation: `scripts/release.sh`,
`.github/workflows/release.yml`, `doc/RELEASING.md`, new
`doc/RELEASE-CHECKLIST.md`, tests.

**Problem or motivation**

Beta promotion only accepts commits that already shipped as a nightly,
and stable promotion expects a soaked beta. There is no supported way to
ship one or two cherry-picked fixes between lanes: an urgent fix must
wait for the nightly cycle or pull in every unrelated master change from
the day. The original channel design called for candidate branches to
cover this, and they were deferred from the initial implementation.

**Proposed solution**

Candidate-branch beta builds: cut `candidate/beta-<target>` from a
nightly's source commit, cherry-pick the fixes, and dispatch `channel:
beta` with the new `candidate_branch` input. Selection enforces the
naming convention, rejects heads that already shipped as a beta or
predate the candidate tooling, and records the cherry-picked commits in
the job summary. Because candidate heads never went through a canary or
nightly, publication is gated on a full `release-verify` run (promoted
nightlies keep skipping re-verification). The stable fix path
(`candidate/release-<target>` as `source_ref`) works through the
existing soak gate: the justification requirement is the deliberate,
recorded trade-off for shipping unsoaked bits, and is now documented as
such.

## What Changed

- `scripts/release.sh`: `--from-candidate` flag (beta only) waives the
shipped-a-nightly requirement while keeping the duplicate-beta guard
- `.github/workflows/release.yml`: `candidate_branch` dispatch input;
candidate mode in `select_beta` (naming validation, duplicate and
tooling-era rejection, cherry-pick recording); new
`verify_beta_candidate` job gating candidate publishes on full
verification
- `doc/RELEASING.md`: beta fix-path and stable fix-path sections
- `doc/RELEASE-CHECKLIST.md` (new): the release captain's checklist for
all four lanes as built
- Tests: dry-run fixture coverage for `--from-candidate` (waives the
nightly guard, keeps the duplicate guard, rejected outside beta) and
wiring tests for candidate validation plus the verification gate

## Verification

- `node --test` on the four affected suites: 42 pass in total (17 + 25
across the two runs), including the 5 new tests
- `bash -n` on `release.sh`; YAML parse of the workflow
- After merge: exercise the path end to end the first time a real
cherry-picked beta is needed — dispatch with a `candidate/beta-*` branch
and confirm the summary records the picks and verification runs

## Risks

- Candidate builds bypass the smoke-tested-nightly provenance by design;
the compensating controls are full verification before publish, the
post-publish beta smoke, the human `npm-beta` gate, and recorded
cherry-picks
- The stable fix path rides the existing justification mechanism rather
than adding a second bypass — one recorded escape hatch, not two

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) in Claude Code, with
extended thinking and full tool use. All changes model-authored under
human direction.

## 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 (pending — will confirm before
merge)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending — will confirm before merge)
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-11 10:05:13 -07:00
Dotta 7ea2068ef8
fix(files): only highlight accessible workspace file links (#11090)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task comments can contain references to files in project and
execution workspaces.
> - Paperclip detected path-shaped inline code and showed it as an
actionable file chip.
> - The UI did not first confirm that the current board session could
open the file.
> - Missing, denied, ambiguous, remote, and unsupported files therefore
looked actionable and failed after a click.
> - This pull request adds an issue-scoped availability check and
promotes only confirmed files to chips.
> - The benefit is that the task thread shows a file action only when
that action can succeed.

## Linked Issues or Issue Description

**What happened?**

Task comments promoted path-shaped inline code to file chips before
Paperclip checked the file. A chip could point to a missing, denied,
ambiguous, remote, or non-previewable file. The action then failed after
the user selected it.

**Expected behavior**

Paperclip must show a file chip only after the server confirms that the
current board session can open the exact file reference. All other
path-shaped text must stay ordinary inline code.

**Steps to reproduce**

1. Add a task comment that contains inline code with a missing or
inaccessible workspace path.
2. Open the task thread as a board user.
3. Observe that the path looks like an actionable file chip.
4. Select the chip and observe that the file cannot open.

**Paperclip version or commit**

`19be4cf927` and earlier.

**Deployment mode**

Local dev and self-hosted server.

**Access context**

Board user.

## What Changed

- Added shared request, response, and validation contracts for batched
workspace-file availability checks.
- Added an issue-scoped server endpoint that resolves file references
with company, issue, workspace, and preview-access checks.
- Added bounded batch concurrency and tests for missing, denied,
ambiguous, remote, unsupported, and available files.
- Added an issue-scoped UI availability registry that deduplicates,
batches, caches, and invalidates file checks.
- Changed task-comment markdown rendering so only confirmed files get
chip styling and file-viewer behavior.
- Bound each chip to the exact workspace target that passed the
availability check.

## Verification

- `pnpm exec vitest run
packages/shared/src/workspace-file-resource.test.ts
server/src/__tests__/file-resources.test.ts
ui/src/components/MarkdownBody.test.tsx
ui/src/components/WorkspaceFileMarkdownBody.availability.test.tsx
ui/src/lib/remark-workspace-file-refs.test.ts
ui/src/lib/workspace-file-availability.test.ts` — 93 passed, 35 skipped.
- `pnpm check:token-gates` — clean.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — all server and UI groups passed. One unchanged CLI
test saw the run-injected static AWS credentials and expected only its
local `AWS_PROFILE`. The same test passed, 8 of 8, after removing only
`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` from its process
environment.

## Risks

- File chips now appear after an asynchronous availability check, so
path-shaped text can briefly render as inline code.
- Availability results use the existing 30-second file-resource cache
window. File-resource invalidation forces a new check.
- The endpoint limits each request to 100 references and the client
chunks larger sets.

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

## Model Used

- OpenAI Codex with GPT-5. The service did not expose a more specific
model ID or context-window size. The agent used high-reasoning mode,
repository tools, command 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
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-11 12:11:45 -04:00
scotttong 815e49bb7c
feat: make chat-style tasks the default experience (#11101) 2026-08-11 09:06:21 -07:00
Devin Foley 2da6a248c3
fix(release): surface recovery commands when a lane tag push is rejected (#11208)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The release subsystem's promotion lanes publish to npm, then push a
lane tag and dispatch the Docker image build at that tag
> - The first nightly of the beta-tooling merge published to npm and
then died at the tag push: GITHUB_TOKEN may not create refs pointing at
workflow-modifying commits from dispatch or scheduled runs
> - The failure was a bare `remote rejected` with no guidance, leaving
the release half-finished (npm live, no tag, no images) until an
operator reverse-engineered the recovery
> - This pull request makes every lane's tag push degrade into exact
recovery instructions in the job summary
> - The benefit is that a rare platform-permission rejection becomes a
two-minute runbook operation instead of a forensic exercise

## Linked Issues or Issue Description

Refs #11008 — the incident occurred promoting that change's own merge
commit, the first workflow-modifying commit to flow through the lanes it
introduced.

**Subsystem affected**

Release automation: `.github/workflows/release.yml`, `doc/RELEASING.md`,
workflow wiring tests.

**Problem or motivation**

Run 31445344811 published `2026.811.0-nightly.0` to npm, then failed
pushing `nightly/v2026.811.0-nightly.0`: `refusing to allow a GitHub App
to create or update workflow .github/workflows/release.yml without
workflows permission`. The tagged commit modifies workflow files, and
GITHUB_TOKEN may not create refs pointing at such commits from dispatch
or scheduled runs (push-event runs are exempt, which is why the canary
tag on the same commit succeeded). The job failed with no explanation
and the Docker dispatch never ran.

**Proposed solution**

Wrap the nightly, beta, and stable tag pushes: on rejection, write the
exact recovery commands into the job summary — create and push the tag
with maintainer credentials, dispatch `docker.yml` at the tag, and for
stable also run `create-github-release.sh` — then fail the job. Document
the cause and recovery in the failure playbooks and pin the three
recovery blocks with a wiring test.

## What Changed

- `.github/workflows/release.yml`: recovery-summary wrappers on the
nightly, beta, and stable tag-push steps
- `doc/RELEASING.md`: failure-playbook entry for the
workflows-permission rejection
- `scripts/__tests__/release-verify-workflow.test.mjs`: wiring test
asserting all three lanes carry the recovery summary

## Verification

- Wiring tests: 6 pass; YAML parse of the workflow
- The recovery commands are exactly the ones used to resolve the real
incident (tag push + `docker.yml` dispatch for
`nightly/v2026.811.0-nightly.0`)

## Risks

- Low. The happy path is unchanged (a successful push skips the
wrapper); the failure path trades a bare error for actionable output and
still fails the job, since the release state is genuinely incomplete

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) in Claude Code, with
extended thinking and full tool use. All changes model-authored under
human direction.

## 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 (pending — will confirm before
merge)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending — will confirm before merge)
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-11 08:48:13 -07:00
Devin Foley 1ea2f0e2d6
feat(cli): add 'paperclipai channels' to show release lanes and the current one (#11210)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The release channels (canary → nightly → beta → stable) select by
install target, and users discover them today only through
maintainer-oriented docs
> - A user who wants to know "which lane am I on, and what else is
there" has no self-serve answer
> - The channel rollout planned a read-only CLI command for exactly this
> - This pull request adds `paperclipai channels`: every lane with the
version its dist-tag resolves to, the install command for it, and which
lane the running install follows
> - The benefit is self-serve lane discovery without reading release
documentation

## Linked Issues or Issue Description

Refs #11008 — the user-facing discovery surface for the channel model
completed there.

**Subsystem affected**

CLI: `cli/src/commands/channels.ts` (new), `cli/src/index.ts`,
`doc/CHANNELS.md`, tests.

**Problem or motivation**

Channel selection is install-based (`@latest` / `@beta` / `@nightly` /
`@canary`), but nothing in the product tells a user which channel their
install follows or what the other lanes currently resolve to. The
information lives in `doc/CHANNELS.md` and the npm registry, neither of
which a running install surfaces.

**Proposed solution**

A read-only `paperclipai channels` command: prints each channel with the
version its dist-tag currently resolves to (per-lane registry lookups
that degrade to `unavailable` individually), the install command for
each, and the running install's lane parsed from its version suffix —
source checkouts carry the repository's placeholder version and are
reported as unmapped rather than guessed. `--json` emits the same data
for scripting.

## What Changed

- `cli/src/commands/channels.ts` (new): channel table, lane parsing,
registry resolution, human and `--json` output
- `cli/src/index.ts`: registers `channels`
- `doc/CHANNELS.md`: "Seeing where you are" section
- `cli/src/__tests__/channels.test.ts` (new): lane parsing including
unknown versions, full resolution against a fake runner, per-lane
degradation, table/dist-tag sync

## Verification

- `vitest run cli/src/__tests__/channels.test.ts`: 5 pass
- `pnpm typecheck` in `cli/`
- Live run against the real registry shows all four lanes with their
current versions (`2026.722.0` / `2026.811.0-beta.0` /
`2026.811.0-nightly.0` / canary) and correctly reports a source checkout
as unmapped

## Risks

- Low. Read-only command reusing the existing `resolvePublishedVersion`
registry helper; no state, no auth, no publish surface

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) in Claude Code, with
extended thinking and full tool use. All changes model-authored under
human direction.

## 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 (pending — will confirm before
merge)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending — will confirm before merge)
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-11 08:47:32 -07:00
Dotta c4abecb2c4
fix(skills): refresh project folders in place (#11066)
## Thinking Path

> - Paperclip helps operators manage agent skills across a company.
> - The installed skills view groups project-backed skills into folders.
> - The view showed two folder creation controls and only offered a
global project scan.
> - Operators need one clear folder action and a refresh action for the
selected project.
> - This pull request keeps folder creation in the folder rail and adds
a scoped project refresh.
> - The benefit is a calmer skills view and faster, more precise project
skill updates.

## Linked Issues or Issue Description

No public GitHub issue exists for this focused UI bug.

**What happened?**

The installed skills view repeated the folder creation action in the
toolbar. A selected project folder also had no way to refresh only its
own project skills.

**Expected behavior**

The folder rail must own folder creation. A selected project-backed
folder must offer a refresh action that scans only that project and
refreshes the skill and folder queries.

**Steps to reproduce**

1. Open the installed skills view for a company with project-backed
skill folders.
2. Select a project folder.
3. Observe the duplicate folder action and the absence of a
project-scoped refresh action.

**Paperclip version or commit**

Reproduced before this two-commit fix on `master`.

**Deployment mode**

Local development with `pnpm dev`.

## What Changed

- Removed the duplicate toolbar folder creation button when the folder
rail exists.
- Preserved the toolbar folder action when no folder rail exists.
- Added a refresh action beside the breadcrumb for a selected
project-backed folder.
- Passed the selected project ID to the project scan API.
- Refreshed both the installed skill list and skill folder data after
scans.
- Added component tests for compact folder creation, the empty-folder
fallback, and scoped project refresh.

## Verification

- `pnpm exec vitest run ui/src/pages/CompanySkills.test.tsx` — 20 tests
passed.
- `pnpm check:token-gates` — passed with all three gates clean.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — the server and UI stages passed 7,475 tests. The CLI
stage then found one environment-sensitive AWS doctor assertion because
this agent runtime injects static AWS credentials.
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm exec vitest
run cli/src/__tests__/secrets.test.ts --project paperclipai` — all 8
tests passed.
- GitHub CI — all latest-head checks passed.

## Risks

- Low risk. The scoped refresh depends on the existing `project:<id>`
folder system key.
- The global scan path is unchanged.
- There are no schema, migration, API contract, dependency, workflow, or
documentation changes.

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

## Model Used

OpenAI Codex, GPT-5 family. The runtime did not expose a more specific
model ID or context-window size. The agent used high-reasoning mode,
repository tools, GitHub 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 (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-11 09:57:19 -04:00
Dotta b58ce27a02
fix: isolate execution workspace summaries (#10790)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip gives operators a summary for each workspace.
> - An execution workspace detail page used the parent project-workspace
summary slot.
> - Two execution workspaces under one project workspace could therefore
show the same summary.
> - This pull request gives each execution workspace its own summary
scope.
> - It also limits the summary snapshot and generated issue to that
execution workspace.
> - The benefit is that a new or parallel execution workspace cannot
inherit unrelated status.

## Linked Issues or Issue Description

**What happened?**

An execution workspace detail page read and refreshed the summary slot
for its parent project workspace. Parallel execution workspaces could
show the same status and include issues from each other.

**Expected behavior**

Each execution workspace must have one isolated summary slot. Its
generated snapshot must include only issues assigned to that execution
workspace.

**Steps to reproduce**

1. Create two execution workspaces under one project workspace.
2. Add different issues to each execution workspace.
3. Generate the summary in the first execution workspace.
4. Open the second execution workspace.
5. Observe that the old implementation could reuse the first summary.

**Paperclip version or commit**

The problem exists on `master` before this pull request.

**Deployment mode**

The issue affects both local trusted and authenticated deployments.

## What Changed

- Added `execution_workspace` to the shared summary-slot scope contract.
- Validated execution-workspace ownership and stored generated summary
issues on the correct execution workspace.
- Limited execution-workspace snapshots to issues with the matching
execution workspace ID.
- Updated the execution workspace page to use its own summary slot.
- Updated Summarizer instructions, routine options, catalog metadata,
documentation, and regression tests.

## Verification

- `NODE_ENV=test pnpm exec vitest run
packages/shared/src/summary-slot.test.ts
server/src/__tests__/summary-slots.test.ts
ui/src/pages/ExecutionWorkspaceDetail.test.tsx` — 30 focused tests
passed; the embedded-Postgres server tests were run outside the
process-restricted sandbox.
- `pnpm check:token-gates` — passed.
- `pnpm --filter @paperclipai/skills-catalog validate` — passed with 17
catalog skills.
- [Latest-head GitHub
Actions](https://github.com/paperclipai/paperclip/actions/runs/31491475405)
— all 22 jobs passed on `beea14cbaf`, including typecheck, build,
server/workspace tests, serialized suites, e2e, canary, and aggregate
verification. One unrelated adapter cleanup test initially hit an
`ENOTEMPTY` temp-directory race; its single permitted rerun passed.
- Greptile — 5/5 confidence on `beea14cbaf`, 12 files reviewed, zero
comments added, and zero unresolved threads.

## Risks

- Low risk. The new scope is additive.
- Existing project and project-workspace summary slots keep their
current keys and behavior.
- A summary generated for an execution workspace now excludes sibling
workspace issues by design.

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

## Model Used

- OpenAI Codex with GPT-5. The deployment does not expose a more
specific model ID or context-window value. It used agentic reasoning,
repository tools, code execution, and GitHub tooling.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-11 08:56:32 -04:00
Dotta 9cdaa5416e
fix(ui): remember folded inbox subtasks (#11069)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The inbox helps operators scan parent tasks and their sub-tasks
> - Operators can fold a parent task to hide its sub-tasks
> - The inbox previously forgot that fold state after a page refresh
> - This pull request stores the fold state for each company and
restores it when the inbox loads
> - The benefit is that the inbox keeps the operator's chosen task
layout across page refreshes

## Linked Issues or Issue Description

**What happened?**

The inbox reset every folded parent task after a page refresh. This made
all nested sub-tasks visible again.

**Expected behavior**

The inbox must keep each folded or unfolded parent state after a page
refresh. The state must remain separate for each company.

**Steps to reproduce**

1. Open the inbox with parent and child tasks.
2. Fold one parent task.
3. Refresh the page.
4. Observe that the child task is visible again without this fix.

**Paperclip version or commit**

Current `master` before this pull request.

**Deployment mode**

Local dev and built-from-source deployments.

## What Changed

- Added company-scoped local storage helpers for collapsed inbox parent
IDs.
- Restored the stored parent fold state when the inbox mounts or the
selected company changes.
- Saved both direct toggle changes and explicit collapse changes.
- Added helper tests and an inbox remount regression test for both
folded and unfolded states.

## Verification

- `pnpm exec vitest run ui/src/lib/inbox.test.ts
ui/src/pages/Inbox.test.tsx` — 77 tests passed.
- `pnpm check:token-gates` — all gates passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/ui build` — passed.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — 3,521 tests passed and four skipped. One unrelated
server test on the current base fails because it reads
`heartbeat.scheduling_suppressed` instead of `issue_commented`; the same
test fails alone and this pull request changes only inbox UI files.

## Risks

- Low risk. The state is local to the browser and scoped by company ID.
- Old parent IDs can remain in local storage after tasks are deleted,
but they do not affect visible tasks.

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

## Model Used

- OpenAI Codex, exact model ID `gpt-5.6-sol`, with reasoning, tool use,
and code execution. The runtime does not expose its 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-08-11 07:09:54 -04:00
Dotta 66575fe519
fix(paperclip-page): scope uploader credentials to the publish helper (#10894)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents publish static pages with the paperclip-page skill and its
`publish.sh` helper
> - The skill docs told operators to bind the page-uploader IAM keys as
the global `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`
> - Static env keys have precedence over `AWS_PROFILE` in the AWS CLI
and in all AWS SDKs
> - Because of this, each agent run lost the host role identity and lost
access to Secrets Manager and other AWS services
> - This pull request adds namespaced credential variables that apply
only to the helper's own `aws` calls
> - The benefit is a stable host AWS identity in agent runs, with no
change to page publishing

## Linked Issues or Issue Description

No public GitHub issue exists. Description of the problem:

**What happened?**

Agent runs on a host with `AWS_PROFILE` set lost access to AWS Secrets
Manager. The failures looked intermittent. The cause is deterministic:
the page-uploader keys were bound as global `AWS_ACCESS_KEY_ID` /
`AWS_SECRET_ACCESS_KEY` in agent run environments. These static keys
shadow `AWS_PROFILE`. Each process in the agent run then used the
S3-upload-only uploader identity.

**Expected behavior**

The page-uploader credentials apply only to the page publish helper. All
other processes keep the host identity from `AWS_PROFILE`.

**Steps to reproduce**

1. Set `AWS_PROFILE` to a role with Secrets Manager access.
2. Export `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` for an IAM user
without that access.
3. Run `aws sts get-caller-identity`. The identity is the IAM user, not
the role.
4. Run `aws secretsmanager list-secrets`. The call fails with
`AccessDeniedException`.

## What Changed

- `publish.sh` reads `PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID` and
`PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY`, with optional
`PAPERCLIP_PAGE_AWS_SESSION_TOKEN`.
- The helper applies these values only to its own `aws` invocations. It
clears ambient `AWS_PROFILE` and `AWS_SESSION_TOKEN` for those calls.
- Credential precedence is: namespaced key pair, then
`PAPERCLIP_PAGE_AWS_PROFILE`, then the ambient credential chain.
Existing global-name bindings continue to work during migration.
- Validation: the key pair must be set together. The pair plus
`PAPERCLIP_PAGE_AWS_PROFILE` is an error. A session token without the
pair is an error.
- `SKILL.md` and `README.md` now instruct operators to bind the secrets
under the namespaced names and explain the shadowing hazard.

## Verification

- Run `node --test
.agents/skills/paperclip-page/scripts/publish.test.mjs`. All 11 tests
pass.
- New tests cover: the incomplete key pair, the pair-plus-profile
conflict, the token-without-pair error, and a fake-`aws` environment
capture that proves the helper's calls see the page keys while
`AWS_PROFILE` and `AWS_SESSION_TOKEN` stay unset.
- Run `bash -n .agents/skills/paperclip-page/scripts/publish.sh` for a
syntax check.

## Risks

- Low risk. The change is contained in one skill helper and its
documents.
- The ambient credential chain remains the fallback, so current
deployments do not break before operators rebind the secrets.
- Operators must rebind the two page secrets to the namespaced names to
get the benefit. The README documents this.

## Model Used

Claude Fable 5 (`claude-fable-5`), Anthropic. Context window: 1,000,000
tokens (128K max output). Agentic coding session with extended thinking
and tool use (Claude Code 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
- [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 Fable 5 <noreply@anthropic.com>
2026-08-10 21:45:58 -04:00
Devin Foley d648becb90
refactor(ci): split workspaces-a into two Vitest native shards
Split the slow workspaces-a CI lane into two Vitest native shards and keep release verification in parity.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-10 17:32:44 -07:00
Devin Foley 6601014898
fix(release): reject promotion sources that predate their channel tooling (#11197)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The release subsystem promotes builds along canary → nightly → beta
→ stable, and each publish job checks out the promotion's source commit
and runs that tree's release tooling
> - The first beta dispatch failed with `unexpected argument: beta`: the
selected nightly's source predated the beta channel, so its `release.sh`
did not know the argument
> - The failure was clean (argument parsing, nothing published) but
cryptic, and the same trap waits for any promotion of a source older
than its target channel's tooling
> - This pull request makes the selection jobs reject such sources with
an actionable error and documents the property
> - The benefit is that a bootstrapping or old-source promotion fails in
seconds with instructions, instead of mid-publish with a parser error

## Linked Issues or Issue Description

Refs #11008 — the guard hardens the beta promotion flow introduced
there, after its first dispatch surfaced the gap described below.

**Subsystem affected**

Release automation: `.github/workflows/release.yml`, `doc/RELEASING.md`,
workflow wiring tests.

**Problem or motivation**

Run 31444045044 (first beta dispatch) failed in `publish_beta` with
`unexpected argument: beta`. Promotions deliberately build from the
pinned source commit, which means they also run that commit's
`scripts/release.sh` — and a source that predates the target channel's
introduction cannot publish it. Nothing guards this today; the error
surfaces deep in the publish job with no explanation.

**Proposed solution**

Guard at selection time: `select_nightly` requires the source canary's
`release.sh` to know the nightly channel, and `select_beta` requires the
source nightly's `release.sh` to know the beta channel. Each guard
literally matches the channel case arm and fails closed with a clear
message naming the remedy (promote a newer source). Document the
tooling-era property in `RELEASING.md` and pin the guards with a wiring
test.

## What Changed

- `.github/workflows/release.yml`: tooling-era guards in
`select_nightly` and `select_beta`
- `doc/RELEASING.md`: documents that promotions run the source commit's
release tooling
- `scripts/__tests__/release-verify-workflow.test.mjs`: wiring test
pinning both guards

## Verification

- Wiring tests: 5 pass
- Guard expressions exercised against real commits: accepts the
beta-capable merge commit of the beta-channel change, rejects a pre-beta
commit
- YAML parse of the workflow
- After merge: the next beta dispatch selects a beta-capable nightly and
passes the guard

## Risks

- Low. Selection-time check only; the guards match the channel case arm
literally and fail closed (with the same actionable message) if that
line is ever reformatted

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) in Claude Code, with
extended thinking and full tool use. All changes model-authored under
human direction.

## 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 (pending — will confirm before
merge)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending — will confirm before merge)
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-10 17:22:54 -07:00
Devin Foley 35aaaa0bd0
feat(server): preserve task timestamps and hierarchy through company import/export (#11193)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Company export/import moves a whole company — agents, tasks,
comments — between instances as a portable bundle
> - The bundle never carried task timestamps or parent links: the export
writes neither, the importer lets database defaults stamp "now", and
sub-tasks arrive flattened
> - Boards sort by recency, so every imported task showing "created just
now" collapses the task list into import order, and the task hierarchy
the user built is gone
> - This pull request adds created/updated/started/completed/cancelled
timestamps and a parent link to the bundle (schema v7), preserves them
end to end on import, and keeps comment imports from clobbering a
preserved updated time
> - The benefit is that an imported company reads like the company the
user left: same recency order, same task tree

## Linked Issues or Issue Description

**What happened?**

After a company import, every task showed as created at import time.
Recency sorting collapsed to import order, and parent/child task nesting
disappeared. The user called out losing "the meaningful task hierarchy
and recency sorting". Cause: the export bundle has no fields for task
timestamps or parent links, the importer lets `defaultNow()` win on
insert, and the comment importer bumps every touched task's `updatedAt`
to now.

**Expected behavior**

An imported company preserves each task's
creation/update/start/completion times and its position in the task
tree, so sorting and nesting on the destination match the source.

**Steps to reproduce**

1. On a source instance, create tasks over several days, including
sub-tasks nested under parents.
2. Export the company and import it into another instance.
3. Every task shows the import moment as its creation/update time and
all tasks are top-level.

## What Changed

- Export writes
`createdAt`/`updatedAt`/`startedAt`/`completedAt`/`cancelledAt` (ISO,
only when set) and `parent: <taskSlug>` into each task's bundle
extension; a parent outside the export selection drops the edge with an
aggregate warning, mirroring the existing blocker-edge warning
(`server/src/services/company-portability.ts`).
- Bundle schema version 6 → 7. All new fields are optional: v5/v6
bundles import unchanged with a version-aware downlevel warning; bundles
newer than the board still fail closed.
- Manifest parsing validates the new timestamps like comment timestamps
(invalid → warn and ignore, never a hard failure); shared types and the
zod validator carry the new optional fields.
- Import resolves parent slugs to pre-generated destination ids, drops
self-references and cycles from tampered bundles with warnings, and
orders rows parents-first because the self-referencing FK is checked per
insert chunk.
- `importIssues` writes the preserved timestamps (falling back to insert
time when absent; `startedAt` stays null unless bundle-carried, per
#11191's semantics) and `parentId`.
- `addImportedComments` no longer blanket-bumps `updatedAt = now()`; it
takes `GREATEST(updated_at, newest imported comment createdAt)`, so a
preserved update time never regresses while unpreserved rows keep the
old behavior.

## Verification

- `pnpm vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/company-portability-import-batching.test.ts
server/src/__tests__/productivity-review-service.test.ts` — 102 passed,
1 pre-existing opt-in benchmark skip. Includes: full round-trip with
exact timestamp equality and a 3-deep parent chain against embedded
Postgres; v6 back-compat (defaults + warning); forward-compat rejection
(v8); cycle/self-reference/invalid-timestamp tampered-bundle handling;
comment-bump preserve-awareness in both directions.
- `pnpm --filter @paperclipai/server typecheck` and `pnpm --filter
@paperclipai/shared typecheck` — clean.

## Risks

- **Rollout ordering**: a board on the previous build (max schema v6)
refuses bundles exported by this build (stamped v7) — the existing
newer-than-supported rejection, working as designed. Cross-instance
moves need the importing board upgraded first. Called out here so
operators aren't surprised during the transition window.
- Parent edges from tampered bundles are dropped with warnings rather
than failing the import; blocker relations already behave this way.
- Timestamps are data-only; no destination schema migration.

Stacked on #11191 (its commit is included here) — merge #11191 first;
this PR then shows only the v7 changes.

## Model Used

- Claude Fable 5 (`claude-fable-5`) via Claude Code CLI, with extended
thinking and tool use (multi-agent implementation with independent
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
- [ ] 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-08-10 17:01:33 -07:00
Devin Foley 4c062a0eb2
fix(ui): stop coercing imported agents to the destination CEO adapter (#11192)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Company import lets an operator bring a package of agents into an
instance, and each agent declares which adapter runs it (Claude Code,
Codex, and so on)
> - The export and the server importer preserve each agent's adapter
faithfully, but the Import page seeds an adapter override for every
agent with the destination CEO's adapter before the user touches
anything
> - Every imported agent therefore arrives as the CEO's adapter (usually
Claude Code) even when the source package holds a mix, and the picker
shows the coerced value as if it were the source's, so nothing looks
wrong
> - This pull request makes the manifest adapter the default, sends
overrides only for agents the user actually changed, and replaces the
silent coercion with an explicit per-agent fallback warning when the
destination truly lacks the source adapter
> - The benefit is that a mixed Claude/Codex team imports as a mixed
Claude/Codex team, and any real adapter gap is visible instead of silent

## Linked Issues or Issue Description

**What happened?**

A user imported a company package whose agents were a mix of Claude Code
and Codex on the source instance. After the import, every agent was
configured as Claude Code. The import preview showed no sign that
anything had been changed. Cause: the Import page initializes its
adapter-override map by assigning every agent the destination CEO's
adapter type and sends that override for every agent, overriding the
manifest's per-agent adapter server-side. For imports into a new
company, the "CEO adapter" is read from whichever unrelated company is
currently selected.

**Expected behavior**

Imported agents keep the adapter declared in the package. An override is
sent only when the operator explicitly picks a different adapter, or
when the source adapter is not installed on the destination — and in
that case the page must say so per agent, not silently substitute.

**Steps to reproduce**

1. On a source instance, create a company with one Claude Code agent and
one Codex agent, and export it.
2. Import the package on another instance whose CEO uses Claude Code,
changing nothing in the import dialog.
3. Both agents arrive configured as Claude Code; the Codex identity is
gone.

## What Changed

- The preview no longer seeds adapter overrides; the override map starts
empty, and the picker displays each agent's manifest adapter
(`ui/src/pages/CompanyImport.tsx`).
- `buildFinalAdapterOverrides` sends an entry only when the effective
adapter differs from the manifest or the agent's adapter config was
edited — untouched agents flow through with no override.
- The page fetches the destination's installed adapters (existing
`adaptersApi.list()` client). When a manifest adapter is missing or
disabled on the destination, only that agent defaults to the CEO's
adapter, with a visible amber warning naming both adapters. If the
adapters request fails, the page fails open: manifest adapters are kept
and no coercion happens.
- Tests: untouched mixed-adapter import sends no overrides; a
user-changed agent sends exactly one; a missing destination adapter
produces the fallback plus rendered warning for that agent only; an
adapters-endpoint failure produces no coercion.

## Verification

- `npx vitest run ui/src/pages/CompanyImport.test.tsx` — 19 passed (15
pre-existing + 4 new).
- `pnpm --filter ./ui typecheck` (`tsc -b`) — clean.

## Risks

- Behavior change: users who previously relied on the silent conversion
(importing packages that reference adapters they don't have) now get an
explicit per-agent fallback with a warning — same outcome, visible. The
server's hard rejection of unknown adapter types remains the backstop
for API callers.
- UI-only change; no server or schema impact.

## Model Used

- Claude Fable 5 (`claude-fable-5`) via Claude Code CLI, with extended
thinking and tool use (multi-agent implementation with independent
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
- [ ] 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-08-10 17:00:36 -07:00
Devin Foley d816eb8095
fix(server): keep imported tasks quiescent under the productivity review sweep (#11191)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Company import brings a full company package — agents, tasks,
routines — into an instance, with `pauseAutomations` promising a quiet
landing
> - The pause covers the imported entities, but the destination's own
productivity-review sweep does not know the difference between imported
rows and live work
> - The importer stamps every imported in-progress task with `startedAt
= now()`, so six hours later the sweep's long-active check fires on
every one of them and floods the board with review tasks and agent
wakeups
> - This pull request stops fabricating `startedAt` on import and makes
the sweep skip tasks whose assignee agent is paused
> - The benefit is that an import lands quietly: no surprise review-task
storm, and paused teams stay paused until the operator activates them

## Linked Issues or Issue Description

**What happened?**

After importing a company package with automations paused, a batch of
"productivity review" tasks appeared roughly six hours later — one for
every imported in-progress task — each with an owner-agent wakeup. The
user described it as jarring and wasteful. Cause: `importIssues`
fabricates `startedAt = now()` for imported in-progress rows, and
`reconcileProductivityReviews` considers any assigned in-progress task
without checking whether the assignee agent is paused, so its
long-active-duration evidence (6 h threshold) trips on the fabricated
timestamp.

**Expected behavior**

An import with paused automations must be quiescent: no destination
sweep should generate work from imported rows until the operator
unpauses the imported team. A paused agent must not accumulate review
tasks it cannot act on.

**Steps to reproduce**

1. Import a company package containing tasks with status `in_progress`
assigned to agents, with "pause automations" enabled.
2. Wait for the productivity-review reconcile (runs at startup and on
the heartbeat scheduler tick) more than six hours after the import.
3. Observe one new review task plus an owner wakeup per imported
in-progress task.

## What Changed

- `importIssues` no longer fabricates `startedAt` for imported
`in_progress` rows; it inserts null (`server/src/services/issues.ts`).
Audited every consumer of `issues.startedAt` — all are null-tolerant,
and normal checkout/status-transition paths set the value when work
really starts.
- `reconcileProductivityReviews` skips candidates whose assignee agent
is `paused`, counting them as skipped
(`server/src/services/productivity-review.ts`). This is a general rule,
not import-specific: a paused agent cannot act on a review.
- Tests: paused-assignee candidate with an old `startedAt` creates no
review, and creates one after unpausing; imported in-progress issue
lands with null `startedAt` (embedded-Postgres import test); the
pre-existing long-active regression test still passes.

## Verification

- `pnpm vitest run
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/company-portability-import-batching.test.ts` — 20
passed, 1 pre-existing opt-in benchmark skip.
- `pnpm vitest run server/src/__tests__/company-portability.test.ts` —
78 passed.
- `pnpm --filter @paperclipai/server typecheck` — clean.

## Risks

- Behavior change beyond imports: tasks assigned to paused agents no
longer receive productivity reviews anywhere. This is intended — the
review would target an agent that cannot respond — and reviews resume on
the first reconcile after unpausing.
- Imported in-progress tasks now carry no `startedAt` until real work
starts on the destination. The one sweep that read the fabricated value
is the one this PR quiets; all other consumers fall back safely (audit
in the commit body).
- Low risk otherwise: no schema change, no API shape change.

## Model Used

- Claude Fable 5 (`claude-fable-5`) via Claude Code CLI, with extended
thinking and tool use (multi-agent implementation with independent
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
- [ ] 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-08-10 17:00:32 -07:00
Devin Foley 8f7b8b3fda
feat(release): add human-gated beta channel with stable soak enforcement (#11008)
> Follow-up to #11006 (merged): rebased onto master and ready for
review.

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The release subsystem now publishes canary (every master push),
nightly (scheduled, smoke-gated, added in #11006), and stable (manual)
> - There is still no human-approved release-candidate lane between
nightly and stable, and nothing enforces that a stable actually soaked
anywhere before shipping
> - Betas need a real approval gate, and stables need a soak policy that
is data, not prose
> - This pull request adds the beta channel: a manual promotion of a
chosen nightly behind the `npm-beta` environment gate, re-smoked after
publish, plus a stable preflight that enforces a 3-day beta soak with a
written-justification bypass
> - The benefit is a complete canary → nightly → beta → stable train
where every stable shipped as a beta first, and emergencies leave a
written trace

## Linked Issues or Issue Description

**Subsystem affected**

Release automation: `scripts/release.sh`, `scripts/release-lib.sh`,
`.github/workflows/release.yml`, `.github/workflows/docker.yml`,
`.github/workflows/release-smoke.yml`.

**Problem or motivation**

After #11006 the project has canary and nightly prerelease lanes, but no
release-candidate lane. Stable promotion has no enforced soak: any ref
can ship as stable directly. There is no approval boundary for a
broader-audience prerelease, and no structured way to record why an
emergency release skipped validation.

**Proposed solution**

Add a `beta` channel: a manual dispatch that promotes a chosen nightly's
source commit, publishes behind the `npm-beta` GitHub environment
(required reviewers are the gate), re-smokes the published beta, and
tags `beta/vX`. Enforce in the stable path that the source commit
shipped as a beta at least 3 days earlier (measured from the beta's npm
publish time), with a `skip_soak_justification` input as the recorded
emergency bypass.

**Alternatives considered**

Codifying the soak policy in docs only. Rejected: an unenforced policy
decays; the preflight makes the policy executable while the
justification input keeps the emergency path usable and auditable.

## What Changed

- `scripts/release.sh` + `scripts/release-lib.sh`: `beta` channel —
requires HEAD to carry a `nightly/v*` tag, publishes the package set as
`YYYY.MDD.P-beta.N` under dist-tag `beta`, tags
`beta/vYYYY.MDD.P-beta.N`
- `.github/workflows/release.yml`:
- `channel: beta` dispatch path: `select_beta` resolves the newest (or
an explicit `source_version`) nightly and fails loudly on selection
problems; `publish_beta` runs behind the `npm-beta` environment, pushes
the tag, and dispatches `docker.yml`; `smoke_beta` re-runs the release
smoke suite against the exact published beta version
- stable path: new `preflight_stable` job enforces the 3-day beta soak
from the beta's npm publish time; `skip_soak_justification` bypasses
with the reason echoed into the job summary; dry runs report without
blocking
- `.github/workflows/docker.yml`: `beta/v*` tags publish `:beta` on both
images, with exact version stamping
- `.github/workflows/release-smoke.yml`: `beta` added to the dispatch
choice list
- Docs: `CHANNELS.md` beta entries; `RELEASING.md` beta lane, soak gate,
and failure playbook; `RELEASE-AUTOMATION-SETUP.md` `npm-beta`
environment setup, including the warning to create the environment
before the first beta dispatch (GitHub auto-creates unprotected
environments on first reference)
- Tests: beta version-counting coverage in
`scripts/release-registry-versions.test.mjs`; beta identity and
nightly-tag guard coverage in
`scripts/__tests__/release-dry-run-notes.test.mjs`

## Verification

- `node --test` on the two touched suites: 17 pass, including the 3 new
beta tests
- `bash -n` on both shell scripts and YAML parse of all three workflows
- After merge, in order: create the `npm-beta` environment, dispatch
`channel: beta` with `dry_run: true` to preview, then a real promotion
of a published nightly through the approval gate, then a stable dry-run
against a young beta to see the soak gate report

## Risks

- If the `npm-beta` environment does not exist when the first beta
dispatch runs, GitHub creates it with no protection rules and the beta
publishes without approval. Mitigated by documentation and by creating
the environment before merge (operator step)
- Until the first beta exists, every stable dispatch requires
`skip_soak_justification`. This is deliberate — the first beta ships
immediately after this merges — but it is a behavior change to the
stable dispatch
- The soak clock reads the beta's npm publish time from the registry; a
registry outage makes the preflight fall back to requiring justification
(fail-closed)

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) in Claude Code, with
extended thinking and full tool use (repository exploration, local test
execution, live registry and git verification). All code, tests, and
docs in this PR were model-authored under human direction.

## 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 (pending — will confirm before
merge)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending — will confirm before merge)
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-10 16:52:59 -07:00
Nicky Leach 459469638e
feat(adapter-codex-local): add secure device-login building blocks (#11097)
## Thinking Path

> - Paperclip connects AI agents to local and remote runtimes.
> - The Codex local adapter needs a safe device-login flow.
> - A future sandbox integration needs strict prompt validation, secret
protection, cleanup, and private credential storage.
> - This pull request adds tested building blocks for that flow.
> - The result gives a later Daytona integration a clear security
boundary.

## Linked Issues or Issue Description

No public issue covers this change.

**Problem or motivation**

The Codex local adapter has no safe, reusable flow to prove device login
inside an isolated sandbox.

**Proposed solution**

Add parser, runner, credential export, and proof helpers. Validate the
prompt, protect login data, store credentials in a private run-scoped
home, and dispose all sandbox resources.

**Alternatives considered**

Do not connect a production Daytona driver in this change. Use an
injected sandbox driver and focused tests first. This keeps the security
controls testable before live provider integration.

**Roadmap alignment**

The change extends the Codex local adapter. It does not add a core
Paperclip route or duplicate a planned core feature.

**Additional context**

The flow keeps the login URL, code, and token out of logs, results, and
errors. The proof home uses a company-scoped root and a run-scoped
private directory.

## What Changed

- Add a pure parser for the exact Codex device-login URL and one-time
code shape.
- Add a sandbox runner with prompt handling, timeout, cancellation, and
disposal.
- Add a credential export step with company scoping, path checks,
payload checks, private modes, locking, and cleanup.
- Add redacted device-login fixtures and focused tests for parsing,
secret redaction, runner outcomes, credential export, and cleanup.

## Verification

- Run `pnpm --filter @paperclipai/adapter-codex-local exec vitest run`.
- Run `pnpm --filter @paperclipai/adapter-codex-local exec tsc
--noEmit`.
- Review tests for strict URL and code validation, timeout,
cancellation, disposal, secret redaction, path safety, payload safety,
file modes, and cleanup.

## Risks

- This change provides building blocks, not a live Daytona proof.
- A later integration must connect the runner to a concrete sandbox
driver.
- Credential export depends on existing Codex authentication cache
helpers.
- Incorrect path or payload assumptions can reject valid credentials.

## Model Used

Codex, GPT-5, tool use, code execution, and repository review. The
Paperclip runtime controls the exact 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 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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-10 16:12:30 -07:00
Devin Foley 5a0985f80a
test(release-smoke): update onboarding spec for the mission-first wizard (#11190)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The release subsystem's nightly lane gates every nightly publish on
the release smoke suite, which drives real onboarding in a browser
against the published artifact
> - With the harness fixed (#11187, #11189), the gate reached the
Playwright suite for the first time in CI — and the spec still walks the
old onboarding wizard, so it fails at "Create your first agent" on every
current build
> - The wizard was redesigned to a mission-first five-step flow, and the
spec rotted silently because the suite never ran in CI before
> - This pull request rewrites the spec to drive the current wizard end
to end
> - The benefit is a smoke gate that actually tests today's product,
verified against a real published canary

## Linked Issues or Issue Description

**Subsystem affected**

Release smoke testing:
`tests/release-smoke/docker-auth-onboarding.spec.ts`.

**Problem or motivation**

Nightly run 31431273139 failed in the smoke Playwright suite: the spec
expects the old wizard step "Create your first agent", but current
builds show the redesigned mission-first flow (front door → company →
mission → team lead → connect model → review). The page snapshot in the
run artifact shows the "Define your mission" step where the spec
expected the agent step. Both retries failed identically — this is
deterministic spec drift, not flake.

**Proposed solution**

Rewrite the spec for the current flow: fill the company name, define the
mission directly (confirming creates the company), name the team lead,
hire it through the adapter step — the adapter environment probe reports
unhealthy in the CLI-less smoke container by design and must not block
the hire — then launch to the dashboard. Assert the company, the
ceo-role agent, and the company goal through the API. The first-task and
assignment-run assertions are removed together with the wizard flow that
created them.

## What Changed

- `tests/release-smoke/docker-auth-onboarding.spec.ts`: rewritten for
the mission-first wizard; sign-in and wizard-opening helpers and the
company-name step are unchanged

## Verification

- Full local run against the real nightly candidate: launched the smoke
container for `paperclipai@2026.810.0-canary.3` via
`scripts/docker-onboard-smoke.sh` (with the #11189 bind fix), then ran
`pnpm run test:release-smoke` against it — 1 passed (4.5s)
- After merge: dispatch `release.yml` with `channel: nightly` to run the
full gate in CI

## Risks

- Low. Test-only change. The spec now asserts less about first-task
creation because the wizard no longer creates a first task; if a
first-run trigger returns to onboarding, the spec should grow that
assertion back

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) in Claude Code, with
extended thinking and full tool use (CI artifact forensics, UI source
tracing, local Docker + Playwright reproduction and verification). All
changes model-authored under human direction.

## 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 (pending — will confirm before
merge)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending — will confirm before merge)
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-10 14:28:38 -07:00
Devin Foley f94f6003c6
fix(release-smoke): pin the smoke container to the lan bind preset (#11189)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The release subsystem's nightly lane gates every nightly on the
release smoke suite, which boots the published artifact in a Docker
container and drives real onboarding
> - The gate kept failing even after the readiness budget fix (#11187),
and the new container-log dump revealed the server was healthy but
listening on 127.0.0.1 inside the container, unreachable through
Docker's port mapping
> - `onboard --yes` without an explicit `--bind` prefers trusted-local
quickstart defaults: it writes a loopback bind into the instance config
and ignores the deployment env vars the harness passes, and that config
outranks `HOST` at runtime
> - This pull request pins the smoke container to the `lan` bind preset
and adds a wiring test for it
> - The benefit is a working nightly gate, verified end to end against a
real published canary

## Linked Issues or Issue Description

**Subsystem affected**

Release smoke testing: `docker/Dockerfile.onboard-smoke`,
`scripts/__tests__/release-verify-workflow.test.mjs`.

**Problem or motivation**

Nightly run 31428558684 failed in smoke with the server unreachable at
the mapped port for the full 420 second budget. The container logs
(captured thanks to #11187) show a fully booted server with `Bind
loopback (127.0.0.1)`. The harness sets `HOST=0.0.0.0` and the
deployment env vars, but `onboard --yes` without `--bind` deliberately
prefers trusted-local defaults, writes `bind: loopback` into the
instance config, and the config outranks `HOST` at runtime. A loopback
listener inside a container is invisible to the port mapping, so the
health check can never pass. This behavior predates the current stable,
so the harness was silently broken against every recent version — it
only surfaced now because the nightly lane is the suite's first CI
consumer.

**Proposed solution**

Pass `--bind lan` in the smoke container command (the flag is supported
by `latest` and canary alike; it selects the all-interfaces preset and
keeps the env-driven authenticated deployment), and pin the flag with a
wiring test so it cannot regress silently.

## What Changed

- `docker/Dockerfile.onboard-smoke`: the onboard command is now `onboard
--yes --bind lan --data-dir ...`, with a comment explaining why the flag
is load-bearing
- `scripts/__tests__/release-verify-workflow.test.mjs`: wiring test
asserting the smoke Dockerfile pins a non-loopback bind preset

## Verification

- Full local harness run against the real nightly candidate
`2026.810.0-canary.1`: container healthy, bind banner shows `lan
(0.0.0.0)`, authenticated bootstrap completed (admin created, bootstrap
invite accepted, board session verified), `/api/health` returns
`bootstrapStatus: ready`
- `node --test scripts/__tests__/release-verify-workflow.test.mjs`: 4
pass
- After merge: dispatch `release.yml` with `channel: nightly` to run the
gate end to end in CI

## Risks

- Low. The change only affects the smoke container. `--bind lan` inside
a container exposes the port to the container network only; reachability
from outside still goes through Docker's explicit port mapping

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) in Claude Code, with
extended thinking and full tool use (CI log forensics, upstream source
tracing, local Docker reproduction and verification). All changes
model-authored under human direction.

## 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 (pending — will confirm before
merge)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending — will confirm before merge)
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-10 13:51:49 -07:00
Devin Foley 30f6999cbe
fix(release-smoke): configurable readiness timeout and diagnostics for slow containers (#11187)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The release subsystem's nightly lane (#11006) gates every nightly
publish on the release smoke suite, which boots the published artifact
in a Docker container
> - The suite's first CI execution failed at the health readiness check:
the harness hard-codes a 90 second budget, but a CI container
cold-installs paperclipai from npm and initializes embedded postgres
with no warm caches
> - When the timeout expired with the container still running, the
harness printed no container logs, so the failure gave no diagnostics
> - This pull request makes the readiness budget configurable, raises it
for CI, and dumps container logs on timeout
> - The benefit is that the nightly gate measures the artifact, not the
runner's cold caches, and a red smoke run is diagnosable from its logs

## Linked Issues or Issue Description

**Subsystem affected**

Release smoke testing: `scripts/docker-onboard-smoke.sh`,
`.github/workflows/release-smoke.yml`.

**Problem or motivation**

Run 31426044332 (first forced nightly after #11006) failed in
`smoke_nightly` with `server did not become ready at
http://localhost:3232/api/health` after exactly 90 seconds. The
harness's readiness window is hard-coded to 90 attempts at 1 second.
Locally that works because the npm cache is warm; in CI the container
downloads the full package set and embedded postgres first. The timeout
path also printed no container logs when the container was still
running, so there was no way to see how far boot had progressed.

**Proposed solution**

Make the readiness budget an environment variable
(`SMOKE_READY_TIMEOUT_SECONDS`, default unchanged at 90 for local use),
set it to 420 in the CI workflow, and dump the last 150 container log
lines when the readiness check times out on a still-running container.

## What Changed

- `scripts/docker-onboard-smoke.sh`: `SMOKE_READY_TIMEOUT_SECONDS` env
var (default 90) replaces the hard-coded readiness budget; timeout with
a still-running container now prints the tail of `docker logs`
- `.github/workflows/release-smoke.yml`: sets
`SMOKE_READY_TIMEOUT_SECONDS=420` for CI runs

## Verification

- `bash -n` on the harness and YAML parse of the workflow
- The real proof is the next `channel: nightly` dispatch of
`release.yml`, which re-runs this suite in CI with the new budget

## Risks

- Low. The local default is unchanged; CI runs simply wait longer before
declaring failure, and a genuinely broken artifact still fails (with
logs now)

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) in Claude Code, with
extended thinking and full tool use. Diagnosis from CI run logs; patch
model-authored under human direction.

## 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 (pending — will confirm before
merge)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending — will confirm before merge)
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-10 13:19:33 -07:00
Devin Foley 5ca752dc81
fix(server): raise company import zip upload limit to 1 GB and make it operator-configurable (#11184)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Company import/export lets an operator move a full company package
between instances, with the Import page uploading the package as one
compressed `.zip`
> - The server caps that upload at 128 MB, and real company packages
with attachments now exceed it — imports fail at the preview step
> - The failure message tells the user to use the CLI folder import, but
that path posts inline JSON capped at 64 MB, so the advice is a dead end
for exactly these packages
> - This pull request raises the zip upload cap to a 1 GB default, makes
it operator-configurable through an environment variable, scales the
decompression-bomb guards from the cap in effect, and replaces the
misleading hint
> - The benefit is that large real-world company packages import
successfully, and operators with unusual needs can tune the cap without
a code change

## Linked Issues or Issue Description

**What happened?**

A company import fails at the preview step with `Preview failed: Import
package exceeds 134217728 bytes`. The package is a valid Paperclip
export. Its compressed size is larger than the 128 MB server cap (one
reported package is 257 MB). The error panel suggests the CLI folder
import, but that path sends the package as one inline JSON body capped
at 64 MB, so it also fails.

**Expected behavior**

A valid company package of realistic size imports successfully through
the Import page. If a package is too large, the error must state the
limit clearly and suggest a step that can work.

**Steps to reproduce**

1. Export a company with enough attachments to make the compressed
package larger than 128 MB.
2. Open the Import page and upload the `.zip`.
3. Click "Preview import".
4. The preview fails with `Import package exceeds 134217728 bytes`.

**Deployment mode**

Reported from a managed deployment; the limit applies to all deployment
modes.

## What Changed

- Raise `PORTABLE_ZIP_UPLOAD_LIMIT_BYTES` from 128 MB to a 1 GB default
(`server/src/http/body-limits.ts`).
- Add the `PAPERCLIP_IMPORT_ZIP_MAX_BYTES` environment override. Invalid
or non-positive values fall back to the default.
- Scale the zip decompression-bomb guard from the configured cap at the
import route: the aggregate inflated ceiling is 4x the cap. The
per-entry ceiling stays at 512 MB because V8's string length limit
applies to an entry regardless (`server/src/routes/companies.ts`,
`packages/shared/src/portability-zip.ts`).
- Report the 422 limit error in MB instead of raw bytes.
- Replace the "use the CLI folder import for very large packages" hint
on preview failure with advice that works: re-export the package without
large attachments (`ui/src/pages/CompanyImport.tsx`).
- Update the stale comment in `ui/src/lib/import-preflight.ts` that made
the same CLI claim.
- Add tests for the new default, the env override, and the
invalid-override fallback.

## Verification

- `pnpm vitest run server/src/__tests__/body-limits.test.ts
packages/shared/src/portability-zip.test.ts
server/src/__tests__/company-portability-routes.test.ts
server/src/__tests__/company-portability.test.ts
server/src/__tests__/company-portability-import-batching.test.ts` — all
pass.
- `pnpm vitest run ui/src/pages/CompanyImport.test.tsx` — passes,
including the updated failure-panel copy assertion.
- `pnpm typecheck` — clean across the workspace.
- Manual: upload a `.zip` larger than the configured cap; the preview
fails with `Import package exceeds the 1024 MB upload limit` and the new
hint. A package between 128 MB and 1 GB now previews and imports.

## Risks

- Peak per-import memory rises with the cap: the upload is buffered in
memory and unzipped in one pass. A 1 GB compressed package can use
several GB transiently. Imports are instance-admin actions, so the
exposure is a deliberate operator action, not anonymous traffic.
Operators on small hosts can lower the cap with
`PAPERCLIP_IMPORT_ZIP_MAX_BYTES`.
- The aggregate bomb guard moves from a fixed 512 MB to 4x the
configured cap. It still bounds expansion far below what a decompression
bomb needs.
- No migration and no API shape change. The 422 message text changes; no
code matches on the old text.

## Model Used

- Claude Fable 5 (`claude-fable-5`) via Claude Code CLI, with extended
thinking and tool use (file edits, local test runs, live-instance
inspection).

## 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
2026-08-10 12:47:03 -07:00
Devin Foley f9173782cd
feat(release): add smoke-gated nightly channel and lane-separated Docker tags (#11006)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The release subsystem publishes the `paperclipai` npm package set
and the Docker images on two lanes: canary on every master push, and
stable on manual promotion
> - There is no middle ground between those lanes. Users must track
every merge or wait weeks for a stable. Docker `:latest` also tracks
master, so Docker users have no stable image at all
> - A calm prerelease lane needs to exist, and it must never ship a
build that failed its checks
> - This pull request adds the nightly channel: a scheduled job that
selects the newest master commit with a green canary publish, runs the
full release smoke suite against that exact published canary, and only
then republishes it as the nightly. It also separates Docker tags by
lane, so `:latest` finally means stable
> - The benefit is that users can follow prereleases at a nightly
cadence with a smoke-tested guarantee, and Docker users get real
`:canary`, `:nightly`, and stable image tags

## Linked Issues or Issue Description

**Subsystem affected**

Release automation: `scripts/release.sh`, `scripts/release-lib.sh`,
`.github/workflows/release.yml`, `.github/workflows/docker.yml`,
`.github/workflows/release-smoke.yml`.

**Problem or motivation**

The project publishes only `canary` (every master push) and `latest`
(manual stable). Users who want prereleases without per-merge churn have
no option. Docker has a second problem: master builds overwrite
`:latest`, and CI-published stables never produced Docker images,
because tags pushed with `GITHUB_TOKEN` do not fire the `v*` tag trigger
in `docker.yml`. No stable-versioned image exists in ghcr today.

**Proposed solution**

Add a `nightly` channel. A scheduled job selects the newest
canary-tagged master commit, smoke-tests that exact published canary,
and republishes the same commit as `YYYY.MDD.P-nightly.N` under the
`nightly` dist-tag. Separate Docker tags by lane (`:canary` for master,
`:nightly` for nightly tags, `:latest` plus version tags for stable tags
only), and have the release jobs dispatch `docker.yml` at the new tag so
lane images actually build.

**Alternatives considered**

Moving the `nightly` dist-tag to the existing canary version without a
republish. Rejected: the version string would say `canary` while the
user is on nightly, which breaks at-a-glance lane identification in bug
reports and `--version` output.

## What Changed

- `scripts/release-lib.sh`: channel-parameterized
`next_prerelease_version` and `prerelease_tag_name` helpers (canary
helpers delegate to them), a `require_channel_tag_at_head` guard, and
the no-provenance retry for Sigstore transparency-log duplicates now
covers the `nightly` dist-tag as well as `canary`
- `scripts/release.sh`: new `nightly` channel. It requires HEAD to carry
a `canary/v*` tag, publishes the full public package set as
`YYYY.MDD.P-nightly.N` under dist-tag `nightly`, and tags the source
commit `nightly/vYYYY.MDD.P-nightly.N`
- `.github/workflows/release.yml`: scheduled nightly chain (09:00 UTC) —
select candidate, smoke it via `release-smoke.yml`, publish on green
under the existing `npm-canary` environment, push the tag, dispatch
`docker.yml`. New `channel` dispatch input (default `stable`, so
existing stable dispatches are unchanged) with `nightly_source_version`
and `dry_run` support for forced runs. The stable path now also
dispatches `docker.yml` at the new `v*` tag
- `.github/workflows/docker.yml`: lane tag mapping for both image jobs —
master pushes publish `:canary` and no longer move `:latest`;
`nightly/v*` tags publish `:nightly`; only stable `v*` tags publish
`:latest` and the versioned tags. New `workflow_dispatch` trigger for
the release-job dispatches. Build-version stamping uses the exact
nightly version on nightly tag builds
- `.github/workflows/release-smoke.yml`: `nightly` added to the dispatch
choice list
- `doc/CHANNELS.md` (new): user-facing guide to the channels
- `doc/RELEASING.md`: nightly lane documentation, Docker tag mapping
table, and a nightly failure playbook
- `doc/RELEASE-AUTOMATION-SETUP.md`: note that nightly reuses
`npm-canary` and needs no npm trusted-publisher changes
- Tests: channel-parameterized version helper coverage in
`scripts/release-registry-versions.test.mjs`, and nightly flow coverage
(publish identity, notes not required, canary-tag guard) in
`scripts/__tests__/release-dry-run-notes.test.mjs`

## Verification

- `node --test` on the release script suites: 68 pass, including 6 new
tests. The only failure, `acpx-patch-packaging.test.mjs`, needs
installed `node_modules` and fails identically on a pristine checkout of
master in the same environment
- `bash -n` on both shell scripts and YAML parse of all three workflows
- Live fail-path check: `./scripts/release.sh nightly --print-version`
from a master tip with no canary tag fails with `HEAD has no canary/v*
tag`
- Live success-path check: the same command from the
`canary/v2026.806.0-canary.7` commit prints `2026.806.0-nightly.0`
- Live selection check: the candidate-selection shell logic run against
the real repository selects the commit of `canary/v2026.806.0-canary.7`,
which matches the current npm `canary` dist-tag exactly
- After merge: dispatch `release.yml` with `channel: nightly` and
`dry_run: true` to preview, then a real forced run to validate end to
end before the first scheduled run

## Risks

- Docker `:latest` changes meaning from "latest master build" to "latest
stable release". This is deliberate and will be announced. Users who
want the old behavior pull `:canary`. Until the first stable release
after this change, `:latest` stays at its current (master-built) image
- The nightly is a rebuild of the same source commit, not the
byte-identical canary artifact that was smoked. The lockfile pins
dependencies, and the publish path's registry-visibility and
clean-prefix install gates still run on the nightly artifacts
- All npm publishing must stay inside `release.yml` because npm trusted
publishing pins that workflow file per package. The nightly jobs were
added to `release.yml` for exactly that reason; this constraint is now
documented in `RELEASING.md`
- The stable-lane Docker dispatch fails gracefully (a warning with
manual instructions) when the source ref predates `docker.yml`'s
`workflow_dispatch` trigger

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) in Claude Code, with
extended thinking and full tool use (repository exploration, local test
execution, live registry and git verification). All code, tests, and
docs in this PR were model-authored under human direction.

## 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 (pending — will confirm before
merge)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending — will confirm before merge)
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-10 12:16:55 -07:00
LeonSGP 6a4e2e1b8c
fix(routes): return 409 for routine checkout conflicts (#3790)
## Thinking Path

> - Paperclip orchestrates AI agents and relies on issue checkout as the
core task-claiming primitive
> - The issue checkout route is the HTTP boundary that translates
service and database outcomes into agent-usable API responses
> - Routine-linked issues are protected by the partial unique index
`issues_open_routine_execution_uq`, which covers only rows whose
`execution_run_id` is set
> - `svc.checkout` sets `execution_run_id`, so a concurrent claim moves
the row into that index and can raise a 23505 mid-request
> - Unhandled, that surfaces as a 500 and crashes the agent run instead
of being a recoverable conflict
> - Drizzle wraps driver failures in its own `Failed query: ...` error,
so the Postgres error carrying `code` and the constraint name is
reachable only through `cause`
> - This pull request translates that violation into a 409 at the
checkout route, detecting it through the cause chain the way
`isReviewPathRecoveryIdempotencyConflict` already does
> - The benefit is that agents handle routine execution contention
through the normal heartbeat conflict path instead of failing on an
internal server error

## Linked Issues or Issue Description

Fixes #3660

Related pull requests found while searching for duplicates:

- #3699 — an earlier attempt at this same route-level fix, closed
unmerged. Same shape, and its check has the flat-error bug described
under Verification.
- #3633 — related work on postgres.js `constraint_name` handling in
conflict detection.
- #5662 — covers the adoption path (`assertCheckoutOwner`) that this
pull request does not.

## What Changed

- Added `server/src/db-errors.ts` with `isUniqueViolation(error,
constraintName?)`, which walks the `cause` chain (depth-capped) and
accepts the postgres.js `constraint_name`, the node-postgres
`constraint`, or the driver message as evidence of SQLSTATE 23505.
- Wrapped `svc.checkout()` in `POST /issues/:id/checkout` with a narrow
try/catch that uses that helper to return **409 Conflict** for
`issues_open_routine_execution_uq`, and rethrows every other error
unchanged.
- Added `server/src/__tests__/db-errors.test.ts` covering the wrapped
and bare error shapes, both constraint field names, the message
fallback, non-matching constraints, non-unique-violation codes, and a
self-referential cause chain.

## Verification

- The new unit test includes the wrapped case `{ cause: { code: "23505",
constraint_name: ... } }` that a flat `error.code` check fails, so it is
a real regression guard rather than a restatement of the implementation.
- The wrapped shape is what this codebase observes in practice:
`server/src/__tests__/plugin-tenant-isolation.test.ts` asserts
`cause?.code === "23505"` against embedded Postgres,
`packages/db/src/pipelines-schema.test.ts` asserts that constraint
failures throw `Failed query`, and
`server/src/services/recovery/review-path-recovery.ts` walks the same
chain.
- CI (verify, e2e, policy) exercises this change against current master
through the pull request merge ref.
- Not verified locally: no monorepo install or typecheck was run in this
environment.

## Risks

- Low. One route gains a catch that matches a single constraint and
rethrows all other errors, so no unrelated failure can be swallowed.
- The 409 body `{ error: ... }` matches the other 409 responses this
route already returns.
- Scope limit: this covers the checkout route only. The adoption path
reached through `assertCheckoutOwner` (heartbeat, plugins, and pipelines
routes) can still surface the same violation as a 500; #5662 targets
that path.
- `isUniqueViolation` is new and intentionally generic. Existing flat
23505 checks elsewhere in the server are left untouched by this pull
request.

## Model Used

- Original change: OpenAI Codex, GPT-5-class tool-using coding agent in
the Codex CLI environment; exact backend model revision is not exposed
in that runtime.
- Follow-up revision (cause-chain detection plus tests): Anthropic
Claude Opus 5 (`claude-opus-5`), tool-using coding agent with extended
thinking 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)
- [ ] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] 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

---------

Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
2026-08-09 20:20:09 -05:00
Manav Shrivastava ebf2b8ff79
fix(server): persist worktree runtime port when ambient PORT does not match (#1849) (#1930)
## What was done
Replaced the strict `!nonEmpty(process.env.PORT)` guard in
`maybePersistWorktreeRuntimePorts` with a new `isPortPinnedByRuntimeEnv`
helper function. This function checks if `process.env.PORT` is set, but
only suppresses persisting the port to configuration if the ambient
`PORT` matches the newly allocated `selectedPort`.

## Why it matters
Fixes issue #1849. Previously, if an ambient `PORT` environment variable
was exported globally (like inheriting from the shell running the parent
workspace), worktrees would silently fail to write their
collision-avoiding ports (e.g. 3103 instead of 3100) back to their
respective local `config.json` files. This resulted in orphaned
sub-worktrees and lost port tracking on reboot. With this fix, worktrees
correctly persist their assigned ports even while nested under an
inherited environment variables stack, while continuing to respect
manual, explicit pinning.

## How to verify
1. Export a port in the shell explicitly: `export PORT=3100`.
2. Launch a sub-worktree instance which receives an auto-assigned free
port (e.g., `3103`).
3. View the underlying `config.json` for that worktree inside
`.paperclip/worktrees/`.
4. The config file should correctly contain `{"server": {"port": 3103}}`
rather than dropping the write operation.

## Risks
None expected. The `Number()` and `Number.isInteger()` checks handle
parsing edge cases cleanly, defaulting robustly to preventing writes if
`process.env.PORT` is somehow malformed (e.g., set to a non-integer),
ensuring absolute safety during misconfigurations.

Co-authored-by: manavshrivastavagit <manavshrivastava@users.noreply.github.com>
2026-08-09 17:29:44 -05:00
Evyatar Bluzer 19be4cf927
refactor(a11y): add scope=col to the agent costs table headers (#1789)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The agent detail page has a Costs section. That section renders a
data table of per-run spend.
> - The table has a header row, but its `<th>` elements carry no `scope`
attribute.
> - A screen reader uses `scope="col"` to bind each data cell to its
column header. Without it, the reader announces a number without telling
the user which column it belongs to.
> - A table of costs is exactly the case where that hurts. Every cell is
a bare figure.
> - This pull request adds `scope="col"` to the five header cells in
that table.
> - The benefit is that assistive technology announces the cost table
correctly. The change is markup only, so sighted users see no
difference.

## Linked Issues or Issue Description

No public GitHub issue covers this. The problem is described in-PR,
following the enhancement template.

**What existing behavior does this improve?**

The Costs table rendered by `CostsSection` in
`ui/src/pages/AgentDetail.tsx`.

**Subsystem affected**

ui/ — React + Vite board UI

**Current behavior**

The table renders five header cells: Date, Run, Input, Output, and Cost.
None of them set `scope`. A screen reader must guess the header-to-cell
relationship, so a user hears a value with no column name attached to
it.

**Proposed behavior**

Each header cell sets `scope="col"`. A screen reader then announces the
column name together with each cell, so a cost figure is read as part of
the Cost column.

**Reason and benefit**

`scope` is the standard way to associate header cells with data cells in
an HTML table. The attribute has no visual effect, so the fix carries no
design cost and makes the table usable with a screen reader.

**Breaking changes**

None. `scope` is a presentational-neutral HTML attribute. No component
API, no styling, and no test changes.

**Related pull requests**

- #2215 proposed the same attribute for the Routines table. It is
closed, because that table no longer exists on master.
- #1524 and #1522 applied `scope="col"` to other tables. Both are
closed.

## What Changed

- Added `scope="col"` to the five `<th>` elements in the `CostsSection`
table in `ui/src/pages/AgentDetail.tsx`.
- Rebased the branch onto current master.
- Dropped the original `ui/src/pages/Routines.tsx` hunks. Master rebuilt
the Routines page around folder-grouped rows, so the table those hunks
targeted no longer exists.
- Dropped the original `HintIcon` opacity change. It altered a visible
colour, which is out of scope for a markup-only accessibility fix.

## Verification

- Run `pnpm --filter @paperclipai/ui typecheck` and `pnpm --filter
@paperclipai/ui build`. This is a markup-only change, so a clean
type-check and build is the relevant automated signal.
- Open an agent detail page and go to the Costs section. Inspect the
header row. Each `<th>` now carries `scope="col"`.
- Navigate the same table with a screen reader, cell by cell. Each cell
is announced with its column name.
- Compare the rendered page before and after. It is unchanged, because
`scope` has no styling effect.

## Risks

Low risk. The change adds one standard HTML attribute to five header
cells in a single table. It introduces no code path, changes no
component API, and has no visual effect. The worst case is that the
attribute is redundant for a reader that already infers the column,
which is harmless.

## Model Used

Anthropic Claude Opus 5, exact model ID `claude-opus-5`. It ran with
extended thinking and repository read/write tools, inside a
maintainer-operated triage agent. The model rebased the branch, dropped
the two out-of-scope hunks, and wrote this description. The original
change was authored by @bluzername, and the model used for that work is
not recorded here.

## 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 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` references)
- [x] I have considered and documented any risks above
- [x] My branch name describes the change and contains no internal
ticket id
- [ ] I have run tests locally and they pass — not run. This is a
markup-only change and the package has no test covering this table.
- [ ] I have added or updated tests where applicable — no test added,
which is why this PR is titled `refactor:`.
- [ ] I have updated relevant documentation to reflect my changes — no
documentation describes this markup.
- [ ] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work — not checked by the maintainer who rebased this.
- [ ] All Paperclip CI gates are green — CI re-runs on this push.
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
Greptile re-reviews on this push.

---------

Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
2026-08-07 20:46:47 -05:00
scotttong cc35c3c395
feat: structure and humanize recovery notices (#11075)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip posts system comments when automatic run recovery cannot
continue
> - These comments currently mix the main event with recovery
identifiers and routing details
> - The task chat shell also renders these comments as large raw text
blocks
> - Operators need a short explanation first and inspectable evidence on
demand
> - This pull request emits structured recovery notices and renders them
as compact humanized rows
> - The benefit is a quieter task thread that keeps the full recovery
evidence available

## Linked Issues or Issue Description

Related prior extraction source: #11070. This pull request replaces only
its structured recovery notice slice with a focused branch based on
current master.

**What existing behavior does this improve?**

Paperclip recovery escalations and the experimental task chat
system-comment renderer.

**Current behavior**

Recovery escalation comments put action identifiers, owner details, run
details, and failure codes into the visible markdown body. The task chat
shell renders the complete system comment as a large text block.

**Proposed behavior**

The server emits a short system notice with typed metadata sections. The
task chat shell classifies known recovery families and renders one
compact row. An operator can expand the row to inspect the full body and
metadata.

**Reason and benefit**

The main thread stays readable during repeated recovery activity. Typed
links and evidence remain available without exposing raw failure text in
the default view.

**Breaking changes**

The visible recovery comment body is shorter. Recovery action
deduplication now reads the structured metadata and still recognizes
legacy body markers. No API schema or database migration changes.

## What Changed

- Emit stranded recovery escalations with `system_notice` presentation
and typed recovery, owner, run, and failure-code metadata.
- Share bounded metadata row builders across recovery notice producers
and preserve legacy deduplication compatibility.
- Humanize known recovery notice families and render compact expandable
task-chat rows.
- Route system-authored comments ahead of derived agent authorship so
recovery notices do not appear as agent bubbles.
- Add focused server and UI regression coverage.

## Verification

- `pnpm check:token-gates` — 3/3 clean.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/shared exec vitest run
src/validators/issue.test.ts` — 32 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/services/recovery/stranded-notice.test.ts
src/__tests__/issue-recovery-actions.test.ts` — 57 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/services/recovery/successful-run-handoff.test.ts
src/services/recovery/stranded-notice.test.ts` — 39 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-process-recovery.test.ts -t 'escalates an
exhausted failed successful-run handoff without using generic
continuation recovery first|escalates an exhausted successful handoff
run that still leaves no disposition'` — 2 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-process-recovery.test.ts -t 'blocks assigned
todo work after the one automatic dispatch recovery was already used'` —
passed.
- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/system-notice-humanizer.test.ts
src/components/task-chat/TaskChatSystemNotice.test.tsx
src/components/task-chat/task-chat-adapter.test.ts` — 15 tests passed.
- Storybook visual baselines were not updated because this chat-shell
path has no affected snapshot baseline. Focused rendering tests and
token gates cover this change.

## Risks

- Consumers that parse recovery action identifiers from comment markdown
must move to structured metadata. Server deduplication remains backward
compatible with legacy comments.
- The humanizer uses stable recovery-family phrases. Unknown notices use
a generic truncated first-sentence fallback.
- The UI changes only the experimental task chat presentation. The
stored comment body and expanded metadata remain available.

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

## Model Used

- OpenAI Codex with GPT-5, reasoning mode, repository tools, shell
execution, and GitHub integration. The runtime did not expose a
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-08-07 18:41:52 -07:00
scotttong 4e9a78db58
feat(ui): persist task chat composer drafts (#11076)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - People send task instructions through the board task chat.
> - A page refresh or task switch can discard an unfinished message in
the redesigned composer.
> - The existing task chat already supplies a task-specific draft key.
> - The redesigned composer must use that key without changing
attachment or send behavior.
> - This pull request restores, saves, and clears text drafts in the
redesigned composer.
> - The benefit is that users can return to unfinished task messages
without losing their text.

## Linked Issues or Issue Description

Related prior work: #11070. This pull request extracts only the final
composer draft behavior from that larger draft.

**Subsystem affected**

ui/ — React + Vite board UI.

**Problem or motivation**

The redesigned task chat composer does not use the draft key that the
task thread already provides. A refresh, navigation, or unmount can lose
an unfinished message.

**Proposed solution**

Persist text drafts by task key in local storage. Restore a draft when
the composer mounts. Save changes after a short delay and flush pending
text during unload or unmount. Clear the draft only after a successful
send.

**Alternatives considered**

The composer could save on every keystroke. A short delay avoids
unnecessary synchronous storage writes. The feature could also stay in
the larger predecessor PR, but a focused PR is easier to review and
verify.

**Roadmap alignment**

This is a focused usability improvement for the task conversation
surface. It does not add or duplicate a roadmap capability.

## What Changed

- Added safe draft storage helpers for load, save, and clear operations.
- Connected the task-specific draft key to the redesigned task chat
composer.
- Preserved drafts across debounce windows, unmounts, page unloads,
failed sends, and React Strict Mode probes.
- Cleared drafts after successful sends without changing current
attachment safeguards.
- Added focused composer and thread integration tests.

## Verification

- `pnpm check:token-gates`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/task-chat/TaskChatComposer.test.tsx
src/components/TaskChatThread.test.tsx`

## Risks

- Local storage can be unavailable or full. The helpers catch storage
errors and keep the composer usable.
- Only text is persisted. Attachments, work mode, and assignee
selections remain session state.
- Draft keys remain task-scoped, so text does not cross task boundaries.

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

## Model Used

OpenAI Codex with model `gpt-5`. The context-window size is not exposed
in this environment. The model used agentic reasoning, tool use, 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-08-07 18:32:49 -07:00
scotttong 34fe57a024
fix(server): ignore sibling worktrees in dev watch (#11074)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Developers can run Paperclip from linked Git worktrees.
> - The server development watcher scans paths near the active checkout.
> - A main checkout can contain many complete sibling worktrees under
`.paperclip/worktrees`.
> - Scanning those sibling checkouts can stall the watcher before it
starts the server.
> - This pull request excludes the shared worktree directory from the
development watcher.
> - The benefit is that development startup stays responsive as the
number of worktrees grows.

## Linked Issues or Issue Description

**What happened?**

The server development watcher traversed sibling checkouts under
`.paperclip/worktrees`. Large worktree collections could make `pnpm dev`
stall before the watcher started the server process.

**Expected behavior**

The watcher must observe only source paths that can reload the active
checkout. It must ignore sibling worktrees in both a main checkout and a
linked worktree.

**Steps to reproduce**

1. Create several linked worktrees under `.paperclip/worktrees`.
2. Add normal dependency and build output trees to those worktrees.
3. Run `pnpm dev` from the main checkout or one linked worktree.
4. Observe the watcher scan sibling worktrees before it starts the
server.

**Paperclip version or commit**

Reproduced on `master` before this change.

**Deployment mode**

Local development with `pnpm dev`.

## What Changed

- Detect whether the active server root is inside the managed
linked-worktree directory.
- Ignore the shared `.paperclip/worktrees` root from both main and
linked checkouts.
- Add regression coverage for the resolved ignore path and its globstar
form.

## Verification

- `./node_modules/.bin/vitest run
server/src/__tests__/dev-watch-ignore.test.ts --reporter=verbose`
- `pnpm --filter @paperclipai/server typecheck`

## Risks

- Low risk. The change affects only local development watch exclusions.
- A non-standard checkout that copies the same `.paperclip/worktrees`
directory layout will receive the same exclusion.

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

## Model Used

- OpenAI Codex, GPT-5, context window not disclosed, with reasoning,
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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 18:30:16 -07:00
Evyatar Bluzer 1791979512
refactor(a11y): add ARIA progressbar attributes to BudgetPolicyCard (#1805)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Humans oversee those agents in teams, so each team needs its own
spend controls
> - The BudgetPolicyCard component shows how much of a budget is
consumed
> - The utilization bar in that card is a styled div with no ARIA role,
value, or label
> - A screen reader user therefore cannot hear how much budget is used
> - This pull request adds role="progressbar" and the matching ARIA
value attributes
> - The benefit is that assistive technology announces budget
utilization the same way sighted users see it

## Linked Issues or Issue Description

No existing issue. Related pull requests: #1869 and #1878 add the same
progressbar
semantics to ProviderQuotaCard and QuotaBar. They touch different files.

The problem follows the bug report template:

**What happened?**
The budget utilization bar in `BudgetPolicyCard` renders as a plain
`div`. It has no
`role`, no `aria-valuenow`, and no accessible name. A screen reader
announces nothing
for it. The user can read the "Remaining" amount, but not the
utilization percentage.

**Expected behavior**
The bar is announced as a progress bar. It reports the current
utilization percentage,
with its minimum and maximum.

**Steps to reproduce**
1. Open a project or agent page that shows the budget card.
2. Start VoiceOver (Cmd+F5 on macOS).
3. Move the cursor to the budget utilization bar.
4. VoiceOver announces nothing.

**Paperclip version or commit**
master, `ui/src/components/BudgetPolicyCard.tsx`.

**Deployment mode**
Local dev (pnpm dev).

## What Changed

- Added `role="progressbar"` to the inner bar element.
- Added `aria-valuenow` with the rounded utilization percentage.
- Added `aria-valuemin={0}` and `aria-valuemax={100}`.
- Added `aria-label` in the form `Budget utilization: 73% used`.

`aria-valuenow` and `aria-label` use the same `progress` value. That
value is already
capped at 100 by `Math.min(100, summary.utilizationPercent)`, so the
reported value
stays inside the min/max range when a scope is over budget.

An earlier revision also changed the budget amount `Input` to
`type="number"`. That
change is removed. It changed input behavior and was not an
accessibility fix.
See "Risks".

## Verification

1. Open a page that shows the budget card.
2. Start VoiceOver (Cmd+F5 on macOS) and move to the utilization bar.
3. VoiceOver announces "Budget utilization: X% used, progress
indicator".
4. Inspect the element. `aria-valuenow` equals the displayed percentage,
and it stays
   at 100 when utilization is above 100%.

The change adds attributes only. There is no visual change.

## Risks

Low risk. The change adds ARIA attributes to one element in one
component. It changes
no logic, no styling, and no layout.

The `type="number"` change is removed on purpose. With `type="number"`,
a browser
reports an empty string for text it cannot parse. `parseDollarInput("")`
then returns
`0` instead of `null`, so the existing "Enter a valid non-negative
dollar amount."
error never appears and unparseable input silently becomes a $0.00
budget. The
`inputMode="decimal"` input keeps that validation path working.

## Model Used

Original implementation by @bluzername. The author did not state a
model.
Scope reduction, branch update, and this description: Claude Opus 5 (1M
context,
extended thinking, tool use), prepared under maintainer review.

## 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 searched GitHub for duplicate or related PRs and linked
them above
- [x] I have 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
- [x] I have considered and documented any risks above
- [ ] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [ ] I have run tests locally and they pass
- [ ] I have added or updated tests where applicable
- [ ] I have updated relevant documentation to reflect my changes
- [ ] 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

---------

Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
2026-08-07 19:51:03 -05:00
Nicky Leach d5208d30c1
feat(adapter-utils): add host-side pack span to managed-runtime tarball build (#11072)
## Thinking Path

> - Paperclip runs AI agents through adapter execution services.
> - The adapter runtime records spans for each stage of agent startup.
> - The managed runtime packs workspace tarballs before it uploads them.
> - These pack operations had no host span, so `stage.sync` omitted pack
time.
> - This pull request adds one `pack` span around both tarball builds.
> - The span nests under the active `stage.sync` step and improves trace
detail.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The managed runtime workspace sync builds a git-history tarball and a
workspace-overlay tarball before upload.

**Subsystem affected**

The change affects `packages/adapter-utils`, which provides adapter
execution and managed runtime support.

**Current behavior**

The host builds both tarballs without an OpenTelemetry span. The
`stage.sync` trace therefore omits the host pack duration.

**Proposed behavior**

The host wraps both tarball builds in one `pack` span. The executor
parents this span under the active startup step.

**Reason and benefit**

The trace shows the time that the host spends packing workspace data.
Operators can use the existing runtime span tree to find sync delays.

**Breaking changes**

None. The default span runner remains a no-op runner, and the existing
control flow remains unchanged.

## What Changed

- Add an optional `runtimeSpan` runner to the managed runtime
preparation path.
- Create one host `pack` span around the two workspace tarball builds.
- Parent the `pack` span under the active startup step.
- Add unit coverage for span emission and span nesting.

## Verification

- Run `tsc --noEmit` for `@paperclipai/adapter-utils`.
- Run the `@paperclipai/adapter-utils` Vitest suite.
- Confirm the suite reports 448 passed tests and 4 skipped tests.
- Confirm the trace test records `pack` under `stage.sync`.

## Risks

The change adds optional tracing only. The default no-op runner
preserves behavior when tracing is not configured.

## Model Used

OpenAI Codex, GPT-5, tool use and code execution. The model reviewed the
handoff and opened this pull request. The implementation author supplied
the commit.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 17:47:30 -07:00
github-actions[bot] 677242344c
chore(lockfile): refresh pnpm-lock.yaml (#11068)
Auto-generated lockfile refresh after dependencies changed on master.
This PR only updates pnpm-lock.yaml.

Co-authored-by: lockfile-bot <lockfile-bot@users.noreply.github.com>
2026-08-07 16:52:45 -07:00
Tonio 384e5f6178
revert(ui): back out onboarding port (#10786) (#11067)
This reverts commit 11e56654f8.

#10786 ported the onboarding flow from a standalone prototype and
repointed `/onboarding` at the new `CloudOnboardingFlow`, deleting the
existing `OnboardingWizard.tsx` in the process. The ported flow is not
ready to be the shipping onboarding experience: it landed as a single
large port rather than an incremental migration, it pulled `motion`,
`three` and `@types/three` onto the UI dependency list for prototype
visuals, and it deleted the wizard that four in-flight pull requests
(#9900, #9501, #8982 and one more) were building on — those went
CONFLICTING the moment the file disappeared.

Rather than keep the half-migrated state on master while that is sorted
out, back the port out whole and re-land it incrementally. This restores
`OnboardingWizard.tsx` and the previous versions of the four e2e specs,
drops the `onboarding-preview.html` Vite entry, the DesignGuide
onboarding section and the `data-viz-misc` storybook story, and removes
the three prototype dependencies from `ui/package.json`.

This is an exact mechanical inverse of the squash commit — 41 files,
+2089/-3647, no hand edits. Reverting this commit restores all 41 files
byte for byte, so the port is recoverable in full when it is ready.

`pnpm-lock.yaml` is deliberately not touched. #10786 never updated it;
bot commit 4683f26c9 (#11036) added the `motion`/`three` entries
afterwards, so the lockfile is now ahead of the manifest. CI owns
lockfile updates (`.github/workflows/pr.yml`) and the policy job
regenerates it from the changed manifest.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 14:55:34 -07:00
Nicky Leach 6b7e0814a0
feat(acp): stream Daytona sandbox agent output and remove the host output poll (#11049)
## Thinking Path

> - Paperclip is the open source app that manages AI agents for work
> - Sandbox providers let agents run in remote and isolated environments
> - Daytona session commands need a path that sends agent output to the
host without host polling
> - Host polling adds delay and repeats provider output work
> - This pull request adds typed execute.log notifications and a log
sink for incremental output
> - This pull request adds an optional ACP session stream with
final-result replay protection
> - The benefit is lower output delay while the default flags keep
current behavior unchanged

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting. The change spans the plugin SDK, Daytona provider,
adapter utilities, and server execution services.

**Problem or motivation**

The Daytona ACP bridge polls a host output file while an agent command
runs. This adds delay and can repeat work. The host also needs a safe
route for provider output chunks.

**Proposed solution**

Add a typed `execute.log` notification with host-issued invocation
correlation. Add an ordered log sink to the environment execute path.
Add an optional ACP session-log path that parses newline-delimited JSON
frames and removes the host output poll for that path.

**Alternatives considered**

Keep the output-file poll as the only path. This keeps the current
behavior but does not provide timely output. The new path stays behind
flags, so the existing path remains the default fallback.

**Roadmap alignment**

This change supports the shipped Cloud / Sandbox agents milestone in
`ROADMAP.md`, including Daytona support.

## What Changed

- Add the typed `execute.log` worker-to-host notification and
company-scoped host route.
- Add ordered `stdout` and `stderr` chunk delivery before the final
execute result.
- Add the Daytona session log sink and the optional ACP streamed session
path.
- Add monotonic frame handling so live and final output reach the host
once.
- Keep `useLogStream` and `streamAgentSessionOutput` off by default.
- Add unit and integration coverage for the notification, execution
target, runtime, and Daytona paths.

## Verification

- Run adapter-utils tests: 445 tests pass locally.
- Run server environment tests: 73 tests pass locally.
- Run Daytona plugin tests: 131 tests pass locally.
- Run TypeScript checks for shared, adapter-utils, and server.
- Review the pull request checks after GitHub completes them.
- All required GitHub checks pass on the current head.

## Risks

The new paths change output delivery only when a feature flag enables
them. The final execute result remains available for parsing and
fallback. The main risk is a provider stream or frame-order error; the
final-result parser limits that risk.

## Model Used

OpenAI Codex, GPT-5, tool use and code execution. The runtime did not
supply a context-window value.

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

No operator documentation change applies because both new flags remain
disabled by default.
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 14:29:58 -07:00
Dotta 0a511ed1b0
feat(apps): support multiple provider connections (#11060)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Apps subsystem connects company tools through governed provider
connections.
> - A company can need more than one account for the same provider.
> - The current database constraint and Apps flow assume one named
connection per company.
> - New quarantined actions also need an explicit review decision before
activation.
> - This pull request supports multiple provider connections and
complete action review decisions.
> - The benefit is safer access control and a clear multi-account Apps
workflow.

## Linked Issues or Issue Description

Refs: #11040

**Subsystem affected**

Cross-cutting. This change affects the Apps UI, the tool access API, the
shared request contract, and the database schema.

**Problem or motivation**

The connection name constraint prevents a company from keeping more than
one connection for a provider. The Apps UI also reuses an existing OAuth
connection when a user asks to connect another account. Action review
can enable selected entries without recording a decision for every
quarantined action.

**Proposed solution**

Remove the company and connection name uniqueness constraint. Let users
open, count, edit, and create multiple provider connections. Require the
finish request to cover every quarantined action exactly once before the
server activates reviewed entries.

**Alternatives considered**

The UI could generate unique internal names and keep the database
constraint. This would preserve a one-connection assumption in the data
model and would make display names part of identity. The server could
also infer review decisions from enabled actions. This would not
distinguish a reviewed disabled action from an action that the user did
not review.

**Roadmap alignment**

This change extends the completed MCP Tool Gateway and Apps milestone.
It also supports the Connected Apps roadmap item. It follows the
navigation and connection management work in #11040.

## What Changed

- Remove the company-scoped connection name uniqueness index with an
ordered and idempotent migration.
- Add a reviewed action list to the finish-app contract and reject
incomplete or duplicate review decisions.
- Activate reviewed entries and keep unreviewed quarantined entries
blocked.
- Enable a completed connection and preserve the company and connection
scope in all updates.
- Show provider connection counts and open the provider setup page from
Browse.
- Let users edit existing connections or connect another account without
reusing an active OAuth connection.
- Update focused server and UI coverage for multiple connections and
action review.

## Verification

- Ran the focused Apps UI suite. All 116 tests passed in 11 files.
- Ran the focused server and CLI suite. All 276 tests passed in 3 files.
- Ran `pnpm --filter @paperclipai/db check:migrations`. The migration
safety check passed.
- Ran `pnpm -r typecheck`. All projects passed.
- Ran `pnpm build`. All projects built successfully.
- Ran `pnpm test:run`. It passed 3,735 tests and skipped 4 tests. One
worktree-safety assertion failed because the execution workspace reloads
its worktree marker. The same test passed with an isolated non-worktree
marker.
- Ran `pnpm check:token-gates`. It reports 12 existing violations in the
unchanged `PaperclipOrbit3D.tsx` file from the target branch.
- Started the six affected Playwright specifications. Chromium could not
start because the host does not provide `libatk-1.0.so.0`. The GitHub
e2e jobs will verify these specifications.
- GitHub Actions passed every final-head CI gate, including all three
e2e shards and the aggregate `e2e` and `verify` jobs.
- Greptile reviewed final commit `9af9200426` at 5/5 with zero review
threads.

## Risks

- Removing the name uniqueness index permits duplicate display names.
Stable connection IDs and UIDs remain unique within a company.
- The finish-app endpoint accepts the new review field as optional for
backward compatibility. When clients send it, the server requires a
complete decision for all quarantined actions.
- Multiple OAuth connections depend on the explicit new-connection route
flag. Focused tests cover active and draft connection reuse.
- The migration is ordered after migration 0210. Its `DROP INDEX IF
EXISTS` statement is safe to repeat.

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

## Model Used

OpenAI Codex with the `gpt-5.6-sol` model assisted this change. The
agent used repository tools, code execution, test execution, and agentic
reasoning. The Codex runtime manages the 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-08-07 16:28:04 -05:00
Dotta a71b9cf628
feat(skills): add MCP integration preparation skill (#11063)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Skills Store lets a company find and install reusable agent
procedures
> - The MCP integration preparation procedure existed outside the app
catalog
> - Paperclip users could not find or install that procedure from the
product
> - This pull request adds the procedure as an optional software
development skill
> - The skill keeps research, human approval, and connector delivery as
separate gates
> - The benefit is a repeatable and governed path from vendor research
to one connector pull request

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The app-shipped Skills Store can install optional skills, but it does
not include the MCP integration preparation workflow from
`paperclip-content`.

**Subsystem affected**

`packages/skills-catalog`.

**Current behavior**

An agent must know where the external workflow lives. The agent cannot
find or install it from the Paperclip skills catalog.

**Proposed behavior**

The optional catalog includes `prepare-mcp-integration`. The installed
skill directs agents through cited research, a research-only content
pull request, an exact-revision human gate, and one Paperclip connector
pull request per approved connection.

**Reason and benefit**

This change makes the existing integration and connector playbooks
available as one installable Paperclip workflow. It also prevents agents
from starting connector code before the research gate is approved.

**Breaking changes**

None. The skill is optional and markdown-only.

Related source work: paperclipai/paperclip-content#13.

## What Changed

- Add the optional `prepare-mcp-integration` catalog skill under
software development
- Add Paperclip catalog metadata for roles, requirements, tags, and
trust classification
- Add a worked Notion MCP research-gate example
- Regenerate the checked-in catalog manifest
- Add the new key to the shipped optional skill test

## Verification

- `pnpm --filter @paperclipai/skills-catalog build:manifest`
- `pnpm --filter @paperclipai/skills-catalog validate`
- `pnpm --filter @paperclipai/skills-catalog test`
- Confirm the generated catalog contains
`paperclipai/optional/software-development/prepare-mcp-integration`
- Confirm the trust level is `markdown_only` and compatibility is
`compatible`

## Risks

- Low risk. This change adds one optional markdown-only catalog entry.
- The workflow can become stale if the two upstream playbooks change.
The skill requires agents to read the current playbooks before each
phase and to update upstream rules when reusable specifications change.

## Model Used

OpenAI Codex with GPT-5.4, reasoning mode, shell tool use, and code
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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 15:40:11 -05:00
Dotta 3435920ae1
docs: minimize plan task graphs (#11057)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip skills guide agents through repeatable work.
> - The plan-to-task skill guides agents when they create issue graphs
from plans.
> - The prior guidance could encourage an issue for every concrete
deliverable.
> - That guidance can create unnecessary subtasks for work that one
owner can complete end to end.
> - This pull request defines the boundaries that justify a separate
task.
> - It also adds a merge-back pass that removes task splits without a
qualifying boundary.
> - The benefit is a smaller issue graph with clear ownership,
dependencies, and review gates.

## Linked Issues or Issue Description

**Issue type**

Unclear or confusing documentation.

**Where is the issue?**

`skills/paperclip-converting-plans-to-tasks/SKILL.md`

**What's wrong?**

The skill says that each concrete deliverable must become an issue. This
can make agents split one end-to-end job into tasks for each step, file,
component, or phase. The result is more coordination work without a real
execution boundary.

**Suggested fix**

Tell agents to start with one end-to-end task. Permit separate tasks
only for ownership, parallel work, dependencies, independent review or
approval, or substantial follow-up work. Require a merge-back pass
before agents create the issue graph.

## What Changed

- Added a rule to use the fewest tasks that can complete and verify the
work.
- Defined the boundaries that qualify work for a separate issue.
- Added a merge-back pass for proposed subtasks without a qualifying
reason.
- Updated dependency, parallel work, verification, and checklist
guidance to enforce the smaller graph.

## Verification

- Ran `pnpm exec vitest run packages/shared/src/frontmatter.test.ts`.
- Result: 1 test file passed and 21 tests passed.
- Ran `git diff --check public-gh/master...HEAD`.
- Confirmed that the PR changes one skill file and has no lockfile,
workflow, image, or migration changes.

## Risks

- Low risk. This change updates skill guidance only.
- Agents might combine work too aggressively. The qualifying boundaries
preserve separate ownership, parallel execution, dependencies, review,
approval, and substantial follow-up work.

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

## Model Used

- OpenAI GPT-5 through Codex. The exact deployment ID and context window
are not exposed. The agent used reasoning, repository tools, command
execution, and GitHub 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
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 15:00:41 -05:00
Dotta b18b0fc39b
feat: refine app connections and legacy worktree startup (#11040)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Apps UI manages app discovery and app connections.
> - The managed worktree runtime starts agent work in repository
worktrees.
> - The Apps routes do not match the main discovery flow, and the
connections view lacks a delete action.
> - Legacy managed worktrees can also start before their pending seed
operation runs.
> - This pull request makes app discovery the main Apps route and makes
connection management explicit.
> - It also seeds legacy managed worktrees before runtime startup and
makes the CLI read the repository-local config.
> - The benefit is a clearer Apps workflow and a safer managed-worktree
startup path.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

This improves the Apps navigation, app connection management, managed
git-worktree startup, and CLI worktree selection.

**Subsystem affected**

Cross-cutting. The change affects `ui/`, `server/`, `cli/`, and
development documentation.

**Current behavior**

The `/apps` route opens the connections list while discovery uses a
nested route. The connections list has no delete action. Some legacy
managed worktrees can start runtime work before their pending seed
operation runs. The CLI can also read an ambient Paperclip config
instead of the repository-local config.

**Proposed behavior**

The `/apps` route opens Browse, and `/apps/connections` opens the
connection list. Users can delete a connection after confirmation.
Runtime startup seeds legacy managed worktrees when required. The CLI
resolves the current worktree from the repository-local
`.paperclip/config.json` file.

**Reason and benefit**

Users can discover apps from the canonical Apps route and can manage
existing connections from a dedicated route. Legacy worktrees receive
their required repository content before agent runtime starts. CLI
worktree selection stays scoped to the current repository.

**Breaking changes**

The `/apps` and `/apps/browse` route behavior changes. Old Browse links
redirect to `/apps`. The change does not modify an API schema or
database schema.

## What Changed

- Make Browse the canonical `/apps` page and move the connection list to
`/apps/connections`.
- Align Apps navigation, redirects, attention links, empty states, and
connection actions with the new routes.
- Add connection deletion with confirmation and clear failure feedback.
- Seed legacy managed git worktrees before runtime startup when their
seed status is pending.
- Read the CLI worktree selection from the repository-local Paperclip
config.
- Update focused UI, server, CLI, and development documentation
coverage.

## Verification

- Ran 202 focused UI, server, and CLI tests. All tests passed.
- Ran `pnpm -r typecheck`. All projects passed.
- Ran `pnpm build`. All projects built successfully.
- Ran `pnpm test:run`. The server and UI stages passed 7,168 tests. The
CLI stage found one environment-sensitive secrets test because this
workspace injects static AWS credentials. The isolated CLI file passed
all 8 tests after those injected variables were unset.
- Ran `pnpm check:token-gates`. It reports 12 existing color-token
violations in the unchanged `PaperclipOrbit3D.tsx` file from the target
branch. This pull request does not modify that file.
- Ran focused regression coverage for repository-root CLI config
resolution and connection deletion state. All tests and affected package
typechecks passed.
- Collected all 27 tests in the six changed Playwright specifications
successfully.
- GitHub Actions passed every latest-head CI gate, including all three
e2e shards and the aggregate `e2e` and `verify` jobs.
- Greptile reviewed the final commit at 5/5 with zero unresolved
threads.

## Risks

- Existing bookmarks for `/apps/browse` redirect to `/apps`.
- Connection deletion changes visible connection state and requires user
confirmation.
- The legacy seed path runs only for managed git worktrees with pending
seed state. Tests cover the startup condition.
- The rebase preserves the target branch's direct OAuth policy for the
Notion connection flow.

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

## Model Used

OpenAI Codex with the GPT-5 model family assisted this change. The agent
used reasoning, repository tools, code execution, and test execution.
The runtime does not expose the exact model snapshot or 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-08-07 14:26:40 -05:00
Michael Nguyen 42c73562c5
fix(heartbeat): backfill projectWorkspaceId when restoring a reused execution workspace (#10171)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Each run executes inside a persisted **execution workspace** (a row
in `execution_workspaces`) that is either freshly created or
**restored/reused** across runs of the same issue
> - Before adapter launch, a guard rejects a restored workspace whose
`projectWorkspaceId` is null while the issue resolves a concrete project
workspace (`persisted_workspace_missing_project_workspace_id`) — a
safety check against binding a run to a workspace with no
project-workspace link
> - The reuse/**restore** path updated the existing row (cwd, branch,
status, metadata…) but **never set `projectWorkspaceId`**, so a row
persisted with a null value stayed null on every restore
> - Result: for an issue that resolves a project workspace, the guard
fires, `reuse_existing` re-selects and re-binds the *same* stale null
row on the next attempt, and the run crash-loops forever with no
self-heal
> - This pull request backfills `projectWorkspaceId` during restore
(prefer the existing binding, fall back to the resolved one) so the row
heals on first reuse and the guard stops firing
> - The benefit is that reused workspaces created before their project
had a primary project workspace self-repair on next use instead of
crash-looping, while genuine mismatches are still surfaced by the guard

## Linked Issues or Issue Description

No public GitHub issue exists — describing the bug inline per the bug
report template (`.github/ISSUE_TEMPLATE/bug_report.yml`):

### What happened?
In `heartbeatService`, the execution-workspace reuse/restore branch
calls
`executionWorkspacesSvc.update(reusableExistingExecutionWorkspace.id, {
… })` without a `projectWorkspaceId` field. Only the sibling CREATE
branch sets `projectWorkspaceId`. So an execution workspace that was
persisted with a null `projectWorkspaceId` (e.g. created before its
project had a primary project workspace) is never backfilled on restore.
When such a workspace is later reused for a run whose issue resolves a
concrete project workspace, the pre-launch guard throws
`persisted_workspace_missing_project_workspace_id`, the run fails, and
`reuse_existing` re-binds the identical stale row on the next attempt —
an unbounded crash-loop with no self-heal.

### Expected behavior
On restore, the reused workspace's `projectWorkspaceId` is backfilled
from the resolved project workspace when it is currently null, so the
guard passes and the run launches. An existing non-null binding is never
overwritten (a genuine mismatch is still surfaced by the separate
`project_workspace_mismatch` guard).

### Steps to reproduce
1. Have an `execution_workspaces` row with `project_workspace_id = NULL`
that is eligible for reuse.
2. Give its project a primary project workspace (so the issue now
resolves a concrete `projectWorkspaceId`).
3. Dispatch a run for an issue in that project that reuses the
workspace. The restore `update()` leaves `project_workspace_id` null,
the launch guard throws
`persisted_workspace_missing_project_workspace_id`, and every subsequent
reuse re-binds the same null row and fails identically.

### Paperclip version or commit
`master` (branched from `14f20be92`); reproduced on a live self-hosted
instance.

### Deployment mode
Self-hosted, embedded Postgres, local adapters.

## What Changed

- New exported pure helper
`reconcileReusedExecutionWorkspaceProjectWorkspaceId(existing,
resolved)` in `server/src/services/heartbeat.ts`, returning `existing ??
resolved ?? null`. It prefers an existing binding (never nulls out a
good value or silently rebinds a genuine mismatch — the guard still
surfaces those), backfills a null binding from the resolved value, and
stays null when neither is present.
- Wire the helper into the reuse/restore
`executionWorkspacesSvc.update(...)` call so the restored row's
`projectWorkspaceId` is set to
`reconcileReusedExecutionWorkspaceProjectWorkspaceId(reusableExistingExecutionWorkspace.projectWorkspaceId,
resolvedProjectWorkspaceId)`. The CREATE branch already set
`projectWorkspaceId`; this brings the restore branch to parity.

## Verification

- Added 3-case unit coverage in
`server/src/__tests__/heartbeat-workspace-session.test.ts` for the
helper: (a) backfills a null existing binding from the resolved value,
(b) never overwrites an existing binding even when a resolved value is
present, (c) returns null when both existing and resolved are absent
(null and undefined inputs).
- Confirmed the `update()` patch type accepts the field:
`executionWorkspacesSvc.update` takes `Partial<typeof
executionWorkspaces.$inferInsert>`, and `projectWorkspaceId` is a column
on that table; both
`reusableExistingExecutionWorkspace.projectWorkspaceId` and
`resolvedProjectWorkspaceId` are `string | null`, matching the helper's
`string | null | undefined` params / `string | null` return.
- Live-instance exposure check (embedded Postgres): 354
`execution_workspaces` rows carry a null `project_workspace_id`; all of
them belong to projects with **no** project workspace, so
`expectedProjectWorkspaceId` currently resolves null and the guard does
not fire today. The fix is durable heal-on-reuse protection for the
moment any such project gains a primary project workspace (or a null row
is reused for an issue that resolves one).
- CI (full pnpm workspace install) runs the authoritative test +
typecheck for this change on this PR.

## Risks

- Low risk; scoped to the execution-workspace restore path, no schema or
API change.
- The helper only ever *adds* a `projectWorkspaceId` where the row had
none; it never overwrites an existing binding, so it cannot mask a real
`project_workspace_mismatch` (that guard still runs after).
- Complementary to (not overlapping with) #10130, which escalates a
terminal `workspace_validation_failed` run to `blocked` from the
recovery side; this PR prevents the guard from firing on reuse in the
first place. Neither depends on the other.

## Model Used

Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, with tool use /
code execution (repo edit, embedded-Postgres exposure query, unit-logic
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 (searched open PRs touching heartbeat / execution-workspace /
reuse; only #10130 is related, and it is complementary)
- [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
- [ ] I have updated relevant documentation to reflect my changes (no
user-facing docs affected)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (in progress)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending)
- [x] I will address all Greptile and reviewer comments before
requesting merge

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 09:06:36 -07:00
Dotta 9abb600e72
docs(connections): MCP-direct/DCR playbook section + Notion dry-run appendix (#11030)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Apps give agents governed access to external tools through catalog
connectors.
> - The connector playbook is the repeatable template for adding such
connectors.
> - Notion just shipped as the first MCP-direct connector with RFC 7591
dynamic client registration (#11009).
> - The playbook had no guidance for MCP-direct connections, OAuth
discovery, or DCR.
> - This pull request documents that path and encodes three mandatory
standards into the connector template.
> - It also adds a Notion dry-run appendix recorded from the live probe
and the shipped implementation.
> - The benefit is that the next MCP-direct connector follows a
recorded, verified path.

> [!IMPORTANT]
> **Depends on #11009.** This PR documents the Notion MCP connector that
ships in #11009 (RFC 7591 DCR, discovery-first endpoints,
`redirectConstraints` enforcement, refresh-rotation hardening).
Reviewing this doc against master before #11009 merges will show the
documented behavior as "not implemented" — that is expected. Draft until
#11009 lands, then re-review.

## What Changed

`doc/connections/CONNECTOR-PLAYBOOK.md` only (+364 lines, no code):

- New **"MCP-Direct Connections"** section: RFC 9728/8414 endpoint
discovery chain, an **RFC 7591 dynamic client registration** subsection
(public client, PKCE S256, env-client precedence, persist-and-reuse),
and a **redirect-URI constraints** subsection (`https-or-loopback-http`,
fail-fast wizard error).
- Three mandatory documentation standards encoded into the connector
template itself: a service-involvement statement (DCR providers need
neither Paperclip ID nor Paperclip Connect — instance-local per the
PAP-14828 spec §10 item 8.4; cloud and self-hosted use the same path), a
required **Connection Flow** section (sequence diagram + exact
authorize/token/registration/callback endpoints), and a required
**Administrator Setup** section.
- A **Notion dry-run appendix** mirroring the Linear appendix, recorded
from the live PAP-16649 probe: verified request sequence, redirect-URI
probe results table, sequence diagram (mermaid), shipped manifest
sketch, representative tool risk classes, admin setup (nothing to
register), governance defaults, and validation hooks.

## Verification

- Docs-only change; no code paths affected. `git diff --stat` shows
exactly one file.
- Every endpoint, error code, and constraint in the appendix was checked
against the shipped implementation on the #11009 head:
`server/src/services/tool-access.ts` (`assertOAuthRedirectConstraints`,
DCR registration metadata, refresh serialization),
`server/src/routes/tool-access.ts` (`POST
/api/tools/oauth/:connectionId/start`, `GET /api/tools/oauth/callback`),
and `packages/shared/src/app-definitions/notion.json`
(`redirectConstraints: "https-or-loopback-http"`).
- The request log mirrors the live probe record from PAP-16649
(2026-08-06/07), not vendor docs alone.
- Mermaid source renders cleanly (rendered PNG attached to PAP-16653).

## Risks

- Low: documentation only. Main risk is doc/implementation drift if
#11009 changes before merging — mitigated by the dependency note above
and re-review after #11009 lands.
- The template changes add mandatory sections for future connector
proposals; existing proposals are not retroactively invalidated.

## Model Used

Claude Fable 5 (claude-fable-5).

## Linked Issues or Issue Description

PAP-16653 (parent PAP-16637 P5). Companion catalog package landed in
paperclip-content (`integrations/catalog/platforms/notion/areas/mcp/`).

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 10:52:04 -05:00
Nicky Leach 01b51dc0e5
fix(runtime): stop Live badge and Working shimmer after task teardown (#10985)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task list and the chat views show a Live badge and a Working
shimmer for an issue that has an active run.
> - A finished task kept the Live badge and the Working shimmer after
the run ended and the sandbox stopped.
> - The user interface reads run liveness from the
`heartbeat_runs.status` row. The run finalizer writes the terminal
status in a step that is separate from the agent `status=done` update.
When the sandbox or the run process stops between the two steps,
`heartbeat_runs.status` stays `running` forever.
> - A run row that stays `running` makes a finished task look
perpetually Live, and the user interface has no guard for an issue that
already reached a terminal status.
> - This pull request closes the invariant "environment lease released
implies the run is terminal" on the server, and adds a user interface
guard that suppresses live state for a terminal issue.
> - The benefit is that a finished task stops showing Live and Working,
both at the source (the run row) and at the surface (the badge and the
shimmer).

## Linked Issues or Issue Description

**Bug description**

- A completed task kept the Live badge and the Working shimmer after its
run ended and the sandbox was torn down.

**Steps to reproduce**

- Run an agent task to completion. Let the sandbox tear down while the
run finalizer is between the `status=done` update and the terminal
run-status write.
- Open the task list or the chat view for the finished task.

**Expected behavior**

- A finished task shows no Live badge and no Working shimmer.

**Actual behavior (before this change)**

- The finished task showed the Live badge and the Working shimmer
because its `heartbeat_runs.status` row stayed `running`.

This pull request supersedes the two separate pull requests #10954
(frontend) and #10955 (backend). It carries all of their changes for the
same race.

## What Changed

Server:

- Run teardown terminalizes a still-running or still-queued run before
it releases the environment lease. It writes `succeeded` when the issue
already reached `done`, `cancelled` when the issue is `cancelled`, and
`interrupted` otherwise. It never overwrites a status that another path
already made terminal.
- The recovery stale-lock sweep terminalizes an orphaned running run to
`interrupted` after it confirms the process and the sandbox are both
gone. It requires recorded process metadata, so it never terminalizes a
live run, a queued run, or a scheduled retry.
- Each terminal transition writes a run event.
- The stale-lock sweep continues and clears the lock when the audit
write fails. It logs the failure loudly.
- New server tests cover both invariants.

User interface:

- A shared guard suppresses the Live badge and the Working shimmer when
the issue status is terminal.
- The guard keeps non-terminal `queued` and `running` issues live.
- The guard prefers the newest issue live-status snapshot.
- New user interface tests cover the guard and the snapshot preference.

## Verification

Server:

- `pnpm --filter @paperclipai/plugin-sdk ensure-build-deps && tsc
--noEmit` in `server/` — 0 errors.
- `pnpm --filter server test
heartbeat-run-lease-release-terminalization.test.ts
recovery-stale-issue-lock-sweep.test.ts` — 12 tests pass.

User interface:

- `pnpm --filter @paperclipai/ui typecheck` — 0 errors.
- `pnpm exec vitest run ui/src/lib/liveIssueIds.test.ts
ui/src/lib/issue-chat-messages.test.ts` — 40 tests pass.

## Risks

- Low risk. The server change only forces a still-live run row to a
terminal status when the lease releases or when the recovery sweep
confirms the process is dead. It never overwrites an existing terminal
status, and it guards the recovery path with process metadata to avoid
terminalizing a live run.
- The user interface change is additive. The guard only suppresses live
state for a terminal issue and keeps queued and running issues live.
- No database migration. No change to any external endpoint.

## Model Used

- Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 07:23:56 -07:00
github-actions[bot] 4683f26c97
chore(lockfile): refresh pnpm-lock.yaml (#11036)
Auto-generated lockfile refresh after dependencies changed on master.
This PR only updates pnpm-lock.yaml.

Co-authored-by: lockfile-bot <lockfile-bot@users.noreply.github.com>
2026-08-07 03:07:55 -05:00
Tonio 11e56654f8
feat(ui): port onboarding flow from prototype; add cloud + local variants (#10786)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - First-run onboarding is the subsystem that turns a brand-new install
into a working company: it creates the company, its goal, a lead agent,
and that agent's first task
> - The existing `OnboardingWizard` carried all of that wiring
correctly, but its UI had drifted from the current design direction, and
a separate design prototype (`paperclip-onboard`) existed as a
standalone visual mock with no backend
> - Porting the prototype's *logic* would have thrown away working,
well-tested backend orchestration; leaving the two apart meant the
design never shipped
> - Separately, cloud and local (self-hosted) installs need meaningfully
different first runs — local has no sign-in and must let the user pick a
locally-installed CLI adapter — so a single linear wizard could not
serve both
> - This pull request rebuilds the presentational layer from the
prototype on top of the existing backend orchestration, and splits it
into two thin flow containers over a shared core
> - The benefit is that the shipped onboarding matches the intended
design, cloud and local can diverge without duplicating logic, and each
can later ship to a different app version while sharing one set of step
components

## Linked Issues or Issue Description

No existing issue — describing inline (feature request).

**What problem does this solve?**
Onboarding is the first thing a new user sees, and the shipped wizard
had drifted from the current design. In parallel, cloud and local
installs need different first-run paths: local has no hosted sign-in,
and its agent runs on a CLI adapter installed on the user's machine,
which the cloud path never has to ask about. There was no way to express
that difference without either forking the whole wizard or bolting
conditionals onto a single linear flow.

**Proposed solution**
Extract the onboarding step views and shell into a shared core, then
compose two thin flow containers (cloud and local) over it. Keep all
backend orchestration in the existing `useOnboardingFlow` hook so no
working logic is rewritten.

**Alternatives considered**
- *Single flow with a `variant` prop* — most DRY, but the two flows are
intended to ship on different app versions, and a shared file would have
to be split later anyway.
- *Two fully independent copies* — simplest per-flow, but every shared
refinement (spacing, motion, copy) would have to be made twice and would
drift.

## What Changed

- **Shared core** under `ui/src/components/onboarding/`:
`OnboardingScaffold` owns the full-screen shell and the single
`AnimatePresence` step crossfade, so both flows transition identically;
step views (Start / Company / Agent / Task), `FooterNav`, `AgentPreview`
and the motion constants are extracted for reuse.
- **`CloudOnboardingFlow`** — `start → company → agent → task`; mounted
in the real app via `OnboardingWizardVariant`. Behaviour matches the
retired wizard, including `previewMock` and the existing-company ("add
an agent") entry point.
- **`LocalOnboardingFlow`** — skips sign-in and adds an optional email
ask (with a privacy assurance), a local model/adapter step that hires
with `requireEnvProbe: true`, and a "star us on GitHub" interstitial
before completing. **Harness-only for now** — the real app still mounts
the cloud flow.
- **Deleted `OnboardingWizard.tsx`** (1,786 lines); updated its
Storybook stories and the `OnboardingWizardVariant` test to the new
components.
- **Orbiting 3D paperclip backdrop** behind the auth and welcome screens
(`three`), code-split so it only downloads on those screens; honours
`prefers-reduced-motion` and disposes its GL context on unmount.
- **`motion`** added for step transitions and the agent-capsule
choreography.
- Visual values routed through design tokens per `DESIGN.md`; `Stepper`
generalized to take a step total (backward compatible); `/design-guide`
page and the component index updated.
- **Standalone preview harness** (`ui/onboarding-preview.html`) with
`?flow=` and `?step=` for backend-free review, wired as a second Vite
rollup input.
- **Adapter env probe bound to the adapter it ran against.**
`hireLeadAgent` reused `adapterEnvResult` for any adapter, so when a
hire failed and the user picked a *different* local adapter and retried,
the previous adapter's verdict satisfied the `requireEnvProbe` guard
while the hire posted the new adapter's config — hiring it unprobed. The
cache is now keyed on the adapter type plus the exact config posted to
the test endpoint, the config is built once and shared by probe and
hire, a failed probe clears the cache, and `clearAdapterEnvResult()`
(called on adapter change) stops the step displaying a stale verdict.
Cloud is unaffected — it hires with `requireEnvProbe: false`. Reported
by Greptile.
- **E2E specs re-pointed at the new flow.** Four specs still drove the
deleted wizard (`onboarding`, `conference-room-typing-intro`,
`planning-mode-visual-verification`, `nux-phase4-screenshots`) and
failed with `element(s) not found` on `"Name your company"` /
`input[placeholder="Acme Corp"]`. Rather than repeat the new drive
sequence four times, `tests/e2e/onboarding-flow.ts` adds one driver per
step (`startCloudOnboarding`, `completeCompanyStep`,
`completeAgentStep`, `completeTaskStep`, `completeCloudOnboarding`) and
the specs import it, so the next flow change touches a single file. Two
now-dead `**/test-environment` route stubs went with it — the cloud flow
hires with `requireEnvProbe: false`, so that probe never fires.

## Verification

- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `npx vitest run` over the onboarding suites
(`OnboardingWizardVariant`, `AgentCapsule`, `onboarding-launch`,
`onboarding-goal`, `onboarding-route`, `onboarding-adapter-config`) — 33
tests pass.
- `pnpm --filter @paperclipai/ui build` — succeeds; the three.js chunk
splits out separately (522 kB raw / 133 kB gzip) rather than entering
the main bundle.
- Both flows driven end-to-end in the preview harness in `previewMock`
(no database writes), plus the cloud flow rendered in the real
authenticated app at `/onboarding` to confirm the mount swap.
- The four re-pointed e2e specs pass locally against the new flow.
- New `ui/src/hooks/useOnboardingFlow.test.tsx` — 4 cases pinning the
adapter-probe cache (switch-adapter retry, cold path, explicit clear,
and the cloud flow's `requireEnvProbe: false`). Verified non-vacuous:
the switch-adapter case fails against the pre-fix code.
- Rebased onto current `master`; `pnpm-lock.yaml` is deliberately
**not** committed — `.github/workflows/pr.yml` regenerates it when a
manifest changes and shares it with downstream jobs as the `pr-lockfile`
artifact.

## Risks

- **Deleting `OnboardingWizard.tsx` is the one change that alters
existing app behaviour.** The cloud flow is intended to be
behaviour-equivalent, and its entry points are covered by the updated
`OnboardingWizardVariant` test, but this is the area to review most
closely.
- **Conflict risk with open PRs that touch the old wizard**: #9900,
#9501, #8982 and #6636 all modify
`ui/src/components/OnboardingWizard.tsx`, which this PR removes.
Whichever lands second will need its change re-applied to the new step
components. Flagging so ordering can be decided deliberately.
- **New dependencies**: `motion` and `three` (+ `@types/three`). `three`
is large, so it is lazily imported and code-split — it does not affect
the main bundle. Both are MIT.
- The **local flow is not reachable in the app** yet (harness/canary
only), so it carries no runtime risk today; wiring it up is a follow-up.
- The auth screens remain **presentational only** — they are not wired
to real auth, unchanged from before this PR.
- **Pre-existing, not introduced here:** `OnboardingWizardVariant`
renders outside `<Routes>` in `App.tsx`, so its `useParams()` never
resolves `:companyPrefix` and `/{prefix}/onboarding` opens the welcome
screen instead of jumping to the agent step. `master` has the identical
structure, so this PR faithfully ports existing behaviour; the working
"add an agent" entry is the launcher card behind the overlay, which is
what the screenshot spec drives. Worth a separate fix.

## Model Used

Claude Opus 5 (`claude-opus-5`) via Claude Code, with extended thinking
and tool use (repo search/edit, local test + build execution, and
browser-driven visual verification of the rendered flows). Portions of
the session also ran on `claude-opus-4-8` and `claude-fable-5`.

## 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 <noreply@anthropic.com>
2026-08-06 23:35:05 -07:00
scotttong b67c512f82
feat(ui): live run label → run detail, running row → task detail (#11034)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The agent detail page has a Dashboard tab. The Dashboard tab shows a
"Live Run" section for the agent's current heartbeat.
> - The "Live Run" section has two clickable pieces: the section heading
and the running row. Both pieces linked to the same run detail page.
> - Two controls that go to the same place waste a navigation affordance
and hide the task the agent runs.
> - This pull request splits the two destinations. The heading goes to
the run. The running row goes to the task.
> - The benefit is that a user reaches the run internals from the label
and the work item from the row, in one click each.

## Linked Issues or Issue Description

No public GitHub issue exists for this change. The description follows
the enhancement issue template.

**What existing behavior does this improve?**
The "Live Run" section on the agent detail page, Dashboard tab (the
`LatestRunCard` component in `ui/src/pages/AgentDetail.tsx`).

**Current behavior**
The "Live Run" heading and the running row both link to the run detail
page (`/agents/:agentId/runs/:runId`). The row shows the run code and an
invocation-source chip. There is a separate "View details →" link that
also goes to the run detail page. A user cannot reach the task the run
works on from this section.

**Proposed behavior**
The heading becomes a link to the run detail page and appends the short
run code, shown as `Live Run · <run code>`. The redundant "View details
→" link is removed. The running row links to the task detail page when
the run's context snapshot resolves to a known issue, and the row then
shows the task status glyph, the task slug, and the task title. A pure
timer heartbeat with no resolvable task keeps the previous behavior: run
code plus source chip, linking to the run detail page.

**Reason and benefit**
The heading and the row now go to distinct, intuitive destinations. A
user reaches the run internals from the label and the work item from the
row, each in a single click. The fallback keeps heartbeats with no task
readable and avoids a blank or broken row.

## What Changed

- Made the "Live Run" / "Latest Run" heading a `Link` to the run detail
page and appended the short run code (`run.id.slice(0, 8)`) in a mono
span, formatted `Live Run · <run code>`. Kept the pulsing live dot.
- Removed the redundant "View details →" link.
- Changed the running row `Link` target to the task detail page
(`/issues/:identifier`) when a task resolves, falling back to the run
detail page otherwise.
- Resolved the task from the run context snapshot
(`contextSnapshot.issueId`, falling back to `contextSnapshot.taskId`)
against a `Map` of the agent's assigned issues threaded in from
`AgentOverview`.
- When a task resolves, replaced the run code and source chip in the row
with the task status glyph (`StatusGlyph`), the task slug, and the task
title. Kept the running spinner, the run status badge, and the
timestamp.

## Verification

- `pnpm check:token-gates` → 3/3 gates clean.
- `pnpm --filter ui typecheck` → passes.
- `pnpm --filter ui exec vitest run
src/pages/AgentDetail.progress.test.ts
src/pages/AgentDetail.instructions.test.tsx` → 10/10 pass.
- Manual (needs a reviewer with a browser): open an agent detail page →
Dashboard tab.
- For a live issue-execution run: the heading reads `Live Run · <run
code>` and opens the run detail page; the row shows the task status
icon, slug, and title and opens the task detail page.
- For a pure timer heartbeat with no task: the row falls back to run
code + source chip and opens the run detail page. No blank row.
  - Confirm both states in light and dark mode.

## Risks

Low risk. The change is presentational and scoped to one component. The
task lookup is defensive: it reads the context snapshot with a fallback
key and only renders the task row when the issue is present in the
already-loaded assigned-issue set, so an unknown or missing issue
degrades to the previous run-detail behavior rather than breaking.

## Model Used

- Provider: Anthropic (Claude).
- Model: claude-opus-4-8 (Opus 4.8).
- Context window: 200K.
- Reasoning mode: extended thinking, tool use.

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 23:18:32 -07:00
Nicky Leach 4e76227f12
feat(plugin-daytona): stream session command logs behind useLogStream (#11021)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Sandbox provider plugins let agents run commands in remote
environments.
> - The Daytona provider polls the exit code while it waits for command
logs.
> - Polling delays log delivery and does not support long-lived streamed
commands.
> - This pull request adds an opt-in Daytona log stream with one
reconnect and a poll fallback.
> - The benefit is faster log delivery while the existing default path
stays unchanged.

## Linked Issues or Issue Description

Refs: #10941

**Subsystem affected**

packages/plugins — sandbox provider plugins.

**Problem or motivation**

The Daytona provider polls the command exit code every 50 milliseconds
while it waits for logs. This delays output and does not support a
long-lived streamed command.

**Proposed solution**

Add the `useLogStream` provider option. Stream stdout and stderr from
the Daytona callback log form, read the exit code after the stream ends,
retry the read with bounded backoff, and fall back to the existing poll
path after a disconnect.

**Alternatives considered**

Keep polling for all commands. This keeps the current behavior but does
not provide timely logs or a path for long-lived commands.

**Roadmap alignment**

This supports the roadmap item for cloud and sandbox agents, including
Daytona.

## What Changed

- Add the opt-in `useLogStream` option with a default of `false`.
- Stream stdout and stderr from the Daytona callback log form.
- Drop replayed log prefixes by delivered byte offset after reconnect.
- Retry once after disconnect, then use the existing poll path.
- Read the exit code once after a successful stream and retry when the
code is not ready.
- Add tests for ordered output, exit-code reads, disconnect fallback,
and reconnect replay handling.

## Verification

- Run `node_modules/.bin/vitest run --config
packages/plugins/sandbox-providers/daytona/vitest.config.ts
packages/plugins/sandbox-providers/daytona/src/plugin.test.ts`.
- Confirm that the full Daytona plugin test project passes with 127
tests.
- Confirm that the changed files type-check against Daytona SDK 0.203.0
types.
- Review the PR against parent PR #10941 before it reaches `master`.

## Risks

- The stream path changes behavior only when `useLogStream` is `true`.
- A stream failure can add one reconnect attempt before the existing
poll fallback.
- The stream path has no command deadline because it supports long-lived
commands.
- No endpoint, stored data, telemetry shape, authentication rule, or
result shape changes.

## Model Used

OpenAI Codex, GPT-5, tool use and code execution. The context window and
reasoning mode are not exposed by this run.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-06 22:56:21 -07:00
Dotta 9485ffea70
fix(config): preserve env files during managed updates (#10980)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The CLI and server both update Paperclip values in `.env` files
> - The server preserved operator content, but the CLI rebuilt the
complete file
> - A CLI rerun could remove comments, custom values, ordering, and
newline style
> - Both paths need one editor with one value encoding and duplicate key
policy
> - The final integration also needs one regression test across the
related setup and sync safety mechanisms
> - This pull request moves the editor to the shared package and adds
cross-cutting rerun-survival coverage
> - The benefit is safe setup and worktree repair reruns that preserve
operator edits

## Linked Issues or Issue Description

**What happened?**

The CLI rebuilt the complete `.env` file when it wrote a managed
Paperclip value. This action removed comments, blank lines, custom keys,
original quoting, and the original newline style.

**Expected behavior**

Paperclip must update only the managed assignments. It must preserve all
unrelated bytes. It must skip the file replacement when all managed
values are current.

**Steps to reproduce**

1. Add comments, custom keys, quoted values, and CRLF newlines to the
Paperclip `.env` file.
2. Run a CLI path that calls the agent JWT secret setup.
3. Observe that the old writer replaces the complete file.

**Paperclip version or commit**

The problem exists on `master` before this pull request.

Related public context: Refs #437.

## What Changed

- Add one shared line-preserving `.env` editor for the CLI and server.
- Define minimal and JSON value encodings in the shared helper.
- Update every stale duplicate of a managed key and preserve current
duplicate encodings.
- Preserve comments, ordering, blank lines, unknown keys, export
prefixes, trailing comments, and newline style.
- Write changed files through a same-directory temporary file and atomic
rename.
- Limit CLI updates to non-empty `PAPERCLIP_*` entries.
- Skip the write when all managed values are current.
- Add shared, CLI, and server regression coverage.
- Refresh the branch after the related config, sandbox, and skill safety
changes landed.
- Add a cross-cutting integration test for config, env-file,
managed-sandbox, and managed-instructions rerun survival.

## Verification

- `pnpm exec vitest run packages/shared/src/env-file.test.ts
packages/shared/src/config-schema.test.ts
cli/src/__tests__/agent-jwt-env.test.ts
cli/src/__tests__/config-store.test.ts
server/src/__tests__/config-file.test.ts
server/src/__tests__/worktree-config.test.ts` passes 39 tests.
- `pnpm exec vitest run
server/src/__tests__/rerun-survival.integration.test.ts` passes 4 tests.
- `pnpm -r typecheck` passes on the previous head. GitHub CI reruns it
on the refreshed head.
- The previous head passed the complete general, serialized, workspace,
and E2E matrix. GitHub CI reruns that matrix on the refreshed head.
- `pnpm build` passes on the previous head. GitHub CI reruns it on the
refreshed head.

## Risks

- Low risk. The production change only affects managed `.env`
assignments.
- Existing managed assignments can keep their original quoting when
their decoded values are current.
- Changed CLI values keep the prior minimal encoding policy. Changed
server values keep the prior JSON encoding policy.
- Duplicate managed assignments now follow one explicit rule: Paperclip
updates each stale occurrence.
- The master refresh had one import-block conflict. The resolution keeps
both the config merge imports and the env-file imports.
- The added integration file is test-only. It has no database, API, or
UI contract effect.

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

## Model Used

OpenAI Codex from the GPT-5 family produced this change with reasoning,
tool use, and code execution. The runtime did not expose the exact model
ID or 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-08-07 00:54:43 -05:00