const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const vm = require('node:vm'); const monitor = fs.readFileSync('web/static/js/monitor.js', 'utf8'); const chat = fs.readFileSync('web/static/js/chat.js', 'utf8'); const reason = '工具调用已被安全规则拦截:识别到 example.gov,禁止访问。\n规则: 政府网站保护'; function sourceFunction(source, name) { const start = source.indexOf(`function ${name}(`); assert.notEqual(start, -1, name); const rest = source.slice(start); const next = rest.slice(1).search(/\n(?:async )?function /); return (next === -1 ? rest : rest.slice(0, next + 1)).split(/\nwindow\.|\nconst toolCallDetailStateByItemId/)[0]; } class Element { constructor() { this.dataset = {}; this.children = []; this.className = ''; this.classList = { contains: (value) => this.className.split(' ').includes(value), add: (...values) => { this.className = [...new Set([...this.className.split(' ').filter(Boolean), ...values])].join(' '); }, remove: (...values) => { this.className = this.className.split(' ').filter((value) => !values.includes(value)).join(' '); } }; } set innerHTML(value) { this.html = value; this.title = new Element(); this.title.className = 'timeline-item-title'; } get innerHTML() { return this.html; } appendChild(child) { child.parent = this; this.children.push(child); } remove() { if (this.parent) this.parent.children = this.parent.children.filter((child) => child !== this); } querySelector(selector) { if (selector === '.timeline-item-title') return this.title || null; if (selector === '.tool-status-badge') return this.children.find((child) => child.classList.contains('tool-status-badge')) || null; return null; } } function runtime() { const ctx = { window: {}, document: { createElement: () => new Element() }, toolCallDetailStateByItemId: new Map(), updateToolDetailToggleLabel() {}, applyEinoTimelineRole() {}, pruneLiveTimelineIfNeeded() {}, getCurrentTimeLocale: () => 'en-US', getTimeFormatOptions: () => ({}), escapeHtml: (value) => String(value).replaceAll('&', '&').replaceAll('<', '<'), }; const funcs = ['collectToolResultTextParts', 'isToolGuardBlockedResult', 'getToolExecutionDisplayStatus', 'getToolResultDisplayState', 'toolDisplayStatusFromState', 'getBackgroundRunningToolLabel', 'getToolCallStatusPresentation', 'applyToolCallStatus', 'parseToolCallArgsFromData', 'toolCallArgsEmpty', 'setToolCallDetailState', 'mergeToolResultIntoCallItem', 'coalesceProcessDetailsToolPairs', 'buildToolResultSectionHtml', 'addTimelineItem']; vm.createContext(ctx); vm.runInContext(funcs.map((name) => sourceFunction(monitor, name)).join('\n'), ctx); ctx.window.getToolExecutionDisplayStatus = ctx.getToolExecutionDisplayStatus; vm.runInContext(['normalizeToolExecutionSummary', 'getToolExecutionStatusLabel', 'formatMCPResultJsonForDisplay'].map((name) => sourceFunction(chat, name)).join('\n'), ctx); return ctx; } test('structured block markers have priority over generic failure and running states', () => { const ctx = runtime(); for (const payload of [ { blocked: true, success: false, isError: true }, { status: 'blocked', isError: true }, { success: false, result: { blocked: true, isError: true, content: [] } }, { success: false, result: JSON.stringify({ _meta: { 'cyberstrike.ai/blocked': true }, isError: true, content: [] }) }, { blocked: true, displayStatus: 'background_running', success: true }, ]) { const state = ctx.getToolResultDisplayState(payload); assert.equal(state.kind, 'blocked'); assert.equal(state.success, false); assert.equal(ctx.toolDisplayStatusFromState(state), 'blocked'); } }); test('legacy guard failures are recognized in raw, nested, serialized and deferred history results', () => { const ctx = runtime(); for (const result of [reason, { isError: true, content: [{ type: 'text', text: reason }] }, JSON.stringify({ isError: true, content: [{ type: 'text', text: reason }] })]) { assert.equal(ctx.getToolResultDisplayState({ result, success: false }).kind, 'blocked'); } assert.equal(ctx.getToolResultDisplayState({ resultPreview: reason, success: false, _payloadDeferred: true }).kind, 'blocked'); assert.equal(ctx.getToolResultDisplayState({ success: false }, { rawText: reason }).kind, 'blocked'); }); test('quoted mentions, prefix lookalikes and explicitly successful output stay ordinary results', () => { const ctx = runtime(); for (const result of ['示例:' + reason, '"' + reason + '"', '工具调用已被安全规则拦截说明文档']) { assert.equal(ctx.getToolResultDisplayState({ result, success: false }).kind, 'error'); } for (const data of [{ success: true, result: reason }, { isError: false, content: [{ text: reason }] }, { status: 'completed', result: reason }]) { assert.equal(ctx.getToolResultDisplayState(data, { rawText: reason }).kind, 'success'); } assert.equal(ctx.getToolResultDisplayState({ success: false, result: 'connection refused' }).kind, 'error'); }); test('live merge replaces a red failure badge with the distinct block badge and keeps the reason', () => { const ctx = runtime(); const timeline = new Element(); ctx.addTimelineItem(timeline, 'tool_call', { title: 'http_request', data: { toolName: 'http_request' }, toolStatus: 'failed' }); const item = timeline.children[0]; assert.equal(item.classList.contains('tool-call-failed'), true); ctx.mergeToolResultIntoCallItem(item, { blocked: true, success: false, result: reason }); assert.equal(item.dataset.toolDisplayStatus, 'blocked'); assert.equal(item.dataset.toolSuccess, '0'); assert.equal(item.classList.contains('tool-call-blocked'), true); assert.equal(item.classList.contains('tool-call-failed'), false); assert.equal(item.title.children.length, 1); assert.match(item.title.children[0].textContent, /已拦截/); assert.equal(ctx.toolCallDetailStateByItemId.get(item.id).rawText, reason); }); test('refresh coalescing preserves blocks even when the old execution summary says failed', () => { const ctx = runtime(); const details = ctx.coalesceProcessDetailsToolPairs([ { id: 'call', eventType: 'tool_call', data: { toolCallId: 'id', toolName: 'http_request' } }, { id: 'result', eventType: 'tool_result', data: { toolCallId: 'id', success: false, result: reason } } ]); assert.equal(details.length, 1); const timeline = new Element(); ctx.addTimelineItem(timeline, 'tool_call', { data: details[0].data, toolStatus: 'failed' }); const item = timeline.children[0]; assert.equal(item.dataset.toolDisplayStatus, 'blocked'); assert.match(item.title.children[0].className, /tool-status-blocked/); assert.equal(item.classList.contains('tool-call-failed'), false); const resultData = ctx.toolCallDetailStateByItemId.get(item.id).resultData; assert.match(ctx.buildToolResultSectionHtml(resultData), /tool-result-section blocked/); assert.doesNotMatch(ctx.buildToolResultSectionHtml(resultData), /tool-result-section error/); }); test('execution summary buttons and raw detail preserve the separate blocked status', () => { const ctx = runtime(); assert.equal(ctx.normalizeToolExecutionSummary({ toolName: 'http_request', status: 'blocked' }).status, 'blocked'); assert.equal(ctx.normalizeToolExecutionSummary({ toolName: 'http_request', status: 'failed', error: reason }).status, 'blocked'); assert.equal(ctx.getToolExecutionStatusLabel('blocked'), '已拦截'); assert.equal(JSON.parse(ctx.formatMCPResultJsonForDisplay({ blocked: true, isError: true, content: [] })).blocked, true); });