Fix mobile rooms and live progress parity

Improve mobile room UX, separate live progress from recent chat, and lock the parity behavior with service tests.
This commit is contained in:
Eyejoker
2026-04-28 01:27:55 +09:00
committed by GitHub
parent 79efa6b37c
commit 20e1a1e5e1
7 changed files with 269 additions and 25 deletions

View File

@@ -356,6 +356,99 @@ describe('web dashboard data', () => {
]);
});
it('uses Discord live progress messages without exposing them as recent chat', () => {
const task = makePairedTask({
id: 'paired-room-progress',
chat_jid: 'dc:ops',
status: 'active',
updated_at: '2026-04-26T06:10:00.000Z',
});
const turn: PairedTurnRecord = {
turn_id: 'turn-progress',
task_id: task.id,
task_updated_at: task.updated_at,
role: 'owner',
intent_kind: 'owner-turn',
state: 'running',
executor_service_id: 'codex-main',
executor_agent_type: 'codex',
attempt_no: 1,
created_at: '2026-04-26T06:00:00.000Z',
updated_at: '2026-04-26T06:10:00.000Z',
completed_at: null,
last_error: null,
};
const statusPrefix = '';
const messages: NewMessage[] = [
{
id: 'msg-latest',
chat_jid: 'dc:ops',
sender: 'user-1',
sender_name: '눈쟁이',
content: 'recent human message',
timestamp: '2026-04-26T06:09:00.000Z',
is_from_me: false,
is_bot_message: false,
message_source_kind: 'human',
},
];
const progressMessages: NewMessage[] = [
{
id: 'msg-stale-progress',
chat_jid: 'dc:ops',
sender: 'bot-1',
sender_name: '오너',
content: `${statusPrefix}stale progress`,
timestamp: '2026-04-26T05:59:00.000Z',
is_from_me: true,
is_bot_message: true,
message_source_kind: 'bot',
},
{
id: 'msg-live-progress',
chat_jid: 'dc:ops',
sender: 'bot-1',
sender_name: '오너',
content: `${statusPrefix}building mobile parity\n\n12s`,
timestamp: '2026-04-26T06:08:00.000Z',
is_from_me: true,
is_bot_message: true,
message_source_kind: 'bot',
},
];
const activity = buildWebDashboardRoomActivity({
serviceId: 'codex-main',
entry: {
jid: 'dc:ops',
name: '#ops',
folder: 'ops',
agentType: 'codex',
status: 'processing',
elapsedMs: 20_000,
pendingMessages: false,
pendingTasks: 0,
},
pairedTask: task,
turns: [turn],
attempts: [],
outputs: [],
messages,
progressMessages,
});
expect(activity.pairedTask?.currentTurn).toMatchObject({
progressText: 'building mobile parity',
progressUpdatedAt: '2026-04-26T06:08:00.000Z',
});
expect(activity.messages).toEqual([
expect.objectContaining({
senderName: '눈쟁이',
content: 'recent human message',
}),
]);
});
it('builds typed inbox items from pending rooms, paired tasks, and CI failures', () => {
const snapshots: StatusSnapshot[] = [
{

View File

@@ -431,6 +431,10 @@ function sanitizeRoomMessage(message: NewMessage): WebDashboardRoomMessage {
*/
const TASK_STATUS_PREFIX = '';
function isTaskStatusMessage(message: NewMessage): boolean {
return (message.content ?? '').startsWith(TASK_STATUS_PREFIX);
}
function turnRoleFromSenderName(name: string | null | undefined): string {
const v = (name ?? '').toLowerCase();
if (v.includes('오너') || v.includes('owner')) return 'owner';
@@ -533,6 +537,7 @@ export function buildWebDashboardRoomActivity(args: {
attempts: PairedTurnAttemptRecord[];
outputs: PairedTurnOutput[];
messages: NewMessage[];
progressMessages?: NewMessage[];
outputLimit?: number;
}): WebDashboardRoomActivity {
const latestAttemptByTurnId = new Map<string, PairedTurnAttemptRecord>();
@@ -561,7 +566,9 @@ export function buildWebDashboardRoomActivity(args: {
elapsedMs: args.entry.elapsedMs,
pendingMessages: args.entry.pendingMessages,
pendingTasks: args.entry.pendingTasks,
messages: args.messages.map(sanitizeRoomMessage),
messages: args.messages
.filter((message) => !isTaskStatusMessage(message))
.map(sanitizeRoomMessage),
pairedTask: args.pairedTask
? {
id: args.pairedTask.id,
@@ -570,7 +577,11 @@ export function buildWebDashboardRoomActivity(args: {
roundTripCount: args.pairedTask.round_trip_count,
updatedAt: args.pairedTask.updated_at,
currentTurn: currentTurn
? sanitizeRoomTurn(currentTurn, currentAttempt, args.messages)
? sanitizeRoomTurn(
currentTurn,
currentAttempt,
args.progressMessages ?? args.messages,
)
: null,
outputs: args.outputs.slice(-outputLimit).map(sanitizeRoomTurnOutput),
}

View File

@@ -1506,6 +1506,22 @@ describe('web dashboard server handler', () => {
message_source_kind: 'human',
},
];
const statusPrefix = '';
const progressMessages: NewMessage[] = [
...messages,
{
id: 'msg-progress',
chat_jid: 'dc:ops',
sender: 'reviewer-bot',
sender_name: '리뷰어',
content: `${statusPrefix}checking current output\n\n9s`,
timestamp: '2026-04-26T05:20:00.000Z',
is_from_me: true,
is_bot_message: true,
message_source_kind: 'bot',
},
];
const requestedMessageLimits: number[] = [];
const handler = createWebDashboardHandler({
readStatusSnapshots: () => [
{
@@ -1535,7 +1551,10 @@ describe('web dashboard server handler', () => {
getPairedTurnAttempts: (turnId) =>
turnId === turns[0]!.turn_id ? attempts : [],
getPairedTurnOutputs: () => outputs,
getRecentChatMessages: () => messages,
getRecentChatMessages: (_jid, limit) => {
requestedMessageLimits.push(limit ?? 20);
return limit && limit > 8 ? progressMessages : messages;
},
});
const response = await handler(
@@ -1555,6 +1574,8 @@ describe('web dashboard server handler', () => {
state: string;
attemptNo: number;
lastError: string;
progressText: string;
progressUpdatedAt: string;
};
outputs: Array<{ outputText: string; turnNumber: number }>;
};
@@ -1568,12 +1589,16 @@ describe('web dashboard server handler', () => {
state: 'running',
attemptNo: 2,
lastError: 'OPENAI_API_KEY=<redacted>',
progressText: 'checking current output',
progressUpdatedAt: '2026-04-26T05:20:00.000Z',
});
expect(body.pairedTask.outputs).toMatchObject([
{ turnNumber: 1, outputText: 'owner final output' },
]);
expect(requestedMessageLimits).toEqual([8, 40]);
expect(body.messages[0]?.content).toContain('BOT_TOKEN=<redacted>');
expect(body.messages[0]?.senderName).toBe('눈쟁이');
expect(body.messages).toHaveLength(1);
});
it('returns 404 for missing room timelines', async () => {

View File

@@ -781,6 +781,9 @@ export function createWebDashboardHandler(
for (const [jid, { snapshot, entry }] of uniqueByJid) {
const pairedTask = loadLatestPairedTaskForChat(jid) ?? null;
const messages = loadRecentChatMessages(jid, 8);
const progressMessages = pairedTask
? loadRecentChatMessages(jid, 40)
: messages;
const outputs = loadRecentPairedTurnOutputsForChat(jid, 8);
if (!pairedTask && messages.length === 0 && outputs.length === 0)
continue;
@@ -796,6 +799,7 @@ export function createWebDashboardHandler(
attempts: [],
outputs,
messages,
progressMessages,
outputLimit: 8,
});
}
@@ -1171,6 +1175,7 @@ export function createWebDashboardHandler(
attempts,
outputs: pairedTask ? loadPairedTurnOutputs(pairedTask.id) : [],
messages: loadRecentChatMessages(timelineRoomJid, 8),
progressMessages: loadRecentChatMessages(timelineRoomJid, 40),
}),
);
}