From 89335ab005932b1a1bf33a3ac02b8788b49b2e7c Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Tue, 31 Mar 2026 12:10:00 +0900 Subject: [PATCH] refactor: drive paired routing from room mode --- src/db.test.ts | 13 +++++----- src/db.ts | 3 +-- src/service-routing.test.ts | 49 ++++++++++++++++++++++++++++++++++++- src/service-routing.ts | 13 +++++++--- 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/db.test.ts b/src/db.test.ts index 0db9786..54971af 100644 --- a/src/db.test.ts +++ b/src/db.test.ts @@ -806,7 +806,7 @@ describe('paired room registration', () => { expect(isPairedRoomJid('dc:solo')).toBe(false); }); - it('falls back to legacy paired-room inference when no explicit room mode exists', () => { + it('falls back to inferred room mode when no explicit room mode exists', () => { setRegisteredGroup('dc:legacy-paired', { name: 'Legacy Paired Claude', folder: 'legacy-paired-claude', @@ -823,12 +823,11 @@ describe('paired room registration', () => { }); expect(getExplicitRoomMode('dc:legacy-paired')).toBeUndefined(); - expect(getEffectiveRoomMode('dc:legacy-paired')).toBe( - isPairedRoomJid('dc:legacy-paired') ? 'tribunal' : 'single', - ); + expect(getEffectiveRoomMode('dc:legacy-paired')).toBe('tribunal'); + expect(isPairedRoomJid('dc:legacy-paired')).toBe(true); }); - it('lets explicit single override dual registration without changing legacy paired inference yet', () => { + it('lets explicit single override dual registration for paired-room checks', () => { setRegisteredGroup('dc:explicit-single', { name: 'Explicit Single Claude', folder: 'explicit-single-claude', @@ -848,7 +847,7 @@ describe('paired room registration', () => { expect(getExplicitRoomMode('dc:explicit-single')).toBe('single'); expect(getEffectiveRoomMode('dc:explicit-single')).toBe('single'); - expect(isPairedRoomJid('dc:explicit-single')).toBe(true); + expect(isPairedRoomJid('dc:explicit-single')).toBe(false); }); it('lets explicit tribunal override solo fallback and clears back to inferred mode', () => { @@ -866,11 +865,13 @@ describe('paired room registration', () => { expect(getExplicitRoomMode('dc:explicit-tribunal')).toBe('tribunal'); expect(getEffectiveRoomMode('dc:explicit-tribunal')).toBe('tribunal'); + expect(isPairedRoomJid('dc:explicit-tribunal')).toBe(true); clearExplicitRoomMode('dc:explicit-tribunal'); expect(getExplicitRoomMode('dc:explicit-tribunal')).toBeUndefined(); expect(getEffectiveRoomMode('dc:explicit-tribunal')).toBe('single'); + expect(isPairedRoomJid('dc:explicit-tribunal')).toBe(false); }); }); diff --git a/src/db.ts b/src/db.ts index a228825..4fd0ed4 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1979,8 +1979,7 @@ export function getEffectiveRoomMode(chatJid: string): RoomMode { } export function isPairedRoomJid(jid: string): boolean { - const types = getRegisteredAgentTypesForJid(jid); - return types.includes('claude-code') && types.includes('codex'); + return getEffectiveRoomMode(jid) === 'tribunal'; } // --- Paired task/project/workspace state --- diff --git a/src/service-routing.test.ts b/src/service-routing.test.ts index 06e5b4c..48f872f 100644 --- a/src/service-routing.test.ts +++ b/src/service-routing.test.ts @@ -1,6 +1,10 @@ import { beforeEach, describe, expect, it } from 'vitest'; -import { _initTestDatabase, setRegisteredGroup } from './db.js'; +import { + _initTestDatabase, + setExplicitRoomMode, + setRegisteredGroup, +} from './db.js'; import { activateCodexFailover, clearGlobalFailover, @@ -78,4 +82,47 @@ describe('service-routing global failover', () => { explicit: false, }); }); + + it('uses explicit single room mode to suppress reviewer lease on dual registration', () => { + setRegisteredGroup('dc:explicit-single', { + name: 'Explicit Single Claude', + folder: 'explicit-single-claude', + trigger: '@Andy', + added_at: '2024-01-01T00:00:00.000Z', + agentType: 'claude-code', + }); + setRegisteredGroup('dc:explicit-single', { + name: 'Explicit Single Codex', + folder: 'explicit-single-codex', + trigger: '@Codex', + added_at: '2024-01-01T00:00:00.000Z', + agentType: 'codex', + }); + setExplicitRoomMode('dc:explicit-single', 'single'); + + expect(getEffectiveChannelLease('dc:explicit-single')).toMatchObject({ + chat_jid: 'dc:explicit-single', + owner_service_id: 'codex-main', + reviewer_service_id: null, + explicit: false, + }); + }); + + it('uses explicit tribunal room mode to add reviewer lease on solo rooms', () => { + setRegisteredGroup('dc:explicit-tribunal', { + name: 'Explicit Tribunal Claude', + folder: 'explicit-tribunal-claude', + trigger: '@Andy', + added_at: '2024-01-01T00:00:00.000Z', + agentType: 'claude-code', + }); + setExplicitRoomMode('dc:explicit-tribunal', 'tribunal'); + + expect(getEffectiveChannelLease('dc:explicit-tribunal')).toMatchObject({ + chat_jid: 'dc:explicit-tribunal', + owner_service_id: 'claude', + reviewer_service_id: 'claude', + explicit: false, + }); + }); }); diff --git a/src/service-routing.ts b/src/service-routing.ts index 9231b33..107372a 100644 --- a/src/service-routing.ts +++ b/src/service-routing.ts @@ -12,6 +12,7 @@ import { import { clearChannelOwnerLease, getAllChannelOwnerLeases, + getEffectiveRoomMode, getRegisteredAgentTypesForJid, setChannelOwnerLease, type ChannelOwnerLeaseRow, @@ -58,11 +59,17 @@ function getDefaultLease(chatJid: string): EffectiveChannelLease { const types = getRegisteredAgentTypesForJid(chatJid); const hasClaude = types.includes('claude-code'); const hasCodex = types.includes('codex'); + const roomMode = getEffectiveRoomMode(chatJid); - if (hasClaude && hasCodex) { - // Owner/reviewer service IDs derived from OWNER_AGENT_TYPE / REVIEWER_AGENT_TYPE env vars + if (roomMode === 'tribunal') { const ownerServiceId = - OWNER_AGENT_TYPE === 'codex' ? CODEX_MAIN_SERVICE_ID : CLAUDE_SERVICE_ID; + hasClaude && hasCodex + ? OWNER_AGENT_TYPE === 'codex' + ? CODEX_MAIN_SERVICE_ID + : CLAUDE_SERVICE_ID + : hasCodex + ? CODEX_MAIN_SERVICE_ID + : CLAUDE_SERVICE_ID; return { chat_jid: chatJid, owner_service_id: ownerServiceId,