import { describe, expect, test } from 'vitest'; import type { AgentRuntimeState } from '../src/app/types'; import { formatAgentRuntimeEvent, projectRuntimeVisibleCurrentWork, projectRuntimeVisibleError, projectSupervisorChatRuntimeStatus, projectSupervisorVisibleConversationText, } from '../src/features/agent-runtime/model'; function providerRetryRuntime(): AgentRuntimeState { return { schemaVersion: 'game-creator-agent-runtime.v1', agentId: 'project-supervisor', taskId: 'task-provider-retry', sessionId: 'session-provider-retry', runId: 'run-provider-retry', source: 'project-supervisor-chat', status: 'running', phase: 'waiting-for-provider-retry', currentTask: '生成项目总控计划', currentAction: 'Provider 上游返回 HTTP 503,准备自动重试 1/3', waitingOn: '预计 30 秒后重试', nextStep: '到期后继续同一 Run', plan: ['生成项目总控计划'], planSteps: [ { step: '这个活跃计划步骤不应覆盖真实 Provider 等待进度', status: 'in_progress', index: 0, }, ], observations: [], allowedTools: [], lastResponse: null, error: null, updatedAt: 1, }; } describe('Agent Runtime Provider 状态投影', () => { test('等待 503 重试时优先显示 Runtime 的真实动作和等待时间', () => { const runtime = providerRetryRuntime(); const expected = 'Provider 上游返回 HTTP 503,准备自动重试 1/3;预计 30 秒后重试'; expect(projectSupervisorChatRuntimeStatus(runtime)).toBe(expected); expect(projectRuntimeVisibleCurrentWork(runtime)).toBe(expected); }); test('旧等待投影不再显示内部 upstream-5xx 分类', () => { const legacyRuntime = { ...providerRetryRuntime(), currentAction: '等待 Provider 瞬态重试 1/3', waitingOn: 'Provider upstream-5xx 瞬态故障退避到期', }; const expected = 'Provider 上游服务暂时不可用,准备自动重试 1/3'; expect(projectSupervisorChatRuntimeStatus(legacyRuntime)).toBe(expected); expect(projectRuntimeVisibleCurrentWork(legacyRuntime)).toBe(expected); expect(projectSupervisorChatRuntimeStatus(legacyRuntime)).not.toContain( 'upstream-5xx', ); }); test('严格解析 503 重试耗尽的稳定字段', () => { const fingerprint = 'a'.repeat(64); const message = `agentLlm.project-supervisor 规划调用 LLM 失败:kind=upstream-503 ` + `httpStatus=503 fingerprint=${fingerprint} chars=248 ` + 'retryAttempt=3 maxRetries=3 retryState=exhausted'; expect(projectRuntimeVisibleError(message, '项目总控 Agent', true)).toBe( '项目总控 Agent 上游服务返回 HTTP 503;自动重试已耗尽(3/3)', ); const failedRuntime = { ...providerRetryRuntime(), status: 'failed', phase: 'failed', error: message, }; expect(projectSupervisorChatRuntimeStatus(failedRuntime)).toBe( '项目总控 Agent 上游服务返回 HTTP 503;自动重试已耗尽(3/3)', ); }); test('即使前缀含恶意上游正文也只展示严格字段派生的摘要', () => { const fingerprint = 'b'.repeat(64); const malicious = 'https://provider.example/private?api_key=sk-secret C:\\Users\\victim\\project'; const message = `${malicious}:kind=upstream-503 httpStatus=503 ` + `fingerprint=${fingerprint} chars=999 retryAttempt=3 ` + 'maxRetries=3 retryState=exhausted'; const visible = projectRuntimeVisibleError(message, '项目总控 Agent', true); expect(visible).toBe( '项目总控 Agent 上游服务返回 HTTP 503;自动重试已耗尽(3/3)', ); expect(visible).not.toContain('provider.example'); expect(visible).not.toContain('sk-secret'); expect(visible).not.toContain('victim'); }); test('拒绝字段不一致或带尾随正文的伪造 Provider 摘要', () => { const fingerprint = 'c'.repeat(64); const inconsistent = `kind=upstream-503 httpStatus=500 fingerprint=${fingerprint} chars=1 ` + 'retryAttempt=3 maxRetries=3 retryState=exhausted'; const trailingBody = `kind=upstream-503 httpStatus=503 fingerprint=${fingerprint} chars=1 ` + 'retryAttempt=3 maxRetries=3 retryState=exhausted provider body'; expect( projectRuntimeVisibleError(inconsistent, '项目总控 Agent', true), ).toBe('项目总控 Agent 执行失败,请稍后重试'); expect( projectRuntimeVisibleError(trailingBody, '项目总控 Agent', true), ).toBe('项目总控 Agent 执行失败,请稍后重试'); }); test('隐藏旧失败对话与事件中的内部诊断标记', () => { const fingerprint = 'd'.repeat(64); const legacyConversation = '后台任务失败: [redacted sensitive context] ' + `kind=upstream-503 httpStatus=503 fingerprint=${fingerprint} chars=99 ` + 'retryAttempt=3 maxRetries=3 retryState=exhausted'; expect(projectSupervisorVisibleConversationText(legacyConversation)).toBe( '项目总控 Agent 上游服务返回 HTTP 503;自动重试已耗尽(3/3)', ); expect( projectSupervisorVisibleConversationText(legacyConversation, 'user'), ).toBe(legacyConversation); const formattedEvent = formatAgentRuntimeEvent({ schemaVersion: 'game-creator-agent-runtime.v1', agentId: 'project-supervisor', taskId: 'task-provider-retry', sessionId: 'session-provider-retry', runId: 'run-provider-retry', source: 'project-supervisor-chat', eventType: 'turn.failed', status: 'failed', phase: 'failed', summary: 'Agent Runtime 本轮处理失败。', detail: ` [redacted sensitive context] ` + `errorSha256=${fingerprint} · errorChars=99`, updatedAt: 1, }); expect(formattedEvent).toBe( 'turn.failed · failed / failed · Agent Runtime 本轮处理失败。', ); expect(formattedEvent).not.toContain('errorSha256'); expect(formattedEvent).not.toContain(fingerprint); const emptySummaryEvent = formatAgentRuntimeEvent({ schemaVersion: 'game-creator-agent-runtime.v1', agentId: 'project-supervisor', taskId: 'task-provider-retry', sessionId: 'session-provider-retry', runId: 'run-provider-retry', source: 'project-supervisor-chat', eventType: 'error', status: 'failed', phase: 'failed', summary: '', detail: ` [redacted sensitive context] ` + `fingerprint=${fingerprint} chars=99`, updatedAt: 1, }); expect(emptySummaryEvent).toBe( 'error · failed / failed · Agent Runtime 本轮处理失败。', ); expect(emptySummaryEvent).not.toContain('absolute-path'); expect(emptySummaryEvent).not.toContain(fingerprint); }); });