## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The `grok_local` adapter runs the native Grok Build CLI in headless
mode for unattended agent heartbeats
> - Grok CLI 1.0 started to enforce the `dontAsk` permission mode as
deny-by-default, and it takes precedence over `--always-approve`
> - The adapter passes both flags on every run, so each run dies on its
first tool call and is still recorded as a success
> - This pull request removes the `dontAsk` default so unattended runs
rely on `--always-approve` alone
> - The benefit is that `grok_local` agents can execute tools again on
current Grok CLI releases
## Linked Issues or Issue Description
No public issue exists. Description per the bug template:
**What happened?**
Every `grok_local` run on Grok CLI 1.0.x stops on its first tool call.
The stream shows the tool call move from `pending` to `failed` with
"User cancelled the execution for tool `run_terminal_command`", and the
session ends with `stopReason: "cancelled"` after one turn. The CLI
exits 0, so Paperclip records the run as succeeded with no work done,
and the issue lands in missing-disposition recovery.
**Expected behavior**
Unattended runs must auto-approve tool executions. The adapter already
passes `--always-approve` for this.
**Steps to reproduce**
In a clean Linux environment with Grok CLI 1.0.3 and `XAI_API_KEY` set,
run the adapter's exact invocation shape:
`grok --output-format streaming-json --permission-mode dontAsk
--always-approve --disable-web-search --single "Run the shell command:
echo ok"`
The tool call is denied. Drop `--permission-mode dontAsk` (or use
`--permission-mode bypassPermissions`) and the same command executes the
tool. On Grok 0.2.x the original combination worked because the CLI
accepted `dontAsk` without enforcing it; the 0.2.39 embedded docs state
the flag takes effect only for `bypassPermissions` / always-approve.
**Paperclip version or commit**
master (917d2350f)
## What Changed
- `packages/adapters/grok-local/src/server/execute.ts`: `permissionMode`
no longer defaults to `dontAsk`. The adapter passes no
`--permission-mode` flag unless one is explicitly configured.
`--always-approve` (default on) remains the unattended policy.
- `packages/adapters/grok-local/src/index.ts`: config doc updated to
explain the new default and the Grok 1.0 semantics.
- `packages/adapters/grok-local/src/server/execute.test.ts`:
default-args assertion now requires the absence of `--permission-mode`;
new test covers explicit `permissionMode` pass-through.
## Verification
- `npx vitest run packages/adapters/grok-local` — 7 files, 29 tests, all
pass.
- `pnpm --filter @paperclipai/adapter-grok-local typecheck` — clean.
- Live matrix against Grok CLI 1.0.3 in a clean sandbox: `dontAsk
--always-approve` denies the first tool call; `--always-approve` alone
executes it; `bypassPermissions --always-approve` executes it; `dontAsk`
alone denies it.
## Risks
- Low risk. Operators who explicitly set `permissionMode` keep their
value verbatim. Only the implicit default changes, and the old default
is what breaks every run on current Grok CLI releases.
- On Grok 0.2.x the flag was unenforced, so omitting it does not change
behavior there.
## Model Used
Claude Fable 5 (`claude-fable-5`), extended thinking, tool use, via
Claude Code CLI.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Execution workspaces give each run an isolated directory and a
selected base ref
> - A remote-only base ref can fail before `git worktree add` when the
ref is not local
> - A setup failure before adapter dispatch must block the run without
an agent-only retry
> - This pull request resolves both remote-tracking ref forms and bounds
recovery for the same unresolved ref
> - The benefit is correct workspace setup and no repeated pre-adapter
recovery loop
## Linked Issues or Issue Description
This PR has no existing public issue. It addresses a workspace setup
bug.
**What happened?**
A remote-only base ref could fail before `git worktree add`. A setup
failure before adapter dispatch could also queue an agent-only
missing-comment retry.
**Expected behavior**
Paperclip must resolve `fix/foo` and `origin/fix/foo` before it creates
a worktree. An unresolved ref must create a human-owned configuration
blocker. Paperclip must not queue an agent-only retry when the adapter
never starts.
**Steps to reproduce**
1. Configure an execution workspace with a base ref that exists only on
the remote.
2. Start a run that creates a fresh worktree.
3. Repeat the run with the same unresolved ref.
4. Observe one configuration blocker and no repeated agent-only recovery
action.
**Paperclip version or commit**
`7664e323189bc219d8cbe00433b2e82b682b0504`
**Deployment mode**
Built from source with `pnpm dev`.
**Agent adapter(s) involved**
Not adapter-specific. The failure occurs before adapter dispatch.
**Database mode**
Not database-related.
**Access context**
Both board and agent execution paths can use execution workspaces.
Related public pull request: `Refs #11123`.
## What Changed
- Resolve remote-only base refs with the authenticated fetch helper
before `git worktree add`.
- Support both unqualified refs and remote-tracking refs.
- Raise a `configuration_incomplete` blocker when the requested ref
remains unresolved.
- Suppress missing-comment retries when setup fails before adapter
dispatch.
- Add the requested ref to the recovery fingerprint to bound identical
recovery actions.
- Add focused tests and update the execution semantics document.
## Verification
- `tsc --noEmit` passed for the changed server code.
- Focused Vitest suites passed, including four base-ref tests,
fingerprint deduplication, and pre-adapter retry suppression.
- GitHub Actions must run the full pull request gate.
## Risks
Low risk. The change affects workspace setup before adapter dispatch.
Existing worktree reuse remains unchanged. An unresolved ref now creates
a clear configuration blocker instead of starting an adapter run.
## Model Used
OpenAI GPT-5; exact model ID `gpt-5`; agentic tool use and repository
review.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have 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>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Managed deployments provision a platform-managed default environment
for agent runs; the UI shows this environment in selectors, the agent
form, run details, and the environments page
> - Those surfaces append the raw driver key to the environment name, so
users see labels like "Paperclip Computer (sandbox)", "Paperclip
Computer · sandbox", and fallback copy such as "Managed sandbox" and
"The sandbox has no ready authentication"
> - "sandbox" is infrastructure vocabulary, not the product name of the
environment; showing it next to the managed environment's name is
confusing and off-brand
> - This pull request renders platform-managed environments by name
alone and rewords the sandbox-phrased copy, while user-created
environments keep the driver suffix so mixed lists stay distinguishable
> - The benefit is that the default environment reads as one clear
product name everywhere, and self-hosted users lose nothing: their own
environments still show the driver
## Linked Issues or Issue Description
**What existing behavior does this improve?**
Display of the platform-managed default environment across the UI.
**Subsystem affected**
UI (environment selectors, agent config form, environments page, agents
page, run details) and the claude-local/codex-local adapter auth checks.
**Current behavior**
The agent form labels the inherited default environment as "Name
(sandbox)". Environment selectors and the environments list render "Name
· sandbox". The agents page describes the environment as "<provider>
sandbox provider". The agent form's fallback label is "Managed sandbox".
Adapter auth checks say "The sandbox has no ready authentication for
this adapter."
**Proposed behavior**
Platform-managed environment rows (`metadata.managedByPaperclip`) render
their name alone. The fallback label is "Paperclip Computer". The agents
page describes managed environments as "Managed by Paperclip". Run
details omit the driver suffix for sandbox-driver environments (the
adjacent Provider entry already identifies the mechanism). Adapter auth
checks say "This environment has no ready authentication for this
adapter."
**Reason and benefit**
The managed environment carries a product name. Appending the raw driver
key ("sandbox") to it is noise and contradicts the product naming.
User-created environments keep the driver suffix, so mixed lists stay
distinguishable.
**Breaking changes**
None. Message text of the auth check is not read programmatically; the
UI keys off `ADAPTER_AUTH_MISSING_CHECK_CODE`. Rows without the managed
marker render exactly as before.
## What Changed
- New `environmentDisplayLabel` helper in
`ui/src/lib/managed-sandbox-environment.ts`: managed rows → name alone;
other rows → "Name · driver".
- `AgentConfigForm`: inherited-default label uses the helper; fallback
copy "Managed sandbox" → "Paperclip Computer"; environment options use
the helper.
- `ProjectProperties`, `CompanyEnvironments`: environment selector
options use the helper; the environments-list row hides the driver
suffix on managed rows; the managed detail page's fallback description
no longer says "sandbox".
- `Agents` page: managed environments are described as "Managed by
Paperclip" instead of "<provider> sandbox provider".
- `CommentThread` run details: the driver suffix is omitted for
sandbox-driver environments.
- claude-local and codex-local adapters: auth-missing check message/hint
reworded from "sandbox" to "environment" (ACP and environment-test
paths); claude-local probe/effort/login hints reworded the same way.
- Run status lines: "Syncing workspace to sandbox", "Exporting git
changes from sandbox", "Starting adapter in sandbox", and friends now
say "environment"; "Finalizing sandbox workspace" → "Finalizing
workspace". Templated transfer-progress lines map the `sandbox`
transport key to "environment" for display (`runtime-progress.ts`).
- Agent form sign-in panel: "Sign in to the sandbox" → "Sign in to the
environment"; "Authenticated. The sandbox has credentials now." → "…The
environment has credentials now."
- Feature catalog + instance settings card: "Managed Sandbox Only" →
"Managed Environment Only" (setting key unchanged; the card keeps its
alphabetical slot).
- Server agents routes: execution-target failure and test-identity copy
no longer say "sandbox"; workspace-mode label "Cloud sandbox" → "Cloud
environment".
- Tests: new `environmentDisplayLabel` unit cases; new `AgentConfigForm`
render case asserting the managed default renders without "(sandbox)" or
"· sandbox"; status-line assertions updated across adapter-utils, server
heartbeat/live-run, and UI chat suites.
## Verification
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `pnpm --filter @paperclipai/adapter-claude-local typecheck` and
`--filter @paperclipai/adapter-codex-local typecheck` — clean.
- `vitest run` for `managed-sandbox-environment.test.ts`,
`AgentConfigForm.render.test.tsx`, `CompanyEnvironments.test.tsx`,
`Agents.test.tsx`, `CommentThread.test.tsx`, `NewAgent.test.tsx` — all
green (118 tests across the two runs).
## Risks
Low risk. Cosmetic label changes only; no data or API changes. Rows
without `metadata.managedByPaperclip` render exactly as before, so
self-hosted deployments with their own environments see no change. The
only self-hosted-visible wording changes are the adapter auth-check
message and the driver suffix omission on sandbox-driver rows in run
details.
## Model Used
- Claude (Anthropic) — claude-fable-5 (Claude Fable 5), Claude Code CLI,
extended thinking, tool use.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes (no
docs reference these labels)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip uses duplex routes to carry data from plugin workers.
> - PR #11860 added the product fix for buffered data after an early
route end.
> - The fix needs a regression test for a listener that binds after the
byte cap ends the route.
> - This pull request adds that test and protects the fix from later
regressions.
> - The benefit is clear test coverage for late-listener delivery.
## Linked Issues or Issue Description
This pull request adds regression coverage for the fix in [PR
#11860](https://github.com/paperclipai/paperclip/pull/11860).
The product fix already exists on `master`. Before that fix, a late
listener could receive no data after the byte cap ended the route. The
test sends two three-byte `€` chunks to a route with a four-byte cap,
waits for route end, then binds the listener. It expects the first valid
chunk.
## What Changed
- Add one server regression test for late-listener delivery after
byte-cap route termination.
- Keep the product code unchanged in this pull request.
## Verification
- The test passes on the current branch.
- PR #11860 merged the product fix into `master` at commit
`33eb68b3ae4ce7ee27b31c59bd41db600ad47d19`.
- GitHub CI passes on the current head.
- Greptile reports 5/5 with no blocking finding.
## Risks
Low risk. This pull request changes one test file and no product code,
schema, public API, or authentication flow.
## Model Used
OpenAI GPT-5. Runtime model ID: GPT-5. Context window: not exposed in
this run. Capabilities used: repository review, GitHub operations, and
tool use. The implementation came from the engineer's authorized test
commit.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used with version and capability
details
- [x] I have checked ROADMAP.md and confirmed this pull request does not
duplicate planned core work
- [x] I have searched GitHub for duplicate or related pull requests and
linked them above
- [x] I have either linked an existing issue or described the issue in
this pull request
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [x] I have run the relevant test and GitHub CI passes
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation, or documentation does not
apply
- [x] I have considered and documented the 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>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Adapter Test checks whether an agent adapter can run with its
configured environment, and every local-driver adapter (Claude, Codex,
Gemini, OpenCode, Pi, Cursor, etc.) shares this Test route and its UI
resolution logic
> - The Claude ACP Test lane could report pass without checking local or
remote authentication, and the shared Test route and UI had gaps in
environment binding, probe safety, and managed-sandbox resolution that
affect every adapter that uses the Test button, not only Claude
> - This pull request verifies authentication on every Claude ACP
target, and closes the shared Test-route/UI gaps: tenant-binding on the
route, a managed-sandbox-only redirect that matches the real run path,
and a three-tier environment resolution in the UI
> - The benefit is a truthful Test result with safer probe execution and
tenant isolation, for Claude specifically and for every other local
adapter that shares this Test surface
## Linked Issues or Issue Description
**What happened?**
The Claude ACP Test lane returned `status: "pass"` without checking
authentication for some local and non-sandbox targets. Separately, the
shared `/companies/:companyId/adapters/:type/test-environment` route —
used by every local-driver adapter, not only Claude — accepted a foreign
environment id, and its UI resolution did not mirror the server's
managed-sandbox-only redirect.
**Expected behavior**
The Test lane checks the resolved credential and hello probe for every
Claude ACP target. The shared adapter Test route rejects a foreign
environment before it reveals environment details or starts a lease, for
any adapter type. The Test's environment resolution (UI and server)
matches the real run's three-tier resolution, including the
managed-sandbox-only redirect.
**Steps to reproduce**
1. Run the Claude ACP Test lane against a local target without a valid
credential.
2. Run the adapter Test route with an environment id from another
company (any adapter type).
3. Observe the pass result on step 1, or the missing tenant-binding
rejection on step 2.
**Paperclip version or commit**
`933749e01f74e82ce5d315c071be534d04e01158`
**Deployment mode**
Local dev (`pnpm dev`) and server route tests.
**Agent adapter(s) involved**
Claude Code directly (the ACP auth-verification work). The
tenant-binding guard, managed-sandbox-only redirect, and UI three-tier
resolution apply to the shared adapter Test route and affect every
local-driver adapter (Codex, Gemini, OpenCode, Pi, Cursor, etc.), not
only Claude — see "What Changed" below for the split between Claude-only
and shared changes.
**Database mode**
Not database-related.
**Access context**
Both board and agent paths use the affected Test surface, for every
local-driver adapter.
**Additional context**
Two commits that were previously bundled into this PR — a
`plugin-worker-manager` duplex-channel frame-bound fix and a
`workspace-runtime` exit-persist crash fix — are unrelated to the
adapter Test lane and have been split out into their own PRs: #11860 and
#11861.
## What Changed
Claude-only (`packages/adapters/claude-local`):
- Verify `CLAUDE_CODE_OAUTH_TOKEN` and run the hello probe for every
Claude ACP target.
- Keep `adapter_auth_missing` sandbox-only and report missing
non-sandbox credentials as a warning.
- Add a deny-by-default probe environment builder for the ACP and CLI
local probes.
- Log only fixed probe context and allowlisted classifications.
- Seed the host OAuth token into the hello probe environment.
Shared, cross-adapter (`server/src/routes/agents.ts`,
`ui/src/lib/adapter-test-environment.ts`,
`ui/src/components/AgentConfigForm.tsx`,
`ui/src/components/OnboardingWizard.tsx`):
- Add a company-binding guard and a binding assertion for the generic
`/companies/:companyId/adapters/:type/test-environment` route, so a
foreign-company environment id is rejected before any secret resolution
or sandbox lease, for every adapter type.
- Resolve all three server environment tiers (agent default, instance
default, local default) in the UI, and add the managed-sandbox-only
redirect so the Test probes the same target a real run would use.
- Enforce onboarding Test results: block hire on a failed environment
test.
- Add regression tests for authentication, tenant binding, probe safety,
diagnostics, and UI resolution.
## Verification
- Adapter suites pass for the Claude local server probe, remote, ACP,
auth, probe environment, and config paths.
- Server route tests pass, including the five tenant-binding cases.
- UI adapter Test environment resolver tests pass for all three
resolution tiers.
- Adapter package `tsc --noEmit` exits 0.
- Full CI must pass on this pull request.
## Risks
The probe environment now denies caller variables by default. A required
variable that is not on the allowlist could stop a probe from starting.
The route now rejects foreign environment ids with a fixed 403 response.
The managed-sandbox-only redirect changes where the Test (and the login
affordance) probes for every local-driver adapter under that policy, not
only Claude — operators running other local adapters under
managed-sandbox-only will see their Test target move from local to the
managed sandbox, matching what real runs already do. The change limits
secret and diagnostic exposure.
## Model Used
Original implementation: OpenAI Codex, GPT-5; exact context window not
exposed in that run; tool use and code execution.
This revision (commit split and title/description correction): Claude,
Sonnet 5 (claude-sonnet-5). The original title and description described
this PR as Claude-only; review found it also changes the shared adapter
Test route and UI resolution used by every local-driver adapter, and
carried two unrelated server fixes. Claude split those two commits into
#11860 and #11861 via `git rebase --onto` (verified byte-identical to
the original tree minus those commits) and rewrote this description to
reflect the actual scope. No functional code in this PR was authored by
Claude.
## 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>
## Thinking Path
> - Paperclip uses a pnpm workspace for its packages.
> - Package manifests and the lockfile must stay synchronized.
> - A merged Node version policy fix changed package manifests on
master.
> - The frozen lockfile no longer matched those manifests.
> - This automated pull request refreshes the generated lockfile.
> - The benefit is that clean installs and required CI jobs can run
again.
## What Changed
- Regenerated `pnpm-lock.yaml` from the current package manifests.
## Verification
- `pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile`
- The repository PR workflow validates the generated lockfile with a
frozen install.
## Risks
Low risk. This is an automation-generated lockfile-only update after
manifest changes on master.
## Model Used
None — automation-generated.
Co-authored-by: lockfile-bot <lockfile-bot@users.noreply.github.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip now requires Node.js 24.11.0 or later
> - Each workspace package must publish the same Node.js engine
requirement
> - The Kimi adapter entered `master` after the Node.js upgrade branch
started
> - Its package still used Node.js 22 types and had no engine
requirement
> - This pull request aligns the Kimi adapter with the repository
Node.js policy
> - The benefit is that the Node.js policy check passes again on
`master`
## Linked Issues or Issue Description
**What happened?**
The `pnpm check:node-version` command fails on `master`. The Kimi
adapter uses `@types/node` 22 and has no `engines.node` value.
**Expected behavior**
All workspace packages must use Node.js 24 types and declare Node.js
24.11.0 as the minimum version.
**Steps to reproduce**
1. Check out commit `a7e689b3c`.
2. Use Node.js 24.11.0.
3. Run `pnpm check:node-version`.
**Paperclip version or commit**
`a7e689b3c`
**Deployment mode**
Local dev (`pnpm dev`).
## What Changed
- Update the Kimi adapter to use `@types/node` 24.
- Add the repository minimum Node.js engine requirement to the Kimi
adapter package.
- Keep `pnpm-lock.yaml` out of this pull request.
## Verification
- `npx -y -p node@24.11.0 -c 'node --version && pnpm check:node-version
&& pnpm --filter @paperclipai/adapter-kimi-local typecheck'`
- The command reports Node.js `v24.11.0`.
- The Node.js policy check passes.
- The Kimi adapter typecheck passes.
- A broader local suite was started and stopped at the maintainer's
request after the focused checks passed.
## Risks
- Low risk. This change updates package metadata and development types
only.
- The lockfile refresh runs in separate repository automation.
> 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, with repository inspection, shell tools, and code
execution.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The task composer and the New Task dialog show a work-mode chip (how
the agent will run a task)
> - The default mode was labeled "Agent mode", and the New Task dialog
chip abbreviated every mode to one word ("Auto", "Plan", "Ask")
> - "Agent mode" is confusing because every mode runs an agent, and the
abbreviated chip hid what the label means
> - This pull request renames the mode to "Auto mode" and makes every
mode chip show the full label
> - The benefit is a clearer, consistent mode name in every place the
user selects a work mode
## Linked Issues or Issue Description
No public GitHub issue exists for this change. Description follows the
enhancement template:
**What existing behavior does this improve?**
The work-mode selector chips in the task composer and in the New Task
dialog.
**Subsystem affected**
UI (`ui/src/lib/work-mode-meta.ts`,
`ui/src/components/NewIssueDialog.tsx`).
**Current behavior**
The default work mode is labeled "Agent mode". The New Task dialog chip
shows a shortened label ("Auto", "Plan", "Ask") from a separate
`shortLabel` field.
**Proposed behavior**
The default work mode is labeled "Auto mode". Every chip shows the full
label ("Auto mode", "Plan mode", "Ask mode"). The `shortLabel` field is
removed so no surface can fall back to the short form.
**Reason and benefit**
"Agent mode" does not describe the behavior — all modes use an agent.
"Auto mode" states what the mode does. One label field keeps every
surface consistent.
**Breaking changes**
None. This is a display-string change only. No API, storage, or mode-key
changes.
## What Changed
- Renamed the `standard` work-mode label from "Agent mode" to "Auto
mode" in `ui/src/lib/work-mode-meta.ts`, the single source for all mode
chips.
- Changed the New Task dialog mode chip to render the full `label`
instead of `shortLabel`.
- Deleted the `shortLabel` field from `WorkModeMeta` so nothing can
silently regress to the short form.
- Updated unit tests and fixtures to pin the full labels.
## Verification
- Run `pnpm --filter @paperclipai/ui test --
src/lib/work-mode-meta.test.ts src/components/NewIssueDialog.test.tsx
src/components/IssueChatThread.test.tsx
src/components/task-chat/TaskChatComposer.test.tsx`. All tests pass. The
tests assert the labels are exactly "Auto mode", "Plan mode", and "Ask
mode".
- Manual: start the dev server, open the board, press `c` to open the
New Task dialog, and press Cmd+Period to cycle modes. The chip reads
"Auto mode", "Plan mode", then "Ask mode". The composer chip on an open
task shows the same labels.
## Risks
- Low risk. Display strings only. The chip is a few pixels wider in the
New Task dialog; no layout overflow was observed in any of the three
modes.
## Model Used
- Claude Fable 5 (Anthropic, model ID `claude-fable-5`), extended
thinking with tool use, run inside a Claude Code agent session.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] 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: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip runs across the CLI, server, adapters, plugins, CI, and
container images.
> - These surfaces declared different Node.js versions from 20 through
24.
> - A newer `@types/node` major can expose APIs that the supported
runtime does not provide.
> - Node.js 20 is no longer a suitable project baseline, and Node.js 24
is the current LTS line.
> - This pull request sets Node.js 24.11.0 as one repository-wide
baseline, adds a drift check, and gives users actionable startup
guidance when their runtime is too old.
> - The benefit is one clear runtime contract for development, release,
installation, and published packages.
## Linked Issues or Issue Description
Refs #2734
Refs #11727
Refs #739
## What Changed
- Require Node.js 24.11.0 or newer in all 42 package manifests and
runtime checks.
- Use Node.js 24 in GitHub Actions, Docker images, smoke images, sandbox
setup, portable installs, and esbuild targets.
- Align every direct `@types/node` declaration on `^24.0.0`.
- Prevent Dependabot from opening major `@types/node` upgrades without a
matching runtime decision.
- Add `.nvmrc` and a CI policy check for Node version drift.
- Update ACP version gates, tests, and user documentation for the new
minimum.
- Print a non-blocking warning on CLI and server startup when Node is
unsupported, with remediation through a version manager or the
documented downloaded `install.sh` workflow.
- Deduplicate that warning when `paperclipai run` boots the CLI and
server in the same process.
## Verification
- `node scripts/check-node-version-policy.mjs`
- `node --check scripts/check-node-version-policy.mjs`
- `node --check cli/esbuild.config.mjs`
- `node --check scripts/generate-npm-package-json.mjs`
- `bash -n scripts/install.sh scripts/test-install-sh-docker.sh
scripts/e2e-install-lifecycle.sh`
- Parsed all 42 package manifests and confirmed `engines.node` is
`>=24.11.0`.
- `git diff --check`
- `vitest run
packages/adapter-utils/src/sandbox-install-command.test.ts` passed with
3 tests.
- `vitest run cli/src/node-version.test.ts` passed with 4 tests.
- Directly exercised the shared warning helper for unsupported-version
messaging and same-process deduplication.
- The focused exe.dev suite could not resolve the locally unbuilt plugin
SDK from this isolated worktree. A full offline workspace install was
also blocked because the package-manager signature verifier requires
registry access. The full suite was not run locally; draft CI performs a
clean install and evaluates the wider impact.
## Risks
- This is a breaking runtime change for users, plugins, and deployments
that still use Node.js 20 or 22.
- Published workspace packages will now produce an engine warning or
failure in strict package managers on older Node.js releases.
- Node.js 24 can reveal dependency, native module, Playwright, or agent
CLI compatibility issues in CI.
- The bootstrap installer now installs Node.js 24 when the current
runtime is older than 24.11.0.
- The portable sandbox fallback is pinned to Node.js 24.11.0 and depends
on that upstream tarball remaining available.
- Unsupported runtimes continue booting after a warning, so a later
incompatibility can still fail at its point of use.
- The CLI and server share the warning policy through the published
`@paperclipai/shared` package; packaging checks must keep that subpath
export available.
- This PR does not commit `pnpm-lock.yaml` because repository policy
assigns lockfile generation to CI.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex based on GPT-5. The exact deployment ID and context
window are not exposed in this session. Reasoning, repository tools,
shell execution, and GitHub tools were enabled.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The host and a plugin worker talk over a duplex channel route with
bounds on buffered frames and total bytes
> - A worker can batch its data and exit frames with the open reply, so
those frames arrive before the route binds and before a listener
attaches
> - Two of the route bounds did not hold on that pre-bind path: a shared
limit let the pre-open hold swallow an over-limit frame before the
buffered-frame bound could end the route, and the route end discarded
chunks a later listener still needed
> - This pull request gives the pre-open hold its own ceiling above the
buffered bound, and keeps the buffered chunks across a route end
> - The benefit is a duplex route that enforces its bounds and preserves
valid data, even when a worker batches frames ahead of the bind
## Linked Issues or Issue Description
No existing GitHub issue covers this. Filing it directly here, following
the bug report template.
**What happened?**
Two duplex channel route bounds in
`server/src/services/plugin-worker-manager.ts` did not hold when the
data and exit frames arrived in the open-reply read batch, before the
route bound:
- The pre-open hold and the pre-bind buffered-frame bound shared one
limit. When a caller lowered the buffered bound, the hold dropped the
overflow frame as a protocol error before the buffered bound could end
the route, so the route never ended.
- The route end discarded the buffered chunks. A frame can end the route
during the replay, before a listener attaches, and the chunks the host
accepted before that frame are valid data.
**Expected behavior**
The pre-open hold uses its own ceiling, above the buffered bound, so the
replay after the bind lets the buffered bound end the route. A route end
keeps the buffered chunks so a listener that attaches after the end
still drains them.
**Steps to reproduce**
1. Open a duplex channel where the worker batches several data frames
with the open reply.
2. Lower `maxPreBindBufferedFrames` below the batch size.
3. Observe the route fails to end on the buffered-frame bound, or a
listener that attaches after an end-during-replay never receives the
chunks buffered before that end.
**Paperclip version or commit**
`933749e01f74e82ce5d315c071be534d04e01158`
**Deployment mode**
Local dev (`pnpm dev`) and server unit tests.
**Agent adapter(s) involved**
None — this is host/plugin-worker transport infrastructure, not
adapter-specific.
**Database mode**
Not database-related.
**Access context**
Any board or agent path that runs a plugin worker over a duplex channel
route.
## What Changed
- Give the pre-open frame hold its own ceiling
(`MAX_DUPLEX_CHANNEL_PRE_OPEN_HOLD_FRAMES`), separate from the pre-bind
buffered-frame bound, so lowering the buffered bound still ends the
route instead of being pre-empted by the hold.
- Keep the buffered chunks on a route end instead of discarding them, so
a listener that attaches after an end-during-replay still drains the
data the host already accepted.
- Add two regression tests that batch frames with the open reply, so
both bounds run through the pre-bind path deterministically.
## Verification
- `cd server && npx vitest run
src/__tests__/plugin-worker-manager-duplex.test.ts` — 24/24 tests pass,
including the two new regression cases.
## Risks
Low risk. This only changes bound bookkeeping on an internal transport
path (frame hold ceiling and end-time buffer retention); it does not
change the wire protocol or any public API. The new ceiling is a
constant above the existing buffered bound, so pre-open holds are still
capped.
## Model Used
Claude, Sonnet 5 (claude-sonnet-5); assisted with repository-grounded
diff review and drafted this PR description from the commit and code
history. No functional code in this PR was authored by Claude — the fix
itself is Priya Raman's, preserved with original authorship intact.
## 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>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - A runtime service (e.g. a dev server an agent started) runs as a
child process tracked against a project row
> - When that child exits on its own, the host records its terminal
status in the database as a detached, best-effort persist
> - A caller can delete the project (or company) while the child still
runs, so the `project_id` foreign key rejects that persist, and the
detached write had no error handler, turning the rejection into an
unhandled crash
> - This pull request wraps the exit-time persist in a try/catch and
logs the failure instead of crashing the host
> - The benefit is a host that survives a project deleted out from under
a still-running runtime service, instead of taking down the whole
process on an unrelated cleanup
## Linked Issues or Issue Description
No existing GitHub issue covers this. Filing it directly here, following
the bug report template.
**What happened?**
`registerRuntimeService`'s child `exit` handler in
`server/src/services/workspace-runtime.ts` runs a detached, unawaited
persist of the terminal service status. If the parent project row was
deleted while the service was still running, the `project_id` foreign
key rejects the write. The detached persist had no error handler, so the
rejection surfaced as an unhandled promise rejection and could crash the
host.
**Expected behavior**
The exit-time persist is best effort: every error inside it is caught
and logged, so a foreign-key rejection (or any other persist failure)
never crashes the host.
**Steps to reproduce**
1. Start a runtime service tied to a project.
2. Delete the project (or company) while the service is still running.
3. Let the child process exit on its own.
4. Observe the detached persist throws an unhandled foreign-key error.
**Paperclip version or commit**
`933749e01f74e82ce5d315c071be534d04e01158`
**Deployment mode**
Local dev (`pnpm dev`) and server unit tests (embedded Postgres).
**Agent adapter(s) involved**
None — this is runtime-service lifecycle infrastructure, not
adapter-specific.
**Database mode**
Embedded/managed Postgres — the fix concerns the `project_id` foreign
key on the runtime-service table.
**Access context**
Any board or agent path that starts a runtime service (e.g. a dev
server) tied to a project that can later be deleted.
## What Changed
- Wrap the exit-handler's `cleanupRecordExposure` /
`removeLocalServiceRegistryRecord` / `persistRuntimeServiceRecord`
sequence in a try/catch; log a warning on failure instead of letting the
rejection escape.
- Terminate real child processes in the embedded-postgres test teardown
before the row deletes, so a left-over child does not exit later and
write a row that references an already-deleted project.
## Verification
- `cd server && npx vitest run src/__tests__/workspace-runtime.test.ts`
covers the new exit-persist-after-parent-delete regression case. This
suite spins up embedded Postgres and did not finish inside this review's
local time budget, so I did not confirm a local pass — deferring to CI,
which runs it as part of the normal server test job.
## Risks
Low risk. The change only adds error handling around an existing
best-effort, detached persist — it does not change the happy-path
behavior or the persisted schema. A persist failure is now logged
instead of crashing the host, which is strictly safer.
## Model Used
Claude, Sonnet 5 (claude-sonnet-5); assisted with repository-grounded
diff review and drafted this PR description from the commit and code
history. No functional code in this PR was authored by Claude — the fix
itself is Priya Raman's, preserved with original authorship intact.
## 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>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The release workflow publishes every public package to npm and polls
each version's registry visibility before continuing
> - #11834 raised the poll budget from 60 seconds to 5 minutes after npm
CDN propagation lag failed four canary runs
> - The very next canary run missed by ten seconds:
`adapter-opencode-local@2026.821.0-canary.2` was accepted at 07:02:27
UTC and became visible at 07:07:40, just past the final poll
> - This pull request doubles the per-package budget to 10 minutes
> - The benefit is a release train that survives the one consistently
slow package; the 90-minute publish job timeout from #11834 already
absorbs it
## Linked Issues or Issue Description
Follow-up to #11834. Evidence in the `Release` run for `16149a75f`:
every package's publish became visible within seconds except
`adapter-opencode-local`, which has lagged 3-5+ minutes on all of
today's runs and exceeded the 5-minute budget by ten seconds on the
latest.
## What Changed
- `NPM_PUBLISH_VERIFY_ATTEMPTS` 30 → 60 (with
`NPM_PUBLISH_VERIFY_DELAY_SECONDS: "10"`, a 10-minute per-package
budget), plus the comment documenting the observed near-miss.
## Verification
- Same env-override plumbing verified in #11834; only the numeric budget
changes. The next master push (this merge) exercises the canary path.
## Risks
- Low risk: a genuinely failed publish reports in up to 10 minutes;
healthy publishes exit the poll on first visibility.
## Model Used
Claude Fable 5 (Anthropic, `claude-fable-5`) with extended thinking and
agentic tool use via the Claude Code CLI.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable (not applicable:
numeric workflow env tuning)
- [x] I have updated relevant documentation to reflect my changes
(workflow comment updated)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Plugins can contribute detail tabs to project pages, and plugin UI
bundles render through a host-provided React bridge
> - A cold load of a plugin-tab deep link redirected to the Issues tab,
because the plugin-slots query is disabled until the project's company
resolves and a disabled query reports `isLoading: false`
> - The redirect made shareable plugin-tab URLs and browser
reload/back/forward unreliable for every detail-tab plugin
> - Separately, the bridge shim rebuilt `jsx`/`jsxs` on top of
`createElement(type, { children })`, which drops React's static-children
marking and floods the dev console with "unique key" warnings from
plugin components
> - This pull request holds the tab decision on a skeleton until
contributions actually load, and exposes the host's real
`react/jsx-runtime` on the plugin bridge with the old shim as fallback
> - The benefit is that plugin detail tabs survive direct open, reload,
back, and forward, and plugin surfaces stop emitting spurious React key
warnings
## Linked Issues or Issue Description
**What happened**
Opening a project URL with `?tab=plugin:<publisher>.<plugin>:<tab>`
directly, or reloading while on such a tab, redirected to the project's
Issues tab even though the plugin contribution was registered and
available. The dev console also showed "each child in a list should have
a unique key" warnings pointing at plugin-rendered components.
**Expected behavior**
Direct open, reload, back, and forward keep the requested plugin tab
when its contribution is available. When the contribution truly does not
exist, the page still falls back safely to the Issues tab. Plugin
components with static multi-child JSX render without React key
warnings.
**Steps to reproduce**
1. Install a plugin that contributes a project detail tab.
2. Open the tab, copy the URL, and open it in a new browser tab (or
press reload).
3. Observe the redirect to `/projects/<ref>/issues` before the
plugin-slots query has ever run.
4. With React in development mode, open any plugin tab that renders
sibling elements and observe key warnings in the console.
**Version or commit**
Reproduced on `master` at `733ffbf7c`.
**Deployment mode**
Local development instance (managed runtime).
## What Changed
- `ui/src/pages/ProjectDetail.tsx`: the plugin-tab fallback now waits
until the company is resolved and the plugin-slots query has finished
before it decides. While the decision is pending it renders the detail
`PageSkeleton` instead of navigating away. Loading a project that fails
to load still shows the error state.
- `ui/src/plugins/bridge-init.ts`: the plugin bridge registry now
exposes the host's real `react/jsx-runtime` module as `reactJsxRuntime`.
- `ui/src/plugins/slots.tsx`: the `react/jsx-runtime` shim served to
plugin bundles prefers the bridged runtime's `jsx`/`jsxs`/`Fragment` and
keeps the previous `createElement`-based implementation as a fallback.
- `ui/src/pages/ProjectDetail.test.tsx`: new tests for cold deep links —
pending slots query, disabled query before company resolution,
registered tab render, and fallback when the tab is not contributed.
- `ui/src/plugins/bridge.test.ts`: new tests that the bridge exposes the
real runtime and that unkeyed static children render through it without
key warnings.
## Verification
- `npx vitest run src/pages/ProjectDetail.test.tsx
src/plugins/bridge.test.ts` in `ui/` — 2 files, 18 tests, all pass.
- `pnpm check:token-gates` — all gates clean.
- Manual: open a project with a plugin detail tab, switch to the tab,
reload the browser, use back/forward, and open the URL in a fresh tab.
The tab persists. Remove the plugin and open the same URL. The page
falls back to the Issues tab.
## Risks
- Low risk. The fallback redirect still happens whenever contributions
finish loading without the requested tab; the change only defers it
until the answer is known, so an unavailable contribution cannot get
stuck on the skeleton.
- The jsx-runtime shim keeps the old `createElement` path as a fallback
when the bridged runtime is absent, so older bridge registries keep
working.
## Model Used
- Claude (Anthropic) — `claude-fable-5`, extended thinking enabled,
agentic tool use (file edits, shell, test execution) via Claude Code.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or similar PRs and linked
related PRs above where they exist
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
— *the branch predates this rule; squash-merge keeps the branch name off
`master`*
- [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
(code-level docs; no user-facing docs affected)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green — *pending first CI run on this
PR*
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
*pending first review*
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The release workflow publishes every public package to npm per
master push (canary) and per promotion channel
> - `release.sh` polls the registry after each publish and aborts the
whole release when a version is not visible within 60 seconds
> - npm accepts publishes immediately, but its CDN can lag packument
propagation by several minutes; on 2026-08-21 this failed four
consecutive canary runs mid-loop even though every publish succeeded
> - This pull request sets the script's existing visibility-budget env
overrides at the workflow level to 10 minutes
> - The benefit is a release train that tolerates registry propagation
lag; a healthy publish still exits the poll on its first visible check
## Linked Issues or Issue Description
Not applicable for a `ci:` workflow tuning change. Evidence: four
consecutive `Release` runs on master failed in `publish_canary` with
"npm did not publish and expose <package>@<version>", while the raw
registry packument shows each of those versions present minutes later
(`2026.821.0-canary.0` accepted 01:36 UTC, visible 01:40;
`2026.821.0-canary.1` accepted 05:54, visible 05:57).
## What Changed
- Set `NPM_PUBLISH_VERIFY_ATTEMPTS: "30"` and
`NPM_PUBLISH_VERIFY_DELAY_SECONDS: "10"` in the `Release` workflow's
top-level `env`, raising `release.sh`'s post-publish visibility poll
from 60 seconds to 5 minutes per package for every channel. Both
variables are existing overrides read by the script
(`scripts/release.sh` lines 311-312); no script change.
- Raised the four publish jobs' `timeout-minutes` from 45 to 90 so
several laggard packages fit inside the job without exhausting it before
the tag push / Docker / release steps.
## Verification
- `release.sh` reads the two env overrides with defaults
(`${NPM_PUBLISH_VERIFY_ATTEMPTS:-12}` /
`${NPM_PUBLISH_VERIFY_DELAY_SECONDS:-5}`), so workflow-level env reaches
`publish_package_to_npm_and_wait` unchanged.
- Not run: a live release (needs the npm-canary environment). The next
master push exercises the canary path with the new budget.
## Risks
- Low risk: a genuinely failed publish now takes up to 5 minutes to
report instead of 1, and a pathological batch where most packages lag
the full budget still fails inside the 90-minute job — that pattern
means a real registry incident. The poll exits early on success, so
healthy releases are unaffected.
## Model Used
Claude Fable 5 (Anthropic, `claude-fable-5`) with extended thinking and
agentic tool use via the Claude Code CLI (release log forensics against
raw registry packument timestamps).
## 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 (not applicable:
workflow env tuning)
- [x] I have updated relevant documentation to reflect my changes
(comment in the workflow documents the budget rationale)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip's `commitperclip-review` workflow runs
`check-pr-security.mjs` on every PR and files a **draft security
advisory** whenever one of its heuristics fires (#6469).
> - The heuristics fire on most ordinary PRs: any change under
`server/src/routes/agents.ts` / `companies.ts` / `approvals.ts` /
`authz.ts` / `MarkdownBody.tsx`, any test file containing `fetch(` /
`exec(` / `process.env.X`, any `key: "<20+ chars>"` string (it flagged
`pluginKey: "paperclipai.plugin-llm-wiki"`), any touch of
`.github/workflows/`.
> - The repository now holds **1,566 commitperclip-authored draft
advisories** against ~99 human-reported ones, burying the reports that
matter under the 🔒 Security tab.
> - Nothing consumes them: no code reads the drafts, nothing reads the
`security-review` check run the script also posts, and `master` has no
required status checks. The "Review and dismiss if not a real concern"
footer assumed a human triage loop that never existed.
> - A second bug made it worse: the advisories endpoint is
cursor-paginated and ignores `page=`, so `findExistingDraftAdvisory`
only ever saw the newest 100 drafts and re-flagged PRs got a second
draft (1,566 drafts for 1,386 distinct PRs; 136 PRs have 2+).
> - Removing the gate stops the flood at the source; the quality gates
and Dependency Review carry on unchanged.
## Linked Issues or Issue Description
**Problem:** `check-pr-security.mjs` files a draft security advisory for
nearly every PR, flooding the repository's advisory list with
bot-authored noise that no one reads. Human-reported advisories in
`triage` state are buried among ~1,560 `🚨 Security flag — PR #NNNN`
drafts.
**Expected:** the advisory list contains only real vulnerability
reports. Heuristic PR checks, if wanted at all, do not create disclosure
records.
## What Changed
- Deleted `.github/scripts/check-pr-security.mjs` and
`.github/scripts/tests/check-pr-security.test.mjs`.
- Removed the `Run security gates` step from
`.github/workflows/commitperclip-review.yml`, and the `security-events:
write` permission that only it used.
- No other script imports from the removed module (`resolveBaseRef`
lives in `check-pr-dependencies.mjs` and stays).
## Verification
- `node --test .github/scripts/tests/*.test.mjs` → 114 pass, 0 fail.
- `grep -rn check-pr-security .github` → no remaining references.
- The 1,563 existing bot drafts are being closed out-of-band via the API
(there is no delete endpoint for advisories).
## Risks
- Low. The only behaviour removed is the draft-advisory filing and the
informational `security-review` check run, neither of which is consumed
by code or branch protection.
- Recommended follow-up for an org admin: drop `security_advisories:
write` from the commitperclip App's permissions so no workflow can
recreate this.
## Model Used
Claude Fable 5 (claude-fable-5) via Claude Code, with tool use: GitHub
API reads, file edits, local test runs.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass
- [x] I have considered and documented any risks above
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016HitAcRu3NW5YDeBxXxePi
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The instance settings surface (Access, Plugins, Adapters, General,
Experimental) assumes the person at the keyboard operates the whole
instance
> - Operators who host Paperclip for others — a managed cloud or an
internal shared server — expose settings pages and toggles that do not
apply to their deployment, and the related mutation APIs stay open
> - A hosted tenant can open Plugins or Adapters, try an action, and hit
a confusing failure, because only a few hardcoded platform floors exist
> - This pull request adds a generic, operator-configured visibility
mechanism: one env var hides declared settings surfaces in the UI and
floors their mutation routes with a stable 403 code
> - The benefit is a clean hosted-tenant settings surface for any
operator, with zero behavior change for normal self-hosted instances
## Linked Issues or Issue Description
**Subsystem affected**
Instance settings (server routes and UI), the shared settings registry
in `packages/shared`, and the `/api/health` bootstrap payload.
**Problem or motivation**
An operator who hosts Paperclip for other people cannot hide settings
surfaces that the platform manages. Tenants see Access, Plugins, and
Adapters pages, backup retention, and host-level experimental toggles
that do nothing useful for them. The mutation APIs behind these surfaces
also stay open, so a tenant admin can attempt actions the platform must
control. ROADMAP.md names a cleaner shared deployment story as a goal
("Teams should be able to run the same product in hosted or semi-hosted
environments without changing the mental model").
**Proposed solution**
Add a declarative registry of hideable settings surfaces and one env
var, `PAPERCLIP_HIDDEN_SETTINGS`. The server parses the list at boot,
reports it on `/api/health`, and rejects value-changing writes to hidden
surfaces with a stable `settings_operator_managed` 403 code. The UI
reads the list from the health payload and removes the hidden pages,
sections, and toggles from navigation, routes, and page content. Unknown
keys warn and are ignored, so one list can roll across a fleet with
mixed app versions. With the variable unset, behavior is byte-identical
to today.
**Alternatives considered**
- Hardcode the hidden set for cloud instances in this repo: rejected,
because each hosting operator needs a different policy, and policy does
not belong in shared code.
- Deliver the hidden set through the managed-config document: rejected,
because that channel is cloud-specific and fail-closed on unknown
fields; a plain env var works for any operator, including self-hosted
shared servers.
- Lock the controls with a badge instead of hiding them: rejected for
these surfaces, because they are meaningless to tenants, not merely
platform-controlled; the existing managed-overlay lock stays the right
tool for controlled flags.
**Roadmap alignment**
Supports the "shared deployment story" item in ROADMAP.md: hosted and
semi-hosted deployments keep the same product with a settings surface
that matches what the tenant can actually do.
## What Changed
- New `packages/shared/src/settings-visibility.ts`: registry of hideable
surfaces (every instance settings page — profile, environments, access,
heartbeats, experimental, plugins, adapters; every Instance → General
section; every experimental flag as `instance.experimental.<key>`), the
`PAPERCLIP_HIDDEN_SETTINGS` parser, and the `settings_operator_managed`
error code. The General page stays visible as the settings root and
redirect target.
- New `server/src/services/settings-visibility.ts`: parse-once accessor;
unknown keys log one warning and are ignored.
- `/api/health` reports `hiddenSettings` on every response shape; the
field is omitted when nothing is hidden.
- Server floors on hidden surfaces, with same-value echo tolerance (the
`executionMode` precedent): field-backed general sections and
experimental keys reject value-changing PATCHes, and hiding the whole
Experimental page floors every toggle; plugin lifecycle and config
writes, adapter management writes, and the Access admin routes (reads
included) return 403 `settings_operator_managed`. Reads the app itself
needs (plugin `ui-contributions`, adapter metadata, plugin job trigger)
stay open. Pages without instance-scoped mutation routes are hidden in
the UI only.
- UI: new `useHiddenSettings` hook and `HiddenSettingsPageGate` route
gate (hidden pages redirect to the settings root); the settings sidebar
and tab bar drop hidden entries; remembered settings paths remap to the
default page; `InstanceGeneralSettings` skips hidden sections; every
`ExperimentalToggleCard` now carries its flag key and renders nothing
when hidden.
- Removed the dead `InstanceSidebar` component (referenced only by its
own test).
- Docs: `docs/deploy/environment-variables.md` documents the variable
and the key registry.
## Verification
- `pnpm vitest run` over the new and extended suites: shared registry
and parser, representative floor tests per route class (changed-value
403, same-value echo 200, unset env 200, page-level Experimental
hiding), the health field, the route gate, nav filtering, and
section/card hiding with one hidden example per surface kind — 168 tests
pass.
- Full root `pnpm typecheck` passes.
- Manual: booted a server with the variable set. `/api/health` lists the
keys; an unknown key logs one warning and the server boots; hidden pages
redirect; hidden sections and cards do not render; hidden-field PATCH
returns 403 with `details.code = "settings_operator_managed"`; a
same-value echo returns 200. Unset the variable: the full settings
surface returns and responses are byte-identical to master.
## Risks
- Low risk for self-hosted instances: with the variable unset, the
hidden set is empty, the health field is omitted, and no floor
activates.
- Flooring plugin config writes assumes hosted deployments configure
plugins through the platform. If a future bundled plugin needs
tenant-entered config, the floor needs a narrow carve-out.
- Hidden-key floors tolerate same-value echoes, so API clients that
round-trip full GET responses keep working.
- Hiding a toggle does not change its value; operators pair hiding with
the desired default where the value matters.
## Model Used
Claude Fable 5 (Anthropic, `claude-fable-5`) with extended thinking and
agentic tool use, driven through the Claude Code CLI (file edits, test
execution, and live-server verification loops).
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Bumps
[@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)
from 4.7.0 to 6.0.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/releases">@vitejs/plugin-react's
releases</a>.</em></p>
<blockquote>
<h2>plugin-react@6.0.5</h2>
<h3>Fixed the react compiler preset filter to be linear (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1353">#1353</a>)</h3>
<p>The improved filter in v6.0.3 was non-linear and caused a performance
regression (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/issues/1349">#1349</a>).
The filter was changed to be linear to avoid that.</p>
<h2>plugin-react@6.0.4</h2>
<h3>Fixed <code>$RefreshSig$ is not defined</code> error when running
<code>vite dev</code> with <code>NODE_ENV=production</code></h3>
<p>When running <code>vite dev</code> with
<code>NODE_ENV=production</code>, the app errored with
<code>$RefreshSig$ is not defined</code>.
This error is now fixed.</p>
<h2>plugin-react@6.0.3</h2>
<p>No release notes provided.</p>
<h2>plugin-react@6.0.2</h2>
<h3>Allow all options in reactCompilerPreset (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1189">#1189</a>)</h3>
<p>This is a type only change. Only <code>compilationMode</code> and
<code>target</code> options were available for
<code>reactCompilerPreset</code>.</p>
<h2>plugin-react@6.0.1</h2>
<h3>Expand <code>@rolldown/plugin-babel</code> peer dep range (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1146">#1146</a>)</h3>
<p>Expanded <code>@rolldown/plugin-babel</code> peer dep range to
include <code>^0.2.0</code>.</p>
<h2>plugin-react@6.0.0</h2>
<h3>Remove Babel Related Features (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1123">#1123</a>)</h3>
<p>Vite 8+ can handle React Refresh Transform by Oxc and doesn't need
Babel for it. With that, there are no transform applied that requires
Babel. To reduce the installation size of this plugin, babel is no
longer a dependency of this plugin and the related features are
removed.</p>
<p>If you are using Babel, you can use
<code>@rolldown/plugin-babel</code> together with this plugin:</p>
<pre lang="diff"><code> import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import babel from '@rolldown/plugin-babel'
<p>export default defineConfig({
plugins: [</p>
<ul>
<li>
<pre><code>react({
</code></pre>
</li>
<li>
<pre><code> babel: {
</code></pre>
</li>
<li>
<pre><code> plugins: ['@babel/plugin-proposal-throw-expressions'],
</code></pre>
</li>
<li>
<pre><code> },
</code></pre>
</li>
<li>
<pre><code>}),
</code></pre>
</li>
</ul>
<ul>
<li>
<pre><code>react(),
</code></pre>
</li>
<li>
<pre><code>babel({
</code></pre>
</li>
<li>
<pre><code> plugins: ['@babel/plugin-proposal-throw-expressions'],
</code></pre>
</li>
<li>
<pre><code>}),
</code></pre>
]
})
</code></pre></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md">@vitejs/plugin-react's
changelog</a>.</em></p>
<blockquote>
<h2>6.0.5 (2026-07-30)</h2>
<h3>Fixed the react compiler preset filter to be linear (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1353">#1353</a>)</h3>
<p>The improved filter in v6.0.3 was non-linear and caused a performance
regression (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/issues/1349">#1349</a>).
The filter was changed to be linear to avoid that.</p>
<h2>6.0.4 (2026-07-22)</h2>
<h3>Fixed <code>$RefreshSig$ is not defined</code> error when running
<code>vite dev</code> with <code>NODE_ENV=production</code></h3>
<p>When running <code>vite dev</code> with
<code>NODE_ENV=production</code>, the app errored with
<code>$RefreshSig$ is not defined</code>.
This error is now fixed.</p>
<h2>6.0.3 (2026-06-23)</h2>
<h3>Improve the react compiler preset filter to reduce false-positives
(<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1138">#1138</a>)</h3>
<p>Improved the filter in the react compiler babel preset to reduce the
false-positives so that less modules are processed by the react
compiler.</p>
<h2>6.0.2 (2026-05-14)</h2>
<h3>Allow all options in reactCompilerPreset (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1189">#1189</a>)</h3>
<p>This is a type only change. Only <code>compilationMode</code> and
<code>target</code> options were available for
<code>reactCompilerPreset</code>.</p>
<h2>6.0.1 (2026-03-13)</h2>
<h3>Expand <code>@rolldown/plugin-babel</code> peer dep range (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1146">#1146</a>)</h3>
<p>Expanded <code>@rolldown/plugin-babel</code> peer dep range to
include <code>^0.2.0</code>.</p>
<h2>6.0.0 (2026-03-12)</h2>
<h2>6.0.0-beta.0 (2026-03-03)</h2>
<h3>Remove Babel Related Features (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1123">#1123</a>)</h3>
<p>Vite 8+ can handle React Refresh Transform by Oxc and doesn't need
Babel for it. With that, there are no transform applied that requires
Babel. To reduce the installation size of this plugin, babel is no
longer a dependency of this plugin and the related features are
removed.</p>
<p>If you are using Babel, you can use
<code>@rolldown/plugin-babel</code> together with this plugin:</p>
<pre lang="diff"><code> import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import babel from '@rolldown/plugin-babel'
<p>export default defineConfig({
plugins: [</p>
<ul>
<li>
<pre><code>react({
</code></pre>
</li>
<li>
<pre><code> babel: {
</code></pre>
</li>
</ul>
<p></tr></table>
</code></pre></p>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="68c0cb8796"><code>68c0cb8</code></a>
release: plugin-react@6.0.5 (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1362">#1362</a>)</li>
<li><a
href="555cdbc126"><code>555cdbc</code></a>
fix(react): make the react compiler preset filter linear (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1353">#1353</a>)</li>
<li><a
href="a00a9f8240"><code>a00a9f8</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1327">#1327</a>)</li>
<li><a
href="f4b549822e"><code>f4b5498</code></a>
release: plugin-react@6.0.4</li>
<li><a
href="7a4065906e"><code>7a40659</code></a>
fix(react): <code>$RefreshSig$ is not defined</code> with
NODE_ENV=production vite dev ...</li>
<li><a
href="98b32d47a3"><code>98b32d4</code></a>
fix(deps): update react 19.2.8 (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1298">#1298</a>)</li>
<li><a
href="8ae5449be2"><code>8ae5449</code></a>
fix: babel-plugin-react-compiler cannot be imported when used in a
framework ...</li>
<li><a
href="f09ea01b0b"><code>f09ea01</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1282">#1282</a>)</li>
<li><a
href="640fd358a0"><code>640fd35</code></a>
release: plugin-react@6.0.3</li>
<li><a
href="889efb02cd"><code>889efb0</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1249">#1249</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.5/packages/plugin-react">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 <code>@vitejs/plugin-react</code> since your current
version.</p>
</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>
Bumps
[@clack/prompts](https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts)
from 0.11.0 to 1.7.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/bombshell-dev/clack/releases">@clack/prompts's
releases</a>.</em></p>
<blockquote>
<h2><code>@clack/prompts</code><a
href="https://github.com/1"><code>@1</code></a>.7.0</h2>
<h3>Minor Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/bombshell-dev/clack/pull/574">#574</a>
<a
href="8f1c380683"><code>8f1c380</code></a>
Thanks <a
href="https://github.com/dreyfus92"><code>@dreyfus92</code></a>! - Add
<code>showInstructions</code> option to <code>select</code>,
<code>multiselect</code>, and <code>groupMultiselect</code>. Keyboard
hints remain shown by default; pass <code>showInstructions: false</code>
to hide them.</li>
</ul>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/bombshell-dev/clack/pull/582">#582</a>
<a
href="4b249539e2"><code>4b24953</code></a>
Thanks <a href="https://github.com/43081j"><code>@43081j</code></a>! -
Handle empty arrays in various prompts and utilities.</p>
</li>
<li>
<p>Updated dependencies [<a
href="4b249539e2"><code>4b24953</code></a>]:</p>
<ul>
<li><code>@clack/core</code><a
href="https://github.com/1"><code>@1</code></a>.4.3</li>
</ul>
</li>
</ul>
<h2><code>@clack/prompts</code><a
href="https://github.com/1"><code>@1</code></a>.6.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/bombshell-dev/clack/pull/568">#568</a>
<a
href="f87933fb7b"><code>f87933f</code></a>
Thanks <a
href="https://github.com/florian-lefebvre"><code>@florian-lefebvre</code></a>!
- Updates default formatter of <code>note()</code> to note dim lines
anymore</p>
<p>If you want the old behavior, provide a <code>format()</code>
function:</p>
<pre lang="diff"><code>import { note } from '@clack/prompts';
+import { styleText } from 'node:util';
<p>note(
'You can edit the file src/index.jsx',
'Next steps.'</p>
<ul>
<li>{ format: (text) => styleText('dim', text) }
);
</code></pre></li>
</ul>
</li>
<li>
<p><a
href="https://redirect.github.com/bombshell-dev/clack/pull/567">#567</a>
<a
href="cc6aab5018"><code>cc6aab5</code></a>
Thanks <a
href="https://github.com/dreyfus92"><code>@dreyfus92</code></a>! - Add
keyboard instruction footers to <code>select</code>,
<code>multiselect</code>, and <code>groupMultiselect</code> in the
active state, matching autocomplete. No option — always shown.</p>
</li>
</ul>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="2f2b52f77c"><code>2f2b52f</code></a>,
<a
href="e1b6ee71a7"><code>e1b6ee7</code></a>]:
<ul>
<li><code>@clack/core</code><a
href="https://github.com/1"><code>@1</code></a>.4.2</li>
</ul>
</li>
</ul>
<h2><code>@clack/prompts</code><a
href="https://github.com/1"><code>@1</code></a>.5.1</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/bombshell-dev/clack/pull/548">#548</a>
<a
href="2356e97c1f"><code>2356e97</code></a>
Thanks <a href="https://github.com/43081j"><code>@43081j</code></a>! -
Remove sourcemaps and enable pretty-ish build output.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/bombshell-dev/clack/pull/546">#546</a>
<a
href="56e9d67077"><code>56e9d67</code></a>
Thanks <a
href="https://github.com/ghostdevv"><code>@ghostdevv</code></a>! -
docs: add jsdoc for <code>date</code>, <code>limit-options</code>, and
<code>messages</code></p>
</li>
<li>
<p>Updated dependencies [<a
href="2356e97c1f"><code>2356e97</code></a>]:</p>
<ul>
<li><code>@clack/core</code><a
href="https://github.com/1"><code>@1</code></a>.4.1</li>
</ul>
</li>
</ul>
<h2><code>@clack/prompts</code><a
href="https://github.com/1"><code>@1</code></a>.5.0</h2>
<h3>Minor Changes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bombshell-dev/clack/blob/main/packages/prompts/CHANGELOG.md">@clack/prompts's
changelog</a>.</em></p>
<blockquote>
<h2>1.7.0</h2>
<h3>Minor Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/bombshell-dev/clack/pull/574">#574</a>
<a
href="8f1c380683"><code>8f1c380</code></a>
Thanks <a
href="https://github.com/dreyfus92"><code>@dreyfus92</code></a>! - Add
<code>showInstructions</code> option to <code>select</code>,
<code>multiselect</code>, and <code>groupMultiselect</code>. Keyboard
hints remain shown by default; pass <code>showInstructions: false</code>
to hide them.</li>
</ul>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/bombshell-dev/clack/pull/582">#582</a>
<a
href="4b249539e2"><code>4b24953</code></a>
Thanks <a href="https://github.com/43081j"><code>@43081j</code></a>! -
Handle empty arrays in various prompts and utilities.</p>
</li>
<li>
<p>Updated dependencies [<a
href="4b249539e2"><code>4b24953</code></a>]:</p>
<ul>
<li><code>@clack/core</code><a
href="https://github.com/1"><code>@1</code></a>.4.3</li>
</ul>
</li>
</ul>
<h2>1.6.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/bombshell-dev/clack/pull/568">#568</a>
<a
href="f87933fb7b"><code>f87933f</code></a>
Thanks <a
href="https://github.com/florian-lefebvre"><code>@florian-lefebvre</code></a>!
- Updates default formatter of <code>note()</code> to note dim lines
anymore</p>
<p>If you want the old behavior, provide a <code>format()</code>
function:</p>
<pre lang="diff"><code>import { note } from '@clack/prompts';
+import { styleText } from 'node:util';
<p>note(
'You can edit the file src/index.jsx',
'Next steps.'</p>
<ul>
<li>{ format: (text) => styleText('dim', text) }
);
</code></pre></li>
</ul>
</li>
<li>
<p><a
href="https://redirect.github.com/bombshell-dev/clack/pull/567">#567</a>
<a
href="cc6aab5018"><code>cc6aab5</code></a>
Thanks <a
href="https://github.com/dreyfus92"><code>@dreyfus92</code></a>! - Add
keyboard instruction footers to <code>select</code>,
<code>multiselect</code>, and <code>groupMultiselect</code> in the
active state, matching autocomplete. No option — always shown.</p>
</li>
</ul>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="2f2b52f77c"><code>2f2b52f</code></a>,
<a
href="e1b6ee71a7"><code>e1b6ee7</code></a>]:
<ul>
<li><code>@clack/core</code><a
href="https://github.com/1"><code>@1</code></a>.4.2</li>
</ul>
</li>
</ul>
<h2>1.5.1</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/bombshell-dev/clack/pull/548">#548</a>
<a
href="2356e97c1f"><code>2356e97</code></a>
Thanks <a href="https://github.com/43081j"><code>@43081j</code></a>! -
Remove sourcemaps and enable pretty-ish build output.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/bombshell-dev/clack/pull/546">#546</a>
<a
href="56e9d67077"><code>56e9d67</code></a>
Thanks <a
href="https://github.com/ghostdevv"><code>@ghostdevv</code></a>! -
docs: add jsdoc for <code>date</code>, <code>limit-options</code>, and
<code>messages</code></p>
</li>
<li>
<p>Updated dependencies [<a
href="2356e97c1f"><code>2356e97</code></a>]:</p>
<ul>
<li><code>@clack/core</code><a
href="https://github.com/1"><code>@1</code></a>.4.1</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="dc5bce8aae"><code>dc5bce8</code></a>
[ci] release (<a
href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/575">#575</a>)</li>
<li><a
href="4b249539e2"><code>4b24953</code></a>
chore: enable strict index checks (<a
href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/582">#582</a>)</li>
<li><a
href="8f1c380683"><code>8f1c380</code></a>
feat(prompts): add showInstructions opt-out (<a
href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/574">#574</a>)</li>
<li><a
href="06c16c7b65"><code>06c16c7</code></a>
chore: update license copyright to Bombshell contributors (<a
href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/572">#572</a>)</li>
<li><a
href="0e700562df"><code>0e70056</code></a>
[ci] release (<a
href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/562">#562</a>)</li>
<li><a
href="f87933fb7b"><code>f87933f</code></a>
fix(prompts): do not dim note contents (<a
href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/568">#568</a>)</li>
<li><a
href="cc6aab5018"><code>cc6aab5</code></a>
feat(prompts): add instructions footer for <code>select</code>,
<code>multi-select</code> & `group-...</li>
<li><a
href="02ae19193e"><code>02ae191</code></a>
[ci] release (<a
href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/549">#549</a>)</li>
<li><a
href="56e9d67077"><code>56e9d67</code></a>
docs: add jsdoc for <code>date</code>, <code>limit-options</code>, and
<code>messages</code> (<a
href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/546">#546</a>)</li>
<li><a
href="030ba4d776"><code>030ba4d</code></a>
[ci] release (<a
href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/539">#539</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/bombshell-dev/clack/commits/@clack/prompts@1.7.0/packages/prompts">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 <code>@clack/prompts</code> since your current
version.</p>
</details>
<br />
[](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>
Co-authored-by: Priya Raman <priya@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The heartbeat system records agent runs and can add a run summary to
an issue.
> - The ACPX engine receives output text and internal thought text as
separate streams.
> - The default summary strategy joined both streams and could publish
internal text in an issue comment.
> - Paperclip already has final-output segmentation for run summaries.
> - This pull request makes final-output-only summaries mandatory and
removes the configuration bypass.
> - The benefit is that automatic issue comments contain the intended
final message instead of internal execution text.
## Linked Issues or Issue Description
Refs #11761
**What happened?**
The ACPX engine used the full summary strategy when an adapter did not
set `summaryStrategy`. That strategy joined all text deltas, including
thought-stream text and intermediate narration. The heartbeat finalizer
could then store that summary as an issue comment.
**Expected behavior**
An automatic issue comment must use only the final output segment.
Configuration must not allow thought-stream text or intermediate
narration into that summary.
**Steps to reproduce**
1. Run an ACPX adapter without a configured `summaryStrategy`.
2. Emit an output delta, a thought delta, a tool call, and a final
output delta.
3. Read the generated run summary.
4. Observe that the old default included all text deltas.
**Paperclip version or commit**
`54b8bec44417511c623999613f9f1006f8af0517`
**Deployment mode**
Built from source with a local ACPX adapter.
## What Changed
- Limit ACPX run summaries to the final non-empty output segment.
- Ignore the legacy full-summary setting so configuration cannot bypass
containment.
- Update regression tests for the safe default and an attempted unsafe
override.
## Verification
- Observed the new guard fail before the implementation change because
the summary contained thought text.
- Ran `pnpm exec vitest run
packages/adapter-utils/src/acpx-engine/execute.test.ts -t "defaults run
summaries to the final output segment without thought text|does not
allow configuration to include thought text in run summaries"`. Result:
2 passed.
- Ran `pnpm exec vitest run
packages/adapter-utils/src/acpx-engine/execute.test.ts`. Result: 130
passed.
- Ran `pnpm --filter @paperclipai/adapter-utils typecheck`. Result:
passed.
## Risks
- Run summaries are shorter for adapters that relied on full text
aggregation.
- The old `summaryStrategy: "full"` setting no longer changes summary
behavior. This is an intentional containment change.
- The change does not alter run logs or tool events. It changes only the
summary selected for downstream use.
> This is a focused security and privacy bug fix. It does not add
roadmap scope.
## Model Used
- OpenAI Codex on the GPT-5 family. The runtime did not expose the exact
model ID or context-window size. Reasoning, tool use, terminal
execution, and code editing were enabled.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change and contains no internal task
id
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant inline documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The control plane must keep each active issue on a clear execution
or recovery path.
> - A missing issue disposition can require more than one bounded repair
attempt.
> - A server restart could lose that repair path or move source
ownership to the recovery owner.
> - A parked or expired retry could also make the user interface show a
false healthy state.
> - Concurrent recovery loops must not schedule the same repair attempt
twice.
> - This pull request keeps retry state durable, makes scheduling
atomic, and keeps source ownership stable.
> - The benefit is that recovery continues after a restart and operators
see the correct state.
## Linked Issues or Issue Description
**What happened?**
A run that ended without a valid issue disposition could lose its repair
path after a server restart. Manager recovery could also change the
source owner. In addition, a parked or expired retry could make the
issue look healthy when no active work existed. Concurrent
reconciliation could also schedule the same repair attempt twice.
**Expected behavior**
Paperclip must keep bounded source and manager repair attempts across
restarts. Recovery ownership must stay separate from source issue
ownership. The server and user interface must report only a live retry
as active work. Each repair attempt must be scheduled at most once per
company.
**Steps to reproduce**
1. Start an agent run on an issue.
2. End the run without a valid issue disposition.
3. Let the first repair attempt schedule a retry.
4. Restart the server, let the retry time pass without a live run, or
start two reconciliation loops together.
5. Observe that the repair path can stop, the issue can show a false
healthy state, or duplicate retries can be created.
**Paperclip version or commit**
The problem existed on `master` before candidate head
`d8e620fe86bade7df18decac332007f5821ae04f`.
**Deployment mode**
The problem affects self-hosted servers and local builds that use
automatic recovery.
## What Changed
- Persist bounded source-owner and manager repair lineages with stable
fingerprints and retry limits.
- Resume incomplete disposition repairs after a server restart.
- Keep recovery ownership separate from source issue ownership and
enforce source mutation authority.
- Project live retry evidence into issue and blocker summaries.
- Show recovery owner, return owner, attempt count, and retry state in
the board user interface.
- Treat expired or parked retries as attention states unless a queued or
running attempt exists.
- Atomically deduplicate disposition-repair wake requests with a
company-scoped partial unique index.
- Reuse the winning run when concurrent reconciliation loses the
uniqueness race, without duplicate scheduling activity.
- Honor disabled on-demand wake policy before recovery scheduling and
again before delayed retry promotion.
- Keep the new index migration safe for lagging seeded databases that
already contain the index.
- Add server and user interface tests for recovery, restart, ownership,
retry, concurrency, and blocker states.
- Update the implementation and execution semantics documents.
## Verification
- Focused server recovery and ownership suites: 282 tests passed on the
repaired base candidate.
- Focused user interface recovery suites: 128 tests passed on the
repaired base candidate.
- Atomic-deduplication schema and recovery suites: 111 tests passed on
the first Greptile repair.
- Recovery and scheduled-retry wake-policy suites: 126 tests passed at
`d8e620fe86bade7df18decac332007f5821ae04f`.
- The exact lagging-source migration-order test passed after the index
migration became idempotent: 1 test passed and 62 unrelated tests were
skipped.
- `@paperclipai/db` and `@paperclipai/server` typechecks passed at the
current head.
- Migration generation and migration safety checks passed for migration
`0226_tan_colossus.sql`.
- `pnpm check:token-gates` passed on the repaired base candidate.
- `pnpm -r typecheck` passed on the repaired base candidate.
- `pnpm build` passed on the repaired base candidate.
- `pnpm test:run` passed 4,540 tests on the repaired base candidate.
Four fixed-port cases met listeners that already existed on the host.
- The two unchanged fixed-port files passed in an isolated network
namespace: 129 tests passed and 27 tests were skipped.
- Independent Security and QA reviews approved
`63c0423aab54c66f2293a20b0fb3f3b013ee3ba8`; exact-head re-review is
required after automated checks settle on
`d8e620fe86bade7df18decac332007f5821ae04f`.
## Risks
- Recovery orchestration affects issue liveness and ownership. The new
paths use bounded attempts, stable fingerprints, row locks, authority
checks, and database uniqueness.
- A conservative attention state can show more warnings when a scheduled
retry has no queued or running attempt. It does not hide stopped work.
- Migration `0226_tan_colossus.sql` creates a partial unique index on a
known-large table. Migrations run transactionally, so `CONCURRENTLY` is
unavailable. The matching disposition-repair key namespace is introduced
by this release, so deployed databases have no matching rows before the
index is added.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex from the GPT-5 model family used agentic reasoning, tool
use, and code execution. The runtime did not expose the exact model ID
or context window.
- Anthropic Claude Opus 5 used a 1M context window, tool use, and code
execution for part of the user interface repair, as recorded in the
commit history.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [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: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Runs on sandbox execution targets spend their first minutes in
preparation phases — config seed, workspace and skills sync into the
sandbox — before the agent CLI produces its first transcript token
> - The engine already reports these phases through the runtime-progress
mechanism (`onRuntimeProgress` →
`recordCurrentHeartbeatRunRuntimeProgress` → `currentStatusMessage` on
the live run), and the pre-task-chat issue view surfaced them in its
live status line
> - The chat-style task view's live tail dropped that affordance: with
zero renderable transcript entries it shows an opaque "Waiting for
transcript..." for minutes, which reads as a hang (and prompted a real
is-this-broken investigation on a healthy run)
> - This pull request surfaces the live run's `currentStatusMessage` as
the tail's empty-state message, with the generic wait text as fallback
> - The benefit is that operators watching a sandbox run see "Syncing
workspace to sandbox" instead of wondering whether the run is stuck —
for every adapter, with no adapter identities involved
## Linked Issues or Issue Description
**What existing behavior does this improve?**
The chat-style task view's live transcript tail (`TaskChatThread` →
`TaskChatLiveTail`), introduced with the experimental chat-style task
view.
**Current behavior**
While a live run has no renderable transcript entries yet — the normal
state for the multi-minute sandbox preparation window — the tail shows a
static "Waiting for transcript...". The run's live
`currentStatusMessage` (e.g. "Syncing workspace to sandbox", emitted by
the sandbox-managed runtime's progress reporting) is available on the
same live-run object but unused by this surface, although the earlier
issue-chat view did display it.
**Proposed behavior**
When the tail is streaming a live run and no transcript rows exist yet,
the empty-state message prefers the run's `currentStatusMessage`; the
generic wait text remains the fallback when no runtime status has been
reported (e.g. local runs that produce output immediately, or the brief
pre-status window).
**Reason and benefit**
The preparation phases are real, reportable progress that the engine
already emits. Showing them turns a minutes-long apparent hang into a
legible status, for every adapter and execution target, using data the
view already receives.
## What Changed
- `ui/src/components/TaskChatThread.tsx`: the live tail's `emptyMessage`
prefers `liveRun.currentStatusMessage` (guarded to the run the tail is
actually streaming) over the static "Waiting for transcript..."
fallback. Queued runs keep "Waiting to start...".
- `ui/src/components/TaskChatThread.test.tsx`: a test covering both
branches — a live run with a runtime status shows it (and not the wait
text), and a run without one keeps the generic message.
## Verification
- `vitest run ui/src/components/TaskChatThread.test.tsx
ui/src/components/task-chat/TaskChatLiveTail.test.tsx`: 22/22 pass
(including the new test)
- `pnpm --filter @paperclipai/ui typecheck`: clean
- Reproduced live: a `claude_local` run on a Daytona sandbox environment
showed "Waiting for transcript..." for the full sync window; with this
change the same window shows the streamed preparation statuses
## Risks
- Low: a one-expression change to an empty-state string, active only
while a live run has produced no renderable transcript rows. The
fallback path is byte-identical to today.
- `currentStatusMessage` is truncated/humanized upstream by the
runtime-progress reporter; this surface renders it verbatim in the same
muted style as the wait text.
## Model Used
- Anthropic, **Claude Fable 5** (`claude-fable-5`) via Claude Code, with
repository, shell, and Git tooling. It traced the runtime-progress
mechanism end-to-end (engine emitter → heartbeat recorder → live-run API
→ both thread views), identified the dropped affordance in the
chat-style view, and wrote the fix and test.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Auto-generated lockfile refresh after dependencies changed on master.
This PR only updates pnpm-lock.yaml.
Co-authored-by: lockfile-bot <lockfile-bot@users.noreply.github.com>
## 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):






## 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>
## 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>
## 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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Local agent adapters stream their work through the shared acpx
engine, which every ACP adapter (`claude_local`, `codex_local`,
`gemini_local`, custom ACP) runs on
> - Verbose streaming backends break two shared-engine behaviors: the
auto-posted run summary concatenates every text delta including the
thought stream (a long multi-tool run once auto-posted a ~50k character
monologue as an issue comment), and token-by-token tool-argument
streaming floods the run log with tens of thousands of
placeholder-titled in-progress events per run
> - These fixes were developed inside the `kimi_local` adapter PR, where
Kimi Code's streaming volume (~16,000 text deltas per run vs ~290 for a
comparable Claude run) surfaced both problems
> - Changing this behavior for all adapters at once is a fleet-wide
risk, and hardcoding adapter identities in shared code does not scale to
many adapters (or work at all for externally-shipped plugin adapters) —
so the behaviors become invocation-config parameters that an adapter's
own acpx config builder sets, with defaults preserving today's behavior
byte-for-byte
> - The benefit is that the machinery lands fully tested with zero
behavior change for existing adapters — pin tests prove it — the engine
carries no adapter identities, and any adapter (including `custom_acp`
configs for external backends) opts in declaratively
## Linked Issues or Issue Description
- Refs #9967 — extracted from the `kimi_local` adapter PR and
restructured to be inert by default; the commit preserves the original
author's (@hawikk) authorship.
**Current behavior**
When an acpx-engine run ends without the agent leaving a comment, the
auto-posted summary is every streamed text delta concatenated, thought
stream included. Backends that stream tool arguments emit tens of
thousands of placeholder-titled `in_progress` tool events into the
stored run log, pinning the live activity indicator to a generic "tool
call". There is no mechanism for an adapter to vary either behavior, and
shared code must never branch on adapter identities.
**Proposed behavior**
Two engine invocation-config parameters, read with behavior-preserving
defaults: `summaryStrategy` (`"full"` = existing concatenation, the
default; `"lastOutputSegment"` = segment output at tool starts, exclude
thought stream, post the last non-empty segment) and
`coalescePlaceholderToolUpdates` (`false` = never drop an event, the
default; `true` = coalesce placeholder-titled in-progress updates). An
adapter opts in from its own acpx config builder — the engine has no
per-adapter knowledge, no adapter identity appears anywhere in shared
code, and `custom_acp` agent configs can set the same knobs for external
verbose backends.
**Reason and benefit**
Existing adapters are provably unaffected — new pin tests assert the
default path's summary and tool-event output byte-for-byte, so any
future change that alters behavior for claude/codex/gemini/custom fails
the suite. The verbose-backend handling still lands fully tested,
activated declaratively by the adapter that needs it (the `kimi_local`
adapter PR sets both knobs in its config builder).
## What Changed
- `packages/adapter-utils/src/acpx-engine/execute.ts`: the run
preparation parses `summaryStrategy` and
`coalescePlaceholderToolUpdates` from the invocation config (validated,
defaulted); summary accumulation and `emitRuntimeEvent` branch on the
prepared values. The default path is the pre-existing code
(`textParts.join("")`, no event filtering). `buildAcpxRunSummary` is the
exported last-segment strategy.
- `packages/adapter-utils/src/acpx-engine/execute.test.ts`: a pin test
asserting the default path's exact summary (thought stream included) and
full tool-event stream (placeholder-titled in-progress updates present,
names restored); opt-in tests for each knob; a `buildAcpxRunSummary`
unit test.
- `ui/src/adapters/types.ts`: `UIAdapterModule` gains an optional
`transcriptPresentation` capability — `maxVisibleEntries` (issue-chat
transcript window, default 30) and `liveReasoningView` (`"ticker"`
default; `"scrollLog"` renders live reasoning in a scrollable
auto-following box with one entry per tool call).
- `ui/src/lib/issue-chat-messages.ts` and
`ui/src/components/IssueChatThread.tsx`: shared code resolves the hints
via `findUIAdapter(adapterType)` with today's defaults as fallback — no
adapter identities anywhere. The `scrollLog` rendering component ships
here but is unreachable until an adapter declares it.
- `ui/src/lib/issue-chat-messages.test.ts`: a capability test registers
a synthetic verbose adapter and asserts the wider window; the
pre-existing test keeps pinning the default 30-entry window.
No adapter declares any of this in this PR — every adapter renders and
summarizes exactly as before, and there is no per-adapter data anywhere.
The `kimi_local` adapter PR (#9967, stacked on this branch) is the first
consumer: it declares `transcriptPresentation` in its own UI module and
sets the engine knobs in its own acpx config builder.
## Verification
- Engine suite: 130/130 pass (126 existing + 4 new); chat suites
(`issue-chat-messages`, `IssueChatThread`, `RunChatSurface`): all pass
including the new synthetic-adapter capability test — 237 tests across
the touched surfaces
- The pin tests are the regression guard: the engine test encodes
today's summary text and tool-event sequence for a default-config run,
and the existing 30-entry-window test pins the default transcript
window, so "nothing changed for Claude/Codex users" is an executable
assertion, not a review judgment
- `pnpm --filter @paperclipai/adapter-utils --filter @paperclipai/ui
typecheck`: clean
## Risks
- Low: with no adapter setting the knobs, every code path taken in
production is the existing one. The only behavioral surface is additive
(an unused strategy and an unused filter), exercised by tests.
- The knobs are ordinary invocation-config keys, so a `custom_acp` agent
config can also set them — intended: an external verbose backend gets
the same handling without code changes. Both knobs only affect that
agent's own run summaries and run-log verbosity.
## Model Used
- Original implementation authored in #9967 by @hawikk (models
documented there: Moonshot AI Kimi K3 Coding via Kimi Code CLI 0.27.0,
OpenAI GPT-5 Codex, Anthropic Claude Opus 4.8). The commit preserves
that authorship.
- Extraction, restructuring into config-declared parameters, pin tests,
and verification: Anthropic, **Claude Fable 5** (`claude-fable-5`) via
Claude Code, with repository, shell, and Git tooling.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Hawik <davapa@gmail.com>
## 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>
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 ||
>=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>"not all"</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="6584485f09"><code>6584485</code></a>
30.0.1</li>
<li><a
href="0c51df6d80"><code>0c51df6</code></a>
Update dependencies and dev dependencies</li>
<li><a
href="32adb340bf"><code>32adb34</code></a>
Bump <code>@asamuzakjp/dom-selector</code></li>
<li><a
href="70f014aa1d"><code>70f014a</code></a>
Speed up range operations on large documents</li>
<li><a
href="250d7ee387"><code>250d7ee</code></a>
Partially fix getComputedStyle with calc()</li>
<li><a
href="20a01fc4a5"><code>20a01fc</code></a>
30.0.0</li>
<li><a
href="8c8e583c4f"><code>8c8e583</code></a>
Precompute WPT expectation matches</li>
<li><a
href="f32245cfed"><code>f32245c</code></a>
Bump Node.js floor and dependencies</li>
<li><a
href="03ef23b451"><code>03ef23b</code></a>
Add background-position longhands</li>
<li><a
href="ded056f38d"><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>
## 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>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task detail view uses a chat-style thread: agent turns, activity
phases, and a message composer.
> - A colleague's UX review found three defects: the composer draft
stayed visible after send, disclosure carets in turns and status pills
were misaligned, and the composer was not horizontally aligned with the
thread column.
> - These defects make the chat surface feel unpolished and cause
confusion about whether a message was sent.
> - This pull request fixes all three defects with small, targeted UI
changes and adds regression tests for each.
> - The benefit is a chat surface that behaves and aligns like users
expect from a messaging UI.
## Linked Issues or Issue Description
No public GitHub issue exists for this. Description of the underlying
problems:
**What happened?**
Three UI defects in the chat-style task view:
1. After a user pressed send, the composer kept the draft text until the
server round-trip finished. Fast typers could see stale text and doubt
the message was sent.
2. The disclosure carets on collapsed agent turns and the caret inside
the status pill did not share one alignment axis. They rendered at
different x-offsets and sizes.
3. The composer container had different horizontal padding than the
thread column above it, so the input box did not line up with the
message bubbles.
**Expected behavior**
The composer clears the instant a send starts. All disclosure carets sit
on one vertical axis with one size. The composer's left and right edges
align with the thread column.
**Steps to reproduce**
Open any task in the chat-style task view. Type a message and press
Enter — watch the composer text. Collapse and expand agent turns —
compare caret positions. Compare the composer's horizontal edges with
the message bubbles above it.
## What Changed
- `TaskChatComposer.tsx`: clear the draft synchronously when a send
starts instead of after the request resolves; restore the draft if the
send fails.
- `TaskChatTurn.tsx` and `TaskChatStatusPill.tsx`: use one shared caret
alignment (size, x-offset) for turn disclosure and status pill carets,
with supporting utility styles in `ui/src/index.css`.
- `TaskChatThread.tsx`: align the composer container with the thread
column padding.
- Added or updated unit tests in `TaskChatComposer.test.tsx`,
`TaskChatTurn.test.tsx`, `TaskChatActivityPhase.test.tsx`, and
`TaskChatThread.test.tsx`.
## Verification
- Run `pnpm vitest run
src/components/task-chat/TaskChatComposer.test.tsx
src/components/task-chat/TaskChatActivityPhase.test.tsx
src/components/task-chat/TaskChatTurn.test.tsx
src/components/TaskChatThread.test.tsx` in `ui/` — 4 files, 64 tests,
all pass.
- Manual: open a task in the chat view, send a message, and confirm the
composer clears immediately. Collapse/expand turns and confirm the
carets align. Compare composer edges with the thread column; the
alignment fixes were also pixel-verified with screenshots during local
review.
## Risks
- Low risk. All changes are render-layer only; no server or data
changes.
- The composer now clears optimistically. If a send fails, the draft is
restored, so no user text is lost.
- Caret alignment uses shared CSS utilities; visual regressions would
show in the existing component tests and in any screenshot diff.
## Model Used
- Implementation: OpenAI Codex CLI coding agent (Codex model family,
agentic tool use) via Paperclip's Codex adapter.
- Review, verification, rebase, and PR preparation: Anthropic Claude,
model id `claude-fable-5` (extended thinking, tool use), via Claude
Code.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## 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>
## 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>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Its release pipeline publishes a set of npm packages from CI with
npm trusted publishing (GitHub OIDC), gated by
`scripts/release-package-manifest.json`
> - A brand-new package name cannot be published by CI directly: the PR
bootstrap gate requires the name to resolve on npm, and a
trusted-publisher rule can only be configured after the package page
exists
> - The current bootstrap helper closes that gap by building the package
locally and publishing its real output from a maintainer machine —
before the PR that adds the package has passed CI or review
> - This pull request replaces that flow: the helper now publishes a
minimal deprecated placeholder at version `0.0.0` that only reserves the
name, so every real version ships from CI
> - The benefit is that unreviewed build output never reaches npm, and
the bootstrap runs from any checkout (including `master`, before the new
package's PR merges) with no local build
## Linked Issues or Issue Description
**What existing behavior does this improve?**
The one-time npm bootstrap for a brand-new release package (`pnpm run
release:bootstrap-package`).
**Current behavior**
The helper builds the target package locally and publishes the real
build output from a maintainer machine. That content has not passed
repository CI or review at publish time. The helper also requires the
new package to exist in the local workspace, so it must run from the
(unmerged) PR branch that adds the package.
**Proposed behavior**
The helper publishes a three-file placeholder at version `0.0.0`
(manifest, README, and an `index.js` that throws a descriptive error),
waits for the registry to show the package, then deprecates it. The PR
bootstrap gate (`scripts/check-release-package-bootstrap.mjs`) only
requires the name to resolve on the registry, so the placeholder
satisfies it. The first real calver release from CI supersedes the
placeholder, and a stable release moves `latest` off it — the same
`latest` window that existed under the old flow, but containing an
explicit inert stub instead of unreviewed code.
**Reason and benefit**
Real package content only ever reaches npm from CI, after review and
merge. The bootstrap becomes safer (scope guard refuses names outside
`@paperclipai/`, already-published names are rejected) and simpler (no
local build, no workspace state, runs from any checkout).
**Breaking changes**
None at runtime. The helper's CLI surface changes: it now takes a
package name only (no directory selector) and drops `--skip-build`.
`doc/PUBLISHING.md` is updated to match.
## What Changed
- `scripts/bootstrap-npm-package.mjs`: replaced the build-and-publish
flow with a placeholder publish — stages `package.json` + `README.md` +
throwing `index.js` at version `0.0.0` in a temp directory, previews
with `npm publish --dry-run`, and publishes only with `--publish`.
One-time passwords are prompted interactively (never passed as
arguments, since they are single-use and would land in shell history),
with re-prompt on a rejected or expired code. After publishing, the
helper polls the registry until the package is visible (a first publish
can lag by minutes; verified live at ~5 minutes), requiring two
consecutive sightings before prompting for a second code and deprecating
the placeholder so accidental installs warn loudly; on timeout or
failure it prints the exact manual `npm deprecate` command. Added an
`@paperclipai/`-scope guard and a fail-fast error when `--publish` runs
without an interactive terminal. Removed the workspace-plan dependency
so it runs from any checkout.
- `scripts/bootstrap-npm-package.test.mjs`: rewrote for the new
interface — argument parsing, scope validation, the generated
placeholder files (manifest shape, throwing entry point, README), the
OTP re-prompt loop, and the registry poll (consecutive-sighting
requirement, timeout, transient-error tolerance) via injected fakes.
- `doc/PUBLISHING.md`: rewrote the "One-time bootstrap sequence for a
new package" section for the placeholder flow, including the `latest`
dist-tag window and the trusted-publishing setup ordering (placeholder
publish → trusted publisher rule → `"publishFromCi": true`).
- `.github/scripts/check-pr-release-bootstrap.mjs` (+ test, + wiring in
`run-quality-gates.mjs`): new informational commitperclip notice on PRs
that need this bootstrap. It fires when the PR newly release-enables a
package that is missing from npm, or adds an unpublished `publishFromCi:
false` package that published packages declare a `workspace:*`
dependency on, and names the exact maintainer command — so contributors
know the red `policy` check is not theirs to fix. It never fails the
gate (the `policy` job remains the enforcer), only looks up
scope-validated names on the registry, and stays quiet on registry
errors.
## Verification
- `node --test scripts/bootstrap-npm-package.test.mjs`: 13/13 pass
- `node --test .github/scripts/tests/*.test.mjs`: 147/147 pass (10 new
for the PR notice)
- `pnpm run test:release-registry`: 82/82 pass
- Replayed the new PR notice against a real historical PR's live API
data (files, manifest at base and head refs): with the registry in its
pre-bootstrap state it produces the exact maintainer instruction; with
the package bootstrapped it stays silent
- Full live end-to-end run: the flow bootstrapped
`@paperclipai/adapter-kimi-local` for real — dry-run preview (634-byte,
3-file tarball), publish, registry visibility after ~5 minutes of
propagation lag, deprecation confirmed via `npm view ... deprecated`
- Guards verified live: an already-published name is rejected, an
out-of-scope name (`left-pad`) is rejected, unknown options (including
the removed `--otp`) are rejected, and `--publish` in a non-interactive
shell fails fast before any network call
## Risks
- The `latest` dist-tag points at the deprecated `0.0.0` placeholder
until the first stable release supersedes it. This window also existed
under the old flow (which parked `latest` at a locally built version);
internal consumers are unaffected because release version rewrites pin
exact calver versions.
- The registry poll caps at ~10 minutes. If propagation is slower than
that, the helper prints the exact `npm deprecate ... --otp <code>`
command to run manually once `npm view` resolves.
- The helper no longer validates the name against the workspace release
plan, so a typo within the `@paperclipai/` scope would reserve a wrong
name. The dry-run preview shows the exact name before any publish.
## Model Used
- Anthropic, **Claude Fable 5** (`claude-fable-5`) via Claude Code, with
repository, shell, and Git tooling. It analyzed the existing bootstrap
flow and the release scripts (`release-package-map.mjs`,
`check-release-package-bootstrap.mjs`, `release.sh` dist-tag handling),
wrote the replacement script and tests, updated the documentation, and
ran the verification above.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## 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>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandbox-managed runtimes move files between a host and an isolated
sandbox
> - The git-bundle export path reads the full bundle into host memory
> - The workspace restore path already uses the provider native
`syncOut` transfer
> - This pull request uses `syncOut` for bundle export and keeps
`readFile` as a fallback
> - The change reduces host buffering and exposes the transfer to
provider tracing
## Linked Issues or Issue Description
**What happened?**
The sandbox git-bundle export used `client.readFile` even when the
provider supported native `syncOut`. The path buffered the full bundle
in host memory and used a chunked base64 command loop.
**Expected behavior**
The export should use one confined native file transfer when the
provider supports `syncOut`. Providers without that capability should
keep the existing `readFile` fallback.
**Steps to reproduce**
1. Prepare a sandbox-managed runtime with native `syncOut` support.
2. Export the sandbox git bundle.
3. Inspect the sync operations and file reads.
4. Confirm that the bundle uses one native file mapping and that the
status file still uses `readFile`.
**Paperclip version or commit**
Commit `3fc88d14be7133867897e87838276e15aba675fd`.
**Deployment mode**
Built from source. The change applies to sandbox-managed runtime
execution.
**Additional context**
This PR contains a focused bug fix. No public issue matched this change
during the duplicate search.
## What Changed
- Route bundle export through `nativeSyncOut` when the provider supports
it.
- Keep the existing `readFile` fallback for providers without native
sync support.
- Confine the native bundle mapping with `assertSyncOperationsConfined`.
- Cover the native path, fallback path, retry path, and confinement
checks with unit tests.
## Verification
- `npx vitest run
packages/adapter-utils/src/sandbox-managed-runtime.test.ts` — 52 tests
passed.
- `npx tsc --noEmit` in `packages/adapter-utils` — passed.
- The PR CI workflow must pass before merge.
## Risks
Low risk. The native path runs only when the provider advertises
`syncOut`. The existing `readFile` path remains available as a fallback.
Native transfer progress reports only start and finish events.
## Model Used
OpenAI GPT-5 Codex. Exact runtime model ID: GPT-5 Codex. Context window:
not exposed in this run. Capabilities used: code review, shell tools,
GitHub CLI, and Paperclip API coordination.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## 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>
## 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>
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>
## 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>
## 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>
## 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>