fix(opencode): queue specific relay behind active relay

This commit is contained in:
777genius 2026-05-14 02:14:09 +03:00
parent bc571f5fc7
commit e016515024
2 changed files with 115 additions and 82 deletions

View file

@ -21748,10 +21748,9 @@ export class TeamProvisioningService {
const relayKey = this.getOpenCodeMemberRelayKey(teamName, memberName);
const existing = this.openCodeMemberInboxRelayInFlight.get(relayKey);
if (existing) {
const existingResult = await existing;
const onlyMessageId = options.onlyMessageId?.trim();
if (!onlyMessageId) {
return existingResult;
return existing;
}
const inboxMessages = await this.inboxReader
.getMessagesFor(teamName, memberName)
@ -21764,7 +21763,6 @@ export class TeamProvisioningService {
delivered: 1,
failed: 0,
lastDelivery: { delivered: true },
diagnostics: existingResult.diagnostics,
};
}
if (!targetMessage) {
@ -21782,6 +21780,29 @@ export class TeamProvisioningService {
diagnostics: [diagnostic],
};
}
const diagnostic = `opencode_inbox_relay_queued_behind_active_relay: ${relayKey}/${onlyMessageId}`;
this.scheduleOpenCodeMemberInboxDeliveryWake({
teamName,
memberName,
messageId: onlyMessageId,
delayMs: 500,
});
return {
relayed: 0,
attempted: 1,
delivered: 0,
failed: 0,
lastDelivery: {
delivered: true,
accepted: false,
responsePending: true,
queuedBehindMessageId: onlyMessageId,
reason: 'opencode_inbox_relay_queued_behind_active_relay',
diagnostics: [diagnostic],
},
diagnostics: [diagnostic],
};
}
const work = (async (): Promise<OpenCodeMemberInboxRelayResult> => {

View file

@ -2706,90 +2706,102 @@ Messages:
}
});
it('does not let an older in-flight OpenCode relay mask a specific UI-send message', async () => {
it('queues a specific OpenCode relay behind an active member relay without duplicate prompts', async () => {
vi.useFakeTimers();
const service = new TeamProvisioningService();
const teamName = 'my-team';
hoisted.files.set(
`/mock/teams/${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' },
],
})
);
seedMemberInbox(teamName, 'jack', [
{
from: 'bob',
to: 'jack',
text: 'Older watcher message.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-inflight-old',
},
]);
try {
hoisted.files.set(
`/mock/teams/${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' },
],
})
);
seedMemberInbox(teamName, 'jack', [
{
from: 'bob',
to: 'jack',
text: 'Older watcher message.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-inflight-old',
},
]);
const oldDeliveryStarted = createDeferred<void>();
const releaseOldDelivery = createDeferred<void>();
const deliverSpy = vi
.spyOn(service, 'deliverOpenCodeMemberMessage')
.mockImplementation(async (_teamName, input) => {
if (input.messageId === 'opencode-inflight-old') {
oldDeliveryStarted.resolve(undefined);
await releaseOldDelivery.promise;
}
return { delivered: true, diagnostics: [] };
const oldDeliveryStarted = createDeferred<void>();
const releaseOldDelivery = createDeferred<void>();
const deliverSpy = vi
.spyOn(service, 'deliverOpenCodeMemberMessage')
.mockImplementation(async (_teamName, input) => {
if (input.messageId === 'opencode-inflight-old') {
oldDeliveryStarted.resolve(undefined);
await releaseOldDelivery.promise;
}
return { delivered: true, diagnostics: [] };
});
const watcherRelay = service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
await oldDeliveryStarted.promise;
seedMemberInbox(teamName, 'jack', [
{
from: 'bob',
to: 'jack',
text: 'Older watcher message.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-inflight-old',
},
{
from: 'user',
to: 'jack',
text: 'New UI message.',
timestamp: '2026-02-23T17:00:01.000Z',
read: false,
messageId: 'opencode-inflight-new',
},
]);
await expect(
service.relayOpenCodeMemberInboxMessages(teamName, 'jack', {
onlyMessageId: 'opencode-inflight-new',
source: 'ui-send',
deliveryMetadata: { replyRecipient: 'user' },
})
).resolves.toMatchObject({
attempted: 1,
delivered: 0,
failed: 0,
lastDelivery: {
delivered: true,
accepted: false,
responsePending: true,
queuedBehindMessageId: 'opencode-inflight-new',
reason: 'opencode_inbox_relay_queued_behind_active_relay',
},
});
releaseOldDelivery.resolve(undefined);
const watcherRelay = service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
await oldDeliveryStarted.promise;
seedMemberInbox(teamName, 'jack', [
{
from: 'bob',
to: 'jack',
text: 'Older watcher message.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-inflight-old',
},
{
from: 'user',
to: 'jack',
text: 'New UI message.',
timestamp: '2026-02-23T17:00:01.000Z',
read: false,
messageId: 'opencode-inflight-new',
},
]);
const uiRelay = service.relayOpenCodeMemberInboxMessages(teamName, 'jack', {
onlyMessageId: 'opencode-inflight-new',
source: 'ui-send',
deliveryMetadata: { replyRecipient: 'user' },
});
releaseOldDelivery.resolve(undefined);
await expect(watcherRelay).resolves.toMatchObject({
attempted: 1,
delivered: 1,
});
await expect(uiRelay).resolves.toMatchObject({
attempted: 1,
delivered: 1,
failed: 0,
});
expect(deliverSpy).toHaveBeenCalledWith(
teamName,
expect.objectContaining({ messageId: 'opencode-inflight-old' })
);
expect(deliverSpy).toHaveBeenCalledWith(
teamName,
expect.objectContaining({ messageId: 'opencode-inflight-new' })
);
const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]');
expect(rows.map((row: { read?: boolean }) => row.read)).toEqual([true, true]);
await expect(watcherRelay).resolves.toMatchObject({
attempted: 1,
delivered: 1,
});
expect(deliverSpy).toHaveBeenCalledTimes(1);
expect(deliverSpy).toHaveBeenCalledWith(
teamName,
expect.objectContaining({ messageId: 'opencode-inflight-old' })
);
const rows = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]'
);
expect(rows.map((row: { read?: boolean }) => row.read)).toEqual([true, false]);
} finally {
vi.useRealTimers();
}
});
it('treats an already-read specific OpenCode inbox row as delivered for UI-send relay', async () => {