From 6a07eb87c879936041ada4e9bac7006e0770e517 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Jun 2026 18:41:21 +0000 Subject: [PATCH 1/5] Initial plan From 0aa55ed6d32b4440db10837cb2b8b6a1612ebb96 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Jun 2026 18:57:33 +0000 Subject: [PATCH 2/5] refactor(agent-service): extract resolveAgentImageConfig helper Extract image-selection logic from buildAgentService into a standalone resolveAgentImageConfig function (Sub-concern C, ~46 lines). - Isolates the three-way branch (GHCR preset / local build / passthrough) making image-provenance logic straightforward to audit - Reduces buildAgentService from 167 to ~120 lines - Exports resolveAgentImageConfig and testHelpers shim for direct unit testing without constructing a full service object - Adds 6 focused unit tests covering all three branches Closes #5118 --- src/services/agent-service.test.ts | 71 ++++++++++++++++++++++++++++++ src/services/agent-service.ts | 56 ++++++++++++++++------- 2 files changed, 111 insertions(+), 16 deletions(-) diff --git a/src/services/agent-service.test.ts b/src/services/agent-service.test.ts index 063d4d520..3b1b2e72c 100644 --- a/src/services/agent-service.test.ts +++ b/src/services/agent-service.test.ts @@ -496,3 +496,74 @@ describe('agent service', () => { }); }); }); + +import { testHelpers } from './agent-service'; +import { parseImageTag } from '../image-tag'; +import * as nodePath from 'path'; + +const { resolveAgentImageConfig } = testHelpers; + +describe('resolveAgentImageConfig', () => { + const projectRoot = '/fake/project'; + const registry = 'ghcr.io/github/gh-aw-firewall'; + const parsedTag = parseImageTag('latest'); + + const baseImageConfig = { useGHCR: true, registry, parsedTag, projectRoot }; + + it('returns GHCR agent image for default preset', () => { + const result = resolveAgentImageConfig( + { agentImage: 'default', buildLocal: false } as any, + baseImageConfig, + ); + expect(result).toEqual({ image: 'ghcr.io/github/gh-aw-firewall/agent:latest' }); + }); + + it('returns GHCR agent-act image for act preset', () => { + const result = resolveAgentImageConfig( + { agentImage: 'act', buildLocal: false } as any, + baseImageConfig, + ); + expect(result).toEqual({ image: 'ghcr.io/github/gh-aw-firewall/agent-act:latest' }); + }); + + it('returns build config for default preset with --build-local', () => { + const result = resolveAgentImageConfig( + { agentImage: 'default', buildLocal: true } as any, + { ...baseImageConfig, useGHCR: false }, + ) as any; + expect(result.build).toBeDefined(); + expect(result.build.dockerfile).toBe('Dockerfile'); + expect(result.build.context).toBe(nodePath.join(projectRoot, 'containers/agent')); + expect(result.build.args.BASE_IMAGE).toBeUndefined(); + expect(result.image).toBeUndefined(); + }); + + it('returns build config for act preset with --build-local', () => { + const result = resolveAgentImageConfig( + { agentImage: 'act', buildLocal: true } as any, + { ...baseImageConfig, useGHCR: false }, + ) as any; + expect(result.build).toBeDefined(); + expect(result.build.args.BASE_IMAGE).toMatch(/catthehacker/); + }); + + it('returns build config with BASE_IMAGE for custom (non-preset) image', () => { + const result = resolveAgentImageConfig( + { agentImage: 'ubuntu:24.04', buildLocal: false } as any, + { ...baseImageConfig, useGHCR: false }, + ) as any; + expect(result.build).toBeDefined(); + expect(result.build.args.BASE_IMAGE).toBe('ubuntu:24.04'); + }); + + it('returns direct image passthrough when useGHCR is false, buildLocal is false, and preset image is specified', () => { + // Else branch fires when: !useGHCR && !buildLocal && isPreset + // (e.g. user disabled GHCR pull but did not pass --build-local, using the 'default' preset) + const result = resolveAgentImageConfig( + { agentImage: 'default', buildLocal: false } as any, + { ...baseImageConfig, useGHCR: false }, + ) as any; + expect(result.image).toBe('default'); + expect(result.build).toBeUndefined(); + }); +}); diff --git a/src/services/agent-service.ts b/src/services/agent-service.ts index 207f8321a..91f0546b0 100644 --- a/src/services/agent-service.ts +++ b/src/services/agent-service.ts @@ -31,7 +31,6 @@ interface AgentServiceParams { */ export function buildAgentService(params: AgentServiceParams): any { const { config, networkConfig, environment, agentVolumes, dnsServers, imageConfig } = params; - const { useGHCR, registry, parsedTag, projectRoot } = imageConfig; // Agent service configuration const agentService: any = { @@ -147,9 +146,27 @@ export function buildAgentService(params: AgentServiceParams): any { environment.AWF_ENABLE_HOST_ACCESS = '1'; } - // Use GHCR image or build locally - // Priority: GHCR preset images > local build (when requested) > custom images - // For presets ('default', 'act'), use GHCR images + Object.assign(agentService, resolveAgentImageConfig(config, imageConfig)); + + return agentService; +} + +// ─── Image Selection ───────────────────────────────────────────────────────── + +/** + * Resolves the image or build configuration for the agent container. + * + * Priority: GHCR preset images > local build (when requested or non-preset) > custom image passthrough + * + * Returns either `{ image: string }` (pull from registry) or + * `{ build: { context, dockerfile, args } }` (local build), suitable for + * spreading onto a Docker Compose service object. + */ +export function resolveAgentImageConfig( + config: WrapperConfig, + imageConfig: ImageBuildConfig, +): { image: string } | { build: { context: string; dockerfile: string; args?: Record } } { + const { useGHCR, registry, parsedTag, projectRoot } = imageConfig; const agentImage = config.agentImage || 'default'; const isPreset = agentImage === 'default' || agentImage === 'act'; @@ -157,9 +174,12 @@ export function buildAgentService(params: AgentServiceParams): any { // Use pre-built GHCR image for preset images // The GHCR images already have the necessary setup for chroot mode const imageName = agentImage === 'act' ? 'agent-act' : 'agent'; - agentService.image = buildRuntimeImageRef(registry, imageName, parsedTag); - logger.debug(`Using GHCR image ${agentService.image}`); - } else if (config.buildLocal || !isPreset) { + const image = buildRuntimeImageRef(registry, imageName, parsedTag); + logger.debug(`Using GHCR image ${image}`); + return { image }; + } + + if (config.buildLocal || !isPreset) { // Build locally when: // 1. --build-local is explicitly specified, OR // 2. A custom (non-preset) image is specified @@ -184,20 +204,24 @@ export function buildAgentService(params: AgentServiceParams): any { } // For 'default' preset with --build-local, use the Dockerfile's default (ubuntu:22.04) - agentService.build = { - context: path.join(projectRoot, 'containers/agent'), - dockerfile, - args: buildArgs, + return { + build: { + context: path.join(projectRoot, 'containers/agent'), + dockerfile, + args: buildArgs, + }, }; - } else { - // Custom image specified without --build-local - // Use the image directly (user is responsible for ensuring compatibility) - agentService.image = agentImage; } - return agentService; + // Custom image specified without --build-local + // Use the image directly (user is responsible for ensuring compatibility) + return { image: agentImage }; } +// ts-prune-ignore-next +/** @internal Exported for unit testing only */ +export const testHelpers = { resolveAgentImageConfig }; + // ─── iptables-init Service ──────────────────────────────────────────────────── interface IptablesInitServiceParams { From 37f80447d5339aa3aa873fe5ac33b6164575e0a9 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Tue, 16 Jun 2026 14:07:23 -0700 Subject: [PATCH 3/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/services/agent-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/agent-service.ts b/src/services/agent-service.ts index 91f0546b0..8da7a26ec 100644 --- a/src/services/agent-service.ts +++ b/src/services/agent-service.ts @@ -171,7 +171,7 @@ export function resolveAgentImageConfig( const isPreset = agentImage === 'default' || agentImage === 'act'; if (useGHCR && isPreset) { - // Use pre-built GHCR image for preset images + if (useGHCR && isPreset && !config.buildLocal) { // The GHCR images already have the necessary setup for chroot mode const imageName = agentImage === 'act' ? 'agent-act' : 'agent'; const image = buildRuntimeImageRef(registry, imageName, parsedTag); From 87c2a3c9478653a89b7dfd62d595fcdeb2487f15 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Tue, 16 Jun 2026 14:07:32 -0700 Subject: [PATCH 4/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/services/agent-service.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/agent-service.test.ts b/src/services/agent-service.test.ts index 3b1b2e72c..5ff6af4b0 100644 --- a/src/services/agent-service.test.ts +++ b/src/services/agent-service.test.ts @@ -499,8 +499,8 @@ describe('agent service', () => { import { testHelpers } from './agent-service'; import { parseImageTag } from '../image-tag'; -import * as nodePath from 'path'; +const nodePath = path; const { resolveAgentImageConfig } = testHelpers; describe('resolveAgentImageConfig', () => { From 6c3b94afb21a0ef3452dbbdfe68759b3482b2a99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Jun 2026 21:14:54 +0000 Subject: [PATCH 5/5] fix(agent-service): repair syntax and import ordering --- src/services/agent-service.test.ts | 5 ++--- src/services/agent-service.ts | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/services/agent-service.test.ts b/src/services/agent-service.test.ts index 5ff6af4b0..62955db97 100644 --- a/src/services/agent-service.test.ts +++ b/src/services/agent-service.test.ts @@ -1,4 +1,6 @@ import { generateDockerCompose, WrapperConfig, baseConfig, mockNetworkConfig, useTempWorkDir } from './service-test-setup.test-utils'; +import { testHelpers } from './agent-service'; +import { parseImageTag } from '../image-tag'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; @@ -497,9 +499,6 @@ describe('agent service', () => { }); }); -import { testHelpers } from './agent-service'; -import { parseImageTag } from '../image-tag'; - const nodePath = path; const { resolveAgentImageConfig } = testHelpers; diff --git a/src/services/agent-service.ts b/src/services/agent-service.ts index 8da7a26ec..ebef760b7 100644 --- a/src/services/agent-service.ts +++ b/src/services/agent-service.ts @@ -170,7 +170,6 @@ export function resolveAgentImageConfig( const agentImage = config.agentImage || 'default'; const isPreset = agentImage === 'default' || agentImage === 'act'; - if (useGHCR && isPreset) { if (useGHCR && isPreset && !config.buildLocal) { // The GHCR images already have the necessary setup for chroot mode const imageName = agentImage === 'act' ? 'agent-act' : 'agent';