Commit Graph
1098 Commits
Author SHA1 Message Date
Priya RamanandPaperclip e26b917971 fix(sandbox): enforce the duplex readiness cap on every pre-READY path and tolerate PTY noise
Enforce the O(1) readiness-buffer cap on every pre-READY path. Before this
change the gate checked the cap only on the no-newline branch. An over-cap blank
prefix followed by a valid nonce-bound READY frame in one chunk reached READY
acceptance without a cap check. The gate now rejects when the cursor passes the
cap after it skips a line, and before it decodes a completed candidate line. The
cap check has priority over READY acceptance on every path. A cap breach settles
protocol contamination and falls back to the file bridge with bounded cleanup.

Make the readiness gate tolerant of pre-READY noise. A PTY channel echoes the
launch wrapper line before it sets raw mode, so the first line is often not the
READY frame. The gate now skips each pre-READY line that does not decode as a
READY frame, then accepts the first line that decodes as a READY frame with the
matching nonce. A READY frame with a wrong nonce still fails the handshake. The
skipped bytes stay in the capped buffer, so the cap and the timeout still bound
the wait, and the newline scan stays linear in the bytes received.

Fix the gateway launch command. A POSIX shell rejects an environment-assignment
prefix on exec, so exec NAME=value command exits 127 and the gateway never
starts. Use exec env NAME=value command, which keeps the exec-replaces-shell
property and passes the assigned environment.

Add fallback_reason to the channel-open span on the fallback path only.
fallback_reason is an existing closed dimension key, so no new key reaches a sink.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 20:24:36 +00:00
Priya RamanandPaperclip 49bb13dc23 fix(sandbox): make the pre-READY duplex blank-line scan incremental
The pre-READY readiness gate scanned for newlines incrementally, but the
blank-line branch was not incremental. Each leading blank line reset the
search to offset zero and copied the remaining buffer, so one untrusted
chunk of blank lines plus one invalid line drove quadratic CPU work in the
pre-authentication gate.

Keep an advancing line-start cursor and a scan cursor over an append-only
buffer. Skip a blank line by moving the cursors past the newline, without a
buffer copy and without a search restart. Slice only the single candidate
line for the decode. Each code unit is read at most one time.

The scan counter now records the real indexOf distance, so a test can prove
the blank-line path stays linear. Add a regression that sends one chunk of
15000 blank lines and an invalid line, and asserts the scan work is within
4x the bytes received and the contaminated fallback and cleanup happen.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 19:44:39 +00:00
Priya RamanandPaperclip 65a8cecf31 fix(sandbox): make the pre-READY duplex newline scan incremental
The pre-READY readiness gate searched the full accumulated buffer with
buffer.indexOf("\\n") for every chunk. String#indexOf is linear in the
searched string, so many small newline-less chunks made the cumulative
scan work quadratic in the bytes received before the cap fired. An
untrusted provider could monopolize a host worker CPU.

Record the buffer length before each append and search from that offset
with buffer.indexOf("\\n", from). A newline can only be inside the new
chunk, so the search is correct and the total scan work is linear. Reset
the offset to 0 after the blank-line skip consumes a line.

Add a regression test that counts the scan work through a test-only seam
and asserts it stays proportional to the bytes received. The O(1) buffer
cap, the protocol_contamination result, the settled byte-drop guard, the
nonce check, and the file-bridge fallback stay unchanged.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 19:30:16 +00:00
Priya RamanandPaperclip a33d2aaae9 fix(sandbox): bound the pre-READY duplex buffer cap check in O(1)
The duplex readiness gate checked the pre-READY buffer cap with
Buffer.byteLength, which is O(n) in the buffer length. A provider that
controls the chunk size could send many small newline-less chunks, so the
per-chunk byte scan made the pre-READY window quadratic in the bytes
received. This was a CPU-exhaustion path on channel open.

Gate on buffer.length, the UTF-16 code-unit count, which is O(1). The UTF-8
byte length is greater than or equal to the UTF-16 code-unit count for every
string, so the check never fires before the byte cap is truly exceeded. It
can fire late by at most a factor of 3, so peak buffer memory stays bounded
near 3 MB. The failure reason and every other gate behavior stay unchanged.

Add a test that feeds many small newline-less chunks past the cap and asserts
the gate finishes with protocol contamination.

Correct one telemetry README sentence. Counters carry no dimension labels;
fallback_reason and loss_class fold into the counter metric name. The full
closed dimension set rides only the spans and the transport event.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 17:53:24 +00:00
Priya RamanandPaperclip 8e26e17408 feat(sandbox): stream duplex run logs and bind the duplex telemetry recorder
Stream sandbox run logs on the duplex transport with the same gate and the
same log line as the file transport. The duplex path starts no file bridge
worker, so the host creates the log directory before the tail starts. The
step is best effort and never tears down a working duplex transport.

Bind the fixed duplex telemetry surface to real host sinks. The host builds
one recorder per run: the spans go to the OTel tracer, the guarded counters
go to the tool-runtime metric store, and the transport event goes to the
run-event path. Each sink runs guarded and fire-and-forget, so a telemetry
failure never breaks the request path. The server attaches the recorder to
the sandbox target next to the runner; a run with no recorder keeps the safe
no-op default. Update the telemetry README to name the counter store file.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 17:20:28 +00:00
Priya RamanandPaperclip 2215416898 fix(sandbox): harden the duplex transport before the bridge merges
Bound the pre-READY readiness buffer, apply the header allowlist on the
host duplex forward path, derive the nested broker budgets from the
forward budget, and remove a dead cleanup branch.

- The readiness gate caps the pre-READY buffer at the codec frame-size
  bound plus one line of margin. Past the cap with no newline, the gate
  finishes with protocol contamination. The gate drops later bytes after
  it settles, so a failed handshake never grows the buffer.
- The broker forward callback applies the host header allowlist next to
  the route check, so a frame header outside the allowlist never reaches
  the authenticated fetch. The sandbox-side filter stays a convenience.
- The broker budgets derive the response budget and the gateway wait
  budget from the forward budget, so any forward budget keeps the nested
  order. The host constructs the broker inside the guarded region, so a
  construction throw closes the channel and selects the file bridge.
- The channel-open catch block drops the dead `if (channel)` branch,
  because the open call is the last statement of the try.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 16:56:40 +00:00
Priya RamanandPaperclip f09d5ba416 fix(sandbox): make a pre-response duplex forward failure non-retryable for a mutating method
The duplex broker forwarded each request and mapped a forward rejection to a
retryable 502 when its controller was not aborted. A fetch can reject after the
request bytes reach the host and the host commits a mutation, but before the
response headers arrive. A retry then sent a new request id outside the broker
deduplication set, so the host applied the mutation twice.

The broker now classifies a non-aborted forward rejection by method safety. A
safe method (GET, HEAD, OPTIONS, TRACE) never changes host state, so its
rejection stays a retryable 502. Any other method may have committed, so the
broker returns a non-retryable 504 and marks the outcome indeterminate. The
in-sandbox gateway maps the indeterminate outcome to a terminal 409, so a caller
never repeats a possibly-committed mutation. Add broker coverage for both paths.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 06:28:17 +00:00
Priya RamanandPaperclip d68ef2a41a docs(telemetry): document the sandbox duplex transport contract
The duplex transport adds spans, one event, counters, and dimensions in
duplex-telemetry.ts. The generated telemetry contract does not cover them, so
the telemetry README is their canonical contract. Add a section for the span,
event, and counter names, the closed dimension-key set with its enum values, and
the opt-in and no-user-content rules.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 06:19:27 +00:00
Priya RamanandPaperclip d0c4968e97 fix(sandbox): make a post-commit response-read failure non-retryable
The bridge forward reads the host response body after the fetch resolves. A
mutating request may have committed on the host by then. A body over the size
limit, or a stream read error, made the forward reject. The duplex broker and
the file bridge both mapped that rejection to a retryable 502. A retry then sent
a new request id outside the broker deduplication set, so the host applied the
mutation twice.

The forward now catches a response-body read failure and returns a non-retryable
504 with the indeterminate outcome marker. The in-sandbox server maps the
indeterminate 504 to a non-retryable 409 for both transports, so a caller never
repeats a possibly-committed mutation. Add broker and integration coverage.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 06:19:27 +00:00
Priya RamanandPaperclip 8036019807 fix(sandbox): bound the duplex request-id bytes in the codec and the broker
The duplex frame codec accepted a request or a response `id` of any length up
to the whole-frame cap. The host broker keeps one id per distinct dispatched
request for the no-replay guarantee. A provider that controls the transport
could send distinct near-frame-limit ids and force the host to retain many
gigabytes before the lifetime count ceiling, so the host adapter process could
exhaust memory.

Add a finite protocol bound for the id field:
- Export `DEFAULT_MAX_DUPLEX_REQUEST_ID_BYTES` (256 bytes). The generated
  gateway builds each id with `randomUUID()` (36 bytes), so this bound gives
  large headroom and caps the retained bytes per id.
- Reject an over-limit id as an `id_too_large` protocol error in both codec
  copies (the host codec and the embedded gateway codec). The read path returns
  the error; it never throws. The whole-frame cap stays unchanged.
- Enforce the id bound in the broker before the seen set, the pending record,
  and the forward, as defense in depth. The refusal allocates no controller,
  no timer, and no forward, and records no telemetry.
- State the worst-case retained id bytes at the lifetime-limit definition
  (50,000 * 256 = 12,800,000 bytes).

Add shared fixture vectors for a maximal-allowed id and an over-limit id, so
both codec copies prove the same bound. Add regression tests for the flood,
the no-replay path, and the byte-not-character measure.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 05:40:14 +00:00
Priya RamanandPaperclip fc1f74131e test(sandbox): prove the duplex broker request limits
Inject more than the limit of unique valid frames straight into the broker
through an in-memory channel, the same as a malicious provider. The tests prove:
no forward past the limit, bounded pending and retained-id bookkeeping, orderly
bounded 503 responses with no route, query, body, or token, a freed slot that
forwards a resent refused id one time, and an unchanged loss and telemetry
redaction surface.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 05:21:28 +00:00
Priya RamanandPaperclip 0d728d0f45 feat(sandbox): bound untrusted duplex broker frame concurrency and id retention
The provider controls the duplex transport directly, so each request frame is
untrusted. The broker now enforces two finite, fail-closed limits before it adds
the id to the seen set, allocates the pending record, or calls the forward
handler:

- an in-flight limit that bounds the controllers, the timers, and the concurrent
  authenticated forwards.
- a lifetime limit that bounds the retained request-id memory.

On a limit the broker answers the refused request with one bounded terminal 503
response and forwards nothing. An in-flight refusal is retryable and keeps the id
out of the seen set, so a resend passes after a slot frees. A lifetime refusal is
terminal. The refusal preserves the no-replay and no-double-dispatch rules and
records no telemetry, so the fixed observability surface stays unchanged.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 05:21:27 +00:00
Priya RamanandPaperclip 50c9c10a80 test(sandbox): pin the exact fixed duplex dimension-key set
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 05:04:27 +00:00
Priya RamanandPaperclip 767d12c166 test(sandbox): prove the live duplex provider metrics and fix the channel command argument
Pass the channel command as an argument list, so the live launch wrapper builds
the raw-mode wrapper instead of failing on a string. The live test now runs when
the provider credential is present.

Add two live cases against the real provider. The first sends repeated channel
round trips over one open channel, measures the round-trip latency, and asserts
the provider session count never rises: zero idle polls and zero per-request
session growth. The second forces a mid-flight disconnect and asserts no leaked
session and a still-usable sandbox.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 05:03:12 +00:00
Priya RamanandPaperclip c881bc460c feat(sandbox): add fixed duplex observability with a redacted telemetry surface
Add the closed telemetry contract for the sandbox duplex transport: the two
fixed spans, the four guarded counters, and the one transport event. A new
provider-scoped facade maps each record to the fixed names and dimension keys.
It maps the raw provider key through a one-value allowlist before every sink, so
a plugin key never reaches a span, a counter, or the event; a non-approved key
maps to the constant other.

Wire the facade into the transport selection path and the broker. The selection
path records the channel-open span, the fallback counter, and the transport
event, and it maps each readiness failure to a fixed fallback reason. The broker
records one request span with latency per delivered request and one loss record
per terminal loss, classified as pre_dispatch or post_dispatch.

Keep the per-request debug output off the duplex path: suppress the route and
query proxy logs and pass no free-form logger to the broker, so no route, query,
body, token, or raw provider error rides a log line, a span, an event, or a
counter. Every recorder call sits inside an error swallow, so a telemetry
failure never breaks the request path. The default recorder is a no-op, so the
surface stays inert until the host binds a real recorder.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 04:57:22 +00:00
Priya RamanandPaperclip 43f9d3c4a2 feat(sandbox): wire the duplex bridge kill switch to the transport selection callers
Carry the per-run duplex bridge kill switch from the instance setting to
every caller of startAdapterExecutionTargetPaperclipBridge. The host reads
readSandboxDuplexBridgeInput() and stamps enableSandboxDuplexBridge on the
sandbox target, on the same seam as effectiveCapabilities. The value stays on
the host and never enters the sandbox environment.

The stamp fails closed. An absent runtime, an absent read method, a read
error, or any value other than true keeps the file bridge. The adapter-side
spec parse reads the boolean and treats an absent or non-boolean field as no
grant. The acpx engine and the six local adapter callers pass the parsed value
into the bridge, so the two-gate selection is now reachable.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 04:33:31 +00:00
Priya RamanandPaperclip 3e8d9d6f6b test(sandbox): add the local real-process duplex end-to-end harness
Spawn the generated gateway with plain node from a temporary file, attach
the real host broker to the child stdin and stdout, and forward each
request to a local fake API server on a real HTTP call. The harness needs
no provider credentials and adds no dependency.

The harness proves the composed duplex path over real kernel pipes:
- The child sends a valid liveness-only READY frame to the broker.
- One HTTP request travels through the child and the broker to the API and
  the response body returns unchanged.
- A large response spans many pipe chunks and returns complete.
- A multi-byte UTF-8 sequence split across chunk borders decodes correctly.
- Concurrent requests through one child each receive the matching response.
- A real stdin EOF makes the gateway answer 409 outcome_indeterminate to
  every outstanding request and 503 bridge_unavailable to every new one.
- A real process kill moves the broker to lost, stops all dispatch, and
  leaves no open pipe or socket handle after teardown.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 03:21:45 +00:00
Priya RamanandPaperclip aa55a49370 feat(sandbox): select the duplex transport behind two gates with a liveness-only readiness contract
Select the duplex transport only when the experimental setting
enableSandboxDuplexBridge is exactly true and the resolved capability
duplexCommandStream is exactly true. Any other value of either gate
selects the file bridge.

The host assigns the loopback port and one random per-open nonce, and
passes both to the gateway only through the launch environment. The host
builds the sandbox-facing origin http://127.0.0.1:<assigned port> from
its own stored port. No field of any channel frame contributes to the
endpoint. Change the READY frame from an address string to a strict
version-plus-nonce schema in the host codec, the embedded gateway codec,
and the shared fixture; the gateway echoes the nonce and sends READY only
after it binds the assigned port. The gateway binds exactly that port or
writes a diagnostic and exits nonzero.

Readiness fails closed. On a mismatched nonce, an address-bearing or
incomplete READY frame, protocol contamination before READY, a bind
failure, or a timeout, the host closes the partial channel inside a
bounded budget and selects the file bridge, and leaves no live provider
session. The broker starts only after readiness passes, so no request
that carries the bridge token reaches a rejected endpoint. Teardown
closes the channel before lease release.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 02:57:40 +00:00
Priya RamanandPaperclip cf0d2b823a feat(sandbox): add the host duplex bridge broker with nested budgets
Add the host broker for the sandbox duplex channel. The broker reads
request frames from one persistent channel, forwards each request on the
existing API path, and writes one response frame back. It sends heartbeat
frames on an interval to prove liveness.

The broker keeps the forward path unchanged. The caller passes a forward
handler, and the broker calls it for each request. The handler owns the
route allowlist, the token replacement, and the run attribution, so those
rules stay in one place.

The broker runs nested timeout budgets: forward 30 s, response 32 s, and
gateway wait 35 s. Each inner budget is smaller than its outer budget. The
broker asserts this order at construction and rejects a configuration that
breaks it.

Loss is terminal. The broker detects loss through channel exit, a stream
write failure, a protocol failure, a heartbeat write failure, and a close
timeout. On loss the broker stops, records the loss for metrics only, and
dispatches nothing more. It never reconnects and never replays a request.

Wire the existing forward timeout in execution-target.ts to a new option
that defaults to the broker forward budget (30 s), so the current behavior
does not change.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 02:27:58 +00:00
Nicky LeachandPaperclip faab2620ad feat(sandbox): add a duplex transport for Daytona behind a default-off kill switch (#11750)
## Thinking Path

> - Paperclip is an open source app that manages AI agents for work
> - Paperclip runs agents in local and remote sandbox environments
> - A sandbox needs a bounded channel for commands and asynchronous
input
> - Daytona needs a real pseudo-terminal transport for this channel
> - The sandbox gateway also needs a mode that handles channel loss
safely
> - This pull request adds the Daytona transport and gateway mode behind
a default-off kill switch
> - The benefit is a tested foundation for later transport selection

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting (multiple of the above): sandbox providers, plugin SDK,
server settings, and shared types.

**Problem or motivation**

The merged sandbox protocol has no runtime transport for Daytona. The
generated sandbox gateway also has no duplex mode. A later
transport-selection change needs both parts and a safe per-run gate.

**Proposed solution**

Add a Daytona `duplexCommandStream` transport over a raw
pseudo-terminal. Add a generated gateway mode named `duplex_v1`. Add the
`enableSandboxDuplexBridge` setting with a default value of `false`.
Keep transport selection disabled until a later pull request.

**Alternatives considered**

Keep the protocol unused until the transport-selection change. This
would delay provider tests and leave the gateway path without direct
coverage.

**Roadmap alignment**

This change supports the completed Roadmap item for cloud and sandbox
agents. It extends the merged sandbox channel foundation in pull request
#11738.

**Additional context**

The Daytona provider remains an untrusted boundary. Deployments must use
least-privilege provider credentials and provider-side quota controls.
Operators must name an owner for duplex telemetry retention before
rollout.

## What Changed

- Add the Daytona `duplexCommandStream` capability over a raw
pseudo-terminal.
- Add a launch wrapper that disables echo and newline translation for
NDJSON frames.
- Close channels on lease release, destroy, resume of a stopped worker,
and worker shutdown.
- Declare the capability in the Daytona manifest and set
`PLUGIN_VERSION` to `0.1.5`.
- Add the worker-to-host notification sink at `ctx.duplexChannel.data`
and `ctx.duplexChannel.exit`.
- Add the generated sandbox gateway mode
`PAPERCLIP_API_BRIDGE_MODE=duplex_v1`.
- Add channel-loss results of `409 outcome_indeterminate` and `503
bridge_unavailable`.
- Add the per-run setting `enableSandboxDuplexBridge`, with a default
value of `false`.
- Add unit tests, generated-source codec tests, lifecycle tests, and a
credential-gated live Daytona test.

## Verification

- Daytona suite: 185 tests pass.
- Adapter utilities: 754 tests pass and 4 tests skip.
- Plugin SDK: 62 tests pass.
- Shared package: 28 tests pass.
- Server duplex tests pass.
- Shared, plugin SDK, server, and Daytona TypeScript checks pass.
- The live Daytona test passes 3 cases when `DAYTONA_API_KEY` is set.
- The live Daytona test skips 3 cases without `DAYTONA_API_KEY`.
- CI must run the full workspace typecheck, test, and build gates after
PR creation.

## Risks

- The Daytona control plane and pseudo-terminal remain untrusted
boundaries.
- The duplex gateway changes behavior only when the mode and per-run
setting enable it.
- A lost channel fails requests without replay, so callers must handle
indeterminate outcomes.
- The transport-selection change must require both `duplexCommandStream
=== true` and `enableSandboxDuplexBridge === true`.
- The provider credential and quota limits need operator control before
rollout.

## Model Used

OpenAI Codex, GPT-5, 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-19 17:14:47 -07:00
Nicky LeachandPaperclip d5416fde9a fix(runtime): route sandbox git-bundle export through native syncOut (#11749)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandbox-managed runtimes move files between a host and an isolated
sandbox
> - The git-bundle export path reads the full bundle into host memory
> - The workspace restore path already uses the provider native
`syncOut` transfer
> - This pull request uses `syncOut` for bundle export and keeps
`readFile` as a fallback
> - The change reduces host buffering and exposes the transfer to
provider tracing

## Linked Issues or Issue Description

**What happened?**

The sandbox git-bundle export used `client.readFile` even when the
provider supported native `syncOut`. The path buffered the full bundle
in host memory and used a chunked base64 command loop.

**Expected behavior**

The export should use one confined native file transfer when the
provider supports `syncOut`. Providers without that capability should
keep the existing `readFile` fallback.

**Steps to reproduce**

1. Prepare a sandbox-managed runtime with native `syncOut` support.
2. Export the sandbox git bundle.
3. Inspect the sync operations and file reads.
4. Confirm that the bundle uses one native file mapping and that the
status file still uses `readFile`.

**Paperclip version or commit**

Commit `3fc88d14be7133867897e87838276e15aba675fd`.

**Deployment mode**

Built from source. The change applies to sandbox-managed runtime
execution.

**Additional context**

This PR contains a focused bug fix. No public issue matched this change
during the duplicate search.

## What Changed

- Route bundle export through `nativeSyncOut` when the provider supports
it.
- Keep the existing `readFile` fallback for providers without native
sync support.
- Confine the native bundle mapping with `assertSyncOperationsConfined`.
- Cover the native path, fallback path, retry path, and confinement
checks with unit tests.

## Verification

- `npx vitest run
packages/adapter-utils/src/sandbox-managed-runtime.test.ts` — 52 tests
passed.
- `npx tsc --noEmit` in `packages/adapter-utils` — passed.
- The PR CI workflow must pass before merge.

## Risks

Low risk. The native path runs only when the provider advertises
`syncOut`. The existing `readFile` path remains available as a fallback.
Native transfer progress reports only start and finish events.

## Model Used

OpenAI GPT-5 Codex. Exact runtime model ID: GPT-5 Codex. Context window:
not exposed in this run. Capabilities used: code review, shell tools,
GitHub CLI, and Paperclip API coordination.

## 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-19 16:50:04 -07:00
Nicky LeachandPaperclip 8161244284 feat(sandbox): add opt-in duplex command-stream foundation (capability, protocol, bounded host route, frame codec) (#11738)
## Thinking Path

> - Paperclip provides a control plane for companies that run AI agents.
> - Sandboxed agents need a safe execution path for persistent command
streams.
> - The existing callback transport does not provide a bounded, generic
duplex route.
> - The host must control capability access, route identity, protocol
limits, and close behavior.
> - This pull request adds an opt-in duplex command-stream foundation
across the sandbox layers.
> - The feature stays inert because no current provider declares the
capability.

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting (multiple of the above)

**Problem or motivation**

Sandbox command execution needs a persistent host-to-sandbox stream. The
current callback bridge uses a file transport and does not provide this
generic route.

**Proposed solution**

Add a fail-closed provider capability, generic worker protocol messages,
a host-owned bounded route, cross-layer service mediation, and a
versioned newline-delimited frame codec.

**Alternatives considered**

Keep the file transport and add feature-specific commands. This does not
provide one reusable duplex contract or host-owned route bounds.

**Roadmap alignment**

This work supports the completed Cloud / Sandbox agents roadmap area and
the safe autonomy goal in the product definition.

**Additional context**

The change passed a two-stage security review. The final code review
verdict was approve after fixes for active-stream bounds and
service-layer capability mediation.

## What Changed

- Add the opt-in `duplexCommandStream` provider capability with
fail-closed narrowing.
- Add duplex open, write, stop, and close requests and data and exit
notifications to the plugin worker protocol.
- Add a host-owned route with bounds for chunk size, cumulative bytes,
lifetime, protocol errors, pending requests, and pre-bind buffering.
- Add close acknowledgement handling with worker retirement when the
close remains unconfirmed.
- Wire `openDuplexChannel` through the execution target, runtime
service, and plugin worker.
- Add a versioned frame codec with shared wire-compatibility vectors and
split UTF-8 handling.

## Verification

- `server/src/__tests__/plugin-worker-manager-duplex.test.ts` passes 18
tests.
- `server/src/__tests__/environment-execution-target-duplex.test.ts`
passes 11 tests.
- `packages/adapter-utils/src/duplex-frame-codec.test.ts` passes 38
tests.
- `server/src/__tests__/sandbox-capability-contract.test.ts` passes 15
tests.
- Setup-token pseudo-terminal regression tests pass 47 tests.
- Server TypeScript check passes.
- Continuous integration will run the full required test, typecheck,
build, and policy checks.

## Risks

- Providers that opt into the capability must implement the complete
worker protocol.
- Route limit defaults can close a stream when a workload exceeds the
configured bounds.
- The capability remains disabled for current providers, so current
production behavior does not change.

## Model Used

OpenAI GPT-5 (`gpt-5`), with tool use and code execution. The model
reviewed and prepared this pull request from the supplied implementation
and verification record.

## 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-19 13:39:13 -07:00
bd059a073d fix(workspaces): make managed runtimes reliable across restarts (#11740)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Execution workspaces need isolated databases, ports, and runtime
services
> - Concurrent workspaces could reuse ports or lose service ownership
after a restart
> - A markerless worktree also needed seed recovery, but normal
markerless instances still needed to boot
> - This pull request makes seed, port, and service ownership state
explicit and recoverable
> - It also checks live process and listener identity before it reclaims
shared resources
> - The benefit is reliable workspace startup, restart, adoption, and
concurrent provisioning

## Linked Issues or Issue Description

**What happened?**

Managed workspaces could lose runtime service ownership after a
control-plane restart. Concurrent worktrees could also reuse a port when
their parent paths differed. A seed recovery change made every
markerless instance resolve a worktree seed source, so normal instances
without a source could not start.

**Expected behavior**

Paperclip must preserve healthy managed services across restarts. It
must reserve unique ports across worktree parents. It must provision a
registered markerless worktree, but it must skip seed work for a normal
markerless instance.

**Steps to reproduce**

1. Start two managed worktrees under different parent paths at the same
time.
2. Restart the control plane while a managed service stays alive.
3. Start Paperclip with a config that has no seed markers and no
registered worktree source.
4. Observe duplicate port selection, lost service adoption, or a
seed-source startup error.

**Paperclip version or commit**

Current `master` plus the workspace runtime reliability changes in this
pull request.

**Deployment mode**

Local development with managed execution workspaces and embedded
Postgres.

## What Changed

- Added a shared port registry with lease heartbeats, process identity
checks, and live listener probes.
- Reserved worktree ports across custom parent paths and repaired
duplicate legacy assignments.
- Preserved and adopted healthy managed services across control-plane
restarts.
- Reconciled guest bind modes and verified listener ownership before
termination or reuse.
- Provisioned registered markerless worktree databases and kept normal
markerless instance startup as a no-op.
- Added CLI, shared, server, and shell regression tests for seed, port,
listener, restart, and adoption behavior.
- Updated the worktree development documentation.

## Verification

- `pnpm exec vitest run cli/src/__tests__/worktree.test.ts
--reporter=verbose` — 63 tests passed.
- `pnpm exec vitest run
packages/shared/src/worktree-port-registry.test.ts --reporter=verbose` —
5 tests passed.
- Focused runtime Vitest set — 199 tests passed across 37 suites.
- `node --test scripts/__tests__/provision-worktree-self-heal.test.mjs`
— 10 tests passed.
- `git diff --check` passed.

## Risks

- Port reservation now depends on lease and process identity data. The
fallback listener probe prevents early reclamation when process metadata
is incomplete.
- Runtime adoption is stricter about bind and owner identity. The tests
cover healthy adoption, stale records, PID reuse, and unrelated
listeners.
- Markerless seed detection now separates registered worktrees from
normal instances. The tests cover both paths.
- There are no database schema migrations.

> 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. The serving snapshot and
context-window size are not exposed. The agent used reasoning,
repository tools, code execution, and test execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Dev Agent <dev@paperclip.ing>
2026-08-19 14:55:16 -05:00
Nicky LeachandPaperclip 233be4b36c feat: parallelize sandbox file-sync behind a provider opt-in capability (#11736)
## Thinking Path

> - Paperclip runs AI agents through local and remote execution
adapters.
> - Sandbox providers move workspace and asset files before and after
agent runs.
> - Serial file transfers delay startup and teardown when several
operations do not depend on each other.
> - Providers need an opt-in contract so existing providers keep their
serial behavior.
> - This pull request adds a bounded scheduler and routes inbound and
outbound sync operations through it.
> - The benefit is shorter sandbox setup and teardown with stable
errors, clear telemetry, and a safe opt-in path.

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting (multiple of the above): packages/shared,
packages/adapter-utils, packages/plugins, and server.

**Problem or motivation**

Sandbox sync processes the workspace, assets, and referenced projects in
series. This adds avoidable wait time to agent startup and teardown.

**Proposed solution**

Add a fail-closed provider capability named concurrentSyncOperations.
Use a bounded scheduler with a limit of four operations. Preserve
operation order for error reporting. Keep non-opted-in providers on the
serial path.

**Alternatives considered**

Increase the serial transfer speed or add provider-specific schedulers.
Those options do not provide one shared contract or stable behavior
across providers.

**Roadmap alignment**

ROADMAP.md lists cloud and sandbox agents as a product area. This change
improves sandbox execution without changing the control-plane contract.

**Additional context**

The Daytona provider opts in. Board trials on this commit showed overlap
for inbound sync and outbound restore, with no referenced-project
staging failures.

## What Changed

- Add the concurrentSyncOperations sandbox capability and fail-closed
parsing.
- Add a bounded settle-all scheduler with stable input-order errors.
- Parallelize inbound workspace, asset, and referenced-project sync
operations when the provider opts in.
- Parallelize outbound workspace and asset restore operations when the
provider opts in.
- Surface referenced-project failure text in run logs and server
telemetry.
- Add Daytona sync spans and the capability declaration.
- Preserve in-flight upload scratch tarballs during workspace wipe.
- Add unit and regression tests for the scheduler, coordinators,
provider behavior, telemetry, and wipe race.

## Verification

- Run the adapter-utils and server type checks.
- Run the targeted adapter-utils, server, and Daytona test suites.
- Run the full automated sweep.
- Review six cold Daytona trials, with three serial and three parallel
runs.
- Confirm that parallel trials show inbound overlap and outbound restore
overlap.
- Confirm that providers without the capability keep serial behavior.

## Risks

- Providers must opt in only when their file operations can run safely
at the same time.
- A provider that declares the capability incorrectly can expose
transfer races.
- The scheduler keeps a limit of four to bound resource use.
- Providers without the capability keep the prior serial behavior.

## Model Used

OpenAI GPT-5 in the Codex runtime. The model used tool calls, code
inspection, and GitHub workflow support. The model did not author the
implementation commits.

## 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
- [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-19 12:34:11 -07:00
Nicky LeachandPaperclip e0b64529b3 feat(auth): normalize agent login in the sandbox onto one session table and a capability contract (#11730)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandbox agents need a safe login path for each supported adapter
> - Codex device login and Claude setup-token login used separate
session stores and route logic
> - Separate stores made session lookup, expiry, and login capability
checks harder to keep consistent
> - This pull request unifies both flows on one session table and one
capability contract
> - The benefit is one company-scoped login model with public session
identifiers and shared lifecycle rules

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting (multiple of the above)

**Problem or motivation**

Codex and Claude sandbox login used separate session stores and
different route paths. This split increased the risk of inconsistent
company scoping, session lookup, and cleanup.

**Proposed solution**

Use `adapter_auth_sessions` for both login flows. Use public session
identifiers for API access. Select login behavior from projected adapter
capability data. Share the route spine, lease arguments, runner
lifecycle, and reaper rules.

**Alternatives considered**

Keep two session tables and add matching fixes to both routes. This
keeps duplicate logic and does not provide one capability contract, so
this pull request uses shared infrastructure.

**Roadmap alignment**

This change supports the shipped Cloud / Sandbox agents milestone in
`ROADMAP.md`.

## What Changed

- Unify Codex device login and Claude setup-token login on
`adapter_auth_sessions`.
- Return and look up sessions with company-scoped public session
identifiers.
- Enforce one active session for each company, owner, and adapter.
- Share the login route spine, sandbox lease arguments, runner
lifecycle, and missing-auth check.
- Add a standalone setup-token reaper with adapter-specific row
selection.
- Add optional login capability projection for adapters and drive route
and UI selection from that data.
- Rename the provider flag to `supportsLoginPty` and validate its
deprecated alias.
- Remove the old Claude setup-token session table and add the required
migrations.

## Verification

- Server typecheck passed with `tsc`.
- Database typecheck passed.
- UI typecheck passed with `tsc -b`.
- Codex login service and route suites passed.
- Setup-token session, route, and reaper suites passed.
- Adapter session schema, plugin validator, capability projection, UI
render, and Daytona suites passed.
- GitHub Actions must confirm the complete CI gate after pull request
creation.

## Risks

- The migrations remove short-lived in-flight login rows during
deployment. A login that spans the migration can continue until its
provider lease expires.
- The Codex credential store remains company-scoped. A cross-owner
credential race remains a documented, board-accepted risk.
- API clients that use internal session row identifiers no longer work.
The API accepts only public session identifiers.

## Model Used

Codex, GPT-5, exact runtime model ID not exposed in this handoff, large
context window, reasoning, and repository tool use. The implementing
engineer produced the code with AI assistance.

## 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-19 11:51:31 -07:00
dependabot[bot] 98298cb7ff build(deps-dev): bump tsx from 4.23.1 to 4.23.12 (#11722)
Bumps [tsx](https://github.com/privatenumber/tsx) from 4.23.1 to
4.23.12.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/privatenumber/tsx/releases">tsx's
releases</a>.</em></p>
<blockquote>
<h2>v4.23.12</h2>
<h2><a
href="https://github.com/privatenumber/tsx/compare/v4.23.11...v4.23.12">4.23.12</a>
(2026-08-10)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>shim <code>import.meta</code> when tokens are split by comments or
newlines (<a
href="https://redirect.github.com/privatenumber/tsx/issues/829">#829</a>)
(<a
href="https://github.com/privatenumber/tsx/commit/ed9d33046a135de13a35fdfce12368b79d1b1518">ed9d330</a>),
closes <a
href="https://redirect.github.com/privatenumber/tsx/issues/828">#828</a></li>
</ul>
<hr />
<p>This release is also available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/tsx/v/4.23.12"><code>npm
package (@​latest dist-tag)</code></a></li>
</ul>
<h2>v4.23.11</h2>
<h2><a
href="https://github.com/privatenumber/tsx/compare/v4.23.10...v4.23.11">4.23.11</a>
(2026-08-07)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>preserve async ESM require fallback (<a
href="https://github.com/privatenumber/tsx/commit/55cbecef8ebe839c7110e8c141a1c3bc4da326cd">55cbece</a>)</li>
</ul>
<hr />
<p>This release is also available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/tsx/v/4.23.11"><code>npm
package (@​latest dist-tag)</code></a></li>
</ul>
<h2>v4.23.10</h2>
<h2><a
href="https://github.com/privatenumber/tsx/compare/v4.23.9...v4.23.10">4.23.10</a>
(2026-08-07)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>support nyc coverage discovery (<a
href="https://redirect.github.com/privatenumber/tsx/issues/710">#710</a>)
(<a
href="https://github.com/privatenumber/tsx/commit/ec1bcd5f711e5159b67cb0aea211f06cf2cfce8a">ec1bcd5</a>)</li>
</ul>
<hr />
<p>This release is also available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/tsx/v/4.23.10"><code>npm
package (@​latest dist-tag)</code></a></li>
</ul>
<h2>v4.23.9</h2>
<h2><a
href="https://github.com/privatenumber/tsx/compare/v4.23.8...v4.23.9">4.23.9</a>
(2026-08-06)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>map Node test locations (<a
href="https://github.com/privatenumber/tsx/commit/2f55884195a8c745fbe64a0288de69bc062ed876">2f55884</a>)</li>
<li>support data URLs in tsImport (<a
href="https://github.com/privatenumber/tsx/commit/b94f46f6b6a7e6dc575624b0ecc7124318723056">b94f46f</a>)</li>
</ul>
<hr />
<p>This release is also available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/tsx/v/4.23.9"><code>npm
package (@​latest dist-tag)</code></a></li>
</ul>
<h2>v4.23.8</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/privatenumber/tsx/commit/ed9d33046a135de13a35fdfce12368b79d1b1518"><code>ed9d330</code></a>
fix: shim <code>import.meta</code> when tokens are split by comments or
newlines (<a
href="https://redirect.github.com/privatenumber/tsx/issues/829">#829</a>)</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/651f5bec70d9a116d1fe1706000b7ca011a516fc"><code>651f5be</code></a>
test: cover CommonJS TypeScript import.meta paths</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/bd3bc6448e957c1172eb91a0584ec7fec2d6a7ad"><code>bd3bc64</code></a>
test: cover CommonJS loader source fallback</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/55cbecef8ebe839c7110e8c141a1c3bc4da326cd"><code>55cbece</code></a>
fix: preserve async ESM require fallback</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/6c5ba85f7a1e57f06bcb760718d6ba3b978e5a05"><code>6c5ba85</code></a>
docs: document CommonJS default interop</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/ec1bcd5f711e5159b67cb0aea211f06cf2cfce8a"><code>ec1bcd5</code></a>
fix: support nyc coverage discovery (<a
href="https://redirect.github.com/privatenumber/tsx/issues/710">#710</a>)</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/b6e5b48a7b0fa4639e67119c8b02150ec8c6cef7"><code>b6e5b48</code></a>
docs: clarify CommonJS default imports</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/2f55884195a8c745fbe64a0288de69bc062ed876"><code>2f55884</code></a>
fix: map Node test locations</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/de935d588b6c0959ac8af71e2cf40d1a61e659e7"><code>de935d5</code></a>
docs: document Node source-map stack formatting</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/b94f46f6b6a7e6dc575624b0ecc7124318723056"><code>b94f46f</code></a>
fix: support data URLs in tsImport</li>
<li>Additional commits viewable in <a
href="https://github.com/privatenumber/tsx/compare/v4.23.1...v4.23.12">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tsx&package-manager=npm_and_yarn&previous-version=4.23.1&new-version=4.23.12)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-19 10:32:04 -07:00
dependabot[bot] ec8d7dd21f build(deps): bump ws from 8.21.1 to 8.21.3 (#11729)
Bumps [ws](https://github.com/websockets/ws) from 8.21.1 to 8.21.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/websockets/ws/releases">ws's
releases</a>.</em></p>
<blockquote>
<h2>8.21.3</h2>
<h1>Bug fixes</h1>
<ul>
<li>The server now correctly rejects permessage-deflate offers if the
incoming
<code>client_max_window_bits</code> parameter value is smaller than its
configured
<code>clientMaxWindowBits</code> (e97a20ea).</li>
</ul>
<h2>8.21.2</h2>
<h1>Bug fixes</h1>
<ul>
<li>Fixed a test for <a href="https://github.com/nodejs/citgm">CITGM</a>
(2eb3be0b).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/websockets/ws/commit/c791e707eab3c13dd9a261d2479c3cc4a49a6fed"><code>c791e70</code></a>
[dist] 8.21.3</li>
<li><a
href="https://github.com/websockets/ws/commit/e97a20eaa6f2ad7969419eed732a506453251eb9"><code>e97a20e</code></a>
[fix] Reject offers with <code>client_max_window_bits</code> below
config</li>
<li><a
href="https://github.com/websockets/ws/commit/787ebf22ce3d091fb6f931d20b4c7e914ba7cf85"><code>787ebf2</code></a>
[dist] 8.21.2</li>
<li><a
href="https://github.com/websockets/ws/commit/b4d62ebad40c3b925c84ff305a47975406015422"><code>b4d62eb</code></a>
Revert &quot;[ci] Trust Coveralls Homebrew tap&quot;</li>
<li><a
href="https://github.com/websockets/ws/commit/e4bb883723a0c18452eea10a74139901ae33c61d"><code>e4bb883</code></a>
[security] Use GitHub PVR as main reporting channel</li>
<li><a
href="https://github.com/websockets/ws/commit/2eb3be0bff2453e2654b1315c5872e8d5d424a50"><code>2eb3be0</code></a>
[test] Skip test on Node.js versions where it does not apply</li>
<li>See full diff in <a
href="https://github.com/websockets/ws/compare/8.21.1...8.21.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ws&package-manager=npm_and_yarn&previous-version=8.21.1&new-version=8.21.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-19 10:30:13 -07:00
DottaandPaperclip e1df4c6068 fix(workspaces): keep deferred seed databases reliable (#11706)
## Thinking Path

> - Paperclip manages agent work in isolated execution workspaces.
> - A workspace depends on a valid database seed before it can run.
> - Deferred seed failures were hidden behind a successful provision
status.
> - The seed restore also had two possible owners for the embedded
PostgreSQL process.
> - That allowed the target database to stop while the restore was still
running.
> - This pull request makes seed failures visible and gives the seed
process sole lifecycle ownership.
> - The benefit is that workspace provisioning reports the real result
and does not stop its own target database.

## Linked Issues or Issue Description

Related: #11684

**What happened?**

Initial worktree provisioning could report success before its deferred
database seed completed. The seed restore could also reuse a target
embedded PostgreSQL process with another shutdown owner. This could stop
the target database during the restore.

**Expected behavior**

Workspace status must show a failed deferred seed as a failure. The seed
restore must own the target embedded PostgreSQL process until restore,
migration, and validation finish.

**Steps to reproduce**

1. Provision a worktree with deferred database seeding.
2. Make the seed manifest end in a failed state while the command exits
with code 0.
3. Observe that the provision status remains successful on `master`.
4. Start a seed restore against an already-running target embedded
PostgreSQL process.
5. Observe that another lifecycle owner can stop the target during
restore.

**Paperclip version or commit**

`51a843e135`

**Deployment mode**

Local dev with execution workspaces and embedded PostgreSQL.

## What Changed

- Add a first-class `workspace_seed` operation for deferred database
seeds.
- Require terminal, verified seed evidence before the seed operation
succeeds.
- Surface the seed phase and failure metadata in workspace status and UI
state.
- Give the seed process exclusive lifecycle ownership of the target
embedded PostgreSQL process.
- Suppress imported embedded-Postgres exit hooks without removing
existing host listeners.
- Record a credential-safe shutdown diagnostic in failed seed manifests.

## Verification

- The original deferred-seed commit passed 4 server tests, 24
workspace-status UI tests, shared/server/UI typechecks, and the UI token
gate.
- The original PostgreSQL-lifecycle commit passed 3 lifecycle tests, 3
ownership/diagnostic tests, 1 real embedded-Postgres seed integration,
and the affected package typechecks.
- No local tests were rerun after the clean cherry-pick because the
operator requested the shortest landing path.
- Review the automatic PR checks for the clean `origin/master` replay.

## Risks

- A live target database now causes an early error instead of being
reused. The error includes recovery guidance.
- Workspace consumers must handle the new `workspace_seed` operation
type. Shared types and UI state handling are updated in this pull
request.

> 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, high-reasoning mode, with repository, shell, and
GitHub tool use. The runtime does not expose a more specific deployment
suffix or 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
- [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-19 10:41:29 -05:00
dependabot[bot] 536d5880c5 build(deps): bump @cursor/sdk from 1.0.24 to 1.0.28 (#11520)
Bumps [@cursor/sdk](https://github.com/cursor/cursor) from 1.0.24 to
1.0.28.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/cursor/cursor/commits">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@cursor/sdk&package-manager=npm_and_yarn&previous-version=1.0.24&new-version=1.0.28)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-19 00:48:17 -07:00
DottaandPaperclip a2bf936f9a feat(workspaces): sign the workspace login handoff and gate readiness (#11671)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Managed worktree services run isolated Paperclip instances with
cloned databases.
> - A reachable service was reported as ready even when its database,
runtime identity, or login path was not usable.
> - The first candidate added verified database seeding and managed
repair in #11665.
> - This pull request consolidates that candidate with signed login
handoff and a complete readiness contract.
> - Post-QA fixes close five defects in repair identity, repair
responses, UI retry, seed journal handling, and seed-source trust.
> - The benefit is a workspace that either opens safely or reports one
accurate recovery action.

## Linked Issues or Issue Description

No public GitHub issue exists for this work, so the problem is described
here.

**What happened**

Managed workspace URLs could return HTTP 200 and report ready while
login failed. QA also found cases where repair used the wrong instance
identity, returned a generic error, left the UI stuck, rejected a safe
journal lag, or trusted a mutable workspace manifest.

**Expected behavior**

Opening a ready workspace signs the board user in to the correct
isolated instance. Provisioning and repair use a registered source and
report a structured recovery state.

**Actual behavior**

Entry depended on a password copied into the clone. Several failure
paths could publish stale readiness, hide the repair precondition, or
trust state that the workspace could modify.

**Additional context**

This pull request includes the commits first published in #11665. That
pull request keeps the original base head for review history. This
consolidated pull request is the merge candidate. Related open readiness
work includes #11575 and #11621.

## What Changed

- Adds a short-lived, signed, single-use login ticket. It binds the
user, workspace, instance, and runtime origin.
- Exchanges the ticket through Better Auth. It creates the session and
cookie through the supported adapter path.
- Adds protected workspace readiness fields for the database, clone
data, login handoff, seed phase, and runtime identity.
- Fails readiness closed when the guest has no company or
execution-workspace binding.
- Binds ticket issuance to the exact cloned user and active company
membership selected for the handoff.
- Verifies every current active board identity through the exact-user
handoff before publication or reuse.
- Gates managed runtime publication on the readiness contract and the
recorded worktree instance identity.
- Refreshes runtime work products from the live runtime row after a port
change.
- Adds one workspace access card with ready, degraded, repairing, and
failed states.
- Uses the runtime response identity for repair. It returns structured
repair precondition errors.
- Lets a valid source journal lag converge during provisioning.
- Binds seed and repair manifests to a source registered outside the
agent-writable worktree.
- Clears recovered UI errors so a successful retry can open the
workspace.
- Makes runtime tests register canonical sources and avoid ports owned
by live host listeners.
- Keeps Vitest on source suites when compiled `dist` trees exist.
- Isolates CLI and adapter tests from ambient AWS and runtime API
environment variables.
- Preserves a 404 response for cross-company workspace ID lookups before
runtime authorization.
- Makes concurrent single-flight coverage independent of
path-canonicalization scheduling order.

## Verification

The following checks passed on the integrated head:

```sh
pnpm -r typecheck
pnpm build
pnpm check:token-gates
pnpm --filter @paperclipai/db check:migrations
```

- The server source lane passed 420 files and 4,953 tests. Five tests
were skipped.
- The CLI lane passed 57 files and 385 tests.
- The database lane passed 26 files and 97 tests.
- The shared package passed 58 files and 506 tests.
- The adapter utility lane passed 640 tests. Four tests were skipped.
- The Claude adapter passed 220 tests. One test was skipped.
- The Codex adapter passed 323 tests.
- The OpenClaw adapter passed 13 tests.
- The OpenCode adapter passed 42 tests.
- The plugin SDK passed 45 tests.
- The workspace runtime suite passed 124 tests.
- The caller-scoped readiness and handoff suite passed 52 tests.
- The workspace provisioning shell suite passed 7 tests.
- The runtime exposure suite passed 17 tests while live host mappings
occupied fixed test ports.
- `git diff --check` passed and the worktree is clean.

The serialized route lane will run in GitHub CI with its normal shards.
No deployment or active-workspace migration was performed.

## Risks

- This is a medium-risk authentication and runtime-readiness change.
- The login ticket uses exact origin, workspace, instance, and user
binding. It has a short expiry and a one-time nonce.
- Runtime publication is stricter. A real readiness, identity, per-user
handoff, or control-plane database disagreement now blocks publication.
- This pull request supersedes #11665 as the merge candidate. Close
#11665 after this pull request merges.
- No new database migration is included. The lockfile and workflow files
are unchanged.
- Deployment and active-workspace migration are intentionally outside
this pull request.

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

## Model Used

Claude Opus 5 (`claude-opus-5[1m]`), 1M context, extended thinking, tool
use, and code execution produced the main candidate. OpenAI GPT-5
(`gpt-5`) through Codex, with agentic reasoning, tool use, and code
execution, integrated the post-QA fixes and hardened the test gates. The
Codex context-window size was not exposed.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-19 02:37:02 -05:00
dependabot[bot] 03ddca4df7 build(deps): bump @agentclientprotocol/codex-acp from 1.1.7 to 1.2.0 (#11515)
Bumps
[@agentclientprotocol/codex-acp](https://github.com/agentclientprotocol/codex-acp)
from 1.1.7 to 1.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/agentclientprotocol/codex-acp/releases">@​agentclientprotocol/codex-acp's
releases</a>.</em></p>
<blockquote>
<h2>v1.2.0</h2>
<h2><a
href="https://github.com/agentclientprotocol/codex-acp/compare/v1.1.14...v1.2.0">1.2.0</a>
(2026-08-11)</h2>
<h3>Features</h3>
<ul>
<li>expose typed session failures for AIR (<a
href="https://redirect.github.com/agentclientprotocol/codex-acp/issues/383">#383</a>)
(<a
href="https://github.com/agentclientprotocol/codex-acp/commit/54987e1c4a4f878af9afad96ec8b6b0b48c7045e">54987e1</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>normalize cwd filters for Windows sessions (<a
href="https://redirect.github.com/agentclientprotocol/codex-acp/issues/377">#377</a>)
(<a
href="https://github.com/agentclientprotocol/codex-acp/commit/145ebba5d2030b4aa6d19cbb89d190b7b498d454">145ebba</a>)</li>
</ul>
<h2>v1.1.14</h2>
<h2>What's Changed</h2>
<ul>
<li>Update codex to 0.147.0 by <a
href="https://github.com/acp-release-bot"><code>@​acp-release-bot</code></a>[bot]
in <a
href="https://redirect.github.com/agentclientprotocol/codex-acp/pull/375">agentclientprotocol/codex-acp#375</a></li>
<li>feat: support replacing goals through ACP control by <a
href="https://github.com/nikita-ashihmin"><code>@​nikita-ashihmin</code></a>
in <a
href="https://redirect.github.com/agentclientprotocol/codex-acp/pull/376">agentclientprotocol/codex-acp#376</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/agentclientprotocol/codex-acp/compare/v1.1.13...v1.1.14">https://github.com/agentclientprotocol/codex-acp/compare/v1.1.13...v1.1.14</a></p>
<h2>v1.1.13</h2>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/agentclientprotocol/codex-acp/compare/v1.1.12...v1.1.13">https://github.com/agentclientprotocol/codex-acp/compare/v1.1.12...v1.1.13</a></p>
<h2>v1.1.12</h2>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/agentclientprotocol/codex-acp/compare/v1.1.11...v1.1.12">https://github.com/agentclientprotocol/codex-acp/compare/v1.1.11...v1.1.12</a></p>
<h2>v1.1.11</h2>
<h2>What's Changed</h2>
<ul>
<li>build(deps-dev): bump the npm_and_yarn group across 1 directory with
3 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/agentclientprotocol/codex-acp/pull/372">agentclientprotocol/codex-acp#372</a></li>
<li>Fix resuming paused goals by <a
href="https://github.com/nikita-ashihmin"><code>@​nikita-ashihmin</code></a>
in <a
href="https://redirect.github.com/agentclientprotocol/codex-acp/pull/374">agentclientprotocol/codex-acp#374</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/agentclientprotocol/codex-acp/compare/v1.1.10...v1.1.11">https://github.com/agentclientprotocol/codex-acp/compare/v1.1.10...v1.1.11</a></p>
<h2>v1.1.10</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: Stop emitting &quot;Conversation interrupted&quot; message by
<a href="https://github.com/Rizzen"><code>@​Rizzen</code></a> in <a
href="https://redirect.github.com/agentclientprotocol/codex-acp/pull/358">agentclientprotocol/codex-acp#358</a></li>
<li>Update codex to 0.146.0 by <a
href="https://github.com/acp-release-bot"><code>@​acp-release-bot</code></a>[bot]
in <a
href="https://redirect.github.com/agentclientprotocol/codex-acp/pull/341">agentclientprotocol/codex-acp#341</a></li>
<li>build(deps): bump the npm_and_yarn group across 1 directory with 2
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/agentclientprotocol/codex-acp/pull/362">agentclientprotocol/codex-acp#362</a></li>
<li>feat: support device code authentication via URL elicitation by <a
href="https://github.com/AlexandrSuhinin"><code>@​AlexandrSuhinin</code></a>
in <a
href="https://redirect.github.com/agentclientprotocol/codex-acp/pull/347">agentclientprotocol/codex-acp#347</a></li>
<li>Update codex to 0.146.1 by <a
href="https://github.com/acp-release-bot"><code>@​acp-release-bot</code></a>[bot]
in <a
href="https://redirect.github.com/agentclientprotocol/codex-acp/pull/370">agentclientprotocol/codex-acp#370</a></li>
<li>feat: expose provider-neutral ACP goal extension by <a
href="https://github.com/nikita-ashihmin"><code>@​nikita-ashihmin</code></a>
in <a
href="https://redirect.github.com/agentclientprotocol/codex-acp/pull/371">agentclientprotocol/codex-acp#371</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/agentclientprotocol/codex-acp/compare/v1.1.9...v1.1.10">https://github.com/agentclientprotocol/codex-acp/compare/v1.1.9...v1.1.10</a></p>
<h2>v1.1.9</h2>
<h2>What's Changed</h2>
<ul>
<li>Throttle ACP plan update snapshots by <a
href="https://github.com/nikita-ashihmin"><code>@​nikita-ashihmin</code></a>
in <a
href="https://redirect.github.com/agentclientprotocol/codex-acp/pull/354">agentclientprotocol/codex-acp#354</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/agentclientprotocol/codex-acp/blob/main/CHANGELOG.md">@​agentclientprotocol/codex-acp's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/agentclientprotocol/codex-acp/compare/v1.1.14...v1.2.0">1.2.0</a>
(2026-08-11)</h2>
<h3>Features</h3>
<ul>
<li>expose typed session failures for AIR (<a
href="https://redirect.github.com/agentclientprotocol/codex-acp/issues/383">#383</a>)
(<a
href="https://github.com/agentclientprotocol/codex-acp/commit/54987e1c4a4f878af9afad96ec8b6b0b48c7045e">54987e1</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>normalize cwd filters for Windows sessions (<a
href="https://redirect.github.com/agentclientprotocol/codex-acp/issues/377">#377</a>)
(<a
href="https://github.com/agentclientprotocol/codex-acp/commit/145ebba5d2030b4aa6d19cbb89d190b7b498d454">145ebba</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/agentclientprotocol/codex-acp/commit/b51bedf60050c60fef78fc669e6ccf2ff61e3f47"><code>b51bedf</code></a>
chore(main): release 1.2.0 (<a
href="https://redirect.github.com/agentclientprotocol/codex-acp/issues/389">#389</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/codex-acp/commit/2dccf45b53a33089d5b6e82508a1887aec8b20cc"><code>2dccf45</code></a>
ci: release-please release flow (<a
href="https://redirect.github.com/agentclientprotocol/codex-acp/issues/388">#388</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/codex-acp/commit/54987e1c4a4f878af9afad96ec8b6b0b48c7045e"><code>54987e1</code></a>
feat: expose typed session failures for AIR (<a
href="https://redirect.github.com/agentclientprotocol/codex-acp/issues/383">#383</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/codex-acp/commit/9edc92458504a9653f539f2a515f59e4a95796a7"><code>9edc924</code></a>
build(deps-dev): bump hono in the npm_and_yarn group across 1 directory
(<a
href="https://redirect.github.com/agentclientprotocol/codex-acp/issues/380">#380</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/codex-acp/commit/145ebba5d2030b4aa6d19cbb89d190b7b498d454"><code>145ebba</code></a>
fix: normalize cwd filters for Windows sessions (<a
href="https://redirect.github.com/agentclientprotocol/codex-acp/issues/377">#377</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/codex-acp/commit/5faefec5d55ded33c54b68ffec93def4f6c547f5"><code>5faefec</code></a>
Release v1.1.14</li>
<li><a
href="https://github.com/agentclientprotocol/codex-acp/commit/0d45a13c2618e8175a24d0bb080a482c7920f291"><code>0d45a13</code></a>
feat: support replacing goals through ACP control (<a
href="https://redirect.github.com/agentclientprotocol/codex-acp/issues/376">#376</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/codex-acp/commit/a8cedc8d3789371f992979a4f4facac29f0bd681"><code>a8cedc8</code></a>
Update codex to 0.147.0 (<a
href="https://redirect.github.com/agentclientprotocol/codex-acp/issues/375">#375</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/codex-acp/commit/ea57892f7d8e305fc1bd489de420adb19de055b0"><code>ea57892</code></a>
Release v1.1.13</li>
<li><a
href="https://github.com/agentclientprotocol/codex-acp/commit/91cbfd30467d57eae06d3a57ccb068320bb7e171"><code>91cbfd3</code></a>
Release v1.1.12</li>
<li>Additional commits viewable in <a
href="https://github.com/agentclientprotocol/codex-acp/compare/v1.1.7...v1.2.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@agentclientprotocol/codex-acp&package-manager=npm_and_yarn&previous-version=1.1.7&new-version=1.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-19 00:29:53 -07:00
Harjoth KharaandClaude Opus 4.8 aad97d93fe fix(hermes): surface real reasoning text from reasoning.available events (#9237)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - When an agent runs through the Hermes gateway adapter, its stdout is
parsed line-by-line into transcript entries that the issue chat renders
(the UI fetches the adapter's `./ui-parser` from
`/api/adapters/:type/ui-parser.js` and runs `parseStdoutLine`
client-side)
> - Reasoning-capable models emit a `reasoning.available` gateway event
carrying the model's reasoning text, and the chat renders `thinking`
parts as expandable chain-of-thought
> - The gateway parser mapped `reasoning.available` to a hardcoded
`"Hermes reasoning available"` string and discarded the event payload,
so the "thinking" part had no real content — the indicator looked static
and expanding it revealed nothing (#9209)
> - This pull request extracts the actual reasoning text from the event
payload and uses it as the `thinking` part's text, keeping the old
string only as a fallback for payloads that carry no text
> - The benefit is that the "Hermes reasoning available" indicator now
surfaces the model's real reasoning, which the existing
expandable-thinking UI can display

## Linked Issues or Issue Description

Fixes: #9209

## What Changed

- `packages/adapters/hermes/src/gateway/ui/parse-stdout.ts`: the
`reasoning.available` handler now extracts the reasoning text from the
event `data` via a small helper (`extractReasoningText`), checking the
plausible field names (`reasoning`, `reasoning_text`, `thinking`,
`text`, `summary`, `content`) and recursing one level into nested `data`
/ `payload` records, with ANSI stripped. The prior `"Hermes reasoning
available"` string is kept only as a fallback when no text field is
present.
- `packages/adapters/hermes/gateway-ui-parser.cjs`: applied the
identical logical change to the committed CommonJS mirror (exported as
`./gateway/ui-parser`), keeping the two files in sync.
- `packages/adapters/hermes/src/gateway/ui/parse-stdout.test.ts` (new):
unit tests for the gateway parser (there were none) covering
direct-field, `summary`, nested `data`/`payload` extraction, the no-text
fallback, and regression guards for `message.delta` and plain stdout.

## Verification

Ran from `packages/adapters/hermes`:

- `node_modules/.bin/vitest run src/gateway/ui/parse-stdout.test.ts` →
**8/8 passed**.
- Negative control: stashed the source changes and re-ran the same test
file against the current (pre-patch) parser → **4/8 failed** (exactly
the reasoning-extraction assertions), then restored — confirming the
tests are discriminating, not vacuous.
- `npx tsc --noEmit -p .` → clean.

Real-behavior proof (driving the actual shipped `gateway-ui-parser.cjs`
`parseStdoutLine`) is in the block below.

## Risks

- **Low risk.** Behavior is unchanged for events that carry no
recognizable text field — the `"Hermes reasoning available"` fallback is
preserved (verified). Only the `reasoning.available` branch changed;
`message.delta`, `run.failed`/`run.error`, and the generic/system/stdout
branches are untouched.
- The exact field name in a real `reasoning.available` payload is
defined by the external Hermes gateway and is not present anywhere in
this repo, so the extraction is intentionally defensive across several
plausible field names rather than pinned to one. If the real event nests
the text differently than `data` / `payload`, it will fall back to the
existing placeholder (i.e. no regression vs. today). Happy to tighten
the field list against real gateway traffic if a maintainer can share a
sample.

## Model Used

Claude Sonnet 5 (`claude-sonnet-5`) via Claude Code, with tool use and
local test execution (ran vitest/tsc against the change). Planning, code
review, and the real-behavior proof were done with Claude (Opus 4.8) in
the same session.

## Real behavior proof

**Behavior addressed:** A `reasoning.available` Hermes gateway event now
produces a `thinking` transcript part containing the model's real
reasoning text, instead of a static `"Hermes reasoning available"`
placeholder with no content behind it (#9209).

**Real environment tested:** Drove the actual shipped production
artifact — `packages/adapters/hermes/gateway-ui-parser.cjs`, the exact
module the UI loads via `/api/adapters/hermes-gateway/ui-parser.js` and
runs to parse gateway stdout — on Node v24.16.0, macOS. The input is a
raw stdout line in the exact format emitted by
`packages/adapters/hermes/src/gateway/server/execute.ts`
(`[hermes-gateway:event] run=… event=reasoning.available data=…`). Only
the external gateway boundary (the raw line) is synthesized; the parser
code path is the real one.

**Exact steps or command run after this patch:**
```
# BEFORE = git show HEAD:…/gateway-ui-parser.cjs ; AFTER = patched artifact
node proof.cjs   # requires each parser build and calls parseStdoutLine(line, ts)
# line = [hermes-gateway:event] run=run-abc123 event=reasoning.available \
#        data={"text":"Checking whether the cache key includes the tenant id before I refactor the lookup."}
```

**Evidence after fix:**
```
===== BEFORE (master / old code) =====
[ { "kind": "thinking", "ts": "…", "text": "Hermes reasoning available" } ]
thinking part carries real reasoning text? -> NO (static placeholder, nothing for the UI to expand)

===== AFTER (this patch) =====
[ { "kind": "thinking", "ts": "…",
    "text": "Checking whether the cache key includes the tenant id before I refactor the lookup." } ]
thinking part carries real reasoning text? -> YES
```
Additional cases through the same shipped artifact after the patch:
```
-- nested payload (data.payload.reasoning) --
{"kind":"thinking","ts":"…","text":"Weighing two migration orders."}
-- bare signal, no text field (regression guard) --
{"kind":"thinking","ts":"…","text":"Hermes reasoning available"}     # fallback preserved
-- message.delta still works (regression guard) --
{"kind":"assistant","ts":"…","text":"Hello","delta":true}
```

**Observed result after fix:** The `reasoning.available` event yields a
`thinking` part carrying the model's real reasoning text (top-level or
nested), which the existing expandable-thinking rendering in the chat
can display. Events with no text field still yield the original
placeholder, and unrelated events are unaffected.

**What was not tested:** I did not run against a live Hermes gateway —
Paperclip's Hermes gateway binary and its credentials aren't available
on this machine, and no captured real `reasoning.available` payload
exists in the repo, so the exact wire field name is inferred (hence the
defensive multi-field extraction + safe fallback). I also did not render
the full React chat component in jsdom; the change is confined to the
parser, and the chat's expandable `thinking` rendering already exists
(`ui/src/components/IssueChatThread.tsx`). CI / unit tests here are
supplemental to the runtime proof above.

## 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 `9209 in:body` and keyword variants — none found)
- [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
(`fix/hermes-reasoning-available-payload`) 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 describe this behavior; none needed)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (will confirm once CI runs on the
PR)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(will address on review)
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 14:19:04 -05:00
Nicky LeachandPaperclip 120ae5428f feat(server): add a one-click relink action for detached custom-image templates (#11641)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip environments can use captured custom images for agent runs
> - A configuration fingerprint change can detach a valid custom-image
template
> - Operators need a safe way to confirm that the image still matches
the boot source
> - This pull request adds a guarded relink action with drift
classification and audit logging
> - The benefit is a deliberate relink without a new sandbox boot or
provider snapshot

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting environment, server, and UI behavior.

**Problem or motivation**

A custom-image template detaches when the environment configuration
fingerprint changes. The runtime then uses the base image, even when the
boot source did not change. The only prior remedy required a full
re-capture.

**Proposed solution**

Add an operator-triggered relink action. Classify configuration drift
from a server-owned boot-relevant snapshot. Relink knob-only drift
without confirmation. Require explicit confirmation for boot-source or
unclassified drift. Guard the route for instance administrators and
record a safe activity event.

**Alternatives considered**

Keep requiring a full re-capture. This adds a sandbox boot and provider
snapshot for cases where the image remains correct.

**Roadmap alignment**

The roadmap has no matching custom-image relink item. This change
addresses an environment operation gap.

**Additional context**

The relink response exposes raw drift values only in the transient 409
response to the instance administrator. The service never persists or
logs fingerprints or configuration values. Reserved identity-path
segments fail closed.

## What Changed

- Add `relinkActiveTemplate` with drift classification and conditional
fingerprint update.
- Persist a server-owned boot-relevant configuration snapshot during
capture.
- Add the guarded relink route with strict request validation and
activity logging.
- Add the relink action and confirmation flow to the environment page.
- Add service, route, UI, and OpenAPI coverage.

## Verification

- Run the focused service suite: `pnpm vitest run
server/src/services/environment-custom-images-service.test.ts`.
- Run the focused route suite: `pnpm vitest run
server/src/routes/environment-custom-image-routes.test.ts`.
- Run the focused UI suite: `pnpm vitest run
ui/src/pages/CompanyEnvironments.test.tsx`.
- Run server and UI TypeScript checks.
- Confirm the OpenAPI snapshot matches the new route.
- Confirm all required GitHub checks pass on commit
`e46fdcfe94a719be854adf8849d30714e5b70b93`.
- Confirm Greptile reports 5/5 with no unresolved review threads.

## Risks

The relink action can keep an image after configuration drift. The
service requires explicit confirmation for boot-source or unclassified
drift. Reserved path segments produce a safe unresolved marker and never
enter stored values.

## Model Used

OpenAI GPT-5 Codex. The model used repository inspection, GitHub
operations, and PR preparation with tool use and code execution. The
runtime did not expose a context-window value or a separate
reasoning-mode 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
- [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-18 11:43:39 -07:00
Devin Foley 393da0f67c fix(adapter-utils): graft unrelated imported histories instead of failing the run (#11638)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - At run finalize, the host imports the sandbox git history and
reconciles it with the local worktree in `integrateImportedGitHead`
> - Transported workspaces are depth-1 shallow clones, so the boundary
commit reads as parentless inside the sandbox
> - A `git commit --amend` there rewrites the boundary commit into a
root commit, and the re-imported history no longer connects to the host
history
> - `git merge-tree` has no common base to merge against, so the sync
throws "Failed to merge concurrent remote git histories" and the run
fails with its work stranded in the sandbox
> - This pull request grafts the imported tree onto the current head as
a single commit instead of failing
> - The benefit is that a history rewrite inside the sandbox can no
longer lose a run's work

## Linked Issues or Issue Description

No existing issue found. I searched issues and PRs for "unrelated
histories", "Failed to merge concurrent", and "shallow". Depends on
#11637 (merged; the graft commit reuses its identity constant). This PR
is now rebased onto `master`.

**What happened?**

An agent run amended a commit inside its sandbox workspace to address
review feedback. The sandbox clone is depth-1 shallow, so git treated
the boundary commit as parentless and the amend produced a root commit.
At finalize, the host-side sync failed with `Failed to merge concurrent
remote git histories for <sha>` and the run was marked failed. A
follow-up run had to repair the branch by hand: fetch the true parent
from origin and rebuild the commit with `git commit-tree`.

**Expected behavior**

The sync must never strand completed work. When the imported history
shares no ancestor with the local one, the imported tree should still
land on the current head, with the imported message preserved and the
graft recorded.

**Steps to reproduce**

1. Start a run whose workspace transport uses the shallow clone path
(`withShallowGitWorkspaceClone`, depth 1).
2. Inside the sandbox workspace, run `git commit --amend` on the
boundary commit. The result is a parentless root commit.
3. Finish the run. The host-side `integrateImportedGitHead` finds no
merge base, `merge-tree` fails, and the run fails.

## What Changed

- `git-workspace-sync.ts`: new exported
`createUnrelatedHistoryGraftCommit` helper. It reads the imported head's
tree and message, and creates one commit on top of the current head with
the deterministic sync identity and a trailer that records the graft and
both shas.
- `integrateImportedGitHead` (both the remote-git-sync version and the
SSH copy in `ssh.ts`): when `merge-base` reports no common ancestor,
graft instead of throwing. The ref update keeps the same
compare-and-swap and concurrent-retry semantics as the merge path.
- The graft is gated on `git merge-base` exiting with status 1 — the
no-ancestor signal. Operational failures (timeout, missing object,
repository error) keep the loud merge failure instead of rewriting the
tip.
- New regression tests: one builds the exact shallow-amend shape (a root
commit rebuilt from the base tree) and asserts the graft lands on the
current head with the imported tree, subject, and graft trailer; one
integrates a well-formed sha the repository does not hold and asserts
the integration still throws with the branch tip unchanged.

## Verification

- `pnpm vitest run
packages/adapter-utils/src/git-workspace-sync.test.ts` — 19/19 pass
(includes the new graft test and the merge-base failure-discrimination
test).
- `pnpm --filter @paperclipai/adapter-utils typecheck` — clean.
- Full `pnpm vitest run packages/adapter-utils`: every file passes
except `local-process-sandbox.test.ts`, which fails identically on an
untouched `master` checkout on macOS (bubblewrap-dependent,
pre-existing, unrelated).

## Risks

- Behavioral shift: unrelated imported histories previously failed the
integration; now they land as a squash-graft. In this degenerate case
there is no base to merge against, so the imported tree is taken
wholesale and concurrent local-only tree changes are superseded at the
tip. The local commits keep their place in the graft's ancestry, and the
trailer records both shas, so nothing is unrecoverable. The old behavior
lost the imported work instead, which is the worse failure for an
autonomous run.
- The graft reuses the imported head's commit message, so branch history
still reads naturally after a sandbox rewrite.

## Model Used

- Claude Fable 5 (`claude-fable-5`), extended thinking, via Claude Code
CLI (tool use for code exploration, test runs, and 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 (no
doc surface describes this internal sync path)
- [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-18 11:14:39 -07:00
Devin Foley 9ea8143c87 fix(adapter-utils): give sync-created merge commits a deterministic git identity (#11637)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Runs execute in transported workspaces; at finalize, the host syncs
the sandbox git history back into the local worktree
> - When both sides advanced, `integrateImportedGitHead` reconciles them
with `git merge-tree` plus `git commit-tree` on the host
> - Execution hosts are often containers with no git config and no
resolvable hostname, so `commit-tree` fails with "Author identity
unknown"
> - That one local command failure marks the whole run as failed, even
though the run's work succeeded
> - This pull request gives sync-created merge commits an explicit,
deterministic identity at the call site
> - The benefit is that workspace finalize no longer depends on ambient
host git configuration

## Linked Issues or Issue Description

No existing issue found. I searched issues and PRs for "Author identity
unknown", "unrelated histories", and "commit-tree identity".

**What happened?**

A run finished its work, but workspace finalize failed. The host-side
sync ran `git commit-tree <tree> -p <localHead> -p <importedHead> -m
"Paperclip remote git sync merge <sha>"`. Git exited with `Author
identity unknown ... fatal: unable to auto-detect email address (got
'node@<container-id>.(none)')`. The adapter recorded the whole run as
failed, and the host worktree kept the stale head. Any container
deployment without a global gitconfig reproduces this; I observed it on
a Paperclip Cloud stack.

**Expected behavior**

Commits that the sync machinery itself creates must not depend on
ambient host git configuration. The merge commit is machine-authored, so
it should carry a deterministic Paperclip identity.

**Steps to reproduce**

1. Run the Paperclip server in a container with no
`user.name`/`user.email` git config and a hostname git cannot turn into
an email.
2. Let a run's sandbox branch diverge from the host worktree, so both
sides advance.
3. Workspace finalize calls `integrateImportedGitHead`. The `git
commit-tree` step fails with "Author identity unknown" and the run
fails.

## What Changed

- `git-workspace-sync.ts`: new exported `GIT_SYNC_COMMIT_IDENTITY_ARGS`
(`-c user.name=Paperclip -c user.email=noreply@paperclip.ing`), applied
to the `commit-tree` call in `integrateImportedGitHead`.
- `ssh.ts`: the SSH-sync copy of `integrateImportedGitHead` applies the
same identity args to its `commit-tree` call.
- New regression test: builds divergent histories in a repo with no
configured identity and asserts the sync merge commit is created with
the deterministic identity, correct parents, and merged tree.

## Verification

- `pnpm vitest run
packages/adapter-utils/src/git-workspace-sync.test.ts` — 18/18 pass.
- `pnpm --filter @paperclipai/adapter-utils typecheck` — clean.
- Negative proof: with the source fix stashed, the new test fails on the
identity assertion.
- Full `pnpm vitest run packages/adapter-utils`: every file passes
except `local-process-sandbox.test.ts`, which fails identically on an
untouched `master` checkout on macOS (bubblewrap-dependent,
pre-existing, unrelated).

## Risks

Low risk. The change only adds `-c` identity flags to two
machine-generated commit invocations. `GIT_AUTHOR_*` / `GIT_COMMITTER_*`
environment variables still take precedence over `-c` when an operator
sets them, so existing deployments that configure an identity keep their
behavior.

## Model Used

- Claude Fable 5 (`claude-fable-5`), extended thinking, via Claude Code
CLI (tool use for code exploration, test runs, and 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 (no
doc surface describes this internal sync path)
- [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-18 11:00:54 -07:00
DottaandPaperclip 6b8e42168e Add governed secret alias confirmation cards (#11486)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents need scoped secret bindings to use external services safely.
> - Agents could not request an existing secret under a new config name
without an internal secret identifier.
> - Existing binding proposals were only visible in Settings and did not
create an issue-thread approval path.
> - A confirmation card could record acceptance without proving that the
binding was created.
> - This pull request extends the existing secret proposal system with
safe source references and governed issue-thread confirmation cards.
> - The benefit is a one-click flow that creates the binding or shows a
clear failure without exposing secret material.

## Linked Issues or Issue Description

Related prerequisite: #11482.

**Subsystem affected**

Cross-cutting: server REST APIs, shared interaction contracts, database
proposal schema, and issue-thread UI.

**Problem or motivation**

An agent can need an existing bound secret under a second config name.
The agent cannot safely discover the internal secret identifier. The
existing proposal is also easy for the operator to miss because it only
appears in Settings. A generic confirmation can record acceptance
without executing the binding.

**Proposed solution**

Let an agent create a binding proposal from one of its existing config
paths. Mint a server-owned, human-only confirmation card on the
checked-out issue. Recheck the operator's target-agent permission under
the proposal row lock. Execute the existing proposal transaction after
card acceptance. Store an `executed` or `failed` result on the card.
Render the complete lifecycle in the issue thread and attention
resolver.

**Alternatives considered**

A new alias subsystem would duplicate proposal quotas, expiry,
authorization, and binding synchronization. A text-only issue comment
would not provide a governed action or an execution result. An
agent-supplied card payload would permit metadata smuggling. This change
uses the existing proposal transaction and a server-owned payload
instead.

**Roadmap alignment**

This change extends the completed "Secrets Manager with per-agent
access" roadmap item. It preserves scoped bindings and audited
resolution. The required GitHub search found no other open duplicate
issue or pull request.

## What Changed

- Added safe source-config-path binding proposals and preserved
user-secret ownership checks.
- Added a proposal-to-interaction link and an idempotent database
migration.
- Minted human-only `request_confirmation` cards with server-owned
`secretProposal` metadata.
- Rejected agent-supplied governed metadata and agent addressees.
- Rechecked `agent_config:update` authority under the proposal lock
before execution.
- Recorded `executed` or `failed` results and posted a failure comment
when no binding was created.
- Settled failed accepted proposals atomically and mirrored rejection,
withdrawal, and expiry in both directions.
- Emitted `secret.binding.created` for new agent binding writes.
- Added a dedicated issue-thread card for pending, executed, failed,
rejected, withdrawn, and expired states.
- Showed only the source label, target agent, config path, skeptical
justification, expiry, and safe failure code.
- Replaced resolved attention-query entries immediately with the
stitched server result.
- Added focused server, database, UI, and state-transition tests.
- Added Storybook fixtures for every review state and documented the API
and agent behavior.

## Verification

- `pnpm exec vitest run
ui/src/components/IssueThreadInteractionCard.test.tsx
ui/src/components/AttentionInteractionResolver.test.ts` — 58 passed.
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
- `pnpm build-storybook`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/db check:migrations`
- `NODE_ENV=test pnpm exec vitest run
server/src/__tests__/issue-thread-interaction-routes.test.ts
server/src/__tests__/secret-proposals-routes.test.ts
server/src/__tests__/secrets-routes.test.ts
server/src/__tests__/agents-service-secret-bindings.test.ts` — 142
passed.
- `NODE_ENV=test pnpm --filter @paperclipai/db exec vitest run
src/company-secret-proposals-migration.test.ts --silent` — 1 passed.
- `pnpm -r typecheck`
- `pnpm test:run` — server 4,175 passed, UI 4,109 passed; the CLI
AWS-doctor case passes 8/8 with runtime-injected static AWS credential
variables unset.
- `pnpm build`
- `git diff --check origin/master...HEAD`

## Risks

- Migration `0221` adds one nullable foreign key and one index. It uses
idempotent guards.
- The accept route performs a governed write after it records card
acceptance. A failed write is visible and settles the proposal as
rejected.
- Concurrent proposal and card resolution must use
proposal-before-interaction lock order. A race test covers direct
approval against card rejection.
- The new audit event increases activity rows for newly added agent
bindings. It does not include secret values or fingerprints.
- The card includes only safe proposal metadata. It does not include
secret value, fingerprint, version, or internal secret identifiers.
- The UI uses the stitched resolution result. Focused tests cover
immediate cache replacement and every terminal state.

> This work extends an existing completed roadmap capability. The GitHub
duplicate search returned no other open related work.

## Model Used

- OpenAI Codex with model ID `gpt-5`. The runtime did not expose its
context-window size. Reasoning, repository tools, code execution,
database integration tests, UI rendering, and GitHub tools 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-18 09:44:24 -05:00
Nicky LeachandPaperclip b446ff59bf refactor(acpx-engine): coordinator-owned ACP run lifecycle with a typed resource ledger (#11576)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agent adapters run agent sessions through the ACPX engine
> - The ACPX engine handled one run attempt as a long implicit procedure
> - That shape made resource ownership, cleanup order, and failure
behavior hard to verify
> - This pull request gives the attempt a coordinator, a typed resource
ledger, separate run sites, and explicit turn and settlement sequences
> - The benefit is clear ownership, one cleanup path, safer session
reuse, and testable failure behavior

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The ACPX engine manages startup, turn execution, session reuse, and
cleanup inside one large run procedure.

**Current behavior**

The run procedure owns several resources through implicit control flow.
Cleanup and session reuse behavior depend on lane-specific branches and
error paths.

**Proposed behavior**

The coordinator owns the run attempt. A typed ledger records six
resources and their states. Host and sandbox run sites own lane-specific
acquisition. Turn and settlement sequences expose typed outcomes. The
engine emits allowlisted phase telemetry.

**Reason and benefit**

Explicit ownership makes cleanup and failure behavior easier to inspect.
The fault matrix and characterization tests protect the external result
while the refactor reduces hidden control flow.

**Breaking changes**

None to the public adapter contract. The host warm-save path now closes
and relaunches the runtime because a transferred runtime could retain a
run-scoped credential. A cold session-handshake failure now closes the
created runtime.

**Additional context**

This pull request contains the ACPX engine lifecycle refactor, its
tests, and the lifecycle document.

## What Changed

- Add a run coordinator for startup, turn execution, settlement, and
result reproduction.
- Add a typed resource ledger with open, sealed, and consumed states.
- Add host and sandbox run sites for lane-specific resource acquisition.
- Replace separate runtime maps with a generic session reuse store.
- Split session fingerprint identity from the outer session key.
- Add typed turn and settlement sequences with one cleanup owner.
- Add a closed allowlist for phase telemetry.
- Add characterization tests and a 17-case fault matrix.
- Add `doc/acp-run-lifecycle.md`.

## Verification

- `npx vitest run packages/adapter-utils/src/acpx-engine/` passes 18
files and 286 tests at the submitted commit.
- `pnpm --filter @paperclipai/adapter-utils typecheck` reports 0 errors
at the submitted commit.
- Run the full pull request checks after GitHub starts CI.
- Run Greptile review after the pull request opens.

## Risks

- The refactor changes internal control flow across the ACPX engine.
- Host warm-save behavior now closes and relaunches the runtime.
- Settlement changes the handling of a cold session-handshake failure
from a leak to a close.
- The characterization baselines and fault matrix reduce the risk of an
external behavior change.

> Paperclip is the open source app people use to manage AI agents for
work
> The adapter layer runs agent sessions through the ACPX engine
> The engine needs explicit lifecycle ownership for reliable cleanup
> This pull request adds coordinator-owned phases and a typed resource
ledger
> The result makes lifecycle behavior easier to test and review

## Model Used

OpenAI GPT-5 Codex. Exact model ID: GPT-5. The model used tool
execution, repository inspection, and code review support. The
implementation author supplied the submitted code.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [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-17 22:02:16 -07:00
1c366a9059 fix(server): reject invalid agent credentials instead of downgrading to the local user actor (#11589)
<!-- 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 app people use to manage AI agents for
work
> - The server authenticates each agent request in `actorMiddleware`
before it attributes chat comments
> - When an agent bearer token failed verification, the middleware
called `next()` with no error and the request continued without an agent
actor
> - The request then fell back to the local user actor, so the server
stored agent replies as user comments
> - The task chat UI renders user comments in blue bubbles, so agent
messages appeared as blue user bubbles
> - This pull request rejects invalid agent credentials with 401 instead
of a silent downgrade
> - The benefit is that agent messages keep agent attribution, and
broken credentials fail loudly with a clear retry message

## Linked Issues or Issue Description

**What happened?**

A user cancelled an onboarding question card. The agent posted a
follow-up reply. The reply appeared in a blue bubble, which the UI
reserves for human messages. The agent run held an expired local agent
JWT. The auth middleware could not verify the token, called `next()`
without an actor, and the request fell back to the local user identity.
The server stored the agent comment as a user comment.

**Expected behavior**

Agent messages always render as agent bubbles. A request with invalid
agent credentials must fail with 401 so the adapter can refresh
credentials and retry. It must not post content under a human identity.

**Steps to reproduce**

1. Start a local Paperclip instance.
2. Give an agent run an expired or malformed agent JWT.
3. Let the agent post an issue comment through the API bridge.
4. Before this change: the comment is stored with the local user
identity and renders as a blue bubble. After this change: the request
fails with 401 and a message that tells the caller to obtain fresh
credentials.

## What Changed

- `server/src/middleware/auth.ts`: a bearer token that fails
verification now produces a 401 `unauthorized` error instead of a silent
fall-through to the anonymous/local-user actor.
- The 401 message states the cause: expired token, unverifiable token,
empty bearer token, missing agent record, agent record in another
company, terminated agent, or agent pending approval.
- The API-key path now also rejects an agent record whose company does
not match the key.
- `packages/adapter-utils/src/execution-target.ts`: the bridge proxy now
writes a `comment id: <id>` marker to the run log for each posted issue
comment, so misattributed comments can be traced to a run.
- `ui/src/components/task-chat/task-chat-adapter.test.ts`: a regression
test asserts that a recovered `local-board` comment with a derived agent
author renders as an agent bubble, not a user bubble.
- `server/src/__tests__/agent-auth-middleware.test.ts` and
`packages/adapter-utils/src/execution-target-sandbox.test.ts`: new tests
cover each rejection path and the log marker.

## Verification

- Run `pnpm vitest run src/__tests__/agent-auth-middleware.test.ts` in
`server/` — 14 tests pass.
- Run `pnpm vitest run execution-target-sandbox` at the repo root — 44
tests pass.
- Run `pnpm vitest run
src/components/task-chat/task-chat-adapter.test.ts` in `ui/` — 4 tests
pass.
- Manual check: post an issue comment with an expired agent JWT; the API
returns 401 with a retry message and no comment is stored.

## Risks

- Behavioral shift: requests that previously continued as anonymous or
local-user actors after a failed agent-token verification now receive
401. Any caller that relied on the silent downgrade must refresh its
credentials. This is the intended fix, and the adapters already handle
401 with a credential refresh.
- No schema or migration changes. Low risk otherwise.

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

## Model Used

- Claude (Anthropic), model ID `claude-fable-5`, via Claude Code with
extended thinking and tool use (agent harness with shell, file, and git
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
- [ ] All Paperclip CI gates are green
- [x] 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: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 21:36:11 -07:00
DottaandPaperclip 8087661bb8 fix: bound workspace Git scans (#11572)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Workspaces let users and agents inspect files that belong to an
issue
> - Changed-file views use full-tree Git status scans
> - Many issue views could start those scans at the same time and make
the server unresponsive
> - Route-level limits did not protect the process or coalesce work for
one repository
> - This pull request adds one bounded scheduler for every expensive
workspace Git scan
> - It also starts browser scans only when the file panel is open and
visible
> - The benefit is bounded child-process use and responsive health
checks during request storms

## Linked Issues or Issue Description

**What happened?**

Many changed-file requests could start full `git status --porcelain=v1
-z --untracked-files=all` scans at the same time. One production
incident produced about 270 direct Git child processes. The Node process
stayed alive but stopped answering health requests in time.

**Expected behavior**

Paperclip must bound expensive Git work across all companies, actors,
issues, repositories, and browser tabs. Duplicate requests for one
worktree must share work. Excess requests must fail fast with a
retryable response. Hidden or closed file panels must not start scans.

**Steps to reproduce**

1. Open changed-file views for many issue and actor keys.
2. Send requests for two large workspace roots at the same time.
3. Observe that route-level limiter keys allow many full Git scans to
run together.
4. Observe delayed health responses and accumulated Git children.

**Paperclip version or commit**

Reproduced on master before commit `43ab441f0f`.

**Deployment mode**

Self-hosted server with local workspace repositories.

## What Changed

- Add a process-wide scheduler with configurable concurrency, queue
capacity, timeout, and cache TTL.
- Add fair admission, a bounded queue, canonical worktree keys,
single-flight joins, and bounded result caching.
- Add subprocess timeouts, TERM-to-KILL escalation, bounded output,
waiter cancellation, and slot cleanup.
- Route full-tree status work from file resources, workspace runtime,
execution workspaces, and adapter overlay sync through the scheduler.
- Return stable retryable `503` and `504` error codes for saturation and
timeout.
- Add structured logs with safe workspace hashes, durations, queue
state, cache use, joins, and terminal outcomes.
- Gate UI queries on panel and document visibility. Cancel queries on
close, hide, unmount, and workspace change.
- Disable focus and reconnect bursts. Keep one explicit refresh action
and a retryable unavailable state.
- Document the 10-second default freshness tradeoff and all
configuration variables.
- Add unit, route, UI, adapter, and deterministic 500-request load
coverage.

## Verification

- `pnpm -r typecheck`
- `pnpm build`
- `pnpm check:token-gates`
- `pnpm --filter @paperclipai/server exec vitest run
src/services/workspace-git-operation-scheduler.test.ts
src/__tests__/file-resources-git-scan-load.test.ts --reporter=dot` — 16
tests passed.
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/WorkspaceFileBrowser.test.tsx
src/lib/page-visibility.test.ts --reporter=dot` — 38 tests passed.
- `pnpm --filter @paperclipai/adapter-utils exec vitest run
src/git-workspace-sync.test.ts --reporter=dot` — 16 tests passed.
- Existing file-resource, workspace-runtime, and execution-workspace
regression selections passed.
- Two cleanup safety regressions prove failed scans preserve the
worktree before archive and at the final deletion fence.
- Before: the incident produced about 270 Git children and health
requests timed out.
- After: 500 concurrent requests across 500 issue keys, 73 actors, and
two roots started two underlying scans. Peak scan concurrency was 2. All
500 requests succeeded. Health p99 was 4.94 ms. The harness found zero
unreaped children.
- The full local Vitest run passed 4,267 tests. Ten existing fixed-port
HTTPS exposure tests could not run because this host already owns
Tailnet listeners on ports 42000 and 52000. Clean GitHub CI is the final
full-suite result.
- Latest-head GitHub CI passed all required test, typecheck, build,
canary, e2e, policy, and security gates.
- Greptile completed at 5/5 with zero unresolved comments,
recommendations, or follow-ups.

## Risks

- Changed-file results can be up to 10 seconds old by default. Explicit
refresh remains available.
- A full queue returns a retryable `503` instead of waiting without a
bound.
- A scan that exceeds the default 8-second deadline returns a retryable
`504` and terminates its process group.
- Operators can tune all limits with documented environment variables.
Safe defaults protect local and shared servers.

> 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 does not expose the exact
deployment ID or context-window size. High 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-17 22:11:30 -05:00
Devin Foley 48f4ae16ac fix(codex-local): keep a promoted device-login credential when re-seeding the managed home (#11578)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The codex_local adapter supports a device login that runs in a
trusted sandbox and promotes the credential into the per-company managed
Codex home
> - The managed-home re-seeding step treats every regular-file
`auth.json` as apikey-mode residue and removes it so the shared-home
symlink can be restored
> - The promotion writes the company credential as a regular file, so
the first environment Test or run after a successful login deletes it
> - On a server with no shared Codex login — any containerized
deployment — nothing replaces the file, and the UI reports that the
sandbox has no ready authentication right after it reported a successful
login
> - This pull request makes the cleanup identity-anchored: a
subscription credential whose identity the shared source does not hold
survives re-seeding
> - The benefit is that a device login stays usable after Test and runs,
on hosts with and without a shared Codex login

## Linked Issues or Issue Description

No public GitHub issue covers this. The problem is described in-PR
following the bug template. Related public PRs:
[#11237](https://github.com/paperclipai/paperclip/pull/11237) added the
sandbox device login and the credential promotion,
[#11097](https://github.com/paperclipai/paperclip/pull/11097) added its
building blocks, and the `ensureSymlink` heal for stale copies came from
the fix for #5028.
[#9621](https://github.com/paperclipai/paperclip/pull/9621) touches the
adjacent sandbox auth sync-back lane but not this defect.

**Subsystem affected**

packages/adapters/codex-local — managed `CODEX_HOME` seeding
(`codex-home.ts`).

**Current behavior**

A successful device login promotes the subscription `auth.json` into the
company Codex home as a regular file, and the UI reports the login as
authenticated. The next `seedManagedCodexHome` call — the environment
Test probe and every execute both run it — removes any regular-file
`auth.json` when no API key is configured, because the cleanup assumes
such a file is apikey-mode residue left by a previous run. It then
symlinks `auth.json` from the shared source home. On a server whose
shared home has no Codex login (a container image, for example), there
is no source to symlink, so the home ends with no credential at all. The
Test probe then reports "The sandbox has no ready authentication for
this adapter" immediately after a successful login, and a fresh login
repeats the same cycle. On a server whose shared home does hold a login,
the symlink silently replaces the promoted account with the host
account.

**Expected behavior**

The credential a device login promoted stays in the company home across
Test probes and runs. The #5028 heal (a stale regular-file copy of the
shared credential becomes a symlink to the live source) and the
apikey-residue cleanup keep working.

**Steps to reproduce**

1. Run the server in an environment whose shared Codex home
(`$CODEX_HOME` or `~/.codex`) has no `auth.json`.
2. Complete a Codex device login for a company; the promotion writes the
company home `auth.json` and the UI reports authenticated.
3. Click Test on a codex_local agent (or start a run). The probe reports
no ready authentication, and the promoted `auth.json` is gone from the
company home.

**Proposed solution**

Make the cleanup identity-anchored, the same rule the promotion and the
cache vend already use. A regular-file `auth.json` survives re-seeding
when it holds a usable subscription identity that the shared source does
not also hold, and the shared symlink does not replace it. A
same-identity regular file is still the #5028 stale copy and is still
healed into the symlink, because the symlink serves the same account
with live, rotating tokens. An apikey-mode or unreadable file is still
removed.

## What Changed

- `seedManagedCodexHome` reads the target `auth.json` before the cleanup
and keeps it when `readSubscriptionAccountId` yields an identity the
shared source `auth.json` does not hold. The kept file is excluded from
the shared symlink pass, and the function logs a fixed line when it
keeps the file.
- The function doc comment states the kept-promoted-credential rule.
- Four new `seedManagedCodexHome` test cases: a promoted credential with
no shared auth, a promoted credential with a different shared identity,
the same-identity #5028 heal, and apikey-mode residue removal.

## Verification

```sh
cd packages/adapters/codex-local
npx tsc --noEmit         # clean
npx vitest run           # 28 files, 321 passed, 1 skipped
```

The four new cases fail on the previous code: the first two observed the
promoted file deleted (and, with a shared login present, replaced by the
shared symlink).

## Risks

Low risk. The change narrows one deletion path. Deployments that never
use the device login see no difference: without a promoted subscription
file, the cleanup and the symlink behave exactly as before, and the
#5028 heal is pinned by an existing test plus a new same-identity test.
The one deliberate behavioral shift: after a device login, the promoted
company credential now stays authoritative over the shared host login
for that company — which is the promotion's documented contract ("the
company credential slot").

## Model Used

Claude Fable 5 (`claude-fable-5`), extended thinking, with tool use and
code execution — investigation, implementation, and 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
- [ ] 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-17 20:11:07 -07:00
dmndbrp-ossandTest 7ef75f5636 fix(opencode-local): retry models preflight during transient contention (#9225)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Local CLI adapters are responsible for starting agent runtimes and
validating that their configured models are usable before a run starts.
> - The OpenCode local adapter checks `opencode models` during model
discovery and preflight validation.
> - On hosts with a shared Ollama daemon, that lightweight metadata call
can transiently queue behind an active generation and time out or return
a short failure.
> - Treating that transient contention as a hard adapter failure
prevents otherwise valid local OpenCode runs from starting.
> - This pull request adds a small bounded retry/backoff around OpenCode
model discovery while keeping the existing per-attempt timeout and
surfacing a final failure when retries are exhausted.
> - The benefit is fewer false adapter failures during local Ollama
contention without changing shared Ollama configuration or hiding
genuinely stuck model discovery.

## Linked Issues or Issue Description

No public GitHub issue exists for this adapter reliability bug.

Bug description:
- What happened: `opencode models` can transiently time out or fail
while a shared local Ollama daemon is busy serving another OpenCode
generation, causing the adapter preflight to fail before the actual run
starts.
- Expected behavior: transient model-list contention should be retried
briefly before declaring the adapter unavailable.
- Steps to reproduce: run an OpenCode local adapter using an
Ollama-backed model while another `opencode run` is actively generating
against the same daemon, then trigger model discovery/preflight during
that contention window.
- Paperclip version/commit: observed on the current Paperclip
master-line OpenCode local adapter before this change.
- Deployment mode: local trusted / local CLI adapter execution with a
shared local Ollama daemon.

Related search:
- Searched public GitHub issues for `opencode models preflight retry`;
no matching issue found.
- Searched public GitHub PRs for `opencode models preflight retry`; no
matching PR found. The only search hit was unrelated OpenClaw gateway
authentication work (#6121).

## What Changed

- Added bounded retry/backoff to OpenCode model discovery: three total
attempts with 2s and 4s waits between failures.
- Preserved the existing 20s per-attempt `opencode models` timeout.
- Retry covers timeout and non-zero process exits, while spawn-level
failures still surface immediately.
- Added unit coverage for transient fail -> timeout -> success behavior
and exhausted retry behavior.
- Updated existing OpenCode environment diagnostic tests with explicit
timeouts for the intentional retry/backoff path.

## Verification

- `pnpm --filter @paperclipai/adapter-opencode-local exec vitest run
src/server/models.test.ts src/server/execute.test.ts` -> 2 files passed,
13 tests passed.
- `pnpm --filter @paperclipai/adapter-opencode-local typecheck` ->
passed.
- `pnpm vitest run
server/src/__tests__/opencode-local-adapter-environment.test.ts` -> 1
file passed, 3 tests passed.
- Branch diff against current `upstream/master` is limited to
`packages/adapters/opencode-local/src/server/models.ts`,
`packages/adapters/opencode-local/src/server/models.test.ts`, and
`server/src/__tests__/opencode-local-adapter-environment.test.ts`.

## Risks

Low risk. This only changes OpenCode model discovery behavior and keeps
the preflight bounded. A genuinely unavailable `opencode models` call
still fails after three attempts, and command spawn failures are not
masked.

## Model Used

OpenAI Codex, GPT-5.5 coding agent, tool-enabled repository editing and
shell verification in a local Paperclip workspace.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [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: Test <test@paperclip.ing>
2026-08-17 14:02:30 -07:00
Jannes StubbemannandClaude Fable 5 d77eeb8914 fix(sandbox-bridge): allow the agent-hire skill's routes through the callback bridge (#8978)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Managed agents run inside a sandbox and reach the Paperclip server
only through the sandbox callback bridge, which forwards a fixed route
allowlist (`DEFAULT_SANDBOX_CALLBACK_BRIDGE_ROUTE_ALLOWLIST`)
> - The `paperclip-create-agent` skill instructs an agent to call
adapter/icon discovery endpoints, compare existing agent configurations,
submit a hire request, and link the resulting approval to its source
issue
> - None of those routes were on the bridge allowlist, so a sandboxed
agent following the skill correctly hit `Route not allowed` on every
call — including the hire `POST` itself — making hiring impossible from
inside a sandbox
> - This pull request adds the six routes the skill uses to the bridge
allowlist, while keeping direct agent creation (`POST
/api/companies/:id/agents`) denied
> - The benefit is that hiring works end-to-end for sandboxed agents
through the approval-gated `agent-hires` path, without widening the
bridge beyond what the skill needs

## Linked Issues or Issue Description

No public issue exists; describing the bug in-PR (bug template fields):

- **What happened:** A managed agent running in a sandbox followed the
`paperclip-create-agent` skill and got `Route not allowed` from the
callback bridge on every endpoint the skill documents — adapter
discovery (`/llms/agent-configuration.txt`,
`/llms/agent-configuration/:adapterType.txt`, `/llms/agent-icons.txt`),
config comparison (`GET /api/companies/:id/agent-configurations`), the
hire submission (`POST /api/companies/:id/agent-hires`), and approval
linking (`POST /api/issues/:id/approvals`).
- **Expected behavior:** An agent with hiring permission can complete
the hire flow from inside a sandbox; the bridge forwards the skill's
routes and the server enforces authorization (`canCreateAgents`).
- **Impact:** Hiring by sandboxed agents was fully broken — the failure
is in the transport allowlist, not permissions, so no configuration
could work around it.

Related: #8981 (companion fix making the `paperclip-create-agent` skill
available to agents that can hire; supersedes #8823). The two changes
serve the same end-to-end hire flow but are independently mergeable —
this PR is purely the bridge transport allowlist.

Supersedes #8853.

## What Changed

- `packages/adapter-utils/src/sandbox-callback-bridge.ts`: add six
routes used by the `paperclip-create-agent` skill to
`DEFAULT_SANDBOX_CALLBACK_BRIDGE_ROUTE_ALLOWLIST` (three `GET /llms/...`
discovery routes, `GET .../agent-configurations`, `POST
.../agent-hires`, `POST /api/issues/:id/approvals`), with a comment
documenting why direct agent creation stays denied
- `packages/adapter-utils/src/sandbox-callback-bridge.test.ts`: assert
the six routes are allowed, and add negative cases proving the regexes
do not over-match (no `POST .../agents`, no non-`.txt` or arbitrary
`/llms` files, no `agent-hires` sub-resources)

## Verification

- `npx vitest run
packages/adapter-utils/src/sandbox-callback-bridge.test.ts` — 13/13
tests pass locally
- `npx tsc --noEmit -p packages/adapter-utils` — clean
- Manual: run a managed agent in a sandbox, invoke the
`paperclip-create-agent` skill, and confirm the discovery calls, hire
`POST`, and approval linking all pass through the bridge; `POST
/api/companies/:id/agents` still returns `Route not allowed`

## Risks

- Low risk: additive allowlist entries only; anchored regexes with
`[^/]+` segments prevent over-matching (covered by tests)
- The bridge allowlist bounds surface area but does not replace
server-side authorization — the hire `POST` remains approval-gated and
permission-checked (`canCreateAgents`) on the server

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

## Model Used

- Claude (Anthropic) — Fable 5 (`claude-fable-5`), extended thinking,
agentic tool use via Claude Code; original diff authored with Claude
Opus 4.8 (1M context)

## Checklist

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

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 14:02:24 -07:00
93ff6a8771 fix(cursor-cloud): drop unreachable Paperclip API callback for remote… (#8546)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run via per-adapter execute paths; the `cursor_cloud` adapter
runs the agent in Cursor's cloud (remote), orchestrated server-side via
the Cursor Agent SDK
> - Local adapters receive a run-scoped Paperclip JWT
(`supportsLocalAgentJwt=true`) injected as `PAPERCLIP_API_KEY` so the
agent can call the Paperclip API; `cursor_cloud` is intentionally
`supportsLocalAgentJwt=false` (no JWT minted for a remote worker)
> - But `buildPaperclipEnv` always sets `PAPERCLIP_API_URL` (defaulting
to the local runtime host), so the remote cloud worker is handed a
callback URL it can neither reach nor authenticate against
> - Any agent-initiated Paperclip API call from the cloud worker
therefore fails with a 401 (or is unreachable), producing log noise and
confusing failures
> - This pull request drops the callback wiring when there is no usable
key, so cloud-side Paperclip tools degrade to a clean no-op
> - The benefit is no spurious 401s from remote cloud runs, with run
results unaffected (delivered server-side via the Cursor Agent SDK)

## Linked Issues or Issue Description

No existing public issue — describing the bug inline (per
`.github/ISSUE_TEMPLATE/bug_report.yml`):

**What happened**

`cursor_cloud` runs emit 401s when the remote cloud agent attempts
Paperclip API calls. Root cause: `buildPaperclipEnv`
(`packages/adapter-utils/src/server-utils.ts`) always sets
`PAPERCLIP_API_URL` (local runtime default), while `cursor_cloud` has
`supportsLocalAgentJwt=false`, so no `PAPERCLIP_API_KEY` is minted — URL
present, key absent → 401 / unreachable from `buildWakeEnv` in
`packages/adapters/cursor-cloud/src/server/execute.ts`.

**Expected behavior**

A remote cloud worker that is not issued a run JWT should not attempt
(and fail) Paperclip API callbacks.

**Steps to reproduce**

1. Configure a `cursor_cloud` agent (runs in Cursor's cloud;
`supportsLocalAgentJwt=false`).
2. Trigger a run that causes the cloud agent to make a Paperclip API
call.
3. Observe a 401 (or unreachable) because `PAPERCLIP_API_URL` points at
an unreachable local runtime and no key is present.

**Paperclip version**

Reproduced on current `master` (cutover base `e68188c43`).

**Deployment mode**

Self-hosted control plane, `cursor_cloud` adapter (remote execution in
Cursor's cloud).

**Related PRs (searched; none duplicate this fix):**

- #8197 — `claude_local` opt-out of the sandbox *bridge* for
direct-reachable remote SSH targets. Related family, but the opposite
situation: that path keeps the callback because the remote is reachable
**and** has a run token. `cursor_cloud` has neither, so here the
callback is removed.
- #8130, #4794, #8025 — `PAPERCLIP_API_URL`/loopback injection for
**local** agents (distinct from the remote cloud worker case).
- #401 — alternative agent-auth scheme (run-ID header when no bearer
token); different approach, not overlapping with this targeted fix.

## What Changed

- `packages/adapters/cursor-cloud/src/server/execute.ts`: in
`buildWakeEnv`, when there is no usable `PAPERCLIP_API_KEY`, delete
`PAPERCLIP_API_URL` and `PAPERCLIP_API_BRIDGE_MODE` so the remote worker
performs no Paperclip API callbacks. Informational `PAPERCLIP_*` vars
(run id, agent id, company id, task, wake reason) still flow. When a key
*is* present (operator-provided), the URL is retained.
- `packages/adapters/cursor-cloud/src/server/execute.test.ts`: new test
asserting no callback vars are injected when no run JWT is present;
positive assertion that the URL is retained when a key is present.

## Verification

- `pnpm exec vitest run
packages/adapters/cursor-cloud/src/server/execute.test.ts` → **5/5
pass**.
- `pnpm --filter @paperclipai/adapter-cursor-cloud typecheck` →
**green**.
- Confirmed result delivery does not depend on this callback:
`execute()` reads results server-side via `Agent.getRun()` and
`run.wait()`.

## Risks

- **Low risk.** Only affects the env handed to remote `cursor_cloud`
workers. No schema/migration/behavioral change to result delivery (which
is server-side). When an operator explicitly provides
`PAPERCLIP_API_KEY`, the callback URL is retained, preserving
intentional callback setups.

## Model Used

- **Claude Opus 4.8** (Anthropic), extended/high reasoning mode, via the
Cursor agent with tool use + code execution. Diagnosis grounded in the
adapter/runtime code.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (none found)
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub references)
- [x] My branch name describes the change
(`fix/cursor-cloud-skip-unreachable-callback`) 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 (N/A —
no documented behavior changes)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending CI run)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Sebastian Heyneman <sebastian@joinnova.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-17 14:02:16 -07:00
SamandSami Rusani 14fd8aee36 fix(server): flag truncated issue descriptions (#4771)
## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies.
> - The issue list API is one of the surfaces API consumers use to
synchronize issue metadata.
> - The list endpoint intentionally returns a bounded `description`
preview so large descriptions do not bloat list responses.
> - Before this change, that preview looked like a complete field value
because the response did not say whether it had been shortened.
> - That made round-trip clients vulnerable to accidentally PATCHing a
preview back over the full description.
> - This pull request keeps the existing preview behavior but adds an
explicit `descriptionTruncated` flag.
> - The benefit is backwards-compatible visibility into truncated issue
descriptions, so clients can avoid data-loss workflows.

## Linked Issues or Issue Description

Fixes #4758.

Related PR: #4792 also targets #4758, but it includes unrelated logger
changes and currently has separate review/security concerns. This PR
keeps the fix scoped to the issue-list description truncation API
behavior.

## What Changed

- Added `descriptionTruncated` to the issue list projection when
`description` exceeds the existing 1200-character preview limit.
- Exposed `descriptionTruncated?: boolean` on the shared `Issue` type.
- Added service tests for truncated descriptions, exact-limit
descriptions, null descriptions, and multibyte-safe preview truncation.

## Verification

June 18, 2026 refresh after rebasing onto current `origin/master`:

- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm typecheck`
- `git diff --check origin/master...HEAD`
- GitHub PR checks are green on head `12e828e6`.

Earlier pre-review verification also included `pnpm test`.

## Risks

- Low risk. This is an additive API response field; existing clients can
ignore it.
- The list endpoint still returns the same bounded `description`
preview. Clients that need full text should continue fetching the issue
detail, but can now detect when that is necessary.
- No database migration or UI behavior change.

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

## Model Used

- OpenAI Codex based on GPT-5, via Codex desktop on April 29, June 15,
and June 18, 2026. Used tool-assisted repository inspection, code
editing, local test execution, GitHub CLI workflows, and PR review
follow-up. Exact context window size is not surfaced by the tool.

## 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 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 (N/A: no UI change)
- [x] I have updated relevant documentation to reflect my changes (N/A:
additive API field covered by tests)
- [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: Sami Rusani <sr@samirusani>
2026-08-17 14:02:01 -07:00
Nicky LeachandPaperclip c1c46f1e4e feat: Claude login on the new-agent page before agent creation (#11347)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Claude local adapter supports subscription login through a
sandbox
> - The new-agent page must show login before the user creates an agent
> - Test results must not expose raw sandbox diagnostics or secret
values
> - This pull request adds the login UI to both Test lanes and closes
the diagnostic boundary
> - The branch also adds durable cleanup recovery for failed sandbox
teardown
> - Reusable sandboxes must retain both their recorded teardown
configuration and a valid lifecycle path until destruction succeeds
> - The benefit is a usable login flow with fixed public checks,
redacted server logs, and recoverable sandbox cleanup

## Linked Issues or Issue Description

Related public work:
[#9488](https://github.com/paperclipai/paperclip/pull/9488) adds
first-class recognition for `CLAUDE_CODE_OAUTH_TOKEN` in headless and
remote runs. Related public issue:
[#2681](https://github.com/paperclipai/paperclip/issues/2681) requests
Claude Code subscription support. This pull request adds the login
transport and new-agent UI flow that those changes do not provide.

**Subsystem affected:** Claude local adapter, server login probes,
sandbox provider setup, cleanup recovery, and the new-agent UI.

**Problem or motivation:** The Test lanes did not show the sandbox login
panel in all supported cases. Test results also exposed raw probe
diagnostics, and JSON escapes could end secret redaction early.

**Proposed solution:** Surface the login capability through the bundled
provider manifest. Prepare the same probe runtime in the ACP lane. Send
diagnostics only to redacted server logs. Keep Test checks on fixed
public messages. Normalize login URL hints to allowlisted HTTPS Claude
and Anthropic hosts. Consume JSON escapes during redaction. Preserve
failed sandbox cleanup state across retries and restarts, and prevent
deletion from severing the lifecycle context of a live reusable sandbox.

**Alternatives considered:** Keep raw diagnostics in Test checks or
trust login URL text from the sandbox. Both choices increase information
exposure. Keep separate probe behavior in the ACP lane. That choice
would leave the two Test lanes inconsistent.

## What Changed

- Surface the sandbox login panel on both Test lanes.
- Reconcile the bundled Daytona plugin manifest so
`supportsSetupTokenLogin` reaches the UI capability gate.
- Prepare the ACP Test lane with the same probe runtime as the CLI Test
lane.
- Add the `claude_acp_login_probe_unavailable` warning when the ACP
probe cannot run.
- Send raw sandbox diagnostics only to redacted server logs.
- Keep Test checks on fixed public messages in the ACP, managed-config,
and CLI paths.
- Normalize login URL hints to allowlisted HTTPS Claude and Anthropic
hosts.
- Redact JSON and escaped-JSON secret values, including escaped quotes
and backslashes.
- Preserve orphan cleanup records across provider failures, restarts,
and unavailable plugins.
- Atomically block environment deletion while a live reusable sandbox
lease still depends on it.
- Verify pending cleanup destroys plugin sandboxes with the provider
configuration recorded on the lease, even after the current environment
configuration changes.

## Verification

- Head under review: `506b7fa2d83c36bfa5fd722ee9d95b0c7431c241`.
- Focused environment route/service/runtime coverage passes: 196 tests
across 3 files.
- `pnpm -r typecheck` passes.
- `pnpm build` passes.
- The full Vitest run completed with 4,754 passing and 28 failing tests.
All 23 source-test failures reproduce unchanged on parent head
`58cfe61a33191ce03d965d65085d26064b4888ba`; the other 5 are duplicate
executions from stale `server/dist` output. The failures are unrelated
macOS path/listener and scheduler-fixture failures, so there is no new
bad commit for bisect to localize.
- All required CI checks pass for the current head, including build,
typecheck/release registry, all server and workspace shards, serialized
server suites, canary, and e2e.
- A fresh Greptile review for `506b7fa2d83c36bfa5fd722ee9d95b0c7431c241`
reports 5/5, “safe to merge,” with no blocking failure remaining.

## Risks

- A probe or redaction change could hide useful server diagnostics.
- An allowlist change could reject a valid Claude login URL.
- Cleanup recovery changes could affect provider teardown ordering.
- An environment with a live reusable sandbox can no longer be deleted
until the owning issue or execution workspace completes teardown.
- The implementation keeps public Test messages fixed and sends detail
to redacted server logs.

## Model Used

OpenAI GPT-5 via Codex — exact model ID: GPT-5; tool use and code
execution enabled; extended reasoning enabled. The implementation author
used AI-assisted development.

## 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 documented the result
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation or confirmed no separate
documentation change is needed
- [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-17 13:42:51 -07:00
Nicky LeachandPaperclip 3061ce6901 feat(sandbox): stream session output by capability, drop three operator flags (#11557)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandboxed agents use provider capabilities to select safe execution
paths
> - Session output still depends on three operator flags that duplicate
capability data
> - Duplicate flags can drift from the verified sandbox capability
snapshot
> - This pull request makes the capability snapshot the only streaming
decision and removes the obsolete flags
> - The benefit is default streaming with a poll fallback when a
capability or stream fails

## Linked Issues or Issue Description

**What existing behavior does this improve?**

ACP sandbox session-output streaming and sandbox execution
configuration.

**Subsystem affected**

Cross-cutting (multiple of the above): server/, packages/shared/,
packages/adapter-utils/, and packages/plugins/.

**Current behavior**

Session-output streaming requires operator flags in the server and
Daytona plugin configuration. Saved configurations can retain a removed
key.

**Proposed behavior**

The verified capability snapshot selects streaming. The Daytona plugin
uses persistent sessions by default, keeps bypass commands one-shot, and
falls back from the log stream to polling. Removed configuration keys
become inert.

**Reason and benefit**

One capability source prevents configuration drift. The fallback keeps
output available when capability resolution or log streaming fails.

**Breaking changes**

The three operator flags no longer control session-output streaming.
Existing saved keys load but have no effect.

## What Changed

- Remove `useSessions` and `useLogStream` from the Daytona plugin
configuration and manifest.
- Remove `streamAgentSessionOutput` from server configuration, shared
types, and execution-target plumbing.
- Select streaming from `persistentProcessSessions` and
`independentControlCommands`.
- Keep poll fallback on capability resolution failure and stream
failure.
- Strip removed keys from strict fake-sandbox and catchall plugin
configuration.
- Update the sandbox capability documentation and focused tests.

## Verification

- `tsc --noEmit` passed in `packages/shared`, `packages/adapter-utils`,
`server`, and the Daytona plugin.
- Daytona `plugin.test.ts` passed 139 tests.
- Server capability, configuration, route, and runtime suites passed 160
tests.
- `packages/adapter-utils` `execution-target-sandbox.test.ts` passed 44
tests.
- The capability matrix covers stream, poll, and resolution-failure
paths.
- Removed-key tests cover strict fake-sandbox and catchall plugin
schemas.

## Risks

- A capability snapshot that lacks either required session capability
uses polling.
- A log stream failure uses polling and can increase request count.
- Existing removed configuration keys no longer change behavior.
- The isolated-worktree Daytona Vitest run has a pre-existing missing
`packages/adapters/droid-local` reference. CI and standard checkouts use
the committed configuration.

## Model Used

OpenAI Codex, GPT-5, tool use and code review assistance. The exact
runtime context window is managed by the Codex platform.

## 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-17 13:25:30 -07:00
Nicky LeachandPaperclip e71ce9a9d3 feat: sandbox provider capability contract with fail-closed effective resolution (#11463)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip runs work through adapters and sandbox providers
> - Providers need a clear contract so the server can use only verified
capabilities
> - A declared capability must not grant a method that the live worker
did not verify
> - This pull request adds manifest declarations and fail-closed
effective capability resolution
> - The benefit is safe provider reuse across execution targets and run
lifecycles

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting (multiple of the above)

**Problem or motivation**

Sandbox providers expose different runtime methods. The server needs one
safe capability contract that accounts for provider declarations, worker
verification, and narrowing configuration.

**Proposed solution**

Add strict manifest validation for five sandbox capabilities. Resolve
effective capabilities as the subset of verified, declared, and narrowed
values. Store the result as a frozen execution-target snapshot.

**Alternatives considered**

Trusting the manifest alone could grant methods that the worker does not
support. Trusting only a fixed built-in list would reject valid
third-party providers. The intersection rule keeps the verified runtime
ceiling and supports both provider types.

**Roadmap alignment**

This change supports the ACP run lifecycle track and the sandbox
provider contract work in the current roadmap.

**Additional context**

The legacy `supportsReusableLeases` field remains supported. The nested
capability validator rejects unknown keys. Missing or unavailable
verification resolves all capabilities to `false`.

## What Changed

- Add strict `sandboxCapabilities` manifest validation with legacy
reusable-lease compatibility.
- Carry declarations through the ready-driver projection.
- Add fail-closed effective resolution from verified, declared, and
narrowed capabilities.
- Add narrowing for provider configuration, Kubernetes Job leases, and
Daytona sessions.
- Add a frozen read-only capability snapshot to execution targets.
- Add focused tests and keep existing characterization baselines
covered.
- Add and update sandbox provider capability documentation.

## Verification

- `npx vitest run packages/shared/src/validators/plugin.test.ts`
- `npx vitest run
server/src/__tests__/plugin-environment-driver-sandbox-capabilities.test.ts`
- `npx vitest run
server/src/__tests__/sandbox-capability-contract.test.ts`
- `npx vitest run
server/src/__tests__/environment-execution-target-capabilities.test.ts`
- `npx vitest run
packages/adapter-utils/src/acpx-engine/startup-characterization.test.ts
packages/adapter-utils/src/acpx-engine/turn-characterization.test.ts
packages/adapter-utils/src/acpx-engine/settlement-characterization.test.ts
packages/adapter-utils/src/acpx-engine/composed-run-characterization.test.ts`
- Package typechecks for shared, server, and adapter-utils pass.
- Stage-2 security review suites pass with 28 tests.

## Risks

The resolver fails closed when verification is absent or unavailable.
Providers that rely on undeclared capabilities may see narrower behavior
until they expose verified worker methods. The change does not alter the
existing native-sync guard.

## Model Used

OpenAI Codex, GPT-5, exact runtime model ID `gpt-5`, tool use and code
execution. The implementation author used this model to assist with the
change.

## 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-17 10:55:40 -07:00
Dotta f4802b1bbc feat(runtime-exposure): least-privilege Tailscale HTTPS broker, shared contract, and persisted exposure state (#11524)
<!-- Simplified Technical English (ASD-STE100). -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip starts and supervises managed runtime services for a
project's execution workspaces, so an agent's branch can be previewed
while it works
> - Those services only listen on plain loopback HTTP. A person on
another device, or on a phone, cannot open the preview
> - A Tailscale HTTPS mapping solves this, but `tailscale serve` needs
host privileges that the Paperclip server process must not hold
> - This pull request adds the foundation only: a separate
least-privilege host broker, the shared exposure contract, and the
database columns that hold exposure state
> - Nothing calls the broker yet, so there is no behavior change. The
benefit is that the privileged surface is small, reviewable, and
isolated before any lifecycle code depends on it

## Linked Issues or Issue Description

No public GitHub issue exists. The change follows the feature request
template.

**Subsystem affected**

Managed workspace runtime services, the shared type and validator
package, and the database schema.

**Problem or motivation**

A managed runtime service binds to loopback only. There is no supported
way to reach that preview from another device. Adding HTTPS directly to
the server would mean the server process runs `tailscale serve`, which
needs privileges far wider than the task requires. A compromised or
buggy server could then map any port to the tailnet.

**Proposed solution**

Split the privileged work into a separate broker process with a narrow
protocol, and define one shared contract that the server, the UI, the
runtime, and the broker all read. Land this foundation first, with no
caller, so the privileged code can be reviewed on its own.

**Alternatives considered**

- Call `tailscale serve` from the server process. This was rejected
because it gives the server unrestricted mapping authority.
- Use `sudo` for single `tailscale` commands. This was rejected because
the argument list is the only guard, and it is easy to widen by
accident.
- Use a generic reverse proxy. This was rejected because it does not
remove the need for a privileged Tailscale mapping step.

**Roadmap alignment**

This supports the existing managed workspace runtime capability. It adds
no new product surface on its own.

**Additional context**

The broker is the security boundary of the feature, so it is
deliberately the first slice. Three later pull requests build on it: the
server exposure lifecycle, the runtime lease and recovery integration,
and the leased-port mediator.

## What Changed

- Add the `@paperclipai/tailscale-https-broker` workspace package. The
broker listens on a unix socket, authorizes each peer with
`SO_PEERCRED`, and answers a small request protocol.
- Restrict what the broker will map. It accepts only same-number
HTTPS-to-loopback pairs inside the Paperclip port range, refuses
protected ports, and confirms that the loopback port belongs to a
Paperclip-owned listener.
- Parse every request with a strict JSON reader that rejects duplicate
keys, prototype keys, and unknown fields.
- Write an append-only audit record for each broker decision.
- Add the shared exposure contract in `@paperclipai/shared`: the
`RuntimeExposureConfig`, `RuntimeExposureState`, and
`RuntimeExposureStatus` types, their zod validators, the app and HMR
port rules, and the loopback-bind helpers.
- Persist exposure state on `workspace_runtime_services` with the new
`exposure` column, plus the server-private `exposure_handle` and
`backend_url` columns that are never serialized to API clients.
- Add the `execution_workspace_runtime_leases` table that the later
lease slice uses.
- Extend the runtime read-model test fixture for the three new columns.

## Verification

Focused checks, all run on this branch:

- `pnpm --filter @paperclipai/tailscale-https-broker test` — 12 files,
82 tests pass. This covers peer credentials, port policy, protected
ports, the serve config writer, the strict JSON reader, argv parsing,
and the socket server.
- `pnpm --filter @paperclipai/tailscale-https-broker typecheck` — clean.
- `npx vitest run --root packages/shared src/runtime-exposure
src/validators/runtime-exposure.test.ts` — 3 files, 40 tests pass.
- `pnpm --filter @paperclipai/db typecheck` — runs `check:migrations`
first. Migration numbering and migration safety both pass.
- `pnpm --filter @paperclipai/shared typecheck` — clean.
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `npx vitest run --root server
src/services/workspace-runtime-read-model.test.ts` — 3 tests pass.
- `npx tsc --noEmit -p server/tsconfig.json` — 139 errors, which is
exactly the count on `master` before this branch. All 139 come from the
unbuilt `@paperclipai/plugin-sdk` package.

To confirm the exposure state is inert, start a managed runtime service
as usual. The new columns stay null and the service behaves as it does
today.

## Risks

- Migration risk is low. Both migrations only add a table and three
nullable columns. No column is backfilled and no existing column
changes. The migration safety check passes.
- Behavior risk is low. No code path calls the broker in this pull
request, and the shared exposure fields are optional.
- The broker is privileged, so it is the real risk surface. It is
mitigated by peer-credential authorization, a fixed port range, a
protected-port deny list, same-number pair enforcement,
listener-ownership checks, strict JSON parsing, and an audit trail.
Reviewers should read
`packages/tailscale-https-broker/src/authorization.ts` and
`src/port-policy.ts` closely.
- The broker requires a `tailscale` version floor, which its README
records. An older host CLI makes the broker refuse to start rather than
map incorrectly.
- `pnpm-lock.yaml` changes because a new workspace package is added. The
diff is the new importer block, plus one duplicate `tinyexec` entry that
pnpm removed.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR.

## Model Used

Claude Opus 5 (`claude-opus-5`), 1M context window, extended thinking,
with tool use and code execution.

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Issue interactions give agents and people a structured decision
record.
> - Resolver routes used different authorization rules.
> - Some routes blocked valid agents, including task watchdogs with
normal issue access.
> - The API did not show who could resolve a pending interaction.
> - This pull request gives every interaction kind one resolver policy
evaluator.
> - The benefit is a clear decision path with consistent governance and
company isolation.

## Linked Issues or Issue Description

Fixes: #8087

Refs: #7403

Related PR: #11082 proposes board-only confirmation rules. This change
keeps human-only review as an explicit policy.

**What happened?**

Agents could create issue interactions. Some resolver routes still
required board access.

This left valid agent confirmations pending. Task watchdogs could see
the same problem without board identity.

**Expected behavior**

Every interaction kind must use one resolver policy contract.

The contract must support `anyone`, `not_creator`, and `human_only`. It
must also apply all normal governance controls.

**Steps to reproduce**

1. Create a `request_confirmation` interaction as an agent.
2. Resolve it with another authorized agent.
3. Observe the board-only denial.

**Paperclip version or commit**

The problem exists on `master` before this change.

**Deployment mode**

Local development with `pnpm dev`.

## What Changed

- Add canonical policies for `anyone`, `not_creator`, and `human_only`.
- Use one server evaluator for every interaction kind.
- Apply named addressees, company limits, review rules, and task
watchdog scope.
- Charge cross-issue resolutions to the existing per-run action limit.
- Return the effective resolver audience in attention and interaction
data.
- Show the audience, governance choices, and denial reasons in the board
UI.
- Add telemetry, API documents, product documents, and regression
fixtures.
- Add migration provenance for safe legacy behavior.
- Make migration `0218` safe for complete replays and partial prior
runs.

## Product Rules

- An interaction records a response. It does not grant authority for the
next action.
- `anyone` lets any authorized issue participant respond.
- `not_creator` requires a responder other than the interaction creator.
- `human_only` requires an authorized person.
- A named addressee, company policy, or governed action can narrow the
audience.
- These controls cannot widen the audience.
- A task watchdog uses the same rules as an ordinary agent.
- A task watchdog does not receive board authority.
- An agent resolution on another issue uses the shared cross-issue
action limit.
- Legacy pending interactions keep their earlier restrictions.
- The UI shows the effective audience and a permanent denial reason.

## Verification

- `pnpm --filter @paperclipai/db check:migrations`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run
packages/db/src/issue-thread-interaction-resolver-policy-migration.test.ts`
- The focused PostgreSQL test applies migration `0218` twice.
- The test also completes a partial prior run and preserves existing
provenance.
- The latest GitHub head has 29 successful checks.
- The opt-in Storybook visual check skipped as expected.
- Greptile reports 5/5 with no open comments.

## Risks

- New interaction writes use `anyone` by default.
- Callers must select `not_creator` or `human_only` when they need
stricter review.
- Legacy pending interactions keep the old creator and human
restrictions.
- Migration `0218` fills only missing provenance fields during recovery.
- Cross-issue resolutions can reach the existing action limit.
- The shared evaluator affects every interaction kind.
- Route, service, database, shared contract, and UI tests cover these
rules.

> This work matches the Agent Reviews and Approvals direction in
`ROADMAP.md`. It does not duplicate a planned item.

## Model Used

OpenAI Codex, GPT-5. The runtime does not expose the exact deployment ID
or context window.

The agent used reasoning, repository tools, shell commands, 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 linked public issues or described the issue with the
required labels
- [x] I have not referenced internal Paperclip issues or links
- [x] My branch name describes the change and contains no internal
ticket id
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation
- [x] I have considered and documented the risks
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open comments
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-16 13:46:50 -05:00
TonioandClaude Opus 5 92047cac46 test(adapter-utils): make the next sandbox flake diagnosable (#11483)
`execution-target-sandbox` has failed twice in CI and not once in several
hundred local runs. This does not fix it. It makes the next occurrence carry
its own evidence, because a third unreproducible failure would teach nothing.

The observed signature was an empty stdout with exit code 0 - the child exited
cleanly having produced nothing, which is what a lost stdin frame looks like
from the test's side. Three mechanisms were checked and ruled out rather than
assumed: the helper resolving on `exit` rather than `close` (a 200-iteration
probe produced no truncations, and the failure was empty rather than partial);
the wrapper reporting exit before stdout drains (it already listens on
`close`); and frame writes racing (the stream wrapper's `writeEvent` is
synchronous and sequence-numbered).

Two candidates remain and the runtime tree separates them. A stdin queue frame
still present means the host wrote it and the wrapper never consumed it; a
drained queue with no output means it was consumed and the reply was lost on
the way back. The report prints that tree, both proxy streams, the exit code,
and the elapsed time - the last because the bridge and proxy run on 5s budgets
that are generous locally and tight on a runner sharing a box with 19 other
lanes.

Timeouts are deliberately unchanged. Raising them would probably make the
symptom go away, which is the reason not to do it blind.

The first revision capped the tree walk one level above the queue frames, so
"the queue is empty" and "the walk never looked" printed identically - the
distinction the report exists to make. Caught in review. Verifying that the
reporter printed something was not enough; it had to print the thing that
discriminates, which is now checked by planting a frame and forcing the
assertion.

adapter-utils typecheck clean; 44 pass, stable across repeated runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 10:47:35 -07:00