refactor: simplify paired review system and add reviewer container isolation

Phase 1 — Strip over-engineering (-3,665 lines):
- DB tables: 7 → 3 (remove executions, approvals, artifacts, events)
- Task statuses: 11 → 5 (active, review_ready, in_review, merge_ready, completed)
- Remove plan governance, event sourcing, gate/verdict, freshness guards
- paired-execution-context: 980 → 299 lines
- Session commands: remove /risk, /plan, /approve-plan, /request-plan-changes

Phase 2 — Container isolation for reviewers:
- Add container-runtime.ts (Docker abstraction)
- Add credential-proxy.ts (API key injection without container exposure)
- Add container-runner.ts (reviewer-specific read-only mount + tmpfs)
- Add container/Dockerfile + agent-runner (Chromium, Claude Code, Codex)
- agent-runner.ts: auto-route reviewer execution to container mode
This commit is contained in:
Eyejoker
2026-03-29 18:16:28 +09:00
parent 3dd41f749e
commit fc9f2867b9
24 changed files with 1174 additions and 4900 deletions

View File

@@ -20,6 +20,7 @@ export {
} from './agent-runner-snapshot.js';
import { logger } from './logger.js';
import { OUTPUT_END_MARKER, OUTPUT_START_MARKER } from './agent-protocol.js';
import { runReviewerContainer } from './container-runner.js';
import {
AgentOutputPhase,
AgentType,
@@ -67,6 +68,37 @@ export async function runAgentProcess(
onOutput?: (output: AgentOutput) => Promise<void>,
envOverrides?: Record<string, string>,
): Promise<AgentOutput> {
// ── Reviewer container mode ─────────────────────────────────────
// When EJCLAW_REVIEWER_RUNTIME is set in envOverrides, run the reviewer
// inside a Docker container with read-only source mount instead of
// as a host process. This provides kernel-level write protection.
const isReviewerContainer =
envOverrides?.EJCLAW_REVIEWER_RUNTIME === '1' &&
process.env.REVIEWER_CONTAINER_ENABLED !== '0';
if (isReviewerContainer) {
const ownerWorkspaceDir =
envOverrides?.EJCLAW_WORK_DIR || group.workDir || process.cwd();
return runReviewerContainer({
group,
input: {
prompt: input.prompt,
sessionId: input.sessionId,
groupFolder: input.groupFolder,
chatJid: input.chatJid,
runId: input.runId || `${Date.now()}`,
isMain: input.isMain,
assistantName: input.assistantName,
},
ownerWorkspaceDir,
envOverrides,
onOutput,
onProcess: (proc, containerName) => {
onProcess(proc, containerName, '');
},
});
}
// ── Host process mode (owner) ───────────────────────────────────
const startTime = Date.now();
const { env, groupDir, runnerDir } = prepareGroupEnvironment(
group,