fix(ci): stabilize relay priority tests

This commit is contained in:
777genius 2026-06-02 09:46:43 +03:00
parent d5c40e5a7c
commit 440d37162b
3 changed files with 289 additions and 15 deletions

View file

@ -3444,6 +3444,47 @@ function getOpenCodeInboxRelayPriority(
return 0;
}
function getLeadInboxRelayPriority(message: Pick<InboxMessage, 'messageKind'>): number {
if (message.messageKind === 'member_work_sync_nudge') {
return 30;
}
return 0;
}
function compareInboxRelayMessages(
a: Pick<InboxMessage, 'messageKind' | 'source' | 'timestamp'> & { messageId: string },
b: Pick<InboxMessage, 'messageKind' | 'source' | 'timestamp'> & { messageId: string },
getPriority: (message: Pick<InboxMessage, 'messageKind' | 'source'>) => number
): number {
const priorityDelta = getPriority(b) - getPriority(a);
if (priorityDelta !== 0) return priorityDelta;
const aTime = Date.parse(a.timestamp);
const bTime = Date.parse(b.timestamp);
if (Number.isFinite(aTime) && Number.isFinite(bTime)) {
const timeDelta = aTime - bTime;
if (timeDelta !== 0) return timeDelta;
} else if (Number.isFinite(aTime)) {
return -1;
} else if (Number.isFinite(bTime)) {
return 1;
}
return a.messageId.localeCompare(b.messageId);
}
function compareOpenCodeInboxRelayMessagesByPriority(
a: Pick<InboxMessage, 'messageKind' | 'source' | 'timestamp'> & { messageId: string },
b: Pick<InboxMessage, 'messageKind' | 'source' | 'timestamp'> & { messageId: string }
): number {
return compareInboxRelayMessages(a, b, getOpenCodeInboxRelayPriority);
}
function compareLeadInboxRelayMessagesByPriority(
a: Pick<InboxMessage, 'messageKind' | 'source' | 'timestamp'> & { messageId: string },
b: Pick<InboxMessage, 'messageKind' | 'source' | 'timestamp'> & { messageId: string }
): number {
return compareInboxRelayMessages(a, b, getLeadInboxRelayPriority);
}
export class TeamProvisioningService {
private readonly runtimeLaneCoordinator = createTeamRuntimeLaneCoordinator();
private readonly providerConnectionService = ProviderConnectionService.getInstance();
@ -23567,13 +23608,7 @@ export class TeamProvisioningService {
if (typeof message.text !== 'string' || message.text.trim().length === 0) return false;
return this.hasStableMessageId(message);
})
.sort((a, b) => {
const priorityDelta = getOpenCodeInboxRelayPriority(a) - getOpenCodeInboxRelayPriority(b);
if (priorityDelta !== 0) return priorityDelta;
const timeDelta = Date.parse(a.timestamp) - Date.parse(b.timestamp);
if (timeDelta !== 0) return timeDelta;
return a.messageId.localeCompare(b.messageId);
})
.sort(compareOpenCodeInboxRelayMessagesByPriority)
.slice(0, 10);
let taskRefInferenceTasks: Promise<readonly TeamTask[]> | null = null;
@ -24491,13 +24526,26 @@ export class TeamProvisioningService {
if (actionableUnread.length === 0) return 0;
const MAX_RELAY = 10;
const userOriginatedUnread = actionableUnread.filter((message) =>
const prioritizedActionableUnread = [...actionableUnread].sort(
compareLeadInboxRelayMessagesByPriority
);
const priorityUnread = prioritizedActionableUnread.filter(
(message) => getLeadInboxRelayPriority(message) > 0
);
const userOriginatedUnread = prioritizedActionableUnread.filter((message) =>
this.isUserOriginatedLeadRelayMessage(message)
);
const replyVisibility: 'user' | 'internal_activity' =
userOriginatedUnread.length > 0 ? 'user' : 'internal_activity';
const batchSource = userOriginatedUnread.length > 0 ? userOriginatedUnread : actionableUnread;
const batchSource =
priorityUnread.length > 0
? priorityUnread
: userOriginatedUnread.length > 0
? userOriginatedUnread
: prioritizedActionableUnread;
const batch = batchSource.slice(0, MAX_RELAY);
const replyVisibility: 'user' | 'internal_activity' =
priorityUnread.length === 0 && userOriginatedUnread.length > 0
? 'user'
: 'internal_activity';
const batchIds = new Set(batch.map((message) => message.messageId));
const hasPendingFollowUpRelay = unread.some(
(message) => !batchIds.has(message.messageId) && !readOnlyIgnoredIds.has(message.messageId)
@ -24540,7 +24588,7 @@ export class TeamProvisioningService {
const message = [
`You have new inbox messages addressed to you (team lead "${leadName}").`,
`Process them in order (oldest first).`,
`Process them in the listed order. High-priority work-sync control messages may appear before older routine rows.`,
`If action is required, delegate via task creation or SendMessage, and keep responses minimal.`,
...replyVisibilityInstruction,
`If there is no action to take, produce ZERO text output. Do NOT write "No action needed.", status echoes, or any other no-op summary.`,

View file

@ -220,6 +220,38 @@ type LeadWorkSyncTestInboxMessage = {
messageKind?: string;
taskRefs?: LeadWorkSyncTestTaskRef[];
};
type LeadRelayPriorityTestInboxMessage = LeadWorkSyncTestInboxMessage & {
source?: string;
workSyncIntent?: string;
};
type LeadRelayPriorityTestRun = ReturnType<typeof createMemberSpawnRun> & {
leadRelayCapture?: { resolveOnce(text: string): void } | null;
};
type LeadRelayPriorityServiceHarness = {
runs: Map<string, LeadRelayPriorityTestRun>;
aliveRunByTeam: Map<string, string>;
configReader: {
getConfig(teamName: string): Promise<Record<string, unknown>>;
};
inboxReader: {
getMessagesFor(
teamName: string,
inboxName: string
): Promise<LeadRelayPriorityTestInboxMessage[]>;
};
confirmSameTeamNativeMatches(input: unknown): Promise<{
nativeMatchedMessageIds: Set<string>;
persisted: boolean;
}>;
markInboxMessagesRead(
teamName: string,
inboxName: string,
messages: LeadRelayPriorityTestInboxMessage[]
): Promise<void>;
resolveControlApiBaseUrl(): Promise<string | null>;
scheduleLeadInboxFollowUpRelay(teamName: string): void;
sendMessageToRun(run: LeadRelayPriorityTestRun, message: string): Promise<void>;
};
type LeadWorkSyncReadCommitTestHarness = {
hasAcceptedLeadWorkSyncReport(input: { teamName: string; leadName: string }): Promise<boolean>;
getLeadRelayReadCommitBatch(input: {
@ -11798,6 +11830,88 @@ describe('TeamProvisioningService', () => {
}
});
it('prioritizes OpenCode work-sync nudges over older ordinary inbox rows', async () => {
const svc = new TeamProvisioningService();
const sendMessageToMember = vi.fn(async (input: Record<string, unknown>) => ({
ok: true,
providerId: 'opencode',
memberName: String(input.memberName),
sessionId: 'oc-session-bob',
runtimePromptMessageId: `runtime-${String(input.messageId)}`,
prePromptCursor: 'cursor-before',
responseObservation: {
state: 'responded_non_visible_tool' as const,
deliveredUserMessageId: `oc-user-${String(input.messageId)}`,
assistantMessageId: `oc-assistant-${String(input.messageId)}`,
toolCallNames:
input.messageKind === 'member_work_sync_nudge'
? ['member_work_sync_status', 'member_work_sync_report']
: ['task_get'],
visibleMessageToolCallId: null,
visibleReplyMessageId: null,
visibleReplyCorrelation: null,
latestAssistantPreview: null,
reason: null,
},
diagnostics: [],
}));
await configureOpenCodeBobDeliveryService({ svc, sendMessageToMember });
svc.setMemberWorkSyncAcceptedReportChecker(async () => true);
const inboxDir = path.join(tempTeamsBase, 'team-a', 'inboxes');
await fsPromises.mkdir(inboxDir, { recursive: true });
await fsPromises.writeFile(
path.join(inboxDir, 'bob.json'),
`${JSON.stringify(
[
{
from: 'team-lead',
to: 'bob',
text: 'Older ordinary follow-up.',
timestamp: '2026-04-25T09:00:00.000Z',
read: false,
messageId: 'msg-ordinary-old',
},
{
from: 'system',
to: 'bob',
text: 'Work sync check for #task-1.',
timestamp: '2026-04-25T10:00:00.000Z',
read: false,
messageId: 'msg-work-sync-priority',
source: 'system_notification',
messageKind: 'member_work_sync_nudge',
workSyncIntent: 'agenda_sync',
taskRefs: [
{
taskId: 'task-1',
displayId: 'task-1',
teamName: 'team-a',
},
],
},
],
null,
2
)}\n`,
'utf8'
);
await expect(svc.relayOpenCodeMemberInboxMessages('team-a', 'bob')).resolves.toMatchObject({
attempted: 1,
delivered: 1,
failed: 0,
relayed: 1,
});
expect(sendMessageToMember).toHaveBeenCalledTimes(1);
expect(sendMessageToMember).toHaveBeenCalledWith(
expect.objectContaining({
messageId: 'msg-work-sync-priority',
messageKind: 'member_work_sync_nudge',
})
);
});
it('retries OpenCode direct asks after non-visible tool activity with an explicit retry header', async () => {
const svc = new TeamProvisioningService();
const sendMessageToMember = vi.fn(async (input: Record<string, unknown>) => ({
@ -12536,7 +12650,7 @@ describe('TeamProvisioningService', () => {
responsePending: true,
responseState: 'prompt_delivered_no_assistant_message',
ledgerStatus: 'retry_scheduled',
reason: 'prompt_delivered_no_assistant_message',
reason: 'member_work_sync_report_required',
});
});
@ -24353,6 +24467,114 @@ describe('TeamProvisioningService', () => {
expect(readCommitBatch).toEqual([normalMessage]);
});
it('prioritizes lead work-sync nudges without mixing them into user-visible batches', async () => {
const teamName = 'lead-work-sync-priority-team';
const svc = new TeamProvisioningService();
const harness = svc as unknown as LeadRelayPriorityServiceHarness;
const run = createMemberSpawnRun({
teamName,
expectedMembers: ['alice'],
}) as LeadRelayPriorityTestRun;
run.child = { pid: 123 };
run.processKilled = false;
run.cancelRequested = false;
run.provisioningComplete = true;
const oldUserMessages = Array.from({ length: 10 }, (_, index) => {
const suffix = String(index + 1).padStart(2, '0');
return {
from: 'user',
to: 'team-lead',
text: `Older user request ${suffix}.`,
timestamp: `2026-04-25T09:${suffix}:00.000Z`,
messageId: `msg-user-${suffix}`,
source: 'user_sent',
read: false,
};
});
const workSyncMessage = {
from: 'system',
to: 'team-lead',
text: 'Work sync required for task-1.',
timestamp: '2026-04-25T10:00:00.000Z',
messageId: 'msg-work-sync-priority',
source: 'system_notification',
messageKind: 'member_work_sync_nudge',
workSyncIntent: 'agenda_sync',
taskRefs: [{ taskId: 'task-1', displayId: 'task-1', teamName }],
read: false,
};
const ordinarySystemMessage = {
from: 'system',
to: 'team-lead',
text: 'Routine system notification.',
timestamp: '2026-04-25T09:59:00.000Z',
messageId: 'msg-system-routine',
source: 'system_notification',
read: false,
};
const inboxMessages: LeadRelayPriorityTestInboxMessage[] = [
...oldUserMessages,
ordinarySystemMessage,
workSyncMessage,
];
let deliveredPrompt = '';
const recoveryScheduler = vi.fn(async () => ({
scheduled: true,
reason: 'scheduled',
}));
const sendMessageToRun = vi.fn(
async (targetRun: LeadRelayPriorityTestRun, message: string) => {
deliveredPrompt = message;
targetRun.leadRelayCapture?.resolveOnce('');
}
);
harness.runs.set(run.runId, run);
harness.aliveRunByTeam.set(teamName, run.runId);
harness.configReader = {
getConfig: vi.fn(async () => ({
projectPath: '/repo',
members: [
{ name: 'team-lead', agentType: 'team-lead', role: 'Team Lead' },
{ name: 'alice', role: 'Developer' },
],
})),
};
vi.spyOn(harness.inboxReader, 'getMessagesFor').mockResolvedValue(inboxMessages);
harness.confirmSameTeamNativeMatches = vi.fn(async () => ({
nativeMatchedMessageIds: new Set<string>(),
persisted: true,
}));
harness.markInboxMessagesRead = vi.fn(async () => undefined);
harness.resolveControlApiBaseUrl = vi.fn(async () => null);
harness.scheduleLeadInboxFollowUpRelay = vi.fn();
harness.sendMessageToRun = sendMessageToRun;
svc.setMemberWorkSyncProofMissingRecoveryScheduler(recoveryScheduler);
await expect(svc.relayLeadInboxMessages(teamName)).resolves.toBe(1);
expect(sendMessageToRun).toHaveBeenCalledTimes(1);
const messagesSection = deliveredPrompt.slice(deliveredPrompt.indexOf('Messages:'));
expect(messagesSection).toContain('1) From: system');
expect(messagesSection).toContain('Message kind: member_work_sync_nudge');
expect(messagesSection).toContain('Work sync required for task-1.');
expect(messagesSection).not.toContain('Older user request 01.');
expect(messagesSection).not.toContain('Older user request 10.');
expect(messagesSection).not.toContain('Routine system notification.');
expect(deliveredPrompt).toContain(
'Plain text reply visibility for this batch: internal lead activity only.'
);
expect(recoveryScheduler).toHaveBeenCalledWith({
teamName,
memberName: 'team-lead',
originalMessageId: 'msg-work-sync-priority',
taskRefs: [{ taskId: 'task-1', displayId: 'task-1', teamName }],
reason: 'lead_member_work_sync_report_required',
});
expect(harness.scheduleLeadInboxFollowUpRelay).toHaveBeenCalledWith(teamName);
});
it('read-commits lead work-sync inbox rows after accepted report proof', async () => {
const svc = new TeamProvisioningService();
const harness = leadWorkSyncReadCommitHarness(svc);

View file

@ -228,11 +228,15 @@ describe('useRuntimeProviderManagement', () => {
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(ConfigurableHarness, { enabled: true }));
await Promise.resolve();
});
await vi.waitFor(() => {
expect(state?.error ?? '').toContain('wrong runtime binary');
await act(async () => {
await vi.waitFor(() => {
expect(state?.error ?? '').toContain('wrong runtime binary');
});
});
expect(state?.errorDiagnostics?.binaryPath).toBe('/opt/homebrew/bin/opencode');
await act(async () => {