agent-ecosystem/test/main/services/team/TeamProvisioningServiceRelay.test.ts

5931 lines
196 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* eslint-disable @typescript-eslint/no-explicit-any */
import { promises as fsPromises } from 'fs';
import { beforeEach, describe, expect, it, vi } from 'vitest';
const hoisted = vi.hoisted(() => {
const files = new Map<string, string>();
let atomicWriteShouldFail = false;
// Normalize path separators so tests pass on Windows (backslash → forward slash)
const norm = (p: string): string => p.replace(/\\/g, '/');
const stat = vi.fn(async (filePath: string) => {
const data = files.get(norm(filePath));
if (data === undefined) {
const error = new Error('ENOENT') as NodeJS.ErrnoException;
error.code = 'ENOENT';
throw error;
}
const size = Buffer.byteLength(data, 'utf8');
return {
isFile: () => true,
size,
mode: 0o100644,
dev: 1,
ino: 1,
mtimeMs: 1,
ctimeMs: 1,
birthtimeMs: 1,
mtimeNs: 1n,
ctimeNs: 1n,
birthtimeNs: 1n,
};
});
const readFile = vi.fn(async (filePath: string) => {
const data = files.get(norm(filePath));
if (data === undefined) {
const error = new Error('ENOENT') as NodeJS.ErrnoException;
error.code = 'ENOENT';
throw error;
}
return data;
});
const atomicWrite = vi.fn(async (filePath: string, data: string) => {
if (atomicWriteShouldFail) {
throw new Error('atomic write failed');
}
files.set(norm(filePath), data);
});
const mkdir = vi.fn(async () => undefined);
return {
files,
stat,
readFile,
mkdir,
atomicWrite,
appendSentMessage: vi.fn((teamName: string, message: Record<string, unknown>) => {
const sentMessagesPath = `/mock/teams/${teamName}/sentMessages.json`;
const current = files.get(sentMessagesPath);
const rows = current ? (JSON.parse(current) as unknown[]) : [];
rows.push(message);
files.set(sentMessagesPath, JSON.stringify(rows));
return message;
}),
sendInboxMessage: vi.fn((teamName: string, message: Record<string, unknown>) => {
const member =
typeof message.member === 'string'
? message.member
: typeof message.to === 'string'
? message.to
: 'unknown';
const p = `/mock/teams/${teamName}/inboxes/${member}.json`;
const current = files.get(p);
const rows = current ? (JSON.parse(current) as unknown[]) : [];
rows.push(message);
files.set(p, JSON.stringify(rows));
return { deliveredToInbox: true, messageId: 'mock-id', message };
}),
setAtomicWriteShouldFail: (next: boolean) => {
atomicWriteShouldFail = next;
},
};
});
vi.mock('fs', async (importOriginal) => {
const actual = await importOriginal<typeof import('fs')>();
return {
...actual,
promises: {
...actual.promises,
stat: hoisted.stat,
readFile: hoisted.readFile,
mkdir: hoisted.mkdir,
},
};
});
vi.mock('node:fs/promises', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:fs/promises')>();
return {
...actual,
stat: hoisted.stat,
readFile: hoisted.readFile,
mkdir: hoisted.mkdir,
};
});
vi.mock('../../../../src/main/services/team/atomicWrite', () => ({
atomicWriteAsync: hoisted.atomicWrite,
}));
vi.mock('../../../../src/main/services/team/fileLock', () => ({
withFileLock: async (_filePath: string, fn: () => Promise<unknown>) => await fn(),
withFileLockSync: (_filePath: string, fn: () => unknown) => fn(),
}));
vi.mock('../../../../src/main/services/team/inboxLock', () => ({
withInboxLock: async (_filePath: string, fn: () => Promise<unknown>) => await fn(),
}));
vi.mock('../../../../src/main/utils/pathDecoder', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../../../src/main/utils/pathDecoder')>();
return {
...actual,
getTeamsBasePath: () => '/mock/teams',
};
});
vi.mock('../../../../src/main/utils/fsRead', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../../../src/main/utils/fsRead')>();
return {
...actual,
readFileUtf8WithTimeout: hoisted.readFile,
};
});
vi.mock('agent-teams-controller', () => ({
AGENT_TEAMS_TEAMMATE_OPERATIONAL_TOOL_NAMES: [] as readonly string[],
AGENT_TEAMS_NAMESPACED_LEAD_BOOTSTRAP_TOOL_NAMES: [] as readonly string[],
AGENT_TEAMS_NAMESPACED_TEAMMATE_OPERATIONAL_TOOL_NAMES: [] as readonly string[],
createController: ({ teamName }: { teamName: string }) => ({
messages: {
appendSentMessage: (message: Record<string, unknown>) =>
hoisted.appendSentMessage(teamName, message),
sendMessage: (message: Record<string, unknown>) =>
hoisted.sendInboxMessage(teamName, message),
},
}),
protocols: {
buildActionModeProtocolText: (delegate: string) =>
`ACTION MODE PROTOCOL (mock, delegate: ${delegate})`,
buildProcessProtocolText: (teamName: string) =>
`BACKGROUND PROCESS REGISTRATION (mock for ${teamName})`,
},
}));
import { buildLegacyInboxMessageId } from '../../../../src/main/services/team/inboxMessageIdentity';
import * as OpenCodeRuntimeStore from '../../../../src/main/services/team/opencode/store/OpenCodeRuntimeManifestEvidenceReader';
import { TeamRuntimeAdapterRegistry } from '../../../../src/main/services/team/runtime';
import { TeamConfigReader } from '../../../../src/main/services/team/TeamConfigReader';
import { TeamProvisioningService } from '../../../../src/main/services/team/TeamProvisioningService';
import { getTeamsBasePath } from '../../../../src/main/utils/pathDecoder';
function seedConfig(teamName: string): void {
hoisted.files.set(
`/mock/teams/${teamName}/config.json`,
JSON.stringify({
name: 'My Team',
members: [{ name: 'team-lead', agentType: 'team-lead' }],
})
);
}
function seedLeadInbox(teamName: string, messages: unknown[]): void {
hoisted.files.set(`/mock/teams/${teamName}/inboxes/team-lead.json`, JSON.stringify(messages));
}
function seedMemberInbox(teamName: string, memberName: string, messages: unknown[]): void {
hoisted.files.set(`/mock/teams/${teamName}/inboxes/${memberName}.json`, JSON.stringify(messages));
}
function createDeferred<T>(): {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (error: unknown) => void;
} {
let resolve!: (value: T) => void;
let reject!: (error: unknown) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
function attachAliveRun(
service: TeamProvisioningService,
teamName: string,
opts?: { writable?: boolean; runId?: string; provisioningComplete?: boolean }
): { writeSpy: ReturnType<typeof vi.fn>; runId: string } {
const runId = opts?.runId ?? 'run-1';
const writeSpy = vi.fn((_data: unknown, cb?: (err?: Error | null) => void) => {
if (typeof cb === 'function') cb(null);
return true;
});
const writable = opts?.writable ?? true;
(service as unknown as { aliveRunByTeam: Map<string, string> }).aliveRunByTeam.set(
teamName,
runId
);
(service as unknown as { runs: Map<string, unknown> }).runs.set(runId, {
runId,
teamName,
request: {
teamName,
members: [{ name: 'team-lead', role: 'team-lead' }],
},
startedAt: '2026-02-23T09:59:00.000Z',
leadMsgSeq: 0,
pendingToolCalls: [],
activeToolCalls: new Map(),
pendingDirectCrossTeamSendRefresh: false,
lastLeadTextEmitMs: 0,
activeCrossTeamReplyHints: [],
pendingInboxRelayCandidates: [],
pendingApprovals: new Map(),
processedPermissionRequestIds: new Set(),
silentUserDmForward: null,
silentUserDmForwardClearHandle: null,
child: {
stdin: {
writable,
write: writeSpy,
},
},
processKilled: false,
cancelRequested: false,
provisioningComplete: opts?.provisioningComplete ?? true,
leadRelayCapture: null,
});
return { writeSpy, runId };
}
function buildOpenCodeProofMissingRecord(input: {
teamName: string;
memberName: string;
laneId: string;
inboxMessageId: string;
taskRefs: Array<{ teamName: string; taskId: string; displayId: string }>;
}): Record<string, unknown> {
return {
id: `opencode-prompt:${input.inboxMessageId}`,
teamName: input.teamName,
memberName: input.memberName,
laneId: input.laneId,
runId: null,
runtimeSessionId: null,
inboxMessageId: input.inboxMessageId,
inboxTimestamp: '2026-02-23T17:31:00.000Z',
source: 'watcher',
messageKind: 'default',
replyRecipient: 'team-lead',
actionMode: 'do',
taskRefs: input.taskRefs,
payloadHash: 'sha256:test',
status: 'failed_terminal',
responseState: 'responded_non_visible_tool',
attempts: 3,
maxAttempts: 3,
acceptanceUnknown: false,
nextAttemptAt: null,
lastAttemptAt: '2026-02-23T17:31:10.000Z',
lastObservedAt: '2026-02-23T17:31:15.000Z',
acceptedAt: '2026-02-23T17:31:05.000Z',
respondedAt: '2026-02-23T17:31:15.000Z',
failedAt: '2026-02-23T17:31:20.000Z',
inboxReadCommittedAt: null,
inboxReadCommitError: null,
prePromptCursor: null,
postPromptCursor: null,
deliveredUserMessageId: 'msg-user',
observedAssistantMessageId: 'msg-assistant',
observedAssistantPreview: null,
observedToolCallNames: ['task_get', 'glob'],
observedVisibleMessageId: null,
visibleReplyMessageId: null,
visibleReplyInbox: null,
visibleReplyCorrelation: null,
lastReason: 'non_visible_tool_without_task_progress',
diagnostics: ['non_visible_tool_without_task_progress'],
createdAt: '2026-02-23T17:31:00.000Z',
updatedAt: '2026-02-23T17:31:20.000Z',
};
}
function seedOpenCodeBusyStatusFixture(input: {
service: TeamProvisioningService;
teamName: string;
laneId: string;
inboxMessages: unknown[];
memberName?: string;
laneState?: 'active' | 'stopped';
ledgerRecords?: Record<string, unknown>[];
activeRecord?: Record<string, unknown> | null;
}): void {
const memberName = input.memberName ?? 'jack';
const teamsBasePath = getTeamsBasePath();
hoisted.files.set(
`${teamsBasePath}/${input.teamName}/config.json`,
JSON.stringify({
name: input.teamName,
projectPath: '/tmp/my-team',
members: [
{ name: 'team-lead', agentType: 'team-lead' },
{ name: memberName, role: 'developer', providerId: 'opencode', model: 'openrouter/test' },
],
})
);
hoisted.files.set(
`${teamsBasePath}/${input.teamName}/inboxes/${memberName}.json`,
JSON.stringify(input.inboxMessages)
);
(input.service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({
ok: true,
canonicalMemberName: memberName,
laneId: input.laneId,
}));
vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({
version: 1,
updatedAt: '2026-02-23T17:30:00.000Z',
lanes: {
[input.laneId]: {
laneId: input.laneId,
state: input.laneState ?? 'active',
updatedAt: '2026-02-23T17:30:00.000Z',
},
},
});
vi.spyOn(input.service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
list: vi.fn(async () => input.ledgerRecords ?? []),
getActiveForMember: vi.fn(async () => input.activeRecord ?? null),
});
}
async function waitForCapture(service: TeamProvisioningService): Promise<any> {
const runs = (service as unknown as { runs: Map<string, unknown> }).runs;
const run = runs.get('run-1') as any;
for (let i = 0; i < 50; i++) {
if (run?.leadRelayCapture) return run;
// Progress async awaits in relayLeadInboxMessages
await Promise.resolve();
}
for (let i = 0; i < 50; i++) {
if (run?.leadRelayCapture) return run;
await new Promise((r) => setTimeout(r, 0));
}
return run;
}
describe('TeamProvisioningService relayLeadInboxMessages', () => {
beforeEach(() => {
TeamConfigReader.clearCacheForTests();
hoisted.files.clear();
hoisted.readFile.mockClear();
hoisted.mkdir.mockClear();
hoisted.atomicWrite.mockClear();
hoisted.setAtomicWriteShouldFail(false);
hoisted.appendSentMessage.mockClear();
hoisted.sendInboxMessage.mockClear();
hoisted.setAtomicWriteShouldFail(false);
vi.spyOn(fsPromises, 'mkdir').mockImplementation(hoisted.mkdir as never);
});
it('relays unread lead inbox messages into stdin', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'bob',
text: 'Please assign this to Alice.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Need delegation',
messageId: 'm-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
expect(run?.leadRelayCapture).toBeTruthy();
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'OK, will do.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
const relayed = await relayPromise;
expect(relayed).toBe(1);
expect(writeSpy).toHaveBeenCalledTimes(1);
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('"type":"user"');
expect(payload).toContain('Please assign this to Alice.');
expect(service.getLiveLeadProcessMessages(teamName)).toHaveLength(1);
});
it('does not persist echoed lead relay prompts as user-visible replies', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'tom',
text: '#f8d7235a done.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: '#f8d7235a done',
messageId: 'm-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
const payload = JSON.parse(String(writeSpy.mock.calls[0]?.[0] ?? '{}')) as {
message?: { content?: Array<{ text?: string }> };
};
const relayedPrompt = payload.message?.content?.[0]?.text ?? '';
expect(relayedPrompt).toContain('You have new inbox messages addressed to you');
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: `Human: ${relayedPrompt}` }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
expect(service.getLiveLeadProcessMessages(teamName)).toHaveLength(0);
expect(hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`)).toBeUndefined();
});
it('does not persist bare transcript speaker placeholders as lead replies', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'tom',
text: '#f8d7235a done.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: '#f8d7235a done',
messageId: 'm-1',
},
]);
attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Human: ' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
expect(service.getLiveLeadProcessMessages(teamName)).toHaveLength(0);
expect(hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`)).toBeUndefined();
});
it('records non-user lead relay summary text as internal lead activity', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'tom',
text: '#f8d7235a done.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: '#f8d7235a done',
messageId: 'm-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
const payload = JSON.parse(String(writeSpy.mock.calls[0]?.[0] ?? '{}')) as {
message?: { content?: Array<{ text?: string }> };
};
const relayedPrompt = payload.message?.content?.[0]?.text ?? '';
expect(relayedPrompt).toContain(
'Do not use that internal status line to confirm, correct, or relay task, kanban, review, PR, branch, merge, or queue state unless you verified it with the source-of-truth tool in this turn.'
);
expect(relayedPrompt).toContain(
'Treat teammate/system/cross-team claims about task, kanban, review, PR, branch, merge, or queue state as unverified until checked.'
);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: `Human: ${relayedPrompt}\n\nDelegated to bob.` }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
const live = service.getLiveLeadProcessMessages(teamName);
expect(live.map((message) => message.text)).toEqual(['Delegated to bob.']);
expect(live[0]?.to).toBeUndefined();
expect(hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`)).toBeUndefined();
});
it('suppresses unverified non-user lead relay state claims from internal activity', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'tom',
text: '#f8d7235a done.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: '#f8d7235a done',
messageId: 'm-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
const payload = JSON.parse(String(writeSpy.mock.calls[0]?.[0] ?? '{}')) as {
message?: { content?: Array<{ text?: string }> };
};
const relayedPrompt = payload.message?.content?.[0]?.text ?? '';
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [
{
type: 'text',
text:
`Human: ${relayedPrompt}\n\n` +
'Confirmed - both claims in msg 17eb3109 were false. #38730980 already approved and PR #38 is OPEN, mergeCommit=null.',
},
],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
expect(service.getLiveLeadProcessMessages(teamName)).toHaveLength(0);
expect(hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`)).toBeUndefined();
});
it.each([
{
caseName: 'keeps task-ref delegation status',
replyText: 'Delegated #f8d7235a to bob.',
expectedLiveText: 'Delegated #f8d7235a to bob.',
},
{
caseName: 'keeps verification-needed status',
replyText: 'Verification needed before confirming #f8d7235a.',
expectedLiveText: 'Verification needed before confirming #f8d7235a.',
},
{
caseName: 'suppresses completed task claim',
replyText: 'Task #f8d7235a is completed.',
expectedLiveText: null,
},
{
caseName: 'suppresses merged PR claim',
replyText: 'PR #38 merged.',
expectedLiveText: null,
},
{
caseName: 'suppresses queue-clear claim',
replyText: 'Queue genuinely clear for #f8d7235a.',
expectedLiveText: null,
},
])(
'classifies non-user lead relay internal activity: $caseName',
async ({ caseName, replyText, expectedLiveText }) => {
const service = new TeamProvisioningService();
const teamName = `my-team-${caseName.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}`;
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'tom',
text: '#f8d7235a done.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: '#f8d7235a done',
messageId: 'm-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
const payload = JSON.parse(String(writeSpy.mock.calls[0]?.[0] ?? '{}')) as {
message?: { content?: Array<{ text?: string }> };
};
const relayedPrompt = payload.message?.content?.[0]?.text ?? '';
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: `Human: ${relayedPrompt}\n\n${replyText}` }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
const liveTexts = service.getLiveLeadProcessMessages(teamName).map((message) => message.text);
expect(liveTexts).toEqual(expectedLiveText === null ? [] : [expectedLiveText]);
expect(hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`)).toBeUndefined();
}
);
it('keeps user-originated lead relay replies user-visible', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'user',
text: 'Create the docs task.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Docs task',
messageId: 'user-msg-1',
source: 'user_sent',
},
]);
attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Creating the task now.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
const live = service.getLiveLeadProcessMessages(teamName);
expect(live.map((message) => message.text)).toEqual(['Creating the task now.']);
expect(live[0]?.to).toBe('user');
const sentRows = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`) ?? '[]'
) as Array<{
text?: string;
to?: string;
}>;
expect(sentRows).toMatchObject([{ text: 'Creating the task now.', to: 'user' }]);
});
it('does not suppress state-like user-originated lead relay replies', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'user',
text: 'What is the task status?',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Task status',
messageId: 'user-msg-state-like',
source: 'user_sent',
},
]);
attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Task #f8d7235a is completed.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
const live = service.getLiveLeadProcessMessages(teamName);
expect(live.map((message) => ({ to: message.to, text: message.text }))).toEqual([
{ to: 'user', text: 'Task #f8d7235a is completed.' },
]);
const sentRows = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`) ?? '[]'
) as Array<{ text?: string; to?: string }>;
expect(sentRows).toMatchObject([{ to: 'user', text: 'Task #f8d7235a is completed.' }]);
});
it('does not mix internal lead relay rows into a user-visible relay batch', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'bob',
text: 'Internal status for the lead.',
timestamp: '2026-02-23T09:59:00.000Z',
read: false,
summary: 'Internal status',
messageId: 'internal-msg-1',
},
{
from: 'user',
text: 'Please create the release task.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Release task',
messageId: 'user-msg-2',
source: 'user_sent',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Creating the release task.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('Please create the release task.');
expect(payload).not.toContain('Internal status for the lead.');
await vi.waitFor(() => expect(writeSpy.mock.calls.length).toBe(2), { timeout: 1000 });
const followUpRun = await waitForCapture(service);
(service as any).handleStreamJsonMessage(followUpRun, {
type: 'assistant',
content: [{ type: 'text', text: 'Noted internal status.' }],
});
(service as any).handleStreamJsonMessage(followUpRun, { type: 'result', subtype: 'success' });
});
it('relays deferred internal rows on the next pass after a user-visible batch', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'bob',
text: 'Internal status for the lead.',
timestamp: '2026-02-23T09:59:00.000Z',
read: false,
summary: 'Internal status',
messageId: 'internal-msg-next-pass',
source: 'system_notification',
},
{
from: 'user',
text: 'Please create the release task.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Release task',
messageId: 'user-msg-next-pass',
source: 'user_sent',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const firstPromise = service.relayLeadInboxMessages(teamName);
let run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Creating the release task.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(firstPromise).resolves.toBe(1);
await vi.waitFor(() => expect(writeSpy.mock.calls.length).toBe(2), { timeout: 1000 });
run = await waitForCapture(service);
expect(run?.leadRelayCapture).toBeTruthy();
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Noted internal status.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
for (let i = 0; i < 20 && service.getLiveLeadProcessMessages(teamName).length < 2; i++) {
await Promise.resolve();
}
const firstPayload = String(writeSpy.mock.calls[0]?.[0] ?? '');
const secondPayload = String(writeSpy.mock.calls[1]?.[0] ?? '');
expect(firstPayload).toContain('Please create the release task.');
expect(firstPayload).not.toContain('Internal status for the lead.');
expect(secondPayload).toContain('Internal status for the lead.');
const live = service.getLiveLeadProcessMessages(teamName);
expect(live.map((message) => ({ to: message.to, text: message.text }))).toEqual([
{ to: 'user', text: 'Creating the release task.' },
{ to: undefined, text: 'Noted internal status.' },
]);
});
it('does not duplicate relay narration when the lead sends an explicit visible message', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'bob',
text: 'This needs the user to know.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Notify user',
messageId: 'internal-msg-2',
},
]);
attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [
{ type: 'text', text: 'Sending the user update now.' },
{
type: 'tool_use',
name: 'SendMessage',
input: {
recipient: 'user',
content: 'Bob found an issue that needs your attention.',
summary: 'Needs attention',
},
},
],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
const live = service.getLiveLeadProcessMessages(teamName);
expect(live.map((message) => message.text)).toEqual([
'Bob found an issue that needs your attention.',
]);
const sentRows = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`) ?? '[]'
) as Array<{ text?: string }>;
expect(sentRows.map((message) => message.text)).toEqual([
'Bob found an issue that needs your attention.',
]);
});
it('keeps explicit teammate SendMessage from non-user lead relay visible', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'bob',
text: 'Alice should review the release notes.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Ask Alice',
messageId: 'internal-msg-teammate-send',
},
]);
attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [
{ type: 'text', text: 'Sending Alice the handoff.' },
{
type: 'tool_use',
name: 'SendMessage',
input: {
recipient: 'alice',
content: 'Please review the release notes.',
summary: 'Review release notes',
},
},
],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
const live = service.getLiveLeadProcessMessages(teamName);
expect(live.map((message) => ({ to: message.to, text: message.text }))).toEqual([
{ to: 'alice', text: 'Please review the release notes.' },
]);
const aliceInbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]'
) as Array<{ member?: string; text?: string }>;
expect(aliceInbox).toMatchObject([
{ member: 'alice', text: 'Please review the release notes.' },
]);
expect(hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`)).toBeUndefined();
});
it('keeps user-originated plain reply when the lead also messages a teammate', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'user',
text: 'Please ask Alice to review the release notes.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Review release notes',
messageId: 'user-msg-3',
source: 'user_sent',
},
]);
attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [
{ type: 'text', text: 'Asked Alice to review the release notes.' },
{
type: 'tool_use',
name: 'SendMessage',
input: {
recipient: 'alice',
content: 'Please review the release notes.',
summary: 'Review release notes',
},
},
],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
const live = service.getLiveLeadProcessMessages(teamName);
expect(live.map((message) => ({ to: message.to, text: message.text }))).toEqual([
{ to: 'alice', text: 'Please review the release notes.' },
{ to: 'user', text: 'Asked Alice to review the release notes.' },
]);
const sentRows = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`) ?? '[]'
) as Array<{ text?: string; to?: string }>;
expect(sentRows).toMatchObject([
{ to: 'user', text: 'Asked Alice to review the release notes.' },
]);
});
it('treats member work sync nudges as actionable in lead relay prompt', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
service.setControlApiBaseUrlResolver(async () => 'http://127.0.0.1:43123');
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'system',
text: 'Work sync check: you have current actionable work assigned.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Work sync check',
messageId: 'm-work-sync-1',
source: 'system_notification',
messageKind: 'member_work_sync_nudge',
workSyncIntent: 'agenda_sync',
taskRefs: [{ teamName, taskId: 'task-1', displayId: '11111111' }],
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
expect(run?.leadRelayCapture).toBeTruthy();
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('Message kind: member_work_sync_nudge');
expect(payload).toContain('Work-sync intent: agenda_sync');
expect(payload).toContain('it is actionable work-sync control traffic');
expect(payload).toContain('A member_work_sync_status call alone is incomplete');
expect(payload).toContain(
'Call member_work_sync_status with teamName=\\"my-team\\", memberName=\\"team-lead\\", controlUrl=\\"http://127.0.0.1:43123\\"'
);
expect(payload).toContain('call member_work_sync_report');
expect(payload).toContain('controlUrl=\\"http://127.0.0.1:43123\\"');
expect(payload).toContain('taskIds from the nudge task refs');
expect(payload).toContain(
'Do not use provider names, runtime names, or team names as memberName'
);
expect(payload).toContain('Do NOT ignore it as a pure system notification');
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
});
it('uses snapshot config reads for lead inbox relay routing', async () => {
const getConfig = vi.fn(async () => {
throw new Error('verified config read should not be used for inbox relay routing');
});
const getConfigSnapshot = vi.fn(async () => ({
name: 'My Team',
members: [{ name: 'team-lead', agentType: 'team-lead' }],
}));
const service = new TeamProvisioningService({
getConfig,
getConfigSnapshot,
} as any);
const teamName = 'my-team';
seedLeadInbox(teamName, [
{
from: 'bob',
text: 'Please assign this to Alice.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Need delegation',
messageId: 'm-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'OK, will do.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await expect(relayPromise).resolves.toBe(1);
expect(writeSpy).toHaveBeenCalledTimes(1);
expect(getConfigSnapshot).toHaveBeenCalledWith(teamName);
expect(getConfig).not.toHaveBeenCalled();
});
it('shows assistant text after relay capture has already settled', () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
attachAliveRun(service, teamName);
const run = (service as unknown as { runs: Map<string, unknown> }).runs.get('run-1') as {
leadRelayCapture: {
leadName: string;
startedAt: string;
textParts: string[];
settled: boolean;
idleHandle: NodeJS.Timeout | null;
idleMs: number;
resolveOnce: (text: string) => void;
rejectOnce: (error: string) => void;
timeoutHandle: NodeJS.Timeout;
} | null;
};
run.leadRelayCapture = {
leadName: 'team-lead',
startedAt: new Date().toISOString(),
textParts: [],
settled: true,
idleHandle: null,
idleMs: 800,
resolveOnce: vi.fn(),
rejectOnce: vi.fn(),
timeoutHandle: setTimeout(() => undefined, 60_000),
};
try {
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Late reply after relay completion.' }],
});
const live = service.getLiveLeadProcessMessages(teamName);
expect(live).toHaveLength(1);
expect(live[0].to).toBeUndefined();
expect(live[0].text).toBe('Late reply after relay completion.');
expect(live[0].source).toBe('lead_process');
} finally {
clearTimeout(run.leadRelayCapture.timeoutHandle);
run.leadRelayCapture = null;
}
});
it('does not show internal control echoes as late lead thoughts', () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
attachAliveRun(service, teamName);
const run = (service as unknown as { runs: Map<string, unknown> }).runs.get('run-1') as {
leadRelayCapture: null;
};
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [
{
type: 'text',
text: `Human: You have new inbox messages addressed to you (team lead "team-lead").
Process them in order (oldest first).
If action is required, delegate via task creation or SendMessage, and keep responses minimal.
Messages:
1) From: tom
Timestamp: 2026-05-06T15:02:54.853Z
Text:
#f8d7235a done.`,
},
],
});
expect(service.getLiveLeadProcessMessages(teamName)).toHaveLength(0);
});
it('adds substantive-only task comment guidance for lead relay prompts', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'alice',
text: 'Automated task comment notification from @alice on #abcd1234 "Investigate":\n\n> Root cause found.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Comment on #abcd1234',
source: 'system_notification',
messageId: 'm-comment-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
expect(run?.leadRelayCapture).toBeTruthy();
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('Source: system_notification');
expect(payload).toContain('summary looks like \\"Comment on #...\\"');
expect(payload).toContain(
'reply via task_add_comment only when you have a substantive board update'
);
expect(payload).toContain('Do NOT post acknowledgement-only task comments');
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Will reply on the task.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await relayPromise;
});
it('dedups by messageId even if markRead fails', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'bob',
text: 'Ping leader',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Ping',
messageId: 'm-1',
},
]);
hoisted.setAtomicWriteShouldFail(true);
const { writeSpy } = attachAliveRun(service, teamName);
const firstPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
expect(run?.leadRelayCapture).toBeTruthy();
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Acknowledged.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
const first = await firstPromise;
const second = await service.relayLeadInboxMessages(teamName);
expect(first).toBe(1);
expect(second).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(1);
expect(hoisted.appendSentMessage).not.toHaveBeenCalled();
expect(service.getLiveLeadProcessMessages(teamName).map((message) => message.text)).toEqual([
'Acknowledged.',
]);
});
it('does not mark as relayed when stdin is not writable', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'bob',
text: 'Hello',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
messageId: 'm-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName, { writable: false });
const first = await service.relayLeadInboxMessages(teamName);
expect(first).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
(service as unknown as { runs: Map<string, unknown> }).runs.set('run-1', {
runId: 'run-1',
teamName,
request: {
teamName,
members: [{ name: 'team-lead', role: 'team-lead' }],
},
activeToolCalls: new Map(),
pendingToolCalls: [],
leadMsgSeq: 0,
pendingDirectCrossTeamSendRefresh: false,
lastLeadTextEmitMs: 0,
activeCrossTeamReplyHints: [],
pendingInboxRelayCandidates: [],
silentUserDmForward: null,
silentUserDmForwardClearHandle: null,
child: { stdin: { writable: true, write: writeSpy } },
processKilled: false,
cancelRequested: false,
provisioningComplete: true,
leadRelayCapture: null,
});
const secondPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
expect(run?.leadRelayCapture).toBeTruthy();
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Hi.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
const second = await secondPromise;
expect(second).toBe(1);
expect(writeSpy).toHaveBeenCalledTimes(1);
});
it('does not let stale lead inbox relay work write into a newer run', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const inboxMessages = [
{
from: 'bob',
text: 'Please pick this up.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
messageId: 'm-stale-lead-1',
},
];
seedConfig(teamName);
seedLeadInbox(teamName, inboxMessages);
const { writeSpy: oldWriteSpy, runId: oldRunId } = attachAliveRun(service, teamName, {
runId: 'run-old',
});
const inboxDeferred = createDeferred<typeof inboxMessages>();
const inboxReader = (
service as unknown as {
inboxReader: {
getMessagesFor: (team: string, member: string) => Promise<typeof inboxMessages>;
};
}
).inboxReader;
const inboxSpy = vi
.spyOn(inboxReader, 'getMessagesFor')
.mockImplementationOnce(async () => await inboxDeferred.promise)
.mockImplementation(async () => inboxMessages);
const relayPromise = service.relayLeadInboxMessages(teamName);
await Promise.resolve();
const oldRun = (service as unknown as { runs: Map<string, any> }).runs.get(oldRunId);
oldRun.processKilled = true;
oldRun.cancelRequested = true;
oldRun.child.stdin.writable = false;
const { writeSpy: newWriteSpy } = attachAliveRun(service, teamName, { runId: 'run-new' });
inboxDeferred.resolve(inboxMessages);
await expect(relayPromise).resolves.toBe(0);
expect(oldWriteSpy).not.toHaveBeenCalled();
expect(newWriteSpy).not.toHaveBeenCalled();
inboxSpy.mockRestore();
});
it('does not let stale lead relay consume a newer run permission_request', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const permissionMessage = {
from: 'alice',
text: JSON.stringify({
type: 'permission_request',
request_id: 'perm-new-run-1',
agent_id: 'alice',
tool_name: 'Bash',
input: { command: 'git status' },
}),
timestamp: '2026-02-23T10:00:30.000Z',
read: false,
messageId: 'perm-inbox-1',
};
seedConfig(teamName);
seedLeadInbox(teamName, [permissionMessage]);
const { runId: oldRunId } = attachAliveRun(service, teamName, { runId: 'run-old' });
const inboxDeferred = createDeferred<[typeof permissionMessage]>();
const inboxReader = (
service as unknown as {
inboxReader: {
getMessagesFor: (team: string, member: string) => Promise<[typeof permissionMessage]>;
};
}
).inboxReader;
const inboxSpy = vi
.spyOn(inboxReader, 'getMessagesFor')
.mockImplementationOnce(async () => await inboxDeferred.promise)
.mockImplementation(async () => [permissionMessage]);
const relayPromise = service.relayLeadInboxMessages(teamName);
await Promise.resolve();
const oldRun = (service as unknown as { runs: Map<string, any> }).runs.get(oldRunId);
oldRun.processKilled = true;
oldRun.cancelRequested = true;
oldRun.child.stdin.writable = false;
attachAliveRun(service, teamName, { runId: 'run-new' });
inboxDeferred.resolve([permissionMessage]);
await expect(relayPromise).resolves.toBe(0);
const inbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/team-lead.json`) ?? '[]'
) as Array<{ messageId?: string; read?: boolean }>;
expect(inbox).toEqual([
expect.objectContaining({
messageId: 'perm-inbox-1',
read: false,
}),
]);
expect(oldRun.pendingApprovals.size).toBe(0);
expect(oldRun.processedPermissionRequestIds.size).toBe(0);
inboxSpy.mockRestore();
});
it('relays legacy lead inbox rows with generated messageId', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'bob',
text: 'Legacy row without id',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
expect(run?.leadRelayCapture).toBeTruthy();
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Ok.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
const relayed = await relayPromise;
expect(relayed).toBe(1);
expect(writeSpy).toHaveBeenCalledTimes(1);
});
it('resolves cross-team reply metadata only for a single matching team hint', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
attachAliveRun(service, teamName);
const run = (service as unknown as { runs: Map<string, unknown> }).runs.get('run-1') as {
activeCrossTeamReplyHints: Array<{ toTeam: string; conversationId: string }>;
};
run.activeCrossTeamReplyHints = [{ toTeam: 'other-team', conversationId: 'conv-1' }];
expect(service.resolveCrossTeamReplyMetadata(teamName, 'other-team')).toEqual({
conversationId: 'conv-1',
replyToConversationId: 'conv-1',
});
run.activeCrossTeamReplyHints = [
{ toTeam: 'other-team', conversationId: 'conv-1' },
{ toTeam: 'other-team', conversationId: 'conv-2' },
];
expect(service.resolveCrossTeamReplyMetadata(teamName, 'other-team')).toBeNull();
});
it('includes explicit cross-team reply instructions in lead relay prompts', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'other-team.team-lead',
to: 'team-lead',
text: '<cross-team from="other-team.team-lead" depth="0" conversationId="conv-explicit" />\nNeed your answer.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
source: 'cross_team',
messageId: 'm-cross-team-explicit',
conversationId: 'conv-explicit',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
expect(run?.leadRelayCapture).toBeTruthy();
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('Source: cross_team');
expect(payload).toContain('Cross-team conversationId: conv-explicit');
expect(payload).toContain(
'Call the MCP tool named cross_team_send with toTeam=\\"other-team\\"'
);
expect(payload).toContain('replyToConversationId=\\"conv-explicit\\"');
expect(payload).toContain('NEVER set recipient/to to \\"cross_team_send\\"');
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Replying properly.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await relayPromise;
});
it('does not relay cross-team sender copies back into the live lead', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'user',
to: 'other-team.team-lead',
text: 'How is the progress on that task?',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
source: 'cross_team_sent',
messageId: 'm-cross-team-sent-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayLeadInboxMessages(teamName);
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
const updatedInbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/team-lead.json`) ?? '[]'
) as Array<{ messageId?: string }>;
expect(updatedInbox).toHaveLength(1);
expect(updatedInbox[0]?.messageId).toBe('m-cross-team-sent-1');
});
it('does not relay returned cross-team replies back into the originating lead', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'user',
to: 'other-team.team-lead',
text: 'Original outbound request',
timestamp: '2026-02-23T10:00:00.000Z',
read: true,
source: 'cross_team_sent',
messageId: 'm-cross-team-sent-1',
conversationId: 'conv-1',
},
{
from: 'other-team.team-lead',
to: 'team-lead',
text: '<cross-team from="other-team.team-lead" depth="0" conversationId="conv-1" replyToConversationId="conv-1" />\nReply back to origin.',
timestamp: '2026-02-23T10:01:00.000Z',
read: false,
source: 'cross_team',
messageId: 'm-cross-team-reply-1',
conversationId: 'conv-1',
replyToConversationId: 'conv-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayLeadInboxMessages(teamName);
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
const updatedInbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/team-lead.json`) ?? '[]'
) as Array<{ messageId?: string; read?: boolean }>;
expect(updatedInbox).toHaveLength(2);
expect(updatedInbox[1]?.messageId).toBe('m-cross-team-reply-1');
});
it('does not relay a fast first reply while outbound sender copy is still pending', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
service.registerPendingCrossTeamReplyExpectation(teamName, 'other-team', 'conv-race');
seedLeadInbox(teamName, [
{
from: 'other-team.team-lead',
to: 'team-lead',
text: '<cross-team from="other-team.team-lead" depth="0" conversationId="conv-race" replyToConversationId="conv-race" />\nFast reply before sender copy.',
timestamp: '2026-02-23T10:01:00.000Z',
read: false,
source: 'cross_team',
messageId: 'm-cross-team-race-1',
conversationId: 'conv-race',
replyToConversationId: 'conv-race',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayLeadInboxMessages(teamName);
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
});
it('relays later follow-up messages after the first reply in a conversation was already received', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'user',
to: 'other-team.team-lead',
text: 'Original outbound request',
timestamp: '2026-02-23T10:00:00.000Z',
read: true,
source: 'cross_team_sent',
messageId: 'm-cross-team-sent-2',
conversationId: 'conv-followup',
},
{
from: 'other-team.team-lead',
to: 'team-lead',
text: '<cross-team from="other-team.team-lead" depth="0" conversationId="conv-followup" replyToConversationId="conv-followup" />\nFirst answer.',
timestamp: '2026-02-23T10:01:00.000Z',
read: true,
source: 'cross_team',
messageId: 'm-cross-team-first-reply',
conversationId: 'conv-followup',
replyToConversationId: 'conv-followup',
},
{
from: 'other-team.team-lead',
to: 'team-lead',
text: '<cross-team from="other-team.team-lead" depth="0" conversationId="conv-followup" replyToConversationId="conv-followup" />\nCan you confirm one more detail?',
timestamp: '2026-02-23T10:02:00.000Z',
read: false,
source: 'cross_team',
messageId: 'm-cross-team-followup',
conversationId: 'conv-followup',
replyToConversationId: 'conv-followup',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
expect(run?.leadRelayCapture).toBeTruthy();
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'I will answer the follow-up.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
const relayed = await relayPromise;
expect(relayed).toBe(1);
expect(writeSpy).toHaveBeenCalledTimes(1);
});
it('relays unread teammate inbox messages through the live team process', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', [
{
from: 'team-lead',
text: 'Comment on task #abcd1234 "Investigate":\n\nPlease retry with logging enabled.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Comment on #abcd1234',
messageId: 'm-alice-1',
source: 'system_notification',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'alice');
expect(relayed).toBe(1);
expect(writeSpy).toHaveBeenCalledTimes(1);
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('"type":"user"');
expect(payload).toContain('to=\\"alice\\"');
expect(payload).toContain('Source: system_notification');
expect(payload).toContain('forward that notification exactly once without paraphrasing');
expect(payload).toContain('Please retry with logging enabled.');
});
it('prioritizes member work-sync nudges over older ordinary member relay rows', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', [
...Array.from({ length: 11 }, (_, index) => ({
from: 'team-lead',
text: `Routine relay row ${index + 1}.`,
timestamp: `2026-02-23T10:${String(index).padStart(2, '0')}:00.000Z`,
read: false,
messageId: `m-ordinary-${index + 1}`,
})),
{
from: 'system',
text: 'Call member_work_sync_status, then member_work_sync_report.',
timestamp: '2026-02-23T10:30:00.000Z',
read: false,
messageId: 'm-work-sync-late',
messageKind: 'member_work_sync_nudge',
workSyncIntent: 'agenda_sync',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'alice');
expect(relayed).toBe(10);
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('1) From: system');
expect(payload).toContain('MessageId: m-work-sync-late');
expect(payload).toContain('Message kind: member_work_sync_nudge');
expect(payload).not.toContain('MessageId: m-ordinary-11');
});
it('keeps native member work-sync rows unread without accepted report proof', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
service.setMemberWorkSyncAcceptedReportChecker(async () => false);
seedMemberInbox(teamName, 'alice', [
{
from: 'system',
text: 'Call member_work_sync_status, then member_work_sync_report.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
messageId: 'm-work-sync-unproved',
messageKind: 'member_work_sync_nudge',
workSyncIntent: 'agenda_sync',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const firstRelayed = await service.relayMemberInboxMessages(teamName, 'alice');
const rowsAfterFirst = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]'
) as Array<{ read?: boolean }>;
expect(firstRelayed).toBe(1);
expect(rowsAfterFirst[0]?.read).toBe(false);
const secondRelayed = await service.relayMemberInboxMessages(teamName, 'alice');
expect(secondRelayed).toBe(1);
expect(writeSpy).toHaveBeenCalledTimes(2);
});
it('read-commits native member work-sync rows after accepted report proof', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
service.setMemberWorkSyncAcceptedReportChecker(async () => true);
seedMemberInbox(teamName, 'alice', [
{
from: 'system',
text: 'Call member_work_sync_status, then member_work_sync_report.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
messageId: 'm-work-sync-proved',
messageKind: 'member_work_sync_nudge',
workSyncIntent: 'agenda_sync',
},
]);
attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'alice');
const rows = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]'
) as Array<{ read?: boolean }>;
expect(relayed).toBe(1);
expect(rows[0]?.read).toBe(true);
await expect(service.relayMemberInboxMessages(teamName, 'alice')).resolves.toBe(0);
});
it('retries a work-sync nudge after member relay times out before stdin write completes', async () => {
vi.useFakeTimers();
const service = new TeamProvisioningService();
const teamName = 'my-team';
try {
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', [
{
from: 'system',
text: 'Call member_work_sync_status, then member_work_sync_report.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
messageId: 'm-work-sync-retry',
messageKind: 'member_work_sync_nudge',
workSyncIntent: 'agenda_sync',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
writeSpy.mockImplementationOnce(() => true);
const firstRelay = service.relayMemberInboxMessages(teamName, 'alice');
await vi.advanceTimersByTimeAsync(0);
expect(writeSpy).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(120_000);
await expect(firstRelay).resolves.toBe(0);
vi.mocked(console.warn).mockClear();
const secondRelay = await service.relayMemberInboxMessages(teamName, 'alice');
expect(secondRelay).toBe(1);
expect(writeSpy).toHaveBeenCalledTimes(2);
const secondPayload = String(writeSpy.mock.calls[1]?.[0] ?? '');
expect(secondPayload).toContain('MessageId: m-work-sync-retry');
expect(secondPayload).toContain('Message kind: member_work_sync_nudge');
} finally {
vi.useRealTimers();
}
});
it('marks exact teammate relay copies with relayOfMessageId', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', [
{
from: 'team-lead',
text:
`**Comment on task #abcd1234**\n> Investigate\n\n> Please retry with logging enabled.\n\n` +
'<agent-block>\nReply using task_add_comment\n</agent-block>',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
summary: 'Comment on #abcd1234',
messageId: 'm-alice-1',
source: 'system_notification',
},
]);
attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'alice');
expect(relayed).toBe(1);
const run = (service as unknown as { runs: Map<string, unknown> }).runs.get('run-1') as unknown;
expect(run).toBeTruthy();
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [
{
type: 'tool_use',
name: 'SendMessage',
input: {
recipient: 'alice',
summary: 'Comment on #abcd1234',
content:
`**Comment on task #abcd1234**\n> Investigate\n\n> Please retry with logging enabled.\n\n` +
'<agent-block>\nHidden internal instructions\n</agent-block>',
},
},
],
});
const inbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]'
) as Array<{ messageId?: string; relayOfMessageId?: string; source?: string }>;
const relayedCopy = inbox.find((row) => row.messageId?.startsWith('lead-sendmsg-run-1-'));
expect(relayedCopy).toMatchObject({
source: 'lead_process',
relayOfMessageId: 'm-alice-1',
});
});
it('does not capture user-dm silent forwards as extra lead_process messages', () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
attachAliveRun(service, teamName);
const run = (service as unknown as { runs: Map<string, unknown> }).runs.get('run-1') as {
silentUserDmForward: {
target: string;
startedAt: string;
mode: 'user_dm' | 'member_inbox_relay';
} | null;
};
run.silentUserDmForward = {
target: 'alice',
startedAt: new Date().toISOString(),
mode: 'user_dm',
};
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [
{
type: 'tool_use',
name: 'SendMessage',
input: {
recipient: 'alice',
summary: 'Forwarded DM',
content: 'User DM payload',
},
},
],
});
const inbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]'
) as Array<{ messageId?: string; source?: string }>;
expect(inbox).toHaveLength(0);
});
it('does not relay pseudo cross-team member inboxes as teammates', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'cross-team:team-alpha-super', [
{
from: 'team-lead',
text: 'Stale pseudo recipient inbox',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
messageId: 'm-pseudo-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'cross-team:team-alpha-super');
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
});
it('does not relay tool-like cross-team inbox names as teammates', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'cross_team_send', [
{
from: 'team-lead',
text: 'Wrongly routed tool recipient inbox',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
messageId: 'm-tool-recipient-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'cross_team_send');
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
});
it('does not relay malformed underscore-style pseudo cross-team inbox names as teammates', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'cross_team::team-best', [
{
from: 'team-lead',
text: 'Wrongly routed underscore pseudo inbox',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
messageId: 'm-underscore-pseudo-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'cross_team::team-best');
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
});
it('includes user message provenance in lead inbox relay prompt', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'user',
text: 'Build the authentication module',
timestamp: '2026-02-23T14:00:00.000Z',
read: false,
summary: 'Auth module request',
messageId: 'msg-provenance-001',
source: 'user_sent',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Creating task.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await relayPromise;
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('Eligible for task_create_from_message: yes');
expect(payload).toContain('User MessageId: msg-provenance-001');
expect(payload).toContain('Build the authentication module');
});
it('includes MessageId in member inbox relay prompt for provenance', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', [
{
from: 'bob',
text: 'Please review my changes',
timestamp: '2026-02-23T15:00:00.000Z',
read: false,
summary: 'Review request',
messageId: 'msg-member-relay-001',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
await service.relayMemberInboxMessages(teamName, 'alice');
expect(writeSpy).toHaveBeenCalledTimes(1);
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('MessageId: msg-member-relay-001');
expect(payload).toContain('Please review my changes');
});
it('does not let stale member inbox relay work write into a newer run', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const inboxMessages = [
{
from: 'user',
text: 'Please sync with Alice.',
timestamp: '2026-02-23T10:00:00.000Z',
read: false,
messageId: 'm-stale-member-1',
},
];
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', inboxMessages);
const { writeSpy: oldWriteSpy, runId: oldRunId } = attachAliveRun(service, teamName, {
runId: 'run-old',
});
const inboxDeferred = createDeferred<typeof inboxMessages>();
const inboxReader = (
service as unknown as {
inboxReader: {
getMessagesFor: (team: string, member: string) => Promise<typeof inboxMessages>;
};
}
).inboxReader;
const inboxSpy = vi
.spyOn(inboxReader, 'getMessagesFor')
.mockImplementationOnce(async () => await inboxDeferred.promise)
.mockImplementation(async () => inboxMessages);
const relayPromise = service.relayMemberInboxMessages(teamName, 'alice');
await Promise.resolve();
const oldRun = (service as unknown as { runs: Map<string, any> }).runs.get(oldRunId);
oldRun.processKilled = true;
oldRun.cancelRequested = true;
oldRun.child.stdin.writable = false;
const { writeSpy: newWriteSpy } = attachAliveRun(service, teamName, { runId: 'run-new' });
inboxDeferred.resolve(inboxMessages);
await expect(relayPromise).resolves.toBe(0);
expect(oldWriteSpy).not.toHaveBeenCalled();
expect(newWriteSpy).not.toHaveBeenCalled();
inboxSpy.mockRestore();
});
it('marks pure member heartbeat idle as read without relaying it', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', [
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
}),
timestamp: '2026-02-23T15:10:00.000Z',
read: false,
messageId: 'idle-member-heartbeat-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'alice');
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
const inbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]'
) as Array<{ messageId?: string; read?: boolean }>;
expect(inbox).toEqual([
expect.objectContaining({
messageId: 'idle-member-heartbeat-1',
read: true,
}),
]);
});
it('marks member heartbeat with peer summary read and does not relay it', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', [
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
summary: '[to bob] aligned on rollout order',
}),
timestamp: '2026-02-23T15:11:00.000Z',
read: false,
messageId: 'idle-member-summary-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const first = await service.relayMemberInboxMessages(teamName, 'alice');
const second = await service.relayMemberInboxMessages(teamName, 'alice');
expect(first).toBe(0);
expect(second).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
const inbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]'
) as Array<{ messageId?: string; read?: boolean }>;
expect(inbox).toEqual([
expect.objectContaining({
messageId: 'idle-member-summary-1',
read: true,
}),
]);
});
it('marks legacy member passive idle rows read via fallback identity', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', [
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
summary: '[to bob] aligned on rollout order',
}),
timestamp: '2026-02-23T15:11:30.000Z',
read: false,
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'alice');
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
const inbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]'
) as Array<{ read?: boolean }>;
expect(inbox).toEqual([expect.objectContaining({ read: true })]);
});
it('marks byte-identical legacy member passive idle duplicates read together', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
const duplicate = {
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
summary: '[to bob] aligned on rollout order',
}),
timestamp: '2026-02-23T15:11:31.000Z',
read: false,
};
seedMemberInbox(teamName, 'alice', [duplicate, { ...duplicate }]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'alice');
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
const inbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]'
) as Array<{ read?: boolean }>;
expect(inbox).toEqual([
expect.objectContaining({ read: true }),
expect.objectContaining({ read: true }),
]);
});
it('retries passive member idle on next cycle when exact mark-read fails', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', [
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
summary: '[to bob] aligned on rollout order',
}),
timestamp: '2026-02-23T15:11:45.000Z',
read: false,
messageId: 'idle-member-summary-fail-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
hoisted.setAtomicWriteShouldFail(true);
const first = await service.relayMemberInboxMessages(teamName, 'alice');
hoisted.setAtomicWriteShouldFail(false);
const second = await service.relayMemberInboxMessages(teamName, 'alice');
expect(first).toBe(0);
expect(second).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
const inbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]'
) as Array<{ messageId?: string; read?: boolean }>;
expect(inbox).toEqual([
expect.objectContaining({
messageId: 'idle-member-summary-fail-1',
read: true,
}),
]);
});
it('does not rewrite the inbox file when exact mark-read is a no-op on an already-read legacy row', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const legacyRow = {
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
summary: '[to bob] aligned on rollout order',
}),
timestamp: '2026-02-23T15:11:46.000Z',
read: true,
};
seedMemberInbox(teamName, 'alice', [legacyRow]);
await (service as any).markInboxMessagesRead(teamName, 'alice', [
{
messageId: buildLegacyInboxMessageId(legacyRow.from, legacyRow.timestamp, legacyRow.text),
},
]);
expect(hoisted.atomicWrite).not.toHaveBeenCalled();
});
it('marks persisted duplicate messageId passive rows read together', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', [
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
summary: '[to bob] aligned on rollout order',
}),
timestamp: '2026-02-23T15:11:47.000Z',
read: false,
messageId: 'dup-passive-id-1',
},
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
summary: '[to bob] aligned on rollout order',
}),
timestamp: '2026-02-23T15:11:48.000Z',
read: false,
messageId: 'dup-passive-id-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'alice');
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
const inbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]'
) as Array<{ messageId?: string; read?: boolean }>;
expect(inbox).toEqual([
expect.objectContaining({ messageId: 'dup-passive-id-1', read: true }),
expect.objectContaining({ messageId: 'dup-passive-id-1', read: true }),
]);
});
it('relays actionable member idle notifications such as failures', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedMemberInbox(teamName, 'alice', [
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'failed',
completedStatus: 'failed',
failureReason: 'teammate crashed',
}),
timestamp: '2026-02-23T15:12:00.000Z',
read: false,
messageId: 'idle-member-failure-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayMemberInboxMessages(teamName, 'alice');
expect(relayed).toBe(1);
expect(writeSpy).toHaveBeenCalledTimes(1);
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('idle_notification');
expect(payload).toContain('teammate crashed');
});
it('lead inbox relay prompt mentions task_create_from_message for user messages with messageId', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
hoisted.files.set(
`/mock/teams/${teamName}/config.json`,
JSON.stringify({
name: 'My Team',
members: [
{ name: 'team-lead', agentType: 'team-lead' },
{ name: 'alice', role: 'developer' },
],
})
);
seedLeadInbox(teamName, [
{
from: 'user',
text: 'Implement dark mode',
timestamp: '2026-02-23T16:00:00.000Z',
read: false,
summary: 'Dark mode',
messageId: 'msg-task-pref-001',
source: 'user_sent',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Got it.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await relayPromise;
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('task_create_from_message');
expect(payload).toContain('Current durable team context:');
expect(payload).toContain(`- Team name: ${teamName}`);
expect(payload).toContain(`teamName MUST be \\"${teamName}\\"`);
expect(payload).toContain('Eligible for task_create_from_message: yes');
expect(payload).toContain('User MessageId: msg-task-pref-001');
});
it('does not present teammate inbox message ids as task_create_from_message provenance', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'jack',
text: 'Могу начать с проверки массовых удалений в docs-site.',
timestamp: '2026-02-23T16:05:00.000Z',
read: false,
summary: 'Нет назначенных задач для jack',
messageId: 'inbox-jack-001',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Понял.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
await relayPromise;
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('Eligible for task_create_from_message: no');
expect(payload).not.toContain('User MessageId: inbox-jack-001');
});
it('marks pure lead heartbeat idle as read without relaying it', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
}),
timestamp: '2026-02-23T16:10:00.000Z',
read: false,
messageId: 'idle-lead-heartbeat-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayLeadInboxMessages(teamName);
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
const inbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/team-lead.json`) ?? '[]'
) as Array<{ messageId?: string; read?: boolean }>;
expect(inbox).toEqual([
expect.objectContaining({
messageId: 'idle-lead-heartbeat-1',
read: true,
}),
]);
});
it('marks lead heartbeat with peer summary read across repeated scans and does not relay it', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
summary: '[to bob] aligned on rollout order',
}),
timestamp: '2026-02-23T16:11:00.000Z',
read: false,
messageId: 'idle-lead-summary-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const first = await service.relayLeadInboxMessages(teamName);
const second = await service.relayLeadInboxMessages(teamName);
expect(first).toBe(0);
expect(second).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
const inbox = JSON.parse(
hoisted.files.get(`/mock/teams/${teamName}/inboxes/team-lead.json`) ?? '[]'
) as Array<{ messageId?: string; read?: boolean }>;
expect(inbox).toEqual([
expect.objectContaining({
messageId: 'idle-lead-summary-1',
read: true,
}),
]);
});
it('does not clear pending cross-team reply expectations for passive lead idle', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
service.registerPendingCrossTeamReplyExpectation(teamName, 'other-team', 'conv-passive');
seedLeadInbox(teamName, [
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
summary: '[to bob] aligned on rollout order',
}),
timestamp: '2026-02-23T16:11:30.000Z',
read: false,
messageId: 'idle-lead-summary-2',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayLeadInboxMessages(teamName);
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
const pendingKeys = (service as any).getPendingCrossTeamReplyExpectationKeys(teamName);
expect(Array.from(pendingKeys)).toContain('other-team\0conv-passive');
});
it('does not feed passive lead idle into same-team native matching', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
summary: '[to bob] aligned on rollout order',
}),
timestamp: '2026-02-23T16:11:45.000Z',
read: false,
messageId: 'idle-lead-summary-native-match-1',
},
]);
const nativeMatchSpy = vi
.spyOn(service as any, 'confirmSameTeamNativeMatches')
.mockResolvedValue({ nativeMatchedMessageIds: new Set<string>(), persisted: true });
const { writeSpy } = attachAliveRun(service, teamName);
const relayed = await service.relayLeadInboxMessages(teamName);
expect(relayed).toBe(0);
expect(writeSpy).toHaveBeenCalledTimes(0);
expect(nativeMatchSpy).toHaveBeenCalledWith(teamName, 'team-lead', []);
});
it('does not let cross-team idle-shaped payloads inherit passive idle handling', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'other-team.alice',
text: JSON.stringify({
type: 'idle_notification',
idleReason: 'available',
summary: '[to bob] aligned on rollout order',
}),
timestamp: '2026-02-23T16:11:50.000Z',
read: false,
messageId: 'cross-team-idle-shaped-1',
source: 'cross_team',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Seen.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
const relayed = await relayPromise;
expect(relayed).toBe(1);
expect(writeSpy).toHaveBeenCalledTimes(1);
});
it('relays actionable lead idle notifications such as task-terminal updates', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
seedConfig(teamName);
seedLeadInbox(teamName, [
{
from: 'alice',
text: JSON.stringify({
type: 'idle_notification',
completedTaskId: 'task-1',
completedStatus: 'blocked',
}),
timestamp: '2026-02-23T16:12:00.000Z',
read: false,
messageId: 'idle-lead-terminal-1',
},
]);
const { writeSpy } = attachAliveRun(service, teamName);
const relayPromise = service.relayLeadInboxMessages(teamName);
const run = await waitForCapture(service);
(service as any).handleStreamJsonMessage(run, {
type: 'assistant',
content: [{ type: 'text', text: 'Investigating blocker.' }],
});
(service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' });
const relayed = await relayPromise;
expect(relayed).toBe(1);
expect(writeSpy).toHaveBeenCalledTimes(1);
const payload = String(writeSpy.mock.calls[0]?.[0] ?? '');
expect(payload).toContain('idle_notification');
expect(payload).toContain('blocked');
});
it('relays unread OpenCode member inbox rows to the runtime before marking them read', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
hoisted.files.set(
`/mock/teams/${teamName}/config.json`,
JSON.stringify({
name: teamName,
projectPath: '/mock/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: 'Please review this.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-relay-1',
taskRefs: [{ teamName, taskId: 'task-1', displayId: 'abcd1234' }],
actionMode: 'ask',
},
]);
const deliverSpy = vi
.spyOn(service, 'deliverOpenCodeMemberMessage')
.mockResolvedValue({ delivered: true, diagnostics: [] });
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
expect(relay).toMatchObject({ relayed: 1, attempted: 1, delivered: 1, failed: 0 });
expect(deliverSpy).toHaveBeenCalledWith(
teamName,
expect.objectContaining({
memberName: 'jack',
text: 'Please review this.',
messageId: 'opencode-relay-1',
replyRecipient: 'bob',
actionMode: 'ask',
taskRefs: [{ teamName, taskId: 'task-1', displayId: 'abcd1234' }],
})
);
const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]');
expect(rows[0].read).toBe(true);
});
it('uses inferred task refs when relaying legacy OpenCode inbox rows without structured refs', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const taskRefs = [{ teamName, taskId: 'task-1', displayId: 'abcd1234' }];
hoisted.files.set(
`/mock/teams/${teamName}/config.json`,
JSON.stringify({
name: teamName,
projectPath: '/mock/my-team',
members: [
{ name: 'team-lead', agentType: 'team-lead' },
{ name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' },
],
})
);
seedMemberInbox(teamName, 'jack', [
{
from: 'team-lead',
to: 'jack',
text: '**Comment on task #abcd1234**\n\nPlease continue.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-relay-infer-1',
summary: 'Comment on #abcd1234',
},
]);
const inferSpy = vi
.spyOn(service as any, 'inferOpenCodeInboxMessageTaskRefs')
.mockResolvedValue(taskRefs);
const deliverSpy = vi
.spyOn(service, 'deliverOpenCodeMemberMessage')
.mockResolvedValue({ delivered: true, diagnostics: [] });
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
expect(relay).toMatchObject({ relayed: 1, attempted: 1, delivered: 1, failed: 0 });
expect(inferSpy).toHaveBeenCalledWith(
teamName,
expect.objectContaining({ messageId: 'opencode-relay-infer-1' }),
expect.any(Function)
);
expect(deliverSpy).toHaveBeenCalledWith(
teamName,
expect.objectContaining({
messageId: 'opencode-relay-infer-1',
taskRefs,
})
);
});
it('keeps OpenCode member inbox rows unread while runtime response is pending', async () => {
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: 'Please answer this.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-response-pending-1',
actionMode: 'ask',
},
]);
vi.spyOn(service, 'deliverOpenCodeMemberMessage').mockResolvedValue({
delivered: true,
accepted: true,
responsePending: true,
responseState: 'pending',
diagnostics: ['opencode_delivery_response_pending'],
});
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
expect(relay).toMatchObject({
relayed: 0,
attempted: 1,
delivered: 0,
failed: 0,
lastDelivery: { delivered: true, responsePending: true },
});
const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]');
expect(rows[0].read).toBe(false);
});
it('keeps accepted OpenCode prompt rows pending without warning when response proof is terminally absent', async () => {
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: 'Please sync your current task.',
timestamp: '2026-02-23T17:04:00.000Z',
read: false,
messageId: 'opencode-accepted-terminal-empty-1',
actionMode: 'do',
messageKind: 'member_work_sync_nudge',
},
]);
vi.spyOn(service, 'deliverOpenCodeMemberMessage').mockResolvedValue({
delivered: false,
accepted: true,
responsePending: false,
responseState: 'empty_assistant_turn',
ledgerStatus: 'failed_terminal',
ledgerRecordId: 'ledger-1',
laneId: 'secondary:opencode:jack',
reason: 'empty_assistant_turn',
diagnostics: ['empty_assistant_turn'],
});
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
expect(relay).toMatchObject({
relayed: 0,
attempted: 1,
delivered: 0,
failed: 0,
lastDelivery: {
delivered: false,
accepted: true,
responsePending: false,
ledgerStatus: 'failed_terminal',
reason: 'empty_assistant_turn',
},
});
expect(vi.mocked(console.warn)).not.toHaveBeenCalledWith(
expect.stringContaining('OpenCode inbox relay failed')
);
const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]');
expect(rows[0].read).toBe(false);
});
it('does not treat empty OpenCode observations as accepted without delivered prompt proof', () => {
const service = new TeamProvisioningService();
const isAccepted = (
service as unknown as {
isOpenCodePromptAcceptedByObservation: (observation?: unknown) => boolean;
}
).isOpenCodePromptAcceptedByObservation.bind(service);
expect(
isAccepted({
state: 'empty_assistant_turn',
deliveredUserMessageId: null,
})
).toBe(false);
expect(
isAccepted({
state: 'prompt_delivered_no_assistant_message',
deliveredUserMessageId: '',
})
).toBe(false);
expect(
isAccepted({
state: 'empty_assistant_turn',
deliveredUserMessageId: 'opencode-user-message-1',
})
).toBe(true);
});
it('reuses existing OpenCode prompt ledger metadata during watchdog relay retries', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const taskRefs = [{ teamName, taskId: 'task-1', displayId: 'abcd1234' }];
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: 'Please answer the app user.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-ledger-metadata-1',
actionMode: 'ask',
},
]);
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
getByInboxMessage: vi.fn(async () => ({
id: 'record-1',
status: 'retry_scheduled',
replyRecipient: 'user',
actionMode: 'delegate',
taskRefs,
source: 'manual',
})),
});
const deliverSpy = vi.spyOn(service, 'deliverOpenCodeMemberMessage').mockResolvedValue({
delivered: true,
accepted: true,
responsePending: true,
responseState: 'pending',
diagnostics: ['opencode_delivery_response_pending'],
});
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack', {
onlyMessageId: 'opencode-ledger-metadata-1',
source: 'watchdog',
});
expect(relay).toMatchObject({
attempted: 1,
delivered: 0,
failed: 0,
lastDelivery: { delivered: true, responsePending: true },
});
expect(deliverSpy).toHaveBeenCalledWith(
teamName,
expect.objectContaining({
messageId: 'opencode-ledger-metadata-1',
replyRecipient: 'user',
actionMode: 'delegate',
taskRefs,
source: 'manual',
})
);
});
it('records and schedules a retry when the OpenCode bridge throws during prompt delivery', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
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' },
],
})
);
const sendMessageToMember = vi.fn(async () => {
throw new Error('bridge crashed');
});
service.setRuntimeAdapterRegistry(
new TeamRuntimeAdapterRegistry([
{
providerId: 'opencode',
prepare: vi.fn(),
launch: vi.fn(),
reconcile: vi.fn(),
stop: vi.fn(),
sendMessageToMember,
} as any,
])
);
vi.spyOn(service as any, 'getCurrentOpenCodeRuntimeRunId').mockReturnValue('opencode-run-1');
vi.spyOn(
service as any,
'findDeliverableOpenCodeRuntimeBootstrapSessionEvidence'
).mockResolvedValue({
id: 'session-jack',
teamName,
memberName: 'jack',
laneId,
runId: 'opencode-run-1',
source: 'runtime_bootstrap_checkin',
});
vi.spyOn(service as any, 'applyOpenCodeVisibleDestinationProof').mockImplementation(
async (input: any) => ({
ledgerRecord: input.ledgerRecord,
visibleReply: null,
})
);
vi.spyOn(service as any, 'materializeOpenCodePlainTextReplyIfNeeded').mockImplementation(
async (input: any) => ({
ledgerRecord: input.ledgerRecord,
visibleReply: null,
})
);
const watchdogSpy = vi
.spyOn(service as any, 'scheduleOpenCodePromptDeliveryWatchdog')
.mockImplementation(() => undefined);
const records: any[] = [];
const ledger = {
getActiveForMember: vi.fn(async () => null),
ensurePending: vi.fn(async (input: Record<string, unknown>) => {
const record = {
id: 'ledger-send-exception-1',
teamName,
memberName: 'jack',
laneId,
runId: 'opencode-run-1',
runtimeSessionId: null,
runtimePromptMessageId: null,
runtimePromptMessageIds: [],
lastRuntimePromptMessageId: null,
lastDeliveryAttemptIdWithAcceptedPrompt: null,
inboxMessageId: 'opencode-send-exception-1',
inboxTimestamp: '2026-02-23T17:00:00.000Z',
source: 'watcher',
messageKind: 'default',
workSyncIntent: null,
replyRecipient: 'team-lead',
actionMode: 'do',
taskRefs: [],
payloadHash: 'sha256:test',
status: 'pending',
responseState: 'not_observed',
attempts: 0,
maxAttempts: 3,
acceptanceUnknown: false,
nextAttemptAt: null,
lastAttemptAt: null,
lastObservedAt: null,
acceptedAt: null,
respondedAt: null,
failedAt: null,
inboxReadCommittedAt: null,
inboxReadCommitError: null,
prePromptCursor: null,
postPromptCursor: null,
deliveredUserMessageId: null,
observedAssistantMessageId: null,
observedAssistantPreview: null,
observedToolCallNames: [],
observedVisibleMessageId: null,
visibleReplyMessageId: null,
visibleReplyInbox: null,
visibleReplyCorrelation: null,
lastReason: null,
diagnostics: [] as string[],
createdAt: '2026-02-23T17:00:00.000Z',
updatedAt: '2026-02-23T17:00:00.000Z',
...input,
};
records.push(record);
return record;
}),
applyDeliveryResult: vi.fn(async (input: Record<string, unknown>) => {
const record = records[0];
Object.assign(record, {
status: 'failed_retryable',
responseState: 'reconcile_failed',
attempts: 1,
lastAttemptAt: input.now,
lastReason: input.reason,
diagnostics: input.diagnostics,
updatedAt: input.now,
});
return record;
}),
markNextAttemptScheduled: vi.fn(async (input: Record<string, unknown>) => {
const record = records[0];
Object.assign(record, {
status: input.status,
nextAttemptAt: input.nextAttemptAt,
lastReason: input.reason,
updatedAt: input.scheduledAt,
});
return record;
}),
markFailedTerminal: vi.fn(),
};
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue(ledger);
const delivery = await service.deliverOpenCodeMemberMessage(teamName, {
memberName: 'jack',
text: 'Please continue task.',
messageId: 'opencode-send-exception-1',
source: 'watcher',
replyRecipient: 'team-lead',
actionMode: 'do',
inboxTimestamp: '2026-02-23T17:00:00.000Z',
});
expect(sendMessageToMember).toHaveBeenCalledTimes(1);
expect(ledger.applyDeliveryResult).toHaveBeenCalledWith(
expect.objectContaining({
accepted: false,
attempted: true,
reason: expect.stringContaining('bridge crashed'),
})
);
expect(ledger.markNextAttemptScheduled).toHaveBeenCalledWith(
expect.objectContaining({
status: 'retry_scheduled',
reason: expect.stringContaining('bridge crashed'),
})
);
expect(watchdogSpy).toHaveBeenCalledWith(
expect.objectContaining({
teamName,
memberName: 'jack',
messageId: 'opencode-send-exception-1',
})
);
expect(delivery).toMatchObject({
delivered: false,
accepted: false,
responsePending: true,
ledgerStatus: 'retry_scheduled',
ledgerRecordId: 'ledger-send-exception-1',
reason: expect.stringContaining('bridge crashed'),
});
});
it('does not postpone an earlier OpenCode prompt watchdog wake when rescheduled later', async () => {
vi.useFakeTimers();
try {
const service = new TeamProvisioningService();
const relaySpy = vi
.spyOn(service, 'relayOpenCodeMemberInboxMessages')
.mockResolvedValue({ attempted: 1, delivered: 0, failed: 0 } as any);
vi.spyOn(service as any, 'canDeliverToOpenCodeRuntimeForTeam').mockReturnValue(true);
(service as any).scheduleOpenCodePromptDeliveryWatchdog({
teamName: 'my-team',
memberName: 'jack',
messageId: 'message-1',
delayMs: 500,
});
(service as any).scheduleOpenCodePromptDeliveryWatchdog({
teamName: 'my-team',
memberName: 'jack',
messageId: 'message-1',
delayMs: 60_000,
});
await vi.advanceTimersByTimeAsync(501);
expect(relaySpy).toHaveBeenCalledTimes(1);
expect(relaySpy).toHaveBeenCalledWith('my-team', 'jack', {
onlyMessageId: 'message-1',
source: 'watchdog',
});
} finally {
vi.useRealTimers();
}
});
it('ignores stale OpenCode watchdog jobs after the runtime lane is no longer active', async () => {
vi.useFakeTimers();
try {
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: 'Please sync.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-stale-watchdog-1',
},
]);
const deliverSpy = vi
.spyOn(service, 'deliverOpenCodeMemberMessage')
.mockRejectedValue(
new Error('OpenCode prompt delivery record not found: opencode-prompt:stale')
);
(service as any).scheduleOpenCodePromptDeliveryWatchdog({
teamName,
memberName: 'jack',
messageId: 'opencode-stale-watchdog-1',
delayMs: 500,
});
await vi.advanceTimersByTimeAsync(500);
await Promise.resolve();
expect(deliverSpy).not.toHaveBeenCalled();
expect(vi.mocked(console.warn)).not.toHaveBeenCalled();
} finally {
vi.useRealTimers();
}
});
it('does not classify missing OpenCode watchdog ledger rows as stale while the lane is active', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
attachAliveRun(service, teamName);
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: 'Please sync.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-active-watchdog-1',
},
]);
vi.spyOn(service as any, 'isOpenCodeRuntimeLaneIndexActive').mockResolvedValue(true);
await expect(
(service as any).isStaleOpenCodePromptDeliveryWatchdogError({
teamName,
memberName: 'jack',
messageId: 'opencode-active-watchdog-1',
error: new Error('OpenCode prompt delivery record not found: opencode-prompt:active'),
})
).resolves.toBe(false);
});
it('skips failed-terminal OpenCode rows without blocking newer unread rows', async () => {
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' },
],
})
);
const identity = await (service as any).resolveOpenCodeMemberDeliveryIdentity(teamName, 'jack');
expect(identity.ok).toBe(true);
const failedRecord = {
id: 'ledger-terminal-old',
status: 'failed_terminal',
inboxMessageId: 'opencode-terminal-old',
lastReason: 'opencode_attachments_not_supported_for_secondary_runtime',
diagnostics: ['opencode_attachments_not_supported_for_secondary_runtime'],
};
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
getByInboxMessage: vi.fn(async (input: { inboxMessageId: string }) =>
input.inboxMessageId === 'opencode-terminal-old' ? failedRecord : null
),
});
seedMemberInbox(teamName, 'jack', [
{
from: 'bob',
to: 'jack',
text: 'Old terminal row.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-terminal-old',
},
{
from: 'bob',
to: 'jack',
text: 'New deliverable row.',
timestamp: '2026-02-23T17:00:02.000Z',
read: false,
messageId: 'opencode-terminal-new',
},
]);
const deliverSpy = vi
.spyOn(service, 'deliverOpenCodeMemberMessage')
.mockResolvedValue({ delivered: true, diagnostics: [] });
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
expect(relay).toMatchObject({ relayed: 1, attempted: 1, delivered: 1, failed: 0 });
expect(relay.diagnostics?.join('\n')).toContain(
'opencode_attachments_not_supported_for_secondary_runtime'
);
expect(deliverSpy).toHaveBeenCalledTimes(1);
expect(deliverSpy).toHaveBeenCalledWith(
teamName,
expect.objectContaining({ messageId: 'opencode-terminal-new' })
);
const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]');
expect(rows.map((row: { read?: boolean }) => row.read)).toEqual([false, true]);
});
it('emits advisory refresh when a failed-terminal OpenCode row is recovered by visible reply proof', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const taskRefs = [{ teamName, taskId: 'task-recovered', displayId: 'task-rec' }];
const ledgerRecord = {
id: 'ledger-terminal-recovered',
teamName,
memberName: 'jack',
laneId: 'secondary:opencode:jack',
runId: 'run-1',
runtimeSessionId: 'ses-1',
inboxMessageId: 'opencode-terminal-recovered',
inboxTimestamp: '2026-02-23T17:00:00.000Z',
source: 'watcher',
messageKind: null,
replyRecipient: 'team-lead',
actionMode: null,
taskRefs,
payloadHash: 'sha256:test',
status: 'failed_terminal',
responseState: 'session_stale',
attempts: 1,
maxAttempts: 3,
acceptanceUnknown: false,
nextAttemptAt: null,
lastAttemptAt: '2026-02-23T17:00:03.000Z',
lastObservedAt: '2026-02-23T17:00:05.000Z',
acceptedAt: '2026-02-23T17:00:03.000Z',
respondedAt: null,
failedAt: '2026-02-23T17:00:08.000Z',
inboxReadCommittedAt: null,
inboxReadCommitError: null,
prePromptCursor: null,
postPromptCursor: null,
deliveredUserMessageId: 'runtime-user-1',
observedAssistantMessageId: null,
observedAssistantPreview: null,
observedToolCallNames: [],
observedVisibleMessageId: null,
visibleReplyMessageId: null,
visibleReplyInbox: null,
visibleReplyCorrelation: null,
lastReason: 'opencode_session_stale_observe_loop_after_accepted_prompt',
diagnostics: ['opencode_session_stale_observe_loop_after_accepted_prompt'],
createdAt: '2026-02-23T17:00:00.000Z',
updatedAt: '2026-02-23T17:00:08.000Z',
};
const visibleReply = {
inboxName: 'team-lead',
message: {
from: 'jack',
to: 'team-lead',
text: 'Recovered visible reply with task results.',
summary: '#task-rec done',
timestamp: '2026-02-23T17:01:00.000Z',
read: true,
source: 'runtime_delivery',
relayOfMessageId: 'opencode-terminal-recovered',
messageId: 'visible-reply-recovered',
taskRefs,
},
};
vi.spyOn(service as any, 'findOpenCodeVisibleReplyByRelayOfMessageId').mockResolvedValue(
visibleReply
);
const applyDestinationProof = vi.fn(async (input: Record<string, unknown>) => ({
...ledgerRecord,
status: 'responded',
responseState: 'responded_visible_message',
failedAt: null,
lastReason: null,
visibleReplyInbox: input.visibleReplyInbox,
visibleReplyMessageId: input.visibleReplyMessageId,
visibleReplyCorrelation: input.visibleReplyCorrelation,
inboxReadCommittedAt: '2026-02-23T17:01:01.000Z',
respondedAt: input.observedAt,
updatedAt: input.observedAt,
}));
const advisoryInvalidator = vi.fn();
const teamChangeEmitter = vi.fn();
service.setMemberRuntimeAdvisoryInvalidator(advisoryInvalidator);
service.setTeamChangeEmitter(teamChangeEmitter);
const result = await (service as any).applyOpenCodeVisibleDestinationProof({
ledger: { applyDestinationProof },
ledgerRecord,
teamName,
replyRecipient: 'team-lead',
memberName: 'jack',
});
expect(result.visibleReply).toBe(visibleReply);
expect(result.ledgerRecord.status).toBe('responded');
expect(applyDestinationProof).toHaveBeenCalledWith(
expect.objectContaining({
id: 'ledger-terminal-recovered',
visibleReplyInbox: 'team-lead',
visibleReplyMessageId: 'visible-reply-recovered',
visibleReplyCorrelation: 'relayOfMessageId',
semanticallySufficient: true,
})
);
expect(advisoryInvalidator).toHaveBeenCalledWith(teamName, 'jack');
expect(teamChangeEmitter).toHaveBeenCalledWith(
expect.objectContaining({
type: 'member-advisory',
teamName,
detail: 'runtime-delivery-reply:jack:opencode-terminal-recovered',
})
);
});
it('does not emit advisory refresh again for already proven OpenCode visible replies', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const ledgerRecord = {
id: 'ledger-already-proven',
teamName,
memberName: 'jack',
laneId: 'secondary:opencode:jack',
runId: 'run-1',
runtimeSessionId: 'ses-1',
inboxMessageId: 'opencode-already-proven',
inboxTimestamp: '2026-02-23T17:00:00.000Z',
source: 'watcher',
messageKind: null,
replyRecipient: 'team-lead',
actionMode: null,
taskRefs: [],
payloadHash: 'sha256:test',
status: 'responded',
responseState: 'responded_visible_message',
attempts: 1,
maxAttempts: 3,
acceptanceUnknown: false,
nextAttemptAt: null,
lastAttemptAt: '2026-02-23T17:00:03.000Z',
lastObservedAt: '2026-02-23T17:01:00.000Z',
acceptedAt: '2026-02-23T17:00:03.000Z',
respondedAt: '2026-02-23T17:01:00.000Z',
failedAt: null,
inboxReadCommittedAt: '2026-02-23T17:01:01.000Z',
inboxReadCommitError: null,
prePromptCursor: null,
postPromptCursor: null,
deliveredUserMessageId: 'runtime-user-1',
observedAssistantMessageId: null,
observedAssistantPreview: null,
observedToolCallNames: [],
observedVisibleMessageId: null,
visibleReplyMessageId: 'visible-reply-proven',
visibleReplyInbox: 'team-lead',
visibleReplyCorrelation: 'relayOfMessageId',
lastReason: null,
diagnostics: [],
createdAt: '2026-02-23T17:00:00.000Z',
updatedAt: '2026-02-23T17:01:01.000Z',
};
const visibleReply = {
inboxName: 'team-lead',
message: {
from: 'jack',
to: 'team-lead',
text: 'Already proven visible reply.',
summary: '#done',
timestamp: '2026-02-23T17:01:00.000Z',
read: true,
source: 'runtime_delivery',
relayOfMessageId: 'opencode-already-proven',
messageId: 'visible-reply-proven',
},
};
vi.spyOn(service as any, 'findOpenCodeVisibleReplyByRelayOfMessageId').mockResolvedValue(
visibleReply
);
const applyDestinationProof = vi.fn(async () => ledgerRecord);
const advisoryInvalidator = vi.fn();
const teamChangeEmitter = vi.fn();
service.setMemberRuntimeAdvisoryInvalidator(advisoryInvalidator);
service.setTeamChangeEmitter(teamChangeEmitter);
const result = await (service as any).applyOpenCodeVisibleDestinationProof({
ledger: { applyDestinationProof },
ledgerRecord,
teamName,
replyRecipient: 'team-lead',
memberName: 'jack',
});
expect(result.visibleReply).toBe(visibleReply);
expect(applyDestinationProof).toHaveBeenCalledWith(
expect.objectContaining({
id: 'ledger-already-proven',
visibleReplyMessageId: 'visible-reply-proven',
semanticallySufficient: true,
})
);
expect(advisoryInvalidator).not.toHaveBeenCalled();
expect(teamChangeEmitter).not.toHaveBeenCalled();
});
it('retries failed-terminal OpenCode rows caused by stale runtime manifest watermark', async () => {
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' },
],
})
);
const identity = await (service as any).resolveOpenCodeMemberDeliveryIdentity(teamName, 'jack');
expect(identity.ok).toBe(true);
const staleRecord = {
id: 'ledger-terminal-stale-manifest',
teamName,
memberName: 'jack',
laneId: 'secondary:opencode:jack',
runId: 'run-1',
status: 'failed_terminal',
responseState: 'reconcile_failed',
attempts: 3,
maxAttempts: 3,
inboxMessageId: 'opencode-terminal-stale-manifest',
replyRecipient: 'team-lead',
actionMode: null,
taskRefs: [],
source: 'watcher',
lastReason:
'opencode_message_delivery_exception: Bridge server runtime manifest high watermark is stale',
diagnostics: [
'opencode_message_delivery_exception: Bridge server runtime manifest high watermark is stale',
],
};
const markNextAttemptScheduled = vi.fn(async (input: Record<string, unknown>) => ({
...staleRecord,
status: input.status,
nextAttemptAt: input.nextAttemptAt,
lastReason: input.reason,
}));
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
getByInboxMessage: vi.fn(async (input: { inboxMessageId: string }) =>
input.inboxMessageId === 'opencode-terminal-stale-manifest' ? staleRecord : null
),
markNextAttemptScheduled,
});
seedMemberInbox(teamName, 'jack', [
{
from: 'bob',
to: 'jack',
text: 'Old stale manifest row.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-terminal-stale-manifest',
},
{
from: 'bob',
to: 'jack',
text: 'New row should wait behind the retried old row.',
timestamp: '2026-02-23T17:00:02.000Z',
read: false,
messageId: 'opencode-terminal-new',
},
]);
const deliverSpy = vi
.spyOn(service, 'deliverOpenCodeMemberMessage')
.mockResolvedValue({ delivered: true, diagnostics: [] });
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
expect(markNextAttemptScheduled).toHaveBeenCalledWith(
expect.objectContaining({
id: 'ledger-terminal-stale-manifest',
status: 'retry_scheduled',
reason: 'opencode_prompt_delivery_requeued_after_runtime_manifest_high_watermark_fix',
})
);
expect(relay).toMatchObject({ relayed: 1, attempted: 1, delivered: 1, failed: 0 });
expect(deliverSpy).toHaveBeenCalledTimes(1);
expect(deliverSpy).toHaveBeenCalledWith(
teamName,
expect.objectContaining({ messageId: 'opencode-terminal-stale-manifest' })
);
const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]');
expect(rows.map((row: { read?: boolean }) => row.read)).toEqual([true, false]);
});
it('fails OpenCode secondary rows with missing attachment payloads terminally without text-only delivery', async () => {
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' },
],
})
);
const identity = await (service as any).resolveOpenCodeMemberDeliveryIdentity(teamName, 'jack');
expect(identity.ok).toBe(true);
const records: any[] = [];
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
getByInboxMessage: vi.fn(async () => null),
ensurePending: vi.fn(async (input: Record<string, unknown>) => {
const record = {
id: 'ledger-attachment-1',
status: 'pending',
responseState: 'not_observed',
diagnostics: [] as string[],
...input,
};
records.push(record);
return record;
}),
markFailedTerminal: vi.fn(async (input: { id: string; reason: string; failedAt: string }) => {
const record = records.find((candidate) => candidate.id === input.id);
Object.assign(record, {
status: 'failed_terminal',
failedAt: input.failedAt,
lastReason: input.reason,
diagnostics: [input.reason],
});
return record;
}),
list: vi.fn(async () => records),
});
seedMemberInbox(teamName, 'jack', [
{
from: 'bob',
to: 'jack',
text: 'Please inspect the attachment.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-attachment-1',
attachments: [
{
id: 'att-1',
filename: 'trace.log',
mimeType: 'text/plain',
size: 128,
addedAt: '2026-02-23T17:00:00.000Z',
},
],
},
]);
const deliverSpy = vi.spyOn(service, 'deliverOpenCodeMemberMessage');
const logSpy = vi
.spyOn(service as any, 'logOpenCodePromptDeliveryEvent')
.mockImplementation(() => undefined);
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
const expectedReason = 'opencode_inbox_attachment_payload_unavailable: att-1';
expect(relay).toMatchObject({
relayed: 0,
attempted: 1,
delivered: 0,
failed: 1,
lastDelivery: {
delivered: false,
reason: expectedReason,
},
});
expect(deliverSpy).not.toHaveBeenCalled();
expect(relay.diagnostics?.join('\n')).toContain(expectedReason);
vi.mocked(console.warn).mockClear();
const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]');
expect(rows[0].read).toBe(false);
expect(records[0]).toMatchObject({
inboxMessageId: 'opencode-attachment-1',
status: 'failed_terminal',
lastReason: expectedReason,
});
expect(logSpy).toHaveBeenCalledWith(
'opencode_prompt_delivery_terminal_failure',
expect.objectContaining({
inboxMessageId: 'opencode-attachment-1',
status: 'failed_terminal',
lastReason: expectedReason,
}),
expect.objectContaining({
attachmentPayloadUnavailable: true,
reason: expectedReason,
})
);
});
it('rebuilds missing OpenCode prompt ledger rows from unread inbox on startup scan', async () => {
vi.useFakeTimers();
try {
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' },
],
})
);
const identity = await (service as any).resolveOpenCodeMemberDeliveryIdentity(
teamName,
'jack'
);
expect(identity.ok).toBe(true);
const laneId = identity.laneId;
const records: any[] = [];
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
pruneTerminalRecords: vi.fn(async () => ({ pruned: 0, remaining: records.length })),
list: vi.fn(async () => records),
getByInboxMessage: vi.fn(async () => null),
ensurePending: vi.fn(async (input: Record<string, unknown>) => {
const record = {
id: 'ledger-rebuild-1',
status: 'pending',
responseState: 'not_observed',
acceptanceUnknown: false,
diagnostics: [] as string[],
...input,
};
records.push(record);
return record;
}),
markAcceptanceUnknown: vi.fn(
async (input: {
id: string;
reason: string;
nextAttemptAt: string;
markedAt: string;
}) => {
const record = records.find((candidate) => candidate.id === input.id);
Object.assign(record, {
status: 'failed_retryable',
acceptanceUnknown: true,
nextAttemptAt: input.nextAttemptAt,
lastReason: input.reason,
updatedAt: input.markedAt,
});
return record;
}
),
markFailedTerminal: vi.fn(async (input: { id: string; reason: string }) => {
const record = records.find((candidate) => candidate.id === input.id);
Object.assign(record, {
status: 'failed_terminal',
lastReason: input.reason,
diagnostics: [input.reason],
});
return record;
}),
});
seedMemberInbox(teamName, 'jack', [
{
from: 'bob',
to: 'jack',
text: 'Recover this delivery.',
timestamp: '2026-02-23T17:00:00.000Z',
read: false,
messageId: 'opencode-rebuild-1',
},
]);
const scheduled = await (service as any).scanOpenCodePromptDeliveryWatchdogForActiveLanes(
teamName,
[laneId]
);
expect(scheduled).toBe(1);
expect(records[0]).toMatchObject({
inboxMessageId: 'opencode-rebuild-1',
status: 'failed_retryable',
acceptanceUnknown: true,
lastReason: 'opencode_prompt_delivery_ledger_rebuilt_from_unread_inbox',
});
} finally {
vi.useRealTimers();
}
});
it('schedules existing pending OpenCode prompt ledger rows with no next attempt on startup scan', async () => {
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' },
],
})
);
const identity = await (service as any).resolveOpenCodeMemberDeliveryIdentity(teamName, 'jack');
expect(identity.ok).toBe(true);
const laneId = identity.laneId;
const scheduleSpy = vi
.spyOn(service as any, 'scheduleOpenCodePromptDeliveryWatchdog')
.mockImplementation(() => undefined);
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
pruneTerminalRecords: vi.fn(async () => ({ pruned: 0, remaining: 1 })),
list: vi.fn(async () => [
{
id: 'ledger-existing-pending-1',
teamName,
memberName: 'jack',
laneId,
inboxMessageId: 'opencode-existing-pending-1',
status: 'pending',
responseState: 'not_observed',
attempts: 0,
maxAttempts: 3,
nextAttemptAt: null,
diagnostics: [],
createdAt: '2026-02-23T17:00:00.000Z',
},
]),
getByInboxMessage: vi.fn(async () => null),
});
const scheduled = await (service as any).scanOpenCodePromptDeliveryWatchdogForActiveLanes(
teamName,
[laneId]
);
expect(scheduled).toBe(1);
expect(scheduleSpy).toHaveBeenCalledWith(
expect.objectContaining({
teamName,
memberName: 'jack',
messageId: 'opencode-existing-pending-1',
delayMs: expect.any(Number),
})
);
});
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';
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 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);
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('keeps an already-read work-sync nudge pending when it is queued behind an active relay', async () => {
vi.useFakeTimers();
const service = new TeamProvisioningService();
const teamName = 'my-team';
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>();
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 wakeSpy = vi
.spyOn(service, 'scheduleOpenCodeMemberInboxDeliveryWake')
.mockImplementation(() => 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: 'system',
to: 'jack',
text: 'Call member_work_sync_status, then member_work_sync_report.',
timestamp: '2026-02-23T17:00:01.000Z',
read: true,
messageId: 'work-sync-read-queued',
messageKind: 'member_work_sync_nudge',
workSyncIntent: 'agenda_sync',
},
]);
await expect(
service.relayOpenCodeMemberInboxMessages(teamName, 'jack', {
onlyMessageId: 'work-sync-read-queued',
source: 'watchdog',
})
).resolves.toMatchObject({
attempted: 1,
delivered: 0,
failed: 0,
lastDelivery: {
delivered: true,
accepted: false,
responsePending: true,
reason: 'opencode_work_sync_read_commit_waiting_for_active_relay',
},
});
expect(wakeSpy).toHaveBeenCalledWith({
teamName,
memberName: 'jack',
messageId: 'work-sync-read-queued',
delayMs: 500,
});
releaseOldDelivery.resolve(undefined);
await watcherRelay;
} finally {
vi.useRealTimers();
}
});
it('times out a hung existing OpenCode member relay in-flight lock', async () => {
vi.useFakeTimers();
const service = new TeamProvisioningService();
const teamName = 'my-team';
const relayKey = `opencode:${teamName}:jack`;
try {
(
service as unknown as {
openCodeMemberInboxRelayInFlight: Map<string, Promise<unknown>>;
}
).openCodeMemberInboxRelayInFlight.set(relayKey, new Promise(() => undefined));
const relay = service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
await vi.advanceTimersByTimeAsync(120_000);
await expect(relay).resolves.toMatchObject({
attempted: 0,
delivered: 0,
failed: 1,
lastDelivery: {
delivered: false,
accepted: false,
responsePending: false,
reason: 'opencode_member_inbox_relay_timed_out',
},
});
expect(
(
service as unknown as {
openCodeMemberInboxRelayInFlight: Map<string, Promise<unknown>>;
}
).openCodeMemberInboxRelayInFlight.has(relayKey)
).toBe(false);
expect(vi.mocked(console.warn).mock.calls[0]?.join(' ')).toContain(
'opencode_member_inbox_relay_timed_out'
);
vi.mocked(console.warn).mockClear();
} finally {
vi.useRealTimers();
}
});
it('times out a hung existing lead relay in-flight lock', async () => {
vi.useFakeTimers();
const service = new TeamProvisioningService();
const teamName = 'my-team';
try {
(
service as unknown as {
leadInboxRelayInFlight: Map<string, Promise<number>>;
}
).leadInboxRelayInFlight.set(teamName, new Promise(() => undefined));
const relay = service.relayLeadInboxMessages(teamName);
await vi.advanceTimersByTimeAsync(120_000);
await expect(relay).resolves.toBe(0);
expect(
(
service as unknown as {
leadInboxRelayInFlight: Map<string, Promise<number>>;
}
).leadInboxRelayInFlight.has(teamName)
).toBe(false);
expect(vi.mocked(console.warn).mock.calls[0]?.join(' ')).toContain(
'lead_inbox_relay_timed_out'
);
vi.mocked(console.warn).mockClear();
} finally {
vi.useRealTimers();
}
});
it('times out a hung existing member relay in-flight lock', async () => {
vi.useFakeTimers();
const service = new TeamProvisioningService();
const teamName = 'my-team';
const relayKey = `${teamName}:alice`;
try {
(
service as unknown as {
memberInboxRelayInFlight: Map<string, Promise<number>>;
}
).memberInboxRelayInFlight.set(relayKey, new Promise(() => undefined));
const relay = service.relayMemberInboxMessages(teamName, 'alice');
await vi.advanceTimersByTimeAsync(120_000);
await expect(relay).resolves.toBe(0);
expect(
(
service as unknown as {
memberInboxRelayInFlight: Map<string, Promise<number>>;
}
).memberInboxRelayInFlight.has(relayKey)
).toBe(false);
expect(vi.mocked(console.warn).mock.calls[0]?.join(' ')).toContain(
'member_inbox_relay_timed_out'
);
vi.mocked(console.warn).mockClear();
} finally {
vi.useRealTimers();
}
});
it('does not convert non-timeout member relay failures into timeout results', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const relayKey = `${teamName}:alice`;
const rejected = Promise.reject(new Error('relay failed'));
rejected.catch(() => undefined);
(
service as unknown as {
memberInboxRelayInFlight: Map<string, Promise<number>>;
}
).memberInboxRelayInFlight.set(relayKey, rejected);
await expect(service.relayMemberInboxMessages(teamName, 'alice')).rejects.toThrow(
'relay failed'
);
expect(vi.mocked(console.warn)).not.toHaveBeenCalled();
});
it('treats an already-read specific OpenCode inbox row as delivered for UI-send relay', async () => {
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: 'user',
to: 'jack',
text: 'Already relayed.',
timestamp: '2026-02-23T17:02:00.000Z',
read: true,
messageId: 'opencode-already-read-1',
},
]);
const deliverSpy = vi.spyOn(service, 'deliverOpenCodeMemberMessage');
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack', {
onlyMessageId: 'opencode-already-read-1',
source: 'ui-send',
});
expect(relay).toMatchObject({
relayed: 0,
attempted: 1,
delivered: 1,
failed: 0,
lastDelivery: { delivered: true },
});
expect(deliverSpy).not.toHaveBeenCalled();
});
it('does not treat an already-read work-sync nudge as delivered without the work-sync proof path', async () => {
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: 'system',
to: 'jack',
text: 'Call member_work_sync_status, then member_work_sync_report.',
timestamp: '2026-02-23T17:02:00.000Z',
read: true,
messageId: 'work-sync-read-1',
messageKind: 'member_work_sync_nudge',
workSyncIntent: 'agenda_sync',
taskRefs: [{ taskId: 'task-1', teamName }],
},
]);
const deliverSpy = vi.spyOn(service, 'deliverOpenCodeMemberMessage').mockResolvedValue({
delivered: true,
accepted: false,
responsePending: true,
reason: 'member_work_sync_report_required',
diagnostics: ['member_work_sync_report_required'],
});
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack', {
onlyMessageId: 'work-sync-read-1',
source: 'watchdog',
});
expect(deliverSpy).toHaveBeenCalledWith(
teamName,
expect.objectContaining({
memberName: 'jack',
messageId: 'work-sync-read-1',
messageKind: 'member_work_sync_nudge',
workSyncIntent: 'agenda_sync',
})
);
expect(relay).toMatchObject({
attempted: 1,
delivered: 0,
failed: 0,
lastDelivery: {
delivered: true,
accepted: false,
responsePending: true,
reason: 'member_work_sync_report_required',
},
});
});
it('routes watcher inbox changes for OpenCode members through direct runtime relay', async () => {
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: 'Please review this.',
timestamp: '2026-02-23T17:05:00.000Z',
read: false,
messageId: 'opencode-selector-relay-1',
},
]);
vi.spyOn(service, 'deliverOpenCodeMemberMessage').mockResolvedValue({
delivered: true,
diagnostics: [],
});
const recipientSpy = vi.spyOn(service, 'isOpenCodeRuntimeRecipient');
const relay = await service.relayInboxFileToLiveRecipient(teamName, 'jack');
expect(relay).toMatchObject({ kind: 'opencode_member', relayed: 1 });
expect(recipientSpy).toHaveBeenCalledTimes(1);
const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]');
expect(rows[0].read).toBe(true);
});
it('routes OpenCode lead inbox rows through OpenCode member relay', async () => {
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',
providerId: 'opencode',
model: 'openrouter/test',
},
],
})
);
seedLeadInbox(teamName, [
{
from: 'user',
to: 'team-lead',
text: 'Please coordinate.',
timestamp: '2026-02-23T17:06:00.000Z',
read: false,
messageId: 'opencode-lead-unread-1',
},
]);
const relaySpy = vi.spyOn(service, 'relayOpenCodeMemberInboxMessages').mockResolvedValue({
relayed: 1,
attempted: 1,
delivered: 1,
failed: 0,
diagnostics: ['fake OpenCode lead relay ready'],
lastDelivery: {
delivered: true,
accepted: true,
responsePending: false,
},
});
const relay = await service.relayInboxFileToLiveRecipient(teamName, 'team-lead', {
onlyMessageId: 'opencode-lead-unread-1',
source: 'ui-send',
deliveryMetadata: {
replyRecipient: 'user',
actionMode: 'do',
},
});
expect(relay).toMatchObject({
kind: 'opencode_member',
relayed: 1,
diagnostics: ['fake OpenCode lead relay ready'],
lastDelivery: {
delivered: true,
accepted: true,
responsePending: false,
},
});
expect(relaySpy).toHaveBeenCalledWith(
teamName,
'team-lead',
expect.objectContaining({
onlyMessageId: 'opencode-lead-unread-1',
source: 'ui-send',
deliveryMetadata: expect.objectContaining({
replyRecipient: 'user',
actionMode: 'do',
}),
})
);
});
it('keeps failed OpenCode member inbox relay rows unread for retry', async () => {
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: 'Please review this.',
timestamp: '2026-02-23T17:10:00.000Z',
read: false,
messageId: 'opencode-relay-failed-1',
},
]);
vi.spyOn(service, 'deliverOpenCodeMemberMessage').mockResolvedValue({
delivered: false,
reason: 'opencode_runtime_not_active',
diagnostics: ['opencode_runtime_not_active'],
});
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
expect(relay).toMatchObject({
relayed: 0,
attempted: 1,
delivered: 0,
failed: 1,
lastDelivery: { delivered: false, reason: 'opencode_runtime_not_active' },
});
expect(vi.mocked(console.warn).mock.calls[0]?.join(' ')).toContain(
'OpenCode inbox relay failed for jack/opencode-relay-failed-1'
);
vi.mocked(console.warn).mockClear();
const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]');
expect(rows[0].read).toBe(false);
});
it('treats OpenCode mark-read failure after prompt acceptance as an uncommitted relay', async () => {
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: 'Please review this.',
timestamp: '2026-02-23T17:20:00.000Z',
read: false,
messageId: 'opencode-mark-read-failed-1',
},
]);
vi.spyOn(service, 'deliverOpenCodeMemberMessage').mockResolvedValue({
delivered: true,
diagnostics: [],
});
vi.spyOn(service as any, 'markInboxMessagesRead').mockRejectedValue(new Error('write failed'));
const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack');
expect(relay).toMatchObject({
relayed: 0,
attempted: 1,
delivered: 0,
failed: 1,
lastDelivery: {
delivered: false,
reason: 'opencode_inbox_mark_read_failed_after_delivery',
},
});
expect(relay.diagnostics?.join('\n')).toContain(
'opencode_inbox_mark_read_failed_after_delivery'
);
expect(vi.mocked(console.warn).mock.calls[0]?.join(' ')).toContain(
'opencode_inbox_mark_read_failed_after_delivery'
);
vi.mocked(console.warn).mockClear();
const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]');
expect(rows[0].read).toBe(false);
});
it('fails closed when OpenCode prompt ledger cannot be inspected for work-sync busy checks', 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(
OpenCodeRuntimeStore.getOpenCodeRuntimeLaneIndexPath(teamsBasePath, teamName),
JSON.stringify({
version: 1,
updatedAt: '2026-02-23T17:30:00.000Z',
lanes: {
primary: {
laneId: 'primary',
state: 'active',
updatedAt: '2026-02-23T17:30:00.000Z',
},
[laneId]: {
laneId,
state: 'active',
updatedAt: '2026-02-23T17:30:00.000Z',
},
},
})
);
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',
},
},
});
hoisted.files.set(`${teamsBasePath}/${teamName}/inboxes/jack.json`, JSON.stringify([]));
(service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({
ok: true,
canonicalMemberName: 'jack',
laneId,
}));
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
getActiveForMember: vi.fn(async () => {
throw new Error('ledger read failed');
}),
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:31:00.000Z',
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_prompt_ledger_unavailable',
});
});
it('treats unread OpenCode foreground inbox messages as busy for work-sync checks', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const teamsBasePath = getTeamsBasePath();
const wakeSpy = vi
.spyOn(service, 'scheduleOpenCodeMemberInboxDeliveryWake')
.mockImplementation(() => undefined);
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',
},
])
);
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:31:10.000Z',
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_foreground_inbox_unread',
activeMessageId: 'foreground-message-1',
});
expect(wakeSpy).toHaveBeenCalledWith({
teamName,
memberName: 'jack',
messageId: 'foreground-message-1',
delayMs: 500,
});
});
it('wakes an active OpenCode foreground delivery instead of blocking work-sync on unread inbox state', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const wakeSpy = vi
.spyOn(service, 'scheduleOpenCodeMemberInboxDeliveryWake')
.mockImplementation(() => undefined);
seedOpenCodeBusyStatusFixture({
service,
teamName,
laneId,
inboxMessages: [
{
from: 'team-lead',
to: 'jack',
text: 'New task assigned to you.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'foreground-message-1',
messageKind: 'default',
},
],
activeRecord: {
inboxMessageId: 'foreground-message-1',
messageKind: 'default',
nextAttemptAt: '2026-02-23T17:33:00.000Z',
},
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:31:10.000Z',
workSyncIntent: 'agenda_sync',
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_prompt_delivery_active:default',
activeMessageId: 'foreground-message-1',
retryAfterIso: '2026-02-23T17:33:00.000Z',
});
expect(wakeSpy).toHaveBeenCalledWith(
expect.objectContaining({
teamName,
memberName: 'jack',
messageId: 'foreground-message-1',
})
);
});
it('prioritizes an active OpenCode prompt ledger over newer unread foreground messages', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const wakeSpy = vi
.spyOn(service, 'scheduleOpenCodeMemberInboxDeliveryWake')
.mockImplementation(() => undefined);
seedOpenCodeBusyStatusFixture({
service,
teamName,
laneId,
inboxMessages: [
{
from: 'team-lead',
to: 'jack',
text: 'Follow-up comment after assignment.',
timestamp: '2026-02-23T17:32:00.000Z',
read: false,
messageId: 'foreground-comment-1',
messageKind: 'default',
},
],
activeRecord: {
inboxMessageId: 'foreground-assignment-1',
messageKind: 'default',
nextAttemptAt: '2026-02-23T17:33:00.000Z',
},
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:32:10.000Z',
workSyncIntent: 'agenda_sync',
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_prompt_delivery_active:default',
activeMessageId: 'foreground-assignment-1',
retryAfterIso: '2026-02-23T17:33:00.000Z',
});
expect(wakeSpy).toHaveBeenCalledWith(
expect.objectContaining({
teamName,
memberName: 'jack',
messageId: 'foreground-assignment-1',
})
);
});
it('recovers a missing OpenCode lane before using an active prompt ledger as busy state', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const memberName = 'jack';
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: memberName, role: 'developer', providerId: 'opencode', model: 'openrouter/test' },
],
})
);
hoisted.files.set(
`${teamsBasePath}/${teamName}/inboxes/${memberName}.json`,
JSON.stringify([
{
from: 'team-lead',
to: memberName,
text: 'New task assigned to you.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'foreground-message-1',
messageKind: 'default',
},
])
);
(service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({
ok: true,
canonicalMemberName: memberName,
laneId,
}));
vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex')
.mockResolvedValueOnce({
version: 1,
updatedAt: '2026-02-23T17:30:00.000Z',
lanes: {},
})
.mockResolvedValue({
version: 1,
updatedAt: '2026-02-23T17:30:01.000Z',
lanes: {
[laneId]: {
laneId,
state: 'active',
updatedAt: '2026-02-23T17:30:01.000Z',
},
},
});
const recoverySpy = vi
.spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery')
.mockResolvedValue(true);
const wakeSpy = vi
.spyOn(service, 'scheduleOpenCodeMemberInboxDeliveryWake')
.mockImplementation(() => undefined);
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
getActiveForMember: vi.fn(async () => ({
id: 'ledger-foreground-message-1',
teamName,
memberName,
laneId,
inboxMessageId: 'foreground-message-1',
messageKind: 'default',
status: 'pending',
responseState: 'not_observed',
nextAttemptAt: '2026-02-23T17:33:00.000Z',
})),
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName,
nowIso: '2026-02-23T17:31:10.000Z',
workSyncIntent: 'agenda_sync',
});
expect(recoverySpy).toHaveBeenCalledWith({ teamName, memberName });
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_prompt_delivery_active:default',
activeMessageId: 'foreground-message-1',
});
expect(wakeSpy).toHaveBeenCalledWith(
expect.objectContaining({ teamName, memberName, messageId: 'foreground-message-1' })
);
});
it('does not use an active OpenCode prompt ledger when recovery leaves the lane inactive', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const memberName = 'jack';
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: memberName, role: 'developer', providerId: 'opencode', model: 'openrouter/test' },
],
})
);
hoisted.files.set(
`${teamsBasePath}/${teamName}/inboxes/${memberName}.json`,
JSON.stringify([
{
from: 'team-lead',
to: memberName,
text: 'New task assigned to you.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'foreground-message-1',
messageKind: 'default',
},
])
);
(service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({
ok: true,
canonicalMemberName: memberName,
laneId,
}));
vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({
version: 1,
updatedAt: '2026-02-23T17:30:00.000Z',
lanes: {},
});
const recoverySpy = vi
.spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery')
.mockResolvedValue(true);
const wakeSpy = vi
.spyOn(service, 'scheduleOpenCodeMemberInboxDeliveryWake')
.mockImplementation(() => undefined);
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
getActiveForMember: vi.fn(async () => ({
id: 'ledger-foreground-message-1',
teamName,
memberName,
laneId,
inboxMessageId: 'foreground-message-1',
messageKind: 'default',
status: 'pending',
responseState: 'not_observed',
nextAttemptAt: '2026-02-23T17:33:00.000Z',
})),
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName,
nowIso: '2026-02-23T17:31:10.000Z',
workSyncIntent: 'agenda_sync',
});
expect(recoverySpy).toHaveBeenCalledWith({ teamName, memberName });
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_foreground_inbox_unread',
activeMessageId: 'foreground-message-1',
});
expect(wakeSpy).toHaveBeenCalledWith({
teamName,
memberName,
messageId: 'foreground-message-1',
delayMs: 500,
});
});
it('recovers a missing OpenCode lane before treating work-sync delivery as unavailable', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const memberName = 'jack';
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: memberName, role: 'developer', providerId: 'opencode', model: 'openrouter/test' },
],
})
);
hoisted.files.set(
`${teamsBasePath}/${teamName}/inboxes/${memberName}.json`,
JSON.stringify([])
);
(service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({
ok: true,
canonicalMemberName: memberName,
laneId,
}));
vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex')
.mockResolvedValueOnce({
version: 1,
updatedAt: '2026-02-23T17:30:00.000Z',
lanes: {},
})
.mockResolvedValue({
version: 1,
updatedAt: '2026-02-23T17:30:01.000Z',
lanes: {
[laneId]: {
laneId,
state: 'active',
updatedAt: '2026-02-23T17:30:01.000Z',
},
},
});
const recoverySpy = vi
.spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery')
.mockResolvedValue(true);
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
getActiveForMember: vi.fn(async () => null),
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName,
nowIso: '2026-02-23T17:31:10.000Z',
workSyncIntent: 'agenda_sync',
});
expect(recoverySpy).toHaveBeenCalledWith({ teamName, memberName });
expect(busy).toEqual({ busy: false });
});
it('keeps OpenCode work-sync busy when recovery reports success but lane index is still inactive', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const memberName = 'jack';
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: memberName, role: 'developer', providerId: 'opencode', model: 'openrouter/test' },
],
})
);
hoisted.files.set(
`${teamsBasePath}/${teamName}/inboxes/${memberName}.json`,
JSON.stringify([])
);
(service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({
ok: true,
canonicalMemberName: memberName,
laneId,
}));
vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({
version: 1,
updatedAt: '2026-02-23T17:30:00.000Z',
lanes: {},
});
const recoverySpy = vi
.spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery')
.mockResolvedValue(true);
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName,
nowIso: '2026-02-23T17:31:10.000Z',
workSyncIntent: 'agenda_sync',
});
expect(recoverySpy).toHaveBeenCalledWith({ teamName, memberName });
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_no_active_lane',
});
});
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('allows OpenCode agenda-sync recovery past the exact proof-missing foreground message', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const teamsBasePath = getTeamsBasePath();
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
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: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'proof-missing-message-1',
messageKind: 'default',
taskRefs: [taskRef],
},
])
);
(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({
list: vi.fn(async () => [
buildOpenCodeProofMissingRecord({
teamName,
memberName: 'jack',
laneId,
inboxMessageId: 'proof-missing-message-1',
taskRefs: [taskRef],
}),
]),
getActiveForMember: vi.fn(async () => null),
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:32:00.000Z',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
});
expect(busy).toEqual({ busy: false });
});
it('allows OpenCode agenda-sync proof-missing bypass after recovering a missing lane index', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const teamsBasePath = getTeamsBasePath();
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
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: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'proof-missing-message-1',
messageKind: 'default',
taskRefs: [taskRef],
},
])
);
(service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({
ok: true,
canonicalMemberName: 'jack',
laneId,
}));
vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex')
.mockResolvedValueOnce({
version: 1,
updatedAt: '2026-02-23T17:30:00.000Z',
lanes: {},
})
.mockResolvedValue({
version: 1,
updatedAt: '2026-02-23T17:30:01.000Z',
lanes: {
[laneId]: {
laneId,
state: 'active',
updatedAt: '2026-02-23T17:30:01.000Z',
},
},
});
const recoverySpy = vi
.spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery')
.mockResolvedValue(true);
vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({
list: vi.fn(async () => [
buildOpenCodeProofMissingRecord({
teamName,
memberName: 'jack',
laneId,
inboxMessageId: 'proof-missing-message-1',
taskRefs: [taskRef],
}),
]),
getActiveForMember: vi.fn(async () => null),
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:32:00.000Z',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
});
expect(recoverySpy).toHaveBeenCalledWith({ teamName, memberName: 'jack' });
expect(busy).toEqual({ busy: false });
});
it('keeps OpenCode agenda-sync proof-missing bypass disabled when lane index is unreadable', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
(service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({
ok: true,
canonicalMemberName: 'jack',
laneId,
}));
vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockRejectedValue(
new Error('temporary read failure')
);
const recoverySpy = vi
.spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery')
.mockResolvedValue(true);
const bypass = await (service as any).getOpenCodeAgendaSyncRecoveryBypassMessageIds({
teamName,
memberName: 'jack',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
foregroundMessages: [
{
from: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'proof-missing-message-1',
messageKind: 'default',
taskRefs: [taskRef],
},
],
});
expect(recoverySpy).not.toHaveBeenCalled();
expect(bypass).toEqual(new Set());
});
it('allows OpenCode agenda-sync recovery for legacy proof-missing foreground ids', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
const legacyMessage = {
from: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageKind: 'default',
taskRefs: [taskRef],
};
const legacyMessageId = buildLegacyInboxMessageId(
legacyMessage.from,
legacyMessage.timestamp,
legacyMessage.text
);
seedOpenCodeBusyStatusFixture({
service,
teamName,
laneId,
inboxMessages: [legacyMessage],
ledgerRecords: [
buildOpenCodeProofMissingRecord({
teamName,
memberName: 'jack',
laneId,
inboxMessageId: legacyMessageId,
taskRefs: [taskRef],
}),
],
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:32:00.000Z',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
});
expect(busy).toEqual({ busy: false });
});
it('keeps newer same-task OpenCode foreground messages busy during agenda-sync recovery', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
seedOpenCodeBusyStatusFixture({
service,
teamName,
laneId,
inboxMessages: [
{
from: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'proof-missing-message-1',
messageKind: 'default',
taskRefs: [taskRef],
},
{
from: 'team-lead',
to: 'jack',
text: 'Dependency resolved. Please re-check #task1234.',
timestamp: '2026-02-23T17:31:40.000Z',
read: false,
messageId: 'same-task-follow-up-1',
messageKind: 'default',
taskRefs: [taskRef],
},
],
ledgerRecords: [
buildOpenCodeProofMissingRecord({
teamName,
memberName: 'jack',
laneId,
inboxMessageId: 'proof-missing-message-1',
taskRefs: [taskRef],
}),
],
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:32:00.000Z',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_foreground_inbox_unread',
activeMessageId: 'same-task-follow-up-1',
});
});
it('keeps OpenCode agenda-sync busy when proof-missing recovery evidence is absent', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const teamsBasePath = getTeamsBasePath();
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
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: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'foreground-message-1',
messageKind: 'default',
taskRefs: [taskRef],
},
])
);
(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({
list: vi.fn(async () => []),
getActiveForMember: vi.fn(async () => null),
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:31:10.000Z',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_foreground_inbox_unread',
activeMessageId: 'foreground-message-1',
});
});
it('keeps unrelated unread OpenCode foreground messages busy during agenda-sync recovery', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const teamsBasePath = getTeamsBasePath();
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
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: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'proof-missing-message-1',
messageKind: 'default',
taskRefs: [taskRef],
},
{
from: 'user',
to: 'jack',
text: 'Unrelated direct instruction.',
timestamp: '2026-02-23T17:31:20.000Z',
read: false,
messageId: 'unrelated-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({
list: vi.fn(async () => [
buildOpenCodeProofMissingRecord({
teamName,
memberName: 'jack',
laneId,
inboxMessageId: 'proof-missing-message-1',
taskRefs: [taskRef],
}),
]),
getActiveForMember: vi.fn(async () => null),
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:31:30.000Z',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_foreground_inbox_unread',
activeMessageId: 'unrelated-message-1',
});
});
it('keeps OpenCode agenda-sync busy when an active prompt ledger record exists after recovery bypass', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const teamsBasePath = getTeamsBasePath();
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
const proofMissingRecord = buildOpenCodeProofMissingRecord({
teamName,
memberName: 'jack',
laneId,
inboxMessageId: 'proof-missing-message-1',
taskRefs: [taskRef],
});
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: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'proof-missing-message-1',
messageKind: 'default',
taskRefs: [taskRef],
},
])
);
(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({
list: vi.fn(async () => [proofMissingRecord]),
getActiveForMember: vi.fn(async () => ({
...proofMissingRecord,
id: 'opencode-prompt:active-nudge-1',
inboxMessageId: 'active-nudge-1',
messageKind: 'member_work_sync_nudge',
status: 'accepted',
nextAttemptAt: '2026-02-23T17:33:00.000Z',
})),
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:32:00.000Z',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_prompt_delivery_active:member_work_sync_nudge',
activeMessageId: 'active-nudge-1',
retryAfterIso: '2026-02-23T17:33:00.000Z',
});
});
it('keeps OpenCode agenda-sync busy for same-task proof-missing messages with attachments', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const teamsBasePath = getTeamsBasePath();
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
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: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'proof-missing-message-1',
messageKind: 'default',
taskRefs: [taskRef],
attachments: [{ id: 'attachment-1', filename: 'notes.txt', mimeType: 'text/plain' }],
},
])
);
(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({
list: vi.fn(async () => [
buildOpenCodeProofMissingRecord({
teamName,
memberName: 'jack',
laneId,
inboxMessageId: 'proof-missing-message-1',
taskRefs: [taskRef],
}),
]),
getActiveForMember: vi.fn(async () => null),
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:32:00.000Z',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_foreground_inbox_unread',
activeMessageId: 'proof-missing-message-1',
});
});
it('keeps OpenCode proof-missing foreground messages busy outside agenda-sync recovery', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
seedOpenCodeBusyStatusFixture({
service,
teamName,
laneId,
inboxMessages: [
{
from: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'proof-missing-message-1',
messageKind: 'default',
taskRefs: [taskRef],
},
],
ledgerRecords: [
buildOpenCodeProofMissingRecord({
teamName,
memberName: 'jack',
laneId,
inboxMessageId: 'proof-missing-message-1',
taskRefs: [taskRef],
}),
],
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:32:00.000Z',
taskRefs: [taskRef],
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_foreground_inbox_unread',
activeMessageId: 'proof-missing-message-1',
});
});
it('keeps OpenCode agenda-sync busy when proof-missing record task refs do not overlap', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
const otherTaskRef = { teamName, taskId: 'task-9999', displayId: 'task9999' };
seedOpenCodeBusyStatusFixture({
service,
teamName,
laneId,
inboxMessages: [
{
from: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'proof-missing-message-1',
messageKind: 'default',
taskRefs: [taskRef],
},
],
ledgerRecords: [
buildOpenCodeProofMissingRecord({
teamName,
memberName: 'jack',
laneId,
inboxMessageId: 'proof-missing-message-1',
taskRefs: [otherTaskRef],
}),
],
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:32:00.000Z',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_foreground_inbox_unread',
activeMessageId: 'proof-missing-message-1',
});
});
it('keeps OpenCode agenda-sync busy when terminal ledger reason is not proof missing', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
seedOpenCodeBusyStatusFixture({
service,
teamName,
laneId,
inboxMessages: [
{
from: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'terminal-message-1',
messageKind: 'default',
taskRefs: [taskRef],
},
],
ledgerRecords: [
{
...buildOpenCodeProofMissingRecord({
teamName,
memberName: 'jack',
laneId,
inboxMessageId: 'terminal-message-1',
taskRefs: [taskRef],
}),
responseState: 'permission_blocked',
lastReason: 'permission_blocked',
diagnostics: ['permission_blocked'],
},
],
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:32:00.000Z',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_foreground_inbox_unread',
activeMessageId: 'terminal-message-1',
});
});
it('keeps OpenCode agenda-sync busy when the proof-missing lane is inactive', async () => {
const service = new TeamProvisioningService();
const teamName = 'my-team';
const laneId = 'secondary:opencode:jack';
const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' };
seedOpenCodeBusyStatusFixture({
service,
teamName,
laneId,
laneState: 'stopped',
inboxMessages: [
{
from: 'team-lead',
to: 'jack',
text: 'Please continue task #task1234.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'proof-missing-message-1',
messageKind: 'default',
taskRefs: [taskRef],
},
],
ledgerRecords: [
buildOpenCodeProofMissingRecord({
teamName,
memberName: 'jack',
laneId,
inboxMessageId: 'proof-missing-message-1',
taskRefs: [taskRef],
}),
],
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:32:00.000Z',
workSyncIntent: 'agenda_sync',
taskRefs: [taskRef],
});
expect(busy).toMatchObject({
busy: true,
reason: 'opencode_foreground_inbox_unread',
activeMessageId: 'proof-missing-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';
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: 'team-lead',
to: 'jack',
text: '**Please review** task #task1234\n\nFIRST call review_start.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'review-request-1',
source: 'system_notification',
summary: 'Review request for #task1234',
},
])
);
(service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(() =>
Promise.resolve({
ok: true,
canonicalMemberName: 'jack',
laneId,
})
);
vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockReturnValue(
Promise.resolve({
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(() => Promise.resolve(null)),
});
const busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:31:10.000Z',
workSyncIntent: 'review_pickup',
taskRefs: [{ teamName, taskId: 'task-1234', displayId: 'task1234' }],
});
expect(busy).toEqual({ busy: false });
const mismatchedTaskBusy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:31:10.000Z',
workSyncIntent: 'review_pickup',
taskRefs: [{ teamName, taskId: 'other-task', displayId: 'other' }],
});
expect(mismatchedTaskBusy).toMatchObject({
busy: true,
reason: 'opencode_foreground_inbox_unread',
activeMessageId: 'review-request-1',
});
});
it('does not treat unread OpenCode work-sync nudges as foreground busy blockers', 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: 'system',
to: 'jack',
text: 'Work sync check.',
timestamp: '2026-02-23T17:31:00.000Z',
read: false,
messageId: 'work-sync-nudge-1',
messageKind: 'member_work_sync_nudge',
},
])
);
(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 busy = await service.getOpenCodeMemberDeliveryBusyStatus({
teamName,
memberName: 'jack',
nowIso: '2026-02-23T17:31:10.000Z',
});
expect(busy).toEqual({ busy: false });
});
});