From 5c8def0674424af9231f26fd84224e451e6bc0d9 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Fri, 29 May 2026 06:14:08 +0900 Subject: [PATCH] fix: pass room role to Codex MCP tools (#185) --- src/agent-runner-environment.test.ts | 89 ++++++++++++++++++++++++++++ src/agent-runner-environment.ts | 9 ++- src/agent-runner.ts | 1 + 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/agent-runner-environment.test.ts b/src/agent-runner-environment.test.ts index 4f5be7e..6592b1b 100644 --- a/src/agent-runner-environment.test.ts +++ b/src/agent-runner-environment.test.ts @@ -118,6 +118,21 @@ function writeSkill(dir: string, name: string): void { ); } +function resetRoutingMocks(): void { + vi.mocked(config.isReviewService).mockReturnValue(false); + vi.mocked(serviceRouting.hasReviewerLease).mockReturnValue(false); + vi.mocked(serviceRouting.getEffectiveChannelLease).mockReturnValue({ + chat_jid: 'dc:test', + owner_service_id: 'claude', + reviewer_service_id: 'codex-main', + arbiter_service_id: null, + owner_failover_active: false, + activated_at: null, + reason: null, + explicit: false, + }); +} + describe('prepareGroupEnvironment codex auth handling', () => { let tempRoot: string; let previousCwd: string; @@ -380,6 +395,79 @@ describe('prepareGroupEnvironment codex auth handling', () => { }); }); +describe('prepareGroupEnvironment Codex MCP room role env', () => { + let tempRoot: string; + let previousCwd: string; + + beforeEach(() => { + tempRoot = fs.mkdtempSync(path.join('/tmp', 'ejclaw-agent-env-')); + previousCwd = process.cwd(); + process.chdir(tempRoot); + + process.env.EJ_TEST_ROOT = tempRoot; + process.env.EJ_TEST_HOME = path.join(tempRoot, 'home'); + fs.mkdirSync(process.env.EJ_TEST_HOME, { recursive: true }); + + mockReadEnvFile.mockReset(); + mockGetActiveCodexAuthPath.mockReset(); + resetRoutingMocks(); + }); + + afterEach(() => { + process.chdir(previousCwd); + delete process.env.EJ_TEST_ROOT; + delete process.env.EJ_TEST_HOME; + fs.rmSync(tempRoot, { recursive: true, force: true }); + }); + + function writeMcpServer(): void { + const mcpServerPath = path.join( + tempRoot, + 'runners', + 'agent-runner', + 'dist', + 'ipc-mcp-stdio.js', + ); + fs.mkdirSync(path.dirname(mcpServerPath), { recursive: true }); + fs.writeFileSync(mcpServerPath, '// test mcp server\n'); + } + + function readCodexConfigToml(): string { + return fs.readFileSync( + path.join( + tempRoot, + 'sessions', + group.folder, + 'services', + 'codex-main', + '.codex', + 'config.toml', + ), + 'utf-8', + ); + } + + it('writes paired owner role into the Codex MCP config env', () => { + mockReadEnvFile.mockReturnValue({}); + writeMcpServer(); + + prepareGroupEnvironment(group, false, 'dc:test', { + roomRole: 'owner', + }); + + expect(readCodexConfigToml()).toContain('EJCLAW_ROOM_ROLE = "owner"'); + }); + + it('omits room role from non-paired Codex MCP config env', () => { + mockReadEnvFile.mockReturnValue({}); + writeMcpServer(); + + prepareGroupEnvironment(group, true, 'dc:test'); + + expect(readCodexConfigToml()).not.toContain('EJCLAW_ROOM_ROLE'); + }); +}); + describe('prepareGroupEnvironment room skill overrides', () => { let tempRoot: string; let previousCwd: string; @@ -694,6 +782,7 @@ args = ["other.js"] expect(toml).toContain('[mcp_servers.ejclaw]'); expect(toml).toContain('EJCLAW_IPC_DIR = "/workspace/ipc"'); expect(toml).toContain('EJCLAW_GROUP_FOLDER = "codex-test-group"'); + expect(toml).toContain('EJCLAW_ROOM_ROLE = "reviewer"'); expect(toml).toContain('EJCLAW_WORK_DIR = "/workspace/project"'); expect(toml).not.toContain('old-ipc.js'); expect(toml).not.toContain('"/old/ipc"'); diff --git a/src/agent-runner-environment.ts b/src/agent-runner-environment.ts index 4efcefb..12b309a 100644 --- a/src/agent-runner-environment.ts +++ b/src/agent-runner-environment.ts @@ -34,7 +34,7 @@ import { getEffectiveChannelLease, hasReviewerLease, } from './service-routing.js'; -import type { AgentType, RegisteredGroup } from './types.js'; +import type { AgentType, PairedRoomRole, RegisteredGroup } from './types.js'; import type { StoredRoomSkillOverride } from './db/rooms.js'; // writeCodexApiKeyAuth removed — Codex uses OAuth only. @@ -173,6 +173,7 @@ function upsertEjclawMcpServerSection(args: { groupFolder: string; isMain: boolean; agentType: AgentType; + roomRole?: PairedRoomRole; workDir?: string; }): void { const stripEjclawMcpServerSections = (input: string): string => { @@ -221,6 +222,7 @@ EJCLAW_CHAT_JID = ${JSON.stringify(args.chatJid)} EJCLAW_GROUP_FOLDER = ${JSON.stringify(args.groupFolder)} EJCLAW_IS_MAIN = ${JSON.stringify(args.isMain ? '1' : '0')} EJCLAW_AGENT_TYPE = ${JSON.stringify(args.agentType)} +${args.roomRole ? `EJCLAW_ROOM_ROLE = ${JSON.stringify(args.roomRole)}\n` : ''} ${args.workDir ? `EJCLAW_WORK_DIR = ${JSON.stringify(args.workDir)}\n` : ''} `; fs.writeFileSync(args.sessionConfigPath, toml.trimEnd() + '\n' + mcpSection); @@ -347,6 +349,7 @@ function prepareCodexSessionEnvironment(args: { sessionRootDir: string; chatJid: string; isMain: boolean; + roomRole?: PairedRoomRole; isPairedRoom: boolean; useFailoverPromptPack: boolean; memoryBriefing?: string; @@ -489,6 +492,7 @@ function prepareCodexSessionEnvironment(args: { groupFolder: args.group.folder, isMain: args.isMain, agentType: 'codex', + roomRole: args.roomRole, workDir: args.env.EJCLAW_WORK_DIR || args.group.workDir || args.projectRoot, }); @@ -520,6 +524,7 @@ export function prepareGroupEnvironment( runtimeTaskId?: string; useTaskScopedSession?: boolean; skillOverrides?: StoredRoomSkillOverride[]; + roomRole?: PairedRoomRole; }, ): PreparedGroupEnvironment { const projectRoot = process.cwd(); @@ -659,6 +664,7 @@ export function prepareGroupEnvironment( sessionRootDir, chatJid, isMain, + roomRole: options?.roomRole, isPairedRoom, useFailoverPromptPack: useCodexReviewFailoverPromptPack, memoryBriefing: options?.memoryBriefing, @@ -799,6 +805,7 @@ export function prepareReadonlySessionEnvironment(args: { groupFolder, isMain, agentType, + roomRole: role, workDir, }); } diff --git a/src/agent-runner.ts b/src/agent-runner.ts index 6a826da..e52d811 100644 --- a/src/agent-runner.ts +++ b/src/agent-runner.ts @@ -98,6 +98,7 @@ export async function runAgentProcess( runtimeTaskId: input.runtimeTaskId, useTaskScopedSession: input.useTaskScopedSession, skillOverrides, + roomRole: input.roomRoleContext?.role, }, );