fix(logs): refine graph preview readability
This commit is contained in:
parent
e777610baa
commit
7c39fbd8e7
3 changed files with 280 additions and 102 deletions
|
|
@ -28,8 +28,8 @@ import type {
|
||||||
const LOG_PREVIEW_FALLBACK_WIDTH = 260;
|
const LOG_PREVIEW_FALLBACK_WIDTH = 260;
|
||||||
const LOG_PREVIEW_FALLBACK_HEIGHT = 292;
|
const LOG_PREVIEW_FALLBACK_HEIGHT = 292;
|
||||||
const NEW_LOG_HIGHLIGHT_MS = 1_000;
|
const NEW_LOG_HIGHLIGHT_MS = 1_000;
|
||||||
const COMPACT_ROW_TEXT_LIMIT = 92;
|
const COMPACT_ROW_TEXT_LIMIT = 76;
|
||||||
const COMPACT_ROW_MIN_PREVIEW_LIMIT = 48;
|
const COMPACT_ROW_MIN_PREVIEW_LIMIT = 40;
|
||||||
|
|
||||||
interface StableRectLike {
|
interface StableRectLike {
|
||||||
left: number;
|
left: number;
|
||||||
|
|
@ -434,18 +434,18 @@ export const GraphMemberLogPreviewHud = ({
|
||||||
? 'border-rose-400/35 bg-rose-950/20 hover:border-rose-300/50 hover:bg-rose-950/30'
|
? 'border-rose-400/35 bg-rose-950/20 hover:border-rose-300/50 hover:bg-rose-950/30'
|
||||||
: 'border-white/10 bg-[rgba(8,14,28,0.52)] hover:border-white/20 hover:bg-[rgba(12,20,40,0.78)]';
|
: 'border-white/10 bg-[rgba(8,14,28,0.52)] hover:border-white/20 hover:bg-[rgba(12,20,40,0.78)]';
|
||||||
const iconClassName = isError
|
const iconClassName = isError
|
||||||
? 'float-left mr-2 inline-flex size-5 shrink-0 items-center justify-center rounded bg-rose-500/10'
|
? 'float-left mr-2 mt-px inline-flex size-5 shrink-0 items-center justify-center rounded bg-rose-500/10'
|
||||||
: 'float-left mr-2 inline-flex size-5 shrink-0 items-center justify-center rounded bg-white/5';
|
: 'float-left mr-2 mt-px inline-flex size-5 shrink-0 items-center justify-center rounded bg-white/5';
|
||||||
const headerClassName = 'inline-flex h-5 items-center align-top';
|
const headerClassName = 'inline align-baseline';
|
||||||
const titleClassName = isError
|
const titleClassName = isError
|
||||||
? 'text-[11px] font-medium leading-[18px] text-rose-100'
|
? 'align-baseline text-[11px] font-medium leading-[18px] text-rose-100'
|
||||||
: 'text-[11px] font-medium leading-[18px] text-slate-200';
|
: 'align-baseline text-[11px] font-medium leading-[18px] text-slate-200';
|
||||||
const timeClassName = isError
|
const timeClassName = isError
|
||||||
? 'ml-1 text-[9px] font-normal leading-[18px] text-rose-300/70'
|
? 'ml-1 align-baseline text-[9px] font-normal leading-[18px] text-rose-300/70'
|
||||||
: 'ml-1 text-[9px] font-normal leading-[18px] text-slate-500';
|
: 'ml-1 align-baseline text-[9px] font-normal leading-[18px] text-slate-500';
|
||||||
const previewClassName = isError
|
const previewClassName = isError
|
||||||
? 'ml-1 break-words align-top text-[10px] leading-[18px] text-rose-100/85'
|
? 'ml-1 break-words align-baseline text-[10px] leading-[18px] text-rose-100/85'
|
||||||
: 'ml-1 break-words align-top text-[10px] leading-[18px] text-slate-300/85';
|
: 'ml-1 break-words align-baseline text-[10px] leading-[18px] text-slate-300/85';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,12 @@ describe('memberLogPreviewExtractor', () => {
|
||||||
message({
|
message({
|
||||||
uuid: 'new',
|
uuid: 'new',
|
||||||
timestamp: '2026-04-01T10:01:00.000Z',
|
timestamp: '2026-04-01T10:01:00.000Z',
|
||||||
content: [{ type: 'text', text: '<system-reminder>latest answer</system-reminder>' }],
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: 'latest answer <system-reminder>hidden reminder text</system-reminder>',
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
@ -51,6 +56,40 @@ describe('memberLogPreviewExtractor', () => {
|
||||||
preview: 'latest answer',
|
preview: 'latest answer',
|
||||||
});
|
});
|
||||||
expect(result.items[1]?.preview).toBe('older answer');
|
expect(result.items[1]?.preview).toBe('older answer');
|
||||||
|
expect(JSON.stringify(result.items)).not.toContain('hidden reminder text');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes agent-only blocks from generic assistant text previews', () => {
|
||||||
|
const result = extractMemberLogPreviewItems({
|
||||||
|
provider: 'opencode_runtime',
|
||||||
|
maxItems: 3,
|
||||||
|
textLimit: 160,
|
||||||
|
messages: [
|
||||||
|
message({
|
||||||
|
uuid: 'assistant-hidden-block',
|
||||||
|
timestamp: '2026-04-01T10:00:00.000Z',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: `Visible user-facing update.
|
||||||
|
|
||||||
|
<info_for_agent>
|
||||||
|
API Error: 500 hidden MCP protocol instructions.
|
||||||
|
</info_for_agent>`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.items[0]).toMatchObject({
|
||||||
|
kind: 'text',
|
||||||
|
title: 'Assistant',
|
||||||
|
preview: 'Visible user-facing update.',
|
||||||
|
tone: 'neutral',
|
||||||
|
});
|
||||||
|
expect(JSON.stringify(result.items)).not.toContain('Hidden MCP protocol');
|
||||||
|
expect(JSON.stringify(result.items)).not.toContain('API Error: 500');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('marks assistant runtime error text as error tone without flagging normal text', () => {
|
it('marks assistant runtime error text as error tone without flagging normal text', () => {
|
||||||
|
|
@ -276,7 +315,7 @@ Reply to this comment using MCP tool task_add_comment.
|
||||||
expect(result.items[0]).toMatchObject({
|
expect(result.items[0]).toMatchObject({
|
||||||
kind: 'tool_result',
|
kind: 'tool_result',
|
||||||
title: 'Message sent',
|
title: 'Message sent',
|
||||||
preview: 'Message sent to team-lead - #abc done',
|
preview: 'to team-lead - #abc done',
|
||||||
});
|
});
|
||||||
expect(result.items).toHaveLength(1);
|
expect(result.items).toHaveLength(1);
|
||||||
expect(JSON.stringify(result.items)).not.toContain('deliveredToInbox');
|
expect(JSON.stringify(result.items)).not.toContain('deliveredToInbox');
|
||||||
|
|
@ -856,6 +895,47 @@ Reply to this comment using MCP tool task_add_comment.
|
||||||
expect(result.items[0]?.preview).not.toContain('[{');
|
expect(result.items[0]?.preview).not.toContain('[{');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('formats primitive task list arrays as task refs instead of empty lists', () => {
|
||||||
|
const result = extractMemberLogPreviewItems({
|
||||||
|
provider: 'claude_transcript',
|
||||||
|
maxItems: 3,
|
||||||
|
textLimit: 160,
|
||||||
|
messages: [
|
||||||
|
message({
|
||||||
|
uuid: 'primitive-list-call',
|
||||||
|
timestamp: '2026-04-01T10:00:00.000Z',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'tool_use',
|
||||||
|
id: 'tool-primitive-list',
|
||||||
|
name: 'mcp__agent-teams__task_list',
|
||||||
|
input: { teamName: 'demo' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
message({
|
||||||
|
uuid: 'primitive-list-result',
|
||||||
|
type: 'user',
|
||||||
|
role: 'user',
|
||||||
|
timestamp: '2026-04-01T10:01:00.000Z',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'tool_result',
|
||||||
|
tool_use_id: 'tool-primitive-list',
|
||||||
|
content: JSON.stringify(['abc12345', 'def67890']),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.items[0]).toMatchObject({
|
||||||
|
kind: 'tool_result',
|
||||||
|
title: 'Task list',
|
||||||
|
preview: '2 tasks - #abc12345; #def67890',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('formats sourceToolUseID result wrappers with text-block content arrays', () => {
|
it('formats sourceToolUseID result wrappers with text-block content arrays', () => {
|
||||||
const result = extractMemberLogPreviewItems({
|
const result = extractMemberLogPreviewItems({
|
||||||
provider: 'claude_transcript',
|
provider: 'claude_transcript',
|
||||||
|
|
@ -1258,6 +1338,84 @@ Reply to this comment using MCP tool task_add_comment.
|
||||||
});
|
});
|
||||||
expect(processResult.items[0]?.preview).not.toContain('[{');
|
expect(processResult.items[0]?.preview).not.toContain('[{');
|
||||||
|
|
||||||
|
const primitiveProcessResult = extractMemberLogPreviewItems({
|
||||||
|
provider: 'opencode_runtime',
|
||||||
|
maxItems: 3,
|
||||||
|
textLimit: 160,
|
||||||
|
messages: [
|
||||||
|
message({
|
||||||
|
uuid: 'primitive-process-call',
|
||||||
|
timestamp: '2026-04-01T10:00:00.000Z',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'tool_use',
|
||||||
|
id: 'tool-primitive-process-list',
|
||||||
|
name: 'agent-teams_process_list',
|
||||||
|
input: { teamName: 'relay-works-10' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
message({
|
||||||
|
uuid: 'primitive-process-result',
|
||||||
|
type: 'user',
|
||||||
|
role: 'user',
|
||||||
|
timestamp: '2026-04-01T10:01:00.000Z',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'tool_result',
|
||||||
|
tool_use_id: 'tool-primitive-process-list',
|
||||||
|
content: ['vite dev', 'pnpm test'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(primitiveProcessResult.items[0]).toMatchObject({
|
||||||
|
kind: 'tool_result',
|
||||||
|
title: 'Process list',
|
||||||
|
preview: '2 processes - vite dev; pnpm test',
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrappedPrimitiveProcessResult = extractMemberLogPreviewItems({
|
||||||
|
provider: 'opencode_runtime',
|
||||||
|
maxItems: 3,
|
||||||
|
textLimit: 160,
|
||||||
|
messages: [
|
||||||
|
message({
|
||||||
|
uuid: 'wrapped-primitive-process-call',
|
||||||
|
timestamp: '2026-04-01T10:00:00.000Z',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'tool_use',
|
||||||
|
id: 'tool-wrapped-primitive-process-list',
|
||||||
|
name: 'agent-teams_process_list',
|
||||||
|
input: { teamName: 'relay-works-10' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
message({
|
||||||
|
uuid: 'wrapped-primitive-process-result',
|
||||||
|
type: 'user',
|
||||||
|
role: 'user',
|
||||||
|
timestamp: '2026-04-01T10:01:00.000Z',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'tool_result',
|
||||||
|
tool_use_id: 'tool-wrapped-primitive-process-list',
|
||||||
|
content: { processes: ['vite dev', 'pnpm test'] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrappedPrimitiveProcessResult.items[0]).toMatchObject({
|
||||||
|
kind: 'tool_result',
|
||||||
|
title: 'Process list',
|
||||||
|
preview: '2 processes - vite dev; pnpm test',
|
||||||
|
});
|
||||||
|
|
||||||
const taskUpdateResult = extractMemberLogPreviewItems({
|
const taskUpdateResult = extractMemberLogPreviewItems({
|
||||||
provider: 'opencode_runtime',
|
provider: 'opencode_runtime',
|
||||||
maxItems: 3,
|
maxItems: 3,
|
||||||
|
|
@ -1321,7 +1479,7 @@ Reply to this comment using MCP tool task_add_comment.
|
||||||
{
|
{
|
||||||
toolName: 'agent-teams_cross_team_list_targets',
|
toolName: 'agent-teams_cross_team_list_targets',
|
||||||
input: { teamName: 'relay-works-10' },
|
input: { teamName: 'relay-works-10' },
|
||||||
result: JSON.stringify([{ teamName: 'qa-team' }, { name: 'design-team' }]),
|
result: JSON.stringify(['qa-team', 'design-team']),
|
||||||
expectedTitle: 'Cross-team targets',
|
expectedTitle: 'Cross-team targets',
|
||||||
expectedPreview: '2 teams - qa-team; design-team',
|
expectedPreview: '2 teams - qa-team; design-team',
|
||||||
},
|
},
|
||||||
|
|
@ -1337,7 +1495,7 @@ Reply to this comment using MCP tool task_add_comment.
|
||||||
{
|
{
|
||||||
toolName: 'agent-teams_process_register',
|
toolName: 'agent-teams_process_register',
|
||||||
input: { label: 'vite dev', command: 'pnpm dev' },
|
input: { label: 'vite dev', command: 'pnpm dev' },
|
||||||
result: { process: { label: 'vite dev', status: 'running' } },
|
result: { pid: 123, process: { label: 'vite dev', status: 'running' } },
|
||||||
expectedTitle: 'Process registered',
|
expectedTitle: 'Process registered',
|
||||||
expectedPreview: 'Registered vite dev running',
|
expectedPreview: 'Registered vite dev running',
|
||||||
},
|
},
|
||||||
|
|
@ -1549,6 +1707,43 @@ Reply to this comment using MCP tool task_add_comment.
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses content block order before mirrored toolCalls for same-message readability', () => {
|
||||||
|
const result = extractMemberLogPreviewItems({
|
||||||
|
provider: 'claude_transcript',
|
||||||
|
maxItems: 3,
|
||||||
|
textLimit: 120,
|
||||||
|
messages: [
|
||||||
|
message({
|
||||||
|
uuid: 'mirrored-tool-call',
|
||||||
|
timestamp: '2026-04-01T10:01:00.000Z',
|
||||||
|
content: [
|
||||||
|
{ type: 'text', text: 'I will inspect the files first.' },
|
||||||
|
{
|
||||||
|
type: 'tool_use',
|
||||||
|
id: 'tool-read',
|
||||||
|
name: 'Read',
|
||||||
|
input: { file_path: 'src/app.ts' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
toolCalls: [
|
||||||
|
{
|
||||||
|
id: 'tool-read',
|
||||||
|
name: 'Read',
|
||||||
|
input: { file_path: 'src/app.ts' },
|
||||||
|
isTask: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.items.map((item) => item.title)).toEqual(['Assistant', 'Read']);
|
||||||
|
expect(result.items[0]).toMatchObject({
|
||||||
|
kind: 'text',
|
||||||
|
preview: 'I will inspect the files first.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('caps preview items at three and reports overflow', () => {
|
it('caps preview items at three and reports overflow', () => {
|
||||||
const result = extractMemberLogPreviewItems({
|
const result = extractMemberLogPreviewItems({
|
||||||
provider: 'claude_transcript',
|
provider: 'claude_transcript',
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ function parseJsonLikeString(value: string): unknown {
|
||||||
}
|
}
|
||||||
|
|
||||||
function truncatePreview(value: string, limit: number): { preview: string; truncated: boolean } {
|
function truncatePreview(value: string, limit: number): { preview: string; truncated: boolean } {
|
||||||
const compact = compactWhitespace(value);
|
const compact = compactWhitespace(removeHiddenInstructionBlocks(value));
|
||||||
if (compact.length <= limit) {
|
if (compact.length <= limit) {
|
||||||
return { preview: compact, truncated: false };
|
return { preview: compact, truncated: false };
|
||||||
}
|
}
|
||||||
|
|
@ -388,7 +388,7 @@ function formatRuntimeErrorText(
|
||||||
value: string,
|
value: string,
|
||||||
limit: number
|
limit: number
|
||||||
): (ValuePreview & { title: string }) | null {
|
): (ValuePreview & { title: string }) | null {
|
||||||
const compact = compactWhitespace(value);
|
const compact = compactWhitespace(removeHiddenInstructionBlocks(value));
|
||||||
if (!compact) {
|
if (!compact) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -642,7 +642,11 @@ function countArrayField(payload: Record<string, unknown>, keys: readonly string
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTaskCollectionItem(task: Record<string, unknown>): string | null {
|
function formatTaskCollectionItem(item: unknown): string | null {
|
||||||
|
const task = asRecord(item);
|
||||||
|
if (!task) {
|
||||||
|
return formatTaskRef(stringifyPrimitive(item));
|
||||||
|
}
|
||||||
const taskRef = taskRefFromPayload(task);
|
const taskRef = taskRefFromPayload(task);
|
||||||
const taskSummary = shortTaskSummary(task);
|
const taskSummary = shortTaskSummary(task);
|
||||||
if (taskRef && taskSummary) return `${taskRef}: ${taskSummary}`;
|
if (taskRef && taskSummary) return `${taskRef}: ${taskSummary}`;
|
||||||
|
|
@ -659,20 +663,17 @@ function formatTaskCollectionArrayPayload(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tasks = items
|
if (items.length === 0) {
|
||||||
.map((item) => asRecord(item))
|
|
||||||
.filter((item): item is Record<string, unknown> => Boolean(item));
|
|
||||||
if (tasks.length === 0) {
|
|
||||||
return {
|
return {
|
||||||
title: canonical === 'task_briefing' ? 'Task briefing' : 'Task list',
|
title: canonical === 'task_briefing' ? 'Task briefing' : 'Task list',
|
||||||
text: '0 tasks',
|
text: '0 tasks',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const taskSummaries = tasks.slice(0, 3).map(formatTaskCollectionItem).filter(Boolean);
|
const taskSummaries = items.slice(0, 3).map(formatTaskCollectionItem).filter(Boolean);
|
||||||
const remainingTaskCount = Math.max(0, tasks.length - taskSummaries.length);
|
const remainingTaskCount = Math.max(0, items.length - taskSummaries.length);
|
||||||
const moreText = remainingTaskCount > 0 ? `; +${remainingTaskCount} more` : '';
|
const moreText = remainingTaskCount > 0 ? `; +${remainingTaskCount} more` : '';
|
||||||
const countText = `${tasks.length} ${tasks.length === 1 ? 'task' : 'tasks'}`;
|
const countText = `${items.length} ${items.length === 1 ? 'task' : 'tasks'}`;
|
||||||
return {
|
return {
|
||||||
title: canonical === 'task_briefing' ? 'Task briefing' : 'Task list',
|
title: canonical === 'task_briefing' ? 'Task briefing' : 'Task list',
|
||||||
text:
|
text:
|
||||||
|
|
@ -713,32 +714,7 @@ function formatProcessCollectionPayload(
|
||||||
(Array.isArray(payload.processes) ? payload.processes : null) ??
|
(Array.isArray(payload.processes) ? payload.processes : null) ??
|
||||||
(Array.isArray(payload.items) ? payload.items : null);
|
(Array.isArray(payload.items) ? payload.items : null);
|
||||||
if (rawProcesses) {
|
if (rawProcesses) {
|
||||||
const processes = rawProcesses
|
return formatProcessCollectionArrayPayload(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']);
|
const processCount = countArrayField(payload, ['processes', 'items']);
|
||||||
|
|
@ -749,12 +725,11 @@ function formatProcessCollectionPayload(
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatProcessCollectionArrayPayload(items: readonly unknown[]): KnownPayloadPreview {
|
function formatProcessCollectionArrayPayload(items: readonly unknown[]): KnownPayloadPreview {
|
||||||
const processes = items
|
const processSummaries = items
|
||||||
.map((item) => asRecord(item))
|
|
||||||
.filter((item): item is Record<string, unknown> => Boolean(item));
|
|
||||||
const processSummaries = processes
|
|
||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
.map((process) => {
|
.map((item) => {
|
||||||
|
const process = asRecord(item);
|
||||||
|
if (!process) return stringifyPrimitive(item);
|
||||||
const label =
|
const label =
|
||||||
stringField(process, 'label') ??
|
stringField(process, 'label') ??
|
||||||
stringField(process, 'name') ??
|
stringField(process, 'name') ??
|
||||||
|
|
@ -764,10 +739,10 @@ function formatProcessCollectionArrayPayload(items: readonly unknown[]): KnownPa
|
||||||
if (label && status) return `${label} ${status}`;
|
if (label && status) return `${label} ${status}`;
|
||||||
return label || status;
|
return label || status;
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter((summary): summary is string => Boolean(summary));
|
||||||
const remainingCount = Math.max(0, processes.length - processSummaries.length);
|
const remainingCount = Math.max(0, items.length - processSummaries.length);
|
||||||
const moreText = remainingCount > 0 ? `; +${remainingCount} more` : '';
|
const moreText = remainingCount > 0 ? `; +${remainingCount} more` : '';
|
||||||
const countText = `${processes.length} ${processes.length === 1 ? 'process' : 'processes'}`;
|
const countText = `${items.length} ${items.length === 1 ? 'process' : 'processes'}`;
|
||||||
return {
|
return {
|
||||||
title: 'Process list',
|
title: 'Process list',
|
||||||
text:
|
text:
|
||||||
|
|
@ -787,14 +762,14 @@ function processLabelFromPayload(
|
||||||
stringField(payload, 'label'),
|
stringField(payload, 'label'),
|
||||||
stringField(payload, 'name'),
|
stringField(payload, 'name'),
|
||||||
stringField(payload, 'command'),
|
stringField(payload, 'command'),
|
||||||
stringField(payload, 'processId'),
|
|
||||||
stringField(payload, 'id'),
|
|
||||||
stringifyPrimitive(payload.pid),
|
|
||||||
stringField(process, 'label'),
|
stringField(process, 'label'),
|
||||||
stringField(process, 'name'),
|
stringField(process, 'name'),
|
||||||
stringField(process, 'command'),
|
stringField(process, 'command'),
|
||||||
|
stringField(payload, 'processId'),
|
||||||
|
stringField(payload, 'id'),
|
||||||
stringField(process, 'processId'),
|
stringField(process, 'processId'),
|
||||||
stringField(process, 'id'),
|
stringField(process, 'id'),
|
||||||
|
stringifyPrimitive(payload.pid),
|
||||||
stringifyPrimitive(process?.pid),
|
stringifyPrimitive(process?.pid),
|
||||||
stringField(fallbackInput ?? undefined, 'label'),
|
stringField(fallbackInput ?? undefined, 'label'),
|
||||||
stringField(fallbackInput ?? undefined, 'name'),
|
stringField(fallbackInput ?? undefined, 'name'),
|
||||||
|
|
@ -882,29 +857,28 @@ function formatCrossTeamCollectionArrayPayload(
|
||||||
: null;
|
: null;
|
||||||
if (!title) return null;
|
if (!title) return null;
|
||||||
|
|
||||||
const records = items
|
const summaries = items
|
||||||
.map((item) => asRecord(item))
|
|
||||||
.filter((item): item is Record<string, unknown> => Boolean(item));
|
|
||||||
const summaries = records
|
|
||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
.map((item) =>
|
.map((item) => {
|
||||||
canonical === 'cross_team_list_targets'
|
const record = asRecord(item);
|
||||||
? formatCrossTeamTargetItem(item)
|
if (!record) return stringifyPrimitive(item);
|
||||||
: formatCrossTeamOutboxItem(item)
|
return canonical === 'cross_team_list_targets'
|
||||||
)
|
? formatCrossTeamTargetItem(record)
|
||||||
.filter(Boolean);
|
: formatCrossTeamOutboxItem(record);
|
||||||
const remainingCount = Math.max(0, records.length - summaries.length);
|
})
|
||||||
|
.filter((summary): summary is string => Boolean(summary));
|
||||||
|
const remainingCount = Math.max(0, items.length - summaries.length);
|
||||||
const moreText = remainingCount > 0 ? `; +${remainingCount} more` : '';
|
const moreText = remainingCount > 0 ? `; +${remainingCount} more` : '';
|
||||||
|
|
||||||
if (canonical === 'cross_team_list_targets') {
|
if (canonical === 'cross_team_list_targets') {
|
||||||
const countText = `${records.length} ${records.length === 1 ? 'team' : 'teams'}`;
|
const countText = `${items.length} ${items.length === 1 ? 'team' : 'teams'}`;
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
text: summaries.length > 0 ? `${countText} - ${summaries.join('; ')}${moreText}` : countText,
|
text: summaries.length > 0 ? `${countText} - ${summaries.join('; ')}${moreText}` : countText,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const countText = `${records.length} ${records.length === 1 ? 'message' : 'messages'}`;
|
const countText = `${items.length} ${items.length === 1 ? 'message' : 'messages'}`;
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
text: summaries.length > 0 ? `${countText} - ${summaries.join('; ')}${moreText}` : countText,
|
text: summaries.length > 0 ? `${countText} - ${summaries.join('; ')}${moreText}` : countText,
|
||||||
|
|
@ -1240,6 +1214,12 @@ function formatErrorPayload(payload: Record<string, unknown>): KnownPayloadPrevi
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stripMessageSentPrefix(value: string): string | null {
|
||||||
|
const compact = compactWhitespace(value);
|
||||||
|
const stripped = compact.replace(/^message sent\b/i, '').trim();
|
||||||
|
return stripped.length > 0 ? stripped : null;
|
||||||
|
}
|
||||||
|
|
||||||
function formatMessageSendPayload(payload: Record<string, unknown>): string | null {
|
function formatMessageSendPayload(payload: Record<string, unknown>): string | null {
|
||||||
const routing = asRecord(payload.routing) ?? undefined;
|
const routing = asRecord(payload.routing) ?? undefined;
|
||||||
const messageRecord = asRecord(payload.message) ?? undefined;
|
const messageRecord = asRecord(payload.message) ?? undefined;
|
||||||
|
|
@ -1251,13 +1231,16 @@ function formatMessageSendPayload(payload: Record<string, unknown>): string | nu
|
||||||
stringField(messageRecord, 'content') ??
|
stringField(messageRecord, 'content') ??
|
||||||
stringField(routing, 'content');
|
stringField(routing, 'content');
|
||||||
|
|
||||||
if (deliveryMessage && summary) return `${deliveryMessage} - ${summary}`;
|
if (deliveryMessage && summary) {
|
||||||
if (summary && target) return `Message sent to ${target} - ${summary}`;
|
const deliveryTarget = stripMessageSentPrefix(deliveryMessage);
|
||||||
|
return deliveryTarget ? `${deliveryTarget} - ${summary}` : summary;
|
||||||
|
}
|
||||||
|
if (summary && target) return `to ${target} - ${summary}`;
|
||||||
if (summary) return summary;
|
if (summary) return summary;
|
||||||
if (deliveryMessage) return deliveryMessage;
|
if (deliveryMessage) return stripMessageSentPrefix(deliveryMessage) ?? deliveryMessage;
|
||||||
if (messageText && target) return `Message sent to ${target} - ${messageText}`;
|
if (messageText && target) return `to ${target} - ${messageText}`;
|
||||||
if (messageText) return messageText;
|
if (messageText) return messageText;
|
||||||
if (target) return `Message sent to ${target}`;
|
if (target) return `to ${target}`;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1284,8 +1267,8 @@ function formatMessageSendResultFromInput(payload: Record<string, unknown>): str
|
||||||
stringField(payload, 'text') ??
|
stringField(payload, 'text') ??
|
||||||
stringField(payload, 'message') ??
|
stringField(payload, 'message') ??
|
||||||
stringField(payload, 'content');
|
stringField(payload, 'content');
|
||||||
if (target && summary) return `Message sent to ${target} - ${summary}`;
|
if (target && summary) return `to ${target} - ${summary}`;
|
||||||
if (target) return `Message sent to ${target}`;
|
if (target) return `to ${target}`;
|
||||||
if (summary) return summary;
|
if (summary) return summary;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -1399,7 +1382,7 @@ function formatPlainToolResultStatus(
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatPlainToolErrorText(value: string, limit: number): ValuePreview | null {
|
function formatPlainToolErrorText(value: string, limit: number): ValuePreview | null {
|
||||||
const compact = compactWhitespace(value);
|
const compact = compactWhitespace(removeHiddenInstructionBlocks(value));
|
||||||
if (!compact) {
|
if (!compact) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -2023,12 +2006,6 @@ function collectToolUseCandidates(input: {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
input.message.toolCalls?.forEach((toolCall, index) => {
|
|
||||||
const normalized = normalizeToolCall(toolCall);
|
|
||||||
if (normalized) {
|
|
||||||
addTool(normalized, 100 + index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (Array.isArray(input.message.content)) {
|
if (Array.isArray(input.message.content)) {
|
||||||
input.message.content.forEach((block, index) => {
|
input.message.content.forEach((block, index) => {
|
||||||
if (!isToolUseBlock(block)) return;
|
if (!isToolUseBlock(block)) return;
|
||||||
|
|
@ -2042,6 +2019,12 @@ function collectToolUseCandidates(input: {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
input.message.toolCalls?.forEach((toolCall, index) => {
|
||||||
|
const normalized = normalizeToolCall(toolCall);
|
||||||
|
if (normalized) {
|
||||||
|
addTool(normalized, 100 + index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
@ -2105,6 +2088,19 @@ function collectToolResultCandidates(input: {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Array.isArray(input.message.content)) {
|
||||||
|
input.message.content.forEach((block, index) => {
|
||||||
|
if (!isToolResultBlock(block)) return;
|
||||||
|
addResult(
|
||||||
|
{
|
||||||
|
toolUseId: block.tool_use_id,
|
||||||
|
content: block.content,
|
||||||
|
isError: block.is_error === true,
|
||||||
|
},
|
||||||
|
index
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
input.message.toolResults?.forEach((result, index) =>
|
input.message.toolResults?.forEach((result, index) =>
|
||||||
addResult(
|
addResult(
|
||||||
{
|
{
|
||||||
|
|
@ -2125,19 +2121,6 @@ function collectToolResultCandidates(input: {
|
||||||
240
|
240
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (Array.isArray(input.message.content)) {
|
|
||||||
input.message.content.forEach((block, index) => {
|
|
||||||
if (!isToolResultBlock(block)) return;
|
|
||||||
addResult(
|
|
||||||
{
|
|
||||||
toolUseId: block.tool_use_id,
|
|
||||||
content: block.content,
|
|
||||||
isError: block.is_error === true,
|
|
||||||
},
|
|
||||||
index
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue