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:
@@ -2832,6 +2832,7 @@ function RoomBoardV2({
|
|||||||
const [filter, setFilter] = useState<RoomFilter>('all');
|
const [filter, setFilter] = useState<RoomFilter>('all');
|
||||||
const [sort, setSort] = useState<RoomSort>('recent');
|
const [sort, setSort] = useState<RoomSort>('recent');
|
||||||
const [drafts, setDrafts] = useState<Record<string, string>>({});
|
const [drafts, setDrafts] = useState<Record<string, string>>({});
|
||||||
|
const [mobileDetailOpen, setMobileDetailOpen] = useState(false);
|
||||||
|
|
||||||
const allEntries: RoomEntryWithService[] = snapshots.flatMap((snapshot) =>
|
const allEntries: RoomEntryWithService[] = snapshots.flatMap((snapshot) =>
|
||||||
snapshot.entries.map((entry) => ({
|
snapshot.entries.map((entry) => ({
|
||||||
@@ -2869,7 +2870,10 @@ function RoomBoardV2({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const nextJid = selectedEntry?.jid ?? null;
|
const nextJid = selectedEntry?.jid ?? null;
|
||||||
if (nextJid !== selectedJid) onSelectedJidChange(nextJid);
|
if (nextJid !== selectedJid) {
|
||||||
|
onSelectedJidChange(nextJid);
|
||||||
|
setMobileDetailOpen(false);
|
||||||
|
}
|
||||||
}, [onSelectedJidChange, selectedEntry?.jid, selectedJid]);
|
}, [onSelectedJidChange, selectedEntry?.jid, selectedJid]);
|
||||||
|
|
||||||
if (allEntries.length === 0) {
|
if (allEntries.length === 0) {
|
||||||
@@ -2937,7 +2941,9 @@ function RoomBoardV2({
|
|||||||
{sorted.length === 0 ? (
|
{sorted.length === 0 ? (
|
||||||
<EmptyState>{t.rooms.empty}</EmptyState>
|
<EmptyState>{t.rooms.empty}</EmptyState>
|
||||||
) : (
|
) : (
|
||||||
<div className="rooms-twopane">
|
<div
|
||||||
|
className={`rooms-twopane${mobileDetailOpen ? ' is-detail-open' : ''}`}
|
||||||
|
>
|
||||||
<aside className="rooms-list" aria-label={t.rooms.cardsAria}>
|
<aside className="rooms-list" aria-label={t.rooms.cardsAria}>
|
||||||
{sorted.map((entry) => {
|
{sorted.map((entry) => {
|
||||||
const items = inbox.filter((it) => it.roomJid === entry.jid);
|
const items = inbox.filter((it) => it.roomJid === entry.jid);
|
||||||
@@ -2949,7 +2955,10 @@ function RoomBoardV2({
|
|||||||
aria-current={active ? 'page' : undefined}
|
aria-current={active ? 'page' : undefined}
|
||||||
className={`rooms-list-item status-${entry.status}${active ? ' is-active' : ''}`}
|
className={`rooms-list-item status-${entry.status}${active ? ' is-active' : ''}`}
|
||||||
key={`${entry.serviceId}:${entry.jid}`}
|
key={`${entry.serviceId}:${entry.jid}`}
|
||||||
onClick={() => onSelectedJidChange(entry.jid)}
|
onClick={() => {
|
||||||
|
onSelectedJidChange(entry.jid);
|
||||||
|
setMobileDetailOpen(true);
|
||||||
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span className={`room-pulse pulse-${entry.status}`}>
|
<span className={`room-pulse pulse-${entry.status}`}>
|
||||||
@@ -2977,6 +2986,13 @@ function RoomBoardV2({
|
|||||||
})}
|
})}
|
||||||
</aside>
|
</aside>
|
||||||
<main className="rooms-detail">
|
<main className="rooms-detail">
|
||||||
|
<button
|
||||||
|
className="rooms-mobile-back"
|
||||||
|
onClick={() => setMobileDetailOpen(false)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
← {t.panels.rooms}
|
||||||
|
</button>
|
||||||
{selectedEntry ? (
|
{selectedEntry ? (
|
||||||
<RoomCardV2
|
<RoomCardV2
|
||||||
activity={roomActivity[selectedEntry.jid]}
|
activity={roomActivity[selectedEntry.jid]}
|
||||||
|
|||||||
@@ -3020,8 +3020,8 @@ progress::-moz-progress-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.shell {
|
.shell {
|
||||||
width: min(100% - 16px, 1480px);
|
width: 100%;
|
||||||
padding: 10px 0 calc(24px + env(safe-area-inset-bottom));
|
padding: 8px 0 calc(18px + env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
@@ -3130,9 +3130,10 @@ progress::-moz-progress-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.usage-row {
|
.usage-row {
|
||||||
grid-template-columns:
|
grid-template-columns: minmax(84px, 1fr) minmax(58px, 0.48fr) minmax(
|
||||||
minmax(72px, 0.82fr) minmax(52px, 0.48fr) minmax(52px, 0.48fr)
|
58px,
|
||||||
minmax(48px, 0.36fr);
|
0.48fr
|
||||||
|
);
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
padding: 7px 6px;
|
padding: 7px 6px;
|
||||||
}
|
}
|
||||||
@@ -3181,8 +3182,7 @@ progress::-moz-progress-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.usage-speed {
|
.usage-speed {
|
||||||
min-height: 0;
|
display: none;
|
||||||
padding: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.usage-quota small {
|
.usage-quota small {
|
||||||
@@ -3311,7 +3311,7 @@ progress::-moz-progress-bar {
|
|||||||
|
|
||||||
@media (max-width: 380px) {
|
@media (max-width: 380px) {
|
||||||
.shell {
|
.shell {
|
||||||
width: min(100% - 16px, 1480px);
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-nav {
|
.section-nav {
|
||||||
@@ -3334,9 +3334,10 @@ progress::-moz-progress-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.usage-row {
|
.usage-row {
|
||||||
grid-template-columns:
|
grid-template-columns: minmax(76px, 1fr) minmax(48px, 0.48fr) minmax(
|
||||||
minmax(68px, 0.8fr) minmax(48px, 0.48fr) minmax(48px, 0.48fr)
|
48px,
|
||||||
minmax(44px, 0.34fr);
|
0.48fr
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.usage-account {
|
.usage-account {
|
||||||
@@ -3376,6 +3377,39 @@ progress::-moz-progress-bar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 420px) {
|
||||||
|
.section-nav {
|
||||||
|
grid-template-columns: 48px minmax(0, 1fr) 18px 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-status {
|
||||||
|
width: 12px;
|
||||||
|
min-width: 12px;
|
||||||
|
min-height: 12px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: transparent;
|
||||||
|
text-indent: -999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-status::before {
|
||||||
|
display: block;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--green);
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-status-stale::before {
|
||||||
|
background: var(--yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-status-offline::before {
|
||||||
|
background: var(--red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (display-mode: standalone) {
|
@media (display-mode: standalone) {
|
||||||
.shell {
|
.shell {
|
||||||
padding-top: max(18px, env(safe-area-inset-top));
|
padding-top: max(18px, env(safe-area-inset-top));
|
||||||
@@ -3507,12 +3541,68 @@ progress::-moz-progress-bar {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rooms-mobile-back {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.rooms-twopane {
|
.rooms-twopane {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.view-stack.view-rooms {
|
||||||
|
height: calc(100dvh - 72px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-stack.view-rooms .view-panel {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-stack.view-rooms .rooms-toolbar {
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-stack.view-rooms .rooms-twopane {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-stack.view-rooms .rooms-twopane:not(.is-detail-open) .rooms-detail {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-stack.view-rooms .rooms-twopane.is-detail-open .rooms-list {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-stack.view-rooms .rooms-list,
|
||||||
|
.view-stack.view-rooms .rooms-detail {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-stack.view-rooms .rooms-detail {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-stack.view-rooms .rooms-mobile-back {
|
||||||
|
display: inline-flex;
|
||||||
|
min-height: 42px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 6px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 999px;
|
||||||
|
color: var(--bg-ink);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
font: inherit;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.rooms-list {
|
.rooms-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -4581,10 +4671,14 @@ progress::-moz-progress-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.parsed-body table {
|
.parsed-body table {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
overflow-x: auto;
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
width: auto;
|
width: max-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.parsed-body th,
|
.parsed-body th,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"apps/dashboard/src/App.tsx": {
|
"apps/dashboard/src/App.tsx": {
|
||||||
"maxLines": 4596,
|
"maxLines": 4612,
|
||||||
"maxFunctionLines": 583,
|
"maxFunctionLines": 583,
|
||||||
"maxComplexity": 79,
|
"maxComplexity": 79,
|
||||||
"maxNesting": 4,
|
"maxNesting": 4,
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"owner": "dashboard i18n hotspot"
|
"owner": "dashboard i18n hotspot"
|
||||||
},
|
},
|
||||||
"apps/dashboard/src/styles.css": {
|
"apps/dashboard/src/styles.css": {
|
||||||
"maxLines": 4781,
|
"maxLines": 4875,
|
||||||
"owner": "dashboard stylesheet hotspot"
|
"owner": "dashboard stylesheet hotspot"
|
||||||
},
|
},
|
||||||
"runners/agent-runner/src/index.ts": {
|
"runners/agent-runner/src/index.ts": {
|
||||||
@@ -229,17 +229,17 @@
|
|||||||
"owner": "verification test hotspot"
|
"owner": "verification test hotspot"
|
||||||
},
|
},
|
||||||
"src/web-dashboard-data.test.ts": {
|
"src/web-dashboard-data.test.ts": {
|
||||||
"maxFunctionLines": 411,
|
"maxFunctionLines": 504,
|
||||||
"owner": "web dashboard data test hotspot"
|
"owner": "web dashboard data test hotspot"
|
||||||
},
|
},
|
||||||
"src/web-dashboard-server.test.ts": {
|
"src/web-dashboard-server.test.ts": {
|
||||||
"maxLines": 1629,
|
"maxLines": 1654,
|
||||||
"maxFunctionLines": 1552,
|
"maxFunctionLines": 1577,
|
||||||
"owner": "web dashboard server test hotspot"
|
"owner": "web dashboard server test hotspot"
|
||||||
},
|
},
|
||||||
"src/web-dashboard-server.ts": {
|
"src/web-dashboard-server.ts": {
|
||||||
"maxLines": 1754,
|
"maxLines": 1759,
|
||||||
"maxFunctionLines": 1002,
|
"maxFunctionLines": 1007,
|
||||||
"maxComplexity": 158,
|
"maxComplexity": 158,
|
||||||
"maxNesting": 3,
|
"maxNesting": 3,
|
||||||
"owner": "web dashboard server hotspot"
|
"owner": "web dashboard server hotspot"
|
||||||
|
|||||||
@@ -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', () => {
|
it('builds typed inbox items from pending rooms, paired tasks, and CI failures', () => {
|
||||||
const snapshots: StatusSnapshot[] = [
|
const snapshots: StatusSnapshot[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -431,6 +431,10 @@ function sanitizeRoomMessage(message: NewMessage): WebDashboardRoomMessage {
|
|||||||
*/
|
*/
|
||||||
const TASK_STATUS_PREFIX = '';
|
const TASK_STATUS_PREFIX = '';
|
||||||
|
|
||||||
|
function isTaskStatusMessage(message: NewMessage): boolean {
|
||||||
|
return (message.content ?? '').startsWith(TASK_STATUS_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
function turnRoleFromSenderName(name: string | null | undefined): string {
|
function turnRoleFromSenderName(name: string | null | undefined): string {
|
||||||
const v = (name ?? '').toLowerCase();
|
const v = (name ?? '').toLowerCase();
|
||||||
if (v.includes('오너') || v.includes('owner')) return 'owner';
|
if (v.includes('오너') || v.includes('owner')) return 'owner';
|
||||||
@@ -533,6 +537,7 @@ export function buildWebDashboardRoomActivity(args: {
|
|||||||
attempts: PairedTurnAttemptRecord[];
|
attempts: PairedTurnAttemptRecord[];
|
||||||
outputs: PairedTurnOutput[];
|
outputs: PairedTurnOutput[];
|
||||||
messages: NewMessage[];
|
messages: NewMessage[];
|
||||||
|
progressMessages?: NewMessage[];
|
||||||
outputLimit?: number;
|
outputLimit?: number;
|
||||||
}): WebDashboardRoomActivity {
|
}): WebDashboardRoomActivity {
|
||||||
const latestAttemptByTurnId = new Map<string, PairedTurnAttemptRecord>();
|
const latestAttemptByTurnId = new Map<string, PairedTurnAttemptRecord>();
|
||||||
@@ -561,7 +566,9 @@ export function buildWebDashboardRoomActivity(args: {
|
|||||||
elapsedMs: args.entry.elapsedMs,
|
elapsedMs: args.entry.elapsedMs,
|
||||||
pendingMessages: args.entry.pendingMessages,
|
pendingMessages: args.entry.pendingMessages,
|
||||||
pendingTasks: args.entry.pendingTasks,
|
pendingTasks: args.entry.pendingTasks,
|
||||||
messages: args.messages.map(sanitizeRoomMessage),
|
messages: args.messages
|
||||||
|
.filter((message) => !isTaskStatusMessage(message))
|
||||||
|
.map(sanitizeRoomMessage),
|
||||||
pairedTask: args.pairedTask
|
pairedTask: args.pairedTask
|
||||||
? {
|
? {
|
||||||
id: args.pairedTask.id,
|
id: args.pairedTask.id,
|
||||||
@@ -570,7 +577,11 @@ export function buildWebDashboardRoomActivity(args: {
|
|||||||
roundTripCount: args.pairedTask.round_trip_count,
|
roundTripCount: args.pairedTask.round_trip_count,
|
||||||
updatedAt: args.pairedTask.updated_at,
|
updatedAt: args.pairedTask.updated_at,
|
||||||
currentTurn: currentTurn
|
currentTurn: currentTurn
|
||||||
? sanitizeRoomTurn(currentTurn, currentAttempt, args.messages)
|
? sanitizeRoomTurn(
|
||||||
|
currentTurn,
|
||||||
|
currentAttempt,
|
||||||
|
args.progressMessages ?? args.messages,
|
||||||
|
)
|
||||||
: null,
|
: null,
|
||||||
outputs: args.outputs.slice(-outputLimit).map(sanitizeRoomTurnOutput),
|
outputs: args.outputs.slice(-outputLimit).map(sanitizeRoomTurnOutput),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1506,6 +1506,22 @@ describe('web dashboard server handler', () => {
|
|||||||
message_source_kind: 'human',
|
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({
|
const handler = createWebDashboardHandler({
|
||||||
readStatusSnapshots: () => [
|
readStatusSnapshots: () => [
|
||||||
{
|
{
|
||||||
@@ -1535,7 +1551,10 @@ describe('web dashboard server handler', () => {
|
|||||||
getPairedTurnAttempts: (turnId) =>
|
getPairedTurnAttempts: (turnId) =>
|
||||||
turnId === turns[0]!.turn_id ? attempts : [],
|
turnId === turns[0]!.turn_id ? attempts : [],
|
||||||
getPairedTurnOutputs: () => outputs,
|
getPairedTurnOutputs: () => outputs,
|
||||||
getRecentChatMessages: () => messages,
|
getRecentChatMessages: (_jid, limit) => {
|
||||||
|
requestedMessageLimits.push(limit ?? 20);
|
||||||
|
return limit && limit > 8 ? progressMessages : messages;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await handler(
|
const response = await handler(
|
||||||
@@ -1555,6 +1574,8 @@ describe('web dashboard server handler', () => {
|
|||||||
state: string;
|
state: string;
|
||||||
attemptNo: number;
|
attemptNo: number;
|
||||||
lastError: string;
|
lastError: string;
|
||||||
|
progressText: string;
|
||||||
|
progressUpdatedAt: string;
|
||||||
};
|
};
|
||||||
outputs: Array<{ outputText: string; turnNumber: number }>;
|
outputs: Array<{ outputText: string; turnNumber: number }>;
|
||||||
};
|
};
|
||||||
@@ -1568,12 +1589,16 @@ describe('web dashboard server handler', () => {
|
|||||||
state: 'running',
|
state: 'running',
|
||||||
attemptNo: 2,
|
attemptNo: 2,
|
||||||
lastError: 'OPENAI_API_KEY=<redacted>',
|
lastError: 'OPENAI_API_KEY=<redacted>',
|
||||||
|
progressText: 'checking current output',
|
||||||
|
progressUpdatedAt: '2026-04-26T05:20:00.000Z',
|
||||||
});
|
});
|
||||||
expect(body.pairedTask.outputs).toMatchObject([
|
expect(body.pairedTask.outputs).toMatchObject([
|
||||||
{ turnNumber: 1, outputText: 'owner final output' },
|
{ turnNumber: 1, outputText: 'owner final output' },
|
||||||
]);
|
]);
|
||||||
|
expect(requestedMessageLimits).toEqual([8, 40]);
|
||||||
expect(body.messages[0]?.content).toContain('BOT_TOKEN=<redacted>');
|
expect(body.messages[0]?.content).toContain('BOT_TOKEN=<redacted>');
|
||||||
expect(body.messages[0]?.senderName).toBe('눈쟁이');
|
expect(body.messages[0]?.senderName).toBe('눈쟁이');
|
||||||
|
expect(body.messages).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns 404 for missing room timelines', async () => {
|
it('returns 404 for missing room timelines', async () => {
|
||||||
|
|||||||
@@ -781,6 +781,9 @@ export function createWebDashboardHandler(
|
|||||||
for (const [jid, { snapshot, entry }] of uniqueByJid) {
|
for (const [jid, { snapshot, entry }] of uniqueByJid) {
|
||||||
const pairedTask = loadLatestPairedTaskForChat(jid) ?? null;
|
const pairedTask = loadLatestPairedTaskForChat(jid) ?? null;
|
||||||
const messages = loadRecentChatMessages(jid, 8);
|
const messages = loadRecentChatMessages(jid, 8);
|
||||||
|
const progressMessages = pairedTask
|
||||||
|
? loadRecentChatMessages(jid, 40)
|
||||||
|
: messages;
|
||||||
const outputs = loadRecentPairedTurnOutputsForChat(jid, 8);
|
const outputs = loadRecentPairedTurnOutputsForChat(jid, 8);
|
||||||
if (!pairedTask && messages.length === 0 && outputs.length === 0)
|
if (!pairedTask && messages.length === 0 && outputs.length === 0)
|
||||||
continue;
|
continue;
|
||||||
@@ -796,6 +799,7 @@ export function createWebDashboardHandler(
|
|||||||
attempts: [],
|
attempts: [],
|
||||||
outputs,
|
outputs,
|
||||||
messages,
|
messages,
|
||||||
|
progressMessages,
|
||||||
outputLimit: 8,
|
outputLimit: 8,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1171,6 +1175,7 @@ export function createWebDashboardHandler(
|
|||||||
attempts,
|
attempts,
|
||||||
outputs: pairedTask ? loadPairedTurnOutputs(pairedTask.id) : [],
|
outputs: pairedTask ? loadPairedTurnOutputs(pairedTask.id) : [],
|
||||||
messages: loadRecentChatMessages(timelineRoomJid, 8),
|
messages: loadRecentChatMessages(timelineRoomJid, 8),
|
||||||
|
progressMessages: loadRecentChatMessages(timelineRoomJid, 40),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user