Commit Graph
1564 Commits
Author SHA1 Message Date
DottaandPaperclip 6d4e65f621 fix(smoke-lab): seed organization grants
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:39:40 +00:00
DottaandPaperclip 89468893b9 feat(apps): resolve credentials through grants
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:23:02 +00:00
DottaandPaperclip 1880c9d21e feat: enforce connection access permissions
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip 5f7926a5d5 fix(apps): include connection lifecycle activity
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip 8ed119deb6 fix(apps): aggregate connection activity
Read the cross-app activity feed from the canonical tool call events used by each connection, preserving filters, pagination, gateway scope, and redacted invocation details.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip 84c2b30358 feat(apps): confirm connections and cache MCP actions
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip e598c41771 fix(apps): preserve MCP follow-ups after master sync
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip 51e5195135 fix(workspaces): bind commands to configured runtime rows
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip 142bcdd1c7 fix(workspaces): restore managed runtime origin hint
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip 3d3a12a3e2 fix(workspaces): keep inventory reads DB-only
Backport the existing master guard so this long-lived feature worktree does not fan out Git scans across its cloned workspace inventory.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
Dotta b4b59388e5 fix(auth): support managed loopback session cookies 2026-08-20 21:20:02 +00:00
DottaandPaperclip faaa0ce428 fix(apps): complete MCP connection follow-ups
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip ca94577206 feat: enable MCP tools by default
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip 18d160d66a fix(oauth): fall back from private CIMD origins
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip b0515d65a2 fix(security): guard connection token brokers
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip bfc8fd81af fix(oauth): return failed callbacks to setup
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandClaude Opus 5 1d000bc903 fix(security): make removing an app revoke its credentials
Removing an app only flipped the connection to `archived`. Everything that
made it work stayed behind: the operator's pasted key or OAuth tokens as live
secrets, the `company_secret_bindings` that project them into runtimes, the
install, the app-managed `app:<connectionId>` profile, gateway tokens minted
against that profile, and any authorization still in flight. Reconnecting
reused the retained credential, so "remove" never actually revoked anything.

Removal is now a teardown, and the order is the security property. Every
database-side access path closes first — grants revoked, bindings and installs
deleted, the app profile removed (archived instead when a gateway's RESTRICT
foreign key forbids deleting it, with that gateway's tokens and sessions
revoked), live local runtimes stopped, catalog entries marked `removed`,
outstanding OAuth state discarded, the connection and its application archived
— so the app is undispatchable before the first call out to a secret provider.
Only then are the credentials revoked, and each secret's ref survives until
that secret is gone: a provider that errors leaves the operation failed closed
and resumable, because the secret row is marked deleted before the provider
call and the surviving refs are what a retry reads to finish the job.

A secret is destroyed only when two independent tests agree: it was minted
into the `tool_app.` namespace by connect/reconnect/OAuth, and nothing outside
this connection references it. Anything ambiguous keeps its row and is
reported as retained — deleting a credential another target still resolves is
an outage, retaining an unreachable row is not.

What stays is deliberate. Connection and application identity, names and
activity survive so a reconnect reuses them, but with no credential, install
or profile, reconnecting has to ask for fresh authentication and rebuild
access. The activity receipt carries cleanup counts only; six of those count
keys name what they counted ("secrets", "token"), so `sanitizeRecord` lets
them through while the value is a finite number and still blanks the same key
carrying anything else.

Refs PAP-17119, PAP-17118, PAP-17089 (QA finding F6).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 21:20:02 +00:00
DottaandPaperclip ba603abace fix(apps): guide generic MCP connection failures
Map OAuth origin, duplicate-name, and DNS failures to stable operator guidance, and derive collision-resistant defaults from host, port, and path.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip 048c3578e3 fix(server): enforce MCP wizard action scope
Prevent connection installs from adding connection-wide allow entries, keep wizard-managed app profiles additive across binding precedence, and repair legacy over-grants with a data migration. Add decision-level regression coverage for generic and curated MCP paths.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandPaperclip 5634c2b966 fix(security): omit query strings from HTTP logs
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:02 +00:00
DottaandClaude Opus 5 f7023b205c fix(security): consume and actor-bind OAuth state on a denied callback
A denial or a cancel is a final answer to an authorization request, but
`completeOAuthCallback` treated it as merely a failed callback: the row
in `tool_oauth_states` survived, so the `state` the user just refused
stayed completable for the rest of its 10-minute TTL by anyone who could
produce a code. Any linked `request_confirmation` also stayed `pending`,
so the board kept offering an authorization link for a flow the user had
already declined and the requesting agent never learned the answer.

Route every terminal callback — success, denial, cancel — through one
`consumeOAuthState` helper. Two properties pull in opposite directions
and both matter:

- a stranger's callback must not *consume* the request, so the row is
  loaded and bound to the caller (expiry, actor type/id, session, or the
  subject user) before anything is deleted, and a failed binding check
  leaves the victim's flow live and completable;
- a replayed callback must not *complete* the request, so the delete is
  the single statement that decides ownership — `DELETE … RETURNING`
  hands the row to exactly one of two concurrent callbacks and the loser
  is told the state is spent rather than exchanging a code against it.

A denial then resolves any linked interaction as `rejected`, scoped to a
still-`pending` row so a board user who answered the card directly keeps
their own answer, with a Paperclip-authored reason rather than the
provider's.

The message and its `providerError` label are PAP-17108's, unchanged.

Five regression tests, both negative-controlled: a code obtained before
a denial can no longer be exchanged; an unrecognized cancel code behaves
the same; another session's or another user's denial is refused without
spending the request, which the owner then completes; the board's prompt
ends `rejected`; and two simultaneous callbacks produce exactly one
token exchange. That last one needs a second database handle — this
driver pipelines everything issued through one handle, which serializes
the two callbacks and makes a non-atomic delete pass — and it has to
connect before racing, or the first callback wins uncontested.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 21:20:02 +00:00
DottaandClaude Opus 5 fc9f6619b1 fix(security): redact upstream OAuth provider errors instead of reflecting them
A generic remote MCP connection points at an arbitrary authorization server, so
`error`, `error_description`, `error_uri` and the response body are all
attacker-chosen. Paperclip was returning the provider's `error_description`
verbatim as the public error message on the callback-denial, token-exchange and
dynamic-client-registration paths, and the callback did it before validating the
state at all. A hostile provider could therefore plant a canary secret, ANSI
escapes, or "paste your recovery key here" instructions into API responses,
board UI copy, and 4xx log lines — in Paperclip's own voice.

The operator now only ever reads text Paperclip authored:

- `normalizeOAuthProviderError` reduces the provider's `error` to a bounded,
  allowlisted label (RFC 6749 §4.1.2.1/§5.2, RFC 7591 §3.2.2, OIDC Core
  §3.1.2.6). An unrecognized code collapses to `unrecognized` rather than being
  echoed, since the label is still untrusted input.
- `oauthProviderErrorMessage` maps that label to stable Paperclip copy. The
  label plus the upstream HTTP status stay in structured `details` for
  diagnosis; the description and body are never read on any path.
- `error_description` is gone from the callback route and from the
  `completeOAuthCallback` input type, so there is nothing left to reflect
  through, and `redactSensitive` scrubs it from the request query that
  `customProps` copies into 4xx log lines.
- The callback validates the state binding and the actor before acting on a
  provider-reported failure, and consumes the state so a denial is terminal.

Refresh goes through the same token-exchange path, and revocation makes no
provider call, so both are covered. Tests plant a canary secret, ANSI escapes
and markdown in `error_description` and assert none of it reaches an API
response, a thrown message, a console line, an audit row or an activity detail;
both assertions were negative-controlled by restoring the reflection.

PAP-17108. Finding 3 of the PAP-17088 security review.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 21:20:02 +00:00
DottaandClaude Opus 5 0aaf6ddbea fix(server): harden the pinned remote MCP transport's failure paths
Pinning to `approved[0]` alone dropped the failover `fetch` gave a
multi-homed host, so one dead A record took the whole connection down.
Walk the approved addresses in resolution order instead — but only for
addresses that are unreachable. A peer that answers from an unapproved
address is the rebinding defence firing, not a reachability problem, so
it still fails closed on the first try rather than being retried past.

Also close the gaps the response deadline left behind:

- a connect timeout used to leak the half-open socket, since nothing
  above that point owned it yet;
- both deadlines threw bare `Error`s, so callers that switch on the
  guard's error codes saw a timeout as an unclassified failure — they
  now carry `remote_http_response_timeout`;
- a finished response left an armed socket timer behind; and
- the default deadline drops from undici's 300 s to 30 s, which is
  already generous for metadata discovery, a token exchange or a
  JSON-RPC round trip. `tools/call` owns a caller-set budget an operator
  can raise to 60 s, so it passes `responseTimeoutMs` explicitly rather
  than being truncated by the tighter default.

Covers each with a regression test, including a peer that dribbles
header bytes forever — the case that rules out `req.setTimeout`, whose
idle clock such a peer can reset indefinitely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 21:20:02 +00:00
DottaandClaude Opus 5 920fe47d35 fix(security): validate discovered OAuth endpoints before browser navigation
A generic remote MCP connection lets the server it points at advertise its own
authorization-server metadata, and `authorization_endpoint` was accepted as an
arbitrary string: `startOAuth` built a `URL` from it and the board passed the
result to `window.location.assign`. A hostile server could therefore advertise
`javascript:`/`data:` to run script in the board's origin, `file:` to read a
local path, or plain `http:`/a credential-bearing URL to downgrade or disguise
where the operator was being sent.

Parse every OAuth endpoint through one shared validator instead — discovered
metadata, `WWW-Authenticate` hints, pasted/persisted config, and gallery
defaults alike. It requires https (loopback http only under the existing
local-development policy, plus this deployment's own origin, which is how the
smoke-lab fixture is served), and rejects embedded credentials, fragments and
malformed values. Discovery drops an unusable endpoint so a second advertised
authorization server still gets a chance, and raises the refusal only if nothing
safe was found — so an unsafe value is never persisted and never reported as a
bare "does not advertise OAuth sign in". The token and registration endpoints go
through the same gate, so a row written before this existed cannot send a code or
client secret over plaintext.

The board applies the same validator at the navigation boundary rather than
trusting the response body, and — since a valid HTTPS page can still phish —
names the authorization host on the redirect screen while keeping the
"Unverified server" label.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 21:20:01 +00:00
Dotta b2bf4599c3 fix(server): give the pinned remote MCP transport a response deadline
The pinned transport replaced platform fetch, and with it undici's
headersTimeout/bodyTimeout. Only the gateway tools/call path passes an
AbortSignal; the OAuth discovery, registration and token-exchange callers
pass none, so a remote server that accepted the connection and then stayed
silent would hold the request open indefinitely. That is a hang the code
this replaced did not have.

Adds a 300s default matching undici, applied as a hard headers deadline
(not req.setTimeout, which is socket-idle based and a dribbling server
could reset forever) plus an idle deadline between body chunks so a
half-sent body rejects in response.text() instead of hanging the caller.

Both new tests hang to the vitest timeout when the deadlines are removed.

Refs PAP-17098
2026-08-20 21:20:01 +00:00
Dotta c281bd1705 fix(server): pin remote MCP egress to the approved address
assertPublicRemoteHttpEndpoint resolved an operator-supplied hostname,
approved it, and then handed the *hostname* to global fetch, which
resolved it a second time. A name server the attacker controls can answer
with a public address for the guard's lookup and a loopback, RFC 1918 or
link-local address for the connection, so an authenticated public
deployment could be walked onto internal services or cloud metadata.

The guard now returns the address set it approved, and
guardedRemoteHttpFetch dials that address itself: one resolution, no
second lookup below it, the original hostname kept for Host, TLS SNI and
certificate identity, and a peer-address check before a single request
byte (or ClientHello) is written. Redirects stay manual so the caller
re-runs the whole guard on every hop it follows.

Wired into all three guard-then-fetch sites: OAuth discovery/registration/
token exchange, catalog tools/list, and the gateway tools/call path.
Two cases keep platform fetch, both because there is nothing to pin:
deployments that allow private endpoints, and URLs that already carry an
IP literal.

Regression coverage models the rebinding name server deterministically —
public on the first answer, 127.0.0.1 after it — and proves the request
lands on the approved address while the loopback listener sees no
connection at all. Reintroducing either half of the fix (a second lookup
at dial time, or dropping the peer check) turns those tests red.

Residual risk unchanged: egress allowlisting and network policy are still
recommended as defence in depth.

Refs PAP-17098
2026-08-20 21:20:01 +00:00
DottaandClaude Opus 5 0d24cc8505 feat(apps): add agent-config help beside Paste a config
An operator who only knows a tool's name has nothing to paste. Add a compact
question-mark control that hands them a ready-made request for an agent: research
the official docs, reply with one paste-ready `mcpServers` JSON object first, use
placeholders for every credential, then explain how to get each one.

The prompt is a constant in shared, deliberately not a template. Nothing about
the company, the endpoint, the pasted config, or a secret can reach it, so
"opening or copying help makes no connection or import request" holds by
construction rather than by review — and a server test lifts the JSON example
straight out of the prompt and runs the real preview parser on it, so the prompt
and the parser cannot drift apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 21:20:01 +00:00
DottaandClaude Opus 5 0c2b2652ea test(server): cover generic remote MCP connections with an OAuth fixture
One in-process fetch fixture stands in for an MCP server plus its authorization
server, so the whole generic path — discovery, registration, authorization,
catalog, review, install, revoke — is deterministic and needs no network or
vendor credentials. Every case deliberately connects by URL and never names a
gallery app, and asserts the connection carries no `sourceTemplateKey`, so a
regression that reintroduced a dependency on curated metadata would fail here.

Covers public/bearer/custom-header endpoints, the four registration tiers and
their preference order, RFC 8707 `resource` on authorize and token, RFC 8414 and
OIDC-suffix discovery for a pathful issuer, `iss` acceptance and mismatch,
re-registration on a moved callback, refusal to auto-re-register over an
operator-supplied client, SSRF rejection, company isolation, secret redaction in
config/refs/API readback, and a client metadata document with no tenant data.

The manual-client binding test caught a real bug: a pasted client's callback was
never stamped on first use, so its binding check would have accepted any
callback forever. Fixed alongside.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 21:20:01 +00:00
DottaandClaude Opus 5 a98c363a92 feat(server): make remote MCP OAuth transport-generic and spec-current
An unknown standards-compliant MCP endpoint could discover an OAuth challenge
but never complete one: `ensureOAuthClient` only reached registration when a
gallery `AppDefinition` method listed `ownershipModes: ["dcr"]`, so a URL-only
connection died on "OAuth client id is not configured".

Replace that gate with the registration preference the current MCP
client-registration guidance recommends — a client the deployment preconfigured
for the issuer, then a Client ID Metadata Document, then dynamic registration,
then client credentials the operator preregistered — and allow a generic
connection to register only after protected-resource/authorization-server
discovery actually produced a metadata document. Client material is bound to
issuer, MCP resource, callback URI and company, and re-registers when any of
those move; a manually supplied client refuses to auto-re-register and asks the
operator instead.

Bring the broker in line with current guidance along the way: RFC 8707
`resource` on authorization, token and refresh requests; RFC 8414 and OIDC
path-aware `.well-known` variants for pathful issuers, with the issuer in the
document validated against the one used to build the URL; RFC 9207 `iss`
validated on the callback when returned; `application_type: web` on DCR; and
`authKind`/issuer/resource persisted so refresh, reconnect, revoke and
diagnostics stop treating a generic connection as `authKind: none`.

Header names and values from a pasted config are re-checked here, at the last
point before they become real request headers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 21:20:01 +00:00
DottaandPaperclip 860eb96c1e feat(apps): simplify PostHog connection setup
Default PostHog to broad tool access, move optional filters and response mode behind a reusable Advanced disclosure, and codify the connection UX rule in the bundled integration skill and playbook.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:01 +00:00
DottaandPaperclip 7b816839c9 fix(apps): preserve scoped headers during tool calls
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:01 +00:00
DottaandPaperclip ba4ff3fdf6 test(apps): cover PostHog gallery route
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:01 +00:00
DottaandPaperclip b5301d3ab3 feat(apps): add governed PostHog connector
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-20 21:20:01 +00:00
233c12f029 feat: add kimi-local adapter for Kimi Code CLI (CLI + ACP engines) (#9967)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Local agent adapters (`claude_local`, `gemini_local`, `grok_local`,
…) are the integration surface that lets Paperclip run coding CLIs on
the host machine
> - The Kimi Code CLI (`kimi`, Moonshot AI) has a documented
non-interactive mode, `kimi -p --output-format stream-json` with session
resume via `kimi -r`, but Paperclip has no built-in adapter for it
> - So Kimi users (especially Kimi membership / OAuth subscribers)
cannot onboard their CLI to Paperclip agent teams
> - This pull request adds a complete built-in `kimi_local` adapter
(both execution engines, session management, instructions + skills
delivery, thinking-effort control, environment test, UI and CLI modules,
docs) following the established `gemini_local`/`grok_local` package
pattern
> - Kimi Code ships an ACP server (`kimi acp`), so the adapter runs on
Paperclip's shared acpx engine by default (streaming transcript with
live tool status, like `claude_local`/`gemini_local`) and falls back to
a headless CLI lane (`kimi -p --output-format stream-json`) when ACP
prerequisites are unavailable
> - The benefit is that Kimi Code becomes a first-class Paperclip agent
lane: selectable in the UI, resumable across heartbeats, with the same
operating context (instruction bundle, skills, effort) and streaming
transcript the other local adapters get

## Linked Issues or Issue Description

- Supersedes #9880 (same branch; expanded from the CLI-only lane into a
complete adapter with the default ACP engine lane, control-plane skill
install, and live transcript wiring)
- Refs #9879 (adapter request for Kimi Code CLI, filed with this PR)
- Refs #163 (original Kimi support request)

Duplicate/related prior PRs, per the dedup search (both appear stale: no
updates or maintainer review since May 2026, and both target an older
Kimi CLI interface; calling them out for reviewer context per
CONTRIBUTING.md):

- Refs #6276 (`feat: add kimi-local adapter`): targets an older
array-based content format (`{type: think}`/`{type: text}` blocks), not
the current documented stream-json schema
- Refs #5202 (`feat(adapter): add Kimi CLI local adapter with Wire
protocol support`): builds on a `--wire` JSON-RPC interface that current
Kimi Code CLI (0.27.0) no longer documents; the current documented
headless interface is `-p --output-format stream-json`

This PR is a fresh implementation against current master and the
currently documented/verified Kimi CLI behavior (see Verification).
Happy to fold in anything useful from the earlier attempts if a reviewer
prefers.

## What Changed

- **New adapter package** `packages/adapters/kimi-local`
(`@paperclipai/adapter-kimi-local`), modeled on
`gemini-local`/`grok-local`:
- `src/server/execute.ts`: spawns `kimi -p <prompt> --output-format
stream-json` (argv array, no shell), `-m <model>` only when configured,
`-r <sessionId>` when the stored session cwd matches the run cwd,
automatic fresh-session retry on unrecoverable-session errors,
headless-safe env (`CI=1`, `NO_COLOR=1`, `KIMI_CODE_NO_AUTO_UPDATE=1`,
`TERM=dumb`; user-configured values win), full remote (ssh/sandbox)
execution lane with runtime install via `@moonshot-ai/kimi-code`
- **Instruction bundle delivery**: the prompt path directive now names
the sibling instruction files (`./HEARTBEAT.md`, `./SOUL.md`,
`./TOOLS.md`) alongside the prepended entry file, and local runs pass
`--add-dir <instructions-dir>` so Kimi can actually open them (matching
`claude_local`). Without this, only the entry file reached Kimi and
agents improvised the operating workflow that `HEARTBEAT.md` documents
- **Thinking effort**: a configured `effort` is forwarded as the
`KIMI_MODEL_THINKING_EFFORT` operational override (Kimi has no
per-invocation effort flag). It is only sent for models that advertise
`support_efforts` (currently `kimi-code/k3`) to avoid provider
rejections, and `medium` maps to `high` since Kimi has no medium tier
(`low`/`high`/`max` pass through)
- **Skills delivery**: desired Paperclip skills are delivered via Kimi's
`--skills-dir` flag from a dedicated per-run directory (a local
snapshot, or the synced snapshot on remote targets), so skills load
reliably and in isolation. Paperclip never overwrites the shared
`$KIMI_CODE_HOME/skills` home, so skills installed by the operator or
other agents are left intact. `--skills-dir` is only passed when at
least one skill is desired, so unconfigured agents keep Kimi's default
skill discovery
- **Live run status**: the adapter now forwards each streamed
stream-json line to `onEvent` (assistant `content` as an assistant
snippet, `tool_calls` as tool-name events), which drives the
issue-thread activity indicator (`currentToolName` /
`lastAssistantSnippet` / `lastEventAt`). Previously the adapter only
wrote the raw run log, so the issue thread showed a stale "no output for
N s" line with no tool or reasoning context while Kimi worked. Tool
results are omitted so the last meaningful "Using X" / snippet is not
overwritten by a generic label
- `src/server/parse.ts`: parses the verified Kimi stream-json event
shapes (`assistant` text, `assistant.tool_calls` with JSON-string
arguments, `tool` results, trailing `meta.session.resume_hint` for
session-id capture) plus failure classifiers (`kimi_auth_required`,
transient network, unrecoverable session). A signaled exit (null exit
code, not a timeout) is now reported as a failure rather than coalesced
to success, and the error message names the terminating signal
- `src/server/skills.ts`: lists/syncs Paperclip skills for the adapter's
skill-management surface
- `src/server/test.ts`: environment test covering CLI resolution + `kimi
--version`, cwd check, auth detection (OAuth credential dirs, keyed
`[providers.*]` in config.toml, or the `KIMI_MODEL_NAME` +
`KIMI_MODEL_API_KEY` env pair), and a live hello probe
- `src/ui/` (stdout-line parser for transcripts, config builder) and
`src/cli/` (stream event formatter) modules
- Root metadata: three managed model aliases
(`kimi-code/kimi-for-coding`, `kimi-code/kimi-for-coding-highspeed`,
`kimi-code/k3`), effort-capable-model metadata (`EFFORT_CAPABLE_MODELS`,
effort mapping helpers), `agentConfigurationDoc`
- Tests: 101 tests across parse, execute (args building, resume gating,
retry, auth error code, timeout, signaled-exit failure, effort
forwarding/gating/mapping, `--add-dir` instructions directive,
`--skills-dir` gating, `onEvent` runtime-event forwarding), ACP engine
(engine resolution, acpx config build, node-version gate), ACP
transcript delegation, environment test, UI parse/build-config
- **ACP engine lane (default)** (`src/server/acp.ts` + shared
`adapter-utils/acpx-engine`): Kimi Code ships an ACP server (`kimi
acp`), so `kimi_local` now runs on Paperclip's shared acpx engine by
default, matching `claude_local`/`codex_local`/`gemini_local`. The
issue-thread transcript streams live (assistant text deltas, tool calls
with a `pending`->`completed` status lifecycle) instead of the CLI
lane's bursty complete-message output. Registered `kimi_local -> "kimi"`
in `ACPX_ADAPTER_AGENT_IDS` and resolved the built-in agent command to
`kimi acp`; `execute.ts` dispatches to the ACP executor first with an
automatic CLI fallback when ACP prerequisites fail (`engine=acp`
requires ACP, `engine=cli` pins the headless lane); `index.ts` falls
back to the shared acpx session codec; the UI/CLI delegate `acpx.*`
events to the shared acpx transcript parser and event formatter. The
headless CLI lane (above) remains as the fallback
- **Registration** (one entry each, mirroring existing adapters): server
adapter registry + `BUILTIN_ADAPTER_TYPES`, `AGENT_ADAPTER_TYPES`
(shared), UI adapter registry + display registry (`Kimi Code`, Moon
icon) + capabilities defaults, CLI adapter registry, `Dockerfile`
(package copy + `npm install --global @moonshot-ai/kimi-code@latest`),
`vitest.config.ts` workspace, `scripts/release-package-manifest.json`
- **Behavioral sets** mirroring `gemini_local` (Kimi resumes sessions
the same way): `GIT_SENSITIVE_LOCAL_ADAPTER_TYPES`,
`SESSIONED_LOCAL_ADAPTERS` (heartbeat + recovery),
`REMOTE_MANAGED_ADAPTERS`, ssh/sandbox execution-target allow-lists,
`ADAPTER_DEFAULT_RULES_BY_TYPE` (`timeoutSec: 0`, `graceSec: 15`), and
`LEGACY_SESSIONED_ADAPTER_TYPES` + `ADAPTER_SESSION_MANAGEMENT` in
adapter-utils
- **UI touch-points**: New Agent default-model branch, AgentConfigForm
command map (`kimi_local: "kimi"`) + model defaults + a Kimi-specific
thinking-effort option list (`Low`/`High`/`Max`, reflecting Kimi's tiers
rather than borrowing Claude's), OnboardingWizard (command map, model
default, `kimi login` / `KIMI_MODEL_NAME + KIMI_MODEL_API_KEY` auth
hints, manual-debug command line), InviteLanding enabled adapters
- **Control-plane skill install** (`cli/src/commands/client/agent.ts`):
`paperclipai agent local-cli` seeded the Paperclip control-plane skills
into `~/.codex/skills` and `~/.claude/skills` so Codex/Claude agents
auto-discover the API reference every run. Kimi had no equivalent
target, so `kimi_local` agents began each session without the
control-plane skill and rediscovered routes (e.g. the company-scoped
`POST /api/companies/{companyId}/issues`) by trial and error. Added
`~/.kimi-code/skills` (honoring `KIMI_CODE_HOME`) as a third install
target for parity. Independent of the per-run `--skills-dir` delivery,
which only applies to explicitly configured skills.
- **Docs**: `docs/adapters/kimi-local.md` (prerequisites, auth options,
config fields including `effort`, session resume, instruction bundle,
skills delivery, control-plane skill install) + a row in
`docs/adapters/overview.md`

Out of scope (deliberately): model profiles, built-in agent
`allowedAdapterTypes` additions.

## Verification\n\nCurrent-master rebase verification (OpenAI Codex,
2026-08-03): 13 focused files / 231 tests pass; adapter-utils, server,
UI, CLI, and Kimi adapter typechecks pass; full repository build and UI
token gates pass. The branch is conflict-free against master at head
`1249df117c5e12e5771b9a570a6340866450619e`.\n\nAutomated (all from repo
root, pnpm 9.15.4, Node 22):

- `vitest run packages/adapters/kimi-local`: 89/89 pass (includes
coverage for the instruction `--add-dir` directive, effort
forwarding/gating/mapping, `--skills-dir` gating, the signaled-exit
failure path, and `onEvent` runtime-event forwarding with cross-chunk
line buffering)
- `vitest run server/src/__tests__/adapter-registry.test.ts
server/src/__tests__/adapter-routes.test.ts
server/src/services/heartbeat-stop-metadata.test.ts
ui/src/adapters/adapter-display-registry.test.ts`: 37/37 pass
- `vitest run cli/src/__tests__/skills.test.ts`: 13/13 pass (the
control-plane skill install target follows the existing Codex/Claude
install path, whose symlink logic is unchanged)
- `vitest run packages/shared`: 307/307 pass; `vitest run
packages/adapter-utils`: pass except one pre-existing, unrelated failure
(`mcp-isolation.integration.test.ts` requires Claude CLI ≥ 2.1.207; host
has 2.1.185, fails identically on unmodified master)
- `pnpm --filter @paperclipai/adapter-kimi-local typecheck|build`, plus
typecheck of `server`, `ui`, `cli`, `adapter-utils`: all clean
- `pnpm install --frozen-lockfile`: passes (the PR diff itself contains
no lockfile changes, per repo policy; verified against a locally
regenerated lockfile)
- `node scripts/check-no-git-push.mjs` and `node
scripts/check-forbidden-tokens.mjs`: pass
- CI note: the `policy` job's release-bootstrap step is expected to stay
red until a maintainer bootstraps the first npm publish of
`@paperclipai/adapter-kimi-local`; see the CI Note for Maintainers
comment. All other contributor-actionable checks are green.

Manual end-to-end (real Kimi CLI 0.27.0, OAuth login, dev server on an
isolated instance):

1. Server `GET /api/adapters` lists `kimi_local` as builtin with correct
capability flags; models endpoint returns the three Kimi models
2. `POST .../adapters/kimi_local/test-environment`: all checks pass,
including a live `kimi -p` hello probe
3. Created a `kimi_local` agent and invoked two heartbeats: run 1
spawned `kimi -p ... --output-format stream-json`, Kimi used its `Read`
tool, produced the expected answer, and the session id was captured from
the `session.resume_hint` meta event; run 2 resumed the **same** Kimi
session (`sessionIdBefore == sessionIdAfter`) via `-r`
4. UI: adapter appears in the New Agent dropdown; selecting it shows the
Kimi command placeholder, the three models, and the Kimi config fields;
the run transcript renders Kimi tool calls via the adapter's stdout
parser

The instruction-bundle, thinking-effort, and `--skills-dir` changes
landed after the manual run above. They are covered by the unit tests
listed under Automated, and the Kimi CLI flags they rely on
(`--add-dir`, `--skills-dir`, `KIMI_MODEL_THINKING_EFFORT`) were
confirmed against the installed Kimi Code CLI 0.27.0 (`kimi --help`,
config-file thinking-effort docs).

Screenshots (assets branch on the fork, not part of the diff):

![Kimi Code in the Add a new agent runtime
picker](https://raw.githubusercontent.com/hawikk/paperclip/assets/kimi-local-pr-screenshots/shots/00-kimicode.png)

![Adapter dropdown with Kimi
Code](https://raw.githubusercontent.com/hawikk/paperclip/assets/kimi-local-pr-screenshots/shots/01-adapter-dropdown-kimi.png)

![Kimi adapter selected: command, model, config
fields](https://raw.githubusercontent.com/hawikk/paperclip/assets/kimi-local-pr-screenshots/shots/02-kimi-adapter-selected.png)

![Kimi models in the model
dropdown](https://raw.githubusercontent.com/hawikk/paperclip/assets/kimi-local-pr-screenshots/shots/03-kimi-model-dropdown.png)

![Successful resumed heartbeat run (kimi_local invocation + parsed
transcript)](https://raw.githubusercontent.com/hawikk/paperclip/assets/kimi-local-pr-screenshots/shots/04-successful-resumed-run.png)

![Agents list showing the Kimi Code
label](https://raw.githubusercontent.com/hawikk/paperclip/assets/kimi-local-pr-screenshots/shots/05-agents-list.png)

## Risks

- Low risk to existing behavior: the change is additive, one new
workspace package plus single-entry registrations alongside existing
adapters; no existing adapter code paths are modified.
- The adapter invokes the locally installed `kimi` CLI; like other local
adapters, run behavior depends on the host's Kimi version. The parser is
written against the documented/verified 0.27.0 stream-json schema and
degrades gracefully (malformed lines are skipped, failures surface as
run errors).
- `--skills-dir` overrides Kimi's auto-discovery of user and project
skills for the run. This is intentional (paperclip-managed agents get a
reproducible, isolated skill set), and it is only passed when at least
one Paperclip skill is desired, so unconfigured agents keep default
discovery.
- Thinking effort is only forwarded to models that advertise
`support_efforts` (currently `kimi-code/k3`); `EFFORT_CAPABLE_MODELS`
must be extended when more Kimi models gain support, otherwise a
configured effort is silently ignored for them.
- `Dockerfile` now installs `@moonshot-ai/kimi-code@latest` globally
alongside the other agent CLIs, so image size increases slightly.
- Maintainer action needed for the npm bootstrap gate: the `policy`
job's release-bootstrap step fails until the first npm publish of
`@paperclipai/adapter-kimi-local` (the gate from #5146 that every new
adapter package has passed through). Enrollment with `publishFromCi:
true` is required by the manifest validator (dropping the entry,
`false`, or `private` are all rejected), so this is intentionally left
to a maintainer. Remaining CI lanes are expected to run once it is done.

## Model Used\n\n- **Current-master rebase, conflict adaptation, and
registry-parity coverage:** OpenAI, **GPT-5 Codex** (Codex agent; exact
serving model ID and context-window size were not exposed to the
runtime), with repository, shell, Git, and GitHub tooling. It preserved
Hawik’s commit authorship, reconciled ACPX and environment-capability
changes, added current registry tests, and ran the verification
above.\n- **Adapter implementation and initial review:** Moonshot AI,
**Kimi K3 Coding** (latest), via **Kimi Code CLI v0.27.0**
(`kimi-code/k3` alias, 1M-token context window, thinking mode, agentic
tool use). The CLI agent explored the repo, wrote the adapter
implementation (delegated to a coder sub-agent of the same model), ran
tests, and drafted the first version of this PR body. A second
model-driven review pass (read-only, same model) audited the diff for
security/correctness before submission; its findings (shell-quoting
hardening, auth-detection false positive, session-compaction
registration, test gaps) were fixed and are included.
- **Harness-context fixes and review responses:** Anthropic, **Claude
Opus 4.8** (`claude-opus-4-8`) via Claude Code. Diagnosed from run logs
that Kimi received only the entry instructions file (not the
`HEARTBEAT.md`/`SOUL.md`/`TOOLS.md` bundle) and that `effort` was never
wired, then implemented the instruction `--add-dir` delivery,
`KIMI_MODEL_THINKING_EFFORT` forwarding, and `--skills-dir` skill
delivery, added the accompanying tests and docs, and addressed the
automated review comments (preserving external skills on remote sync,
treating a signaled exit as a failure). Also extended the `paperclipai
agent local-cli` installer to seed the control-plane skills into
`~/.kimi-code/skills` for Codex/Claude parity, wired `onEvent` runtime
events so the issue-thread activity indicator reflects Kimi's tool and
reasoning output live, and built the ACP engine lane (`kimi acp` via the
shared acpx engine, default) so the transcript streams with live tool
status like the other ACP adapters. The Kimi CLI flags, subcommand, and
env var relied on here were verified against the installed Kimi Code CLI
0.27.0.
- All CLI behaviors claimed here (`-p`, `--output-format stream-json`,
`-r` resume, event shapes, `--add-dir`, `--skills-dir`,
`KIMI_MODEL_THINKING_EFFORT`) were verified empirically against the
installed Kimi CLI, not assumed.

## 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 *(only the release-bootstrap step
remains red, pending the maintainer npm publish described in Risks)*
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
*(will address all Greptile comments as they arrive)*
- [x] I will address all Greptile and reviewer comments before
requesting merge





---

## Maintainer Addendum (2026-08-20)

The shared acpx-engine and issue-chat changes (run-summary segmentation,
placeholder tool-event coalescing,
`ISSUE_CHAT_TRANSCRIPT_MAX_VISIBLE_ENTRIES` 30 → 400, live-reasoning UI)
have been **extracted to #11761** so the cross-adapter behavior changes
review and revert independently — both commits there preserve @hawikk's
authorship. This PR is now the kimi-specific adapter only (60 files,
+3,793/−8, essentially pure addition); the only shared-engine touch left
is the `kimi acp` command resolution. `publishFromCi` is `true` — the
package name is bootstrapped on npm.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Dotta <bippadotta@protonmail.com>
Co-authored-by: Devin Foley <devin@paperclip.ing>
2026-08-20 12:06:33 -07:00
Nicky LeachandPaperclip b83e14ad2c fix(runtime): stop the readiness probe from stealing the guest exposure port (#11788)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The workspace runtime starts guest processes and exposes their
ports.
> - The readiness wait bound the guest port to test whether it was
ready.
> - That bind could take the port before the guest process used it.
> - This pull request reads listener state without a competing bind and
recovers from a real port collision.
> - The benefit is stable runtime exposure and a clear recovery path for
a genuine collision.

## Linked Issues or Issue Description

**What happened?**

The managed HTTPS exposure test failed intermittently with `listen
EADDRINUSE` on `127.0.0.1:42000`. The readiness wait bound the guest
port before the guest process could bind it.

**Expected behavior**

The readiness wait must not hold the guest port. The runtime must
recover when an external process owns the assigned port.

**Steps to reproduce**

1. Run `npx vitest run
server/src/services/workspace-runtime-exposure.test.ts` from the
repository root.
2. Inject a delayed guest bind and a widened readiness-probe hold.
3. Observe the port collision before this fix and the successful retry
after this fix.

**Paperclip version or commit**

`b375bbd913cb2edc8e077f4339ce0745e53bd462`

**Deployment mode**

Built from source with the server test suite.

**Installation method**

Built from source with pnpm.

**Agent adapter(s) involved**

Not adapter-specific. This is a core runtime test.

**Database mode**

Not database-related.

**Relevant logs or output**

Before this fix, the test reported `listen EADDRINUSE: address already
in use 127.0.0.1:42000`.

## What Changed

- Read listener presence from `/proc` on Linux instead of binding the
guest port.
- Keep the bind probe as the fallback on non-Linux hosts.
- Capture the current port owner when an exposed guest exits with
`EADDRINUSE`.
- Quarantine the app and HMR pair, then allocate the next free port pair
within the existing range.
- Add a deterministic regression test for quarantine, re-allocation, and
self-diagnosis logging.

## Verification

- Run `npx vitest run
server/src/services/workspace-runtime-exposure.test.ts` from the
repository root.
- The target suite passes 19 tests locally.
- The related runtime suites pass 105, 128, and 21 tests locally.
- Run `tsc -p server/tsconfig.json` to check the changed server files.
- CI must pass the general server shard and all required checks.
- Greptile must report 5/5 with no open P2 comments, recommendations, or
follow-ups.

## Risks

The Linux readiness path now depends on `/proc` listener data. Non-Linux
hosts retain the existing bind-probe fallback. The port range and
allocation limit do not change.

## Model Used

OpenAI GPT-5. This agent used tool calls for repository checks and
GitHub PR management. Priya Raman authored the code 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-20 12:05:35 -07:00
Devin Foley 933749e01f test(server): deflake postgres teardown and pinned exposure port (#11667)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The server test suite gates every merge and every release cut.
> - Three server tests each failed exactly once on markdown-only or
unrelated diffs, then passed on rerun.
> - One of the three (the git-operation-scheduler owner/joiner race) was
fixed on master independently by
[#11671](https://github.com/paperclipai/paperclip/pull/11671) while this
PR was open, so after rebasing this pull request carries the remaining
two.
> - A flaky gate makes release operators rerun CI and stop trusting red
results.
> - Each remaining flake has a real nondeterminism: a teardown race and
a hard-coded host port.
> - This pull request removes the nondeterminism from the two tests
without weakening what they prove.
> - The benefit is a test gate that fails only when the product is
broken.

## Linked Issues or Issue Description

- [x] I searched open and closed issues and pull requests for these test
files and for these failures. I found no duplicate report or fix.

**What happened?**

Three one-off CI failures occurred during release operations, each on a
diff that could not have caused it, and each passed on rerun:

1. Run
[32086355930](https://github.com/paperclipai/paperclip/actions/runs/32086355930):
`server/src/__tests__/interaction-resolution-cross-issue-cap-postgres.test.ts`
— all 7 tests passed, but vitest recorded an Unhandled Error and failed
the run: `TypeError: Cannot read properties of null (reading 'write')`
at `postgres@3.4.9/src/connection.js:255 Immediate.nextWrite`.
2. Run
[32096743814](https://github.com/paperclipai/paperclip/actions/runs/32096743814):
`server/src/services/workspace-git-operation-scheduler.test.ts` — the
test "coalesces the same canonical key and cleans single-flight state
after success and failure" failed with an AssertionError: the two
concurrent calls came back with the `singleFlightJoined` values swapped.
*(Fixed on master by
[#11671](https://github.com/paperclipai/paperclip/pull/11671) with an
equivalent single-flight barrier while this PR was open; the fix was
dropped from this PR on rebase and the file is no longer touched here.)*
3. Run
[32196201529](https://github.com/paperclipai/paperclip/actions/runs/32196201529):
`server/src/services/workspace-runtime-exposure.test.ts` — the test
"keeps an existing runtime port that is already inside the dedicated
range" failed once out of 610 recorded runs because the runtime came
back on a relocated port instead of the pinned 42500.

**Expected behavior**

The tests pass on every run when the code under test is correct. A red
result means a product defect, not scheduling luck on the CI host.

**Steps to reproduce**

Each flake is a low-probability race, but both remaining mechanisms
reproduce deterministically:

1. Postgres teardown: the suite never ends the postgres.js pool behind
`createDb`; `afterAll` only stops the embedded server. postgres.js
batches small writes and flushes them with `setImmediate`
(`connection.js` `nextWrite`), and `close()` nulls the socket. Stop the
server while the pool is open and a pending flush can run after the
socket is gone.
2. Exposure pinned port: hold any loopback socket on 42500 or 52500
(both are inside the default Linux ephemeral port range, 32768–60999)
and run the test. The allocator correctly relocates, and the assertion
fails with `expected 42000 to be 42500`. The client side of any loopback
connection on the CI host can land on those ports.

**Paperclip version or commit**

Branched from `master` at `4b968d8c0`; rebased onto `5a1ce7aed`.

**Privacy checklist**

I reviewed this description and removed private instance URLs, internal
task identifiers, credentials, and user paths.

## What Changed

Both fixes are test-side. I found no product race.

- `interaction-resolution-cross-issue-cap-postgres.test.ts`: `afterAll`
now ends the drizzle/postgres.js pool (`db.$client.end()`) before it
stops the embedded Postgres server. `end()` waits for in-flight queries,
including a fire-and-forget wake that lands just after a response, and
closes the sockets from the client side first. Sibling suites (for
example `heartbeat-plugin-environment.test.ts`) already use this order;
this suite had skipped the pool shutdown.
- `workspace-runtime-exposure.test.ts`: the pinned-port test no longer
hard-codes 42500. It scans the dedicated range with the suite's real
loopback probe, finds the lowest free app/HMR pair, then pins the next
free pair strictly above it. If the keep-preferred-port path broke, the
ascending fallback scan would return the lower pair, so the assertion
keeps its discriminating power while no longer betting on one fixed host
port staying free.
- *(Dropped on rebase: the `workspace-git-operation-scheduler.test.ts`
coalescing fix, superseded by the equivalent barrier merged in
[#11671](https://github.com/paperclipai/paperclip/pull/11671).)*

## Verification

- Reproduced the exposure flake exactly: with a listener held on
`127.0.0.1:52500`, the pre-fix test fails with `expected 42000 to be
42500`; the fixed test passes with the port still held.
- The postgres flake is a probabilistic teardown race and I could not
trigger it on demand. The mechanism is established from `postgres@3.4.9`
source (`setImmediate`-batched `nextWrite` versus `close()` nulling the
socket) and the fix removes the whole class by closing the pool before
the server.
- Repeat runs after the fix: the pinned-port exposure test 20/20 green
while the Postgres suite looped concurrently for loopback churn;
`interaction-resolution-cross-issue-cap-postgres.test.ts` 15/15 green
with no unhandled errors.
- Re-verified after rebasing onto `5a1ce7aed`: both changed test files
pass and `tsc --noEmit` passes in `server/`.
- Environment note: three unrelated tests in
`workspace-runtime-exposure.test.ts` (the wildcard-bind diagnosis tests)
fail on macOS before and after this change because they read `/proc`;
they are untouched and pass on Linux CI.

## Risks

- Low risk: both changes are test-only; no product code changed.
- The pinned-port test keeps a tiny time-of-check/time-of-use window
between its own probe and the runtime's bind. The window shrinks from
"one fixed port must stay free across the whole CI fleet" to
milliseconds on a pair just verified free.

> 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 Fable 5 (Claude Code) — model ID `claude-fable-5`, with
repository tools and local code execution for reproduction and
repeat-run verification.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [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-20 11:27:39 -07:00
54b8bec444 build(deps): bump pino-http from 10.5.0 to 11.0.0 (#11716)
Bumps [pino-http](https://github.com/pinojs/pino-http) from 10.5.0 to
11.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pinojs/pino-http/releases">pino-http's
releases</a>.</em></p>
<blockquote>
<h2>v11.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>build(deps-dev): bump <code>@​types/node</code> from 22.15.32 to
24.0.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/pinojs/pino-http/pull/370">pinojs/pino-http#370</a></li>
<li>build(deps-dev): bump typescript from 5.8.3 to 5.9.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/pinojs/pino-http/pull/373">pinojs/pino-http#373</a></li>
<li>build(deps-dev): bump tsd from 0.32.0 to 0.33.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/pinojs/pino-http/pull/376">pinojs/pino-http#376</a></li>
<li>build(deps): bump actions/checkout from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/pinojs/pino-http/pull/380">pinojs/pino-http#380</a></li>
<li>build(deps): bump actions/setup-node from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/pinojs/pino-http/pull/382">pinojs/pino-http#382</a></li>
<li>Update for pino@10 by <a
href="https://github.com/jsumners"><code>@​jsumners</code></a> in <a
href="https://redirect.github.com/pinojs/pino-http/pull/383">pinojs/pino-http#383</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/pinojs/pino-http/compare/v10.5.0...v11.0.0">https://github.com/pinojs/pino-http/compare/v10.5.0...v11.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pinojs/pino-http/commit/6615953a8bfebf37c26e6438568724a091f80a82"><code>6615953</code></a>
v11.0.0</li>
<li><a
href="https://github.com/pinojs/pino-http/commit/da0442ea7306b0560eada975391d13853c72b119"><code>da0442e</code></a>
Update for pino@10 (<a
href="https://redirect.github.com/pinojs/pino-http/issues/383">#383</a>)</li>
<li><a
href="https://github.com/pinojs/pino-http/commit/c77bf2546d44dbe7381264955ae97ca76ff302e1"><code>c77bf25</code></a>
build(deps): bump actions/setup-node from 4 to 5 (<a
href="https://redirect.github.com/pinojs/pino-http/issues/382">#382</a>)</li>
<li><a
href="https://github.com/pinojs/pino-http/commit/5b06a223d3ad15f97b1e0f68416410776c47fdde"><code>5b06a22</code></a>
build(deps): bump actions/checkout from 4 to 5 (<a
href="https://redirect.github.com/pinojs/pino-http/issues/380">#380</a>)</li>
<li><a
href="https://github.com/pinojs/pino-http/commit/bc69ee95e184f882570c7a9d551288b5b077fe02"><code>bc69ee9</code></a>
build(deps-dev): bump tsd from 0.32.0 to 0.33.0 (<a
href="https://redirect.github.com/pinojs/pino-http/issues/376">#376</a>)</li>
<li><a
href="https://github.com/pinojs/pino-http/commit/c2ccdb31d8961033c976686102f7d6279f97a1c2"><code>c2ccdb3</code></a>
build(deps-dev): bump typescript from 5.8.3 to 5.9.2 (<a
href="https://redirect.github.com/pinojs/pino-http/issues/373">#373</a>)</li>
<li><a
href="https://github.com/pinojs/pino-http/commit/6dbe316bd5d8860a3efbc963e5b6fed7a4b45384"><code>6dbe316</code></a>
build(deps-dev): bump <code>@​types/node</code> from 22.15.32 to 24.0.3
(<a
href="https://redirect.github.com/pinojs/pino-http/issues/370">#370</a>)</li>
<li>See full diff in <a
href="https://github.com/pinojs/pino-http/compare/v10.5.0...v11.0.0">compare
view</a></li>
</ul>
</details>
<br />

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Priya Raman <priya@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 10:51:54 -07:00
DottaandPaperclip e826188e82 refactor(settings): unify settings and speed up exports (#11789)
## Thinking Path

> - Paperclip is the open source app that people use to manage AI agents
for work.
> - Operators use the settings area to control a company and its
Paperclip instance.
> - The current navigation separates related settings and uses duplicate
instance pages.
> - Company exports also do independent reads in sequence and do extra
work for previews.
> - Hardened workspace commands can differ from their saved command
after loopback binding.
> - This pull request makes these related operator workflows consistent
and faster.
> - The benefit is one clear settings area, faster exports, and stable
runtime command matching.

## Linked Issues or Issue Description

Refs #338

Related: #9834

**What existing behavior does this improve?**

This improves the company settings UI, company export preparation, and
workspace runtime command matching.

**Current behavior**

Company and instance settings use separate navigation and duplicate
pages. Export preparation reads many independent records in sequence.
Preview generation can also build an unused organization image. A
command with a forced loopback bind can fail to match its saved runtime
command.

**Proposed behavior**

Use one settings navigation and put general instance controls on the
company General page. Load independent export data with bounded
concurrency, skip unused preview image work, and load the export page
only when it is needed. Treat the loopback-bound form of a command as
the same runtime command.

**Reason and benefit**

Operators get one clear settings area. Large company exports need fewer
serialized reads. Export previews and initial UI loads do less work.
Hardened runtime services remain linked to their saved command
definitions.

**Breaking changes**

The obsolete instance General URL redirects to the unified settings
page. Access and Heartbeats remain available, and legacy bookmarks keep
their destinations. No API response shape or database schema changes.

## What Changed

- Unified company and instance settings navigation and removed duplicate
instance settings pages.
- Embedded general instance controls in the company General page and
kept access-sensitive navigation behavior.
- Preserved instance Access and Heartbeats controls in the unified
navigation and normalized old bookmarks to those destinations.
- Improved environment and access-state handling when workspace seed
requests overlap.
- Added bounded export reads, a lighter preview path, deferred export
preparation, and lazy export-page loading.
- Matched loopback-bound runtime commands to their saved command
definitions.
- Added focused shared, server, and UI regression tests.

## Verification

- `pnpm exec vitest run <18 changed test files>`: 18 files and 256 tests
passed.
- `pnpm check:token-gates`: passed all four token gates.
- `pnpm -r typecheck`: passed for all workspace projects.
- `pnpm build`: passed for all workspace projects.
- `pnpm test:run`: tests ran without a reported failure, but the runner
did not close after the server handoff tests. The process closed with
status 0 after an interrupt.
- Focused latest-head route tests: 2 files and 4 tests passed.
- GitHub latest-head checks: all completed without failure.
- Greptile: 5/5 with no unresolved review threads.

## Risks

- Medium risk: settings routes and navigation changed across several
operator roles.
- Medium risk: bounded export concurrency increases simultaneous
database reads. The limits stay below the normal pool size.
- Low risk: runtime command matching accepts only the known Tailscale
HTTPS loopback transformation.
- No migrations are included.

> 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 a GPT-5-family coding model. The runtime does not
expose the exact deployed model ID or context-window size. Reasoning,
tool use, and local 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)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details

Exception: This task requires the existing execution branch. The harness
does not permit a branch rename.
- [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-20 12:15:16 -05:00
416a273366 build(deps): bump jsdom and @types/jsdom (#11717)
Bumps [jsdom](https://github.com/jsdom/jsdom) and
[@types/jsdom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jsdom).
These dependencies needed to be updated together.
Updates `jsdom` from 28.1.0 to 30.0.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/jsdom/jsdom/releases">jsdom's
releases</a>.</em></p>
<blockquote>
<h2>v30.0.1</h2>
<ul>
<li>Fixed <code>getComputedStyle()</code> with <code>calc()</code> and
other functions throwing an exception, which regressed in v30.0.0. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
<li>Sped up up range operations on large documents (<a
href="https://github.com/leonidaz"><code>@​leonidaz</code></a>)</li>
</ul>
<h2>v30.0.0</h2>
<p>Breaking changes:</p>
<ul>
<li>Node.js minimum version raised to <code>^22.22.2 || ^24.15.0 ||
&gt;=26.0.0</code>.</li>
</ul>
<p>Other changes:</p>
<ul>
<li>Added <code>CSS.escape()</code> and <code>CSS.supports()</code>
functions. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
<li>Added <code>'background-position-x'</code> and
<code>'background-position-y'</code> CSS properties. (<a
href="https://github.com/olagokemills"><code>@​olagokemills</code></a>)</li>
<li>Fixed <code>getComputedStyle()</code> to convert length values into
pixels. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
<li>Fixed CSS function serialization, e.g., in the return value of
<code>getPropertyValue()</code>. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
<li>Fixed the type of error thrown by <code>document.evaluate()</code>
(<a href="https://github.com/dokson"><code>@​dokson</code></a>)</li>
</ul>
<h2>v29.1.1</h2>
<ul>
<li>Fixed <code>'border-radius'</code> computed style serialization. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
<li>Fixed computed style computation when using
<code>'background-origin'</code> and <code>'background-clip'</code> CSS
properties. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
<li>Significantly optimized initial calls to
<code>getComputedStyle()</code>, before the cache warms up. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
</ul>
<h2>v29.1.0</h2>
<ul>
<li>Added basic support for the ratio CSS type. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
<li>Fixed <code>getComputedStyle()</code> sometimes returning outdated
results after CSS was modified. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
</ul>
<h2>v29.0.2</h2>
<ul>
<li>Significantly improved and sped up <code>getComputedStyle()</code>.
Computed value rules are now applied across a broader set of properties,
and include fixes related to inheritance, defaulting keywords, custom
properties, and color-related values such as <code>currentcolor</code>
and system colors. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
<li>Fixed CSS <code>'background</code>' and <code>'border'</code>
shorthand parsing. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
</ul>
<h2>v29.0.1</h2>
<ul>
<li>Fixed CSS parsing of <code>'border'</code>,
<code>'background'</code>, and their sub-shorthands containing keywords
or <code>var()</code>. (<a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a>)</li>
<li>Fixed <code>getComputedStyle()</code> to return a more functional
<code>CSSStyleDeclaration</code> object, including indexed access
support, which regressed in v29.0.0.</li>
</ul>
<h2>v29.0.0</h2>
<p>Breaking changes:</p>
<ul>
<li>Node.js v22.13.0+ is now the minimum supported v22 version (was
v22.12.0+).</li>
</ul>
<p>Other changes:</p>
<ul>
<li>Overhauled the CSSOM implementation, replacing the <a
href="https://www.npmjs.com/package/@acemir/cssom"><code>@acemir/cssom</code></a>
and <a
href="https://github.com/jsdom/cssstyle"><code>cssstyle</code></a>
dependencies with fresh internal implementations built on webidl2js
wrappers and the <a
href="https://www.npmjs.com/package/css-tree"><code>css-tree</code></a>
parser. Serialization, parsing, and API behavior is improved in various
ways, especially around edge cases.</li>
<li>Added <code>CSSCounterStyleRule</code> and
<code>CSSNamespaceRule</code> to jsdom <code>Window</code>s.</li>
<li>Added <code>cssMediaRule.matches</code> and
<code>cssSupportsRule.matches</code> getters.</li>
<li>Added proper media query parsing in <code>MediaList</code>, using
<code>css-tree</code> instead of naive comma-splitting. Invalid queries
become <code>&quot;not all&quot;</code> per spec.</li>
<li>Added <code>cssKeyframeRule.keyText</code> getter/setter
validation.</li>
<li>Added <code>cssStyleRule.selectorText</code> setter validation:
invalid selectors are now rejected.</li>
<li>Added <code>styleSheet.ownerNode</code>,
<code>styleSheet.href</code>, and <code>styleSheet.title</code>.</li>
<li>Added bad port blocking per the <a
href="https://fetch.spec.whatwg.org/#bad-port">fetch specification</a>,
preventing fetches to commonly-abused ports.</li>
<li>Improved <code>Document</code> initialization performance by lazily
initializing the CSS selector engine, avoiding ~0.5 ms of overhead per
<code>Document</code>. (<a
href="https://github.com/thypon"><code>@​thypon</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/jsdom/jsdom/commit/6584485f094d5b271553005b68804c93a455c002"><code>6584485</code></a>
30.0.1</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/0c51df6d80567202e969ba273c32db01de43c26e"><code>0c51df6</code></a>
Update dependencies and dev dependencies</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/32adb340bf3782194099c6a7b0e0a34c05aa8f24"><code>32adb34</code></a>
Bump <code>@​asamuzakjp/dom-selector</code></li>
<li><a
href="https://github.com/jsdom/jsdom/commit/70f014aa1de06a684b4f7d123bdb832ae8c63c53"><code>70f014a</code></a>
Speed up range operations on large documents</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/250d7ee387109d9674d3e4a79fe1edac96e45476"><code>250d7ee</code></a>
Partially fix getComputedStyle with calc()</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/20a01fc4a5ca1b2a48ec9c546d230624964d2f83"><code>20a01fc</code></a>
30.0.0</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/8c8e583c4f0130f09a1f0ba2728c9aebc4f709c5"><code>8c8e583</code></a>
Precompute WPT expectation matches</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/f32245cfedf894b933bf4625b5649c284422da45"><code>f32245c</code></a>
Bump Node.js floor and dependencies</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/03ef23b4513af92d89d59b2383c5697a0a260111"><code>03ef23b</code></a>
Add background-position longhands</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/ded056f38de1a1e3709feafd0acea57377f85f35"><code>ded056f</code></a>
Test CSS.escape() with numeric IDs</li>
<li>Additional commits viewable in <a
href="https://github.com/jsdom/jsdom/compare/v28.1.0...v30.0.1">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for jsdom since your current version.</p>
</details>
<details>
<summary>Install script changes</summary>
<p>This version modifies <code>prepare</code> script that runs during
installation. Review the package contents before updating.</p>
</details>
<br />

Updates `@types/jsdom` from 28.0.0 to 30.0.0
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jsdom">compare
view</a></li>
</ul>
</details>
<br />

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Priya Raman <priya@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-20 10:11:28 -07:00
Nicky LeachandClaude Opus 5 a9d1f740f0 fix(workspaces): seed managed worktrees when the base checkout has no config (#11752)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents do that work in isolated git worktrees, and a managed
worktree runs its own Paperclip instance with a cloned database
> - That clone needs a seed source, and the source must come from
server-owned registration, never from state the workspace itself can
rewrite
> - The seed-source resolver requires the registered base project
workspace to hold its own `.paperclip/config.json`
> - A managed project workspace is a plain `git clone`, and no code
writes that file into it
> - Every isolated worktree provision, deferred seed, and workspace
repair therefore fails on a managed checkout
> - This pull request lets a named source supply the config when the
base checkout has none
> - The benefit is that managed worktrees provision again, and the seed
source stays server-owned

## Linked Issues or Issue Description

No public GitHub issue exists for this problem. It is described below.

**What happened?**

Agent runs that need an isolated worktree fail during provisioning. The
provision command exits with this error (paths redacted):

```
Execution workspace provision command "bash ./scripts/provision-worktree.sh" failed:
Registered base project workspace has no canonical Paperclip config:
<instance-home>/instances/default/projects/<company-id>/<project-id>/<repo>/.paperclip/config.json
```

`resolveRegisteredWorktreeSeedSource` sets `registeredConfigPath` to
`<baseCwd>/.paperclip/config.json` whenever the caller names a
registered base workspace. It then requires that file to exist.
`scripts/provision-worktree.sh` applies the same rule.

A managed project workspace never has that file.
`materializeManagedProjectWorkspace` creates it with `git clone` and a
rename, so the checkout holds repository content only. The control plane
keeps its config at `<home>/instances/<id>/config.json` instead.

The failure reaches three paths: worktree provisioning, deferred seeding
through `worktree ensure-seeded`, and workspace repair.

The behavior changed in #11671. That pull request replaced a fallback
chain with a single hard requirement. Fixture code in
`scripts/__tests__/provision-worktree-self-heal.test.mjs` writes a
config into the fake base workspace, so tests kept passing.

**Expected behavior**

A managed worktree provisions and seeds from the registered source. The
seed manifest still never selects that source.

**Steps to reproduce**

1. Register the Paperclip repository as a project with a `repoUrl`, so
the server materializes a managed checkout.
2. Assign an issue to an agent whose workspace strategy is
`git_worktree`.
3. Watch the workspace operation log for the provision command.
4. The command exits non-zero with the error above.

**Paperclip version or commit**

Reproduced on `master` at 01ddc26a3.

**Deployment mode**

`local_trusted`, single instance.

**Database mode**

Embedded PostgreSQL.

**Operating system**

Linux, Node.js 22.

**Related pull requests**

- Refs #11671 — introduced the requirement this pull request relaxes.
- Refs #11733 — open work on seed-source preflight. It reads the same
base-workspace config path and skips when the file is absent. It does
not change source selection.
- Refs #11735 — open work on provisioning reliability. It edits the same
four files and will need a rebase after either lands.

## What Changed

- `resolveRegisteredWorktreeSeedSource` sets the registered config path
only when `<baseCwd>/.paperclip/config.json` exists. This makes the
existing `registeredConfigPath ?? explicitSource` branch reachable for a
plain checkout.
- A base workspace that does hold its own config stays authoritative. A
mismatched explicit source is still rejected.
- The resolver throws a named error when the base workspace has no
config and no source is named.
- `readInstanceId` accepts an instance-root config at
`<home>/instances/<id>/config.json`. That layout names its instance by
directory and has no adjacent `.env`. Validation reuses
`resolvePaperclipInstanceId`.
- `scripts/provision-worktree.sh` and
`scripts/provision-worktree-runtime.sh` name the control plane's
instance config as the source when the base workspace has none. The
canonical-path and symlink checks stay.
- The workspace repair route supplies the same fallback, and only when
the base workspace has no config of its own.
- `doc/DEVELOPING.md` records the two source layouts.

## Verification

- `node --test scripts/__tests__/provision-worktree-self-heal.test.mjs`
— 10 tests pass. The fixture no longer writes a config into the base
workspace, so it models a real managed checkout. One test now creates
that config mid-test, which covers both layouts.
- `npx vitest run src/worktree-seed-source.test.ts` in `packages/shared`
— 4 tests pass. Two are new: one resolves an instance-root source, and
one still fails closed when no source exists.
- `npx vitest run src/__tests__/workspace-runtime.test.ts
src/__tests__/execution-workspaces-routes.test.ts
src/__tests__/execution-workspace-runtime-control-conflict.test.ts
src/__tests__/workspace-operations-reconciliation.test.ts
src/__tests__/worktree-seed-server-spawn.test.ts` in `server` — all
pass. Run them one file at a time. They share one test database, and
concurrent runs fail teardown.
- `npx vitest run src/__tests__/worktree.test.ts` in `cli` — 63 tests
pass.
- `pnpm --filter @paperclipai/shared typecheck` — clean.
- Manual check on a live instance: the resolver now returns the instance
config as the source for a managed checkout, with the source instance
`default` and a distinct target instance.

## Risks

Low to moderate.

- The relaxed rule applies only when the base workspace holds no config.
A base workspace that holds one keeps full authority, so the trust model
from #11671 is unchanged. The seed manifest still never selects the
source.
- The instance-id fallback reads a directory name. It applies only to
the `<home>/instances/<id>/config.json` layout, and
`resolvePaperclipInstanceId` rejects an unsafe segment.
- #11735 edits the same four files. Whichever pull request lands second
needs a rebase.
- `pnpm --filter @paperclipai/server typecheck` currently fails on this
checkout with duplicate `drizzle-orm` type instantiations. The failure
is present with and without this change, and the error count is
identical. It comes from an unrelated lockfile state, not from this pull
request.

## Model Used

Claude Opus 5 (`claude-opus-5`), by Anthropic, running in Claude Code.
Extended thinking was on. The model used file, search, and shell tools
to diagnose the failure on a live instance and to run the test suites.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 08:42:16 -07:00
Nicky LeachandPaperclip 5a1ce7aed8 fix(server): stamp built commit into service.version (#11748)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The server emits OpenTelemetry spans so operators can trace agent
work
> - Each span needs a service version that identifies the code that
produced it
> - The current service version comes from a static environment value
and can become stale after a rebuild
> - This pull request records the built commit and resolves the service
version from the build stamp, runtime Git, the environment, or an
unknown fallback
> - The benefit is trace data that identifies the correct built commit
during development and deployment

## Linked Issues or Issue Description

**What happened?**

The server used a static `OTEL_SERVICE_VERSION` value for every
OpenTelemetry span. Rebuilds could produce traces with an old commit
value.

**Expected behavior**

The server should report the built commit when a build stamp exists. It
should use runtime Git, the environment value, or `unknown` as fallback.

**Steps to reproduce**

1. Set `OTEL_SERVICE_VERSION` to an old commit value.
2. Build the server at a different commit.
3. Start the server and inspect the OpenTelemetry service version.
4. Confirm that the built commit takes precedence over the old
environment value.

## What Changed

- Add a build script that writes the short Git commit to
`dist/build-info.json`.
- Resolve `service.version` from the build stamp, runtime Git, the
environment, or `unknown`.
- Log the resolved service version once during server startup.
- Add tests for the resolution order and safe behavior without Git.
- Document the resolution order in `doc/observability.md`.

## Verification

- `pnpm --filter @paperclipai/server build`
- `npx vitest run server/src/__tests__/service-version.test.ts`
- `pnpm --filter @paperclipai/server typecheck`
- Confirm that the build stamp contains the short commit.
- Confirm that the stamp wins over the environment value.
- Confirm that a build without Git exits successfully without a stamp.

## Risks

The server now prefers the built commit over `OTEL_SERVICE_VERSION`. A
build without Git uses the existing environment value or `unknown`. The
change needs no schema migration and has a single-commit rollback path.

## Model Used

OpenAI Codex, GPT-5, tool use and code execution. The runtime does not
expose the context window size or reasoning mode.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [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 21:20:40 -07:00
Nicky LeachandPaperclip 7c8064da1b test(runtime): assert order-independent port invariants for concurrent siblings (#11754)
## Thinking Path

> - Paperclip manages work for AI agents.
> - The workspace runtime starts isolated services for concurrent
workspaces.
> - A runtime test assumed that one concurrent lane always received the
base port.
> - The allocator guarantees distinct ports, but scheduling decides
which lane receives the base port.
> - This pull request changes the test to assert allocator guarantees
without lane-order assumptions.
> - The benefit is a stable test that still checks the complete bounded
port range.

## Linked Issues or Issue Description

**What happened?**

The concurrent sibling workspace runtime test failed intermittently
because it assumed array index 0 received the base port.

**Expected behavior**

The test must accept either lane as the base-port owner while it checks
the allocator invariants.

**Steps to reproduce**

1. Start two isolated workspace runtimes with `Promise.all`.
2. Force the second lane to start first.
3. Run the old assertions.
4. Observe that the test expects the wrong lane to receive the base
port.

**Paperclip version or commit**

This change targets the current `master` branch.

**Deployment mode**

Built from source test suite.

**Installation method**

Built from source with pnpm.

**Database mode**

Not database-related.

## What Changed

- Replace lane-order assertions with order-independent port invariants.
- Assert distinct ports, the base lower port, and the bounded upper
port.
- Keep concurrent startup, service URL checks, and persisted-row checks.

## Verification

- The target test passed 12 consecutive runs.
- The full test file passed 128 of 128 tests.
- Both forced lane orderings passed with the new invariants.
- TypeScript reported no errors in the changed file.
- CI will run after this pull request opens.

## Risks

Low risk. This pull request changes one test file and does not change
runtime code.

## Model Used

OpenAI Codex, GPT-5, tool use and code execution enabled. The model
reviewed and prepared the pull request metadata.

## 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 19:59:40 -07: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 b8a76081ec feat(environments): expose boot-relevant drift attribution in the custom-image overview (#11751)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip lets operators prepare and use custom images for sandbox
environments
> - The custom-image overview detected drift but did not show which boot
source changed
> - Operators need the changed field and values to understand why a
template no longer matches
> - This pull request adds safe drift attribution to the overview API
and the out-of-sync banner
> - The benefit is faster diagnosis without exposing secrets or internal
snapshot data

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting (server and UI).

**Problem or motivation**

The custom-image overview reported drift without identifying the changed
boot source. Operators had to inspect other data to find the cause.

**Proposed solution**

Return a classified drift summary with changed paths and their prior and
current values. Show the boot-source field in the UI banner. Keep legacy
templates and unclassified drift on the generic message.

**Alternatives considered**

The change does not expose the full snapshot or fingerprint. This keeps
the overview contract small and avoids secret disclosure.

**Roadmap alignment**

The change supports the existing custom-image environment workflow and
does not duplicate a roadmap item.

## What Changed

- Add `activeTemplateDrift` to the custom-image overview response.
- Classify drift as `boot_source_drift`, `knob_only`, or `unclassified`.
- Return drifted paths with safe `from` and `to` values.
- Show the changed boot-source field and values in the out-of-sync
banner.
- Keep legacy templates fail-closed and exclude secrets, fingerprints,
and raw snapshots.
- Add server and UI tests for the new behavior.

## Verification

- `npx vitest run
server/src/__tests__/environment-custom-images-service.test.ts` passes
with 27 tests.
- `npx vitest run ui/src/pages/CompanyEnvironments.test.tsx` passes with
27 tests.
- `pnpm --filter @paperclipai/ui exec tsc --noEmit` passes.
- Review the overview response and banner cases for boot-source,
knob-only, and legacy drift.

## Risks

The overview response gains one optional field. Legacy templates remain
compatible because they return `unclassified` and keep the generic
banner. The service excludes secret values, fingerprints, and raw
snapshots.

## Model Used

OpenAI Codex, GPT-5, tool use and code execution enabled. The model
reviewed the handoff and managed the pull request.

## 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] 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>
2026-08-19 16:36:18 -07:00
DottaandPaperclip 2eb9a09c0c fix(runtime): adopt surviving shell-command services (#11744)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Managed workspace services must continue after a control-plane
restart
> - A service command can use shell control operators before it starts
the final process
> - The final process command line then differs from the stored shell
expression
> - Paperclip rejected that valid process even when its listener,
process group, and workspace matched
> - This pull request uses the stronger ownership checks for shell
expressions
> - The benefit is that Paperclip can adopt a valid service after a
restart

## Linked Issues or Issue Description

Refs #11740

**What happened?**

A managed service could use a command such as `env | sort > file; exec
pnpm dev`. After a control-plane restart, the surviving process command
line contained only the final program. Paperclip compared it with the
complete shell expression and rejected the service.

**Expected behavior**

Paperclip must adopt the surviving service when the listener, process
group, and workspace directory prove ownership.

**Steps to reproduce**

1. Configure a managed workspace service with a shell pipeline or
command sequence.
2. Start the service.
3. Restart the control plane while the service stays alive.
4. Observe that Paperclip starts a replacement instead of adopting the
live service.

**Paperclip version or commit**

`bd059a073d`

**Deployment mode**

Local dev with managed workspace services.

## What Changed

- Detect shell control syntax outside quoted strings.
- Skip the weak command-line comparison for these shell expressions.
- Require the live port owner to remain in the recorded process group.
- Keep the existing workspace directory check.
- Add unit and restart-adoption regression tests.

## Verification

- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/local-service-supervisor.test.ts
src/__tests__/workspace-runtime.test.ts -t 'does not compare shell
expressions|re-adopts a live service whose shell command differs'
--reporter=verbose` — 2 passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `git diff --check origin/master...HEAD` — passed.

## Risks

- Low risk. The relaxed command comparison applies only to shell
expressions.
- Listener ownership, process-group ownership, and workspace directory
checks still fail closed.
- This change does not change the database schema, lockfile, workflow
files, or user interface.

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

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-19 16:42:43 -05:00
1b259d7be4 build(deps): bump radix-ui from 1.6.4 to 1.6.7 (#11726)
Bumps
[radix-ui](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radix-ui)
from 1.6.4 to 1.6.7.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/radix-ui/primitives/blob/main/packages/react/radix-ui/CHANGELOG.md">radix-ui's
changelog</a>.</em></p>
<blockquote>
<h2>1.6.6, 1.6.7</h2>
<ul>
<li>Reverted breaking changes that caused compatibility issues with
React Server Components.</li>
</ul>
<h2>1.6.5</h2>
<ul>
<li>Republish through CI to attach provenance attestations. The previous
versions of these packages were published manually outside of CI and
therefore shipped without provenance; this patch re-releases the same
code through the CI pipeline so every package includes an
attestation.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/radix-ui/primitives/commits/1.6.7/packages/react/radix-ui">compare
view</a></li>
</ul>
</details>
<br />

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Priya Raman <priya@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-19 14:28:35 -07:00
DottaandPaperclip 01ddc26a37 fix(routines): clear transient execution failures (#9689)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Scheduled routines track each dispatch in `routine_runs` and link it
to an execution issue
> - Moving an execution issue to `blocked` or `cancelled` correctly
records a failed run state for operator visibility
> - When that issue later resumes or completes, the run can retain the
earlier failure reason and completion timestamp
> - That stale state makes an active or successfully completed routine
appear failed
> - This pull request reconciles the run back to a live state on resume
and preserves cleared failure details as completion context
> - The benefit is that routine run status consistently reflects the
current execution issue lifecycle without losing useful recovery history

## Linked Issues or Issue Description

Refs #9201

### What happened?

A routine execution issue that temporarily moved to `blocked` or
`cancelled` caused its linked routine run to become `failed`. If the
issue later returned to an active status or reached `done`, the routine
run could keep the stale failure reason and terminal timestamp.

### Expected behavior

Active execution issues should have an `issue_created` run with no
failure or completion timestamp. Completed execution issues should have
a `completed` run with no active failure reason, while retaining any
earlier transient failure in structured trigger context for diagnosis.

### Steps to reproduce

1. Create a routine run linked to a routine execution issue.
2. Move the issue to `blocked` and synchronize the run state.
3. Move the issue back to `in_progress` or forward to `done` and
synchronize again.
4. Observe that the run previously retained stale failed-state fields.

### Environment

- Reproduced on `master` at `da549123cc`.
- Core server behavior; not adapter-specific.
- Covered with the embedded PostgreSQL routines service test harness.

## What Changed

- Load the linked routine run while synchronizing execution issue
status.
- Restore transiently failed runs to `issue_created` when their
execution issue resumes active work.
- Clear stale failure state when an execution issue completes and retain
the earlier failure under `triggerPayload.transientFailure`.
- Add regression coverage for both resumed and completed execution
issues.

## Verification

- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/routines-service.test.ts` — 57 tests passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.

## Risks

- Low risk: the change is limited to routine execution issue/run
reconciliation.
- A completed run now stores a prior failed-state reason as structured
transient context instead of leaving `failureReason` populated.
- No schema, migration, API contract, or UI behavior changes are
included.

> 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 using GPT-5.4 with reasoning, repository tools, GitHub
CLI access, code execution, and focused test execution. The runtime did
not expose a context-window size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-19 15:43:26 -05: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
dependabot[bot] 433b1eb099 build(deps): bump @aws-sdk/client-s3 from 3.1106.0 to 3.1111.0 (#11714)
Bumps
[@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3)
from 3.1106.0 to 3.1111.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/aws/aws-sdk-js-v3/releases">@​aws-sdk/client-s3's
releases</a>.</em></p>
<blockquote>
<h2>v3.1111.0</h2>
<h4>3.1111.0(2026-08-14)</h4>
<h5>Chores</h5>
<ul>
<li>upgrade to typescript 7 (<a
href="https://redirect.github.com/aws/aws-sdk-js-v3/pull/8264">#8264</a>)
(<a
href="https://github.com/aws/aws-sdk-js-v3/commit/ca81fbb7398345aa31482dd7aa1971bccf278989">ca81fbb7</a>)</li>
<li>remove jest, use vitest for remaining test suites (<a
href="https://redirect.github.com/aws/aws-sdk-js-v3/pull/8263">#8263</a>)
(<a
href="https://github.com/aws/aws-sdk-js-v3/commit/3a88aada579866331fe642d4b8147ede799deafa">3a88aada</a>)</li>
</ul>
<h5>Documentation Changes</h5>
<ul>
<li><strong>client-redshift:</strong> Amazon Redshift now unlocks a
locked admin user account and resets the failed-login counter when you
update the admin password using the ModifyCluster API. This option is
available only when account lockout security is enabled. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/b93cb20c99c76c43369ffe2fb633577971c93236">b93cb20c</a>)</li>
<li><strong>client-redshift-serverless:</strong> Amazon Redshift now
unlocks a locked admin user account and resets the failed-login counter
when you update the admin password using the UpdateNamespace API. This
option is available only when account lockout security is enabled. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/197b4aa6163ac76eef6c7dce29b922fafdab395d">197b4aa6</a>)</li>
</ul>
<h5>New Features</h5>
<ul>
<li><strong>clients:</strong> update client endpoints as of 2026-08-14
(<a
href="https://github.com/aws/aws-sdk-js-v3/commit/1e7a28061dde539727980cb25689db3c50d1507e">1e7a2806</a>)</li>
<li><strong>client-bedrock-agentcore-control:</strong> Adds AgentCore
Payments support for CMK, Marketplace Subscriptions and QuickCreate (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/39108eb0d601bb5916971f56dc8573ec17842cf8">39108eb0</a>)</li>
<li><strong>client-sagemaker:</strong> Release support for g7.2xlarge,
g7.4xlarge, g7.8xlarge, g7.12xlarge, g7.24xlarge, and g7.48xlarge
instance types for SageMaker HyperPod (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/7198c1938d7897dc4f3839b04d8a55dba0f75c4b">7198c193</a>)</li>
<li><strong>client-mwaa-serverless:</strong> Adds support for Consuming
code for MWAA Serverless (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/e3edae27ddc8bcaf3e30f5ec93abdae0e013fae2">e3edae27</a>)</li>
<li><strong>client-bedrock-agent-runtime:</strong> Adds
CheckIngestedDocumentAcl and GetIngestedDocumentAcl APIs to Amazon
Bedrock Knowledge Bases. Customers can verify user access to documents
based on ingested ACLs and retrieve full ACL details including allow and
deny entries, enabling validation of ACL ingestion without test
retrievals. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/e86c42049cc69793f80c332bd9829ba6871153ad">e86c4204</a>)</li>
<li><strong>client-observabilityadmin:</strong> CloudWatch Logs
centralization rules now support tag propagation. You can configure a
TagPropagationConfiguration on your centralization rule to automatically
sync resource tags from source to destination log groups, with
configurable conflict resolution strategies. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/c57d7a4cd3b279d750cc035e8861f1b2c8857da7">c57d7a4c</a>)</li>
<li><strong>client-bedrock-agentcore:</strong> Add support for the
Machine Payments Protocol (MPP) and x402 upto scheme payments protocol
in Amazon Bedrock AgentCore Payments. Customers can now pay for
MPP-gated resources and also pay services which requires upto scheme in
x402 (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/7fdf457a8a7ff25eb019ef61c074158a3630816a">7fdf457a</a>)</li>
<li><strong>client-glue:</strong> Added support for associating glossary
terms with iterable form items, such as table columns. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/4c2e27d138f73f804774572c7772bcfd6a44006c">4c2e27d1</a>)</li>
</ul>
<hr />
<p>For list of updated packages, view
<strong>updated-packages.md</strong> in
<strong>assets-3.1111.0.zip</strong></p>
<h2>v3.1110.0</h2>
<h4>3.1110.0(2026-08-13)</h4>
<h5>New Features</h5>
<ul>
<li><strong>client-auto-scaling:</strong> Amazon EC2 Auto Scaling now
supports terminating multiple instances in a single
TerminateInstanceInAutoScalingGroup call via the new InstanceIds
parameter, returning an Activities list. LaunchInstances now returns
IdempotentCallInProgressFault for duplicate client tokens. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/ee707980d238c2b5028e03084914a3398ce92709">ee707980</a>)</li>
<li><strong>client-cleanrooms:</strong> This release adds support for
minimum aggregation thresholds and comparison controls to the Custom
analysis rule type. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/1f84f2ae77a1cf4aed0b61dafba5da358894d61f">1f84f2ae</a>)</li>
<li><strong>client-codecommit:</strong> Added the GetBlobDifferences API
operation, which returns line-level diffs between two blob versions
without requiring a local clone. Returns structured hunks with context,
additions, and deletions. Supports pagination for large diffs. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/f1165c620804c3e7e1b035ecb9e083c723b8fba3">f1165c62</a>)</li>
<li><strong>client-securityagent:</strong> Add support for setting a
maximum task-hour budget cap on penetration tests and code reviews, and
for revalidating previously reported findings via a new REVALIDATION job
type. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/aba75d728bf5a19eebc7afa1c78df8f18d9ae8d0">aba75d72</a>)</li>
<li><strong>client-connect:</strong> Adds the StartAssistantContact API
to start chat contacts handled by an AI agent. Adds SegmentAttributes to
StartWebRTCContact, and corrects its error response to now receive
AccessDeniedException (previously returned as an internal server error
due to a missing error declaration). (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/67f9b7bb9bed253cc03580527faf7a94a2ebb2bf">67f9b7bb</a>)</li>
<li><strong>client-acm:</strong> This change allows customers to update
their existing email-validated certificates to use the DNS validation
method. (<a
href="https://github.com/aws/aws-sdk-js-v3/commit/71c194a4678abd7a368925a38c5b63f411fbd483">71c194a4</a>)</li>
</ul>
<hr />
<p>For list of updated packages, view
<strong>updated-packages.md</strong> in
<strong>assets-3.1110.0.zip</strong></p>
<h2>v3.1109.0</h2>
<h4>3.1109.0(2026-08-12)</h4>
<h5>Documentation Changes</h5>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md">@​aws-sdk/client-s3's
changelog</a>.</em></p>
<blockquote>
<h1><a
href="https://github.com/aws/aws-sdk-js-v3/compare/v3.1110.0...v3.1111.0">3.1111.0</a>
(2026-08-14)</h1>
<p><strong>Note:</strong> Version bump only for package
<code>@​aws-sdk/client-s3</code></p>
<h1><a
href="https://github.com/aws/aws-sdk-js-v3/compare/v3.1109.0...v3.1110.0">3.1110.0</a>
(2026-08-13)</h1>
<p><strong>Note:</strong> Version bump only for package
<code>@​aws-sdk/client-s3</code></p>
<h1><a
href="https://github.com/aws/aws-sdk-js-v3/compare/v3.1108.0...v3.1109.0">3.1109.0</a>
(2026-08-12)</h1>
<p><strong>Note:</strong> Version bump only for package
<code>@​aws-sdk/client-s3</code></p>
<h1><a
href="https://github.com/aws/aws-sdk-js-v3/compare/v3.1107.0...v3.1108.0">3.1108.0</a>
(2026-08-11)</h1>
<p><strong>Note:</strong> Version bump only for package
<code>@​aws-sdk/client-s3</code></p>
<h1><a
href="https://github.com/aws/aws-sdk-js-v3/compare/v3.1106.0...v3.1107.0">3.1107.0</a>
(2026-08-10)</h1>
<p><strong>Note:</strong> Version bump only for package
<code>@​aws-sdk/client-s3</code></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/c41e9a98d4227b06099fdc7ea42bcf6fc5a1029f"><code>c41e9a9</code></a>
Publish v3.1111.0</li>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/ca81fbb7398345aa31482dd7aa1971bccf278989"><code>ca81fbb</code></a>
chore: upgrade to typescript 7 (<a
href="https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3/issues/8264">#8264</a>)</li>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/4efe5bc67b71dc5ec652fe77130f3bae9efe0173"><code>4efe5bc</code></a>
Publish v3.1110.0</li>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/d2ee371d0cc488521fbb8a2304f941a4a0b53d1a"><code>d2ee371</code></a>
Publish v3.1109.0</li>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/26b0eb790ff86399b7af7b74ce8c188f25512cc6"><code>26b0eb7</code></a>
Publish v3.1108.0</li>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/785d467fbde2fe2f3720bbb944caefcb198039ed"><code>785d467</code></a>
chore(codegen): update smithy-ts commit to bring in TS6 change (<a
href="https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3/issues/8262">#8262</a>)</li>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/edabd4a5228e259e9f3352cd47414069db755188"><code>edabd4a</code></a>
chore: upgrade to typescript 6 (<a
href="https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3/issues/8257">#8257</a>)</li>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/d87c82ba2021a598a478383ae50647e7c1af45e0"><code>d87c82b</code></a>
Publish v3.1107.0</li>
<li><a
href="https://github.com/aws/aws-sdk-js-v3/commit/2e4482a67896a98b3fc23933c5cf2ad7c1d61f10"><code>2e4482a</code></a>
chore(codegen): smithy-aws-typescript-codegen 0.52.0 (<a
href="https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3/issues/8255">#8255</a>)</li>
<li>See full diff in <a
href="https://github.com/aws/aws-sdk-js-v3/commits/v3.1111.0/clients/client-s3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@aws-sdk/client-s3&package-manager=npm_and_yarn&previous-version=3.1106.0&new-version=3.1111.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 12:53:30 -07:00