Consume Claude ACP as a pinned npm runtime dependency - #451
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Desktop development and release packaging currently depend on a committed snapshot of
@agentclientprotocol/claude-agent-acp, including upstream source, generateddist, tests, configuration, and a separate lockfile. This keeps release inputs reproducible, but it also commits more than 111,000 lines of third-party code and makes upstream updates a manual source-vendor refresh.This PR replaces that snapshot with an isolated, lockfile-pinned npm runtime while preserving the runtime layout and NeverWrite behavior expected by the native backend and packaged app.
Resulting behavior
@agentclientprotocol/claude-agent-acpexactly to0.76.0inapps/desktop/runtimes/claude/..cache/claude-runtime/<rust-target>/through one preparer shared by local development and release staging.native-backend/embedded/claude-agent-acpalongside the embedded Node runtime.NEVERWRITE_CLAUDE_EMBEDDED_DIRandapps/desktop/embedded/claude-agent-acpinputs as complete prepared runtimes instead of modifying those directories during staging.vendor/Claude-agent-acp-upstream/and update architecture, licensing, runtime setup, CI, packaging, and upstream-watch documentation.The migration commits preserve behavior first, and the final dependency-promotion commit updates Claude ACP from
0.75.1to the stable0.76.0release tracked by #442. The release adds capability-gated recommended model/effort metadata and updates the lockedfast-uritransitive dependency from3.1.6to3.1.7. NeverWrite does not advertise the AIRrecommendedValuecapability, so its selected configuration remains unchanged. Native Claude resume, persisted runtime identity, and Claude subagent/steering extensions remain unchanged.Closes #442.
Runtime preparation
The new preparer performs an immutable, target-aware installation:
The target matrix covers Linux x64/ARM64, Windows x64/ARM64, macOS x64/ARM64, and macOS universal. A lock directory prevents concurrent writers from publishing partial generations.
Preserved TaskList hardening
The npm
0.76.0release still uses an ambiguous regex in the textualTaskListfallback. NeverWrite previously carried a source-level fix that replaces it with linear string parsing. Dropping the vendor without carrying this delta forward would restore the potential excessive-backtracking/CPU-stall behavior reported in:This PR preserves that fix as a narrowly scoped patch to the published
dist/tools.js. The preparer refuses to apply it unless both the package version and original file checksum match the expected0.76.0release, and refuses to publish the runtime unless the patched checksum and compatibility baseline match.The regression contracts exercise the installed runtime parser itself, including structured JSON, textual task lists, owners, dependency lists, empty output, malformed suffixes, and adversarial oversized input. They import the runtime from an isolated temporary copy, and the adversarial case runs in a child process with a parent-enforced timeout so a synchronous parser stall cannot hang CI indefinitely.
The patch should be removed only after an upstream release includes an equivalent fix and these contracts pass against the unmodified published artifact.
Runtime and package validation
The new real-runtime smoke starts the published adapter and its native Claude CLI against a deterministic local Anthropic-compatible server. It uses a temporary profile and workspace with synthetic credentials and verifies:
--version;--cli --version;initializeandsession/new;Readtool call against a fixture file;The packaged-sidecar smoke runs this against the embedded runtime and embedded Node. The package workflow also runs the real runtime natively on Linux and Windows x64/ARM64 and both macOS architectures. The Intel macOS job downloads the universal runtime created by the ARM packaging job and exercises its other Node slice.
Electron's after-pack validation now checks the full patched JavaScript baseline, production package versions, and required native Claude CLI architectures in addition to checking that required files exist.
Compatibility retained
claude-acp, persisted history, configuration, queue behavior, model/effort/mode mapping, authentication probes, and provider routing remain unchanged.0.76.0advertises support for recommended configuration values, but only emits them when the client opts in; NeverWrite does not opt in and retains the legacy model/effort option contract.Commits
test(claude): capture runtime compatibility baselinebuild(claude): prepare a locked npm runtime with the TaskList patchrefactor(claude): use the prepared dependency in development and releasestest(claude): exercise the packaged runtime across release targetschore(claude): remove vendored sources and track the npm runtimedocs(claude): align runtime dependency documentationfix(claude): normalize patch line endings on Windowsci(release): prepare Claude runtime before packagingchore(claude): upgrade runtime dependency to 0.76.0Validation performed locally
cargo test --locked -p neverwrite-native-backend— 372 passed.npm test— 2,606 passed, 2 existing TODOs.npm run lint— passed.npm run electron:build— passed; existing large-chunk warnings remain.npm run electron:ai-runtime:smoke— passed after removing the vendor.npm run test:claude-preparation— 8 passed against the0.76.0manifest, lockfile, patch, and watcher.npm run test:claude-runtime— 2 passed against the generated isolated runtime.npm run claude:smoke— passed on0.76.0with the host Node; the earlier migration validation also passed with the official Node22.23.1distribution used by packaging.npm audit --omit=dev— zero known vulnerabilities after lockingfast-uri3.1.7.The cross-platform native execution and full packaged application jobs are configured in CI and have not yet run on this branch. No real Claude account login or paid external service was used during local validation.
Update and rollback notes
Future Claude ACP releases remain deliberate version promotions: update the exact dependency and isolated lockfile, compare the production graph and runtime baseline, decide whether the TaskList patch is still needed, and rerun the contracts and packaged smoke matrix. Patch input discovery now follows the exact manifest version, avoiding a second hard-coded version in the preparer. The upstream watcher now reads this exact dependency pin and reports newer stable tags.
Rollback should revert the dependency manifest/lockfile, patch, preparer, resolver, packaging, workflow, documentation, and vendor removal as one unit. This change introduces no persisted-data migration.