From 0113f1541ec002a7bff70e89372d158464ce8dd1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 16:02:17 +0000 Subject: [PATCH 1/2] Add experiment output hooks and logging --- packages/agent-eval/src/cli.ts | 50 ++++ packages/agent-eval/src/run.ts | 402 +++++++++++++++++---------- packages/agent-eval/src/treatment.ts | 2 + packages/sandbox/src/index.ts | 60 +++- 4 files changed, 356 insertions(+), 158 deletions(-) diff --git a/packages/agent-eval/src/cli.ts b/packages/agent-eval/src/cli.ts index af568a1..3b30aeb 100644 --- a/packages/agent-eval/src/cli.ts +++ b/packages/agent-eval/src/cli.ts @@ -32,6 +32,10 @@ const {values} = parseArgs({ short: 'e', description: 'The file name of the experiment to run', }, + 'show-output': { + type: 'boolean', + description: 'Show stdout and stderr from experiment runs, prefixed with experiment context', + }, }, }) @@ -41,6 +45,7 @@ const MAX_CONCURRENCY = Number.isFinite(parsedConcurrency) && Number.isInteger(parsedConcurrency) && parsedConcurrency >= 1 ? parsedConcurrency : 1 +const SHOW_OUTPUT = values['show-output'] ?? false const experimentConfigs: Array = [] if (!existsSync(ARTIFACTS_DIR)) { @@ -181,6 +186,29 @@ function formatNumber(value: number): string { return new Intl.NumberFormat('en-US').format(value) } +function getTreatmentPrefix(treatment: Treatment): string { + return [ + treatment.experiment.name, + treatment.config.name, + treatment.eval.id, + treatment.model, + ].join(' | ') +} + +function writePrefixedOutput(prefix: string, chunk: string) { + const lines = chunk.split('\n') + const output = lines + .map((line, index) => { + if (line === '' && index === lines.length - 1) { + return '' + } + return `${prefix}${line}` + }) + .join('\n') + + process.stdout.write(output) +} + type TableRow = Record function formatTable(rows: Array, columns: Array): string { @@ -387,6 +415,28 @@ for (const config of experimentConfigs) { artifactsDirectory: ARTIFACTS_DIR, copilotToken: COPILOT_GITHUB_TOKEN, maxConcurrency: MAX_CONCURRENCY, + onEvent(event) { + if (event.type === 'progress') { + console.log( + `Progress: ${event.completed}/${event.total} completed, ${event.running} running, ${event.remaining} left`, + ) + return + } + + if (event.type === 'log') { + const prefix = event.treatment ? `[${getTreatmentPrefix(event.treatment)}]` : `[${config.name}]` + if (event.level === 'error') { + console.error(`${prefix} ${event.message}`) + } else if (SHOW_OUTPUT) { + console.log(`${prefix} ${event.message}`) + } + return + } + + if (SHOW_OUTPUT) { + writePrefixedOutput(`[${getTreatmentPrefix(event.treatment)} | ${event.stream}] `, event.chunk) + } + }, }) results.push(...runResults) } diff --git a/packages/agent-eval/src/run.ts b/packages/agent-eval/src/run.ts index 2fa7414..b47c89c 100644 --- a/packages/agent-eval/src/run.ts +++ b/packages/agent-eval/src/run.ts @@ -10,15 +10,49 @@ type RunOptions = { artifactsDirectory: string copilotToken: string maxConcurrency?: number + onEvent?: (event: RunEvent) => void } +type RunEvent = + | { + type: 'progress' + completed: number + remaining: number + running: number + total: number + } + | { + type: 'log' + level: 'info' | 'error' + message: string + treatment?: Treatment + } + | { + type: 'output' + chunk: string + stream: 'stdout' | 'stderr' + treatment: Treatment + } + function run(treatments: Array, options: RunOptions): Promise> { const maxConcurrency = options.maxConcurrency ?? 1 const queue = treatments.slice() const results: Array = [] - const pending = new Set() + const pending = new Set>() + const total = treatments.length + let completed = 0 let cancelled = false + function emitProgress() { + options.onEvent?.({ + type: 'progress', + completed, + running: pending.size, + remaining: total - completed - pending.size, + total, + }) + } + let resolve: (value: Array) => void let reject: (reason: unknown) => void const deferred = new Promise>((_resolve, _reject) => { @@ -33,6 +67,7 @@ function run(treatments: Array, options: RunOptions): Promise, options: RunOptions): Promise { results.push(result) pending.delete(promise) + completed += 1 + emitProgress() execute() }, error => { cancelled = true pending.delete(promise) + options.onEvent?.({ + type: 'log', + level: 'error', + treatment, + message: `Treatment failed: ${formatError(error)}`, + }) reject(error) }, ) pending.add(promise) + emitProgress() execute() } @@ -76,188 +123,247 @@ function run(treatments: Array, options: RunOptions): Promise(fn: () => Promise, retries: number): Promise { +async function retry( + fn: () => Promise, + retries: number, + onEvent: RunOptions['onEvent'], + treatment: Treatment, +): Promise { try { return await fn() } catch (error) { if (retries > 0) { - console.log('Retrying after error: %s', error) - return retry(fn, retries - 1) + onEvent?.({ + type: 'log', + level: 'error', + treatment, + message: `Retrying after error: ${formatError(error)}`, + }) + return retry(fn, retries - 1, onEvent, treatment) } throw error } } +function formatError(error: unknown): string { + if (error instanceof Error) { + return error.message + } + return String(error) +} + type RunTreatmentOptions = { artifactsDirectory: string copilotToken: string + onEvent?: (event: RunEvent) => void } async function runTreatment( treatment: Treatment, - {artifactsDirectory, copilotToken}: RunTreatmentOptions, + {artifactsDirectory, copilotToken, onEvent}: RunTreatmentOptions, ): Promise { - console.log('Running treatment: %s (%s)', treatment.config.name, treatment.id) - await using sandbox = await Sandbox.create() + const artifactDirectory = path.join(artifactsDirectory, treatment.id) + const stderrLogPath = path.join(artifactDirectory, 'stderr.log') + const stdoutLogPath = path.join(artifactDirectory, 'stdout.log') + let stderrLog = '' + let stdoutLog = '' + + function emitLog(level: 'info' | 'error', message: string) { + onEvent?.({ + type: 'log', + level, + message, + treatment, + }) + } - console.log('Copying files from: %s...', treatment.eval.directory) - await sandbox.copy(treatment.eval.directory, CONTAINER_WORKDIR, { - exclude: ['eval.config.ts', 'eval.test.ts', 'node_modules', '.next'], - }) - await sandbox.runCommand('chown', ['-R', NODE_USER, '.'], { - user: 'root', - }) + const onOutput = (event: {chunk: string; stream: 'stdout' | 'stderr'}) => { + if (event.stream === 'stdout') { + stdoutLog += event.chunk + } else { + stderrLog += event.chunk + } - console.log('Obfuscating package name...') - await sandbox.runCommand('npm', ['pkg', 'set', `name=${treatment.id}`], { - user: NODE_USER, - }) + onEvent?.({ + type: 'output', + treatment, + stream: event.stream, + chunk: event.chunk, + }) + } - console.log('Removing workspace dependency...') - await sandbox.runCommand('npm', ['pkg', 'delete', 'devDependencies.@primer/agent-eval'], { - user: NODE_USER, - }) + await fs.mkdir(artifactDirectory, {recursive: true}) - console.log('Installing dependencies...') - await sandbox.runCommand('npm', ['install'], { - user: NODE_USER, - }) + try { + emitLog('info', `Running treatment: ${treatment.config.name} (${treatment.id})`) + await using sandbox = await Sandbox.create({ + onOutput, + }) + + emitLog('info', `Copying files from: ${treatment.eval.directory}...`) + await sandbox.copy(treatment.eval.directory, CONTAINER_WORKDIR, { + exclude: ['eval.config.ts', 'eval.test.ts', 'node_modules', '.next'], + }) + await sandbox.runCommand('chown', ['-R', NODE_USER, '.'], { + user: 'root', + }) + + emitLog('info', 'Obfuscating package name...') + await sandbox.runCommand('npm', ['pkg', 'set', `name=${treatment.id}`], { + user: NODE_USER, + }) - console.log('Running treatment setup...') - await treatment.config.setup?.({ - sandbox, - }) + emitLog('info', 'Removing workspace dependency...') + await sandbox.runCommand('npm', ['pkg', 'delete', 'devDependencies.@primer/agent-eval'], { + user: NODE_USER, + }) - console.log('Run build script...') - await sandbox.runCommand('npm', ['run', 'build', '--if-present'], { - user: NODE_USER, - }) + emitLog('info', 'Installing dependencies...') + await sandbox.runCommand('npm', ['install'], { + user: NODE_USER, + }) - console.log('Running copilot...') - const {prompt} = treatment.eval.config - const args = [ - '-p', - prompt, - '--model', - treatment.model, - '--allow-all', - '--reasoning-effort', - 'high', - '--mode', - 'autopilot', - '--output-format', - 'json', - ] - const copilotOutput = await sandbox.runCommand('copilot', args, { - user: NODE_USER, - env: { - COPILOT_GITHUB_TOKEN: copilotToken, - }, - }) - const messages: Array = copilotOutput.stdout.split('\n').flatMap(line => { - const trimmed = line.trim() - if (trimmed.length === 0) { - return [] - } - const result = parseMessage(JSON.parse(trimmed)) - if (result.success) { - return result.data - } - console.log('Failed to parse copilot message: %s', line) - return [] - }) + emitLog('info', 'Running treatment setup...') + await treatment.config.setup?.({ + sandbox, + }) - const TEST_PATH = 'eval.test.ts' - await sandbox.copy(treatment.eval.testPath, TEST_PATH) - // Always pass vitest calls even if test suite fails - await sandbox.runCommand( - 'sh', - ['-c', 'npx vitest run "$1" --reporter json --outputFile test-results.json || true', 'vitest-run', TEST_PATH], - { + emitLog('info', 'Run build script...') + await sandbox.runCommand('npm', ['run', 'build', '--if-present'], { user: NODE_USER, - }, - ) + }) + + emitLog('info', 'Running copilot...') + const {prompt} = treatment.eval.config + const args = [ + '-p', + prompt, + '--model', + treatment.model, + '--allow-all', + '--reasoning-effort', + 'high', + '--mode', + 'autopilot', + '--output-format', + 'json', + ] + const copilotOutput = await sandbox.runCommand('copilot', args, { + user: NODE_USER, + env: { + COPILOT_GITHUB_TOKEN: copilotToken, + }, + }) + const messages: Array = copilotOutput.stdout.split('\n').flatMap(line => { + const trimmed = line.trim() + if (trimmed.length === 0) { + return [] + } + const result = parseMessage(JSON.parse(trimmed)) + if (result.success) { + return result.data + } + emitLog('error', `Failed to parse copilot message: ${line}`) + return [] + }) + + const TEST_PATH = 'eval.test.ts' + await sandbox.copy(treatment.eval.testPath, TEST_PATH) + // Always pass vitest calls even if test suite fails + await sandbox.runCommand( + 'sh', + ['-c', 'npx vitest run "$1" --reporter json --outputFile test-results.json || true', 'vitest-run', TEST_PATH], + { + user: NODE_USER, + }, + ) - const testResultsContent = await sandbox.readFile('test-results.json') - const testResults = parseTestResults(JSON.parse(testResultsContent)) - if (!testResults.success) { - throw new Error(`Failed to parse test results: ${testResults.error}`) - } + const testResultsContent = await sandbox.readFile('test-results.json') + const testResults = parseTestResults(JSON.parse(testResultsContent)) + if (!testResults.success) { + emitLog('error', `Failed to parse test results: ${testResults.error}`) + throw new Error(`Failed to parse test results: ${testResults.error}`) + } - // Turns - const assistantTurns = new Set() - // Tools - const toolCalls = new Map() - let outputTokens = 0 + // Turns + const assistantTurns = new Set() + // Tools + const toolCalls = new Map() + let outputTokens = 0 - for (const message of messages) { - if (message.type === 'assistant.turn_start') { - assistantTurns.add(message.data.turnId) - } + for (const message of messages) { + if (message.type === 'assistant.turn_start') { + assistantTurns.add(message.data.turnId) + } - if (message.type === 'assistant.message') { - outputTokens += message.data.outputTokens - } + if (message.type === 'assistant.message') { + outputTokens += message.data.outputTokens + } - if (message.type === 'tool.execution_start') { - const toolName = message.data.toolName - toolCalls.set(toolName, (toolCalls.get(toolName) ?? 0) + 1) + if (message.type === 'tool.execution_start') { + const toolName = message.data.toolName + toolCalls.set(toolName, (toolCalls.get(toolName) ?? 0) + 1) + } } - } - const result = messages.find((message): message is Extract => { - return message.type === 'result' - }) - if (!result) { - throw new Error('No result message found in copilot output') - } + const result = messages.find((message): message is Extract => { + return message.type === 'result' + }) + if (!result) { + emitLog('error', 'No result message found in copilot output') + throw new Error('No result message found in copilot output') + } - const artifactDirectory = path.join(artifactsDirectory, treatment.id) - const workspacePath = path.join(artifactDirectory, 'workspace') - const copilotConfigPath = path.join(artifactDirectory, '.copilot') - const skillsConfigPath = path.join(artifactDirectory, '.agents') - const testResultsPath = path.join(workspacePath, 'test-results.json') - await fs.mkdir(workspacePath, {recursive: true}) - - console.log('Downloading agent workspace to: %s...', workspacePath) - await sandbox.download(CONTAINER_WORKDIR, workspacePath, { - ignore(name) { - return name.includes('node_modules') || name.includes('.next') - }, - }) + const workspacePath = path.join(artifactDirectory, 'workspace') + const copilotConfigPath = path.join(artifactDirectory, '.copilot') + const skillsConfigPath = path.join(artifactDirectory, '.agents') + const testResultsPath = path.join(workspacePath, 'test-results.json') + await fs.mkdir(workspacePath, {recursive: true}) - console.log('Downloading copilot config to: %s...', copilotConfigPath) - await sandbox.download(COPILOT_DIR, copilotConfigPath) - - console.log('Downloading skills config to: %s...', skillsConfigPath) - await sandbox.download(AGENTS_DIR, skillsConfigPath) - - return { - id: randomUUID(), - treatment, - artifacts: { - directory: artifactDirectory, - copilotConfigPath, - skillsConfigPath, - testResultsPath, - workspacePath, - }, - assistant: { - turns: assistantTurns.size, - outputTokens, - premiumRequests: result.usage.premiumRequests, - // Time to complete (latency) - totalApiDurationMs: result.usage.totalApiDurationMs, - sessionDurationMs: result.usage.sessionDurationMs, - tools: Object.fromEntries(toolCalls), - }, - testResults: { - numFailedTests: testResults.data.numFailedTests, - numPassedTests: testResults.data.numPassedTests, - numPendingTests: testResults.data.numPendingTests, - numTodoTests: testResults.data.numTodoTests, - numTotalTests: testResults.data.numTotalTests, - }, + emitLog('info', `Downloading agent workspace to: ${workspacePath}...`) + await sandbox.download(CONTAINER_WORKDIR, workspacePath, { + ignore(name) { + return name.includes('node_modules') || name.includes('.next') + }, + }) + + emitLog('info', `Downloading copilot config to: ${copilotConfigPath}...`) + await sandbox.download(COPILOT_DIR, copilotConfigPath) + + emitLog('info', `Downloading skills config to: ${skillsConfigPath}...`) + await sandbox.download(AGENTS_DIR, skillsConfigPath) + + return { + id: randomUUID(), + treatment, + artifacts: { + directory: artifactDirectory, + copilotConfigPath, + stderrLogPath, + stdoutLogPath, + skillsConfigPath, + testResultsPath, + workspacePath, + }, + assistant: { + turns: assistantTurns.size, + outputTokens, + premiumRequests: result.usage.premiumRequests, + // Time to complete (latency) + totalApiDurationMs: result.usage.totalApiDurationMs, + sessionDurationMs: result.usage.sessionDurationMs, + tools: Object.fromEntries(toolCalls), + }, + testResults: { + numFailedTests: testResults.data.numFailedTests, + numPassedTests: testResults.data.numPassedTests, + numPendingTests: testResults.data.numPendingTests, + numTodoTests: testResults.data.numTodoTests, + numTotalTests: testResults.data.numTotalTests, + }, + } + } finally { + await Promise.all([fs.writeFile(stderrLogPath, stderrLog), fs.writeFile(stdoutLogPath, stdoutLog)]) } } diff --git a/packages/agent-eval/src/treatment.ts b/packages/agent-eval/src/treatment.ts index 64d92d2..b19fe24 100644 --- a/packages/agent-eval/src/treatment.ts +++ b/packages/agent-eval/src/treatment.ts @@ -15,6 +15,8 @@ type TreatmentResult = { artifacts: { copilotConfigPath: string directory: string + stderrLogPath: string + stdoutLogPath: string skillsConfigPath: string testResultsPath: string workspacePath: string diff --git a/packages/sandbox/src/index.ts b/packages/sandbox/src/index.ts index e176da8..464fef2 100644 --- a/packages/sandbox/src/index.ts +++ b/packages/sandbox/src/index.ts @@ -58,6 +58,11 @@ type RunOptions = { env?: Record user?: string allowNonZeroExitCode?: boolean + onOutput?: (event: CommandOutputEvent) => void +} + +type CreateOptions = { + onOutput?: (event: CommandOutputEvent) => void } type CopyOptions = { @@ -73,18 +78,20 @@ const DEFAULT_MCP_CONFIG: McpConfigFile = { } class Sandbox { - static async create() { + static async create(options: CreateOptions = {}) { const docker = new Docker() - const container = await createContainer(docker) - return new Sandbox(docker, container) + const container = await createContainer(docker, options) + return new Sandbox(docker, container, options) } #docker: Docker #container: Docker.Container + #onOutput?: (event: CommandOutputEvent) => void - constructor(docker: Docker, container: InitializedContainer) { + constructor(docker: Docker, container: InitializedContainer, options: CreateOptions = {}) { this.#docker = docker this.#container = container + this.#onOutput = options.onOutput } async [Symbol.asyncDispose]() { @@ -210,6 +217,7 @@ class Sandbox { }, user: options?.user ?? NODE_USER, allowNonZeroExitCode: options?.allowNonZeroExitCode, + onOutput: options?.onOutput ?? this.#onOutput, }) } @@ -268,7 +276,7 @@ type InitializedContainer = Docker.Container & { readonly [INITIALIZED_CONTAINER]?: true } -async function createContainer(docker: Docker): Promise { +async function createContainer(docker: Docker, options: CreateOptions = {}): Promise { await pullImage(docker, 'node:24-slim') const container = await docker.createContainer({ @@ -286,53 +294,67 @@ async function createContainer(docker: Docker): Promise { console.log('Creating workspace directory...') await execCommand(docker, container, 'mkdir', ['-p', CONTAINER_WORKDIR], { user: 'root', + onOutput: options.onOutput, }) await execCommand(docker, container, 'chown', ['-R', NODE_USER, CONTAINER_WORKDIR], { user: 'root', + onOutput: options.onOutput, }) console.log('Installing CA certificates...') await execCommand(docker, container, 'apt-get', ['update'], { user: 'root', + onOutput: options.onOutput, }) await execCommand(docker, container, 'apt-get', ['install', '-y', '--no-install-recommends', 'ca-certificates'], { user: 'root', + onOutput: options.onOutput, }) await execCommand(docker, container, 'test', ['-d', '/etc/ssl/certs'], { user: 'root', + onOutput: options.onOutput, }) console.log('Setting up npm for non-root global installs') await execCommand(docker, container, 'mkdir', ['-p', NPM_GLOBAL_DIR], { user: 'root', + onOutput: options.onOutput, }) await execCommand(docker, container, 'chown', ['-R', NODE_USER, NPM_GLOBAL_DIR], { user: 'root', + onOutput: options.onOutput, }) await execCommand(docker, container, 'npm', ['config', 'set', 'prefix', NPM_GLOBAL_DIR], { user: NODE_USER, + onOutput: options.onOutput, }) console.log('Setting up copilot...') await execCommand(docker, container, 'mkdir', ['-p', COPILOT_DIR], { user: 'root', + onOutput: options.onOutput, }) await execCommand(docker, container, 'chown', ['-R', NODE_USER, COPILOT_DIR], { user: 'root', + onOutput: options.onOutput, }) await execCommand(docker, container, 'npm', ['install', '-g', '@github/copilot'], { user: NODE_USER, + onOutput: options.onOutput, }) await execCommand(docker, container, 'touch', [path.join(COPILOT_DIR, 'mcp-config.json')], { user: NODE_USER, + onOutput: options.onOutput, }) console.log('Setting up agents config...') await execCommand(docker, container, 'mkdir', ['-p', AGENTS_DIR], { user: 'root', + onOutput: options.onOutput, }) await execCommand(docker, container, 'chown', ['-R', NODE_USER, AGENTS_DIR], { user: 'root', + onOutput: options.onOutput, }) return container as InitializedContainer @@ -487,6 +509,12 @@ type CommandResult = { exitCode: number } +type CommandOutputEvent = { + command: ReadonlyArray + stream: 'stdout' | 'stderr' + chunk: string +} + class CommandError extends Error { command: ReadonlyArray result: CommandResult @@ -507,7 +535,7 @@ async function execCommand( options: RunOptions, ): Promise { const cmd = [command, ...args] - const env = options.env ? Object.entries(options.env).map(([key, value]) => `${key}=${value}`) : undefined + const env = options.env ? Object.entries(options.env).map(([key, value]) => `${key}=${value}`) : undefined const exec = await container.exec({ Cmd: cmd, AttachStdout: true, @@ -523,8 +551,20 @@ async function execCommand( }) return new Promise((resolve, reject) => { - const stdout = captureStream(process.stdout) - const stderr = captureStream(process.stderr) + const stdout = captureStream(chunk => { + options.onOutput?.({ + command: cmd, + stream: 'stdout', + chunk, + }) + }) + const stderr = captureStream(chunk => { + options.onOutput?.({ + command: cmd, + stream: 'stderr', + chunk, + }) + }) docker.modem.demuxStream(stream, stdout.stream, stderr.stream) @@ -552,13 +592,13 @@ async function execCommand( }) } -function captureStream(destination: NodeJS.WritableStream): {stream: Writable; read(): string} { +function captureStream(onChunk: (chunk: string) => void): {stream: Writable; read(): string} { const chunks: Array = [] const stream = new Writable({ write(chunk: Buffer | string, encoding, callback) { const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding) chunks.push(buffer) - destination.write(buffer) + onChunk(buffer.toString('utf8')) callback() }, }) From eaf07f0f79d55619039d64ae8e717d273c684434 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 16:03:21 +0000 Subject: [PATCH 2/2] Document experiment output logging --- README.md | 7 +++++++ packages/agent-eval/src/cli.ts | 7 +------ packages/sandbox/src/index.ts | 7 +------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e2f6d6d..98ab40c 100644 --- a/README.md +++ b/README.md @@ -55,3 +55,10 @@ The experiment config will specify: - Evaluations (evals) that are used to grade the output of the model - Treatments that specify the different conditions you would like to test (for example, testing with an MCP server versus without) + +## Run output + +Experiment runs show progress as treatments start and finish. Command stdout and +stderr from each treatment are saved under that treatment's artifact directory as +`stdout.log` and `stderr.log`. Pass `--show-output` to also mirror run output to +stdout with the experiment, treatment, eval, model, and stream as a prefix. diff --git a/packages/agent-eval/src/cli.ts b/packages/agent-eval/src/cli.ts index 3b30aeb..4fa8c3c 100644 --- a/packages/agent-eval/src/cli.ts +++ b/packages/agent-eval/src/cli.ts @@ -187,12 +187,7 @@ function formatNumber(value: number): string { } function getTreatmentPrefix(treatment: Treatment): string { - return [ - treatment.experiment.name, - treatment.config.name, - treatment.eval.id, - treatment.model, - ].join(' | ') + return [treatment.experiment.name, treatment.config.name, treatment.eval.id, treatment.model].join(' | ') } function writePrefixedOutput(prefix: string, chunk: string) { diff --git a/packages/sandbox/src/index.ts b/packages/sandbox/src/index.ts index 464fef2..b456ada 100644 --- a/packages/sandbox/src/index.ts +++ b/packages/sandbox/src/index.ts @@ -291,7 +291,6 @@ async function createContainer(docker: Docker, options: CreateOptions = {}): Pro await container.start() - console.log('Creating workspace directory...') await execCommand(docker, container, 'mkdir', ['-p', CONTAINER_WORKDIR], { user: 'root', onOutput: options.onOutput, @@ -301,7 +300,6 @@ async function createContainer(docker: Docker, options: CreateOptions = {}): Pro onOutput: options.onOutput, }) - console.log('Installing CA certificates...') await execCommand(docker, container, 'apt-get', ['update'], { user: 'root', onOutput: options.onOutput, @@ -315,7 +313,6 @@ async function createContainer(docker: Docker, options: CreateOptions = {}): Pro onOutput: options.onOutput, }) - console.log('Setting up npm for non-root global installs') await execCommand(docker, container, 'mkdir', ['-p', NPM_GLOBAL_DIR], { user: 'root', onOutput: options.onOutput, @@ -329,7 +326,6 @@ async function createContainer(docker: Docker, options: CreateOptions = {}): Pro onOutput: options.onOutput, }) - console.log('Setting up copilot...') await execCommand(docker, container, 'mkdir', ['-p', COPILOT_DIR], { user: 'root', onOutput: options.onOutput, @@ -347,7 +343,6 @@ async function createContainer(docker: Docker, options: CreateOptions = {}): Pro onOutput: options.onOutput, }) - console.log('Setting up agents config...') await execCommand(docker, container, 'mkdir', ['-p', AGENTS_DIR], { user: 'root', onOutput: options.onOutput, @@ -535,7 +530,7 @@ async function execCommand( options: RunOptions, ): Promise { const cmd = [command, ...args] - const env = options.env ? Object.entries(options.env).map(([key, value]) => `${key}=${value}`) : undefined + const env = options.env ? Object.entries(options.env).map(([key, value]) => `${key}=${value}`) : undefined const exec = await container.exec({ Cmd: cmd, AttachStdout: true,