Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
faefd06
feat(acp): integrate ACP harness for agent communication
floreks Aug 28, 2026
b5fdd1c
feat(acp): enhance file read functionality with context checks
floreks Aug 28, 2026
f9b8388
chore(ci): update OPENCODE_VERSION in deployment workflow
floreks Aug 28, 2026
c2f5efd
feat(tool): implement testing and enhance agent mode configuration
floreks Aug 28, 2026
d6bb720
feat(acp): refactor session lifecycle management
floreks Aug 28, 2026
7420b8d
chore(ci): update OPENCODE_VERSION in deployment workflow
floreks Aug 28, 2026
f914b76
feat(acp): enhance tool output processing
floreks Aug 28, 2026
4a85707
Merge remote-tracking branch 'origin/master' into sebastian/prod-5162…
floreks Aug 31, 2026
0992f93
refactor(tool): switch methods to pointer receivers
floreks Aug 31, 2026
70b0b0f
feat(acp): improve session handling and tests
floreks Aug 31, 2026
865bb9f
Merge remote-tracking branch 'origin/master' into sebastian/prod-5162…
floreks Sep 1, 2026
c4996bf
fix(opencode): correct case of `DefaultReviewAgent` return value
floreks Sep 1, 2026
301b105
fix(tests): adjust object creation and correct agent case
floreks Sep 1, 2026
897d9b4
Merge remote-tracking branch 'origin/master' into sebastian/prod-5162…
floreks Sep 2, 2026
a39c0ac
Merge remote-tracking branch 'origin/master' into sebastian/prod-5162…
floreks Sep 3, 2026
70f8980
feat(tool): integrate Agent Client Protocol (ACP) and enhance test co…
floreks Sep 4, 2026
dd0fca5
feat(tool): remove Codex tool implementation and associated tests
floreks Sep 4, 2026
28a5873
refactor(tool): replace `Profile` with `Settings` in Codex configurat…
floreks Sep 7, 2026
195e177
feat(router, session, ci): enhance OpenAI Responses handling, agent s…
floreks Sep 7, 2026
8e94c81
feat(acp): enhance timeout and session workflow handling
floreks Sep 7, 2026
a106123
feat(acp): handle non-blocking FIFO reads and enhance file read logic
floreks Sep 7, 2026
7220f50
feat(tool): add Claude agent implementation and new runtime/test enha…
floreks Sep 8, 2026
2942c98
refactor(tool): simplify Claude configuration and migrate to templates
floreks Sep 8, 2026
b5b497b
chore(ci): update Codex version to 0.153.4 in deployment workflow
floreks Sep 8, 2026
64cb63d
feat(tool): add `IS_SANDBOX` environment variable support for Claude
floreks Sep 8, 2026
79b7fe2
feat(tool): enhance session handling and introduce Gemini agent
floreks Sep 9, 2026
de41437
refactor(tool): replace Gemini model constants with plain strings in …
floreks Sep 9, 2026
cd7ab72
feat(tool): enable conditional filesystem write support based on run …
floreks Sep 9, 2026
25b12ee
feat(acp): restrict file reads to root directory and prevent symlink …
floreks Sep 9, 2026
015dc43
feat(acp): add configurable authentication method support to engine
floreks Sep 10, 2026
feba34c
refactor(tool): update usage resolver and token count logic in Gemini…
floreks Sep 10, 2026
dac9da5
feat(acp): improve tool call handling and session updates
floreks Sep 10, 2026
7cb13cb
fix(acp): handle EOF error gracefully in file read loop
floreks Sep 10, 2026
df64e26
feat(gemini): update to v0.59.0 and adjust session handling
floreks Sep 10, 2026
9b51780
feat(acp): improve tool call recovery and enhance session updates
floreks Sep 10, 2026
7c7a9d6
feat(acp, gemini): enhance tool call input handling and Gemini compat…
floreks Sep 10, 2026
fe74543
refactor(acp, gemini): simplify tool call and runtime handling
floreks Sep 10, 2026
3f66744
feat(gemini): exclude progress-only tools from settings
floreks Sep 11, 2026
c206e43
**refactor(tool): decouple Pi runtime and enhance modularity**
floreks Sep 11, 2026
980c738
refactor(gemini): improve error event handling and variable initializ…
floreks Sep 11, 2026
ef8a85a
refactor(gemini): update tool settings structure, improve tests, and …
floreks Sep 11, 2026
59a76fe
refactor(acp, gemini): improve file handling and update test logic
floreks Sep 11, 2026
575a7ef
refactor(acp, gemini): improve file handling, tool runtime, and tests
floreks Sep 11, 2026
4f7544e
refactor(gemini, acp): update runtime structure, transport logic, and…
floreks Sep 11, 2026
dfe7a10
revert: next round revert for remaining gemini changes
floreks Sep 11, 2026
ec90211
chore(claude): update default claude model
floreks Sep 11, 2026
6c529e6
refactor(claude): update template and tests for model and settings ch…
floreks Sep 11, 2026
a6a44e7
chore: update Claude model version to `claude-sonnet-4-6`
floreks Sep 11, 2026
5a4f07c
chore: update test to use `claude-sonnet-4-6` model reference
floreks Sep 11, 2026
727c8ea
chore(tests): update expected model to `claude-sonnet-4-6`
floreks Sep 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/deployment-operator-cd-agent-harness.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ jobs:
runs-on: ubuntu-latest
env:
NODE_VERSION: 24.11.1
CLAUDE_VERSION: 2.1.72
CLAUDE_VERSION: 2.1.236
GEMINI_VERSION: 0.44.1
OPENCODE_VERSION: 1.17.3
CODEX_VERSION: 0.104.0
OPENCODE_VERSION: 1.18.23
CODEX_VERSION: 0.153.4
PI_VERSION: 0.84.1
outputs:
node: ${{ env.NODE_VERSION }}
Expand Down Expand Up @@ -179,13 +179,13 @@ jobs:
matrix:
agents:
- name: claude
version: 2.1.72
version: 2.1.236
- name: gemini
version: 0.44.1
- name: opencode
version: 1.17.3
version: 1.18.23
- name: codex
version: 0.104.0
version: 0.153.4
- name: pi
version: 0.84.1
permissions:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,44 @@
ARG AGENT_VERSION=latest
ARG AGENT_VERSION=2.1.236
ARG ACP_VERSION=0.75.1

ARG AGENT_HARNESS_BASE_IMAGE_TAG=latest
ARG AGENT_HARNESS_BASE_IMAGE_REPO=ghcr.io/pluralsh/agent-harness-base
ARG AGENT_HARNESS_BASE_IMAGE=$AGENT_HARNESS_BASE_IMAGE_REPO:$AGENT_HARNESS_BASE_IMAGE_TAG

# Stage 1: Install Claude Code native binary (no npm — postinstall is unreliable in CI)
FROM debian:13-slim AS claude-install
# Stage 1: Install Claude Code and its ACP adapter together.
FROM node:26-bookworm-slim AS claude-install

ARG AGENT_VERSION
ARG ACP_VERSION

RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
&& arch="$(dpkg --print-architecture)" \
&& case "$arch" in \
amd64) platform=linux-x64 ;; \
arm64) platform=linux-arm64 ;; \
*) echo "unsupported architecture: $arch" >&2; exit 1 ;; \
esac \
&& if [ "$AGENT_VERSION" = "latest" ]; then \
version="$(curl -fsSL https://downloads.claude.ai/claude-code-releases/latest)"; \
else \
version="$AGENT_VERSION"; \
fi \
&& curl -fsSL \
"https://downloads.claude.ai/claude-code-releases/${version}/${platform}/claude" \
-o /usr/local/bin/claude \
&& chmod +x /usr/local/bin/claude \
&& /usr/local/bin/claude --version \
&& apt-get purge -y curl \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*

# Stage 2: Copy claude binary into agent-harness base
RUN npm install --global \
"@anthropic-ai/claude-code@${AGENT_VERSION}" \
"@agentclientprotocol/claude-agent-acp@${ACP_VERSION}" && \
claude --version && \
claude-agent-acp --version

# Stage 2: Copy Claude and its ACP adapter into agent-harness base.
FROM $AGENT_HARNESS_BASE_IMAGE AS final

COPY --from=claude-install /usr/local/bin/claude /usr/local/bin/claude
COPY --from=claude-install /usr/local/lib/node_modules /usr/local/lib/node_modules
COPY --from=claude-install /usr/local/bin/node /usr/local/bin/node

USER root
RUN chown 65532:65532 /usr/local/bin/claude
# The Node runtime from the install stage links libatomic dynamically, while
# the minimal harness base does not include it.
RUN apt-get update && apt-get install -y --no-install-recommends libatomic1 && \
rm -rf /var/lib/apt/lists/*

# Recreate npm launcher symlinks after copying their packages.
RUN ln -s ../lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe /usr/local/bin/claude && \
ln -s ../lib/node_modules/@agentclientprotocol/claude-agent-acp/dist/index.js /usr/local/bin/claude-agent-acp && \
chown -R 65532:65532 /usr/local/bin/claude /usr/local/bin/claude-agent-acp /usr/local/bin/node /usr/local/lib/node_modules

USER 65532:65532

# Verify the binary runs in the final image (same user and PATH as runtime)
RUN claude --version
RUN claude-agent-acp --version

# The entrypoint remains the agent-harness binary
# The agent-harness will call the claude CLI as needed
# The agent-harness launches claude-agent-acp, which uses the pinned Claude CLI.
48 changes: 17 additions & 31 deletions go/deployment-operator/dockerfiles/agent-harness/codex.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,57 +1,43 @@
ARG NODE_IMAGE_TAG=24
ARG NODE_IMAGE=node:${NODE_IMAGE_TAG}-slim
ARG AGENT_VERSION=0.104.0
ARG AGENT_VERSION=0.153.4
ARG ACP_VERSION=1.10.0

ARG AGENT_HARNESS_BASE_IMAGE_TAG=latest
ARG AGENT_HARNESS_BASE_IMAGE_REPO=ghcr.io/pluralsh/agent-harness-base
ARG AGENT_HARNESS_BASE_IMAGE=$AGENT_HARNESS_BASE_IMAGE_REPO:$AGENT_HARNESS_BASE_IMAGE_TAG

# Stage 1: Install Codex CLI from npm in the Node image and flatten dependencies
# Stage 1: Install pinned Codex ACP adapter and native Codex binaries.
FROM $NODE_IMAGE AS node

USER root

# Install codex CLI globally using npm
RUN npm install -g @openai/codex@0.104.0

# The codex script uses createRequire(import.meta.url) anchored at /usr/local/bin/codex.
# Node's module resolution walks up from /usr/local/bin/ and won't find node_modules
# until it reaches a NODE_PATH entry. The native package (@openai/codex-linux-x64)
# is installed nested inside @openai/codex/node_modules/ — hard-copy it up to the
# top-level @openai scope so NODE_PATH=/usr/local/lib/node_modules can find it.
RUN NESTED="/usr/local/lib/node_modules/@openai/codex/node_modules/@openai" && \
if [ -d "$NESTED" ]; then \
for pkg in "$NESTED"/codex-*; do \
pkgname=$(basename "$pkg"); \
cp -rL "$pkg" "/usr/local/lib/node_modules/@openai/$pkgname"; \
done; \
fi
ARG ACP_VERSION
ARG AGENT_VERSION
RUN npm install -g "@agentclientprotocol/codex-acp@$ACP_VERSION" "@openai/codex@$AGENT_VERSION"

# Verify installation
RUN codex --version
RUN codex-acp --version

# Stage 2: Copy codex CLI into agent-harness base
# Stage 2: Copy Codex ACP adapter and native Codex into agent-harness base
FROM $AGENT_HARNESS_BASE_IMAGE AS final

# Copy the codex CLI from the Node.js image
COPY --from=node /usr/local/bin/codex /usr/local/bin/codex
# Copy the entire @openai scope — now contains both @openai/codex (with its
# nested node_modules) and the promoted top-level @openai/codex-linux-x64 copy
COPY --from=node /usr/local/lib/node_modules/@openai /usr/local/lib/node_modules/@openai
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules

# Copy Node.js runtime (needed to run the CLI)
# Copy the Node.js runtime needed by the adapter.
COPY --from=node /usr/local/bin/node /usr/local/bin/node

# NODE_PATH lets require.resolve() in the codex ESM script find
# /usr/local/lib/node_modules/@openai/codex-linux-x64 at runtime
ENV NODE_PATH=/usr/local/lib/node_modules

# Ensure proper ownership for nonroot user
USER root
RUN chown -R 65532:65532 /usr/local/bin/codex /usr/local/lib/node_modules/@openai /usr/local/bin/node
# COPY dereferences npm launcher symlinks, so recreate them in the final image.
RUN ln -s ../lib/node_modules/@agentclientprotocol/codex-acp/dist/index.js /usr/local/bin/codex-acp && \
ln -s ../lib/node_modules/@openai/codex/bin/codex.js /usr/local/bin/codex && \
chown -R 65532:65532 /usr/local/bin/codex-acp /usr/local/bin/codex /usr/local/lib/node_modules /usr/local/bin/node

ENV CODEX_PATH=/usr/local/bin/codex

# Switch back to nonroot user
USER 65532:65532

# The entrypoint remains the agent-harness binary
# The agent-harness will call the codex CLI as needed
# The agent-harness launches codex-acp directly.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ARG NODE_IMAGE_TAG=24
ARG NODE_IMAGE=node:${NODE_IMAGE_TAG}-slim
ARG AGENT_VERSION=1.17.3
ARG AGENT_VERSION=1.18.23

ARG AGENT_HARNESS_BASE_IMAGE_TAG=latest
ARG AGENT_HARNESS_BASE_IMAGE_REPO=ghcr.io/pluralsh/agent-harness-base
Expand Down
12 changes: 9 additions & 3 deletions go/deployment-operator/dockerfiles/agent-harness/pi.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
ARG NODE_IMAGE_TAG=24
ARG NODE_IMAGE=node:${NODE_IMAGE_TAG}-slim
ARG AGENT_VERSION=0.84.1
ARG ACP_VERSION=0.0.33
ARG MCP_ADAPTER_VERSION=2.21.2

ARG AGENT_HARNESS_BASE_IMAGE_TAG=latest
Expand All @@ -10,12 +11,16 @@ ARG AGENT_HARNESS_BASE_IMAGE=$AGENT_HARNESS_BASE_IMAGE_REPO:$AGENT_HARNESS_BASE_
FROM $NODE_IMAGE AS node

ARG AGENT_VERSION
ARG ACP_VERSION
ARG MCP_ADAPTER_VERSION

USER root
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent@${AGENT_VERSION} && \
RUN npm install -g --ignore-scripts \
@earendil-works/pi-coding-agent@${AGENT_VERSION} \
pi-acp@${ACP_VERSION} && \
npm install --ignore-scripts --prefix /opt/pi-mcp-adapter pi-mcp-adapter@${MCP_ADAPTER_VERSION} && \
pi --version
pi --version && \
pi-acp --version

FROM $AGENT_HARNESS_BASE_IMAGE AS final

Expand All @@ -27,7 +32,8 @@ ENV NODE_PATH=/usr/local/lib/node_modules

USER root
RUN ln -s ../lib/node_modules/@earendil-works/pi-coding-agent/dist/cli.js /usr/local/bin/pi && \
chown -R 65532:65532 /usr/local/bin/node /usr/local/lib/node_modules /opt/pi-mcp-adapter
ln -s ../lib/node_modules/pi-acp/dist/index.js /usr/local/bin/pi-acp && \
chown -R 65532:65532 /usr/local/bin/node /usr/local/bin/pi /usr/local/bin/pi-acp /usr/local/lib/node_modules /opt/pi-mcp-adapter
USER 65532:65532

# The base entrypoint execs /agent-harness as PID 1; verify that the active harness process remains alive.
Expand Down
1 change: 1 addition & 0 deletions go/deployment-operator/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ require (
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.24
github.com/aws/aws-sdk-go-v2/service/eks v1.81.2
github.com/cert-manager/cert-manager v1.19.3
github.com/coder/acp-go-sdk v0.13.5
github.com/cyphar/filepath-securejoin v0.6.1
github.com/evanphx/json-patch/v5 v5.9.11
github.com/ewhauser/gomonty v0.0.14
Expand Down
2 changes: 2 additions & 0 deletions go/deployment-operator/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/coder/acp-go-sdk v0.13.5 h1:LI9jq5xon7xslaYlnoktvTVyDlE37yIk2daT7N9ASYk=
github.com/coder/acp-go-sdk v0.13.5/go.mod h1:yKzM/3R9uELp4+nBAwwtkS0aN1FOFjo11CNPy37yFko=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/containerd/containerd v1.7.33 h1:iAkYGC/ifR/V+0eR4iXWHNGYUF0DF2PmGV5iz4Irj5M=
Expand Down
6 changes: 3 additions & 3 deletions go/deployment-operator/internal/controller/agentrun_pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ var (

// Check .github/workflows/deployment-operator-cd-agent-harness.yaml to see images being published.
defaultContainerVersions = map[console.AgentRuntimeType]string{
console.AgentRuntimeTypeClaude: "%s-claude-2.1.72",
console.AgentRuntimeTypeClaude: "%s-claude-2.1.236",
console.AgentRuntimeTypeGemini: "%s-gemini-0.44.1",
console.AgentRuntimeTypeOpencode: "%s-opencode-1.17.3",
console.AgentRuntimeTypeCodex: "%s-codex-0.104.0",
console.AgentRuntimeTypeOpencode: "%s-opencode-1.18.23",
console.AgentRuntimeTypeCodex: "%s-codex-0.153.4",
console.AgentRuntimeTypePi: "%s-pi-0.84.1",
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,11 @@ func (ar *AgentRun) fromEnv(runtime *console.AgentRuntimeFragment) *AgentRuntime
}

func (ar *AgentRun) IsProxyEnabled() bool {
return ar.Runtime != nil && ar.Runtime.AiProxy
if ar == nil || ar.Runtime == nil {
return false
}

return ar.Runtime.AiProxy
}

func (ar *AgentRun) IsStreamingProxyEnabled() bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,51 @@ func TestHandleToolOutputStreamsStdout(t *testing.T) {
}, "call-1")
}

func TestHandleAgentMessageCorrelatesRecoveredTerminalToolCall(t *testing.T) {
t.Parallel()

m := mocks.NewClientMock(t)
m.On("CreateAgentMessage", mock.Anything, "run-1", mock.MatchedBy(func(attrs gqlclient.AgentMessageAttributes) bool {
return attrs.Metadata != nil && attrs.Metadata.Tool != nil && attrs.Metadata.Tool.State != nil &&
*attrs.Metadata.Tool.State == gqlclient.AgentMessageToolStateRunning
})).Return(&gqlclient.CreateAgentMessage_CreateAgentMessage{ID: "msg-1", Message: "Called tool"}, nil).Once()
m.On("AgentMessageOutput", mock.Anything, mock.MatchedBy(func(attrs gqlclient.AgentMessageOutputAttributes) bool {
return attrs.MessageID == "msg-1" && attrs.Stdout != nil && *attrs.Stdout == "done"
})).Return(nil).Once()
m.On("UpdateAgentMessage", mock.Anything, "msg-1", mock.MatchedBy(func(attrs gqlclient.AgentMessageAttributes) bool {
return attrs.Metadata != nil && attrs.Metadata.Tool != nil && attrs.Metadata.Tool.State != nil &&
*attrs.Metadata.Tool.State == gqlclient.AgentMessageToolStateCompleted
})).Return(&gqlclient.UpdateAgentMessage_UpdateAgentMessage{ID: "msg-1", Message: "Called tool"}, nil).Once()

in := &agentRunController{
agentRunID: "run-1",
consoleClient: m,
toolCallMessageIDs: map[string]string{},
output: output.New(t.Context(), m).WithSizeLimit(1024).WithFlushInterval(time.Hour),
}
callID := "call-1"
in.handleAgentMessage(t.Context(), &gqlclient.AgentMessageAttributes{
Role: gqlclient.AiRoleAssistant,
Message: "Called tool",
Metadata: &gqlclient.AgentMessageMetadataAttributes{Tool: &gqlclient.AgentMessageToolAttributes{
State: lo.ToPtr(gqlclient.AgentMessageToolStateRunning),
Output: lo.ToPtr(v1.RunningToolOutput),
}},
}, callID)
in.handleToolOutput(callID, "done")
in.handleAgentMessage(t.Context(), &gqlclient.AgentMessageAttributes{
Role: gqlclient.AiRoleAssistant,
Message: "Called tool",
Metadata: &gqlclient.AgentMessageMetadataAttributes{Tool: &gqlclient.AgentMessageToolAttributes{
State: lo.ToPtr(gqlclient.AgentMessageToolStateCompleted),
Output: lo.ToPtr("done"),
}},
}, callID)

_, tracked := in.toolCallMessageID(callID)
require.False(t, tracked)
}

func TestHandleAgentMessageKeepsOutputOpenWhenTerminalUpdateFails(t *testing.T) {
t.Parallel()

Expand Down
Loading
Loading