From 54ea9afae621f8683d801a725d32f39d7ffed76d Mon Sep 17 00:00:00 2001 From: ejclaw Date: Thu, 30 Apr 2026 00:45:04 +0900 Subject: [PATCH] fix paired owner progress identity --- src/message-runtime-queue-progress.test.ts | 98 ++++++++++++++++++++++ src/message-runtime-queue.ts | 4 +- src/message-runtime.ts | 2 +- 3 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 src/message-runtime-queue-progress.test.ts diff --git a/src/message-runtime-queue-progress.test.ts b/src/message-runtime-queue-progress.test.ts new file mode 100644 index 0000000..200d3a6 --- /dev/null +++ b/src/message-runtime-queue-progress.test.ts @@ -0,0 +1,98 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { _initTestDatabase, getLatestOpenPairedTaskForChat } from './db.js'; +import { runQueuedGroupTurn } from './message-runtime-queue.js'; +import { resetPairedFollowUpScheduleState } from './paired-follow-up-scheduler.js'; +import type { Channel, RegisteredGroup } from './types.js'; + +function makeGroup(): RegisteredGroup { + return { + name: 'Test Group', + folder: 'test-group', + trigger: '@Andy', + added_at: '2026-03-30T00:00:00.000Z', + requiresTrigger: false, + agentType: 'codex', + workDir: '/repo', + }; +} + +function makeChannel(): Channel { + return { + name: 'discord-review', + connect: vi.fn(), + sendMessage: vi.fn(), + isConnected: vi.fn(() => true), + ownsJid: vi.fn(() => true), + disconnect: vi.fn(), + } as unknown as Channel; +} + +describe('message-runtime queue progress persistence', () => { + beforeEach(() => { + _initTestDatabase(); + resetPairedFollowUpScheduleState(); + }); + + it('creates a paired owner task before the first queued turn so progress has a turn identity', async () => { + const executeTurn = vi.fn(async () => ({ + outputStatus: 'success' as const, + deliverySucceeded: true, + visiblePhase: 'progress', + })); + const lastAgentTimestamps: Record = {}; + const saveState = vi.fn(); + + const outcome = await runQueuedGroupTurn({ + chatJid: 'group@test', + group: makeGroup(), + runId: 'run-new-paired-owner', + log: { info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, + timezone: 'UTC', + missedMessages: [ + { + id: 'human-new-task-1', + chat_jid: 'group@test', + sender: 'user@test', + sender_name: 'User', + content: '새 작업입니다', + timestamp: '2026-03-30T00:00:03.000Z', + seq: 48, + is_bot_message: false, + }, + ], + task: null, + roleToChannel: { + owner: null, + reviewer: makeChannel(), + arbiter: null, + }, + ownerChannel: makeChannel(), + lastAgentTimestamps, + saveState, + executeTurn, + getFixedRoleChannelName: () => 'discord-review', + labelPairedSenders: (_chatJid, messages) => messages, + formatMessages: () => 'formatted prompt', + }); + + const freshTask = getLatestOpenPairedTaskForChat('group@test'); + expect(outcome).toBe(true); + expect(freshTask).toBeDefined(); + expect(executeTurn).toHaveBeenCalledWith( + expect.objectContaining({ + deliveryRole: 'owner', + forcedRole: 'owner', + pairedTurnIdentity: { + turnId: `${freshTask!.id}:${freshTask!.updated_at}:owner-turn`, + taskId: freshTask!.id, + taskUpdatedAt: freshTask!.updated_at, + intentKind: 'owner-turn', + role: 'owner', + }, + }), + ); + expect(lastAgentTimestamps).toEqual({ 'group@test': '48' }); + expect(saveState).toHaveBeenCalled(); + }); +}); diff --git a/src/message-runtime-queue.ts b/src/message-runtime-queue.ts index 4cacf38..79a8a00 100644 --- a/src/message-runtime-queue.ts +++ b/src/message-runtime-queue.ts @@ -166,11 +166,11 @@ export async function runQueuedGroupTurn(args: { ) : !isBotOnlyPairedRoomTurn(chatJid, missedMessages); let fallbackMessages = missedMessages; - if (currentTask && hasHumanMsg) { + if (currentTask !== undefined && hasHumanMsg) { const resolvedTask = resolveOwnerTaskForHumanMessage({ group, chatJid, - existingTask: currentTask, + existingTask: currentTask ?? null, }); currentTask = resolvedTask.task; if (resolvedTask.supersededTask) { diff --git a/src/message-runtime.ts b/src/message-runtime.ts index d074b8f..ec36db9 100644 --- a/src/message-runtime.ts +++ b/src/message-runtime.ts @@ -491,7 +491,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { missedMessages, task: hasReviewerLease(chatJid) ? getLatestOpenPairedTaskForChat(chatJid) - : null, + : undefined, roleToChannel, ownerChannel: channel, lastAgentTimestamps: deps.getLastAgentTimestamps(),