fix(logs): refine runtime preview summaries
This commit is contained in:
parent
0e080abefb
commit
cade7b4fdb
3 changed files with 551 additions and 5 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue