diff --git a/container/Dockerfile b/container/Dockerfile index 832eb10..22799d7 100644 --- a/container/Dockerfile +++ b/container/Dockerfile @@ -1,5 +1,6 @@ # EJClaw Reviewer Container # Runs Claude/Codex agent in read-only isolated environment for code review. +# Build from project root: docker build -f container/Dockerfile -t ejclaw-reviewer:latest . FROM node:22-slim @@ -23,10 +24,10 @@ RUN npm install -g @anthropic-ai/claude-code @openai/codex # Create app directory WORKDIR /app -# Copy and install agent runner -COPY agent-runner/package*.json ./ +# Copy host's agent-runner (same code, same SDK version) +COPY runners/agent-runner/package*.json ./ RUN npm install -COPY agent-runner/ ./ +COPY runners/agent-runner/ ./ RUN npm run build # Create workspace directories diff --git a/container/agent-runner/package.json b/container/agent-runner/package.json deleted file mode 100644 index ba63769..0000000 --- a/container/agent-runner/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "ejclaw-reviewer-runner", - "version": "1.0.0", - "type": "module", - "description": "Reviewer agent runner for EJClaw containers", - "main": "dist/index.js", - "scripts": { - "build": "tsc", - "dev": "tsc --watch" - }, - "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.2.81" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.7.0" - } -} diff --git a/container/agent-runner/src/index.ts b/container/agent-runner/src/index.ts deleted file mode 100644 index 9f435c2..0000000 --- a/container/agent-runner/src/index.ts +++ /dev/null @@ -1,141 +0,0 @@ -/** - * EJClaw Reviewer Container Agent Runner - * - * Runs inside a Docker container. Reads input from stdin, - * invokes Claude Code or Codex, writes output to stdout - * via marker-delimited JSON. - * - * The project directory is mounted read-only — the reviewer - * can read/test code but cannot modify it. - */ -import { query } from '@anthropic-ai/claude-agent-sdk'; -import fs from 'fs'; -import path from 'path'; - -// ── Protocol markers (must match host's agent-protocol.ts) ──────── -const OUTPUT_START_MARKER = '---EJCLAW_OUTPUT_START---'; -const OUTPUT_END_MARKER = '---EJCLAW_OUTPUT_END---'; - -// ── Types ───────────────────────────────────────────────────────── -interface ContainerInput { - prompt: string; - sessionId?: string; - groupFolder: string; - chatJid: string; - runId: string; - isMain: boolean; - assistantName?: string; -} - -interface ContainerOutput { - status: 'success' | 'error'; - result: string | null; - newSessionId?: string; - error?: string; - phase: 'final'; -} - -// ── Helpers ─────────────────────────────────────────────────────── - -function emitOutput(output: ContainerOutput): void { - const json = JSON.stringify(output); - process.stdout.write(`${OUTPUT_START_MARKER}${json}${OUTPUT_END_MARKER}\n`); -} - -function readStdinJson(): Promise { - return new Promise((resolve, reject) => { - const chunks: Buffer[] = []; - process.stdin.on('data', (chunk) => chunks.push(chunk)); - process.stdin.on('end', () => { - try { - resolve(JSON.parse(Buffer.concat(chunks).toString())); - } catch (err) { - reject(new Error(`Failed to parse stdin JSON: ${err}`)); - } - }); - process.stdin.on('error', reject); - }); -} - -// ── Main ────────────────────────────────────────────────────────── - -async function main(): Promise { - const input = await readStdinJson(); - - // Load group CLAUDE.md if present - const groupClaudeMd = path.join('/workspace/group', 'CLAUDE.md'); - const systemPromptParts: string[] = []; - if (fs.existsSync(groupClaudeMd)) { - systemPromptParts.push(fs.readFileSync(groupClaudeMd, 'utf-8')); - } - - // Reviewer-specific instructions - systemPromptParts.push( - 'You are a code reviewer. The project directory is mounted read-only.', - 'You can read files, run tests, and analyze code, but you cannot modify any source files.', - 'Focus on: correctness, security, performance, and test coverage.', - ); - - const systemPrompt = systemPromptParts.join('\n\n'); - - try { - let lastAssistantText = ''; - - for await (const message of query({ - prompt: input.prompt, - options: { - systemPrompt, - allowedTools: [ - 'Read', - 'Bash', - 'Glob', - 'Grep', - 'LS', - 'Agent', - 'WebSearch', - 'WebFetch', - ], - maxTurns: 30, - cwd: '/workspace/project', - permissionMode: 'bypassPermissions', - allowDangerouslySkipPermissions: true, - }, - })) { - if (message.type === 'assistant') { - const content = (message as { message?: { content?: Array<{ type: string; text?: string }> } }).message?.content; - if (content) { - for (const block of content) { - if (block.type === 'text' && block.text) { - lastAssistantText = block.text; - } - } - } - } - } - - emitOutput({ - status: 'success', - result: lastAssistantText || null, - phase: 'final', - }); - } catch (err) { - const errorMsg = err instanceof Error ? err.message : String(err); - emitOutput({ - status: 'error', - result: null, - error: errorMsg, - phase: 'final', - }); - } -} - -main().catch((err) => { - console.error('Fatal container error:', err); - emitOutput({ - status: 'error', - result: null, - error: `Fatal: ${err instanceof Error ? err.message : String(err)}`, - phase: 'final', - }); - process.exit(1); -}); diff --git a/container/agent-runner/tsconfig.json b/container/agent-runner/tsconfig.json deleted file mode 100644 index 8d4e7cc..0000000 --- a/container/agent-runner/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true - }, - "include": ["src/**/*"] -} diff --git a/container/build.sh b/container/build.sh index 9b70785..c01df16 100755 --- a/container/build.sh +++ b/container/build.sh @@ -1,10 +1,11 @@ #!/usr/bin/env bash # Build the EJClaw reviewer container image +# Runs from project root so Dockerfile can COPY runners/agent-runner/ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" echo "Building ejclaw-reviewer container image..." -docker build -t ejclaw-reviewer:latest . +docker build -f "$SCRIPT_DIR/Dockerfile" -t ejclaw-reviewer:latest "$PROJECT_ROOT" echo "Done. Image: ejclaw-reviewer:latest"