fix(member-work-sync): avoid self-blocking proof recovery
This commit is contained in:
parent
39c52c3847
commit
d048113c1d
4 changed files with 99 additions and 1 deletions
|
|
@ -485,6 +485,7 @@ export class MemberWorkSyncNudgeDispatcher {
|
|||
memberName: item.memberName,
|
||||
nowIso,
|
||||
workSyncIntent: item.payload.workSyncIntent,
|
||||
workSyncIntentKey: item.payload.workSyncIntentKey,
|
||||
taskRefs: item.payload.taskRefs,
|
||||
});
|
||||
if (busy?.busy) {
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ export interface MemberWorkSyncBusySignalPort {
|
|||
memberName: string;
|
||||
nowIso: string;
|
||||
workSyncIntent?: MemberWorkSyncOutboxItem['payload']['workSyncIntent'];
|
||||
workSyncIntentKey?: MemberWorkSyncOutboxItem['payload']['workSyncIntentKey'];
|
||||
taskRefs?: MemberWorkSyncOutboxItem['payload']['taskRefs'];
|
||||
}): Promise<{ busy: boolean; reason?: string; retryAfterIso?: string }>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12753,6 +12753,7 @@ export class TeamProvisioningService {
|
|||
memberName: string;
|
||||
nowIso: string;
|
||||
workSyncIntent?: 'agenda_sync' | 'review_pickup';
|
||||
workSyncIntentKey?: string;
|
||||
taskRefs?: TaskRef[];
|
||||
}): Promise<{
|
||||
busy: boolean;
|
||||
|
|
@ -12785,7 +12786,9 @@ export class TeamProvisioningService {
|
|||
(message) => message.messageKind !== 'member_work_sync_nudge'
|
||||
);
|
||||
const blockingForegroundMessages = foregroundMessages.filter(
|
||||
(message) => !this.isCurrentReviewPickupRequestForegroundMessage(message, input)
|
||||
(message) =>
|
||||
!this.isCurrentReviewPickupRequestForegroundMessage(message, input) &&
|
||||
!this.isCurrentProofMissingRecoveryForegroundMessage(message, input)
|
||||
);
|
||||
const unreadForeground = blockingForegroundMessages.find(
|
||||
(message) =>
|
||||
|
|
@ -22214,6 +22217,24 @@ export class TeamProvisioningService {
|
|||
);
|
||||
}
|
||||
|
||||
private isCurrentProofMissingRecoveryForegroundMessage(
|
||||
message: InboxMessage,
|
||||
input: { workSyncIntent?: 'agenda_sync' | 'review_pickup'; workSyncIntentKey?: string }
|
||||
): boolean {
|
||||
if (input.workSyncIntent !== 'agenda_sync') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const prefix = 'proof-missing:';
|
||||
const intentKey = input.workSyncIntentKey?.trim();
|
||||
if (!intentKey?.startsWith(prefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const originalMessageId = intentKey.slice(prefix.length).trim();
|
||||
return this.hasStableMessageId(message) && message.messageId.trim() === originalMessageId;
|
||||
}
|
||||
|
||||
private openCodeReviewPickupRequestTextMentionsTask(input: {
|
||||
summary: string;
|
||||
text: string;
|
||||
|
|
|
|||
|
|
@ -3127,6 +3127,81 @@ Messages:
|
|||
});
|
||||
});
|
||||
|
||||
it('does not let proof-missing recovery get blocked by its original unread message', async () => {
|
||||
const service = new TeamProvisioningService();
|
||||
const teamName = 'my-team';
|
||||
const laneId = 'secondary:opencode:jack';
|
||||
const teamsBasePath = getTeamsBasePath();
|
||||
hoisted.files.set(
|
||||
`${teamsBasePath}/${teamName}/config.json`,
|
||||
JSON.stringify({
|
||||
name: teamName,
|
||||
projectPath: '/tmp/my-team',
|
||||
members: [
|
||||
{ name: 'team-lead', agentType: 'team-lead' },
|
||||
{ name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' },
|
||||
],
|
||||
})
|
||||
);
|
||||
hoisted.files.set(
|
||||
`${teamsBasePath}/${teamName}/inboxes/jack.json`,
|
||||
JSON.stringify([
|
||||
{
|
||||
from: 'user',
|
||||
to: 'jack',
|
||||
text: 'Please check the current issue.',
|
||||
timestamp: '2026-02-23T17:31:00.000Z',
|
||||
read: false,
|
||||
messageId: 'foreground-message-1',
|
||||
messageKind: 'direct',
|
||||
},
|
||||
])
|
||||
);
|
||||
(service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({
|
||||
ok: true,
|
||||
canonicalMemberName: 'jack',
|
||||
laneId,
|
||||
}));
|
||||
vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({
|
||||
version: 1,
|
||||
updatedAt: '2026-02-23T17:30:00.000Z',
|
||||
lanes: {
|
||||
[laneId]: {
|
||||
laneId,
|
||||
state: 'active',
|
||||
updatedAt: '2026-02-23T17:30:00.000Z',
|
||||
},
|
||||
},
|
||||
});
|
||||
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
|
||||
getActiveForMember: vi.fn(async () => null),
|
||||
});
|
||||
|
||||
const sameMessageRecoveryBusy = await service.getOpenCodeMemberDeliveryBusyStatus({
|
||||
teamName,
|
||||
memberName: 'jack',
|
||||
nowIso: '2026-02-23T17:31:10.000Z',
|
||||
workSyncIntent: 'agenda_sync',
|
||||
workSyncIntentKey: 'proof-missing:foreground-message-1',
|
||||
});
|
||||
|
||||
expect(sameMessageRecoveryBusy).toEqual({ busy: false });
|
||||
|
||||
const unrelatedRecoveryBusy = await service.getOpenCodeMemberDeliveryBusyStatus({
|
||||
teamName,
|
||||
memberName: 'jack',
|
||||
nowIso: '2026-02-23T17:31:10.000Z',
|
||||
workSyncIntent: 'agenda_sync',
|
||||
workSyncIntentKey: 'proof-missing:another-message',
|
||||
});
|
||||
|
||||
expect(unrelatedRecoveryBusy).toMatchObject({
|
||||
busy: true,
|
||||
reason: 'opencode_foreground_inbox_unread',
|
||||
activeMessageId: 'foreground-message-1',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not treat the current unread OpenCode review request as busy for review-pickup checks', async () => {
|
||||
const service = new TeamProvisioningService();
|
||||
const teamName = 'my-team';
|
||||
|
|
|
|||
Loading…
Reference in a new issue