import { promises as fsPromises } from 'fs'; import { beforeEach, describe, expect, it, vi } from 'vitest'; const hoisted = vi.hoisted(() => { const files = new Map(); 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) => { 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) => { 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(); 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(); 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) => await fn(), })); vi.mock('../../../../src/main/services/team/inboxLock', () => ({ withInboxLock: async (_filePath: string, fn: () => Promise) => await fn(), })); vi.mock('../../../../src/main/utils/pathDecoder', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, getTeamsBasePath: () => '/mock/teams', }; }); vi.mock('../../../../src/main/utils/fsRead', async (importOriginal) => { const actual = await importOriginal(); 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) => hoisted.appendSentMessage(teamName, message), sendMessage: (message: Record) => 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 { 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(): { promise: Promise; resolve: (value: T) => void; reject: (error: unknown) => void; } { let resolve!: (value: T) => void; let reject!: (error: unknown) => void; const promise = new Promise((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; 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 }).aliveRunByTeam.set( teamName, runId ); (service as unknown as { runs: Map }).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 }; } async function waitForCapture(service: TeamProvisioningService): Promise { const runs = (service as unknown as { runs: Map }).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 ?? ''; (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('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 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 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', 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('it is actionable work-sync control traffic'); 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 }).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 }).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 }).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(); const inboxReader = ( service as unknown as { inboxReader: { getMessagesFor: (team: string, member: string) => Promise; }; } ).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 }).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 }).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 }).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: '\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: '\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: '\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: '\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: '\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('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` + '\nReply using task_add_comment\n', 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 }).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` + '\nHidden internal instructions\n', }, }, ], }); 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 }).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(); const inboxReader = ( service as unknown as { inboxReader: { getMessagesFor: (team: string, member: string) => Promise; }; } ).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 }).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(), 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('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('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('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) => { 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 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, }); }); 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) => { 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('does not let an older in-flight OpenCode relay mask a specific UI-send message', 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: 'Older watcher message.', timestamp: '2026-02-23T17:00:00.000Z', read: false, messageId: 'opencode-inflight-old', }, ]); const oldDeliveryStarted = createDeferred(); const releaseOldDelivery = createDeferred(); 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', }, ]); const uiRelay = service.relayOpenCodeMemberInboxMessages(teamName, 'jack', { onlyMessageId: 'opencode-inflight-new', source: 'ui-send', deliveryMetadata: { replyRecipient: 'user' }, }); releaseOldDelivery.resolve(undefined); await expect(watcherRelay).resolves.toMatchObject({ attempted: 1, delivered: 1, }); await expect(uiRelay).resolves.toMatchObject({ attempted: 1, delivered: 1, failed: 0, }); expect(deliverSpy).toHaveBeenCalledWith( teamName, expect.objectContaining({ messageId: 'opencode-inflight-old' }) ); expect(deliverSpy).toHaveBeenCalledWith( teamName, expect.objectContaining({ messageId: 'opencode-inflight-new' }) ); const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]'); expect(rows.map((row: { read?: boolean }) => row.read)).toEqual([true, true]); }); 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('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('leaves OpenCode lead inbox rows unread with an explicit unsupported diagnostic', 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 relay = await service.relayInboxFileToLiveRecipient(teamName, 'team-lead'); expect(relay).toMatchObject({ kind: 'opencode_lead_unsupported', relayed: 0 }); expect(relay.diagnostics?.join('\n')).toContain('opencode_lead_runtime_session_missing'); expect(vi.mocked(console.warn).mock.calls[0]?.join(' ')).toContain( 'opencode_lead_runtime_session_missing' ); vi.mocked(console.warn).mockClear(); const rows = JSON.parse( hoisted.files.get(`/mock/teams/${teamName}/inboxes/team-lead.json`) ?? '[]' ); expect(rows[0].read).toBe(false); }); 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(); 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', }); }); 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 }); }); });