mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-12 18:06:11 +02:00
## 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>
255 lines
9.5 KiB
Markdown
255 lines
9.5 KiB
Markdown
# Publishing to npm
|
|
|
|
Low-level reference for how Paperclip packages are prepared and published to npm.
|
|
|
|
For the maintainer workflow, use [doc/RELEASING.md](RELEASING.md). This document focuses on packaging internals.
|
|
|
|
## Current Release Entry Points
|
|
|
|
Use these scripts:
|
|
|
|
- [`scripts/release.sh`](../scripts/release.sh) for canary and stable publish flows
|
|
- [`scripts/create-github-release.sh`](../scripts/create-github-release.sh) after pushing a stable tag
|
|
- [`scripts/rollback-latest.sh`](../scripts/rollback-latest.sh) to repoint `latest`
|
|
- [`scripts/build-npm.sh`](../scripts/build-npm.sh) for the CLI packaging build
|
|
|
|
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:
|
|
|
|
```bash
|
|
./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`](../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`](../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:
|
|
|
|
```bash
|
|
# 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`](../ui/package.json), do not republish. Bump the version or use the normal repo-wide release flow in [`scripts/release.sh`](../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:
|
|
|
|
```bash
|
|
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](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`](../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:
|
|
|
|
```bash
|
|
# 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`](../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:
|
|
|
|
```bash
|
|
./scripts/rollback-latest.sh 2026.318.0
|
|
```
|
|
|
|
This is the fastest way to restore the default install path if a stable release is bad.
|
|
|
|
## Related Files
|
|
|
|
- [`scripts/build-npm.sh`](../scripts/build-npm.sh)
|
|
- [`scripts/generate-npm-package-json.mjs`](../scripts/generate-npm-package-json.mjs)
|
|
- [`scripts/generate-ui-package-json.mjs`](../scripts/generate-ui-package-json.mjs)
|
|
- [`scripts/release-package-map.mjs`](../scripts/release-package-map.mjs)
|
|
- [`cli/esbuild.config.mjs`](../cli/esbuild.config.mjs)
|
|
- [`doc/RELEASING.md`](RELEASING.md)
|