style: sync pre-commit formatting fallout

This commit is contained in:
Eyejoker
2026-03-28 05:41:16 +09:00
parent ba9f6871b6
commit e1fdc47552
17 changed files with 261 additions and 175 deletions

View File

@@ -5,7 +5,6 @@ import {
classifyClaudeAuthError, classifyClaudeAuthError,
detectClaudeProviderFailureMessage, detectClaudeProviderFailureMessage,
isClaudeOrgAccessDeniedMessage, isClaudeOrgAccessDeniedMessage,
shouldRotateClaudeToken, shouldRotateClaudeToken,
} from './agent-error-detection.js'; } from './agent-error-detection.js';
@@ -64,5 +63,4 @@ describe('agent-error-detection', () => {
expect(shouldRotateClaudeToken('overloaded')).toBe(false); expect(shouldRotateClaudeToken('overloaded')).toBe(false);
expect(shouldRotateClaudeToken('success-null-result')).toBe(false); expect(shouldRotateClaudeToken('success-null-result')).toBe(false);
}); });
}); });

View File

@@ -63,7 +63,8 @@ vi.mock('./group-folder.js', () => ({
folder: string, folder: string,
serviceId: string, serviceId: string,
taskId: string, taskId: string,
) => `${process.env.EJ_TEST_ROOT}/task-sessions/${folder}/${serviceId}/${taskId}`, ) =>
`${process.env.EJ_TEST_ROOT}/task-sessions/${folder}/${serviceId}/${taskId}`,
})); }));
vi.mock('os', async () => { vi.mock('os', async () => {

View File

@@ -273,7 +273,10 @@ function prepareCodexSessionEnvironment(args: {
: [ : [
readPlatformPrompt('codex', args.projectRoot), readPlatformPrompt('codex', args.projectRoot),
isReviewService(SERVICE_ID) isReviewService(SERVICE_ID)
? readOptionalPromptFile(args.projectRoot, 'codex-review-platform.md') ? readOptionalPromptFile(
args.projectRoot,
'codex-review-platform.md',
)
: undefined, : undefined,
args.isPairedRoom args.isPairedRoom
? readPairedRoomPrompt('codex', args.projectRoot) ? readPairedRoomPrompt('codex', args.projectRoot)

View File

@@ -137,12 +137,10 @@ export function isSessionCommandSenderAllowed(sender: string): boolean {
} }
// Delta handoff: cross-provider session continuity with probe + fallback // Delta handoff: cross-provider session continuity with probe + fallback
export const DELTA_HANDOFF_ENABLED = export const DELTA_HANDOFF_ENABLED = getEnv('DELTA_HANDOFF_ENABLED') === 'true';
getEnv('DELTA_HANDOFF_ENABLED') === 'true';
// Comma-separated list of group folders for canary testing // Comma-separated list of group folders for canary testing
const rawDeltaHandoffCanaryGroups = const rawDeltaHandoffCanaryGroups = getEnv('DELTA_HANDOFF_CANARY_GROUPS') || '';
getEnv('DELTA_HANDOFF_CANARY_GROUPS') || '';
export const DELTA_HANDOFF_CANARY_GROUPS = new Set( export const DELTA_HANDOFF_CANARY_GROUPS = new Set(
rawDeltaHandoffCanaryGroups rawDeltaHandoffCanaryGroups
.split(',') .split(',')

View File

@@ -361,7 +361,9 @@ function createSchema(database: Database.Database): void {
} }
try { try {
database.exec(`ALTER TABLE work_items ADD COLUMN service_id TEXT DEFAULT ''`); database.exec(
`ALTER TABLE work_items ADD COLUMN service_id TEXT DEFAULT ''`,
);
} catch { } catch {
/* column already exists */ /* column already exists */
} }
@@ -884,9 +886,7 @@ export function getRecentChatMessages(
LIMIT ? LIMIT ?
) ORDER BY timestamp ) ORDER BY timestamp
`; `;
const rows = db const rows = db.prepare(sql).all(chatJid, limit) as Array<
.prepare(sql)
.all(chatJid, limit) as Array<
NewMessage & { NewMessage & {
is_from_me?: boolean | number; is_from_me?: boolean | number;
is_bot_message?: boolean | number; is_bot_message?: boolean | number;
@@ -1834,7 +1834,9 @@ function parseLastAgentSeqState(
} }
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`Invalid last_agent_seq JSON for ${serviceId}: not an object`); throw new Error(
`Invalid last_agent_seq JSON for ${serviceId}: not an object`,
);
} }
const cursors: Record<string, string> = {}; const cursors: Record<string, string> = {};

View File

@@ -19,7 +19,8 @@ beforeEach(() => {
}); });
// Helper to create a message // Helper to create a message
const createMessage = (overrides: Partial<{ const createMessage = (
overrides: Partial<{
id: string; id: string;
chat_jid: string; chat_jid: string;
sender: string; sender: string;
@@ -28,7 +29,8 @@ const createMessage = (overrides: Partial<{
timestamp: string; timestamp: string;
is_from_me: boolean; is_from_me: boolean;
is_bot_message: boolean; is_bot_message: boolean;
}> = {}) => ({ }> = {},
) => ({
id: overrides.id ?? 'msg-1', id: overrides.id ?? 'msg-1',
chat_jid: overrides.chat_jid ?? 'dc:test-room', chat_jid: overrides.chat_jid ?? 'dc:test-room',
sender: overrides.sender ?? 'user1', sender: overrides.sender ?? 'user1',
@@ -41,7 +43,13 @@ const createMessage = (overrides: Partial<{
// Helper to setup chat metadata // Helper to setup chat metadata
const setupChat = (jid: string) => { const setupChat = (jid: string) => {
storeChatMetadata(jid, new Date().toISOString(), 'Test Chat', 'discord', true); storeChatMetadata(
jid,
new Date().toISOString(),
'Test Chat',
'discord',
true,
);
}; };
describe('isPairedRoomJid', () => { describe('isPairedRoomJid', () => {
@@ -103,34 +111,40 @@ describe('getLastBotFinalMessage', () => {
setupChat(jid); setupChat(jid);
// Store older bot message (from any bot) // Store older bot message (from any bot)
storeMessage(createMessage({ storeMessage(
createMessage({
id: 'msg-1', id: 'msg-1',
chat_jid: jid, chat_jid: jid,
content: 'First bot message', content: 'First bot message',
timestamp: new Date(now.getTime() - 1000).toISOString(), timestamp: new Date(now.getTime() - 1000).toISOString(),
is_from_me: true, is_from_me: true,
is_bot_message: true, is_bot_message: true,
})); }),
);
// Store newer bot message (from different bot - is_from_me=0 but is_bot_message=1) // Store newer bot message (from different bot - is_from_me=0 but is_bot_message=1)
storeMessage(createMessage({ storeMessage(
createMessage({
id: 'msg-2', id: 'msg-2',
chat_jid: jid, chat_jid: jid,
content: 'Second bot message', content: 'Second bot message',
timestamp: now.toISOString(), timestamp: now.toISOString(),
is_from_me: false, // Different bot is_from_me: false, // Different bot
is_bot_message: true, is_bot_message: true,
})); }),
);
// Store human message (should not be returned) // Store human message (should not be returned)
storeMessage(createMessage({ storeMessage(
createMessage({
id: 'msg-3', id: 'msg-3',
chat_jid: jid, chat_jid: jid,
content: 'Human message', content: 'Human message',
timestamp: new Date(now.getTime() + 1000).toISOString(), timestamp: new Date(now.getTime() + 1000).toISOString(),
is_from_me: false, is_from_me: false,
is_bot_message: false, is_bot_message: false,
})); }),
);
const lastMessages = getLastBotFinalMessage(jid, 'claude-code', 1); const lastMessages = getLastBotFinalMessage(jid, 'claude-code', 1);
expect(lastMessages).toHaveLength(1); expect(lastMessages).toHaveLength(1);
@@ -183,21 +197,27 @@ describe('isDuplicateOfLastBotFinal (runtime function)', () => {
}); });
// Store a bot message (from any bot) // Store a bot message (from any bot)
storeMessage(createMessage({ storeMessage(
createMessage({
id: 'msg-1', id: 'msg-1',
chat_jid: jid, chat_jid: jid,
content: 'DONE — Task completed successfully', content: 'DONE — Task completed successfully',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
is_from_me: false, // Different bot is_from_me: false, // Different bot
is_bot_message: true, is_bot_message: true,
})); }),
);
// Verify it's a paired room // Verify it's a paired room
expect(isPairedRoomJid(jid)).toBe(true); expect(isPairedRoomJid(jid)).toBe(true);
// Verify duplicate detection works via the actual runtime function // Verify duplicate detection works via the actual runtime function
expect(isDuplicateOfLastBotFinal(jid, 'DONE — Task completed successfully')).toBe(true); expect(
expect(isDuplicateOfLastBotFinal(jid, 'donetask completed successfully')).toBe(true); // Normalized match isDuplicateOfLastBotFinal(jid, 'DONETask completed successfully'),
).toBe(true);
expect(
isDuplicateOfLastBotFinal(jid, 'done — task completed successfully'),
).toBe(true); // Normalized match
}); });
it('non-paired room: duplicate check is bypassed', () => { it('non-paired room: duplicate check is bypassed', () => {
@@ -214,14 +234,16 @@ describe('isDuplicateOfLastBotFinal (runtime function)', () => {
}); });
// Store a bot message // Store a bot message
storeMessage(createMessage({ storeMessage(
createMessage({
id: 'msg-1', id: 'msg-1',
chat_jid: jid, chat_jid: jid,
content: 'DONE — Task completed', content: 'DONE — Task completed',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
is_from_me: true, is_from_me: true,
is_bot_message: true, is_bot_message: true,
})); }),
);
// Verify it's NOT a paired room // Verify it's NOT a paired room
expect(isPairedRoomJid(jid)).toBe(false); expect(isPairedRoomJid(jid)).toBe(false);
@@ -252,17 +274,21 @@ describe('isDuplicateOfLastBotFinal (runtime function)', () => {
}); });
// Store a bot message // Store a bot message
storeMessage(createMessage({ storeMessage(
createMessage({
id: 'msg-1', id: 'msg-1',
chat_jid: jid, chat_jid: jid,
content: 'First message content', content: 'First message content',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
is_from_me: true, is_from_me: true,
is_bot_message: true, is_bot_message: true,
})); }),
);
// Verify different content is not a duplicate // Verify different content is not a duplicate
expect(isDuplicateOfLastBotFinal(jid, 'Different message content')).toBe(false); expect(isDuplicateOfLastBotFinal(jid, 'Different message content')).toBe(
false,
);
expect(isDuplicateOfLastBotFinal(jid, 'First message content')).toBe(true); expect(isDuplicateOfLastBotFinal(jid, 'First message content')).toBe(true);
}); });
@@ -288,17 +314,21 @@ describe('isDuplicateOfLastBotFinal (runtime function)', () => {
}); });
// Store a bot message from "codex" (is_from_me=0) // Store a bot message from "codex" (is_from_me=0)
storeMessage(createMessage({ storeMessage(
createMessage({
id: 'msg-1', id: 'msg-1',
chat_jid: jid, chat_jid: jid,
content: 'DONE — Analysis complete', content: 'DONE — Analysis complete',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
is_from_me: false, // Other bot is_from_me: false, // Other bot
is_bot_message: true, is_bot_message: true,
})); }),
);
// Verify claude service detects this as duplicate (cross-bot detection) // Verify claude service detects this as duplicate (cross-bot detection)
expect(isDuplicateOfLastBotFinal(jid, 'DONE — Analysis complete')).toBe(true); expect(isDuplicateOfLastBotFinal(jid, 'DONE — Analysis complete')).toBe(
true,
);
}); });
it('normalization handles whitespace and case differences', () => { it('normalization handles whitespace and case differences', () => {
@@ -323,18 +353,27 @@ describe('isDuplicateOfLastBotFinal (runtime function)', () => {
}); });
// Store a bot message with specific formatting // Store a bot message with specific formatting
storeMessage(createMessage({ storeMessage(
createMessage({
id: 'msg-1', id: 'msg-1',
chat_jid: jid, chat_jid: jid,
content: 'DONE — Task completed\n\nSuccessfully', content: 'DONE — Task completed\n\nSuccessfully',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
is_from_me: true, is_from_me: true,
is_bot_message: true, is_bot_message: true,
})); }),
);
// Same content with different whitespace should be detected as duplicate // Same content with different whitespace should be detected as duplicate
expect(isDuplicateOfLastBotFinal(jid, 'done — task completed successfully')).toBe(true); expect(
expect(isDuplicateOfLastBotFinal(jid, ' DONE Task completed Successfully ')).toBe(true); isDuplicateOfLastBotFinal(jid, 'donetask completed successfully'),
).toBe(true);
expect(
isDuplicateOfLastBotFinal(
jid,
' DONE — Task completed Successfully ',
),
).toBe(true);
// Different content should not be duplicate // Different content should not be duplicate
expect(isDuplicateOfLastBotFinal(jid, 'FAILED — Task failed')).toBe(false); expect(isDuplicateOfLastBotFinal(jid, 'FAILED — Task failed')).toBe(false);
@@ -362,14 +401,16 @@ describe('isDuplicateOfLastBotFinal (runtime function)', () => {
}); });
// Store a bot message (simulating previous delivery) // Store a bot message (simulating previous delivery)
storeMessage(createMessage({ storeMessage(
createMessage({
id: 'msg-1', id: 'msg-1',
chat_jid: jid, chat_jid: jid,
content: 'DONE — Task completed successfully', content: 'DONE — Task completed successfully',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
is_from_me: true, is_from_me: true,
is_bot_message: true, is_bot_message: true,
})); }),
);
// Create a work item with the same content (duplicate) // Create a work item with the same content (duplicate)
const workItem = createProducedWorkItem({ const workItem = createProducedWorkItem({
@@ -416,14 +457,16 @@ describe('isDuplicateOfLastBotFinal (runtime function)', () => {
}); });
// Store a bot message // Store a bot message
storeMessage(createMessage({ storeMessage(
createMessage({
id: 'msg-1', id: 'msg-1',
chat_jid: jid, chat_jid: jid,
content: 'First message', content: 'First message',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
is_from_me: true, is_from_me: true,
is_bot_message: true, is_bot_message: true,
})); }),
);
// Create a work item with DIFFERENT content (non-duplicate) // Create a work item with DIFFERENT content (non-duplicate)
const workItem = createProducedWorkItem({ const workItem = createProducedWorkItem({

View File

@@ -75,7 +75,12 @@ export function resolveServiceGroupSessionsPath(
assertValidGroupFolder(folder); assertValidGroupFolder(folder);
assertValidRuntimeSegment(serviceId, 'service ID'); assertValidRuntimeSegment(serviceId, 'service ID');
const sessionsBaseDir = path.resolve(DATA_DIR, 'sessions'); const sessionsBaseDir = path.resolve(DATA_DIR, 'sessions');
const sessionsPath = path.resolve(sessionsBaseDir, folder, 'services', serviceId); const sessionsPath = path.resolve(
sessionsBaseDir,
folder,
'services',
serviceId,
);
ensureWithinBase(sessionsBaseDir, sessionsPath); ensureWithinBase(sessionsBaseDir, sessionsPath);
return sessionsPath; return sessionsPath;
} }

View File

@@ -71,7 +71,10 @@ import { Channel, NewMessage, RegisteredGroup } from './types.js';
import { logger } from './logger.js'; import { logger } from './logger.js';
import { normalizeStoredSeqCursor } from './message-cursor.js'; import { normalizeStoredSeqCursor } from './message-cursor.js';
import { initCodexTokenRotation } from './codex-token-rotation.js'; import { initCodexTokenRotation } from './codex-token-rotation.js';
import { hasAvailableClaudeToken, initTokenRotation } from './token-rotation.js'; import {
hasAvailableClaudeToken,
initTokenRotation,
} from './token-rotation.js';
import { import {
shouldStartTokenRefreshLoop, shouldStartTokenRefreshLoop,
startTokenRefreshLoop, startTokenRefreshLoop,
@@ -442,7 +445,10 @@ async function main(): Promise<void> {
editFormattedTrackedChannelMessage(channels, jid, messageId, rawText), editFormattedTrackedChannelMessage(channels, jid, messageId, rawText),
}); });
} else { } else {
logger.info({ serviceId: SERVICE_ID }, 'Skipping scheduler for review service'); logger.info(
{ serviceId: SERVICE_ID },
'Skipping scheduler for review service',
);
} }
startIpcWatcher({ startIpcWatcher({
sendMessage: (jid, text) => { sendMessage: (jid, text) => {
@@ -536,7 +542,11 @@ async function main(): Promise<void> {
} }
restoreDefaultChannelLease(lease.chatJid); restoreDefaultChannelLease(lease.chatJid);
logger.info( logger.info(
{ chatJid: lease.chatJid, serviceId: SERVICE_ID, elapsedMin: Math.round(elapsed / 60_000) }, {
chatJid: lease.chatJid,
serviceId: SERVICE_ID,
elapsedMin: Math.round(elapsed / 60_000),
},
'Claude token available and failover hold period elapsed, restored default channel lease', 'Claude token available and failover hold period elapsed, restored default channel lease',
); );
} }

View File

@@ -44,7 +44,8 @@ vi.mock('./logger.js', () => ({
})); }));
vi.mock('./agent-error-detection.js', async (importOriginal) => { vi.mock('./agent-error-detection.js', async (importOriginal) => {
const actual = await importOriginal<typeof import('./agent-error-detection.js')>(); const actual =
await importOriginal<typeof import('./agent-error-detection.js')>();
return { return {
...actual, ...actual,
classifyRotationTrigger: vi.fn((error?: string | null) => { classifyRotationTrigger: vi.fn((error?: string | null) => {
@@ -528,7 +529,8 @@ describe('runAgentForGroup Claude rotation', () => {
await onOutput?.({ await onOutput?.({
status: 'success', status: 'success',
phase: 'final', phase: 'final',
result: 'You\u2019re out of extra usage \u00b7 resets 4am (Asia/Seoul)', result:
'You\u2019re out of extra usage \u00b7 resets 4am (Asia/Seoul)',
}); });
return { return {
status: 'success', status: 'success',

View File

@@ -20,12 +20,19 @@ import {
shouldResetSessionOnAgentFailure, shouldResetSessionOnAgentFailure,
shouldRetryFreshSessionOnAgentFailure, shouldRetryFreshSessionOnAgentFailure,
} from './session-recovery.js'; } from './session-recovery.js';
import { CODEX_MAIN_SERVICE_ID, CODEX_REVIEW_SERVICE_ID, SERVICE_SESSION_SCOPE } from './config.js'; import {
CODEX_MAIN_SERVICE_ID,
CODEX_REVIEW_SERVICE_ID,
SERVICE_SESSION_SCOPE,
} from './config.js';
import { import {
buildSuppressTokenPrompt, buildSuppressTokenPrompt,
classifySuppressTokenOutput, classifySuppressTokenOutput,
} from './output-suppression.js'; } from './output-suppression.js';
import { activateCodexFailover, getEffectiveChannelLease } from './service-routing.js'; import {
activateCodexFailover,
getEffectiveChannelLease,
} from './service-routing.js';
import { import {
evaluateStreamedOutput, evaluateStreamedOutput,
type StreamedOutputState, type StreamedOutputState,
@@ -111,7 +118,8 @@ export async function runAgentForGroup(
const canRotateToken = isClaudeCodeAgent && getTokenCount() > 1; const canRotateToken = isClaudeCodeAgent && getTokenCount() > 1;
const currentLease = getEffectiveChannelLease(chatJid); const currentLease = getEffectiveChannelLease(chatJid);
const reviewerMode = currentLease.reviewer_service_id === SERVICE_SESSION_SCOPE; const reviewerMode =
currentLease.reviewer_service_id === SERVICE_SESSION_SCOPE;
const effectivePrompt = buildSuppressTokenPrompt(prompt, suppressToken, { const effectivePrompt = buildSuppressTokenPrompt(prompt, suppressToken, {
reviewerMode, reviewerMode,
}); });
@@ -569,11 +577,7 @@ export async function runAgentForGroup(
} }
if (primaryAttempt.error) { if (primaryAttempt.error) {
if ( if (canRotateToken && provider === 'claude' && !primaryAttempt.sawOutput) {
canRotateToken &&
provider === 'claude' &&
!primaryAttempt.sawOutput
) {
const errMsg = getErrorMessage(primaryAttempt.error); const errMsg = getErrorMessage(primaryAttempt.error);
const trigger = primaryAttempt.streamedTriggerReason const trigger = primaryAttempt.streamedTriggerReason
? { ? {
@@ -666,11 +670,7 @@ export async function runAgentForGroup(
} }
if (output.status === 'error') { if (output.status === 'error') {
if ( if (canRotateToken && provider === 'claude' && !primaryAttempt.sawOutput) {
canRotateToken &&
provider === 'claude' &&
!primaryAttempt.sawOutput
) {
const trigger = primaryAttempt.streamedTriggerReason const trigger = primaryAttempt.streamedTriggerReason
? { ? {
shouldRetry: true, shouldRetry: true,

View File

@@ -12,7 +12,8 @@ vi.mock('./agent-runner.js', () => ({
})); }));
vi.mock('./output-suppression.js', async (importOriginal) => { vi.mock('./output-suppression.js', async (importOriginal) => {
const actual = await importOriginal<typeof import('./output-suppression.js')>(); const actual =
await importOriginal<typeof import('./output-suppression.js')>();
return { return {
...actual, ...actual,
createSuppressToken: vi.fn(() => '__TEST_SUPPRESS__'), createSuppressToken: vi.fn(() => '__TEST_SUPPRESS__'),

View File

@@ -24,7 +24,11 @@ import {
SERVICE_ID, SERVICE_ID,
} from './config.js'; } from './config.js';
import { GroupQueue, GroupRunContext } from './group-queue.js'; import { GroupQueue, GroupRunContext } from './group-queue.js';
import { findChannel, formatMessages, normalizeMessageForDedupe } from './router.js'; import {
findChannel,
formatMessages,
normalizeMessageForDedupe,
} from './router.js';
import { isTriggerAllowed, loadSenderAllowlist } from './sender-allowlist.js'; import { isTriggerAllowed, loadSenderAllowlist } from './sender-allowlist.js';
import { import {
advanceLastAgentCursor, advanceLastAgentCursor,
@@ -52,7 +56,10 @@ import { shouldServiceProcessChat } from './service-routing.js';
* Check if a message is a duplicate of the last bot final message in a paired room. * Check if a message is a duplicate of the last bot final message in a paired room.
* Exported for testing purposes. * Exported for testing purposes.
*/ */
export function isDuplicateOfLastBotFinal(chatJid: string, text: string): boolean { export function isDuplicateOfLastBotFinal(
chatJid: string,
text: string,
): boolean {
// Only check in paired rooms (both claude and codex registered) // Only check in paired rooms (both claude and codex registered)
if (!isPairedRoomJid(chatJid)) { if (!isPairedRoomJid(chatJid)) {
return false; return false;
@@ -115,7 +122,10 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
* Check if a message is a duplicate of the last bot final message in a paired room. * Check if a message is a duplicate of the last bot final message in a paired room.
* Returns true if duplicate (should be suppressed). * Returns true if duplicate (should be suppressed).
*/ */
const checkDuplicateOfLastBotFinal = (chatJid: string, text: string): boolean => { const checkDuplicateOfLastBotFinal = (
chatJid: string,
text: string,
): boolean => {
return isDuplicateOfLastBotFinal(chatJid, text); return isDuplicateOfLastBotFinal(chatJid, text);
}; };
@@ -129,7 +139,10 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
const replaceMessageId = options?.replaceMessageId ?? null; const replaceMessageId = options?.replaceMessageId ?? null;
// Check for duplicate in paired rooms before attempting delivery // Check for duplicate in paired rooms before attempting delivery
const isDuplicate = checkDuplicateOfLastBotFinal(item.chat_jid, item.result_payload); const isDuplicate = checkDuplicateOfLastBotFinal(
item.chat_jid,
item.result_payload,
);
if (isDuplicate) { if (isDuplicate) {
// Mark as delivered without sending, and don't open continuation // Mark as delivered without sending, and don't open continuation
@@ -263,7 +276,9 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
const isClaudeCodeAgent = const isClaudeCodeAgent =
(group.agentType || 'claude-code') === 'claude-code'; (group.agentType || 'claude-code') === 'claude-code';
const suppressToken = const suppressToken =
isClaudeService() || isReviewService() ? createSuppressToken() : undefined; isClaudeService() || isReviewService()
? createSuppressToken()
: undefined;
const deferTypingUntilVisible = Boolean(suppressToken) && isClaudeService(); const deferTypingUntilVisible = Boolean(suppressToken) && isClaudeService();
const turnController = new MessageTurnController({ const turnController = new MessageTurnController({
@@ -277,7 +292,8 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
deferTypingUntilVisible, deferTypingUntilVisible,
suppressToken, suppressToken,
clearSession: () => deps.clearSession(group.folder), clearSession: () => deps.clearSession(group.folder),
requestClose: (reason) => deps.queue.closeStdin(chatJid, { runId, reason }), requestClose: (reason) =>
deps.queue.closeStdin(chatJid, { runId, reason }),
deliverFinalText: async (text) => { deliverFinalText: async (text) => {
try { try {
const workItem = createProducedWorkItem({ const workItem = createProducedWorkItem({
@@ -352,7 +368,9 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
} }
}; };
const processClaimedHandoff = async (handoff: ServiceHandoff): Promise<void> => { const processClaimedHandoff = async (
handoff: ServiceHandoff,
): Promise<void> => {
const group = deps.getRegisteredGroups()[handoff.chat_jid]; const group = deps.getRegisteredGroups()[handoff.chat_jid];
if (!group) { if (!group) {
failServiceHandoff(handoff.id, 'Group not registered on target service'); failServiceHandoff(handoff.id, 'Group not registered on target service');

View File

@@ -19,8 +19,7 @@ export function shouldEnableSuppressOutputForService(
if (!serviceId) return false; if (!serviceId) return false;
const normalized = normalizeServiceId(serviceId); const normalized = normalizeServiceId(serviceId);
return ( return (
normalized === CLAUDE_SERVICE_ID || normalized === CLAUDE_SERVICE_ID || normalized === CODEX_REVIEW_SERVICE_ID
normalized === CODEX_REVIEW_SERVICE_ID
); );
} }
@@ -29,7 +28,10 @@ export function classifySuppressTokenOutput(
suppressToken: string | undefined, suppressToken: string | undefined,
): 'exact' | 'mixed' | 'none' { ): 'exact' | 'mixed' | 'none' {
const trimmed = rawText.trim(); const trimmed = rawText.trim();
if ((suppressToken && trimmed === suppressToken) || EXACT_ANY_SUPPRESS_TOKEN_PATTERN.test(trimmed)) { if (
(suppressToken && trimmed === suppressToken) ||
EXACT_ANY_SUPPRESS_TOKEN_PATTERN.test(trimmed)
) {
return 'exact'; return 'exact';
} }
if (suppressToken && rawText.includes(suppressToken)) { if (suppressToken && rawText.includes(suppressToken)) {

View File

@@ -99,8 +99,5 @@ export function findChannel(
* - Collapse consecutive whitespace/newlines into single space * - Collapse consecutive whitespace/newlines into single space
*/ */
export function normalizeMessageForDedupe(text: string): string { export function normalizeMessageForDedupe(text: string): string {
return text return text.trim().replace(/\s+/g, ' ').toLowerCase();
.trim()
.replace(/\s+/g, ' ')
.toLowerCase();
} }

View File

@@ -85,7 +85,9 @@ function getDefaultLease(chatJid: string): EffectiveChannelLease {
return { return {
chat_jid: chatJid, chat_jid: chatJid,
owner_service_id: owner_service_id:
SERVICE_AGENT_TYPE === 'codex' ? CODEX_MAIN_SERVICE_ID : CLAUDE_SERVICE_ID, SERVICE_AGENT_TYPE === 'codex'
? CODEX_MAIN_SERVICE_ID
: CLAUDE_SERVICE_ID,
reviewer_service_id: null, reviewer_service_id: null,
activated_at: null, activated_at: null,
reason: null, reason: null,
@@ -106,7 +108,9 @@ export function refreshChannelOwnerCache(force = false): void {
lastLeaseRefreshAt = now; lastLeaseRefreshAt = now;
} }
export function getEffectiveChannelLease(chatJid: string): EffectiveChannelLease { export function getEffectiveChannelLease(
chatJid: string,
): EffectiveChannelLease {
refreshChannelOwnerCache(); refreshChannelOwnerCache();
const row = leaseCache.get(chatJid); const row = leaseCache.get(chatJid);
if (row) { if (row) {
@@ -180,7 +184,10 @@ export function getActiveCodexFailoverLeases(): ActiveFailoverLease[] {
normalizeServiceId(row.reviewer_service_id || '') === normalizeServiceId(row.reviewer_service_id || '') ===
CODEX_MAIN_SERVICE_ID, CODEX_MAIN_SERVICE_ID,
) )
.map((row) => ({ chatJid: row.chat_jid, activatedAt: row.activated_at ?? null })); .map((row) => ({
chatJid: row.chat_jid,
activatedAt: row.activated_at ?? null,
}));
} }
/** @deprecated Use getActiveCodexFailoverLeases() instead */ /** @deprecated Use getActiveCodexFailoverLeases() instead */

View File

@@ -25,7 +25,8 @@ const {
})); }));
vi.mock('./agent-error-detection.js', async (importOriginal) => { vi.mock('./agent-error-detection.js', async (importOriginal) => {
const actual = await importOriginal<typeof import('./agent-error-detection.js')>(); const actual =
await importOriginal<typeof import('./agent-error-detection.js')>();
return { return {
...actual, ...actual,
classifyRotationTrigger: vi.fn((error?: string | null) => { classifyRotationTrigger: vi.fn((error?: string | null) => {
@@ -566,8 +567,7 @@ Check the run.
created_at: '2026-02-22T00:00:00.000Z', created_at: '2026-02-22T00:00:00.000Z',
}); });
(runAgentProcessMock as any) (runAgentProcessMock as any).mockImplementationOnce(
.mockImplementationOnce(
async ( async (
_group: unknown, _group: unknown,
_input: unknown, _input: unknown,

View File

@@ -535,8 +535,7 @@ async function runTask(
} }
}; };
const provider = const provider = context.taskAgentType === 'codex' ? 'codex' : 'claude';
context.taskAgentType === 'codex' ? 'codex' : 'claude';
{ {
const attempt = await runTaskAttempt(provider); const attempt = await runTaskAttempt(provider);