diff --git a/src/agent-runner-environment.ts b/src/agent-runner-environment.ts index 23a57ad..fca8a1d 100644 --- a/src/agent-runner-environment.ts +++ b/src/agent-runner-environment.ts @@ -525,3 +525,60 @@ export function prepareGroupEnvironment( return { env, groupDir, runnerDir }; } + +/** + * Prepare the Claude session directory for a container-based reviewer. + * + * Writes CLAUDE.md (platform + paired room prompts + global memory + briefing), + * syncs skills, and ensures settings.json exist — the same steps that + * `prepareGroupEnvironment` does for host-mode agents, but targeted at an + * externally provided session directory (the one mounted into the container). + */ +export function prepareContainerSessionEnvironment(args: { + sessionDir: string; + chatJid: string; + isMain: boolean; + memoryBriefing?: string; +}): void { + const { sessionDir, chatJid, isMain, memoryBriefing } = args; + const projectRoot = process.cwd(); + + fs.mkdirSync(sessionDir, { recursive: true }); + ensureClaudeSessionSettings(sessionDir); + + // Sync skills from host + const skillSources = [ + path.join(os.homedir(), '.claude', 'skills'), + path.join(projectRoot, 'runners', 'skills'), + ]; + syncDirectoryEntries(skillSources, path.join(sessionDir, 'skills')); + + // Build CLAUDE.md with reviewer-appropriate prompts + const claudePlatformPrompt = readPlatformPrompt('claude-code', projectRoot); + const claudePairedRoomPrompt = isPairedRoomJid(chatJid) + ? readPairedRoomPrompt('claude-code', projectRoot) + : undefined; + const globalDir = path.join(GROUPS_DIR, 'global'); + const globalClaudeMdPath = path.join(globalDir, 'CLAUDE.md'); + const globalClaudeMemory = + !isMain && fs.existsSync(globalClaudeMdPath) + ? fs.readFileSync(globalClaudeMdPath, 'utf-8').trim() + : undefined; + + const sessionClaudeMd = [ + claudePlatformPrompt, + claudePairedRoomPrompt, + globalClaudeMemory, + memoryBriefing, + ] + .filter((value): value is string => Boolean(value)) + .join('\n\n---\n\n') + .trim(); + + const sessionClaudeMdPath = path.join(sessionDir, 'CLAUDE.md'); + if (sessionClaudeMd) { + fs.writeFileSync(sessionClaudeMdPath, sessionClaudeMd + '\n'); + } else if (fs.existsSync(sessionClaudeMdPath)) { + fs.unlinkSync(sessionClaudeMdPath); + } +} diff --git a/src/agent-runner.ts b/src/agent-runner.ts index 5609a54..e8bdc9c 100644 --- a/src/agent-runner.ts +++ b/src/agent-runner.ts @@ -12,7 +12,10 @@ import { AGENT_TIMEOUT, IDLE_TIMEOUT, } from './config.js'; -import { prepareGroupEnvironment } from './agent-runner-environment.js'; +import { + prepareContainerSessionEnvironment, + prepareGroupEnvironment, +} from './agent-runner-environment.js'; import { getEnv } from './env.js'; export { type AvailableGroup, @@ -75,6 +78,19 @@ export async function runAgentProcess( if (envOverrides?.EJCLAW_REVIEWER_RUNTIME === '1') { const ownerWorkspaceDir = envOverrides?.EJCLAW_WORK_DIR || group.workDir || process.cwd(); + + // Prepare session directory for the container (CLAUDE.md, skills, settings) + // so the Claude SDK inside the container has platform & paired room prompts. + const sessionDir = envOverrides?.CLAUDE_CONFIG_DIR; + if (sessionDir) { + prepareContainerSessionEnvironment({ + sessionDir, + chatJid: input.chatJid, + isMain: input.isMain, + memoryBriefing: input.memoryBriefing, + }); + } + return runReviewerContainer({ group, input: { @@ -85,6 +101,7 @@ export async function runAgentProcess( runId: input.runId || `${Date.now()}`, isMain: input.isMain, assistantName: input.assistantName, + roomRoleContext: input.roomRoleContext, }, ownerWorkspaceDir, envOverrides, diff --git a/src/container-runner.ts b/src/container-runner.ts index e8666a5..9b6bc35 100644 --- a/src/container-runner.ts +++ b/src/container-runner.ts @@ -58,6 +58,7 @@ export interface ReviewerContainerInput { runId: string; isMain: boolean; assistantName?: string; + roomRoleContext?: import('./types.js').RoomRoleContext; } interface VolumeMount {