Files
PaperClipAI/doc/PUBLISHING.md
T
Devin FoleyandPaperclip eedc7ddef2 Make ACP the default engine for local adapters (#9238)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Adapter packages are the bridge between the control plane and local
agent harnesses such as Claude Code, Codex, and Gemini CLI.
> - ACP support was concentrated in a separate `acpx_local` adapter,
which made ACP feel like a separate agent choice instead of an execution
capability of the harness adapters.
> - Claude, Codex, and Gemini now have ACP-capable harnesses, so the
native adapter should own ACP selection, fallback, config, transcript
parsing, and environment diagnostics.
> - The standalone ACPX adapter still needs a compatibility path for
existing rows, but it should not be offered as an active adapter for new
agents.
> - This pull request moves the shared ACP runtime into
`@paperclipai/acpx-engine`, wires Claude/Codex/Gemini local adapters to
prefer ACP when prerequisites are available, and retires `acpx_local` to
a tombstone.
> - The benefit is one adapter per harness, richer ACP transcripts by
default where possible, and a migration path for existing Claude/Codex
ACPX agents.

## Linked Issues or Issue Description

Closes #5932 — the broken default `acpx_local` Claude path is replaced
by native `claude_local` ACP support, existing Claude/Codex ACPX rows
migrate to native adapters, and new agents no longer choose the
standalone ACPX adapter.

Refs #4893 — original merged ACPX local adapter runtime that this PR
replaces with native per-harness ACP engines.
Refs #6590 — prior ACPX-Claude seamlessness work folded into the new
native Claude ACP path.
Refs #197 — related open generic ACP/Kiro adapter work; this PR does not
close it because Kiro/custom generic ACP remains a separate adapter
decision.
Refs #7018 — related Kimi-specific `acpx_local` shell failure; this PR
retires the built-in standalone adapter but does not add a native Kimi
adapter.
Refs #8864 — related ACPX prompt/API guidance PR; this PR moves runtime
guidance into the shared/native ACP engine path instead of the old
standalone adapter.
Refs #8881 — related `acpx_local` POSIX shell failure from the old
`acpx` pin; this PR updates ACP dependencies but does not claim
custom/OMP ACP support as a first-class native adapter.
Refs #8964 — related open `acpx_local` stderr cleanup PR; this PR makes
the old runtime path obsolete for new agents but keeps it as a
non-closing reference.

Problem description:

- The standalone `acpx_local` adapter duplicates Claude/Codex agent
choices that already have first-class local adapters.
- ACP should be an execution engine capability of each harness adapter
when the underlying harness supports ACP.
- Existing `acpx_local` agents should either migrate to native harness
adapters or fail with an explicit retirement message instead of silently
falling back to the process adapter.

## What Changed

- Added `@paperclipai/acpx-engine` as the shared ACP execution,
session-codec, CLI formatter, and UI parser package.
- Wired `claude_local`, `codex_local`, and `gemini_local` to auto-select
ACP by default when prerequisites pass, with `engine=cli` opt-out and
`engine=acp` strict mode.
- Added ACP config schema/UI fields, environment checks, session-codec
preservation, transcript parsing, and adapter capability metadata for
the native adapters.
- Retired `acpx_local` to a server tombstone, removed its
UI/package/runtime image surface, and added a migration for existing
Claude/Codex ACPX agents.
- Updated package manifests, lockfile, release tooling, docs, Kubernetes
sandbox defaults, and tests.

## Verification

- `corepack pnpm --filter @paperclipai/acpx-engine typecheck`
- `corepack pnpm --filter @paperclipai/adapter-claude-local typecheck`
- `corepack pnpm --filter @paperclipai/adapter-codex-local typecheck`
- `corepack pnpm --filter @paperclipai/adapter-gemini-local typecheck`
- `corepack pnpm --filter @paperclipai/acpx-engine exec vitest run`
- `corepack pnpm --filter @paperclipai/adapter-claude-local exec vitest
run src/server/acp.test.ts src/server/execute.acp-fallback.test.ts
src/ui/build-config.test.ts`
- `corepack pnpm --filter @paperclipai/adapter-codex-local exec vitest
run src/server/acp.test.ts src/ui/build-config.test.ts`
- `corepack pnpm --filter @paperclipai/adapter-gemini-local exec vitest
run src/server/acp.test.ts src/ui/build-config.test.ts
src/ui/parse-stdout.test.ts`
- `corepack pnpm --filter @paperclipai/plugin-sdk ensure-build-deps &&
corepack pnpm --filter @paperclipai/server exec tsc --noEmit`
- `corepack pnpm --filter @paperclipai/server exec vitest run
src/__tests__/adapter-routes.test.ts
src/__tests__/adapter-session-codecs.test.ts
src/__tests__/adapter-models.test.ts`
- `corepack pnpm --filter @paperclipai/ui typecheck`
- `corepack pnpm --filter @paperclipai/ui exec vitest run
src/adapters/metadata.test.ts
src/adapters/adapter-display-registry.test.ts
src/components/AgentConfigForm.test.ts
src/components/AgentConfigForm.render.test.tsx
src/components/transcript/RunTranscriptView.test.tsx`
- `node --test scripts/bootstrap-npm-package.test.mjs
scripts/release-package-map.test.mjs
scripts/verify-release-registry-state.test.mjs`

Note: the server typecheck script calls `pnpm` internally; this dev
shell exposes pnpm through Corepack only, so I ran the two script steps
manually with `corepack pnpm`.

## Risks

- Migration changes existing `acpx_local` Claude/Codex agents to native
adapter types and clears old ACPX task sessions/runtime state.
- Custom ACP commands remain on the retired tombstone and will need a
separate future adapter/plugin path.
- ACP auto-selection depends on local Node and ACP server command
prerequisites; remote and unsupported environments fall back to CLI
unless `engine=acp` is explicit.
- `@paperclipai/acpx-engine` is a new public package and needs npm
trusted-publishing bootstrap before release automation can publish it.

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

## Model Used

OpenAI GPT-5 via Codex coding agent. Exact hosted model build and
context-window size are not exposed in this runtime. Tool use included
shell execution, repository editing, GitHub CLI operations, and local
test/typecheck execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-07-08 19:05:03 -07:00

9.5 KiB

Publishing to npm

Low-level reference for how Paperclip packages are prepared and published to npm.

For the maintainer workflow, use doc/RELEASING.md. This document focuses on packaging internals.

Current Release Entry Points

Use these scripts:

Paperclip no longer uses release branches or Changesets for publishing.

Why the CLI needs special packaging

The CLI package, paperclipai, imports code from workspace packages such as:

  • @paperclipai/server
  • @paperclipai/db
  • @paperclipai/shared
  • adapter packages under packages/adapters/

Those workspace references are valid in development but not in a publishable npm package. The release flow rewrites versions temporarily, then builds a publishable CLI bundle.

build-npm.sh

Run:

./scripts/build-npm.sh

This script:

  1. runs the forbidden token check unless --skip-checks is supplied
  2. runs pnpm -r typecheck
  3. bundles the CLI entrypoint with esbuild into cli/dist/index.js
  4. verifies the bundled entrypoint with node --check
  5. rewrites cli/package.json into a publishable npm manifest and stores the dev copy as cli/package.dev.json
  6. copies the repo README.md into cli/README.md for npm metadata

After the release script exits, the dev manifest and temporary files are restored automatically.

Package discovery and versioning

Public packages are discovered from:

  • packages/
  • server/
  • ui/
  • cli/

The version rewrite step now uses scripts/release-package-map.mjs, which:

  • finds all public packages
  • sorts them topologically by internal dependencies
  • rewrites each package version to the target release version
  • rewrites internal workspace:* dependency references to the exact target version
  • updates the CLI's displayed version string

Those rewrites are temporary. The working tree is restored after publish or dry-run.

@paperclipai/ui packaging

The UI package publishes prebuilt static assets, not the source workspace.

The ui package uses scripts/generate-ui-package-json.mjs during prepack to swap in a lean publish manifest that:

  • keeps the release-managed name and version
  • publishes only dist/
  • omits the source-only dependency graph from downstream installs

After packing or publishing, postpack restores the development manifest automatically.

Manual first publish for @paperclipai/ui

If you need to publish only the UI package once by hand, use the real package name:

  • @paperclipai/ui

Recommended flow from the repo root:

# optional sanity check: this 404s until the first publish exists
npm view @paperclipai/ui version

# make sure the dist payload is fresh
pnpm --filter @paperclipai/ui build

# confirm your local npm auth before the real publish
npm whoami

# safe preview of the exact publish payload
cd ui
pnpm publish --dry-run --no-git-checks --access public

# real publish
pnpm publish --no-git-checks --access public

Notes:

  • Publish from ui/, not the repo root.
  • prepack automatically rewrites ui/package.json to the lean publish manifest, and postpack restores the dev manifest after the command finishes.
  • If npm view @paperclipai/ui version already returns the same version that is in ui/package.json, do not republish. Bump the version or use the normal repo-wide release flow in scripts/release.sh.

If the first real publish returns npm E404, check npm-side prerequisites before retrying:

  • npm whoami must succeed first. An expired or missing npm login will block the publish.
  • For an organization-scoped package like @paperclipai/ui, the paperclipai npm organization must exist and the publisher must be a member with permission to publish to that scope.
  • The initial publish must include --access public for a public scoped package.
  • npm also requires either account 2FA for publishing or a granular token that is allowed to bypass 2FA.

Version formats

Paperclip uses calendar versions:

  • stable: YYYY.MDD.P
  • canary: YYYY.MDD.P-canary.N

Examples:

  • stable: 2026.318.0
  • canary: 2026.318.1-canary.2

Publish model

Canary

Canaries publish under the npm dist-tag canary.

Example:

  • paperclipai@2026.318.1-canary.2

This keeps the default install path unchanged while allowing explicit installs with:

npx paperclipai@canary onboard

The release script now verifies two things after a canary publish:

  • the canary dist-tag resolves to the version that was just published
  • every published internal @paperclipai/* dependency referenced by that manifest exists on npm

It also treats latest -> canary as a failure by default, because npm metadata can otherwise leave the default install path pointing at an unreleased canary dependency graph. Only pass ./scripts/release.sh canary --allow-canary-latest when that latest behavior is explicitly intended.

Stable

Stable publishes use the npm dist-tag latest.

Example:

  • paperclipai@2026.318.0

Stable publishes do not create a release commit. Instead:

  • package versions are rewritten temporarily
  • packages are published from the chosen source commit
  • git tag vYYYY.MDD.P points at that original commit

Trusted publishing

The intended CI model is npm trusted publishing through GitHub OIDC.

That means:

  • no long-lived NPM_TOKEN in repository secrets
  • GitHub Actions obtains short-lived publish credentials
  • trusted publisher rules are configured per workflow file

See doc/RELEASE-AUTOMATION-SETUP.md for the GitHub/npm setup steps.

Release enrollment for new public packages

Paperclip does not auto-publish every non-private workspace package anymore. CI publishing is controlled by scripts/release-package-manifest.json.

When you add a new public package:

  1. add it to the manifest and decide whether CI should publish it immediately
  2. if CI should publish it, bootstrap the package on npm before merge
  3. if CI should not publish it yet, keep "publishFromCi": false
  4. only enable "publishFromCi": true after npm trusted publishing is configured for that package

PR CI now checks changed release-enabled package manifests against npm. That catches a missing first-publish bootstrap before the change reaches master.

One-time bootstrap sequence for a new package

The first publish of a brand-new package still needs one human maintainer with npm write access. After that, trusted publishing can take over.

Example for a newly added public package from the repo root:

# safe preview
pnpm run release:bootstrap-package -- @paperclipai/new-package

# one-time first publish from an authenticated maintainer machine
pnpm run release:bootstrap-package -- @paperclipai/new-package --publish --otp 123456

The helper script:

  • checks that the package does not already exist on npm
  • builds the target package unless --skip-build is passed
  • runs pnpm publish <package-dir> --dry-run --no-git-checks --access public from the repo root
  • only runs the real pnpm publish <package-dir> --no-git-checks --access public when --publish --otp <code> is provided

The helper intentionally uses pnpm publish instead of npm publish so workspace dependencies and publishConfig export fields are normalized before the package is sent to the registry.

For the real --publish step, the maintainer machine must already be authenticated to npm. If npm whoami returns 401, first run npm logout --registry=https://registry.npmjs.org/ to clear any stale local auth, then run npm login or npm adduser locally as an npm org member, and finally rerun the helper. That local human auth is fine for the one-time bootstrap publish; we just do not want the same auth model inside CI. The helper now requires --otp <code> up front for --publish, so it fails before the real publish attempt if the one-time password is missing.

After that first publish succeeds:

  1. open https://www.npmjs.com/package/@paperclipai/new-package
  2. go to Settings → Trusted publishing
  3. add repository paperclipai/paperclip
  4. set workflow filename to release.yml
  5. optionally go to Settings → Publishing access and enable Require two-factor authentication and disallow tokens
  6. keep publishFromCi: true in scripts/release-package-manifest.json

Once those steps are done, future canary and stable publishes for that package are automated through GitHub OIDC. The manual step is only the first package creation on npm.

Rollback model

Rollback does not unpublish anything.

It repoints the latest dist-tag to a prior stable version:

./scripts/rollback-latest.sh 2026.318.0

This is the fastest way to restore the default install path if a stable release is bad.