GPU_GUARD_MONOREPO/packages/backend/test/subagent_projection.test.ts

487 lines
14 KiB
TypeScript
Raw Permalink Normal View History

2026-05-20 21:39:12 +08:00
import {
buildSubagentProjectionForEntry,
projectSubagentEntries,
} from '../src/modules/netaclaw/session-tree/subagent_projection.js';
import type { SessionTreeEntry } from '../src/modules/netaclaw/session-tree/types.js';
describe('session tree subagent projection', () => {
it('projects subagent_result entries into canonical panels, evidence, and process events', () => {
const entry: SessionTreeEntry = {
id: 'entry-result',
parentId: 'parent',
timestamp: '2026-04-22T10:00:00.000Z',
type: 'subagent_result',
content: {
batchId: 'batch-1',
status: 'completed',
parentEntryId: 'parent',
results: [
{
id: 11,
sortOrder: 0,
status: 'completed',
goal: '检查桌面图片',
summary: '共有 3 张图片',
resultPayload: {
evidenceSummary: {
kind: 'file-count',
title: '图片文件统计',
summary: '根据工具结果统计,图片文件共 3 个。',
files: ['111.jpg', '11123.jpg', '20260417-185140.jpg'],
count: 3,
},
processEvents: [
{
type: 'run_start',
runId: 'subagent-11',
timestamp: '2026-04-22T10:00:01.000Z',
},
{
type: 'tool_call',
runId: 'subagent-11',
timestamp: '2026-04-22T10:00:02.000Z',
toolCallId: 'call-images',
name: 'bash',
label: '执行命令',
args: { command: 'find ~/Desktop -maxdepth 1 -type f -iname "*.jpg"' },
},
{
type: 'tool_result',
runId: 'subagent-11',
timestamp: '2026-04-22T10:00:03.000Z',
toolCallId: 'call-images',
name: 'bash',
label: '执行命令',
result: '111.jpg\n11123.jpg\n20260417-185140.jpg',
},
],
},
},
],
},
metadata: {},
};
const projection = buildSubagentProjectionForEntry(entry);
expect(projection).toBeTruthy();
expect(projection).toMatchObject({
version: 1,
source: 'backend-projection',
diagnostics: {
projectionPath: 'canonical-backend-projection',
selectedSource: 'subagent_result',
inputSources: ['subagent_result'],
rawResultCount: 1,
dedupedResultCount: 1,
projectedTaskCount: 1,
skippedResultCount: 0,
fallbackUsed: false,
},
});
expect(projection?.taskPanels).toHaveLength(1);
expect(projection?.taskPanels[0]).toMatchObject({
key: '11',
title: '检查桌面图片',
status: 'completed',
sortOrder: 0,
summary: '共有 3 张图片',
});
expect(projection?.taskPanels[0].evidenceSummaries).toHaveLength(1);
expect(projection?.taskPanels[0].toolExecutions).toHaveLength(1);
expect(projection?.taskPanels[0].toolExecutions[0]).toMatchObject({
toolCallId: 'call-images',
status: 'completed',
result: '111.jpg\n11123.jpg\n20260417-185140.jpg',
});
expect(projection?.evidenceSummaries).toHaveLength(1);
expect(projection?.processEvents).toHaveLength(3);
});
it('prefers persisted subagent_result metadata.finalResults when present', () => {
const entry: SessionTreeEntry = {
id: 'entry-result-metadata-final-results',
parentId: 'parent',
timestamp: '2026-04-22T10:30:00.000Z',
type: 'subagent_result',
content: {
batchId: 'batch-1',
status: 'completed',
parentEntryId: 'parent',
results: [
{
id: 11,
sortOrder: 0,
status: 'completed',
goal: '旧 content 任务',
summary: '来自 content.results',
resultPayload: {},
},
],
},
metadata: {
finalResults: [
{
id: 22,
sortOrder: 1,
status: 'completed',
goal: '正式 metadata 任务',
summary: '来自 metadata.finalResults',
resultPayload: {},
},
],
},
};
const projection = buildSubagentProjectionForEntry(entry);
expect(projection?.taskPanels).toHaveLength(1);
expect(projection?.taskPanels[0]).toMatchObject({
key: '22',
title: '正式 metadata 任务',
summary: '来自 metadata.finalResults',
sortOrder: 1,
});
expect(projection?.diagnostics).toMatchObject({
projectionPath: 'canonical-backend-projection',
selectedSource: 'subagent_result',
rawResultCount: 1,
dedupedResultCount: 1,
projectedTaskCount: 1,
fallbackUsed: false,
});
});
it('reads subagent_result top-level metadata evidence and process events through the canonical metadata path', () => {
const entry: SessionTreeEntry = {
id: 'entry-result-metadata-replay',
parentId: 'parent',
timestamp: '2026-04-22T10:40:00.000Z',
type: 'subagent_result',
content: {
batchId: 'batch-metadata-replay',
status: 'completed',
parentEntryId: 'parent',
results: [],
},
metadata: {
finalResults: [
{
id: 51,
sortOrder: 0,
status: 'completed',
goal: 'metadata replay task',
summary: 'metadata result summary',
resultPayload: {},
},
],
evidenceSummaries: [
{
kind: 'tool-preview',
title: 'Metadata Evidence',
summary: 'metadata evidence summary',
},
],
processEvents: [
{
type: 'run_start',
runId: 'subagent-51',
timestamp: '2026-04-22T10:40:01.000Z',
},
],
},
};
const projection = buildSubagentProjectionForEntry(entry);
expect(projection?.taskPanels).toHaveLength(1);
expect(projection?.taskPanels[0]).toMatchObject({
key: '51',
title: 'metadata replay task',
summary: 'metadata result summary',
});
expect(projection?.evidenceSummaries).toEqual([
expect.objectContaining({
title: 'Metadata Evidence',
summary: 'metadata evidence summary',
}),
]);
expect(projection?.processEvents).toEqual([
expect.objectContaining({
type: 'run_start',
runId: 'subagent-51',
}),
]);
});
it('projects assistant delegate_parallel metadata and ignores stale entry metadata when message metadata exists', () => {
const freshPayload = {
delegationMode: 'session-subagent',
taskResults: [
{
task: { sortOrder: 0, goal: 'message metadata task' },
result: {
id: 21,
status: 'completed',
summary: 'from message metadata',
resultPayload: {},
},
},
],
};
const stalePayload = {
delegationMode: 'session-subagent',
taskResults: [
{
task: { sortOrder: 1, goal: 'entry metadata task' },
result: {
id: 22,
status: 'completed',
summary: 'from entry metadata',
resultPayload: {},
},
},
],
};
const entry: SessionTreeEntry = {
id: 'entry-message',
parentId: 'parent',
timestamp: '2026-04-22T11:00:00.000Z',
type: 'message',
message: {
role: 'assistant',
content: 'done',
metadata: {
skillExecutions: [
{
name: 'delegate_parallel',
result: freshPayload,
},
],
},
},
metadata: {
skillExecutions: [
{
name: 'delegate_parallel',
result: stalePayload,
},
],
},
};
const projection = buildSubagentProjectionForEntry(entry);
expect(projection?.taskPanels).toHaveLength(1);
expect(projection?.taskPanels[0]).toMatchObject({
key: '21',
title: 'message metadata task',
summary: 'from message metadata',
});
expect(projection?.diagnostics).toMatchObject({
projectionPath: 'canonical-backend-projection',
selectedSource: 'message_metadata',
inputSources: ['message_metadata', 'entry_metadata'],
rawResultCount: 1,
dedupedResultCount: 1,
projectedTaskCount: 1,
skippedResultCount: 0,
fallbackUsed: false,
ignoredSources: ['entry_metadata'],
});
});
it('exposes projection diagnostics for fallback and dedupe decisions', () => {
const payload = {
delegationMode: 'session-subagent',
taskResults: [
{
task: { sortOrder: 0, goal: 'fallback task' },
result: {
id: 41,
status: 'completed',
summary: 'first',
resultPayload: {},
},
},
{
task: { sortOrder: 0, goal: 'fallback task duplicate' },
result: {
id: 41,
status: 'completed',
summary: 'duplicate',
resultPayload: {},
},
},
{
task: { sortOrder: 2, goal: 'invalid task' },
result: null,
},
],
};
const entry: SessionTreeEntry = {
id: 'entry-fallback-diagnostics',
parentId: 'parent',
timestamp: '2026-04-22T11:30:00.000Z',
type: 'message',
message: {
role: 'assistant',
content: 'done',
metadata: {},
},
metadata: {
skillExecutions: [
{
name: 'delegate_parallel',
result: payload,
},
],
},
};
const projection = buildSubagentProjectionForEntry(entry);
expect(projection?.taskPanels).toHaveLength(1);
expect(projection?.diagnostics).toMatchObject({
projectionPath: 'canonical-backend-projection',
selectedSource: 'entry_metadata',
inputSources: ['entry_metadata'],
rawResultCount: 3,
dedupedResultCount: 1,
projectedTaskCount: 1,
skippedResultCount: 1,
fallbackUsed: true,
ignoredSources: [],
});
});
it('returns entries with metadata.subagentProjection without mutating originals', () => {
const entry: SessionTreeEntry = {
id: 'entry-result',
parentId: 'parent',
timestamp: '2026-04-22T12:00:00.000Z',
type: 'subagent_result',
content: {
batchId: 'batch-2',
status: 'completed',
parentEntryId: 'parent',
results: [
{
id: 31,
status: 'completed',
summary: 'done',
resultPayload: {},
},
],
},
};
const projected = projectSubagentEntries([entry]);
expect(projected).not.toBe([entry]);
expect(projected[0]).not.toBe(entry);
expect(entry.metadata).toBeUndefined();
expect(projected[0].metadata?.subagentProjection).toMatchObject({
version: 1,
source: 'backend-projection',
taskPanels: [
{
key: '31',
summary: 'done',
},
],
});
});
it('exposes replay limits and truncation state when projection data is clipped', () => {
const entry: SessionTreeEntry = {
id: 'entry-clipped-result',
parentId: 'parent',
timestamp: '2026-04-22T13:00:00.000Z',
type: 'subagent_result',
content: {
batchId: 'batch-clipped',
status: 'completed',
parentEntryId: 'parent',
results: Array.from({ length: 10 }, (_, index) => ({
id: `result-${index}`,
sortOrder: index,
status: 'completed',
summary: `summary ${index}`,
resultPayload: {
evidenceSummary: {
kind: 'tool-preview',
title: `Evidence ${index}`,
summary: `Evidence summary ${index}`,
},
processEvents: Array.from({ length: index === 0 ? 55 : 0 }, (_event, eventIndex) => ({
type: 'log',
runId: 'subagent-clipped',
text: `event ${eventIndex}`,
})),
},
})),
},
};
const projection = buildSubagentProjectionForEntry(entry);
expect(projection).toMatchObject({
replay: {
limits: {
evidenceSummaries: 8,
processEvents: 50,
taskProcessEvents: 50,
toolExecutions: 24,
},
truncated: {
evidenceSummaries: true,
processEvents: true,
taskPanels: false,
toolExecutions: false,
},
},
});
expect(projection?.evidenceSummaries).toHaveLength(8);
expect(projection?.processEvents).toHaveLength(50);
expect(projection?.taskPanels[0].processEvents).toHaveLength(50);
});
it('marks tool execution truncation only when raw executions exceed the limit', () => {
const buildEntry = (toolCallCount: number): SessionTreeEntry => ({
id: `entry-tool-executions-${toolCallCount}`,
parentId: 'parent',
timestamp: '2026-04-22T14:00:00.000Z',
type: 'subagent_result',
content: {
batchId: `batch-tool-executions-${toolCallCount}`,
status: 'completed',
parentEntryId: 'parent',
results: [
{
id: `result-tool-executions-${toolCallCount}`,
status: 'completed',
summary: 'tool execution count',
resultPayload: {
processEvents: Array.from({ length: toolCallCount }, (_event, index) => ({
type: 'tool_call',
runId: 'subagent-tools',
toolCallId: `call-${index}`,
name: 'bash',
})),
},
},
],
},
});
const exactlyAtLimit = buildSubagentProjectionForEntry(buildEntry(24));
const aboveLimit = buildSubagentProjectionForEntry(buildEntry(25));
expect(exactlyAtLimit?.taskPanels[0].toolExecutions).toHaveLength(24);
expect(exactlyAtLimit?.replay.truncated.toolExecutions).toBe(false);
expect(aboveLimit?.taskPanels[0].toolExecutions).toHaveLength(24);
expect(aboveLimit?.replay.truncated.toolExecutions).toBe(true);
});
});