import assert from 'node:assert/strict';
import test from 'node:test';
import {
	buildSubagentRuntimeMap,
	extractToolRuntimeRoutesByBatch,
	getNormalizedSubagentBatchMetadata,
} from './subagent_batch_runtime';
import {
	buildSubagentToolExecutions,
	describeSubagentProjectionDiagnostics,
	describeContinueFeedback,
	describePendingSendGuard,
	describeEntryDisplay,
	describeSelectionState,
	describeComposerTarget,
	describeTreeNodeBadges,
	extractSubagentProjectionDiagnosticsByEntry,
	extractTreeNodeContextMeta,
	extractSubagentTaskPanelsByEntry,
	extractSubagentEvidenceSummariesByEntry,
	extractSubagentProcessEventsByEntry,
	getNormalizedSubagentResultMetadata,
	summarizeBranchEntry,
	summarizeCompactionEntry,
	summarizeEntryTitle,
} from './subagent_projection';
import type { SessionTreeEntry } from '../types/index.d';

test('getNormalizedSubagentBatchMetadata reads canonical batch metadata arrays', () => {
	const entry: SessionTreeEntry = {
		id: 'batch-entry',
		parentId: 'parent-entry',
		timestamp: '2026-04-22T11:00:00.000Z',
		type: 'subagent_batch',
		content: {
			batchId: 'batch-1',
			status: 'completed',
			mode: 'parallel',
			tasks: [],
		},
		metadata: {
			events: [
				{
					timestamp: '2026-04-22T11:00:01.000Z',
					sequence: 1,
					event: {
						status: 'running',
					},
				},
			],
			finalResults: [
				{
					id: 'result-1',
					status: 'completed',
				},
			],
			toolRuntimeRoutes: [
				{
					name: 'bash',
					kind: 'builtin',
					executionMode: 'parallel',
					supportedInWorker: true,
					workerRoutingHint: 'local',
					runtimeRoute: 'worker-local',
				},
			],
		},
	};

	const metadata = getNormalizedSubagentBatchMetadata(entry);

	assert.equal(metadata.events.length, 1);
	assert.equal(metadata.finalResults.length, 1);
	assert.equal(metadata.toolRuntimeRoutes.length, 1);
	assert.equal(metadata.events[0].sequence, 1);
	assert.equal(metadata.finalResults[0].id, 'result-1');
	assert.equal(metadata.toolRuntimeRoutes[0].runtimeRoute, 'worker-local');
});

test('buildSubagentRuntimeMap uses normalized batch metadata for events and finalResults', () => {
	const entry: SessionTreeEntry = {
		id: 'batch-entry-runtime',
		parentId: 'parent-entry',
		timestamp: '2026-04-22T11:05:00.000Z',
		type: 'subagent_batch',
		content: {
			batchId: 'batch-2',
			status: 'completed',
			mode: 'parallel',
			tasks: [
				{
					id: 'task-1',
					goal: 'check desktop images',
				},
			],
		},
		metadata: {
			events: [
				{
					timestamp: '2026-04-22T11:05:01.000Z',
					sequence: 3,
					event: {
						status: 'completed',
					},
				},
			],
			finalResults: [
				{
					id: 'result-2',
					status: 'completed',
					summary: '3 images found',
				},
			],
		},
	};

	const runtimeByBatch = buildSubagentRuntimeMap([entry]);

	assert.equal(runtimeByBatch['batch-2'].events.length, 1);
	assert.equal(runtimeByBatch['batch-2'].events[0].sequence, 3);
	assert.equal(runtimeByBatch['batch-2'].results.length, 1);
	assert.equal(runtimeByBatch['batch-2'].results[0].id, 'result-2');
});

test('extractToolRuntimeRoutesByBatch uses normalized batch metadata routes', () => {
	const entry: SessionTreeEntry = {
		id: 'batch-entry-routes',
		parentId: 'parent-entry',
		timestamp: '2026-04-22T11:10:00.000Z',
		type: 'subagent_batch',
		content: {
			batchId: 'batch-3',
			status: 'running',
			mode: 'parallel',
			tasks: [],
		},
		metadata: {
			toolRuntimeRoutes: [
				{
					name: 'find_files',
					kind: 'builtin',
					executionMode: 'parallel',
					supportedInWorker: true,
					workerRoutingHint: 'local',
					runtimeRoute: 'worker-local',
				},
			],
		},
	};

	const routesByBatch = extractToolRuntimeRoutesByBatch([entry]);

	assert.equal(routesByBatch['batch-3'].length, 1);
	assert.equal(routesByBatch['batch-3'][0].name, 'find_files');
	assert.equal(routesByBatch['batch-3'][0].runtimeRoute, 'worker-local');
});

test('getNormalizedSubagentResultMetadata reads canonical result metadata arrays', () => {
	const entry: SessionTreeEntry = {
		id: 'result-entry',
		parentId: 'parent-entry',
		timestamp: '2026-04-22T11:15:00.000Z',
		type: 'subagent_result',
		content: {
			batchId: 'batch-result-1',
			status: 'completed',
			results: [],
		},
		metadata: {
			evidenceSummaries: [
				{
					kind: 'tool-preview',
					title: 'Preview',
					summary: 'summary from metadata',
				},
			],
			processEvents: [
				{
					type: 'run_start',
					runId: 'subagent-1',
				},
			],
			finalResults: [
				{
					id: 'final-result-1',
					status: 'completed',
				},
			],
		},
	};

	const metadata = getNormalizedSubagentResultMetadata(entry);

	assert.equal(metadata.evidenceSummaries.length, 1);
	assert.equal(metadata.processEvents.length, 1);
	assert.equal(metadata.finalResults.length, 1);
	assert.equal(metadata.evidenceSummaries[0].title, 'Preview');
	assert.equal(metadata.processEvents[0].type, 'run_start');
	assert.equal(metadata.finalResults[0].id, 'final-result-1');
});

test('extractSubagentEvidenceSummariesByEntry uses normalized result metadata summaries', () => {
	const entry: SessionTreeEntry = {
		id: 'result-entry-summaries',
		parentId: 'parent-entry',
		timestamp: '2026-04-22T11:20:00.000Z',
		type: 'subagent_result',
		content: {
			batchId: 'batch-result-2',
			status: 'completed',
			results: [],
		},
		metadata: {
			evidenceSummaries: [
				{
					kind: 'tool-preview',
					title: 'Metadata summary',
					summary: 'from canonical metadata',
				},
			],
		},
	};

	const summariesByEntry = extractSubagentEvidenceSummariesByEntry([entry]);

	assert.equal(summariesByEntry[entry.id].length, 1);
	assert.equal(summariesByEntry[entry.id][0].title, 'Metadata summary');
});

test('extractSubagentProcessEventsByEntry uses normalized result metadata events', () => {
	const entry: SessionTreeEntry = {
		id: 'result-entry-events',
		parentId: 'parent-entry',
		timestamp: '2026-04-22T11:25:00.000Z',
		type: 'subagent_result',
		content: {
			batchId: 'batch-result-3',
			status: 'completed',
			results: [],
		},
		metadata: {
			processEvents: [
				{
					type: 'run_start',
					runId: 'subagent-3',
				},
				{
					type: 'run_end',
					runId: 'subagent-3',
				},
			],
		},
	};

	const eventsByEntry = extractSubagentProcessEventsByEntry([entry]);

	assert.deepEqual(eventsByEntry[entry.id].map(item => item.type), ['run_start', 'run_end']);
});

test('summarizeBranchEntry derives summary source label and truncated title', () => {
	const entry: SessionTreeEntry = {
		id: 'branch-1',
		parentId: 'message-1',
		timestamp: '2026-04-22T10:00:00.000Z',
		type: 'branch_summary',
		fromId: 'abcdefghi',
		summary: '这是一个非常长的分支摘要，用来验证标题会被截断，同时完整摘要仍然保留给正文区域展示，避免视图层再次拼装字符串，并且保证会超过七十二个字符，以便稳定触发省略展示逻辑。',
	};

	const result = summarizeBranchEntry(entry);

	assert.equal(result.sourceLabel, 'abcdefgh');
	assert.equal(result.summary, entry.summary);
	assert.match(result.title, /\.\.\.$/);
	assert.ok(result.title.length <= 72);
	assert.equal(describeEntryDisplay(entry), result.title);
});

test('summarizeCompactionEntry derives token and boundary labels from entry metadata', () => {
	const entry: SessionTreeEntry = {
		id: 'compaction-1',
		parentId: 'message-1',
		timestamp: '2026-04-22T10:00:00.000Z',
		type: 'compaction',
		tokensBefore: 1200,
		firstKeptEntryId: 'entry-keep-1234',
		metadata: {
			tokensAfter: 320,
		},
		summary: '压缩后的摘要',
	};

	const result = summarizeCompactionEntry(entry);

	assert.equal(result.summary, '压缩后的摘要');
	assert.equal(result.tokenLabel, '1200 -> 320');
	assert.equal(result.boundaryLabel, 'entry-ke');
	assert.equal(result.title, '压缩 1200 -> 320');
	assert.equal(describeEntryDisplay(entry), result.title);
});

test('describeSubagentProjectionDiagnostics returns stable labels for view consumption', () => {
	const result = describeSubagentProjectionDiagnostics({
		selectedSource: 'tool_message',
		inputSources: ['tool_message', 'entry_metadata'],
		ignoredSources: ['subagent_result'],
		rawResultCount: 7,
		dedupedResultCount: 5,
		projectedTaskCount: 3,
		skippedResultCount: 2,
		fallbackUsed: true,
	});

	assert.equal(result.summary, '工具消息 / 3 个任务');
	assert.equal(result.selectedSourceLabel, '工具消息');
	assert.equal(result.inputSourcesLabel, '工具消息 / 条目元数据');
	assert.equal(result.ignoredSourcesLabel, '结果节点');
});

test('describeSubagentProjectionDiagnostics exposes projection path labels', () => {
	const legacyPayload = describeSubagentProjectionDiagnostics({
		selectedSource: 'tool_message',
		inputSources: ['tool_message', 'entry_metadata'],
		ignoredSources: ['subagent_result'],
		rawResultCount: 7,
		dedupedResultCount: 5,
		projectedTaskCount: 3,
		skippedResultCount: 2,
		fallbackUsed: true,
		projectionPath: 'legacy-payload-fallback',
	});
	const canonical = describeSubagentProjectionDiagnostics({
		selectedSource: 'subagent_result',
		inputSources: ['subagent_result'],
		ignoredSources: [],
		rawResultCount: 2,
		dedupedResultCount: 2,
		projectedTaskCount: 2,
		skippedResultCount: 0,
		fallbackUsed: false,
		projectionPath: 'canonical-backend-projection',
	});
	const metadataOnly = describeSubagentProjectionDiagnostics({
		selectedSource: 'subagent_result',
		inputSources: ['subagent_result'],
		ignoredSources: [],
		rawResultCount: 0,
		dedupedResultCount: 0,
		projectedTaskCount: 0,
		skippedResultCount: 0,
		fallbackUsed: true,
		projectionPath: 'metadata-only-fallback',
	});

	assert.equal(legacyPayload.projectionPathLabel, '旧委派载荷 fallback');
	assert.equal(canonical.projectionPathLabel, '后端规范投影');
	assert.equal(metadataOnly.projectionPathLabel, '仅 metadata fallback');
});

test('extractSubagentTaskPanelsByEntry sorts panels by sortOrder and preserves task-level projections', () => {
	const entry: SessionTreeEntry = {
		id: 'entry-subagent-result',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T10:00:00.000Z',
		type: 'subagent_result',
		content: {
			batchId: 'batch-1',
			status: 'completed',
			parentEntryId: 'entry-parent',
			results: [
				{
					id: 'task-b',
					sortOrder: 1,
					status: 'completed',
					goal: 'second task',
					summary: 'done second',
					resultPayload: {
						evidenceSummary: {
							kind: 'tool-preview',
							title: 'Preview',
							summary: 'preview second',
						},
						processEvents: [
							{
								type: 'tool_call',
								runId: 'subagent-b',
								toolCallId: 'call-b',
								name: 'bash',
								args: { cmd: 'echo second' },
							},
							{
								type: 'tool_result',
								runId: 'subagent-b',
								toolCallId: 'call-b',
								name: 'bash',
								result: 'second',
							},
						],
					},
				},
				{
					id: 'task-a',
					sortOrder: 0,
					status: 'failed',
					goal: 'first task',
					error: 'boom',
					resultPayload: {
						processEvents: [
							{
								type: 'run_error',
								runId: 'subagent-a',
								error: 'boom',
							},
						],
					},
				},
			],
		},
		metadata: {},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 2);
	assert.equal(panels[0].title, 'first task');
	assert.equal(panels[0].status, 'failed');
	assert.equal(panels[0].error, 'boom');
	assert.equal(panels[1].title, 'second task');
	assert.equal(panels[1].toolExecutions.length, 1);
	assert.equal(panels[1].toolExecutions[0].status, 'completed');
	assert.deepEqual(panels[1].toolExecutions[0].args, { cmd: 'echo second' });
	assert.equal(panels[1].toolExecutions[0].result, 'second');
});

test('buildSubagentToolExecutions pairs tool calls, tool results, and proxy calls by toolCallId', () => {
	const executions = buildSubagentToolExecutions([
		{
			type: 'tool_call',
			runId: 'subagent-1',
			toolCallId: 'call-1',
			name: 'search',
			args: { q: 'neta' },
		},
		{
			type: 'proxy_tool_call',
			runId: 'subagent-1',
			toolCallId: 'call-2',
			name: 'write_file',
			args: { path: 'a.txt' },
		},
		{
			type: 'tool_result',
			runId: 'subagent-1',
			toolCallId: 'call-1',
			name: 'search',
			result: { items: 3 },
		},
	]);

	assert.equal(executions.length, 2);
	assert.equal(executions[0].toolCallId, 'call-1');
	assert.equal(executions[0].status, 'completed');
	assert.deepEqual(executions[0].result, { items: 3 });
	assert.equal(executions[1].toolCallId, 'call-2');
	assert.equal(executions[1].kind, 'proxy_tool_call');
	assert.equal(executions[1].status, 'running');
	assert.deepEqual(executions[1].args, { path: 'a.txt' });
});

test('extractSubagentTaskPanelsByEntry falls back to array order and preserves completed results without tool_call', () => {
	const entry: SessionTreeEntry = {
		id: 'entry-subagent-result-fallback',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T11:00:00.000Z',
		type: 'subagent_result',
		content: {
			batchId: 'batch-2',
			status: 'completed',
			parentEntryId: 'entry-parent',
			results: [
				{
					id: 'task-no-order',
					status: 'completed',
					summary: 'generated answer',
					resultPayload: {
						processEvents: [
							{
								type: 'tool_result',
								runId: 'subagent-c',
								toolCallId: 'call-c',
								name: 'search',
								result: { items: 1 },
							},
						],
					},
				},
				{
					id: 'task-with-order',
					sortOrder: 5,
					status: 'completed',
					goal: 'later task',
					resultPayload: {},
				},
			],
		},
		metadata: {},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 2);
	assert.equal(panels[0].key, 'task-no-order');
	assert.equal(panels[0].title, 'generated answer');
	assert.equal(panels[0].sortOrder, 0);
	assert.equal(panels[0].toolExecutions.length, 1);
	assert.equal(panels[0].toolExecutions[0].toolCallId, 'call-c');
	assert.equal(panels[0].toolExecutions[0].status, 'completed');
	assert.deepEqual(panels[0].toolExecutions[0].result, { items: 1 });
	assert.equal(panels[1].key, 'task-with-order');
	assert.equal(panels[1].sortOrder, 5);
});

test('extractSubagentTaskPanelsByEntry folds run_error into the latest unfinished tool execution', () => {
	const entry: SessionTreeEntry = {
		id: 'entry-subagent-result-run-error',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T12:00:00.000Z',
		type: 'subagent_result',
		content: {
			batchId: 'batch-3',
			status: 'completed',
			parentEntryId: 'entry-parent',
			results: [
				{
					id: 'task-run-error',
					status: 'failed',
					goal: 'broken task',
					resultPayload: {
						processEvents: [
							{
								type: 'tool_call',
								runId: 'subagent-d',
								timestamp: '2026-04-22T12:00:01.000Z',
								toolCallId: 'call-d',
								name: 'bash',
								args: { cmd: 'exit 1' },
							},
							{
								type: 'run_error',
								runId: 'subagent-d',
								timestamp: '2026-04-22T12:00:02.000Z',
								error: 'command failed',
							},
							{
								type: 'log',
								runId: 'subagent-d',
								timestamp: '2026-04-22T12:00:03.000Z',
								text: 'after failure',
							},
						],
					},
				},
			],
		},
		metadata: {},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 1);
	assert.equal(panels[0].toolExecutions.length, 1);
	assert.equal(panels[0].toolExecutions[0].toolCallId, 'call-d');
	assert.equal(panels[0].toolExecutions[0].status, 'completed');
	assert.equal(panels[0].toolExecutions[0].error, 'command failed');
	assert.equal(panels[0].toolExecutions[0].finishedAt, '2026-04-22T12:00:02.000Z');
	assert.equal(panels[0].processEvents.length, 1);
	assert.equal(panels[0].processEvents[0].type, 'log');
});

test('extractSubagentTaskPanelsByEntry projects delegated tool message taskResults', () => {
	const entry: SessionTreeEntry = {
		id: 'entry-delegate-tool-result',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T13:00:00.000Z',
		type: 'message',
		message: {
			role: 'tool',
			content: JSON.stringify({
				delegationMode: 'session-subagent',
				taskResults: [
					{
						task: {
							sortOrder: 0,
							goal: '统计桌面根目录的图片数量和文件名',
							mode: 'preset',
						},
						result: {
							id: 89,
							status: 'completed',
							summary: '图片数量：3 个',
							resultPayload: {
								finalContent: '图片数量：3 个',
								processEvents: [
									{
										type: 'run_start',
										runId: 'subagent-89',
										timestamp: '2026-04-22T09:11:59.712Z',
									},
									{
										type: 'tool_call',
										runId: 'subagent-89',
										timestamp: '2026-04-22T09:12:08.128Z',
										toolCallId: 'call-images',
										name: 'bash',
										label: '执行命令',
										args: { command: 'find ~/Desktop -maxdepth 1 -type f -iname "*.jpg"' },
									},
									{
										type: 'tool_result',
										runId: 'subagent-89',
										timestamp: '2026-04-22T09:12:08.496Z',
										toolCallId: 'call-images',
										name: 'bash',
										label: '执行命令',
										result: '111.jpg\n11123.jpg\n20260417-185140.jpg',
									},
									{
										type: 'run_end',
										runId: 'subagent-89',
										timestamp: '2026-04-22T09:12:18.333Z',
									},
								],
							},
						},
					},
				],
			}),
		},
		metadata: {},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 1);
	assert.equal(panels[0].title, '统计桌面根目录的图片数量和文件名');
	assert.equal(panels[0].summary, '图片数量：3 个');
	assert.equal(panels[0].sortOrder, 0);
	assert.equal(panels[0].toolExecutions.length, 1);
	assert.equal(panels[0].toolExecutions[0].toolCallId, 'call-images');
	assert.equal(panels[0].toolExecutions[0].startedAt, '2026-04-22T09:12:08.128Z');
	assert.equal(panels[0].toolExecutions[0].finishedAt, '2026-04-22T09:12:08.496Z');
	assert.equal(panels[0].processEvents.length, 2);
	assert.equal(panels[0].processEvents[0].type, 'run_start');
	assert.equal(panels[0].processEvents[1].type, 'run_end');
});

test('extractSubagentTaskPanelsByEntry projects delegated assistant skillExecutions', () => {
	const delegatedPayload = {
		delegationMode: 'session-subagent',
		taskResults: [
			{
				task: {
					sortOrder: 1,
					goal: '检查桌面根目录的xlsx文件',
					mode: 'preset',
				},
				result: {
					id: 93,
					status: 'completed',
					summary: '桌面根目录下共有5个xlsx文件',
					resultPayload: {
						processEvents: [
							{
								type: 'run_start',
								runId: 'subagent-93',
								timestamp: '2026-04-22T09:30:07.348Z',
							},
							{
								type: 'tool_call',
								runId: 'subagent-93',
								timestamp: '2026-04-22T09:30:14.507Z',
								toolCallId: 'call-xlsx',
								name: 'find_files',
								label: '查找文件',
								args: { path: '~/Desktop', pattern: '*.xlsx' },
							},
							{
								type: 'tool_result',
								runId: 'subagent-93',
								timestamp: '2026-04-22T09:30:15.437Z',
								toolCallId: 'call-xlsx',
								name: 'find_files',
								label: '查找文件',
								result: '2.26加入服务期限.xlsx\n3.xlsx',
							},
							{
								type: 'run_end',
								runId: 'subagent-93',
								timestamp: '2026-04-22T09:30:20.891Z',
							},
						],
					},
				},
			},
		],
	};
	const entry: SessionTreeEntry = {
		id: 'entry-assistant-delegate-result',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T13:30:00.000Z',
		type: 'message',
		message: {
			role: 'assistant',
			content: '检查完成',
			metadata: {
				skillExecutions: [
					{
						name: 'delegate_parallel',
						label: 'Delegate Parallel',
						status: 'done',
						result: JSON.stringify(delegatedPayload),
					},
				],
			},
		},
		metadata: {},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 1);
	assert.equal(panels[0].title, '检查桌面根目录的xlsx文件');
	assert.equal(panels[0].summary, '桌面根目录下共有5个xlsx文件');
	assert.equal(panels[0].toolExecutions.length, 1);
	assert.equal(panels[0].toolExecutions[0].toolCallId, 'call-xlsx');
	assert.equal(panels[0].toolExecutions[0].startedAt, '2026-04-22T09:30:14.507Z');
	assert.equal(panels[0].toolExecutions[0].finishedAt, '2026-04-22T09:30:15.437Z');
});

test('extractSubagentTaskPanelsByEntry projects assistant delegate_parallel taskResults with process events', () => {
	const delegatedPayload = {
		delegationMode: 'session-subagent',
		taskResults: [
			{
				sortOrder: 0,
				task: {
					sortOrder: 0,
					goal: 'check desktop root images',
					agentName: 'agent-images',
				},
				result: {
					id: 98,
					status: 'completed',
					summary: '3 images found',
					resultPayload: {
						processEvents: [
							{
								type: 'run_start',
								runId: 'subagent-98',
								timestamp: '2026-04-22T09:44:14.518Z',
							},
							{
								type: 'tool_call',
								runId: 'subagent-98',
								timestamp: '2026-04-22T09:44:26.644Z',
								toolCallId: 'call-images-failed',
								name: 'find_files',
								label: 'find files',
								args: { path: '/c/Users/lixin/Desktop', pattern: '*.{jpg,jpeg,png}' },
							},
							{
								type: 'tool_result',
								runId: 'subagent-98',
								timestamp: '2026-04-22T09:44:26.644Z',
								toolCallId: 'call-images-failed',
								name: 'find_files',
								label: 'find files',
								result: "find_files failed: ENOENT: no such file or directory, stat 'C:\\c\\Users\\lixin\\Desktop'",
							},
							{
								type: 'tool_call',
								runId: 'subagent-98',
								timestamp: '2026-04-22T09:44:29.378Z',
								toolCallId: 'call-images-ok',
								name: 'bash',
								label: 'run command',
								args: { command: 'find ~/Desktop -maxdepth 1 -type f' },
							},
							{
								type: 'tool_result',
								runId: 'subagent-98',
								timestamp: '2026-04-22T09:44:29.716Z',
								toolCallId: 'call-images-ok',
								name: 'bash',
								label: 'run command',
								result: '/c/Users/lixin/Desktop/111.jpg\n/c/Users/lixin/Desktop/11123.jpg\n/c/Users/lixin/Desktop/20260417-185140.jpg',
							},
							{
								type: 'run_end',
								runId: 'subagent-98',
								timestamp: '2026-04-22T09:44:31.572Z',
							},
						],
					},
				},
			},
			{
				sortOrder: 1,
				task: {
					sortOrder: 1,
					goal: 'check desktop root xlsx',
					agentName: 'agent-xlsx',
				},
				result: {
					id: 97,
					status: 'completed',
					summary: '5 xlsx files found',
					resultPayload: {
						processEvents: [
							{
								type: 'run_start',
								runId: 'subagent-97',
								timestamp: '2026-04-22T09:44:14.509Z',
							},
							{
								type: 'tool_call',
								runId: 'subagent-97',
								timestamp: '2026-04-22T09:44:20.608Z',
								toolCallId: 'call-xlsx',
								name: 'find_files',
								label: 'find files',
								args: { path: '~/Desktop', pattern: '*.xlsx' },
							},
							{
								type: 'tool_result',
								runId: 'subagent-97',
								timestamp: '2026-04-22T09:44:21.710Z',
								toolCallId: 'call-xlsx',
								name: 'find_files',
								label: 'find files',
								result: '2.26加入服务期限.xlsx\n2月客服部-何佳菁.xlsx\n3.xlsx\n3月客服部-何佳菁.xlsx\n阿维塔正式结婚证核查结果（2026.4.27）.xlsx',
							},
							{
								type: 'run_end',
								runId: 'subagent-97',
								timestamp: '2026-04-22T09:44:27.258Z',
							},
						],
					},
				},
			},
		],
	};
	const entry: SessionTreeEntry = {
		id: 'entry-assistant-delegate-parallel-1744',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T09:44:40.000Z',
		type: 'message',
		message: {
			role: 'assistant',
			content: 'done',
			metadata: {
				skillExecutions: [
					{
						name: 'delegate_parallel',
						label: 'Delegate Parallel',
						status: 'done',
						result: delegatedPayload,
					},
				],
			},
		},
		metadata: {},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 2);
	assert.equal(panels[0].title, 'check desktop root images');
	assert.equal(panels[0].toolExecutions.length, 2);
	assert.equal(panels[0].toolExecutions[0].toolCallId, 'call-images-failed');
	assert.equal(panels[0].toolExecutions[0].startedAt, '2026-04-22T09:44:26.644Z');
	assert.equal(panels[0].toolExecutions[0].finishedAt, '2026-04-22T09:44:26.644Z');
	assert.equal(panels[0].toolExecutions[1].toolCallId, 'call-images-ok');
	assert.equal(panels[0].toolExecutions[1].result, '/c/Users/lixin/Desktop/111.jpg\n/c/Users/lixin/Desktop/11123.jpg\n/c/Users/lixin/Desktop/20260417-185140.jpg');
	assert.equal(panels[1].title, 'check desktop root xlsx');
	assert.equal(panels[1].toolExecutions.length, 1);
	assert.equal(panels[1].toolExecutions[0].toolCallId, 'call-xlsx');
	assert.equal(panels[1].toolExecutions[0].startedAt, '2026-04-22T09:44:20.608Z');
	assert.equal(panels[1].toolExecutions[0].finishedAt, '2026-04-22T09:44:21.710Z');
});

test('extractSubagentTaskPanelsByEntry dedupes the same delegated payload across message and entry metadata', () => {
	const delegatedPayload = {
		delegationMode: 'session-subagent',
		taskResults: [
			{
				sortOrder: 0,
				task: { sortOrder: 0, goal: 'check images' },
				result: {
					id: 100,
					status: 'completed',
					summary: '3 images',
					resultPayload: {
						processEvents: [
							{
								type: 'tool_call',
								runId: 'subagent-100',
								timestamp: '2026-04-22T09:52:09.011Z',
								toolCallId: 'call-images',
								name: 'bash',
								args: { command: 'ls -la ~/Desktop/*.jpg' },
							},
							{
								type: 'tool_result',
								runId: 'subagent-100',
								timestamp: '2026-04-22T09:52:09.350Z',
								toolCallId: 'call-images',
								name: 'bash',
								result: '111.jpg\n11123.jpg\n20260417-185140.jpg',
							},
						],
					},
				},
			},
			{
				sortOrder: 1,
				task: { sortOrder: 1, goal: 'check xlsx' },
				result: {
					id: 99,
					status: 'completed',
					summary: '5 xlsx',
					resultPayload: {
						processEvents: [
							{
								type: 'tool_call',
								runId: 'subagent-99',
								timestamp: '2026-04-22T09:52:00.758Z',
								toolCallId: 'call-xlsx',
								name: 'find_files',
								args: { path: '~/Desktop', pattern: '*.xlsx' },
							},
						],
					},
				},
			},
		],
	};
	const skillExecution = {
		name: 'delegate_parallel',
		label: 'Delegate Parallel',
		status: 'done',
		result: delegatedPayload,
		rawResult: {
			type: 'json',
			data: delegatedPayload,
		},
	};
	const entry: SessionTreeEntry = {
		id: 'entry-duplicate-delegate-payload',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T09:52:20.000Z',
		type: 'message',
		message: {
			role: 'assistant',
			content: 'done',
			metadata: {
				skillExecutions: [skillExecution],
			},
		},
		metadata: {
			skillExecutions: [skillExecution],
		},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 2);
	assert.equal(panels[0].key, '100');
	assert.equal(panels[1].key, '99');
	assert.equal(panels[0].title, 'check images');
	assert.equal(panels[1].title, 'check xlsx');
});

test('extractSubagentTaskPanelsByEntry prefers message metadata over entry metadata for message entries', () => {
	const messagePayload = {
		delegationMode: 'session-subagent',
		taskResults: [
			{
				task: { sortOrder: 0, goal: 'message metadata task' },
				result: {
					id: 201,
					status: 'completed',
					summary: 'from message metadata',
					resultPayload: {},
				},
			},
		],
	};
	const staleEntryPayload = {
		delegationMode: 'session-subagent',
		taskResults: [
			{
				task: { sortOrder: 1, goal: 'entry metadata task' },
				result: {
					id: 202,
					status: 'completed',
					summary: 'from entry metadata',
					resultPayload: {},
				},
			},
		],
	};
	const entry: SessionTreeEntry = {
		id: 'entry-message-metadata-wins',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T09:59:00.000Z',
		type: 'message',
		message: {
			role: 'assistant',
			content: 'done',
			metadata: {
				skillExecutions: [
					{
						name: 'delegate_parallel',
						status: 'done',
						result: messagePayload,
					},
				],
			},
		},
		metadata: {
			skillExecutions: [
				{
					name: 'delegate_parallel',
					status: 'done',
					result: staleEntryPayload,
				},
			],
		},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 1);
	assert.equal(panels[0].key, '201');
	assert.equal(panels[0].title, 'message metadata task');
	assert.equal(panels[0].summary, 'from message metadata');
});

test('extractSubagentTaskPanelsByEntry prefers canonical backend projection over raw delegated payloads', () => {
	const delegatedPayload = {
		delegationMode: 'session-subagent',
		taskResults: [
			{
				task: { sortOrder: 0, goal: 'raw payload task' },
				result: {
					id: 301,
					status: 'completed',
					summary: 'from raw payload',
					resultPayload: {},
				},
			},
		],
	};
	const entry: SessionTreeEntry = {
		id: 'entry-canonical-projection',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T10:10:00.000Z',
		type: 'message',
		message: {
			role: 'assistant',
			content: 'done',
			metadata: {
				skillExecutions: [
					{
						name: 'delegate_parallel',
						status: 'done',
						result: delegatedPayload,
					},
				],
			},
		},
		metadata: {
			subagentProjection: {
				version: 1,
				source: 'backend-projection',
				replay: {
					limits: {
						evidenceSummaries: 8,
						processEvents: 50,
						taskProcessEvents: 50,
						toolExecutions: 24,
					},
					truncated: {
						evidenceSummaries: false,
						processEvents: false,
						taskPanels: false,
						toolExecutions: false,
					},
				},
				evidenceSummaries: [],
				processEvents: [],
				taskPanels: [
					{
						key: '302',
						title: 'canonical task',
						status: 'completed',
						sortOrder: 0,
						summary: 'from backend projection',
						evidenceSummaries: [],
						processEvents: [],
						toolExecutions: [],
						result: { id: 302 },
					},
				],
				diagnostics: {
					selectedSource: 'message_metadata',
					inputSources: ['message_metadata', 'entry_metadata'],
					ignoredSources: ['entry_metadata'],
					rawResultCount: 1,
					dedupedResultCount: 1,
					projectedTaskCount: 1,
					skippedResultCount: 0,
					fallbackUsed: false,
				},
			},
		},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 1);
	assert.equal(panels[0].key, '302');
	assert.equal(panels[0].title, 'canonical task');
	assert.equal(panels[0].summary, 'from backend projection');
});

test('extractSubagentProjectionDiagnosticsByEntry preserves canonical projection diagnostics', () => {
	const entry: SessionTreeEntry = {
		id: 'entry-canonical-projection-diagnostics',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T10:15:00.000Z',
		type: 'message',
		message: {
			role: 'assistant',
			content: 'done',
		},
		metadata: {
			subagentProjection: {
				version: 1,
				source: 'backend-projection',
				replay: {
					limits: {
						evidenceSummaries: 8,
						processEvents: 50,
						taskProcessEvents: 50,
						toolExecutions: 24,
					},
					truncated: {
						evidenceSummaries: false,
						processEvents: false,
						taskPanels: false,
						toolExecutions: false,
					},
				},
				evidenceSummaries: [],
				processEvents: [],
				taskPanels: [],
				diagnostics: {
					selectedSource: 'entry_metadata',
					inputSources: ['entry_metadata'],
					ignoredSources: [],
					rawResultCount: 3,
					dedupedResultCount: 1,
					projectedTaskCount: 1,
					skippedResultCount: 1,
					fallbackUsed: true,
				},
			},
		},
	};

	const diagnosticsByEntry = extractSubagentProjectionDiagnosticsByEntry([entry]);

	assert.deepEqual(diagnosticsByEntry[entry.id], {
		selectedSource: 'entry_metadata',
		inputSources: ['entry_metadata'],
		ignoredSources: [],
		rawResultCount: 3,
		dedupedResultCount: 1,
		projectedTaskCount: 1,
		skippedResultCount: 1,
		fallbackUsed: true,
		projectionPath: 'canonical-backend-projection',
	});
});

test('extractSubagentProjectionDiagnosticsByEntry synthesizes metadata-only fallback diagnostics', () => {
	const entry: SessionTreeEntry = {
		id: 'entry-metadata-only-diagnostics',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T10:16:00.000Z',
		type: 'subagent_result',
		content: {
			batchId: 'batch-metadata-only',
			status: 'completed',
			results: [],
		},
		metadata: {
			evidenceSummaries: [
				{
					kind: 'tool-preview',
					title: 'Metadata only',
					summary: 'metadata-only result',
				},
			],
		},
	};

	const diagnosticsByEntry = extractSubagentProjectionDiagnosticsByEntry([entry]);

	assert.deepEqual(diagnosticsByEntry[entry.id], {
		selectedSource: 'subagent_result',
		inputSources: ['subagent_result'],
		ignoredSources: [],
		rawResultCount: 0,
		dedupedResultCount: 0,
		projectedTaskCount: 0,
		skippedResultCount: 0,
		fallbackUsed: true,
		projectionPath: 'metadata-only-fallback',
	});
});

test('extractSubagentProjectionDiagnosticsByEntry synthesizes legacy payload fallback diagnostics', () => {
	const entry: SessionTreeEntry = {
		id: 'entry-legacy-payload-diagnostics',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T10:17:00.000Z',
		type: 'message',
		message: {
			role: 'assistant',
			content: 'done',
			metadata: {
				skillExecutions: [
					{
						name: 'delegate_parallel',
						status: 'done',
						result: {
							delegationMode: 'session-subagent',
							taskResults: [
								{
									task: { sortOrder: 0, goal: 'legacy payload task' },
									result: { id: 701, status: 'completed', summary: 'legacy payload summary' },
								},
							],
						},
					},
				],
			},
		},
		metadata: {},
	};

	const diagnosticsByEntry = extractSubagentProjectionDiagnosticsByEntry([entry]);

	assert.deepEqual(diagnosticsByEntry[entry.id], {
		selectedSource: 'message_metadata',
		inputSources: ['message_metadata'],
		ignoredSources: [],
		rawResultCount: 1,
		dedupedResultCount: 1,
		projectedTaskCount: 1,
		skippedResultCount: 0,
		fallbackUsed: true,
		projectionPath: 'legacy-payload-fallback',
	});
});

test('extractSubagentTaskPanelsByEntry falls back when backend projection version is missing', () => {
	const delegatedPayload = {
		delegationMode: 'session-subagent',
		taskResults: [
			{
				task: { sortOrder: 0, goal: 'raw payload task' },
				result: {
					id: 401,
					status: 'completed',
					summary: 'from raw payload',
					resultPayload: {},
				},
			},
		],
	};
	const entry: SessionTreeEntry = {
		id: 'entry-invalid-canonical-projection',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T10:20:00.000Z',
		type: 'message',
		message: {
			role: 'assistant',
			content: 'done',
			metadata: {
				skillExecutions: [
					{
						name: 'delegate_parallel',
						status: 'done',
						result: delegatedPayload,
					},
				],
			},
		},
		metadata: {
			subagentProjection: {
				source: 'backend-projection',
				evidenceSummaries: [],
				processEvents: [],
				taskPanels: [
					{
						key: '402',
						title: 'unversioned canonical task',
						evidenceSummaries: [],
						processEvents: [],
						toolExecutions: [],
					},
				],
			},
		},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 1);
	assert.equal(panels[0].key, '401');
	assert.equal(panels[0].title, 'raw payload task');
	assert.equal(panels[0].summary, 'from raw payload');
});

test('extractSubagentTaskPanelsByEntry marks legacy tool executions as truncated', () => {
	const processEvents = Array.from({ length: 25 }, (_, index) => {
		const toolCallId = `call-${index}`;
		return [
			{
				type: 'tool_call',
				runId: 'subagent-legacy-truncated',
				timestamp: `2026-04-22T10:30:${String(index).padStart(2, '0')}.000Z`,
				toolCallId,
				name: 'bash',
				label: '执行命令',
				args: { command: `echo ${index}` },
			},
			{
				type: 'tool_result',
				runId: 'subagent-legacy-truncated',
				timestamp: `2026-04-22T10:30:${String(index).padStart(2, '0')}.500Z`,
				toolCallId,
				name: 'bash',
				label: '执行命令',
				result: String(index),
			},
		];
	}).flat();
	const entry: SessionTreeEntry = {
		id: 'entry-legacy-truncated-tools',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T10:30:00.000Z',
		type: 'subagent_result',
		content: {
			results: [
				{
					id: 501,
					status: 'completed',
					goal: 'legacy tool truncation',
					resultPayload: { processEvents },
				},
			],
		},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 1);
	assert.equal(panels[0].toolExecutions.length, 24);
	assert.equal(panels[0].toolExecutionsTruncated, true);
});

test('extractSubagentTaskPanelsByEntry omits folded tool events from legacy task replay', () => {
	const entry: SessionTreeEntry = {
		id: 'entry-legacy-task-replay',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T10:35:00.000Z',
		type: 'subagent_result',
		content: {
			results: [
				{
					id: 601,
					status: 'completed',
					goal: 'legacy task replay',
					resultPayload: {
						processEvents: [
							{
								type: 'run_start',
								runId: 'subagent-legacy-replay',
								timestamp: '2026-04-22T10:35:00.000Z',
							},
							{
								type: 'tool_call',
								runId: 'subagent-legacy-replay',
								timestamp: '2026-04-22T10:35:01.000Z',
								toolCallId: 'call-legacy',
								name: 'bash',
								args: { command: 'echo ok' },
							},
							{
								type: 'tool_result',
								runId: 'subagent-legacy-replay',
								timestamp: '2026-04-22T10:35:02.000Z',
								toolCallId: 'call-legacy',
								name: 'bash',
								result: 'ok',
							},
							{
								type: 'token',
								runId: 'subagent-legacy-replay',
								timestamp: '2026-04-22T10:35:03.000Z',
								text: 'done',
							},
						],
					},
				},
			],
		},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 1);
	assert.equal(panels[0].toolExecutions.length, 1);
	assert.deepEqual(panels[0].processEvents.map(event => event.type), ['run_start', 'token']);
});

test('extractSubagentTaskPanelsByEntry projects delegated entry metadata rawResult data', () => {
	const delegatedPayload = {
		delegationMode: 'session-subagent',
		taskResults: [
			{
				task: { sortOrder: 0, goal: '获取桌面根目录所有图片文件的文件名列表和数量统计' },
				result: {
					id: 96,
					status: 'completed',
					summary: '图片文件数量：3 个',
					resultPayload: {
						processEvents: [
							{
								type: 'tool_call',
								runId: 'subagent-96',
								timestamp: '2026-04-22T09:36:27.718Z',
								toolCallId: 'call-images-2',
								name: 'bash',
								label: '执行命令',
								args: { command: 'find /c/Users/lixin/Desktop -maxdepth 1 -type f' },
							},
							{
								type: 'tool_result',
								runId: 'subagent-96',
								timestamp: '2026-04-22T09:36:28.048Z',
								toolCallId: 'call-images-2',
								name: 'bash',
								label: '执行命令',
								result: '/c/Users/lixin/Desktop/111.jpg',
							},
						],
					},
				},
			},
		],
	};
	const entry: SessionTreeEntry = {
		id: 'entry-entry-metadata-delegate-result',
		parentId: 'entry-parent',
		timestamp: '2026-04-22T13:36:00.000Z',
		type: 'message',
		message: {
			role: 'assistant',
			content: '检查完成',
		},
		metadata: {
			skillExecutions: [
				{
					toolName: 'delegate_parallel',
					label: 'Delegate Parallel',
					status: 'done',
					rawResult: {
						type: 'json',
						data: delegatedPayload,
					},
				},
			],
		},
	};

	const panelsByEntry = extractSubagentTaskPanelsByEntry([entry]);
	const panels = panelsByEntry[entry.id];

	assert.equal(panels.length, 1);
	assert.equal(panels[0].title, '获取桌面根目录所有图片文件的文件名列表和数量统计');
	assert.equal(panels[0].toolExecutions.length, 1);
	assert.equal(panels[0].toolExecutions[0].toolCallId, 'call-images-2');
	assert.equal(panels[0].toolExecutions[0].startedAt, '2026-04-22T09:36:27.718Z');
	assert.equal(panels[0].toolExecutions[0].finishedAt, '2026-04-22T09:36:28.048Z');
});

test('extractTreeNodeContextMeta describes branch summary and compaction nodes for tree rendering', () => {
	const branchEntry: SessionTreeEntry = {
		id: 'branch-entry',
		parentId: 'parent-entry',
		timestamp: '2026-04-22T15:00:00.000Z',
		type: 'branch_summary',
		summary: '这是一个很长的分支摘要，用来验证树节点上下文描述是否会给出来源节点提示。',
		fromId: 'source-node-abcdef12',
		metadata: {},
	};

	const compactionEntry: SessionTreeEntry = {
		id: 'compaction-entry',
		parentId: 'branch-entry',
		timestamp: '2026-04-22T15:05:00.000Z',
		type: 'compaction',
		summary: '上下文已压缩',
		tokensBefore: 4096,
		firstKeptEntryId: 'kept-node-fedcba98',
		data: {
			tokensAfter: 1536,
		},
		metadata: {},
	};

	const messageEntry: SessionTreeEntry = {
		id: 'message-entry',
		parentId: 'compaction-entry',
		timestamp: '2026-04-22T15:10:00.000Z',
		type: 'message',
		message: {
			role: 'assistant',
			content: 'plain message',
		},
		metadata: {},
	};

	assert.deepEqual(extractTreeNodeContextMeta(branchEntry), ['来源 source-n']);
	assert.deepEqual(extractTreeNodeContextMeta(compactionEntry), ['4096 -> 1536', '边界 kept-nod']);
	assert.deepEqual(extractTreeNodeContextMeta(messageEntry), []);
});

test('describeComposerTarget explains branch, switch, and leaf continuation modes', () => {
	const rootEntry: SessionTreeEntry = {
		id: 'root-entry',
		parentId: null,
		timestamp: '2026-04-22T15:59:00.000Z',
		type: 'message',
		message: {
			role: 'assistant',
			content: '之前的助手回答',
		},
		metadata: {},
	};

	const userEntry: SessionTreeEntry = {
		id: 'user-entry',
		parentId: 'root-entry',
		timestamp: '2026-04-22T16:00:00.000Z',
		type: 'message',
		message: {
			role: 'user',
			content: '重新开始这个问题',
		},
		metadata: {},
	};

	const branchSummaryEntry: SessionTreeEntry = {
		id: 'branch-summary-entry',
		parentId: 'user-entry',
		timestamp: '2026-04-22T16:01:00.000Z',
		type: 'branch_summary',
		summary: '分支摘要',
		metadata: {},
	};

	const compactionEntry: SessionTreeEntry = {
		id: 'compaction-entry',
		parentId: 'branch-summary-entry',
		timestamp: '2026-04-22T16:02:00.000Z',
		type: 'compaction',
		summary: '压缩摘要',
		metadata: {},
	};

	assert.equal(describeComposerTarget(null, 'leaf-entry').mode, 'leaf');
	const branchTarget = describeComposerTarget(userEntry, 'leaf-entry', {
		'root-entry': rootEntry,
		'user-entry': userEntry,
	});
	assert.equal(branchTarget.mode, 'branch');
	assert.equal(branchTarget.branchBaseTitle, '之前的助手回答');
	assert.equal(branchTarget.replayUserMessage, '重新开始这个问题');
	assert.equal(branchTarget.currentLeafTitle, '当前叶子');
	assert.equal(branchTarget.switchTargetTitle, '之前的助手回答');
	assert.equal(branchTarget.deliveryTargetTitle, '重新开始这个问题');

	const switchTarget = describeComposerTarget(branchSummaryEntry, 'compaction-entry', {
		'compaction-entry': compactionEntry,
		'branch-summary-entry': branchSummaryEntry,
	});
	assert.equal(switchTarget.mode, 'switch');
	assert.equal(switchTarget.currentLeafTitle, '压缩摘要');
	assert.equal(switchTarget.switchTargetTitle, '分支摘要');
	assert.equal(switchTarget.deliveryTargetTitle, '分支摘要');

	const leafTarget = describeComposerTarget(compactionEntry, 'compaction-entry', {
		'compaction-entry': compactionEntry,
	});
	assert.equal(leafTarget.mode, 'leaf');
	assert.equal(leafTarget.currentLeafTitle, '压缩摘要');
	assert.equal(leafTarget.deliveryTargetTitle, '压缩摘要');
});

test('describeSelectionState distinguishes selected, switching, and active leaf states', () => {
	assert.equal(describeSelectionState('entry-a', 'entry-b', 'entry-a').kind, 'switching');
	assert.equal(describeSelectionState('entry-a', 'entry-a', null).kind, 'leaf');
	assert.equal(describeSelectionState('entry-a', 'entry-b', null).kind, 'selected');
	assert.equal(describeSelectionState(null, 'entry-b', null).kind, 'leaf');
});

test('describeSelectionState surfaces pending confirmation after fallback switch', () => {
	const pending = describeSelectionState('entry-a', 'entry-b', null, {
		entryId: 'entry-a',
		previousLeafEntryId: 'entry-b',
	});
	assert.equal(pending.kind, 'pending');
	assert.match(pending.hint, /服务端尚未确认/);

	const unrelatedSelection = describeSelectionState('entry-c', 'entry-b', null, {
		entryId: 'entry-a',
		previousLeafEntryId: 'entry-b',
	});
	assert.equal(unrelatedSelection.kind, 'selected');
});

test('describeContinueFeedback returns success, warning, and error messages', () => {
	assert.equal(describeContinueFeedback({ success: true, title: '节点 A' }).type, 'success');
	assert.equal(describeContinueFeedback({ success: true, fallback: true, title: '节点 B' }).type, 'warning');
	assert.equal(describeContinueFeedback({ success: false, title: '节点 C', error: '切换失败' }).message, '切换失败');
});

test('describeTreeNodeBadges surfaces pending confirmation before generic selected state', () => {
	const entry: SessionTreeEntry = {
		id: 'entry-a',
		parentId: 'root-entry',
		timestamp: '2026-04-22T17:00:00.000Z',
		type: 'message',
		message: {
			role: 'user',
			content: '请从这里继续',
		},
		metadata: {},
	};

	const badges = describeTreeNodeBadges(entry, {
		selectedEntryId: 'entry-a',
		leafEntryId: 'entry-b',
		activePathIds: ['root-entry', 'entry-b'],
		pendingLeafConfirmation: {
			entryId: 'entry-a',
			previousLeafEntryId: 'entry-b',
		},
	});

	assert.deepEqual(
		badges.map(item => item.label),
		['待确认', '可分支'],
	);
	assert.equal(badges[0].tone, 'pending');
});

test('describePendingSendGuard warns before sending on an unconfirmed fallback target', () => {
	const message = describePendingSendGuard('节点 A');
	assert.match(message.title, /待确认/);
	assert.match(message.message, /节点 A/);
	assert.match(message.message, /服务端尚未确认/);
});
