From 9049a6b241cb97419df3461865a55c45c714ad33 Mon Sep 17 00:00:00 2001 From: atlas-bot Date: Sun, 27 Sep 2026 21:33:12 +0000 Subject: [PATCH 1/2] feat(ci): claude-agent-acp in the agent-runtime images (REN-302) The k8s-go adapter test failed with 'Claude ACP server command is not available: claude-agent-acp'. Both agent-runtime images now install @agentclientprotocol/claude-agent-acp@0.73.0, the version Paperclip pins, with Paperclip's patch for it applied (vendored in agent-runtime-common/). go and gofmt are also linked into /usr/local/bin, so every tool is found with the default PATH, not only with the image ENV. The build checks that with env -i. Kaniko context is now ci/images so both images can COPY the shared patch. Co-Authored-By: Claude Opus 5.5 --- .woodpecker/build-agent-runtime.yml | 4 +- README.md | 5 +- .../claude-agent-acp-0.73.0.patch | 66 +++++++++++++++++++ ci/images/agent-runtime-go/Containerfile | 26 +++++++- ci/images/agent-runtime-ops/Containerfile | 19 +++++- 5 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 ci/images/agent-runtime-common/claude-agent-acp-0.73.0.patch diff --git a/.woodpecker/build-agent-runtime.yml b/.woodpecker/build-agent-runtime.yml index ce682f6e..a2dc83c2 100644 --- a/.woodpecker/build-agent-runtime.yml +++ b/.woodpecker/build-agent-runtime.yml @@ -32,6 +32,8 @@ steps: - name: build-agent-runtime image: registry.gitlab.com/gitlab-ci-utils/container-images/kaniko:v1.25.18-debug directory: ci/images + # Context is ci/images (not the variant dir) so both Containerfiles can + # COPY the shared agent-runtime-common/ files. # One pod, both images one after the other (kaniko --cleanup between # builds), at 1 CPU so it still fits if it overlaps another pipeline. backend_options: @@ -56,7 +58,7 @@ steps: for VARIANT in go ops; do /kaniko/executor \ --cleanup \ - --context "agent-runtime-$VARIANT" \ + --context . \ --dockerfile "agent-runtime-$VARIANT/Containerfile" \ --destination "gitea.renner.uno/konairius/agent-runtime-$VARIANT/agent-runtime-claude:$TAG" \ --destination "gitea.renner.uno/konairius/agent-runtime-$VARIANT/agent-runtime-claude:v1" \ diff --git a/README.md b/README.md index c0d28ed0..fbac65a3 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,10 @@ Pod images for the Paperclip Kubernetes sandbox (REN-284). Each is the stock set. Base user (1000, non-root), entrypoint and cmd are unchanged; the images run with a read-only root FS. Source in `ci/images/agent-runtime-*`, built by `.woodpecker/build-agent-runtime.yml` (re-run alone: deployment to -target `build-agent-runtime`), versions bumped by Renovate. +target `build-agent-runtime`), versions bumped by Renovate. Both also +carry `claude-agent-acp` for the ACP engine, pinned and patched like +Paperclip ships it (`ci/images/agent-runtime-common/`); bump it by hand +together with Paperclip. All tools are on the default `PATH`. | Image | `imageRegistry` | Adds | |---|---|---| diff --git a/ci/images/agent-runtime-common/claude-agent-acp-0.73.0.patch b/ci/images/agent-runtime-common/claude-agent-acp-0.73.0.patch new file mode 100644 index 00000000..6774dc65 --- /dev/null +++ b/ci/images/agent-runtime-common/claude-agent-acp-0.73.0.patch @@ -0,0 +1,66 @@ +diff --git a/dist/acp-agent.js b/dist/acp-agent.js +index 0214a509eb5e7b777a69e4e34637941c275d27b1..403be02df66e25eee29e7d5bef14627011e02b30 100644 +--- a/dist/acp-agent.js ++++ b/dist/acp-agent.js +@@ -2922,9 +2922,19 @@ export class ClaudeAcpAgent { + amount: message.total_cost_usd, + currency: "USD", + }, +- ...(message.origin && { +- _meta: { "_claude/origin": message.origin }, +- }), ++ _meta: { ++ ...(message.origin && { "_claude/origin": message.origin }), ++ // ACP's core Usage shape exposes context occupancy and ++ // cost, but not the billable input/output split. Keep the ++ // SDK's bounded aggregate in extension metadata so ACPX can ++ // normalize it without persisting a raw provider message. ++ usage: { ++ input_tokens: message.usage.input_tokens, ++ output_tokens: message.usage.output_tokens, ++ cache_read_input_tokens: message.usage.cache_read_input_tokens, ++ cache_creation_input_tokens: message.usage.cache_creation_input_tokens, ++ }, ++ }, + }, + }); + } +@@ -5311,6 +5321,10 @@ export class ClaudeAcpAgent { + settingSources: ["user", "project", "local"], + ...(thinking !== undefined && { thinking }), + ...userProvidedOptions, ++ // Paperclip Runner owns the complete session context. Its isolated ++ // user root contains only assigned skills; project/local settings ++ // would reintroduce host prompts, plugins, skills, and MCP servers. ++ ...(process.env.PAPERCLIP_ACPX_ISOLATED_CONTEXT === "1" && { settingSources: ["user"] }), + ...(settings && { settings }), + env, + // Override certain fields that must be controlled by ACP +@@ -5318,7 +5332,9 @@ export class ClaudeAcpAgent { + includePartialMessages: true, + forwardSubagentText, + mcpServers: { +- ...(userProvidedOptions?.mcpServers || {}), ++ ...(process.env.PAPERCLIP_ACPX_ISOLATED_CONTEXT === "1" ++ ? {} ++ : (userProvidedOptions?.mcpServers || {})), + ...mcpServers, + ...(fileChangeAuditSupport + ? { [FILE_CHANGE_AUDIT_SERVER_NAME]: fileChangeAuditSupport.mcpServer } +@@ -5329,6 +5345,16 @@ export class ClaudeAcpAgent { + allowDangerouslySkipPermissions: ALLOW_BYPASS, + permissionMode: initialPermissionMode, + canUseTool: this.canUseTool(sessionId), ++ // The runner pins this actual MCP endpoint before provider launch. ++ // Exempt only task delivery/control tools at the SDK dispatch boundary, ++ // not arbitrary provider permission metadata or MCP name prefixes. ++ ...(process.env.PAPERCLIP_ACPX_ISOLATED_CONTEXT === "1" && { ++ allowedTools: mcpServers.paperclip?.type === "http" ++ && mcpServers.paperclip.url === process.env.PAPERCLIP_ACPX_TASK_TOOL_BRIDGE_URL ++ ? ["paperclip_finish", "paperclip_block", "read_current_wake_comments", "request_human_input"] ++ .map((tool) => `mcp__paperclip__${tool}`) ++ : [], ++ }), + // Forward MCP elicitation requests onto ACP elicitation. Only attached + // when the client advertised support, so non-supporting clients keep the + // SDK's default (auto-decline) behavior. (AskUserQuestion is handled in diff --git a/ci/images/agent-runtime-go/Containerfile b/ci/images/agent-runtime-go/Containerfile index c2ea8097..ec862efa 100644 --- a/ci/images/agent-runtime-go/Containerfile +++ b/ci/images/agent-runtime-go/Containerfile @@ -15,6 +15,27 @@ FROM ghcr.io/paperclipai/agent-runtime-claude:git-38d8f371722b315d2fb3bbaa512518 COPY --from=go /usr/local/go /usr/local/go COPY --from=golangci-lint /usr/bin/golangci-lint /usr/local/bin/golangci-lint +# go and gofmt also in /usr/local/bin: a process started with the default PATH +# (not the image ENV) still finds them. +USER root +RUN ln -s /usr/local/go/bin/go /usr/local/bin/go \ + && ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt +USER 1000:1000 + +# ACP server the Paperclip ACP engine starts in the pod (REN-302). Version and +# patch match what Paperclip ships: packages/adapters/claude-local/package.json +# pins 0.73.0 and applies patches/@agentclientprotocol__claude-agent-acp@0.73.0.patch +# (isolated context, task-tool allow-list, usage metadata). Not bumped by +# Renovate on purpose: bump only together with Paperclip, and replace the patch. +# npm's global prefix is /usr (NodeSource), so the binary lands in /usr/bin. +COPY agent-runtime-common/claude-agent-acp-0.73.0.patch /tmp/claude-agent-acp.patch +USER root +RUN npm install -g @agentclientprotocol/claude-agent-acp@0.73.0 \ + && cd /usr/lib/node_modules/@agentclientprotocol/claude-agent-acp \ + && git apply /tmp/claude-agent-acp.patch \ + && rm /tmp/claude-agent-acp.patch \ + && npm cache clean --force +USER 1000:1000 # The sandbox pod has a read-only root FS. Writable mounts: $HOME # (/home/paperclip, 1Gi), $HOME/.cache (1Gi), /tmp (2Gi), /workspace. @@ -27,4 +48,7 @@ ENV PATH=/usr/local/go/bin:$PATH \ GOTOOLCHAIN=local # Base user (1000:1000), entrypoint (tini) and cmd (agent shim) are inherited. -RUN go version && golangci-lint version +# The PATH check uses the base image's default PATH, not the ENV above. +RUN go version && golangci-lint version \ + && env -i PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + sh -ec 'for t in go gofmt golangci-lint claude-agent-acp; do command -v "$t"; done' diff --git a/ci/images/agent-runtime-ops/Containerfile b/ci/images/agent-runtime-ops/Containerfile index a9b5f09a..5a105f3e 100644 --- a/ci/images/agent-runtime-ops/Containerfile +++ b/ci/images/agent-runtime-ops/Containerfile @@ -21,8 +21,25 @@ COPY --from=flux /usr/local/bin/flux /usr/local/bin/flux COPY --from=kustomize /app/kustomize /usr/local/bin/kustomize COPY --from=helm /usr/bin/helm /usr/local/bin/helm +# ACP server the Paperclip ACP engine starts in the pod (REN-302). Version and +# patch match what Paperclip ships: packages/adapters/claude-local/package.json +# pins 0.73.0 and applies patches/@agentclientprotocol__claude-agent-acp@0.73.0.patch +# (isolated context, task-tool allow-list, usage metadata). Not bumped by +# Renovate on purpose: bump only together with Paperclip, and replace the patch. +# npm's global prefix is /usr (NodeSource), so the binary lands in /usr/bin. +COPY agent-runtime-common/claude-agent-acp-0.73.0.patch /tmp/claude-agent-acp.patch +USER root +RUN npm install -g @agentclientprotocol/claude-agent-acp@0.73.0 \ + && cd /usr/lib/node_modules/@agentclientprotocol/claude-agent-acp \ + && git apply /tmp/claude-agent-acp.patch \ + && rm /tmp/claude-agent-acp.patch \ + && npm cache clean --force +USER 1000:1000 + # Read-only root FS: kubectl (~/.kube/cache) and helm (~/.cache, ~/.config) # write only under $HOME, which the sandbox pod mounts writable. No ENV needed. # Base user (1000:1000), entrypoint (tini) and cmd (agent shim) are inherited. -RUN kubectl version --client && flux version --client && kustomize version && helm version +RUN kubectl version --client && flux version --client && kustomize version && helm version \ + && env -i PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + sh -ec 'for t in kubectl flux kustomize helm claude-agent-acp; do command -v "$t"; done' -- 2.54.0 From ed6903ae883ad7d60977dda6d48265a360c4158c Mon Sep 17 00:00:00 2001 From: atlas-bot Date: Sun, 27 Sep 2026 21:40:25 +0000 Subject: [PATCH 2/2] fix(ci): claude-agent-acp with Paperclip's SDK override (REN-302) npm install -g ignored Paperclip's pnpm override and pulled claude-agent-sdk 0.3.257. Install from a package.json with the override (0.3.280) into /opt/claude-agent-acp and link the bin into /usr/local/bin. The build fails if the SDK version is not 0.3.280. Co-Authored-By: Claude Opus 5.5 --- README.md | 3 ++- .../claude-agent-acp.package.json | 9 +++++++++ ci/images/agent-runtime-go/Containerfile | 14 ++++++++++---- ci/images/agent-runtime-ops/Containerfile | 14 ++++++++++---- 4 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 ci/images/agent-runtime-common/claude-agent-acp.package.json diff --git a/README.md b/README.md index fbac65a3..23d87b6b 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,8 @@ images run with a read-only root FS. Source in `ci/images/agent-runtime-*`, built by `.woodpecker/build-agent-runtime.yml` (re-run alone: deployment to target `build-agent-runtime`), versions bumped by Renovate. Both also carry `claude-agent-acp` for the ACP engine, pinned and patched like -Paperclip ships it (`ci/images/agent-runtime-common/`); bump it by hand +Paperclip ships it, SDK override included +(`ci/images/agent-runtime-common/`); bump it by hand together with Paperclip. All tools are on the default `PATH`. | Image | `imageRegistry` | Adds | diff --git a/ci/images/agent-runtime-common/claude-agent-acp.package.json b/ci/images/agent-runtime-common/claude-agent-acp.package.json new file mode 100644 index 00000000..7e105f7c --- /dev/null +++ b/ci/images/agent-runtime-common/claude-agent-acp.package.json @@ -0,0 +1,9 @@ +{ + "private": true, + "dependencies": { + "@agentclientprotocol/claude-agent-acp": "0.73.0" + }, + "overrides": { + "@anthropic-ai/claude-agent-sdk": "0.3.280" + } +} diff --git a/ci/images/agent-runtime-go/Containerfile b/ci/images/agent-runtime-go/Containerfile index ec862efa..c8537675 100644 --- a/ci/images/agent-runtime-go/Containerfile +++ b/ci/images/agent-runtime-go/Containerfile @@ -27,12 +27,18 @@ USER 1000:1000 # pins 0.73.0 and applies patches/@agentclientprotocol__claude-agent-acp@0.73.0.patch # (isolated context, task-tool allow-list, usage metadata). Not bumped by # Renovate on purpose: bump only together with Paperclip, and replace the patch. -# npm's global prefix is /usr (NodeSource), so the binary lands in /usr/bin. +# Paperclip also overrides the SDK (package.json pnpm overrides: +# claude-agent-sdk 0.3.280); a plain `npm install -g` ignores that and pulls an +# older SDK. So install from a package.json with the same override into a +# fixed prefix, and link the binary into /usr/local/bin. +COPY agent-runtime-common/claude-agent-acp.package.json /opt/claude-agent-acp/package.json COPY agent-runtime-common/claude-agent-acp-0.73.0.patch /tmp/claude-agent-acp.patch USER root -RUN npm install -g @agentclientprotocol/claude-agent-acp@0.73.0 \ - && cd /usr/lib/node_modules/@agentclientprotocol/claude-agent-acp \ - && git apply /tmp/claude-agent-acp.patch \ +RUN cd /opt/claude-agent-acp \ + && npm install --omit=dev --no-audit --no-fund \ + && git -C node_modules/@agentclientprotocol/claude-agent-acp apply /tmp/claude-agent-acp.patch \ + && npm ls @anthropic-ai/claude-agent-sdk | grep -q 'claude-agent-sdk@0.3.280' \ + && ln -s /opt/claude-agent-acp/node_modules/.bin/claude-agent-acp /usr/local/bin/claude-agent-acp \ && rm /tmp/claude-agent-acp.patch \ && npm cache clean --force USER 1000:1000 diff --git a/ci/images/agent-runtime-ops/Containerfile b/ci/images/agent-runtime-ops/Containerfile index 5a105f3e..14532e37 100644 --- a/ci/images/agent-runtime-ops/Containerfile +++ b/ci/images/agent-runtime-ops/Containerfile @@ -26,12 +26,18 @@ COPY --from=helm /usr/bin/helm /usr/local/bin/helm # pins 0.73.0 and applies patches/@agentclientprotocol__claude-agent-acp@0.73.0.patch # (isolated context, task-tool allow-list, usage metadata). Not bumped by # Renovate on purpose: bump only together with Paperclip, and replace the patch. -# npm's global prefix is /usr (NodeSource), so the binary lands in /usr/bin. +# Paperclip also overrides the SDK (package.json pnpm overrides: +# claude-agent-sdk 0.3.280); a plain `npm install -g` ignores that and pulls an +# older SDK. So install from a package.json with the same override into a +# fixed prefix, and link the binary into /usr/local/bin. +COPY agent-runtime-common/claude-agent-acp.package.json /opt/claude-agent-acp/package.json COPY agent-runtime-common/claude-agent-acp-0.73.0.patch /tmp/claude-agent-acp.patch USER root -RUN npm install -g @agentclientprotocol/claude-agent-acp@0.73.0 \ - && cd /usr/lib/node_modules/@agentclientprotocol/claude-agent-acp \ - && git apply /tmp/claude-agent-acp.patch \ +RUN cd /opt/claude-agent-acp \ + && npm install --omit=dev --no-audit --no-fund \ + && git -C node_modules/@agentclientprotocol/claude-agent-acp apply /tmp/claude-agent-acp.patch \ + && npm ls @anthropic-ai/claude-agent-sdk | grep -q 'claude-agent-sdk@0.3.280' \ + && ln -s /opt/claude-agent-acp/node_modules/.bin/claude-agent-acp /usr/local/bin/claude-agent-acp \ && rm /tmp/claude-agent-acp.patch \ && npm cache clean --force USER 1000:1000 -- 2.54.0