remove one-shot reviewer mode

This commit is contained in:
Codex
2026-05-27 12:43:15 +09:00
parent 4e90e1d7ec
commit 890991394d
5 changed files with 1 additions and 331 deletions

View File

@@ -45,10 +45,6 @@ import {
enqueuePairedFollowUpAfterEvent,
schedulePairedFollowUpWithMessageCheck,
} from './message-runtime-follow-up.js';
import {
clearOneShotReviewLeaseIfIdle,
maybeActivateOneShotReviewLease,
} from './one-shot-review.js';
import { type ScheduledPairedFollowUpIntentKind } from './paired-follow-up-scheduler.js';
import { transitionPairedTaskStatus } from './paired-execution-context-shared.js';
import {
@@ -342,7 +338,6 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
: 'Skipped duplicate paired follow-up after successful reviewer/arbiter delivery while task state was unchanged',
);
}
clearOneShotReviewLeaseIfIdle(chatJid);
},
});
@@ -421,7 +416,6 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
'Paired room reviewer/arbiter channel resolution',
);
}
clearOneShotReviewLeaseIfIdle(chatJid);
// Delivery retries can come from forced fallback runs whose agent_type
// differs from the room owner's registered agent type.
@@ -655,16 +649,9 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
return gateResult.success;
}
const oneShotReviewGroup =
maybeActivateOneShotReviewLease({
chatJid,
group,
messages: missedMessages,
}) ?? group;
return runQueuedGroupTurn({
chatJid,
group: oneShotReviewGroup,
group,
runId,
log,
timezone: deps.timezone,

View File

@@ -1,138 +0,0 @@
import { beforeEach, describe, expect, it } from 'vitest';
import {
_initTestDatabase,
assignRoom,
getChannelOwnerLease,
getEffectiveRuntimeRoomMode,
} from './db.js';
import {
clearOneShotReviewLeaseIfIdle,
isOneShotReviewRequest,
maybeActivateOneShotReviewLease,
ONE_SHOT_REVIEW_LEASE_REASON,
} from './one-shot-review.js';
import {
hasReviewerLease,
refreshChannelOwnerCache,
} from './service-routing.js';
import type { NewMessage } from './types.js';
function humanMessage(content: string): NewMessage {
return {
id: 'msg-1',
chat_jid: 'dc:single-room',
sender: 'user-1',
sender_name: 'user',
content,
timestamp: '2026-05-27T01:50:00.000Z',
seq: 1,
is_bot_message: false,
};
}
describe('one-shot review', () => {
beforeEach(() => {
_initTestDatabase();
refreshChannelOwnerCache(true);
});
it('detects explicit reviewer requests in user text', () => {
expect(isOneShotReviewRequest('이번 요청만 리뷰어 사용해서 검증해줘')).toBe(
true,
);
expect(isOneShotReviewRequest('리뷰어로 확인해줘')).toBe(true);
expect(isOneShotReviewRequest('그냥 테스트만 돌려줘')).toBe(false);
});
it('temporarily enables a reviewer lease without changing single room mode', () => {
const group = assignRoom('dc:single-room', {
name: 'single room',
folder: 'single_room',
roomMode: 'single',
ownerAgentType: 'claude-code',
workDir: '/tmp/ejclaw-single-room',
});
expect(group).toBeDefined();
expect(getEffectiveRuntimeRoomMode('dc:single-room')).toBe('single');
expect(hasReviewerLease('dc:single-room')).toBe(false);
const reviewGroup = maybeActivateOneShotReviewLease({
chatJid: 'dc:single-room',
group: group!,
messages: [humanMessage('이번 요청만 리뷰어 사용해서 검증해줘')],
});
expect(reviewGroup).toMatchObject({
folder: 'single_room',
workDir: '/tmp/ejclaw-single-room',
});
expect(getEffectiveRuntimeRoomMode('dc:single-room')).toBe('single');
expect(getChannelOwnerLease('dc:single-room')).toMatchObject({
reason: ONE_SHOT_REVIEW_LEASE_REASON,
owner_agent_type: 'claude-code',
});
expect(hasReviewerLease('dc:single-room')).toBe(true);
});
it('does not enable review for ordinary single-mode messages', () => {
const group = assignRoom('dc:single-room', {
name: 'single room',
folder: 'single_room',
roomMode: 'single',
ownerAgentType: 'claude-code',
workDir: '/tmp/ejclaw-single-room',
});
expect(
maybeActivateOneShotReviewLease({
chatJid: 'dc:single-room',
group: group!,
messages: [humanMessage('그냥 구현해줘')],
}),
).toBeNull();
expect(getChannelOwnerLease('dc:single-room')).toBeUndefined();
});
it('uses the group folder as the workDir for one-shot review in rooms without workDir', () => {
const group = assignRoom('dc:single-room', {
name: 'single room',
folder: 'single_room',
roomMode: 'single',
ownerAgentType: 'claude-code',
});
const reviewGroup = maybeActivateOneShotReviewLease({
chatJid: 'dc:single-room',
group: group!,
messages: [humanMessage('리뷰어로 검증해줘')],
});
expect(reviewGroup?.folder).toBe('single_room');
expect(reviewGroup?.workDir).toMatch(/\/groups\/single_room$/);
expect(getChannelOwnerLease('dc:single-room')).toMatchObject({
reason: ONE_SHOT_REVIEW_LEASE_REASON,
});
expect(hasReviewerLease('dc:single-room')).toBe(true);
});
it('clears an idle one-shot reviewer lease', () => {
const group = assignRoom('dc:single-room', {
name: 'single room',
folder: 'single_room',
roomMode: 'single',
ownerAgentType: 'claude-code',
workDir: '/tmp/ejclaw-single-room',
});
maybeActivateOneShotReviewLease({
chatJid: 'dc:single-room',
group: group!,
messages: [humanMessage('리뷰어로 확인해줘')],
});
expect(clearOneShotReviewLeaseIfIdle('dc:single-room')).toBe(true);
expect(getChannelOwnerLease('dc:single-room')).toBeUndefined();
expect(hasReviewerLease('dc:single-room')).toBe(false);
});
});

View File

@@ -1,127 +0,0 @@
import {
ARBITER_AGENT_TYPE,
REVIEWER_AGENT_TYPE,
SERVICE_ID,
} from './config.js';
import {
clearChannelOwnerLease,
getChannelOwnerLease,
getEffectiveRuntimeRoomMode,
getLatestOpenPairedTaskForChat,
getStoredRoomRoleAgentPlan,
setChannelOwnerLease,
} from './db.js';
import { logger } from './logger.js';
import { resolveGroupFolderPath } from './group-folder.js';
import { resolveRoleAgentPlan } from './role-agent-plan.js';
import { resolveRoleServiceShadow } from './role-service-shadow.js';
import {
getEffectiveChannelLease,
hasReviewerLease,
refreshChannelOwnerCache,
resolveLeaseServiceId,
} from './service-routing.js';
import type { NewMessage, RegisteredGroup } from './types.js';
export const ONE_SHOT_REVIEW_LEASE_REASON = 'one-shot-review';
const REVIEW_ONCE_PATTERNS = [
/(이번\s*요청만|이번만|한\s*번만|임시로|필요할\s*때만).{0,30}(리뷰어|reviewer).{0,30}(사용|켜|돌려|붙여|검증|확인|리뷰)/i,
/(리뷰어|reviewer).{0,20}(사용해서|사용해|켜서|돌려서|붙여서).{0,40}(검증|확인|리뷰|체크|봐줘|봐)/i,
/(리뷰어|reviewer).{0,30}(검증해줘|확인해줘|리뷰해줘|체크해줘|봐줘|돌려줘)/i,
/(검증|확인|리뷰|체크).{0,30}(리뷰어|reviewer).{0,30}(사용|켜|돌려|붙여)/i,
];
export function isOneShotReviewRequest(content: string): boolean {
const normalized = content.replace(/\s+/g, ' ').trim();
if (!normalized) return false;
return REVIEW_ONCE_PATTERNS.some((pattern) => pattern.test(normalized));
}
function hasHumanOneShotReviewRequest(messages: NewMessage[]): boolean {
return messages.some(
(message) =>
message.is_from_me !== true &&
!message.is_bot_message &&
isOneShotReviewRequest(message.content),
);
}
export function maybeActivateOneShotReviewLease(args: {
chatJid: string;
group: RegisteredGroup;
messages: NewMessage[];
}): RegisteredGroup | null {
if (getEffectiveRuntimeRoomMode(args.chatJid) !== 'single') return null;
if (hasReviewerLease(args.chatJid)) return null;
if (!hasHumanOneShotReviewRequest(args.messages)) return null;
let effectiveGroup = args.group;
if (!effectiveGroup.workDir) {
effectiveGroup = {
...effectiveGroup,
workDir: resolveGroupFolderPath(effectiveGroup.folder),
};
}
const lease = getEffectiveChannelLease(args.chatJid);
const storedPlan = getStoredRoomRoleAgentPlan(args.chatJid);
const rolePlan = resolveRoleAgentPlan({
paired: true,
groupAgentType:
lease.owner_agent_type ??
storedPlan?.ownerAgentType ??
args.group.agentType,
configuredReviewer: storedPlan?.reviewerAgentType ?? REVIEWER_AGENT_TYPE,
configuredArbiter: storedPlan?.arbiterAgentType ?? ARBITER_AGENT_TYPE,
});
const ownerServiceId =
resolveLeaseServiceId(lease, 'owner') ??
resolveRoleServiceShadow('owner', rolePlan.ownerAgentType) ??
SERVICE_ID;
const reviewerServiceId = resolveRoleServiceShadow(
'reviewer',
rolePlan.reviewerAgentType,
);
if (!reviewerServiceId || !rolePlan.reviewerAgentType) {
return null;
}
setChannelOwnerLease({
chat_jid: args.chatJid,
owner_service_id: ownerServiceId,
reviewer_service_id: reviewerServiceId,
arbiter_service_id: resolveRoleServiceShadow(
'arbiter',
rolePlan.arbiterAgentType,
),
owner_agent_type: rolePlan.ownerAgentType,
reviewer_agent_type: rolePlan.reviewerAgentType,
arbiter_agent_type: rolePlan.arbiterAgentType,
reason: ONE_SHOT_REVIEW_LEASE_REASON,
});
refreshChannelOwnerCache(true);
logger.info(
{
chatJid: args.chatJid,
groupFolder: effectiveGroup.folder,
workDir: effectiveGroup.workDir,
usedDefaultWorkDir: !args.group.workDir,
ownerAgentType: rolePlan.ownerAgentType,
reviewerAgentType: rolePlan.reviewerAgentType,
},
'Activated one-shot reviewer lease for a single-mode room',
);
return effectiveGroup;
}
export function clearOneShotReviewLeaseIfIdle(chatJid: string): boolean {
const lease = getChannelOwnerLease(chatJid);
if (lease?.reason !== ONE_SHOT_REVIEW_LEASE_REASON) return false;
if (getLatestOpenPairedTaskForChat(chatJid)) return false;
clearChannelOwnerLease(chatJid);
refreshChannelOwnerCache(true);
logger.info({ chatJid }, 'Cleared idle one-shot reviewer lease');
return true;
}

View File

@@ -218,24 +218,6 @@ describe('workspace package manager helpers', () => {
expect(execFileSyncMock).not.toHaveBeenCalled();
});
it('prepends the current Bun binary directory for bun workspace commands', () => {
const repoDir = path.join(tempRoot, 'bun-workspace');
fs.mkdirSync(repoDir, { recursive: true });
fs.writeFileSync(
path.join(repoDir, 'package.json'),
JSON.stringify({ packageManager: 'bun@1.3.13' }),
);
const env = buildWorkspaceCommandEnvironment(repoDir, 'bun', {
PATH: '/tmp/bin',
});
const expectedBunBinDir = path.basename(process.execPath).startsWith('bun')
? path.dirname(process.execPath)
: path.join(os.homedir(), '.bun', 'bin');
expect(env.PATH?.split(path.delimiter)[0]).toBe(expectedBunBinDir);
});
it('disables corepack project specs only for lockfile-selected pnpm workspaces under a conflicting ancestor', () => {
const parentDir = path.join(tempRoot, 'parent');
fs.mkdirSync(parentDir, { recursive: true });

View File

@@ -118,36 +118,6 @@ function buildCorepackCommand(
};
}
function prependPathEntry(
baseEnv: NodeJS.ProcessEnv,
entry: string | null,
): NodeJS.ProcessEnv {
if (!entry) {
return baseEnv;
}
const currentPath = baseEnv.PATH ?? '';
const entries = currentPath.split(path.delimiter).filter(Boolean);
if (entries.includes(entry)) {
return baseEnv;
}
return {
...baseEnv,
PATH: [entry, ...entries].join(path.delimiter),
};
}
function resolveCurrentBunBinDir(): string | null {
if (path.basename(process.execPath).startsWith('bun')) {
return path.dirname(process.execPath);
}
const home = process.env.HOME;
if (!home) {
return null;
}
const candidate = path.join(home, '.bun', 'bin');
return fs.existsSync(path.join(candidate, 'bun')) ? candidate : null;
}
function findNearestAncestorPackageManager(
repoDir: string,
): WorkspacePackageManager | null {
@@ -174,10 +144,6 @@ export function buildWorkspaceCommandEnvironment(
packageManager: WorkspacePackageManager,
baseEnv: NodeJS.ProcessEnv = process.env,
): NodeJS.ProcessEnv {
if (packageManager === 'bun') {
return prependPathEntry(baseEnv, resolveCurrentBunBinDir());
}
if (packageManager !== 'pnpm') {
return baseEnv;
}