## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - E2B is a sandbox provider plugin that runs agent code in isolated
cloud environments
> - Operators configure E2B through the plugin settings page
> - But the E2B API key configuration was unclear — the settings field
description didn't explain that pasted keys are auto-saved as company
secrets, and the fallback to the host `E2B_API_KEY` variable wasn't
documented
> - Additionally, long-running E2B sandbox commands were timing out
because the plugin environment RPC driver used a fixed timeout, and
environment commands competed for the single foreground command slot
> - This PR clarifies the E2B configuration UX, fixes RPC timeouts for
plugin environment execution, and runs E2B environment commands in
background mode to avoid blocking the foreground slot
> - The benefit is clearer E2B setup for operators and more reliable
sandbox command execution
## What Changed
- Updated E2B plugin manifest and settings UI to clarify API key
configuration — field description now explains that pasted keys are
saved as company secrets and documents the `E2B_API_KEY` host fallback
- Added test coverage for the plugin settings page rendering
- Fixed `plugin-environment-driver.ts` to pass the configured timeout
through to RPC calls instead of using a hardcoded default
- Updated `environment-runtime.ts` to propagate timeout from the
environment lease to the plugin driver
- Changed E2B sandbox command execution to use background handles so
long-running agent commands don't block the foreground slot needed by
the callback bridge
## Verification
- `pnpm test` — all existing and new tests pass
- `pnpm typecheck` — clean
- Manual: navigate to plugin settings, verify E2B API key field shows
the updated description text
- Manual: run an E2B-backed agent task with a long-running command,
verify it completes without RPC timeout
## Risks
- Low risk. Configuration UX change is cosmetic. The timeout fix passes
an existing value through instead of dropping it. Background command
execution is a behavioral change but only affects E2B sandbox commands —
the foreground slot is still available for bridge health checks.
## Model Used
Codex GPT 5.4 high via Paperclip.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents can run inside sandboxed environments like E2B, which are
isolated from the host network
> - Sandboxed agents need to call back to the Paperclip API to report
progress, post comments, and update issue status
> - But sandbox environments cannot reach the Paperclip server directly
because they run in isolated network namespaces
> - This PR adds a callback bridge that proxies API requests from the
sandbox to the Paperclip server, running as a local HTTP server on the
host that forwards authenticated requests
> - The bridge is started automatically when an adapter launches a
sandbox execution, and torn down when the run completes
> - The benefit is sandboxed agents can interact with the Paperclip API
without requiring network-level access to the host, enabling E2B and
similar providers to work end-to-end
## What Changed
- Added `sandbox-callback-bridge.ts` in `packages/adapter-utils/` — a
lightweight HTTP bridge server that accepts requests from sandbox
environments and proxies them to the Paperclip API with authentication
- Added request validation and security policy: the bridge only forwards
requests to the configured API URL, validates content types, enforces
size limits, and rejects non-API paths
- Wired the bridge into all remote adapter execute paths (claude, codex,
cursor, gemini, pi) — the bridge starts before the agent process and the
bridge URL is passed via environment variables
- Updated `environment-execution-target.ts` to prefer the explicit API
URL from environment lease metadata for sandbox callback routing
- Fixed Claude sandbox runtime setup to work with the bridge
configuration
- Added comprehensive test coverage for bridge request handling, policy
enforcement, and sandbox execution integration
- Fixed browser bundling — the bridge module is excluded from the
frontend bundle via the adapter-utils index export
## Verification
- `pnpm test` — all existing and new tests pass, including bridge unit
tests and sandbox execution integration tests
- `pnpm typecheck` — clean
- Manual: configure an E2B environment, run an agent task, verify the
agent can post comments and update issue status through the bridge
## Risks
- Medium. This is a new network-facing component (HTTP server on
localhost). The security policy restricts forwarding to the configured
API URL only and validates all requests, but any proxy introduces attack
surface. The bridge binds to localhost only and is scoped to the
lifetime of a single agent run.
## Model Used
Codex GPT 5.4 high via Paperclip.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents run inside environments that are leased, and the server
manages runtime state, workspace configuration, and plugin lifecycle
> - Several edge cases caused failures during concurrent operations: a
race condition in runtime state insertion could produce duplicate-key
errors, reused workspaces didn't sync their configuration when the
parent issue was updated, sandbox provider plugins could be queried
before registration completed, and orphaned environment leases from
failed runs were never released
> - This PR fixes these four runtime/environment issues
> - The benefit is more reliable concurrent agent execution and proper
resource cleanup
## What Changed
- `services/heartbeat.ts`: Fixed a race condition where concurrent
runtime state inserts could fail with a duplicate-key error by using an
upsert pattern
- `services/issues.ts`: Sync reused workspace configuration when an
issue is updated, so the workspace reflects the latest issue state
- `services/environment-runtime.ts`: Fixed a startup race where sandbox
provider plugins could be queried before registration completed, by
awaiting plugin readiness before resolving environment drivers
- `services/heartbeat.ts`: Release environment leases for orphaned runs
that lost their process without cleanup
## Verification
- `pnpm test` — all existing and new tests pass, including new tests for
runtime state upsert and process recovery lease cleanup
- `pnpm typecheck` — clean
- Manual: trigger concurrent agent runs to verify no duplicate-key
failures; verify orphaned leases are released after process loss
## Risks
- Low risk. The runtime state upsert changes insert-to-upsert behavior,
which could mask a legitimate duplicate if two different runs produce
the same key — but this is prevented by the run ID being part of the
key. The plugin startup await is bounded by the existing registration
timeout.
## Model Used
Codex GPT 5.4 high via Paperclip.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents can run inside sandboxed environments like E2B, or on remote
hosts via SSH
> - The cursor adapter needs to resolve `cursor-agent` inside sandbox
environments where it's installed in `~/.local/bin`
> - But when using the default `agent` command on a sandbox target, the
adapter didn't know to look in `~/.local/bin/cursor-agent`, causing
"command not found" failures
> - Additionally, repeated SSH runs failed because `git checkout` during
workspace sync conflicted with leftover `.paperclip-runtime` files from
previous runs
> - This PR adds sandbox-aware command resolution for cursor and fixes
the SSH workspace sync conflict
> - The benefit is cursor works in E2B sandboxes out of the box, and
repeated SSH runs don't fail on workspace sync
## What Changed
- `cursor-local`: Added `prepareCursorSandboxCommand` — on sandbox
targets, reads the remote `$HOME`, prepends `~/.local/bin` to PATH, and
prefers `~/.local/bin/cursor-agent` when the default command is
requested; tightened the sandbox command probe to validate the binary
exists before launching; preserves explicit custom command overrides
- `adapter-utils/ssh.ts`: Added `--force` to git checkout in SSH
workspace sync to handle `.paperclip-runtime` untracked file conflicts
from previous runs
## Verification
- `pnpm test` — all existing and new tests pass, including cursor
sandbox probe, sandbox execution, and custom command override tests
- `pnpm typecheck` — clean
- Manual: configure an E2B environment, run a cursor-local task, verify
it resolves cursor-agent from the sandbox install path
## Risks
- Low-medium. The `--force` flag on git checkout could discard
uncommitted changes in the remote workspace, but the workspace is
managed by Paperclip and should not contain user edits.
## Model Used
Codex GPT 5.4 high via Paperclip.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Paperclip is distributed as npm packages, including plugins like
`plugin-e2b`
> - The release process publishes canary and stable builds via npm
dist-tags
> - But there was no automated verification that published packages
actually landed with the correct dist-tags, and broken canary publishes
could silently ship to users
> - This PR adds a registry verification script that checks published
packages match their expected dist-tags, and wires it into PR CI so
regressions are caught before merge
> - The benefit is release integrity is verified automatically, and
broken dist-tag states are caught early
## What Changed
- Added `scripts/verify-release-registry-state.mjs` — verifies that
published npm packages have correct dist-tag assignments and detects
orphaned or mispointed tags
- Added `scripts/verify-release-registry-state.test.mjs` — test coverage
for the verification logic
- Updated `scripts/release.sh` to include canary dist-tag safety checks
before publishing
- Updated `.github/workflows/pr.yml` to run registry verification as a
CI step
- Updated `doc/PUBLISHING.md` and `doc/RELEASING.md` with the new
verification workflow
## Verification
- `pnpm test` — all tests pass including new verification script tests
- `node scripts/verify-release-registry-state.mjs` — runs against the
live npm registry and reports current state
- CI: the new PR workflow step runs on every PR push
## Risks
- Low risk. This is additive CI and tooling — no runtime code changes.
The registry verification is read-only (queries npm, does not publish).
The release script changes add safety checks that abort before
publishing if state is unexpected.
## Model Used
Codex GPT 5.4 high via Paperclip.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents can run on remote hosts via SSH environments
> - When a remote agent needs to call back to the Paperclip API, it
needs a reachable URL
> - But the runtime API URL candidate builder did not account for
private network topologies where the server is only reachable via LAN or
VPN addresses
> - Agents on SSH hosts were failing to connect because the callback URL
pointed to localhost or an unreachable address
> - This PR fixes callback URL selection to honor `PAPERCLIP_API_URL`,
prefer LAN-reachable candidates, filter unreachable link-local
addresses, and include interface hosts in onboarding invite URLs
> - The benefit is SSH-based agents can reliably reach the Paperclip API
on private networks without manual URL configuration
## What Changed
- `runtime-api.ts`: Added `PAPERCLIP_API_URL` as a first-priority
candidate in `buildRuntimeApiCandidateUrls`; extracted
`collectReachableInterfaceHosts` to enumerate non-loopback,
non-link-local network interface IPs with IPv4 preference
- `server/src/index.ts`: Export `PAPERCLIP_API_URL` from the server
environment so it is available to callback candidate resolution
- `server/src/routes/access.ts`: Include LAN interface hosts in
onboarding invite connection candidates
- `server/src/config.ts`: Attempted auto-allowing LAN interface hosts,
then reverted to the per-instance allowlist approach (both commits
included for history clarity)
## Verification
- `pnpm test` — all existing and new tests pass, including new tests for
LAN candidate ordering and link-local filtering
- `pnpm typecheck` — clean
- Manual: start a Paperclip server on a machine with a LAN IP, create an
SSH environment pointing to another host on the same LAN, verify the
agent's callback URL uses the LAN IP rather than localhost
## Risks
- Low-medium. The candidate list now includes more addresses (all
non-loopback LAN interfaces). These are candidates for the agent to try,
not an allowlist — the server's allowed hostnames still gate which
origins are accepted. Ordering change (LAN preferred over loopback)
could affect existing setups where localhost was intentionally
preferred.
## Model Used
Codex GPT 5.4 high via Paperclip.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents run inside environments (local, SSH, E2B sandbox)
> - Operators need to configure and manage these environments
> - But environment settings were buried inside the general company
settings page, making them hard to find
> - Additionally, when testing an agent from the configuration form, the
test always ran locally regardless of which environment was selected
> - This PR moves environments into a dedicated top-level company
settings section and wires the "Test Environment" button to run inside
the selected environment
> - The benefit is operators can find and manage environments more
easily, and the test button now validates the actual environment the
agent will use
## What Changed
- Added a dedicated `CompanyEnvironments` settings page with its own
route and sidebar entry
- Updated `CompanySettingsSidebar` and `CompanySettingsNav` to include
the new environments section
- Modified the agent test route (`POST /agents/:id/test`) to accept an
optional `environmentId` parameter
- Updated all adapter `test.ts` handlers to resolve and use the
specified execution target environment
- Added `resolveTestExecutionTarget` to `execution-target.ts` for remote
environment test resolution with cwd fallback
- Moved the "Test Environment" button and its feedback display into the
`NewAgent` page footer for better UX flow
## Verification
- `pnpm test` — all existing and new tests pass
- `pnpm typecheck` — clean
- Manual: navigate to Company Settings, confirm "Environments" appears
as a top-level section
- Manual: configure an agent with a non-local environment, click "Test
Environment", confirm the test runs inside that environment
## Risks
- Low risk. UI-only routing change for the settings page. The
test-in-environment change adds an optional parameter with a local
fallback, so existing behavior is preserved when no environment is
specified.
## Model Used
Codex GPT 5.4 high via Paperclip.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Per visual feedback the menu was sitting flush against the cursor;
introduce MENTION_MENU_CARET_GAP (10px) so the popover floats roughly
one space-width to the right of the caret.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Default the legacy execution-disposition preview to recently created issues, keep an explicit --all escape hatch, and cover the lookback behavior with a targeted test.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Default the legacy execution-disposition preview to a 48-hour lookback and require --all for full historical scans. This keeps repair passes from treating stale blocked/review rows as current cleanup work unless the operator explicitly asks for a backfill.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The board accepted the canonical executionDisposition payload + classifier
but found the visible badges in IssueRow / IssueProperties / Storybook too
noisy on production surfaces. UI-only walkback: keep the data layer, drop
every visible badge.
- Delete IssueDispositionBadge component and its test.
- Drop the badge import, suppression rule, and trailing-slot references in
IssueRow (desktop + mobile). Existing waiting pill, status-icon ring, and
blockerAttention copy remain unchanged.
- Drop the badge IIFE and related imports from IssueProperties so the
Status row only renders the StatusIcon again.
- Prune the badge cases from IssueRow.test (Live, Stalled, dispatchable,
Resuming, explicit-waiting suppression) so the file still type-checks.
- Storybook: remove "Canonical disposition badges" and "IssueRow with
disposition badge inline" sections plus DispositionRowSurface and
dispositionRowFixtures. The rest of Status Language is unchanged.
Kept: executionDisposition on issue payloads, the resolver and its test,
and the PAP-2818 contract doc — future features can consume the field
without re-running the classifier work.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Align CSS max-height with MENTION_MENU_HEIGHT (208) so the rendered
menu fills the layout-reserved space; previously max-h-[200px] clipped
the box 8px short of the JS layout budget, leaving a visible gap above
the caret line on the flipped path.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Drop the dispatchable "Ready" variant from the badge: the only place it ever
rendered was Storybook (IssueRow and IssueProperties already returned null
for it). dispositionCategory now returns null for dispatchable.
- Pull agent_continuable into a discrete "Resuming" category with a muted
indigo treatment so it doesn't share the rose alarm family with Recovery /
Needs attention / Stalled, and surface "Resuming · n/m" on the visible
badge instead of hiding the attempt counter in the tooltip.
- Differentiate human_escalation_required by owner on the visible label:
Needs board / Needs manager / Needs owner / Needs external. Color stays in
the rose family but the actionable bit is no longer hover-only.
- Bump invalid (Stalled) to a solid rose-600 fill so it stands out against
the rest of the rose family at scan distance, satisfying the parent plan's
acceptance criterion that the board can spot a stalled review leaf without
reading the full thread.
- Mirror the IssueRow suppression rule on IssueProperties so the Status row
doesn't double-render the same waiting/recovery signal as the chat-thread
IssueBlockedNotice.
- Add aria-label={tooltip} on the badge so screen readers get the path
detail, not just the category label.
- Storybook: rename and split the disposition matrix to cover all four
escalation owners, both Resuming attempts, and add a populated IssueRow
surface that shows the badge in production layout context (Live, Resuming,
Needs board, Needs manager, Stalled).
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Mention menu now sits on the same line as the caret and to the right of
the cursor (tracking each typed character instead of staying glued to
the @ marker). When the menu would overflow below the viewport it flips
above the line so it doesn't cover the line.
Touch handling no longer preventDefaults on touchstart, so the dropdown
scrolls natively on mobile; selection runs only on touchend if the
finger lifted with negligible movement (a tap, not a scroll).
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Adds IssueDispositionBadge that maps the canonical disposition to a
small palette of categories (live, ready, waiting, blocked-chain,
recovery, needs attention, stalled). Renders the badge in the inbox
row and on the issue detail status row, suppressing it where the
existing waiting/recovery copy already conveys the same signal so we
don't double up. Storybook gains a section showing every category for
UX review.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Adds a per-issue read-model resolver that loads the disposition state
vector (active runs, queued wakes, agents, interactions, approvals,
recovery/productivity-review issues, latest run evidence) and runs the
shared classifier. Wires it into issue list, detail, and
heartbeat-context responses, and surfaces it on the shared Issue type
as `executionDisposition` so the board can read execution health
without inferring it from status alone.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The explicit-wait IssueBlockedNotice rendered Target/Owner/Resume as a row
of pills. Drop the Target pill entirely and render the remaining Owner and
Resume entries as a plain dl/dt/dd block — the value matters more than the
chip styling for these long-form details.
The "Jump to questions/confirmation/approval" affordance referenced anchor
ids that did not exist in the DOM (`issue-thread-interaction-{id}` and
`issue-approval-{id}`); the actual anchors are `interaction-{id}` and
`approval-{id}`. Switch to those ids and use a button with
scrollIntoView({behavior:"smooth"}) so the jump actually scrolls to the
target.
Add a small ExplicitWaitingJumpAnchor component that flips the arrow ↑/↓
based on whether the target is above or below the banner, recomputing on
scroll/resize.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The Target/Owner/Resume chips in the explicit-wait IssueBlockedNotice
banner used inline-flex with `truncate max-w-[Nrem]` on inner spans, so
on narrow viewports a single chip (especially Resume's full sentence)
extended past the container edge. Cap each chip at max-w-full and let
the value span wrap on word boundaries; desktop layout is unchanged.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Wraps fenced code blocks in MarkdownBody with a relative container and an
absolute-positioned Copy button (top-right) that copies the block contents
to the clipboard via navigator.clipboard with an execCommand fallback for
non-secure contexts. The button is hidden by default and reveals on hover
or focus, swaps to a check icon and "Copied\!" label for ~1.5s after
clicking, and falls back to "Copy failed" when the clipboard write throws.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Replaces the rose liveness-break banner for issues whose blocker-attention
read model reports `state="covered"` with `reason="explicit_waiting"`.
- IssueBlockedNotice: add a sky-toned banner with Hourglass icon, kind-aware
headline/body copy (plan confirmation, user response, suggested tasks,
board approval, generic fallback), Target/Owner/Resume pill row, a "Jump
to confirmation" anchor, and a sky chip strip for parent-of-leaf chain
waits. Rose banner still wins on tie via the existing precedence.
- StatusIcon: add a sky ring + bottom-right nub for explicit_waiting and
emit `data-blocker-attention-state="explicit_waiting"`. Tooltip is
"Waiting on board/user/agent" or prefixed "Blocked · ..." when blocked.
- IssueRow: add a sky `Waiting · {owner}` pill (collapses to `Waiting` on
mobile) when the row's attention reason is explicit_waiting. Owner label
resolves via new optional agent/user maps wired in by IssuesList & Inbox.
- IssueDetail/IssueChatThread: lift the linkedApprovals query into the chat
tab and pass interactions/approvals/agentMap/userLabelMap/currentUserId
through to the new banner.
- Storybook + tests: extend coveredBlockedMatrix with sky cells, add four
new BlockedNotice fixtures (plan, user response, board approval, chain),
and cover the new states with unit tests on StatusIcon, IssueBlockedNotice,
and IssueRow.
Co-Authored-By: Paperclip <noreply@paperclip.ing>