fix(logs): refine runtime preview summaries

This commit is contained in:
777genius 2026-05-07 19:04:37 +03:00
parent 0e080abefb
commit cade7b4fdb
3 changed files with 551 additions and 5 deletions

View file

@ -28,7 +28,7 @@ import type {
const LOG_PREVIEW_FALLBACK_WIDTH = 260;
const LOG_PREVIEW_FALLBACK_HEIGHT = 292;
const NEW_LOG_HIGHLIGHT_MS = 1_000;
const COMPACT_ROW_TEXT_LIMIT = 118;
const COMPACT_ROW_TEXT_LIMIT = 92;
const COMPACT_ROW_MIN_PREVIEW_LIMIT = 48;
interface StableRectLike {

View file

@ -458,6 +458,52 @@ Reply to this comment using MCP tool task_add_comment.
});
});
it('marks plain failed tool-result text as an error when runtime flags are missing', () => {
const result = extractMemberLogPreviewItems({
provider: 'opencode_runtime',
maxItems: 3,
textLimit: 160,
messages: [
message({
uuid: 'read-task-call',
timestamp: '2026-04-01T10:00:00.000Z',
content: [
{
type: 'tool_use',
id: 'tool-task-get',
name: 'agent-teams_task_get',
input: {
taskId: '211e430b-0901-4c9e-9296-2b6e2059a08f',
},
},
],
}),
message({
uuid: 'read-task-result',
type: 'user',
role: 'user',
timestamp: '2026-04-01T10:01:00.000Z',
content: [
{
type: 'tool_result',
tool_use_id: 'tool-task-get',
content:
"Tool 'task_get' execution failed: Task not found: 211e430b-0901-4c9e-9296-2b6e2059a08f",
},
],
}),
],
});
expect(result.items[0]).toMatchObject({
kind: 'tool_result',
title: 'Read task error',
preview:
"Tool 'task_get' execution failed: Task not found: 211e430b-0901-4c9e-9296-2b6e2059a08f",
tone: 'error',
});
});
it('formats orphan comment result payloads without guessing add vs read semantics', () => {
const result = extractMemberLogPreviewItems({
provider: 'claude_transcript',
@ -927,6 +973,292 @@ Reply to this comment using MCP tool task_add_comment.
expect(result.items).toHaveLength(2);
});
it('formats runtime housekeeping previews without leaking internal fields', () => {
const result = extractMemberLogPreviewItems({
provider: 'opencode_runtime',
maxItems: 3,
textLimit: 160,
messages: [
message({
uuid: 'briefing-call',
timestamp: '2026-04-01T10:00:00.000Z',
content: [
{
type: 'tool_use',
id: 'tool-briefing',
name: 'agent-teams_member_briefing',
input: {
teamName: 'relay-works-10',
memberName: 'jack',
runtimeProvider: 'opencode',
},
},
],
}),
message({
uuid: 'briefing-result',
type: 'user',
role: 'user',
timestamp: '2026-04-01T10:01:00.000Z',
content: [
{
type: 'tool_result',
tool_use_id: 'tool-briefing',
content:
'Member briefing for jack on team "relay-works-10" (relay-works-10). Role: developer. CRITICAL: hidden long briefing details.',
},
],
}),
message({
uuid: 'checkin-call',
timestamp: '2026-04-01T10:02:00.000Z',
content: [
{
type: 'tool_use',
id: 'tool-checkin',
name: 'agent-teams_runtime_bootstrap_checkin',
input: {
teamName: 'relay-works-10',
runId: 'run-1',
memberName: 'jack',
runtimeSessionId: 'ses-1',
},
},
],
}),
],
});
expect(result.items[0]).toMatchObject({
kind: 'tool_use',
title: 'Runtime check-in',
preview: 'jack checked in',
});
expect(result.items[1]).toMatchObject({
kind: 'tool_result',
title: 'Member briefing',
preview: 'Loaded briefing for jack',
});
expect(JSON.stringify(result.items)).not.toContain('runtimeSessionId');
expect(JSON.stringify(result.items)).not.toContain('CRITICAL');
const inputOnly = extractMemberLogPreviewItems({
provider: 'opencode_runtime',
maxItems: 3,
textLimit: 160,
messages: [
message({
uuid: 'briefing-input-only',
timestamp: '2026-04-01T10:00:00.000Z',
content: [
{
type: 'tool_use',
id: 'tool-briefing-only',
name: 'agent-teams_member_briefing',
input: {
teamName: 'relay-works-10',
memberName: 'jack',
runtimeProvider: 'opencode',
},
},
],
}),
],
});
expect(inputOnly.items[0]).toMatchObject({
kind: 'tool_use',
title: 'Member briefing',
preview: 'Loaded briefing for jack',
});
const failedBriefing = extractMemberLogPreviewItems({
provider: 'opencode_runtime',
maxItems: 3,
textLimit: 160,
messages: [
message({
uuid: 'briefing-call-failed',
timestamp: '2026-04-01T10:00:00.000Z',
content: [
{
type: 'tool_use',
id: 'tool-briefing-failed',
name: 'agent-teams_member_briefing',
input: {
teamName: 'relay-works-10',
memberName: 'jack',
},
},
],
}),
message({
uuid: 'briefing-result-failed',
type: 'user',
role: 'user',
timestamp: '2026-04-01T10:01:00.000Z',
content: [
{
type: 'tool_result',
tool_use_id: 'tool-briefing-failed',
content: "Tool 'member_briefing' execution failed: runtime session missing",
},
],
}),
],
});
expect(failedBriefing.items[0]).toMatchObject({
kind: 'tool_result',
title: 'Member briefing error',
preview: "Tool 'member_briefing' execution failed: runtime session missing",
tone: 'error',
});
});
it('formats runtime ops, work sync and process previews without internal ids', () => {
const runtimeResult = extractMemberLogPreviewItems({
provider: 'opencode_runtime',
maxItems: 3,
textLimit: 160,
messages: [
message({
uuid: 'heartbeat-call',
timestamp: '2026-04-01T10:00:00.000Z',
content: [
{
type: 'tool_use',
id: 'tool-heartbeat',
name: 'agent-teams_runtime_heartbeat',
input: {
runId: 'run-1',
teamName: 'relay-works-10',
memberName: 'jack',
runtimeSessionId: 'ses-1',
},
},
],
}),
message({
uuid: 'runtime-event-call',
timestamp: '2026-04-01T10:01:00.000Z',
content: [
{
type: 'tool_use',
id: 'tool-runtime-event',
name: 'agent-teams_runtime_task_event',
input: {
memberName: 'jack',
taskId: 'abc12345-0000-0000-0000-000000000000',
event: 'started',
},
},
],
}),
],
});
expect(runtimeResult.items[0]).toMatchObject({
kind: 'tool_use',
title: 'Runtime task event',
preview: 'jack started #abc12345',
});
expect(runtimeResult.items[1]).toMatchObject({
kind: 'tool_use',
title: 'Runtime heartbeat',
preview: 'jack heartbeat',
});
expect(JSON.stringify(runtimeResult.items)).not.toContain('runtimeSessionId');
const workSyncResult = extractMemberLogPreviewItems({
provider: 'opencode_runtime',
maxItems: 3,
textLimit: 160,
messages: [
message({
uuid: 'work-sync-call',
timestamp: '2026-04-01T10:00:00.000Z',
content: [
{
type: 'tool_use',
id: 'tool-work-sync',
name: 'agent-teams_member_work_sync_report',
input: {
memberName: 'jack',
state: 'still_working',
taskIds: ['abc12345-0000-0000-0000-000000000000'],
reportToken: 'secret-token',
},
},
],
}),
message({
uuid: 'work-sync-result',
type: 'user',
role: 'user',
timestamp: '2026-04-01T10:01:00.000Z',
content: [
{
type: 'tool_result',
tool_use_id: 'tool-work-sync',
content: 'ok',
},
],
}),
],
});
expect(workSyncResult.items[0]).toMatchObject({
kind: 'tool_result',
title: 'Work sync report',
preview: 'jack still_working #abc12345',
});
expect(JSON.stringify(workSyncResult.items)).not.toContain('reportToken');
const processResult = extractMemberLogPreviewItems({
provider: 'opencode_runtime',
maxItems: 3,
textLimit: 160,
messages: [
message({
uuid: 'process-call',
timestamp: '2026-04-01T10:00:00.000Z',
content: [
{
type: 'tool_use',
id: 'tool-process-list',
name: 'agent-teams_process_list',
input: { teamName: 'relay-works-10' },
},
],
}),
message({
uuid: 'process-result',
type: 'user',
role: 'user',
timestamp: '2026-04-01T10:01:00.000Z',
content: [
{
type: 'tool_result',
tool_use_id: 'tool-process-list',
content: JSON.stringify([
{ pid: 123, label: 'vite dev', status: 'running' },
{ pid: 456, command: 'pnpm test', status: 'exited' },
]),
},
],
}),
],
});
expect(processResult.items[0]).toMatchObject({
kind: 'tool_result',
title: 'Process list',
preview: '2 processes - vite dev running; pnpm test exited',
});
expect(processResult.items[0]?.preview).not.toContain('[{');
});
it('uses concrete names for generic runtime tool results', () => {
const result = extractMemberLogPreviewItems({
provider: 'opencode_runtime',

View file

@ -469,6 +469,8 @@ function formatToolTitle(toolName: string): string {
if (canonical === 'sendmessage' || canonical === 'message_send') return 'Send message';
if (canonical === 'cross_team_send') return 'Cross-team message';
if (canonical === 'runtime_deliver_message') return 'Runtime delivery';
if (canonical === 'runtime_task_event') return 'Runtime task event';
if (canonical === 'runtime_heartbeat') return 'Runtime heartbeat';
if (canonical === 'task_create' || canonical === 'task_create_from_message') return 'Create task';
if (canonical === 'task_complete') return 'Complete task';
if (canonical === 'task_add_comment') return 'Add comment';
@ -491,6 +493,8 @@ function formatToolTitle(toolName: string): string {
if (canonical === 'review_request_changes') return 'Request changes';
if (canonical === 'runtime_bootstrap_checkin') return 'Runtime check-in';
if (canonical === 'member_briefing') return 'Member briefing';
if (canonical === 'member_work_sync_status') return 'Work sync status';
if (canonical === 'member_work_sync_report') return 'Work sync report';
if (canonical === 'task_add') return 'Add task';
if (canonical === 'task_update') return 'Update task';
if (canonical === 'task_delete') return 'Delete task';
@ -524,7 +528,11 @@ function isToolUseSupersededBySuccessResult(toolName: string): boolean {
canonical === 'cross_team_send' ||
canonical === 'runtime_deliver_message' ||
canonical === 'runtime_bootstrap_checkin' ||
canonical === 'runtime_heartbeat' ||
canonical === 'runtime_task_event' ||
canonical === 'member_briefing' ||
canonical === 'member_work_sync_status' ||
canonical === 'member_work_sync_report' ||
canonical.startsWith('task_') ||
canonical.startsWith('review_')
);
@ -686,6 +694,77 @@ function formatTaskCollectionPayload(payload: Record<string, unknown>): KnownPay
return summary ? { title: 'Task list', text: summary } : null;
}
function formatProcessCollectionPayload(
payload: Record<string, unknown>
): KnownPayloadPreview | null {
const rawProcesses =
(Array.isArray(payload.processes) ? payload.processes : null) ??
(Array.isArray(payload.items) ? payload.items : null);
if (rawProcesses) {
const processes = rawProcesses
.map((item) => asRecord(item))
.filter((item): item is Record<string, unknown> => Boolean(item));
const processSummaries = processes
.slice(0, 3)
.map((process) => {
const label =
stringField(process, 'label') ??
stringField(process, 'name') ??
stringField(process, 'command') ??
stringField(process, 'pid');
const status = stringField(process, 'status');
if (label && status) return `${label} ${status}`;
return label ?? status;
})
.filter(Boolean);
const remainingCount = Math.max(0, processes.length - processSummaries.length);
const moreText = remainingCount > 0 ? `; +${remainingCount} more` : '';
const countText = `${processes.length} ${processes.length === 1 ? 'process' : 'processes'}`;
return {
title: 'Process list',
text:
processSummaries.length > 0
? `${countText} - ${processSummaries.join('; ')}${moreText}`
: countText,
};
}
const processCount = countArrayField(payload, ['processes', 'items']);
if (processCount != null) {
return { title: 'Process list', text: `${processCount} processes` };
}
return null;
}
function formatProcessCollectionArrayPayload(items: readonly unknown[]): KnownPayloadPreview {
const processes = items
.map((item) => asRecord(item))
.filter((item): item is Record<string, unknown> => Boolean(item));
const processSummaries = processes
.slice(0, 3)
.map((process) => {
const label =
stringField(process, 'label') ??
stringField(process, 'name') ??
stringField(process, 'command') ??
stringifyPrimitive(process.pid);
const status = stringField(process, 'status');
if (label && status) return `${label} ${status}`;
return label || status;
})
.filter(Boolean);
const remainingCount = Math.max(0, processes.length - processSummaries.length);
const moreText = remainingCount > 0 ? `; +${remainingCount} more` : '';
const countText = `${processes.length} ${processes.length === 1 ? 'process' : 'processes'}`;
return {
title: 'Process list',
text:
processSummaries.length > 0
? `${countText} - ${processSummaries.join('; ')}${moreText}`
: countText,
};
}
function formatRelationshipPayload(
payload: Record<string, unknown>,
fallbackInput?: Record<string, unknown> | null
@ -766,6 +845,19 @@ function formatTaskToolPayload(
}
if (taskRef) return { title: 'Task created', text: `Created ${taskRef}` };
}
if (canonical === 'task_add') {
if (taskRef && taskSummary) return { title: 'Task added', text: `${taskRef}: ${taskSummary}` };
if (taskRef) return { title: 'Task added', text: `Added ${taskRef}` };
}
if (canonical === 'task_update') {
if (taskRef && status) return { title: 'Task updated', text: `${taskRef} -> ${status}` };
if (taskRef && taskSummary)
return { title: 'Task updated', text: `${taskRef}: ${taskSummary}` };
if (taskRef) return { title: 'Task updated', text: `Updated ${taskRef}` };
}
if (canonical === 'task_delete') {
return taskRef ? { title: 'Task deleted', text: `Deleted ${taskRef}` } : null;
}
if (canonical === 'task_list' || canonical === 'task_briefing') {
const collectionText = formatTaskCollectionPayload(payload);
if (collectionText) {
@ -856,17 +948,38 @@ function formatRuntimePayload(
fallbackInput?: Record<string, unknown> | null
): KnownPayloadPreview | null {
const canonical = canonicalToolNameValue ?? '';
const memberName =
stringField(payload, 'memberName') ??
stringField(payload, 'fromMemberName') ??
stringField(fallbackInput ?? undefined, 'memberName') ??
stringField(fallbackInput ?? undefined, 'fromMemberName');
if (canonical === 'runtime_bootstrap_checkin') {
const memberName =
stringField(payload, 'memberName') ?? stringField(fallbackInput ?? undefined, 'memberName');
return {
title: 'Runtime check-in',
text: memberName ? `${memberName} checked in` : 'Runtime checked in',
};
}
if (canonical === 'runtime_heartbeat') {
return {
title: 'Runtime heartbeat',
text: memberName ? `${memberName} heartbeat` : 'Runtime heartbeat',
};
}
if (canonical === 'runtime_task_event') {
const taskRef = taskRefFromPayload(payload, fallbackInput);
const event =
stringField(payload, 'event') ??
stringField(payload, 'kind') ??
stringField(fallbackInput ?? undefined, 'event') ??
stringField(fallbackInput ?? undefined, 'kind');
const actor = memberName ? `${memberName} ` : '';
if (taskRef && event) {
return { title: 'Runtime task event', text: `${actor}${event} ${taskRef}` };
}
if (taskRef) return { title: 'Runtime task event', text: `${actor}${taskRef}` };
if (event) return { title: 'Runtime task event', text: `${actor}${event}` };
}
if (canonical === 'member_briefing') {
const memberName =
stringField(payload, 'memberName') ?? stringField(fallbackInput ?? undefined, 'memberName');
return {
title: 'Member briefing',
text: memberName ? `Loaded briefing for ${memberName}` : 'Loaded member briefing',
@ -875,6 +988,45 @@ function formatRuntimePayload(
return null;
}
function formatWorkSyncPayload(
payload: Record<string, unknown>,
canonicalToolNameValue: string | null,
fallbackInput?: Record<string, unknown> | null
): KnownPayloadPreview | null {
const canonical = canonicalToolNameValue ?? '';
if (canonical !== 'member_work_sync_status' && canonical !== 'member_work_sync_report') {
return null;
}
const state =
stringField(payload, 'state') ??
stringField(payload, 'status') ??
stringField(fallbackInput ?? undefined, 'state') ??
stringField(fallbackInput ?? undefined, 'status');
const memberName =
stringField(payload, 'memberName') ?? stringField(fallbackInput ?? undefined, 'memberName');
const rawTaskIds = Array.isArray(payload.taskIds)
? payload.taskIds
: Array.isArray(fallbackInput?.taskIds)
? fallbackInput.taskIds
: [];
const taskRefs = [
...new Set(
rawTaskIds
.map((taskId) => (typeof taskId === 'string' ? formatTaskRef(taskId) : null))
.filter((taskRef): taskRef is string => Boolean(taskRef))
),
].slice(0, 3);
const taskText = taskRefs.length > 0 ? ` ${taskRefs.join(', ')}` : '';
const memberText = memberName ? `${memberName} ` : '';
const stateText = state ? `${state}${taskText}` : `updated${taskText}`;
return {
title: canonical === 'member_work_sync_status' ? 'Work sync status' : 'Work sync report',
text: `${memberText}${stateText}`.trim(),
};
}
function formatErrorPayload(payload: Record<string, unknown>): KnownPayloadPreview | null {
if (unknownPayloadLooksLikeError(payload)) {
return { title: 'Tool error', text: payloadErrorMessage(payload) ?? 'Tool reported failure' };
@ -973,6 +1125,19 @@ function formatPlainToolResultStatus(
if (!toolContext) {
return null;
}
if (toolContext.canonicalName === 'member_briefing') {
const memberMatch = /^member briefing for\s+([^\s]+)\s+on team\b/i.exec(
compactWhitespace(value)
);
const memberName =
memberMatch?.[1] ??
stringField(asRecord(toolContext.input), 'memberName') ??
stringField(asRecord(toolContext.input), 'member');
return {
title: 'Member briefing',
text: memberName ? `Loaded briefing for ${memberName}` : 'Loaded member briefing',
};
}
const normalized = compactWhitespace(value).toLowerCase();
if (!['ok', 'done', 'success', 'comment added', 'message sent'].includes(normalized)) {
return null;
@ -986,12 +1151,35 @@ function formatPlainToolResultStatus(
const text = fallbackInput ? formatCrossTeamPayload(fallbackInput) : null;
return text ? { title: 'Cross-team message', text } : null;
}
if (
toolContext.canonicalName === 'member_work_sync_status' ||
toolContext.canonicalName === 'member_work_sync_report'
) {
return formatWorkSyncPayload({}, toolContext.canonicalName, fallbackInput);
}
return (
formatTaskToolPayload({}, toolContext.canonicalName, fallbackInput) ??
formatRuntimePayload({}, toolContext.canonicalName, fallbackInput)
);
}
function formatPlainToolErrorText(value: string, limit: number): ValuePreview | null {
const compact = compactWhitespace(value);
if (!compact) {
return null;
}
const looksLikeError =
/\bexecution failed\b/i.test(compact) ||
/\bfailed without output\b/i.test(compact) ||
/\btool\b[^.]{0,80}\bfailed\b/i.test(compact) ||
/\btask not found\b/i.test(compact) ||
/\bpermission denied\b/i.test(compact) ||
/\b(error|exception|traceback)\s*:/i.test(compact);
return looksLikeError ? { ...truncatePreview(compact, limit), title: 'Tool error' } : null;
}
function formatTaskToolInputPayload(
canonicalToolNameValue: string,
payload: Record<string, unknown>
@ -1076,6 +1264,16 @@ function formatKnownPayloadPreview(
if (runtimeText) {
return runtimeText;
}
const workSyncText = formatWorkSyncPayload(payload, canonical, fallbackInput);
if (workSyncText) {
return workSyncText;
}
if (canonical === 'process_list') {
const processText = formatProcessCollectionPayload(payload);
if (processText) {
return processText;
}
}
if (canonical === 'cross_team_send') {
const crossTeamText = formatCrossTeamPayload(payload);
if (crossTeamText) {
@ -1113,6 +1311,10 @@ function previewUnknownValue(
if (known) {
return { ...truncatePreview(known.text, limit), title: known.title };
}
const plainError = formatPlainToolErrorText(value, limit);
if (plainError) {
return plainError;
}
const plainStatus = formatPlainToolResultStatus(value, toolContext);
if (plainStatus) {
return { ...truncatePreview(plainStatus.text, limit), title: plainStatus.title };
@ -1138,6 +1340,10 @@ function previewUnknownValue(
if (knownCollection) {
return { ...truncatePreview(knownCollection.text, limit), title: knownCollection.title };
}
if (toolContext?.canonicalName === 'process_list') {
const processCollection = formatProcessCollectionArrayPayload(value);
return { ...truncatePreview(processCollection.text, limit), title: processCollection.title };
}
const parts = value
.slice(0, 3)
.map((item) => previewUnknownValue(item, limit, priorityKeys, toolContext).preview)
@ -1192,6 +1398,14 @@ function previewToolInputValue(toolName: string, value: unknown, limit: number):
}
const payload = recordFromUnknown(value);
if (payload) {
const runtimeFormatted = formatRuntimePayload(payload, canonical, payload);
if (runtimeFormatted) {
return truncatePreview(runtimeFormatted.text, limit);
}
const workSyncFormatted = formatWorkSyncPayload(payload, canonical, payload);
if (workSyncFormatted) {
return truncatePreview(workSyncFormatted.text, limit);
}
const taskFormatted = formatTaskToolInputPayload(canonical, payload);
if (taskFormatted) {
return truncatePreview(taskFormatted, limit);