Files
EJClaw/src/message-runtime-handoffs.ts
2026-04-07 23:45:53 +09:00

174 lines
4.8 KiB
TypeScript

import { getErrorMessage } from './utils.js';
import {
claimServiceHandoff,
completeServiceHandoffAndAdvanceTargetCursor,
failServiceHandoff,
getAllPendingServiceHandoffs,
type ServiceHandoff,
} from './db.js';
import { findChannel, findChannelByName } from './router.js';
import { logger } from './logger.js';
import {
getFixedRoleChannelName,
getMissingRoleChannelMessage,
resolveHandoffCursorKey,
resolveHandoffRoleOverride,
} from './message-runtime-shared.js';
import type {
AgentType,
Channel,
PairedRoomRole,
RegisteredGroup,
} from './types.js';
type ExecuteTurnFn = (args: {
group: RegisteredGroup;
prompt: string;
chatJid: string;
runId: string;
channel: Channel;
startSeq: number | null;
endSeq: number | null;
deliveryRole?: PairedRoomRole;
forcedRole?: PairedRoomRole;
forcedAgentType?: AgentType;
}) => Promise<{
outputStatus: 'success' | 'error';
deliverySucceeded: boolean;
visiblePhase: unknown;
}>;
export function enqueuePendingHandoffs(args: {
enqueueTask: (
chatJid: string,
taskId: string,
task: () => Promise<void>,
) => void;
processClaimedHandoff: (handoff: ServiceHandoff) => Promise<void>;
}): void {
for (const handoff of getAllPendingServiceHandoffs()) {
if (!claimServiceHandoff(handoff.id)) {
continue;
}
args.enqueueTask(handoff.chat_jid, `handoff:${handoff.id}`, async () => {
await args.processClaimedHandoff(handoff);
});
}
}
export async function processClaimedHandoff(args: {
handoff: ServiceHandoff;
getRegisteredGroups: () => Record<string, RegisteredGroup>;
channels: Channel[];
executeTurn: ExecuteTurnFn;
lastAgentTimestamps: Record<string, string>;
saveState: () => void;
}): Promise<void> {
const { handoff } = args;
const group = args.getRegisteredGroups()[handoff.chat_jid];
if (!group) {
failServiceHandoff(handoff.id, 'Group not registered on target service');
return;
}
const channel = findChannel(args.channels, handoff.chat_jid);
if (!channel) {
failServiceHandoff(handoff.id, 'No channel owns handoff jid');
return;
}
const handoffRole = resolveHandoffRoleOverride(handoff);
let handoffChannel = channel;
if (handoffRole === 'reviewer') {
const reviewerChannel = findChannelByName(
args.channels,
getFixedRoleChannelName('reviewer'),
);
if (!reviewerChannel) {
failServiceHandoff(handoff.id, getMissingRoleChannelMessage('reviewer'));
return;
}
handoffChannel = reviewerChannel;
} else if (handoffRole === 'arbiter') {
const arbiterChannel = findChannelByName(
args.channels,
getFixedRoleChannelName('arbiter'),
);
if (!arbiterChannel) {
failServiceHandoff(handoff.id, getMissingRoleChannelMessage('arbiter'));
return;
}
handoffChannel = arbiterChannel;
}
const runId = `handoff-${handoff.id}`;
try {
logger.info(
{
chatJid: handoff.chat_jid,
handoffId: handoff.id,
runId,
handoffRole,
targetRole: handoff.target_role ?? null,
targetServiceId: handoff.target_service_id,
targetAgentType: handoff.target_agent_type,
reason: handoff.reason,
intendedRole: handoff.intended_role ?? null,
channelName: handoffChannel.name,
},
'Dispatching claimed service handoff',
);
const result = await args.executeTurn({
group,
prompt: handoff.prompt,
chatJid: handoff.chat_jid,
runId,
channel: handoffChannel,
startSeq: handoff.start_seq,
endSeq: handoff.end_seq,
forcedRole: handoffRole,
forcedAgentType: handoff.target_agent_type,
});
if (!result.deliverySucceeded) {
failServiceHandoff(handoff.id, 'Handoff delivery failed');
return;
}
const cursorKey = resolveHandoffCursorKey(handoff.chat_jid, handoffRole);
const appliedCursor = completeServiceHandoffAndAdvanceTargetCursor({
id: handoff.id,
chat_jid: handoff.chat_jid,
cursor_key: cursorKey,
end_seq: handoff.end_seq,
});
if (appliedCursor) {
args.lastAgentTimestamps[cursorKey] = appliedCursor;
args.saveState();
}
logger.info(
{
chatJid: handoff.chat_jid,
handoffId: handoff.id,
runId,
outputStatus: result.outputStatus,
visiblePhase: result.visiblePhase,
appliedCursor,
cursorKey:
appliedCursor != null
? resolveHandoffCursorKey(handoff.chat_jid, handoffRole)
: null,
},
'Completed claimed service handoff',
);
} catch (err) {
const errorMessage = getErrorMessage(err);
failServiceHandoff(handoff.id, errorMessage);
logger.error(
{ chatJid: handoff.chat_jid, handoffId: handoff.id, err },
'Claimed service handoff failed',
);
}
}