fix: use work items as dashboard outbound SSOT

This commit is contained in:
ejclaw
2026-04-29 01:23:29 +09:00
parent 9655501bdf
commit 6b2ef42374
14 changed files with 523 additions and 81 deletions

View File

@@ -1,7 +1,11 @@
import { describe, expect, it } from 'vitest';
import type { StatusSnapshot } from './status-dashboard.js';
import type { PairedTurnAttemptRecord, PairedTurnRecord } from './db.js';
import type {
PairedTurnAttemptRecord,
PairedTurnRecord,
WorkItem,
} from './db.js';
import type {
NewMessage,
PairedTask,
@@ -65,6 +69,29 @@ function makePairedTask(overrides: Partial<PairedTask>): PairedTask {
};
}
function makeDeliveredWorkItem(overrides: Partial<WorkItem> = {}): WorkItem {
return {
id: 100,
group_folder: 'ops',
chat_jid: 'dc:ops',
agent_type: 'codex',
service_id: 'codex-main',
delivery_role: 'owner',
status: 'delivered',
start_seq: null,
end_seq: null,
result_payload: 'TASK_DONE\n\ncanonical delivered output',
attachments: [],
delivery_attempts: 1,
delivery_message_id: 'discord-msg-100',
last_error: null,
created_at: '2026-04-26T05:30:00.000Z',
updated_at: '2026-04-26T05:30:10.000Z',
delivered_at: '2026-04-26T05:30:10.000Z',
...overrides,
};
}
describe('web dashboard data', () => {
it('builds overview counts from status snapshots and scheduled tasks', () => {
const snapshots: StatusSnapshot[] = [
@@ -356,6 +383,110 @@ describe('web dashboard room activity data', () => {
]);
});
it('uses delivered work items as the visible outbound source when supplied', () => {
const task = makePairedTask({
id: 'paired-canonical-outbound',
chat_jid: 'dc:ops',
status: 'in_review',
updated_at: '2026-04-26T05:30:00.000Z',
});
const output: PairedTurnOutput = {
id: 22,
task_id: task.id,
turn_number: 2,
role: 'owner',
output_text: 'TASK_DONE\n\nexecution artifact only',
verdict: 'task_done',
created_at: '2026-04-26T05:30:00.000Z',
};
const activity = buildWebDashboardRoomActivity({
serviceId: 'codex-main',
entry: {
jid: 'dc:ops',
name: '#ops',
folder: 'ops',
agentType: 'codex',
status: 'inactive',
elapsedMs: null,
pendingMessages: false,
pendingTasks: 0,
},
pairedTask: task,
turns: [],
attempts: [],
outputs: [output],
outboundItems: [makeDeliveredWorkItem()],
messages: [
{
id: 'discord-msg-100',
chat_jid: 'dc:ops',
sender: 'bot-owner',
sender_name: '오너',
content: 'TASK_DONE\n\ncanonical delivered output',
timestamp: '2026-04-26T05:30:10.000Z',
is_from_me: true,
is_bot_message: true,
message_source_kind: 'bot',
},
],
});
expect(activity.pairedTask?.outputs).toEqual([]);
expect(activity.messages).toEqual([
expect.objectContaining({
id: 'work:100',
senderName: 'owner',
content: 'TASK_DONE\n\ncanonical delivered output',
}),
]);
});
it('merges canonical outbound and recent messages in chronological order', () => {
const activity = buildWebDashboardRoomActivity({
serviceId: 'codex-main',
entry: {
jid: 'dc:ops',
name: '#ops',
folder: 'ops',
agentType: 'codex',
status: 'inactive',
elapsedMs: null,
pendingMessages: false,
pendingTasks: 0,
},
pairedTask: null,
turns: [],
attempts: [],
outputs: [],
outboundItems: [
makeDeliveredWorkItem({
id: 101,
delivered_at: '2026-04-26T05:30:00.000Z',
result_payload: 'owner canonical',
}),
],
messages: [
{
id: 'human-after',
chat_jid: 'dc:ops',
sender: 'user-1',
sender_name: '눈쟁이',
content: 'human after',
timestamp: '2026-04-26T05:31:00.000Z',
is_from_me: false,
is_bot_message: false,
message_source_kind: 'human',
},
],
});
expect(activity.messages.map((message) => message.id)).toEqual([
'work:101',
'human-after',
]);
});
it('uses canonical turn progress without exposing status messages as recent chat', () => {
const task = makePairedTask({
id: 'paired-room-progress',