/* eslint-disable @typescript-eslint/no-explicit-any */ import { promises as fsPromises } from 'fs'; import { beforeEach, describe, expect, it, vi } from 'vitest'; const hoisted = vi.hoisted(() => { const files = new Map(); 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(), withFileLockSync: (_filePath: string, fn: () => unknown) => 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 { TeamRuntimeAdapterRegistry } from '../../../../src/main/services/team/runtime'; import { TeamConfigReader } from '../../../../src/main/services/team/TeamConfigReader'; import { TeamProvisioningService } from '../../../../src/main/services/team/TeamProvisioningService'; import { getTeamsBasePath } from '../../../../src/main/utils/pathDecoder'; function seedConfig(teamName: string): void { hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: 'My Team', members: [{ name: 'team-lead', agentType: 'team-lead' }], }) ); } function seedLeadInbox(teamName: string, messages: unknown[]): void { hoisted.files.set(`/mock/teams/${teamName}/inboxes/team-lead.json`, JSON.stringify(messages)); } function seedMemberInbox(teamName: string, memberName: string, messages: unknown[]): void { hoisted.files.set(`/mock/teams/${teamName}/inboxes/${memberName}.json`, JSON.stringify(messages)); } function createDeferred(): { 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 }; } function buildOpenCodeProofMissingRecord(input: { teamName: string; memberName: string; laneId: string; inboxMessageId: string; taskRefs: Array<{ teamName: string; taskId: string; displayId: string }>; }): Record { return { id: `opencode-prompt:${input.inboxMessageId}`, teamName: input.teamName, memberName: input.memberName, laneId: input.laneId, runId: null, runtimeSessionId: null, inboxMessageId: input.inboxMessageId, inboxTimestamp: '2026-02-23T17:31:00.000Z', source: 'watcher', messageKind: 'default', replyRecipient: 'team-lead', actionMode: 'do', taskRefs: input.taskRefs, payloadHash: 'sha256:test', status: 'failed_terminal', responseState: 'responded_non_visible_tool', attempts: 3, maxAttempts: 3, acceptanceUnknown: false, nextAttemptAt: null, lastAttemptAt: '2026-02-23T17:31:10.000Z', lastObservedAt: '2026-02-23T17:31:15.000Z', acceptedAt: '2026-02-23T17:31:05.000Z', respondedAt: '2026-02-23T17:31:15.000Z', failedAt: '2026-02-23T17:31:20.000Z', inboxReadCommittedAt: null, inboxReadCommitError: null, prePromptCursor: null, postPromptCursor: null, deliveredUserMessageId: 'msg-user', observedAssistantMessageId: 'msg-assistant', observedAssistantPreview: null, observedToolCallNames: ['task_get', 'glob'], observedVisibleMessageId: null, visibleReplyMessageId: null, visibleReplyInbox: null, visibleReplyCorrelation: null, lastReason: 'non_visible_tool_without_task_progress', diagnostics: ['non_visible_tool_without_task_progress'], createdAt: '2026-02-23T17:31:00.000Z', updatedAt: '2026-02-23T17:31:20.000Z', }; } function seedOpenCodeBusyStatusFixture(input: { service: TeamProvisioningService; teamName: string; laneId: string; inboxMessages: unknown[]; memberName?: string; laneState?: 'active' | 'stopped'; ledgerRecords?: Record[]; activeRecord?: Record | null; }): void { const memberName = input.memberName ?? 'jack'; const teamsBasePath = getTeamsBasePath(); hoisted.files.set( `${teamsBasePath}/${input.teamName}/config.json`, JSON.stringify({ name: input.teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: memberName, role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${input.teamName}/inboxes/${memberName}.json`, JSON.stringify(input.inboxMessages) ); (input.service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: memberName, laneId: input.laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: { [input.laneId]: { laneId: input.laneId, state: input.laneState ?? 'active', updatedAt: '2026-02-23T17:30:00.000Z', }, }, }); vi.spyOn(input.service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ list: vi.fn(async () => input.ledgerRecords ?? []), getActiveForMember: vi.fn(async () => input.activeRecord ?? null), }); } async function waitForCapture(service: TeamProvisioningService): Promise { 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 ?? ''; expect(relayedPrompt).toContain( 'Do not use that internal status line to confirm, correct, or relay task, kanban, review, PR, branch, merge, or queue state unless you verified it with the source-of-truth tool in this turn.' ); expect(relayedPrompt).toContain( 'Treat teammate/system/cross-team claims about task, kanban, review, PR, branch, merge, or queue state as unverified until checked.' ); (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [{ type: 'text', text: `Human: ${relayedPrompt}\n\nDelegated to bob.` }], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(relayPromise).resolves.toBe(1); const live = service.getLiveLeadProcessMessages(teamName); expect(live.map((message) => message.text)).toEqual(['Delegated to bob.']); expect(live[0]?.to).toBeUndefined(); expect(hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`)).toBeUndefined(); }); it('suppresses unverified non-user lead relay state claims from internal activity', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; seedConfig(teamName); seedLeadInbox(teamName, [ { from: 'tom', text: '#f8d7235a done.', timestamp: '2026-02-23T10:00:00.000Z', read: false, summary: '#f8d7235a done', messageId: 'm-1', }, ]); const { writeSpy } = attachAliveRun(service, teamName); const relayPromise = service.relayLeadInboxMessages(teamName); const run = await waitForCapture(service); const payload = JSON.parse(String(writeSpy.mock.calls[0]?.[0] ?? '{}')) as { message?: { content?: Array<{ text?: string }> }; }; const relayedPrompt = payload.message?.content?.[0]?.text ?? ''; (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [ { type: 'text', text: `Human: ${relayedPrompt}\n\n` + 'Confirmed - both claims in msg 17eb3109 were false. #38730980 already approved and PR #38 is OPEN, mergeCommit=null.', }, ], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(relayPromise).resolves.toBe(1); expect(service.getLiveLeadProcessMessages(teamName)).toHaveLength(0); expect(hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`)).toBeUndefined(); }); it.each([ { caseName: 'keeps task-ref delegation status', replyText: 'Delegated #f8d7235a to bob.', expectedLiveText: 'Delegated #f8d7235a to bob.', }, { caseName: 'keeps verification-needed status', replyText: 'Verification needed before confirming #f8d7235a.', expectedLiveText: 'Verification needed before confirming #f8d7235a.', }, { caseName: 'suppresses completed task claim', replyText: 'Task #f8d7235a is completed.', expectedLiveText: null, }, { caseName: 'suppresses merged PR claim', replyText: 'PR #38 merged.', expectedLiveText: null, }, { caseName: 'suppresses queue-clear claim', replyText: 'Queue genuinely clear for #f8d7235a.', expectedLiveText: null, }, ])( 'classifies non-user lead relay internal activity: $caseName', async ({ caseName, replyText, expectedLiveText }) => { const service = new TeamProvisioningService(); const teamName = `my-team-${caseName.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}`; seedConfig(teamName); seedLeadInbox(teamName, [ { from: 'tom', text: '#f8d7235a done.', timestamp: '2026-02-23T10:00:00.000Z', read: false, summary: '#f8d7235a done', messageId: 'm-1', }, ]); const { writeSpy } = attachAliveRun(service, teamName); const relayPromise = service.relayLeadInboxMessages(teamName); const run = await waitForCapture(service); const payload = JSON.parse(String(writeSpy.mock.calls[0]?.[0] ?? '{}')) as { message?: { content?: Array<{ text?: string }> }; }; const relayedPrompt = payload.message?.content?.[0]?.text ?? ''; (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [{ type: 'text', text: `Human: ${relayedPrompt}\n\n${replyText}` }], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(relayPromise).resolves.toBe(1); const liveTexts = service.getLiveLeadProcessMessages(teamName).map((message) => message.text); expect(liveTexts).toEqual(expectedLiveText === null ? [] : [expectedLiveText]); expect(hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`)).toBeUndefined(); } ); it('keeps user-originated lead relay replies user-visible', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; seedConfig(teamName); seedLeadInbox(teamName, [ { from: 'user', text: 'Create the docs task.', timestamp: '2026-02-23T10:00:00.000Z', read: false, summary: 'Docs task', messageId: 'user-msg-1', source: 'user_sent', }, ]); attachAliveRun(service, teamName); const relayPromise = service.relayLeadInboxMessages(teamName); const run = await waitForCapture(service); (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [{ type: 'text', text: 'Creating the task now.' }], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(relayPromise).resolves.toBe(1); const live = service.getLiveLeadProcessMessages(teamName); expect(live.map((message) => message.text)).toEqual(['Creating the task now.']); expect(live[0]?.to).toBe('user'); const sentRows = JSON.parse( hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`) ?? '[]' ) as Array<{ text?: string; to?: string; }>; expect(sentRows).toMatchObject([{ text: 'Creating the task now.', to: 'user' }]); }); it('does not suppress state-like user-originated lead relay replies', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; seedConfig(teamName); seedLeadInbox(teamName, [ { from: 'user', text: 'What is the task status?', timestamp: '2026-02-23T10:00:00.000Z', read: false, summary: 'Task status', messageId: 'user-msg-state-like', source: 'user_sent', }, ]); attachAliveRun(service, teamName); const relayPromise = service.relayLeadInboxMessages(teamName); const run = await waitForCapture(service); (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [{ type: 'text', text: 'Task #f8d7235a is completed.' }], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(relayPromise).resolves.toBe(1); const live = service.getLiveLeadProcessMessages(teamName); expect(live.map((message) => ({ to: message.to, text: message.text }))).toEqual([ { to: 'user', text: 'Task #f8d7235a is completed.' }, ]); const sentRows = JSON.parse( hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`) ?? '[]' ) as Array<{ text?: string; to?: string }>; expect(sentRows).toMatchObject([{ to: 'user', text: 'Task #f8d7235a is completed.' }]); }); it('does not mix internal lead relay rows into a user-visible relay batch', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; seedConfig(teamName); seedLeadInbox(teamName, [ { from: 'bob', text: 'Internal status for the lead.', timestamp: '2026-02-23T09:59:00.000Z', read: false, summary: 'Internal status', messageId: 'internal-msg-1', }, { from: 'user', text: 'Please create the release task.', timestamp: '2026-02-23T10:00:00.000Z', read: false, summary: 'Release task', messageId: 'user-msg-2', source: 'user_sent', }, ]); const { writeSpy } = attachAliveRun(service, teamName); const relayPromise = service.relayLeadInboxMessages(teamName); const run = await waitForCapture(service); (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [{ type: 'text', text: 'Creating the release task.' }], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(relayPromise).resolves.toBe(1); const payload = String(writeSpy.mock.calls[0]?.[0] ?? ''); expect(payload).toContain('Please create the release task.'); expect(payload).not.toContain('Internal status for the lead.'); await vi.waitFor(() => expect(writeSpy.mock.calls.length).toBe(2), { timeout: 1000 }); const followUpRun = await waitForCapture(service); (service as any).handleStreamJsonMessage(followUpRun, { type: 'assistant', content: [{ type: 'text', text: 'Noted internal status.' }], }); (service as any).handleStreamJsonMessage(followUpRun, { type: 'result', subtype: 'success' }); }); it('relays deferred internal rows on the next pass after a user-visible batch', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; seedConfig(teamName); seedLeadInbox(teamName, [ { from: 'bob', text: 'Internal status for the lead.', timestamp: '2026-02-23T09:59:00.000Z', read: false, summary: 'Internal status', messageId: 'internal-msg-next-pass', source: 'system_notification', }, { from: 'user', text: 'Please create the release task.', timestamp: '2026-02-23T10:00:00.000Z', read: false, summary: 'Release task', messageId: 'user-msg-next-pass', source: 'user_sent', }, ]); const { writeSpy } = attachAliveRun(service, teamName); const firstPromise = service.relayLeadInboxMessages(teamName); let run = await waitForCapture(service); (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [{ type: 'text', text: 'Creating the release task.' }], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(firstPromise).resolves.toBe(1); await vi.waitFor(() => expect(writeSpy.mock.calls.length).toBe(2), { timeout: 1000 }); run = await waitForCapture(service); expect(run?.leadRelayCapture).toBeTruthy(); (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [{ type: 'text', text: 'Noted internal status.' }], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); for (let i = 0; i < 20 && service.getLiveLeadProcessMessages(teamName).length < 2; i++) { await Promise.resolve(); } const firstPayload = String(writeSpy.mock.calls[0]?.[0] ?? ''); const secondPayload = String(writeSpy.mock.calls[1]?.[0] ?? ''); expect(firstPayload).toContain('Please create the release task.'); expect(firstPayload).not.toContain('Internal status for the lead.'); expect(secondPayload).toContain('Internal status for the lead.'); const live = service.getLiveLeadProcessMessages(teamName); expect(live.map((message) => ({ to: message.to, text: message.text }))).toEqual([ { to: 'user', text: 'Creating the release task.' }, { to: undefined, text: 'Noted internal status.' }, ]); }); it('does not duplicate relay narration when the lead sends an explicit visible message', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; seedConfig(teamName); seedLeadInbox(teamName, [ { from: 'bob', text: 'This needs the user to know.', timestamp: '2026-02-23T10:00:00.000Z', read: false, summary: 'Notify user', messageId: 'internal-msg-2', }, ]); attachAliveRun(service, teamName); const relayPromise = service.relayLeadInboxMessages(teamName); const run = await waitForCapture(service); (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [ { type: 'text', text: 'Sending the user update now.' }, { type: 'tool_use', name: 'SendMessage', input: { recipient: 'user', content: 'Bob found an issue that needs your attention.', summary: 'Needs attention', }, }, ], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(relayPromise).resolves.toBe(1); const live = service.getLiveLeadProcessMessages(teamName); expect(live.map((message) => message.text)).toEqual([ 'Bob found an issue that needs your attention.', ]); const sentRows = JSON.parse( hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`) ?? '[]' ) as Array<{ text?: string }>; expect(sentRows.map((message) => message.text)).toEqual([ 'Bob found an issue that needs your attention.', ]); }); it('keeps explicit teammate SendMessage from non-user lead relay visible', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; seedConfig(teamName); seedLeadInbox(teamName, [ { from: 'bob', text: 'Alice should review the release notes.', timestamp: '2026-02-23T10:00:00.000Z', read: false, summary: 'Ask Alice', messageId: 'internal-msg-teammate-send', }, ]); attachAliveRun(service, teamName); const relayPromise = service.relayLeadInboxMessages(teamName); const run = await waitForCapture(service); (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [ { type: 'text', text: 'Sending Alice the handoff.' }, { type: 'tool_use', name: 'SendMessage', input: { recipient: 'alice', content: 'Please review the release notes.', summary: 'Review release notes', }, }, ], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(relayPromise).resolves.toBe(1); const live = service.getLiveLeadProcessMessages(teamName); expect(live.map((message) => ({ to: message.to, text: message.text }))).toEqual([ { to: 'alice', text: 'Please review the release notes.' }, ]); const aliceInbox = JSON.parse( hoisted.files.get(`/mock/teams/${teamName}/inboxes/alice.json`) ?? '[]' ) as Array<{ member?: string; text?: string }>; expect(aliceInbox).toMatchObject([ { member: 'alice', text: 'Please review the release notes.' }, ]); expect(hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`)).toBeUndefined(); }); it('keeps user-originated plain reply when the lead also messages a teammate', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; seedConfig(teamName); seedLeadInbox(teamName, [ { from: 'user', text: 'Please ask Alice to review the release notes.', timestamp: '2026-02-23T10:00:00.000Z', read: false, summary: 'Review release notes', messageId: 'user-msg-3', source: 'user_sent', }, ]); attachAliveRun(service, teamName); const relayPromise = service.relayLeadInboxMessages(teamName); const run = await waitForCapture(service); (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [ { type: 'text', text: 'Asked Alice to review the release notes.' }, { type: 'tool_use', name: 'SendMessage', input: { recipient: 'alice', content: 'Please review the release notes.', summary: 'Review release notes', }, }, ], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(relayPromise).resolves.toBe(1); const live = service.getLiveLeadProcessMessages(teamName); expect(live.map((message) => ({ to: message.to, text: message.text }))).toEqual([ { to: 'alice', text: 'Please review the release notes.' }, { to: 'user', text: 'Asked Alice to review the release notes.' }, ]); const sentRows = JSON.parse( hoisted.files.get(`/mock/teams/${teamName}/sentMessages.json`) ?? '[]' ) as Array<{ text?: string; to?: string }>; expect(sentRows).toMatchObject([ { to: 'user', text: 'Asked Alice to review the release notes.' }, ]); }); it('treats member work sync nudges as actionable in lead relay prompt', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; service.setControlApiBaseUrlResolver(async () => 'http://127.0.0.1:43123'); seedConfig(teamName); seedLeadInbox(teamName, [ { from: 'system', text: 'Work sync check: you have current actionable work assigned.', timestamp: '2026-02-23T10:00:00.000Z', read: false, summary: 'Work sync check', messageId: 'm-work-sync-1', source: 'system_notification', messageKind: 'member_work_sync_nudge', workSyncIntent: 'agenda_sync', taskRefs: [{ teamName, taskId: 'task-1', displayId: '11111111' }], }, ]); const { writeSpy } = attachAliveRun(service, teamName); const relayPromise = service.relayLeadInboxMessages(teamName); const run = await waitForCapture(service); expect(run?.leadRelayCapture).toBeTruthy(); const payload = String(writeSpy.mock.calls[0]?.[0] ?? ''); expect(payload).toContain('Message kind: member_work_sync_nudge'); expect(payload).toContain('Work-sync intent: agenda_sync'); expect(payload).toContain('it is actionable work-sync control traffic'); expect(payload).toContain('A member_work_sync_status call alone is incomplete'); expect(payload).toContain( 'Call member_work_sync_status with teamName=\\"my-team\\", memberName=\\"team-lead\\", controlUrl=\\"http://127.0.0.1:43123\\"' ); expect(payload).toContain('call member_work_sync_report'); expect(payload).toContain('controlUrl=\\"http://127.0.0.1:43123\\"'); expect(payload).toContain('taskIds from the nudge task refs'); expect(payload).toContain( 'Do not use provider names, runtime names, or team names as memberName' ); expect(payload).toContain('Do NOT ignore it as a pure system notification'); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(relayPromise).resolves.toBe(1); }); it('uses snapshot config reads for lead inbox relay routing', async () => { const getConfig = vi.fn(async () => { throw new Error('verified config read should not be used for inbox relay routing'); }); const getConfigSnapshot = vi.fn(async () => ({ name: 'My Team', members: [{ name: 'team-lead', agentType: 'team-lead' }], })); const service = new TeamProvisioningService({ getConfig, getConfigSnapshot, } as any); const teamName = 'my-team'; seedLeadInbox(teamName, [ { from: 'bob', text: 'Please assign this to Alice.', timestamp: '2026-02-23T10:00:00.000Z', read: false, summary: 'Need delegation', messageId: 'm-1', }, ]); const { writeSpy } = attachAliveRun(service, teamName); const relayPromise = service.relayLeadInboxMessages(teamName); const run = await waitForCapture(service); (service as any).handleStreamJsonMessage(run, { type: 'assistant', content: [{ type: 'text', text: 'OK, will do.' }], }); (service as any).handleStreamJsonMessage(run, { type: 'result', subtype: 'success' }); await expect(relayPromise).resolves.toBe(1); expect(writeSpy).toHaveBeenCalledTimes(1); expect(getConfigSnapshot).toHaveBeenCalledWith(teamName); expect(getConfig).not.toHaveBeenCalled(); }); it('shows assistant text after relay capture has already settled', () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; seedConfig(teamName); attachAliveRun(service, teamName); const run = (service as unknown as { runs: Map }).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('uses inferred task refs when relaying legacy OpenCode inbox rows without structured refs', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const taskRefs = [{ teamName, taskId: 'task-1', displayId: 'abcd1234' }]; hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/mock/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); seedMemberInbox(teamName, 'jack', [ { from: 'team-lead', to: 'jack', text: '**Comment on task #abcd1234**\n\nPlease continue.', timestamp: '2026-02-23T17:00:00.000Z', read: false, messageId: 'opencode-relay-infer-1', summary: 'Comment on #abcd1234', }, ]); const inferSpy = vi .spyOn(service as any, 'inferOpenCodeInboxMessageTaskRefs') .mockResolvedValue(taskRefs); const deliverSpy = vi .spyOn(service, 'deliverOpenCodeMemberMessage') .mockResolvedValue({ delivered: true, diagnostics: [] }); const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack'); expect(relay).toMatchObject({ relayed: 1, attempted: 1, delivered: 1, failed: 0 }); expect(inferSpy).toHaveBeenCalledWith( teamName, expect.objectContaining({ messageId: 'opencode-relay-infer-1' }), expect.any(Function) ); expect(deliverSpy).toHaveBeenCalledWith( teamName, expect.objectContaining({ messageId: 'opencode-relay-infer-1', taskRefs, }) ); }); it('keeps OpenCode member inbox rows unread while runtime response is pending', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); seedMemberInbox(teamName, 'jack', [ { from: 'bob', to: 'jack', text: 'Please answer this.', timestamp: '2026-02-23T17:00:00.000Z', read: false, messageId: 'opencode-response-pending-1', actionMode: 'ask', }, ]); vi.spyOn(service, 'deliverOpenCodeMemberMessage').mockResolvedValue({ delivered: true, accepted: true, responsePending: true, responseState: 'pending', diagnostics: ['opencode_delivery_response_pending'], }); const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack'); expect(relay).toMatchObject({ relayed: 0, attempted: 1, delivered: 0, failed: 0, lastDelivery: { delivered: true, responsePending: true }, }); const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]'); expect(rows[0].read).toBe(false); }); it('keeps accepted OpenCode prompt rows pending without warning when response proof is terminally absent', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); seedMemberInbox(teamName, 'jack', [ { from: 'bob', to: 'jack', text: 'Please sync your current task.', timestamp: '2026-02-23T17:04:00.000Z', read: false, messageId: 'opencode-accepted-terminal-empty-1', actionMode: 'do', messageKind: 'member_work_sync_nudge', }, ]); vi.spyOn(service, 'deliverOpenCodeMemberMessage').mockResolvedValue({ delivered: false, accepted: true, responsePending: false, responseState: 'empty_assistant_turn', ledgerStatus: 'failed_terminal', ledgerRecordId: 'ledger-1', laneId: 'secondary:opencode:jack', reason: 'empty_assistant_turn', diagnostics: ['empty_assistant_turn'], }); const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack'); expect(relay).toMatchObject({ relayed: 0, attempted: 1, delivered: 0, failed: 0, lastDelivery: { delivered: false, accepted: true, responsePending: false, ledgerStatus: 'failed_terminal', reason: 'empty_assistant_turn', }, }); expect(vi.mocked(console.warn)).not.toHaveBeenCalledWith( expect.stringContaining('OpenCode inbox relay failed') ); const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]'); expect(rows[0].read).toBe(false); }); it('does not treat empty OpenCode observations as accepted without delivered prompt proof', () => { const service = new TeamProvisioningService(); const isAccepted = ( service as unknown as { isOpenCodePromptAcceptedByObservation: (observation?: unknown) => boolean; } ).isOpenCodePromptAcceptedByObservation.bind(service); expect( isAccepted({ state: 'empty_assistant_turn', deliveredUserMessageId: null, }) ).toBe(false); expect( isAccepted({ state: 'prompt_delivered_no_assistant_message', deliveredUserMessageId: '', }) ).toBe(false); expect( isAccepted({ state: 'empty_assistant_turn', deliveredUserMessageId: 'opencode-user-message-1', }) ).toBe(true); }); it('reuses existing OpenCode prompt ledger metadata during watchdog relay retries', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const taskRefs = [{ teamName, taskId: 'task-1', displayId: 'abcd1234' }]; hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); seedMemberInbox(teamName, 'jack', [ { from: 'bob', to: 'jack', text: 'Please answer the app user.', timestamp: '2026-02-23T17:00:00.000Z', read: false, messageId: 'opencode-ledger-metadata-1', actionMode: 'ask', }, ]); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ getByInboxMessage: vi.fn(async () => ({ id: 'record-1', status: 'retry_scheduled', replyRecipient: 'user', actionMode: 'delegate', taskRefs, source: 'manual', })), }); const deliverSpy = vi.spyOn(service, 'deliverOpenCodeMemberMessage').mockResolvedValue({ delivered: true, accepted: true, responsePending: true, responseState: 'pending', diagnostics: ['opencode_delivery_response_pending'], }); const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack', { onlyMessageId: 'opencode-ledger-metadata-1', source: 'watchdog', }); expect(relay).toMatchObject({ attempted: 1, delivered: 0, failed: 0, lastDelivery: { delivered: true, responsePending: true }, }); expect(deliverSpy).toHaveBeenCalledWith( teamName, expect.objectContaining({ messageId: 'opencode-ledger-metadata-1', replyRecipient: 'user', actionMode: 'delegate', taskRefs, source: 'manual', }) ); }); it('records and schedules a retry when the OpenCode bridge throws during prompt delivery', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); const sendMessageToMember = vi.fn(async () => { throw new Error('bridge crashed'); }); service.setRuntimeAdapterRegistry( new TeamRuntimeAdapterRegistry([ { providerId: 'opencode', prepare: vi.fn(), launch: vi.fn(), reconcile: vi.fn(), stop: vi.fn(), sendMessageToMember, } as any, ]) ); vi.spyOn(service as any, 'getCurrentOpenCodeRuntimeRunId').mockReturnValue('opencode-run-1'); vi.spyOn( service as any, 'findDeliverableOpenCodeRuntimeBootstrapSessionEvidence' ).mockResolvedValue({ id: 'session-jack', teamName, memberName: 'jack', laneId, runId: 'opencode-run-1', source: 'runtime_bootstrap_checkin', }); vi.spyOn(service as any, 'applyOpenCodeVisibleDestinationProof').mockImplementation( async (input: any) => ({ ledgerRecord: input.ledgerRecord, visibleReply: null, }) ); vi.spyOn(service as any, 'materializeOpenCodePlainTextReplyIfNeeded').mockImplementation( async (input: any) => ({ ledgerRecord: input.ledgerRecord, visibleReply: null, }) ); const watchdogSpy = vi .spyOn(service as any, 'scheduleOpenCodePromptDeliveryWatchdog') .mockImplementation(() => undefined); const records: any[] = []; const ledger = { getActiveForMember: vi.fn(async () => null), ensurePending: vi.fn(async (input: Record) => { const record = { id: 'ledger-send-exception-1', teamName, memberName: 'jack', laneId, runId: 'opencode-run-1', runtimeSessionId: null, runtimePromptMessageId: null, runtimePromptMessageIds: [], lastRuntimePromptMessageId: null, lastDeliveryAttemptIdWithAcceptedPrompt: null, inboxMessageId: 'opencode-send-exception-1', inboxTimestamp: '2026-02-23T17:00:00.000Z', source: 'watcher', messageKind: 'default', workSyncIntent: null, replyRecipient: 'team-lead', actionMode: 'do', taskRefs: [], payloadHash: 'sha256:test', status: 'pending', responseState: 'not_observed', attempts: 0, maxAttempts: 3, acceptanceUnknown: false, nextAttemptAt: null, lastAttemptAt: null, lastObservedAt: null, acceptedAt: null, respondedAt: null, failedAt: null, inboxReadCommittedAt: null, inboxReadCommitError: null, prePromptCursor: null, postPromptCursor: null, deliveredUserMessageId: null, observedAssistantMessageId: null, observedAssistantPreview: null, observedToolCallNames: [], observedVisibleMessageId: null, visibleReplyMessageId: null, visibleReplyInbox: null, visibleReplyCorrelation: null, lastReason: null, diagnostics: [] as string[], createdAt: '2026-02-23T17:00:00.000Z', updatedAt: '2026-02-23T17:00:00.000Z', ...input, }; records.push(record); return record; }), applyDeliveryResult: vi.fn(async (input: Record) => { const record = records[0]; Object.assign(record, { status: 'failed_retryable', responseState: 'reconcile_failed', attempts: 1, lastAttemptAt: input.now, lastReason: input.reason, diagnostics: input.diagnostics, updatedAt: input.now, }); return record; }), markNextAttemptScheduled: vi.fn(async (input: Record) => { const record = records[0]; Object.assign(record, { status: input.status, nextAttemptAt: input.nextAttemptAt, lastReason: input.reason, updatedAt: input.scheduledAt, }); return record; }), markFailedTerminal: vi.fn(), }; vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue(ledger); const delivery = await service.deliverOpenCodeMemberMessage(teamName, { memberName: 'jack', text: 'Please continue task.', messageId: 'opencode-send-exception-1', source: 'watcher', replyRecipient: 'team-lead', actionMode: 'do', inboxTimestamp: '2026-02-23T17:00:00.000Z', }); expect(sendMessageToMember).toHaveBeenCalledTimes(1); expect(ledger.applyDeliveryResult).toHaveBeenCalledWith( expect.objectContaining({ accepted: false, attempted: true, reason: expect.stringContaining('bridge crashed'), }) ); expect(ledger.markNextAttemptScheduled).toHaveBeenCalledWith( expect.objectContaining({ status: 'retry_scheduled', reason: expect.stringContaining('bridge crashed'), }) ); expect(watchdogSpy).toHaveBeenCalledWith( expect.objectContaining({ teamName, memberName: 'jack', messageId: 'opencode-send-exception-1', }) ); expect(delivery).toMatchObject({ delivered: false, accepted: false, responsePending: true, ledgerStatus: 'retry_scheduled', ledgerRecordId: 'ledger-send-exception-1', reason: expect.stringContaining('bridge crashed'), }); }); it('does not postpone an earlier OpenCode prompt watchdog wake when rescheduled later', async () => { vi.useFakeTimers(); try { const service = new TeamProvisioningService(); const relaySpy = vi .spyOn(service, 'relayOpenCodeMemberInboxMessages') .mockResolvedValue({ attempted: 1, delivered: 0, failed: 0 } as any); vi.spyOn(service as any, 'canDeliverToOpenCodeRuntimeForTeam').mockReturnValue(true); (service as any).scheduleOpenCodePromptDeliveryWatchdog({ teamName: 'my-team', memberName: 'jack', messageId: 'message-1', delayMs: 500, }); (service as any).scheduleOpenCodePromptDeliveryWatchdog({ teamName: 'my-team', memberName: 'jack', messageId: 'message-1', delayMs: 60_000, }); await vi.advanceTimersByTimeAsync(501); expect(relaySpy).toHaveBeenCalledTimes(1); expect(relaySpy).toHaveBeenCalledWith('my-team', 'jack', { onlyMessageId: 'message-1', source: 'watchdog', }); } finally { vi.useRealTimers(); } }); it('ignores stale OpenCode watchdog jobs after the runtime lane is no longer active', async () => { vi.useFakeTimers(); try { const service = new TeamProvisioningService(); const teamName = 'my-team'; hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); seedMemberInbox(teamName, 'jack', [ { from: 'bob', to: 'jack', text: 'Please sync.', timestamp: '2026-02-23T17:00:00.000Z', read: false, messageId: 'opencode-stale-watchdog-1', }, ]); const deliverSpy = vi .spyOn(service, 'deliverOpenCodeMemberMessage') .mockRejectedValue( new Error('OpenCode prompt delivery record not found: opencode-prompt:stale') ); (service as any).scheduleOpenCodePromptDeliveryWatchdog({ teamName, memberName: 'jack', messageId: 'opencode-stale-watchdog-1', delayMs: 500, }); await vi.advanceTimersByTimeAsync(500); await Promise.resolve(); expect(deliverSpy).not.toHaveBeenCalled(); expect(vi.mocked(console.warn)).not.toHaveBeenCalled(); } finally { vi.useRealTimers(); } }); it('does not classify missing OpenCode watchdog ledger rows as stale while the lane is active', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; attachAliveRun(service, teamName); hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); seedMemberInbox(teamName, 'jack', [ { from: 'bob', to: 'jack', text: 'Please sync.', timestamp: '2026-02-23T17:00:00.000Z', read: false, messageId: 'opencode-active-watchdog-1', }, ]); vi.spyOn(service as any, 'isOpenCodeRuntimeLaneIndexActive').mockResolvedValue(true); await expect( (service as any).isStaleOpenCodePromptDeliveryWatchdogError({ teamName, memberName: 'jack', messageId: 'opencode-active-watchdog-1', error: new Error('OpenCode prompt delivery record not found: opencode-prompt:active'), }) ).resolves.toBe(false); }); it('skips failed-terminal OpenCode rows without blocking newer unread rows', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); const identity = await (service as any).resolveOpenCodeMemberDeliveryIdentity(teamName, 'jack'); expect(identity.ok).toBe(true); const failedRecord = { id: 'ledger-terminal-old', status: 'failed_terminal', inboxMessageId: 'opencode-terminal-old', lastReason: 'opencode_attachments_not_supported_for_secondary_runtime', diagnostics: ['opencode_attachments_not_supported_for_secondary_runtime'], }; vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ getByInboxMessage: vi.fn(async (input: { inboxMessageId: string }) => input.inboxMessageId === 'opencode-terminal-old' ? failedRecord : null ), }); seedMemberInbox(teamName, 'jack', [ { from: 'bob', to: 'jack', text: 'Old terminal row.', timestamp: '2026-02-23T17:00:00.000Z', read: false, messageId: 'opencode-terminal-old', }, { from: 'bob', to: 'jack', text: 'New deliverable row.', timestamp: '2026-02-23T17:00:02.000Z', read: false, messageId: 'opencode-terminal-new', }, ]); const deliverSpy = vi .spyOn(service, 'deliverOpenCodeMemberMessage') .mockResolvedValue({ delivered: true, diagnostics: [] }); const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack'); expect(relay).toMatchObject({ relayed: 1, attempted: 1, delivered: 1, failed: 0 }); expect(relay.diagnostics?.join('\n')).toContain( 'opencode_attachments_not_supported_for_secondary_runtime' ); expect(deliverSpy).toHaveBeenCalledTimes(1); expect(deliverSpy).toHaveBeenCalledWith( teamName, expect.objectContaining({ messageId: 'opencode-terminal-new' }) ); const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]'); expect(rows.map((row: { read?: boolean }) => row.read)).toEqual([false, true]); }); it('emits advisory refresh when a failed-terminal OpenCode row is recovered by visible reply proof', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const taskRefs = [{ teamName, taskId: 'task-recovered', displayId: 'task-rec' }]; const ledgerRecord = { id: 'ledger-terminal-recovered', teamName, memberName: 'jack', laneId: 'secondary:opencode:jack', runId: 'run-1', runtimeSessionId: 'ses-1', inboxMessageId: 'opencode-terminal-recovered', inboxTimestamp: '2026-02-23T17:00:00.000Z', source: 'watcher', messageKind: null, replyRecipient: 'team-lead', actionMode: null, taskRefs, payloadHash: 'sha256:test', status: 'failed_terminal', responseState: 'session_stale', attempts: 1, maxAttempts: 3, acceptanceUnknown: false, nextAttemptAt: null, lastAttemptAt: '2026-02-23T17:00:03.000Z', lastObservedAt: '2026-02-23T17:00:05.000Z', acceptedAt: '2026-02-23T17:00:03.000Z', respondedAt: null, failedAt: '2026-02-23T17:00:08.000Z', inboxReadCommittedAt: null, inboxReadCommitError: null, prePromptCursor: null, postPromptCursor: null, deliveredUserMessageId: 'runtime-user-1', observedAssistantMessageId: null, observedAssistantPreview: null, observedToolCallNames: [], observedVisibleMessageId: null, visibleReplyMessageId: null, visibleReplyInbox: null, visibleReplyCorrelation: null, lastReason: 'opencode_session_stale_observe_loop_after_accepted_prompt', diagnostics: ['opencode_session_stale_observe_loop_after_accepted_prompt'], createdAt: '2026-02-23T17:00:00.000Z', updatedAt: '2026-02-23T17:00:08.000Z', }; const visibleReply = { inboxName: 'team-lead', message: { from: 'jack', to: 'team-lead', text: 'Recovered visible reply with task results.', summary: '#task-rec done', timestamp: '2026-02-23T17:01:00.000Z', read: true, source: 'runtime_delivery', relayOfMessageId: 'opencode-terminal-recovered', messageId: 'visible-reply-recovered', taskRefs, }, }; vi.spyOn(service as any, 'findOpenCodeVisibleReplyByRelayOfMessageId').mockResolvedValue( visibleReply ); const applyDestinationProof = vi.fn(async (input: Record) => ({ ...ledgerRecord, status: 'responded', responseState: 'responded_visible_message', failedAt: null, lastReason: null, visibleReplyInbox: input.visibleReplyInbox, visibleReplyMessageId: input.visibleReplyMessageId, visibleReplyCorrelation: input.visibleReplyCorrelation, inboxReadCommittedAt: '2026-02-23T17:01:01.000Z', respondedAt: input.observedAt, updatedAt: input.observedAt, })); const advisoryInvalidator = vi.fn(); const teamChangeEmitter = vi.fn(); service.setMemberRuntimeAdvisoryInvalidator(advisoryInvalidator); service.setTeamChangeEmitter(teamChangeEmitter); const result = await (service as any).applyOpenCodeVisibleDestinationProof({ ledger: { applyDestinationProof }, ledgerRecord, teamName, replyRecipient: 'team-lead', memberName: 'jack', }); expect(result.visibleReply).toBe(visibleReply); expect(result.ledgerRecord.status).toBe('responded'); expect(applyDestinationProof).toHaveBeenCalledWith( expect.objectContaining({ id: 'ledger-terminal-recovered', visibleReplyInbox: 'team-lead', visibleReplyMessageId: 'visible-reply-recovered', visibleReplyCorrelation: 'relayOfMessageId', semanticallySufficient: true, }) ); expect(advisoryInvalidator).toHaveBeenCalledWith(teamName, 'jack'); expect(teamChangeEmitter).toHaveBeenCalledWith( expect.objectContaining({ type: 'member-advisory', teamName, detail: 'runtime-delivery-reply:jack:opencode-terminal-recovered', }) ); }); it('does not emit advisory refresh again for already proven OpenCode visible replies', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const ledgerRecord = { id: 'ledger-already-proven', teamName, memberName: 'jack', laneId: 'secondary:opencode:jack', runId: 'run-1', runtimeSessionId: 'ses-1', inboxMessageId: 'opencode-already-proven', inboxTimestamp: '2026-02-23T17:00:00.000Z', source: 'watcher', messageKind: null, replyRecipient: 'team-lead', actionMode: null, taskRefs: [], payloadHash: 'sha256:test', status: 'responded', responseState: 'responded_visible_message', attempts: 1, maxAttempts: 3, acceptanceUnknown: false, nextAttemptAt: null, lastAttemptAt: '2026-02-23T17:00:03.000Z', lastObservedAt: '2026-02-23T17:01:00.000Z', acceptedAt: '2026-02-23T17:00:03.000Z', respondedAt: '2026-02-23T17:01:00.000Z', failedAt: null, inboxReadCommittedAt: '2026-02-23T17:01:01.000Z', inboxReadCommitError: null, prePromptCursor: null, postPromptCursor: null, deliveredUserMessageId: 'runtime-user-1', observedAssistantMessageId: null, observedAssistantPreview: null, observedToolCallNames: [], observedVisibleMessageId: null, visibleReplyMessageId: 'visible-reply-proven', visibleReplyInbox: 'team-lead', visibleReplyCorrelation: 'relayOfMessageId', lastReason: null, diagnostics: [], createdAt: '2026-02-23T17:00:00.000Z', updatedAt: '2026-02-23T17:01:01.000Z', }; const visibleReply = { inboxName: 'team-lead', message: { from: 'jack', to: 'team-lead', text: 'Already proven visible reply.', summary: '#done', timestamp: '2026-02-23T17:01:00.000Z', read: true, source: 'runtime_delivery', relayOfMessageId: 'opencode-already-proven', messageId: 'visible-reply-proven', }, }; vi.spyOn(service as any, 'findOpenCodeVisibleReplyByRelayOfMessageId').mockResolvedValue( visibleReply ); const applyDestinationProof = vi.fn(async () => ledgerRecord); const advisoryInvalidator = vi.fn(); const teamChangeEmitter = vi.fn(); service.setMemberRuntimeAdvisoryInvalidator(advisoryInvalidator); service.setTeamChangeEmitter(teamChangeEmitter); const result = await (service as any).applyOpenCodeVisibleDestinationProof({ ledger: { applyDestinationProof }, ledgerRecord, teamName, replyRecipient: 'team-lead', memberName: 'jack', }); expect(result.visibleReply).toBe(visibleReply); expect(applyDestinationProof).toHaveBeenCalledWith( expect.objectContaining({ id: 'ledger-already-proven', visibleReplyMessageId: 'visible-reply-proven', semanticallySufficient: true, }) ); expect(advisoryInvalidator).not.toHaveBeenCalled(); expect(teamChangeEmitter).not.toHaveBeenCalled(); }); it('retries failed-terminal OpenCode rows caused by stale runtime manifest watermark', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); const identity = await (service as any).resolveOpenCodeMemberDeliveryIdentity(teamName, 'jack'); expect(identity.ok).toBe(true); const staleRecord = { id: 'ledger-terminal-stale-manifest', teamName, memberName: 'jack', laneId: 'secondary:opencode:jack', runId: 'run-1', status: 'failed_terminal', responseState: 'reconcile_failed', attempts: 3, maxAttempts: 3, inboxMessageId: 'opencode-terminal-stale-manifest', replyRecipient: 'team-lead', actionMode: null, taskRefs: [], source: 'watcher', lastReason: 'opencode_message_delivery_exception: Bridge server runtime manifest high watermark is stale', diagnostics: [ 'opencode_message_delivery_exception: Bridge server runtime manifest high watermark is stale', ], }; const markNextAttemptScheduled = vi.fn(async (input: Record) => ({ ...staleRecord, status: input.status, nextAttemptAt: input.nextAttemptAt, lastReason: input.reason, })); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ getByInboxMessage: vi.fn(async (input: { inboxMessageId: string }) => input.inboxMessageId === 'opencode-terminal-stale-manifest' ? staleRecord : null ), markNextAttemptScheduled, }); seedMemberInbox(teamName, 'jack', [ { from: 'bob', to: 'jack', text: 'Old stale manifest row.', timestamp: '2026-02-23T17:00:00.000Z', read: false, messageId: 'opencode-terminal-stale-manifest', }, { from: 'bob', to: 'jack', text: 'New row should wait behind the retried old row.', timestamp: '2026-02-23T17:00:02.000Z', read: false, messageId: 'opencode-terminal-new', }, ]); const deliverSpy = vi .spyOn(service, 'deliverOpenCodeMemberMessage') .mockResolvedValue({ delivered: true, diagnostics: [] }); const relay = await service.relayOpenCodeMemberInboxMessages(teamName, 'jack'); expect(markNextAttemptScheduled).toHaveBeenCalledWith( expect.objectContaining({ id: 'ledger-terminal-stale-manifest', status: 'retry_scheduled', reason: 'opencode_prompt_delivery_requeued_after_runtime_manifest_high_watermark_fix', }) ); expect(relay).toMatchObject({ relayed: 1, attempted: 1, delivered: 1, failed: 0 }); expect(deliverSpy).toHaveBeenCalledTimes(1); expect(deliverSpy).toHaveBeenCalledWith( teamName, expect.objectContaining({ messageId: 'opencode-terminal-stale-manifest' }) ); const rows = JSON.parse(hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]'); expect(rows.map((row: { read?: boolean }) => row.read)).toEqual([true, false]); }); it('fails OpenCode secondary rows with missing attachment payloads terminally without text-only delivery', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); const identity = await (service as any).resolveOpenCodeMemberDeliveryIdentity(teamName, 'jack'); expect(identity.ok).toBe(true); const records: any[] = []; vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ getByInboxMessage: vi.fn(async () => null), ensurePending: vi.fn(async (input: Record) => { 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('schedules existing pending OpenCode prompt ledger rows with no next attempt on startup scan', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); const identity = await (service as any).resolveOpenCodeMemberDeliveryIdentity(teamName, 'jack'); expect(identity.ok).toBe(true); const laneId = identity.laneId; const scheduleSpy = vi .spyOn(service as any, 'scheduleOpenCodePromptDeliveryWatchdog') .mockImplementation(() => undefined); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ pruneTerminalRecords: vi.fn(async () => ({ pruned: 0, remaining: 1 })), list: vi.fn(async () => [ { id: 'ledger-existing-pending-1', teamName, memberName: 'jack', laneId, inboxMessageId: 'opencode-existing-pending-1', status: 'pending', responseState: 'not_observed', attempts: 0, maxAttempts: 3, nextAttemptAt: null, diagnostics: [], createdAt: '2026-02-23T17:00:00.000Z', }, ]), getByInboxMessage: vi.fn(async () => null), }); const scheduled = await (service as any).scanOpenCodePromptDeliveryWatchdogForActiveLanes( teamName, [laneId] ); expect(scheduled).toBe(1); expect(scheduleSpy).toHaveBeenCalledWith( expect.objectContaining({ teamName, memberName: 'jack', messageId: 'opencode-existing-pending-1', delayMs: expect.any(Number), }) ); }); it('queues a specific OpenCode relay behind an active member relay without duplicate prompts', async () => { vi.useFakeTimers(); const service = new TeamProvisioningService(); const teamName = 'my-team'; try { hoisted.files.set( `/mock/teams/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); seedMemberInbox(teamName, 'jack', [ { from: 'bob', to: 'jack', text: 'Older watcher message.', timestamp: '2026-02-23T17:00:00.000Z', read: false, messageId: 'opencode-inflight-old', }, ]); const oldDeliveryStarted = createDeferred(); 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', }, ]); await expect( service.relayOpenCodeMemberInboxMessages(teamName, 'jack', { onlyMessageId: 'opencode-inflight-new', source: 'ui-send', deliveryMetadata: { replyRecipient: 'user' }, }) ).resolves.toMatchObject({ attempted: 1, delivered: 0, failed: 0, lastDelivery: { delivered: true, accepted: false, responsePending: true, queuedBehindMessageId: 'opencode-inflight-new', reason: 'opencode_inbox_relay_queued_behind_active_relay', }, }); releaseOldDelivery.resolve(undefined); await expect(watcherRelay).resolves.toMatchObject({ attempted: 1, delivered: 1, }); expect(deliverSpy).toHaveBeenCalledTimes(1); expect(deliverSpy).toHaveBeenCalledWith( teamName, expect.objectContaining({ messageId: 'opencode-inflight-old' }) ); const rows = JSON.parse( hoisted.files.get(`/mock/teams/${teamName}/inboxes/jack.json`) ?? '[]' ); expect(rows.map((row: { read?: boolean }) => row.read)).toEqual([true, false]); } finally { vi.useRealTimers(); } }); it('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(); const wakeSpy = vi .spyOn(service, 'scheduleOpenCodeMemberInboxDeliveryWake') .mockImplementation(() => undefined); hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/jack.json`, JSON.stringify([ { from: 'user', to: 'jack', text: 'Please check the current issue.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'foreground-message-1', messageKind: 'direct', }, ]) ); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:31:10.000Z', }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'foreground-message-1', }); expect(wakeSpy).toHaveBeenCalledWith({ teamName, memberName: 'jack', messageId: 'foreground-message-1', delayMs: 500, }); }); it('wakes an active OpenCode foreground delivery instead of blocking work-sync on unread inbox state', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const wakeSpy = vi .spyOn(service, 'scheduleOpenCodeMemberInboxDeliveryWake') .mockImplementation(() => undefined); seedOpenCodeBusyStatusFixture({ service, teamName, laneId, inboxMessages: [ { from: 'team-lead', to: 'jack', text: 'New task assigned to you.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'foreground-message-1', messageKind: 'default', }, ], activeRecord: { inboxMessageId: 'foreground-message-1', messageKind: 'default', nextAttemptAt: '2026-02-23T17:33:00.000Z', }, }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:31:10.000Z', workSyncIntent: 'agenda_sync', }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_prompt_delivery_active:default', activeMessageId: 'foreground-message-1', retryAfterIso: '2026-02-23T17:33:00.000Z', }); expect(wakeSpy).toHaveBeenCalledWith( expect.objectContaining({ teamName, memberName: 'jack', messageId: 'foreground-message-1', }) ); }); it('prioritizes an active OpenCode prompt ledger over newer unread foreground messages', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const wakeSpy = vi .spyOn(service, 'scheduleOpenCodeMemberInboxDeliveryWake') .mockImplementation(() => undefined); seedOpenCodeBusyStatusFixture({ service, teamName, laneId, inboxMessages: [ { from: 'team-lead', to: 'jack', text: 'Follow-up comment after assignment.', timestamp: '2026-02-23T17:32:00.000Z', read: false, messageId: 'foreground-comment-1', messageKind: 'default', }, ], activeRecord: { inboxMessageId: 'foreground-assignment-1', messageKind: 'default', nextAttemptAt: '2026-02-23T17:33:00.000Z', }, }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:32:10.000Z', workSyncIntent: 'agenda_sync', }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_prompt_delivery_active:default', activeMessageId: 'foreground-assignment-1', retryAfterIso: '2026-02-23T17:33:00.000Z', }); expect(wakeSpy).toHaveBeenCalledWith( expect.objectContaining({ teamName, memberName: 'jack', messageId: 'foreground-assignment-1', }) ); }); it('recovers a missing OpenCode lane before using an active prompt ledger as busy state', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const memberName = 'jack'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: memberName, role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/${memberName}.json`, JSON.stringify([ { from: 'team-lead', to: memberName, text: 'New task assigned to you.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'foreground-message-1', messageKind: 'default', }, ]) ); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: memberName, laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex') .mockResolvedValueOnce({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: {}, }) .mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:01.000Z', lanes: { [laneId]: { laneId, state: 'active', updatedAt: '2026-02-23T17:30:01.000Z', }, }, }); const recoverySpy = vi .spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery') .mockResolvedValue(true); const wakeSpy = vi .spyOn(service, 'scheduleOpenCodeMemberInboxDeliveryWake') .mockImplementation(() => undefined); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ getActiveForMember: vi.fn(async () => ({ id: 'ledger-foreground-message-1', teamName, memberName, laneId, inboxMessageId: 'foreground-message-1', messageKind: 'default', status: 'pending', responseState: 'not_observed', nextAttemptAt: '2026-02-23T17:33:00.000Z', })), }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName, nowIso: '2026-02-23T17:31:10.000Z', workSyncIntent: 'agenda_sync', }); expect(recoverySpy).toHaveBeenCalledWith({ teamName, memberName }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_prompt_delivery_active:default', activeMessageId: 'foreground-message-1', }); expect(wakeSpy).toHaveBeenCalledWith( expect.objectContaining({ teamName, memberName, messageId: 'foreground-message-1' }) ); }); it('does not use an active OpenCode prompt ledger when recovery leaves the lane inactive', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const memberName = 'jack'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: memberName, role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/${memberName}.json`, JSON.stringify([ { from: 'team-lead', to: memberName, text: 'New task assigned to you.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'foreground-message-1', messageKind: 'default', }, ]) ); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: memberName, laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: {}, }); const recoverySpy = vi .spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery') .mockResolvedValue(true); const wakeSpy = vi .spyOn(service, 'scheduleOpenCodeMemberInboxDeliveryWake') .mockImplementation(() => undefined); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ getActiveForMember: vi.fn(async () => ({ id: 'ledger-foreground-message-1', teamName, memberName, laneId, inboxMessageId: 'foreground-message-1', messageKind: 'default', status: 'pending', responseState: 'not_observed', nextAttemptAt: '2026-02-23T17:33:00.000Z', })), }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName, nowIso: '2026-02-23T17:31:10.000Z', workSyncIntent: 'agenda_sync', }); expect(recoverySpy).toHaveBeenCalledWith({ teamName, memberName }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'foreground-message-1', }); expect(wakeSpy).toHaveBeenCalledWith({ teamName, memberName, messageId: 'foreground-message-1', delayMs: 500, }); }); it('recovers a missing OpenCode lane before treating work-sync delivery as unavailable', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const memberName = 'jack'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: memberName, role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set(`${teamsBasePath}/${teamName}/inboxes/${memberName}.json`, JSON.stringify([])); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: memberName, laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex') .mockResolvedValueOnce({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: {}, }) .mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:01.000Z', lanes: { [laneId]: { laneId, state: 'active', updatedAt: '2026-02-23T17:30:01.000Z', }, }, }); const recoverySpy = vi .spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery') .mockResolvedValue(true); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ getActiveForMember: vi.fn(async () => null), }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName, nowIso: '2026-02-23T17:31:10.000Z', workSyncIntent: 'agenda_sync', }); expect(recoverySpy).toHaveBeenCalledWith({ teamName, memberName }); expect(busy).toEqual({ busy: false }); }); it('keeps OpenCode work-sync busy when recovery reports success but lane index is still inactive', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const memberName = 'jack'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: memberName, role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set(`${teamsBasePath}/${teamName}/inboxes/${memberName}.json`, JSON.stringify([])); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: memberName, laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: {}, }); const recoverySpy = vi .spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery') .mockResolvedValue(true); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName, nowIso: '2026-02-23T17:31:10.000Z', workSyncIntent: 'agenda_sync', }); expect(recoverySpy).toHaveBeenCalledWith({ teamName, memberName }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_no_active_lane', }); }); it('does not let proof-missing recovery get blocked by its original unread message', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/jack.json`, JSON.stringify([ { from: 'user', to: 'jack', text: 'Please check the current issue.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'foreground-message-1', messageKind: 'direct', }, ]) ); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: 'jack', laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: { [laneId]: { laneId, state: 'active', updatedAt: '2026-02-23T17:30:00.000Z', }, }, }); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ getActiveForMember: vi.fn(async () => null), }); const sameMessageRecoveryBusy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:31:10.000Z', workSyncIntent: 'agenda_sync', workSyncIntentKey: 'proof-missing:foreground-message-1', }); expect(sameMessageRecoveryBusy).toEqual({ busy: false }); const unrelatedRecoveryBusy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:31:10.000Z', workSyncIntent: 'agenda_sync', workSyncIntentKey: 'proof-missing:another-message', }); expect(unrelatedRecoveryBusy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'foreground-message-1', }); }); it('allows OpenCode agenda-sync recovery past the exact proof-missing foreground message', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/jack.json`, JSON.stringify([ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'proof-missing-message-1', messageKind: 'default', taskRefs: [taskRef], }, ]) ); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: 'jack', laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: { [laneId]: { laneId, state: 'active', updatedAt: '2026-02-23T17:30:00.000Z', }, }, }); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ list: vi.fn(async () => [ buildOpenCodeProofMissingRecord({ teamName, memberName: 'jack', laneId, inboxMessageId: 'proof-missing-message-1', taskRefs: [taskRef], }), ]), getActiveForMember: vi.fn(async () => null), }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:32:00.000Z', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], }); expect(busy).toEqual({ busy: false }); }); it('allows OpenCode agenda-sync proof-missing bypass after recovering a missing lane index', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/jack.json`, JSON.stringify([ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'proof-missing-message-1', messageKind: 'default', taskRefs: [taskRef], }, ]) ); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: 'jack', laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex') .mockResolvedValueOnce({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: {}, }) .mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:01.000Z', lanes: { [laneId]: { laneId, state: 'active', updatedAt: '2026-02-23T17:30:01.000Z', }, }, }); const recoverySpy = vi .spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery') .mockResolvedValue(true); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ list: vi.fn(async () => [ buildOpenCodeProofMissingRecord({ teamName, memberName: 'jack', laneId, inboxMessageId: 'proof-missing-message-1', taskRefs: [taskRef], }), ]), getActiveForMember: vi.fn(async () => null), }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:32:00.000Z', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], }); expect(recoverySpy).toHaveBeenCalledWith({ teamName, memberName: 'jack' }); expect(busy).toEqual({ busy: false }); }); it('keeps OpenCode agenda-sync proof-missing bypass disabled when lane index is unreadable', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: 'jack', laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockRejectedValue( new Error('temporary read failure') ); const recoverySpy = vi .spyOn(service as any, 'tryRecoverOpenCodeRuntimeLaneForConfiguredMemberBeforeDelivery') .mockResolvedValue(true); const bypass = await (service as any).getOpenCodeAgendaSyncRecoveryBypassMessageIds({ teamName, memberName: 'jack', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], foregroundMessages: [ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'proof-missing-message-1', messageKind: 'default', taskRefs: [taskRef], }, ], }); expect(recoverySpy).not.toHaveBeenCalled(); expect(bypass).toEqual(new Set()); }); it('allows OpenCode agenda-sync recovery for legacy proof-missing foreground ids', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; const legacyMessage = { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageKind: 'default', taskRefs: [taskRef], }; const legacyMessageId = buildLegacyInboxMessageId( legacyMessage.from, legacyMessage.timestamp, legacyMessage.text ); seedOpenCodeBusyStatusFixture({ service, teamName, laneId, inboxMessages: [legacyMessage], ledgerRecords: [ buildOpenCodeProofMissingRecord({ teamName, memberName: 'jack', laneId, inboxMessageId: legacyMessageId, taskRefs: [taskRef], }), ], }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:32:00.000Z', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], }); expect(busy).toEqual({ busy: false }); }); it('keeps newer same-task OpenCode foreground messages busy during agenda-sync recovery', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; seedOpenCodeBusyStatusFixture({ service, teamName, laneId, inboxMessages: [ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'proof-missing-message-1', messageKind: 'default', taskRefs: [taskRef], }, { from: 'team-lead', to: 'jack', text: 'Dependency resolved. Please re-check #task1234.', timestamp: '2026-02-23T17:31:40.000Z', read: false, messageId: 'same-task-follow-up-1', messageKind: 'default', taskRefs: [taskRef], }, ], ledgerRecords: [ buildOpenCodeProofMissingRecord({ teamName, memberName: 'jack', laneId, inboxMessageId: 'proof-missing-message-1', taskRefs: [taskRef], }), ], }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:32:00.000Z', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'same-task-follow-up-1', }); }); it('keeps OpenCode agenda-sync busy when proof-missing recovery evidence is absent', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/jack.json`, JSON.stringify([ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'foreground-message-1', messageKind: 'default', taskRefs: [taskRef], }, ]) ); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: 'jack', laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: { [laneId]: { laneId, state: 'active', updatedAt: '2026-02-23T17:30:00.000Z', }, }, }); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ list: vi.fn(async () => []), getActiveForMember: vi.fn(async () => null), }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:31:10.000Z', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'foreground-message-1', }); }); it('keeps unrelated unread OpenCode foreground messages busy during agenda-sync recovery', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/jack.json`, JSON.stringify([ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'proof-missing-message-1', messageKind: 'default', taskRefs: [taskRef], }, { from: 'user', to: 'jack', text: 'Unrelated direct instruction.', timestamp: '2026-02-23T17:31:20.000Z', read: false, messageId: 'unrelated-message-1', messageKind: 'direct', }, ]) ); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: 'jack', laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: { [laneId]: { laneId, state: 'active', updatedAt: '2026-02-23T17:30:00.000Z', }, }, }); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ list: vi.fn(async () => [ buildOpenCodeProofMissingRecord({ teamName, memberName: 'jack', laneId, inboxMessageId: 'proof-missing-message-1', taskRefs: [taskRef], }), ]), getActiveForMember: vi.fn(async () => null), }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:31:30.000Z', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'unrelated-message-1', }); }); it('keeps OpenCode agenda-sync busy when an active prompt ledger record exists after recovery bypass', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; const proofMissingRecord = buildOpenCodeProofMissingRecord({ teamName, memberName: 'jack', laneId, inboxMessageId: 'proof-missing-message-1', taskRefs: [taskRef], }); hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/jack.json`, JSON.stringify([ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'proof-missing-message-1', messageKind: 'default', taskRefs: [taskRef], }, ]) ); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: 'jack', laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: { [laneId]: { laneId, state: 'active', updatedAt: '2026-02-23T17:30:00.000Z', }, }, }); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ list: vi.fn(async () => [proofMissingRecord]), getActiveForMember: vi.fn(async () => ({ ...proofMissingRecord, id: 'opencode-prompt:active-nudge-1', inboxMessageId: 'active-nudge-1', messageKind: 'member_work_sync_nudge', status: 'accepted', nextAttemptAt: '2026-02-23T17:33:00.000Z', })), }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:32:00.000Z', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_prompt_delivery_active:member_work_sync_nudge', activeMessageId: 'active-nudge-1', retryAfterIso: '2026-02-23T17:33:00.000Z', }); }); it('keeps OpenCode agenda-sync busy for same-task proof-missing messages with attachments', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/jack.json`, JSON.stringify([ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'proof-missing-message-1', messageKind: 'default', taskRefs: [taskRef], attachments: [{ id: 'attachment-1', filename: 'notes.txt', mimeType: 'text/plain' }], }, ]) ); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: 'jack', laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: { [laneId]: { laneId, state: 'active', updatedAt: '2026-02-23T17:30:00.000Z', }, }, }); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ list: vi.fn(async () => [ buildOpenCodeProofMissingRecord({ teamName, memberName: 'jack', laneId, inboxMessageId: 'proof-missing-message-1', taskRefs: [taskRef], }), ]), getActiveForMember: vi.fn(async () => null), }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:32:00.000Z', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'proof-missing-message-1', }); }); it('keeps OpenCode proof-missing foreground messages busy outside agenda-sync recovery', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; seedOpenCodeBusyStatusFixture({ service, teamName, laneId, inboxMessages: [ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'proof-missing-message-1', messageKind: 'default', taskRefs: [taskRef], }, ], ledgerRecords: [ buildOpenCodeProofMissingRecord({ teamName, memberName: 'jack', laneId, inboxMessageId: 'proof-missing-message-1', taskRefs: [taskRef], }), ], }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:32:00.000Z', taskRefs: [taskRef], }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'proof-missing-message-1', }); }); it('keeps OpenCode agenda-sync busy when proof-missing record task refs do not overlap', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; const otherTaskRef = { teamName, taskId: 'task-9999', displayId: 'task9999' }; seedOpenCodeBusyStatusFixture({ service, teamName, laneId, inboxMessages: [ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'proof-missing-message-1', messageKind: 'default', taskRefs: [taskRef], }, ], ledgerRecords: [ buildOpenCodeProofMissingRecord({ teamName, memberName: 'jack', laneId, inboxMessageId: 'proof-missing-message-1', taskRefs: [otherTaskRef], }), ], }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:32:00.000Z', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'proof-missing-message-1', }); }); it('keeps OpenCode agenda-sync busy when terminal ledger reason is not proof missing', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; seedOpenCodeBusyStatusFixture({ service, teamName, laneId, inboxMessages: [ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'terminal-message-1', messageKind: 'default', taskRefs: [taskRef], }, ], ledgerRecords: [ { ...buildOpenCodeProofMissingRecord({ teamName, memberName: 'jack', laneId, inboxMessageId: 'terminal-message-1', taskRefs: [taskRef], }), responseState: 'permission_blocked', lastReason: 'permission_blocked', diagnostics: ['permission_blocked'], }, ], }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:32:00.000Z', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'terminal-message-1', }); }); it('keeps OpenCode agenda-sync busy when the proof-missing lane is inactive', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const taskRef = { teamName, taskId: 'task-1234', displayId: 'task1234' }; seedOpenCodeBusyStatusFixture({ service, teamName, laneId, laneState: 'stopped', inboxMessages: [ { from: 'team-lead', to: 'jack', text: 'Please continue task #task1234.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'proof-missing-message-1', messageKind: 'default', taskRefs: [taskRef], }, ], ledgerRecords: [ buildOpenCodeProofMissingRecord({ teamName, memberName: 'jack', laneId, inboxMessageId: 'proof-missing-message-1', taskRefs: [taskRef], }), ], }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:32:00.000Z', workSyncIntent: 'agenda_sync', taskRefs: [taskRef], }); expect(busy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'proof-missing-message-1', }); }); it('does not treat the current unread OpenCode review request as busy for review-pickup checks', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/jack.json`, JSON.stringify([ { from: 'team-lead', to: 'jack', text: '**Please review** task #task1234\n\nFIRST call review_start.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'review-request-1', source: 'system_notification', summary: 'Review request for #task1234', }, ]) ); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(() => Promise.resolve({ ok: true, canonicalMemberName: 'jack', laneId, }) ); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockReturnValue( Promise.resolve({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: { [laneId]: { laneId, state: 'active', updatedAt: '2026-02-23T17:30:00.000Z', }, }, }) ); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ getActiveForMember: vi.fn(() => Promise.resolve(null)), }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:31:10.000Z', workSyncIntent: 'review_pickup', taskRefs: [{ teamName, taskId: 'task-1234', displayId: 'task1234' }], }); expect(busy).toEqual({ busy: false }); const mismatchedTaskBusy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:31:10.000Z', workSyncIntent: 'review_pickup', taskRefs: [{ teamName, taskId: 'other-task', displayId: 'other' }], }); expect(mismatchedTaskBusy).toMatchObject({ busy: true, reason: 'opencode_foreground_inbox_unread', activeMessageId: 'review-request-1', }); }); it('does not treat unread OpenCode work-sync nudges as foreground busy blockers', async () => { const service = new TeamProvisioningService(); const teamName = 'my-team'; const laneId = 'secondary:opencode:jack'; const teamsBasePath = getTeamsBasePath(); hoisted.files.set( `${teamsBasePath}/${teamName}/config.json`, JSON.stringify({ name: teamName, projectPath: '/tmp/my-team', members: [ { name: 'team-lead', agentType: 'team-lead' }, { name: 'jack', role: 'developer', providerId: 'opencode', model: 'openrouter/test' }, ], }) ); hoisted.files.set( `${teamsBasePath}/${teamName}/inboxes/jack.json`, JSON.stringify([ { from: 'system', to: 'jack', text: 'Work sync check.', timestamp: '2026-02-23T17:31:00.000Z', read: false, messageId: 'work-sync-nudge-1', messageKind: 'member_work_sync_nudge', }, ]) ); (service as any).resolveOpenCodeMemberDeliveryIdentity = vi.fn(async () => ({ ok: true, canonicalMemberName: 'jack', laneId, })); vi.spyOn(OpenCodeRuntimeStore, 'readOpenCodeRuntimeLaneIndex').mockResolvedValue({ version: 1, updatedAt: '2026-02-23T17:30:00.000Z', lanes: { [laneId]: { laneId, state: 'active', updatedAt: '2026-02-23T17:30:00.000Z', }, }, }); vi.spyOn(service as any, 'createOpenCodePromptDeliveryLedger').mockReturnValue({ getActiveForMember: vi.fn(async () => null), }); const busy = await service.getOpenCodeMemberDeliveryBusyStatus({ teamName, memberName: 'jack', nowIso: '2026-02-23T17:31:10.000Z', }); expect(busy).toEqual({ busy: false }); }); });