import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdtempSync, rmSync } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; import { init } from '../src/cli/commands/init.js'; import { createMessage, listInbox, getMessage, markMessageDelivered, markMessageRead, ackMessage, } from '../src/core/services/messageService.js'; import { messageReply } from '../src/cli/commands/message.js'; describe('message read-receipts + replies', () => { let cwd: string; beforeEach(async () => { cwd = mkdtempSync(join(tmpdir(), 'ah-msg-receipt-')); await init(cwd, { yes: true, projectName: 'test' }); }); afterEach(() => { rmSync(cwd, { recursive: true, force: true }); }); it('listInbox transitions unread → delivered on fetch (agent-scoped)', () => { const msg = createMessage(cwd, { from: 'codex', to: 'claude', text: 'hi' }); expect(getMessage(cwd, msg.id).message.status).toBe('unread'); const inbox = listInbox(cwd, 'claude'); expect(inbox[0].status).toBe('delivered'); // Persisted on disk, not just in the returned row. expect(getMessage(cwd, msg.id).message.status).toBe('delivered'); }); it('markMessageDelivered never downgrades a stronger receipt', () => { const msg = createMessage(cwd, { from: 'codex', to: 'claude', text: 'hi' }); markMessageRead(cwd, msg.id); const after = markMessageDelivered(cwd, msg.id); expect(after.status).toBe('read'); }); it('ackMessage transitions to acked (strongest receipt)', () => { const msg = createMessage(cwd, { from: 'codex', to: 'claude', text: 'hi' }); const acked = ackMessage(cwd, msg.id, 'claude'); expect(acked.status).toBe('acked'); expect(getMessage(cwd, msg.id).message.status).toBe('acked'); }); it('persists replyTo through create → getMessage', () => { const parent = createMessage(cwd, { from: 'claude', to: 'codex', text: 'do X' }); const reply = createMessage(cwd, { from: 'codex', to: 'claude', text: 'done', replyTo: parent.id }); expect(reply.replyTo).toBe(parent.id); expect(getMessage(cwd, reply.id).message.replyTo).toBe(parent.id); }); it('messageReply routes back to the parent sender and inherits its task', () => { const parent = createMessage(cwd, { from: 'claude', to: 'codex', text: 'do X', taskId: 'TSK-0007' }); const reply = messageReply(cwd, parent.id, { from: 'codex', text: 'on it' }); expect(reply.to).toBe('claude'); // back to parent.from expect(reply.replyTo).toBe(parent.id); expect(reply.taskId).toBe('TSK-0007'); // inherited }); it('messageReply --task overrides the inherited task', () => { const parent = createMessage(cwd, { from: 'claude', to: 'codex', text: 'do X', taskId: 'TSK-0007' }); const reply = messageReply(cwd, parent.id, { from: 'codex', text: 'on it', taskId: 'TSK-0009' }); expect(reply.taskId).toBe('TSK-0009'); }); });