Simplify paired turn routing and prompt builders

This commit is contained in:
ejclaw
2026-04-06 22:44:38 +09:00
parent f22947ba61
commit b62eebc25b
7 changed files with 519 additions and 227 deletions

View File

@@ -4,7 +4,11 @@ import { normalizeStoredSeqCursor } from './message-cursor.js';
import { isTriggerAllowed, loadSenderAllowlist } from './sender-allowlist.js';
import { isTaskStatusControlMessage } from './task-watch-status.js';
import { ARBITER_AGENT_TYPE, REVIEWER_AGENT_TYPE } from './config.js';
import { hasReviewerLease } from './service-routing.js';
import {
hasReviewerLease,
resolveLeaseServiceId,
type EffectiveChannelLease,
} from './service-routing.js';
import {
resolveAgentTypeForRole,
resolveRoleAgentPlan,
@@ -14,6 +18,8 @@ import {
type AgentType,
type Channel,
type NewMessage,
type PairedRoomRole,
type PairedTaskStatus,
type RegisteredGroup,
} from './types.js';
@@ -55,6 +61,36 @@ export function resolveActiveRole(
}
}
export type NextTurnAction =
| { kind: 'none' }
| { kind: 'reviewer-turn' }
| { kind: 'arbiter-turn' }
| { kind: 'owner-follow-up' }
| { kind: 'finalize-owner-turn' };
export function resolveNextTurnAction(args: {
taskStatus?: PairedTaskStatus | null;
lastTurnOutputRole?: PairedRoomRole | null;
}): NextTurnAction {
switch (args.taskStatus) {
case 'review_ready':
case 'in_review':
return { kind: 'reviewer-turn' };
case 'arbiter_requested':
case 'in_arbitration':
return { kind: 'arbiter-turn' };
case 'merge_ready':
return { kind: 'finalize-owner-turn' };
case 'active':
return args.lastTurnOutputRole === 'reviewer' ||
args.lastTurnOutputRole === 'arbiter'
? { kind: 'owner-follow-up' }
: { kind: 'none' };
default:
return { kind: 'none' };
}
}
/** Cursor key for a role. Owner uses chatJid, others use chatJid:role. */
export function resolveCursorKey(
chatJid: string,
@@ -102,6 +138,69 @@ export function resolveSessionFolder(
return `${groupFolder}:${role}`;
}
export interface ExecutionTargetResolution {
inferredRole: PairedRoomRole;
canHonorForcedRole: boolean;
activeRole: PairedRoomRole;
effectiveServiceId: string;
reviewerServiceId: string | null;
arbiterServiceId: string | null;
roleAgentPlan: RoleAgentPlan;
configuredAgentType: AgentType;
effectiveAgentType: AgentType;
sessionFolder: string;
}
export function resolveExecutionTarget(args: {
lease: EffectiveChannelLease;
pairedTaskStatus?: PairedTaskStatus | null;
groupFolder: string;
groupAgentType?: AgentType;
forcedRole?: PairedRoomRole;
forcedAgentType?: AgentType;
}): ExecutionTargetResolution {
const inferredRole = resolveActiveRole(args.pairedTaskStatus);
const canHonorForcedRole = Boolean(
args.forcedRole === 'owner' ||
(args.forcedRole === 'reviewer' && args.lease.reviewer_agent_type) ||
(args.forcedRole === 'arbiter' && args.lease.arbiter_agent_type),
);
const activeRole = canHonorForcedRole ? args.forcedRole! : inferredRole;
const effectiveServiceId = resolveLeaseServiceId(args.lease, activeRole);
if (!effectiveServiceId) {
throw new Error(`Missing runtime service id for ${activeRole} lease`);
}
const reviewerServiceId = resolveLeaseServiceId(args.lease, 'reviewer');
const arbiterServiceId = resolveLeaseServiceId(args.lease, 'arbiter');
const roleAgentPlan = resolveConfiguredRoleAgentPlan(
args.lease.reviewer_agent_type != null,
args.groupAgentType,
);
const configuredAgentType = resolveEffectiveAgentType(
activeRole,
args.groupAgentType,
);
const effectiveAgentType = args.forcedAgentType ?? configuredAgentType;
return {
inferredRole,
canHonorForcedRole,
activeRole,
effectiveServiceId,
reviewerServiceId,
arbiterServiceId,
roleAgentPlan,
configuredAgentType,
effectiveAgentType,
sessionFolder: resolveSessionFolder(
args.groupFolder,
activeRole,
args.groupAgentType,
),
};
}
export function createImplicitContinuationTracker(idleTimeout: number) {
const implicitContinuationUntil = new Map<string, number>();