diff --git a/docs/superpowers/plans/2026-07-10-stage-a-global-product-prototype.md b/docs/superpowers/plans/2026-07-10-stage-a-global-product-prototype.md
new file mode 100644
index 0000000..c9891f2
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-10-stage-a-global-product-prototype.md
@@ -0,0 +1,2019 @@
+# Stage A Global Product Prototype Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 在不新增后端业务 API 的前提下,完成全平台中文高保真前端、类型化演示数据、权限感知导航和关键交互测试,锁定 Stage B 至 Stage D 的 UI 与数据契约。
+
+**Architecture:** 保留现有真实 API 页面,新增 `features/prototype` 作为纯前端契约与演示数据层。模型、智能体、技能和 MCP 页面只消费该层;共享展示模式进入 `components/app`;路由继续使用现有 Hash Router;侧边栏根据当前用户已有权限补充 Stage A 原型菜单。后续阶段只替换数据适配层,不重做页面结构。
+
+**Tech Stack:** React 19、TypeScript 5.7、Vite 6、Tailwind CSS 3.4、shadcn/ui、Radix UI、Lucide、Vitest、Testing Library。
+
+## Global Constraints
+
+- 所有用户可见前端文案必须以中文为主。
+- 所有页面必须使用现有 shadcn/ui 原语和项目自有 `App*` 组件层。
+- 不修改 `backend/`,不新增模型、智能体、技能或 MCP 后端接口。
+- 真实工单、解决方案、知识库和系统治理页面继续调用现有 API。
+- 演示页面必须显示“演示数据”提示,不得伪装成已经持久化或真实执行。
+- 演示导航必须复用当前用户权限:`agent.use`、`agent.manage`、`model.manage`、`tool.manage`。
+- 禁止业务页面直接写原始颜色值;使用语义化主题 token 和 Badge 变体。
+- 表单使用 `AppField`;Select 必须使用 `SelectContent > SelectGroup > SelectItem`。
+- Dialog 必须包含 `DialogTitle`;图标按钮必须具有中文 `aria-label`。
+- 不使用 `space-x-*` 或 `space-y-*`,统一使用 `gap-*`。
+- 每个任务先运行目标测试验证失败,再完成最小实现并验证通过。
+- 本计划取代未执行的 `docs/superpowers/plans/2026-07-06-stage-2-5-global-frontend-prototype.md`。
+
+## File Structure
+
+### Prototype contracts
+
+- `frontend/src/features/prototype/types.ts`:Stage A 所有演示实体的唯一类型定义。
+- `frontend/src/features/prototype/mockData.ts`:生产式中文演示数据,不包含页面 JSX。
+- `frontend/src/features/prototype/navigation.ts`:按权限合并原型菜单的纯函数。
+
+### Shared App components
+
+- `frontend/src/components/app/AppPrototypeNotice.tsx`:统一演示数据提示。
+- `frontend/src/components/app/AppMetricCard.tsx`:工作台指标卡。
+- `frontend/src/components/app/AppRiskBadge.tsx`:工具风险等级。
+- `frontend/src/components/app/AppTraceTimeline.tsx`:智能体步骤轨迹。
+- `frontend/src/components/app/AppReferenceCard.tsx`:知识、方案和工单引用。
+- `frontend/src/components/app/AppApprovalPanel.tsx`:统一人工确认状态。
+
+### Product pages
+
+- `frontend/src/features/dashboard/DashboardPage.tsx`:全平台业务概览。
+- `frontend/src/features/modelhub/ModelProviderPage.tsx`:模型渠道原型。
+- `frontend/src/features/modelhub/ModelTestingPage.tsx`:模型测试原型。
+- `frontend/src/features/agents/AgentListPage.tsx`:智能体配置原型。
+- `frontend/src/features/agents/AgentRunsPage.tsx`:运行列表原型。
+- `frontend/src/features/agents/AgentRunDetailPage.tsx`:执行轨迹详情。
+- `frontend/src/features/skills/SkillListPage.tsx`:技能配置与测试原型。
+- `frontend/src/features/tools/McpToolsPage.tsx`:MCP 服务、工具与调用原型。
+- `frontend/src/features/tickets/TicketAiCreatePanel.tsx`:建单智能体建议区。
+- `frontend/src/features/tickets/TicketAiAnalysisPanel.tsx`:支持分析、引用和相似工单区。
+
+### Tests
+
+- `frontend/src/test/setup.ts`:Testing Library 全局配置。
+- `frontend/src/features/prototype/mockData.test.ts`:演示数据完整性。
+- `frontend/src/features/prototype/navigation.test.ts`:菜单权限与去重。
+- `frontend/src/components/app/prototype-components.test.tsx`:共享组件。
+- 每个新增页面目录包含一个聚焦交互的 `*.test.tsx`。
+
+---
+
+### Task 1: Prototype Test Harness And Typed Contracts
+
+**Files:**
+- Modify: `frontend/package.json`
+- Modify: `frontend/package-lock.json`
+- Create: `frontend/vitest.config.ts`
+- Create: `frontend/src/test/setup.ts`
+- Create: `frontend/src/features/prototype/types.ts`
+- Create: `frontend/src/features/prototype/mockData.ts`
+- Test: `frontend/src/features/prototype/mockData.test.ts`
+
+**Interfaces:**
+- Produces: `DashboardMetric`, `ModelProvider`, `ModelTestResult`, `AgentDefinition`, `AgentRun`, `AgentStep`, `SkillDefinition`, `McpServer`, `McpTool`, `ToolCall`, `KnowledgeReference`, `SimilarTicket`。
+- Produces: `dashboardMetrics`, `modelProviders`, `modelTestResults`, `agents`, `agentRuns`, `skills`, `mcpServers`, `mcpTools`, `toolCalls`, `knowledgeReferences`, `similarTickets`。
+
+- [ ] **Step 1: Install the frontend test harness**
+
+Run from `frontend/`:
+
+```powershell
+npm install --save-dev vitest@^3.2.4 jsdom@^26.1.0 @testing-library/react@^16.3.0 @testing-library/jest-dom@^6.6.3 @testing-library/user-event@^14.6.1
+```
+
+Add these scripts to `frontend/package.json`:
+
+```json
+{
+ "scripts": {
+ "test": "vitest",
+ "test:run": "vitest run"
+ }
+}
+```
+
+Create `frontend/vitest.config.ts`:
+
+```ts
+import path from 'node:path'
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src')
+ }
+ },
+ test: {
+ environment: 'jsdom',
+ setupFiles: ['./src/test/setup.ts'],
+ css: true
+ }
+})
+```
+
+Create `frontend/src/test/setup.ts`:
+
+```ts
+import '@testing-library/jest-dom/vitest'
+```
+
+- [ ] **Step 2: Write the failing mock-data integrity test**
+
+Create `frontend/src/features/prototype/mockData.test.ts`:
+
+```ts
+import { describe, expect, it } from 'vitest'
+
+import {
+ agentRuns,
+ agents,
+ dashboardMetrics,
+ knowledgeReferences,
+ mcpTools,
+ modelProviders,
+ skills
+} from './mockData'
+
+describe('Stage A 演示数据', () => {
+ it('提供所有产品模块所需数据', () => {
+ expect(dashboardMetrics).toHaveLength(4)
+ expect(modelProviders.length).toBeGreaterThanOrEqual(2)
+ expect(agents).toHaveLength(3)
+ expect(skills.length).toBeGreaterThanOrEqual(3)
+ expect(agentRuns.every((run) => run.steps.length > 0)).toBe(true)
+ })
+
+ it('保持可信边界和引用完整性', () => {
+ expect(mcpTools.filter((tool) => tool.access_mode === 'write').every((tool) => tool.requires_human_confirm)).toBe(true)
+ expect(knowledgeReferences.every((reference) => reference.score >= 0 && reference.score <= 1)).toBe(true)
+ })
+})
+```
+
+- [ ] **Step 3: Run the test and verify RED**
+
+Run from `frontend/`:
+
+```powershell
+npm run test:run -- src/features/prototype/mockData.test.ts
+```
+
+Expected: FAIL because `./mockData` does not exist.
+
+- [ ] **Step 4: Create the exact prototype types**
+
+Create `frontend/src/features/prototype/types.ts`:
+
+```ts
+export type DashboardMetric = {
+ id: string
+ label: string
+ value: string
+ change: string
+ trend: 'up' | 'down' | 'flat'
+ tone: 'default' | 'success' | 'warning' | 'danger' | 'ai'
+}
+
+export type ModelProvider = {
+ id: number
+ name: string
+ protocol: 'OpenAI-compatible'
+ base_url: string
+ credential_masked: string
+ enabled: boolean
+ health: 'healthy' | 'degraded' | 'unchecked'
+ model_count: number
+ last_checked_at: string
+}
+
+export type ModelTestResult = {
+ id: number
+ provider_name: string
+ model_name: string
+ prompt: string
+ output: string
+ latency_ms: number
+ input_tokens: number
+ output_tokens: number
+ status: 'success' | 'failed'
+ created_at: string
+}
+
+export type AgentDefinition = {
+ id: number
+ code: 'ticket_intake' | 'support_analysis' | 'knowledge_draft'
+ name: string
+ purpose: string
+ enabled: boolean
+ model_name: string
+ knowledge_scopes: string[]
+ skill_count: number
+ tool_count: number
+ updated_at: string
+}
+
+export type AgentStep = {
+ id: number
+ step_name: string
+ step_type: 'model' | 'retrieval' | 'skill' | 'tool' | 'approval'
+ status: 'success' | 'failed' | 'waiting'
+ input_summary: string
+ output_summary: string
+ latency_ms: number
+}
+
+export type AgentRun = {
+ id: number
+ agent_name: string
+ ticket_no: string
+ trigger_user: string
+ status: 'success' | 'failed' | 'running' | 'waiting_confirm'
+ output_summary: string
+ duration_ms: number
+ created_at: string
+ steps: AgentStep[]
+}
+
+export type SkillDefinition = {
+ id: number
+ name: string
+ scenario: 'field_extract' | 'missing_info' | 'solution_recommend' | 'reply_draft' | 'knowledge_draft'
+ version: string
+ enabled: boolean
+ prompt_template: string
+ input_schema: string
+ output_schema: string
+ updated_at: string
+}
+
+export type McpServer = {
+ id: number
+ name: string
+ endpoint: string
+ auth_type: '无认证' | '令牌'
+ connection_state: 'connected' | 'disconnected' | 'error'
+ tool_count: number
+ last_sync_at: string
+}
+
+export type McpTool = {
+ id: number
+ server_name: string
+ name: string
+ description: string
+ access_mode: 'read' | 'write'
+ risk_level: 'low' | 'medium' | 'high'
+ requires_human_confirm: boolean
+ enabled: boolean
+}
+
+export type ToolCall = {
+ id: number
+ tool_name: string
+ agent_name: string
+ input_summary: string
+ output_summary: string
+ status: 'success' | 'failed' | 'blocked'
+ duration_ms: number
+ created_at: string
+}
+
+export type KnowledgeReference = {
+ id: number
+ title: string
+ source_type: 'knowledge_item' | 'solution' | 'ticket' | 'document'
+ content: string
+ score: number
+ source_label: string
+}
+
+export type SimilarTicket = {
+ id: number
+ ticket_no: string
+ title: string
+ status: 'resolved' | 'closed'
+ similarity: number
+ resolution: string
+}
+```
+
+- [ ] **Step 5: Create production-style Chinese mock data**
+
+Create `frontend/src/features/prototype/mockData.ts` with the exported arrays below. Keep each array typed using the interfaces from `types.ts`.
+
+```ts
+import type {
+ AgentDefinition,
+ AgentRun,
+ DashboardMetric,
+ KnowledgeReference,
+ McpServer,
+ McpTool,
+ ModelProvider,
+ ModelTestResult,
+ SimilarTicket,
+ SkillDefinition,
+ ToolCall
+} from './types'
+
+export const dashboardMetrics: DashboardMetric[] = [
+ { id: 'pending', label: '待处理工单', value: '18', change: '较昨日增加 3 单', trend: 'up', tone: 'warning' },
+ { id: 'resolved', label: '今日已完成', value: '42', change: '完成率 78%', trend: 'up', tone: 'success' },
+ { id: 'knowledge', label: '可复用方案', value: '126', change: '本周新增 8 条', trend: 'up', tone: 'default' },
+ { id: 'agent', label: '智能体成功率', value: '96.4%', change: '最近 24 小时', trend: 'flat', tone: 'ai' }
+]
+
+export const modelProviders: ModelProvider[] = [
+ { id: 1, name: '企业 OpenAI 兼容渠道', protocol: 'OpenAI-compatible', base_url: 'https://llm.example.com/v1', credential_masked: 'sk-****82F1', enabled: true, health: 'healthy', model_count: 4, last_checked_at: '2026-07-10T08:32:00+08:00' },
+ { id: 2, name: '内部推理服务', protocol: 'OpenAI-compatible', base_url: 'http://model-gateway.internal/v1', credential_masked: 'token-****19AC', enabled: true, health: 'degraded', model_count: 2, last_checked_at: '2026-07-10T08:28:00+08:00' }
+]
+
+export const modelTestResults: ModelTestResult[] = [
+ { id: 1, provider_name: '企业 OpenAI 兼容渠道', model_name: 'support-large', prompt: '分析采集进度长期不更新的可能原因', output: '建议先核对任务状态、最近一次采集日志和税局接口响应。', latency_ms: 1280, input_tokens: 86, output_tokens: 42, status: 'success', created_at: '2026-07-10T09:10:00+08:00' },
+ { id: 2, provider_name: '内部推理服务', model_name: 'support-small', prompt: '生成客户回复草稿', output: '已确认任务仍在采集中,我们会继续关注上游接口状态。', latency_ms: 760, input_tokens: 64, output_tokens: 31, status: 'success', created_at: '2026-07-10T09:06:00+08:00' }
+]
+
+export const agents: AgentDefinition[] = [
+ { id: 1, code: 'ticket_intake', name: '建单智能体', purpose: '补全工单字段、识别分类并追问缺失信息', enabled: true, model_name: 'support-small', knowledge_scopes: ['客户支持知识库'], skill_count: 2, tool_count: 0, updated_at: '2026-07-10T08:20:00+08:00' },
+ { id: 2, code: 'support_analysis', name: '支持分析智能体', purpose: '检索知识、推荐方案并生成排查步骤', enabled: true, model_name: 'support-large', knowledge_scopes: ['客户支持知识库', '解决方案库'], skill_count: 3, tool_count: 2, updated_at: '2026-07-10T08:24:00+08:00' },
+ { id: 3, code: 'knowledge_draft', name: '知识沉淀智能体', purpose: '从已解决工单生成待审核知识草稿', enabled: false, model_name: 'support-large', knowledge_scopes: ['解决方案库'], skill_count: 1, tool_count: 0, updated_at: '2026-07-10T08:26:00+08:00' }
+]
+
+export const agentRuns: AgentRun[] = [
+ { id: 1001, agent_name: '支持分析智能体', ticket_no: 'T202607100006', trigger_user: '张工', status: 'waiting_confirm', output_summary: '已生成 3 条排查建议和 1 份客户回复草稿。', duration_ms: 2180, created_at: '2026-07-10T09:18:00+08:00', steps: [
+ { id: 1, step_name: '检索相关知识', step_type: 'retrieval', status: 'success', input_summary: '采集进度长期不更新', output_summary: '命中 2 条知识和 1 个解决方案', latency_ms: 180 },
+ { id: 2, step_name: '查询采集任务', step_type: 'tool', status: 'success', input_summary: '任务编号 TASK-8821', output_summary: '任务运行中,上游接口最近一次返回超时', latency_ms: 420 },
+ { id: 3, step_name: '生成支持分析', step_type: 'model', status: 'success', input_summary: '工单、知识引用与工具结果', output_summary: '生成原因分析、排查步骤和回复草稿', latency_ms: 1580 },
+ { id: 4, step_name: '等待人工采纳', step_type: 'approval', status: 'waiting', input_summary: 'AI 支持分析结果', output_summary: '等待技术人员确认', latency_ms: 0 }
+ ] },
+ { id: 1002, agent_name: '建单智能体', ticket_no: '草稿', trigger_user: '李娜', status: 'success', output_summary: '已识别为快速咨询并补全问题对象。', duration_ms: 940, created_at: '2026-07-10T09:12:00+08:00', steps: [
+ { id: 5, step_name: '抽取工单字段', step_type: 'skill', status: 'success', input_summary: '客户自然语言描述', output_summary: '识别分类、对象和环境', latency_ms: 120 },
+ { id: 6, step_name: '生成字段草稿', step_type: 'model', status: 'success', input_summary: '结构化字段', output_summary: '生成可确认的建单草稿', latency_ms: 820 }
+ ] }
+]
+
+export const skills: SkillDefinition[] = [
+ { id: 1, name: '工单字段抽取', scenario: 'field_extract', version: 'v1.3', enabled: true, prompt_template: '从用户描述中抽取工单分类、问题对象、问题表现、环境和关联系统。', input_schema: '{ "description": "string" }', output_schema: '{ "category_code": "string", "problem_object": "string" }', updated_at: '2026-07-10T08:00:00+08:00' },
+ { id: 2, name: '缺失信息追问', scenario: 'missing_info', version: 'v1.1', enabled: true, prompt_template: '根据已填写字段判断处理工单仍缺少哪些必要信息。', input_schema: '{ "ticket_draft": "object" }', output_schema: '{ "questions": "string[]" }', updated_at: '2026-07-10T08:02:00+08:00' },
+ { id: 3, name: '解决方案推荐', scenario: 'solution_recommend', version: 'v1.5', enabled: true, prompt_template: '根据工单内容和检索结果推荐可复用解决方案,并说明匹配原因。', input_schema: '{ "ticket": "object", "references": "object[]" }', output_schema: '{ "recommendations": "object[]" }', updated_at: '2026-07-10T08:04:00+08:00' }
+]
+
+export const mcpServers: McpServer[] = [
+ { id: 1, name: '采集任务查询服务', endpoint: 'http://collector-mcp.internal', auth_type: '令牌', connection_state: 'connected', tool_count: 3, last_sync_at: '2026-07-10T08:40:00+08:00' },
+ { id: 2, name: '日志检索服务', endpoint: 'http://logs-mcp.internal', auth_type: '令牌', connection_state: 'connected', tool_count: 2, last_sync_at: '2026-07-10T08:36:00+08:00' }
+]
+
+export const mcpTools: McpTool[] = [
+ { id: 1, server_name: '采集任务查询服务', name: '查询采集进度', description: '按任务编号查询当前阶段和最近更新时间', access_mode: 'read', risk_level: 'low', requires_human_confirm: false, enabled: true },
+ { id: 2, server_name: '日志检索服务', name: '检索任务日志', description: '按任务编号读取脱敏日志摘要', access_mode: 'read', risk_level: 'medium', requires_human_confirm: false, enabled: true },
+ { id: 3, server_name: '采集任务查询服务', name: '重启采集任务', description: '重新触发指定采集任务', access_mode: 'write', risk_level: 'high', requires_human_confirm: true, enabled: false }
+]
+
+export const toolCalls: ToolCall[] = [
+ { id: 1, tool_name: '查询采集进度', agent_name: '支持分析智能体', input_summary: 'TASK-8821', output_summary: '运行中,最近更新时间 09:16', status: 'success', duration_ms: 420, created_at: '2026-07-10T09:18:20+08:00' },
+ { id: 2, tool_name: '重启采集任务', agent_name: '支持分析智能体', input_summary: 'TASK-8821', output_summary: '非只读工具不允许自动执行', status: 'blocked', duration_ms: 8, created_at: '2026-07-10T09:18:40+08:00' }
+]
+
+export const knowledgeReferences: KnowledgeReference[] = [
+ { id: 1, title: '采集任务长时间无进度的排查步骤', source_type: 'knowledge_item', content: '先检查任务状态、上游接口响应和最近一次有效日志,再判断是否需要人工重试。', score: 0.93, source_label: '客户支持知识库' },
+ { id: 2, title: '税局接口不稳定时的客户回复模板', source_type: 'solution', content: '确认任务仍在执行后,向客户说明上游接口状态并持续关注采集进度。', score: 0.87, source_label: '解决方案库' }
+]
+
+export const similarTickets: SimilarTicket[] = [
+ { id: 5, ticket_no: 'T202607030005', title: '税局采集任务停留在初始化阶段', status: 'closed', similarity: 0.91, resolution: '确认税局接口波动,任务恢复后回复客户并关闭。' },
+ { id: 2, ticket_no: 'T202607010002', title: '查询申报数据采集进度', status: 'resolved', similarity: 0.84, resolution: '查询任务日志后确认仍在采集中。' }
+]
+```
+
+- [ ] **Step 6: Run the integrity test and typecheck**
+
+Run from `frontend/`:
+
+```powershell
+npm run test:run -- src/features/prototype/mockData.test.ts
+npm run typecheck
+```
+
+Expected: test file passes and TypeScript exits with code 0.
+
+- [ ] **Step 7: Commit Task 1**
+
+```powershell
+git add frontend/package.json frontend/package-lock.json frontend/vitest.config.ts frontend/src/test/setup.ts frontend/src/features/prototype/types.ts frontend/src/features/prototype/mockData.ts frontend/src/features/prototype/mockData.test.ts
+git commit -m "test: add stage a prototype contracts"
+```
+
+---
+
+### Task 2: Shared Prototype App Components
+
+**Files:**
+- Create: `frontend/src/components/app/AppPrototypeNotice.tsx`
+- Create: `frontend/src/components/app/AppMetricCard.tsx`
+- Create: `frontend/src/components/app/AppRiskBadge.tsx`
+- Create: `frontend/src/components/app/AppTraceTimeline.tsx`
+- Create: `frontend/src/components/app/AppReferenceCard.tsx`
+- Create: `frontend/src/components/app/AppApprovalPanel.tsx`
+- Test: `frontend/src/components/app/prototype-components.test.tsx`
+
+**Interfaces:**
+- Consumes: `DashboardMetric['tone']`, `McpTool['risk_level']`, `AgentStep`。
+- Produces: `AppPrototypeNotice`, `AppMetricCard`, `AppRiskBadge`, `AppTraceTimeline`, `AppReferenceCard`, `AppApprovalPanel`。
+
+- [ ] **Step 1: Write failing shared-component tests**
+
+Create `frontend/src/components/app/prototype-components.test.tsx`:
+
+```tsx
+import { render, screen } from '@testing-library/react'
+import { describe, expect, it } from 'vitest'
+
+import { AppMetricCard } from './AppMetricCard'
+import { AppApprovalPanel } from './AppApprovalPanel'
+import { AppPrototypeNotice } from './AppPrototypeNotice'
+import { AppReferenceCard } from './AppReferenceCard'
+import { AppRiskBadge } from './AppRiskBadge'
+import { AppTraceTimeline } from './AppTraceTimeline'
+
+describe('Stage A 共享组件', () => {
+ it('明确标识演示数据', () => {
+ render( )
+ expect(screen.getByText('当前页面使用演示数据')).toBeInTheDocument()
+ })
+
+ it('展示指标、风险、引用和执行步骤', () => {
+ render(
+ <>
+
+
+
+
+
+ >
+ )
+ expect(screen.getByText('待处理工单')).toBeInTheDocument()
+ expect(screen.getByText('高风险')).toBeInTheDocument()
+ expect(screen.getByText('匹配度 93%')).toBeInTheDocument()
+ expect(screen.getByText('检索知识')).toBeInTheDocument()
+ expect(screen.getByText('等待人工采纳')).toBeInTheDocument()
+ })
+})
+```
+
+- [ ] **Step 2: Run tests and verify RED**
+
+```powershell
+npm run test:run -- src/components/app/prototype-components.test.tsx
+```
+
+Expected: FAIL because the six components do not exist.
+
+- [ ] **Step 3: Implement the shared components**
+
+Create `frontend/src/components/app/AppPrototypeNotice.tsx`:
+
+```tsx
+import { FlaskConical } from 'lucide-react'
+
+import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
+
+export function AppPrototypeNotice() {
+ return (
+
+
+ 当前页面使用演示数据
+ 用于确认产品交互和数据契约,不会保存配置或发起真实调用。
+
+ )
+}
+```
+
+Create `frontend/src/components/app/AppMetricCard.tsx`:
+```tsx
+import { Badge } from '@/components/ui/badge'
+import { Card, CardContent } from '@/components/ui/card'
+
+type AppMetricCardProps = {
+ label: string
+ value: string | number
+ change: string
+ tone?: 'default' | 'success' | 'warning' | 'danger' | 'ai'
+}
+
+const variants = {
+ default: 'secondary',
+ success: 'success',
+ warning: 'warning',
+ danger: 'danger',
+ ai: 'ai'
+} as const
+
+export function AppMetricCard({ label, value, change, tone = 'default' }: AppMetricCardProps) {
+ return (
+
+
+
+ {value}
+
+
+ )
+}
+```
+
+Create `frontend/src/components/app/AppRiskBadge.tsx`:
+
+```tsx
+import { Badge } from '@/components/ui/badge'
+import type { McpTool } from '@/features/prototype/types'
+
+const riskConfig = {
+ low: { label: '低风险', variant: 'success' },
+ medium: { label: '中风险', variant: 'warning' },
+ high: { label: '高风险', variant: 'danger' }
+} as const
+
+export function AppRiskBadge({ risk }: { risk: McpTool['risk_level'] }) {
+ const config = riskConfig[risk]
+ return {config.label}
+}
+```
+
+Create `frontend/src/components/app/AppTraceTimeline.tsx`:
+
+```tsx
+import { CheckCircle2, CircleAlert, Clock3 } from 'lucide-react'
+
+import { Badge } from '@/components/ui/badge'
+import type { AgentStep } from '@/features/prototype/types'
+
+const statusLabels = { success: '成功', failed: '失败', waiting: '等待确认' }
+const typeLabels = { model: '模型', retrieval: '知识检索', skill: '技能', tool: '工具', approval: '人工确认' }
+
+export function AppTraceTimeline({ items }: { items: AgentStep[] }) {
+ return (
+
+ {items.map((item) => {
+ const Icon = item.status === 'success' ? CheckCircle2 : item.status === 'failed' ? CircleAlert : Clock3
+ return (
+
+
+
+
+
{item.step_name}
+
{typeLabels[item.step_type]}
+
{statusLabels[item.status]}
+
+
输入:{item.input_summary}
+
输出:{item.output_summary}
+
耗时 {item.latency_ms} ms
+
+
+ )
+ })}
+
+ )
+}
+```
+
+Create `frontend/src/components/app/AppReferenceCard.tsx`:
+
+```tsx
+import { Badge } from '@/components/ui/badge'
+
+const sourceLabels: Record = {
+ knowledge_item: '知识条目',
+ solution: '解决方案',
+ ticket: '历史工单',
+ document: '文档'
+}
+
+type AppReferenceCardProps = {
+ title: string
+ sourceType: string
+ content: string
+ score: number
+ sourceLabel?: string
+}
+
+export function AppReferenceCard({ title, sourceType, content, score, sourceLabel }: AppReferenceCardProps) {
+ return (
+
+
+
{title}
+
{sourceLabels[sourceType] ?? sourceType}
+
匹配度 {Math.round(score * 100)}%
+
+ {content}
+ {sourceLabel ? 来源:{sourceLabel}
: null}
+
+ )
+}
+```
+
+Create `frontend/src/components/app/AppApprovalPanel.tsx`:
+
+```tsx
+import type * as React from 'react'
+
+import { Badge } from '@/components/ui/badge'
+
+type AppApprovalPanelProps = {
+ title: string
+ description: string
+ state: 'waiting' | 'approved' | 'rejected'
+ action?: React.ReactNode
+}
+
+const stateConfig = {
+ waiting: { label: '等待确认', variant: 'warning' },
+ approved: { label: '已确认', variant: 'success' },
+ rejected: { label: '已拒绝', variant: 'danger' }
+} as const
+
+export function AppApprovalPanel({ title, description, state, action }: AppApprovalPanelProps) {
+ const config = stateConfig[state]
+ return (
+
+ )
+}
+```
+
+- [ ] **Step 4: Run shared-component tests and typecheck**
+
+```powershell
+npm run test:run -- src/components/app/prototype-components.test.tsx
+npm run typecheck
+```
+
+Expected: tests pass and TypeScript exits with code 0.
+
+- [ ] **Step 5: Commit Task 2**
+
+```powershell
+git add frontend/src/components/app/AppPrototypeNotice.tsx frontend/src/components/app/AppMetricCard.tsx frontend/src/components/app/AppRiskBadge.tsx frontend/src/components/app/AppTraceTimeline.tsx frontend/src/components/app/AppReferenceCard.tsx frontend/src/components/app/AppApprovalPanel.tsx frontend/src/components/app/prototype-components.test.tsx
+git commit -m "feat: add stage a shared components"
+```
+
+---
+### Task 3: Permission-Aware Navigation And Operations Dashboard
+
+**Files:**
+- Create: `frontend/src/features/prototype/navigation.ts`
+- Test: `frontend/src/features/prototype/navigation.test.ts`
+- Create: `frontend/src/features/dashboard/DashboardPage.tsx`
+- Test: `frontend/src/features/dashboard/DashboardPage.test.tsx`
+- Modify: `frontend/src/components/app/AppSidebar.tsx`
+- Modify: `frontend/src/app/router.tsx`
+
+**Interfaces:**
+- Consumes: `CurrentUser.permissions`, `CurrentUser.menus`, `dashboardMetrics`, `agentRuns`, `mcpTools`。
+- Produces: `mergePrototypeNavigation(groups, permissions): AuthMenu[]`。
+- Produces routes: `/dashboard`, `/agents`, `/agents/runs`, `/skills`, `/modelhub/providers`, `/modelhub/testing`, `/tools/mcp`。
+
+- [ ] **Step 1: Write the failing navigation test**
+
+Create `frontend/src/features/prototype/navigation.test.ts`:
+
+```ts
+import { describe, expect, it } from 'vitest'
+
+import type { AuthMenu } from '@/features/auth/types'
+import { mergePrototypeNavigation } from './navigation'
+
+const baseMenus: AuthMenu[] = [
+ { id: 1, code: 'workbench', title: '工作台', path: '', icon: 'home', permission_code: '', is_visible: true, is_enabled: true, children: [
+ { id: 2, code: 'workbench.overview', title: '运营总览', path: '#/', icon: 'home', permission_code: '', is_visible: true, is_enabled: true, children: [] }
+ ] }
+]
+
+describe('Stage A 原型导航', () => {
+ it('根据管理员权限补充完整产品入口', () => {
+ const groups = mergePrototypeNavigation(baseMenus, ['knowledge.view', 'agent.use', 'agent.manage', 'model.manage', 'tool.manage'])
+ const paths = groups.flatMap((group) => group.children.map((child) => child.path))
+ expect(paths).toEqual(expect.arrayContaining(['#/dashboard', '#/knowledge', '#/agents', '#/agents/runs', '#/skills', '#/modelhub/providers', '#/modelhub/testing', '#/tools/mcp']))
+ })
+
+ it('不向无权限用户暴露管理入口,也不重复已有路径', () => {
+ const once = mergePrototypeNavigation(baseMenus, ['agent.use'])
+ const twice = mergePrototypeNavigation(once, ['agent.use'])
+ const paths = twice.flatMap((group) => group.children.map((child) => child.path))
+ expect(paths.filter((path) => path === '#/agents/runs')).toHaveLength(1)
+ expect(paths).not.toContain('#/agents')
+ expect(paths).not.toContain('#/skills')
+ expect(paths).not.toContain('#/modelhub/providers')
+ })
+})
+```
+
+- [ ] **Step 2: Run the navigation test and verify RED**
+
+```powershell
+npm run test:run -- src/features/prototype/navigation.test.ts
+```
+
+Expected: FAIL because `navigation.ts` does not exist.
+
+- [ ] **Step 3: Implement permission-aware menu merging**
+
+Create `frontend/src/features/prototype/navigation.ts`:
+
+```ts
+import type { AuthMenu } from '@/features/auth/types'
+
+export function mergePrototypeNavigation(groups: AuthMenu[], permissions: string[]): AuthMenu[] {
+ let nextId = -1000
+ function menu(code: string, title: string, path: string, icon: string, permissionCode = ''): AuthMenu {
+ return { id: nextId--, code, title, path, icon, permission_code: permissionCode, is_visible: true, is_enabled: true, children: [] }
+ }
+
+ const result = groups.map((group) => ({ ...group, children: group.children.map((child) => ({ ...child })) }))
+ const allowed = new Set(permissions)
+
+ function hasPath(path: string) {
+ return result.some((group) => group.path === path || group.children.some((child) => child.path === path))
+ }
+
+ function append(groupCode: string, groupTitle: string, groupIcon: string, child: AuthMenu, requiredPermission?: string) {
+ if (requiredPermission && !allowed.has(requiredPermission)) return
+ if (hasPath(child.path)) return
+ let group = result.find((item) => item.code === groupCode)
+ if (!group) {
+ group = menu(groupCode, groupTitle, '', groupIcon)
+ result.push(group)
+ }
+ group.children = [...group.children, child]
+ }
+
+ append('workbench', '工作台', 'home', menu('prototype.dashboard', '业务概览', '#/dashboard', 'activity'))
+ append('prototype.knowledge', '知识与方案', 'book_open', menu('prototype.knowledge.bases', '知识库', '#/knowledge', 'database', 'knowledge.view'), 'knowledge.view')
+ append('prototype.knowledge', '知识与方案', 'book_open', menu('prototype.knowledge.search', '知识检索', '#/knowledge/search', 'search', 'knowledge.view'), 'knowledge.view')
+ append('prototype.agents', '智能体中心', 'brain', menu('prototype.agents.config', '智能体配置', '#/agents', 'brain', 'agent.manage'), 'agent.manage')
+ append('prototype.agents', '智能体中心', 'brain', menu('prototype.agents.runs', '运行记录', '#/agents/runs', 'activity', 'agent.use'), 'agent.use')
+ append('prototype.skills', '技能中心', 'boxes', menu('prototype.skills.config', '技能配置', '#/skills', 'boxes', 'agent.manage'), 'agent.manage')
+ append('prototype.model_tools', '模型与工具', 'settings', menu('prototype.models.providers', '模型渠道', '#/modelhub/providers', 'settings', 'model.manage'), 'model.manage')
+ append('prototype.model_tools', '模型与工具', 'settings', menu('prototype.models.testing', '模型测试', '#/modelhub/testing', 'activity', 'model.manage'), 'model.manage')
+ append('prototype.model_tools', '模型与工具', 'settings', menu('prototype.tools.mcp', '工具接入', '#/tools/mcp', 'plug', 'tool.manage'), 'tool.manage')
+
+ return result.filter((group) => group.children.length > 0)
+}
+```
+
+- [ ] **Step 4: Replace the sidebar fallback with the merge function**
+
+Modify `frontend/src/components/app/AppSidebar.tsx`:
+
+1. Add `Activity`, `Boxes`, `BrainCircuit`, and `Plug` to the Lucide import.
+2. Add icon-map entries:
+
+```tsx
+activity: Activity,
+boxes: Boxes,
+brain: BrainCircuit,
+plug: Plug,
+```
+
+3. Import the merge function:
+
+```tsx
+import { mergePrototypeNavigation } from '@/features/prototype/navigation'
+```
+
+4. Delete the entire `knowledgeNavGroup` constant.
+5. Replace `hasKnowledgeMenu` and the old `groups` assignment with:
+
+```tsx
+const groups = mergePrototypeNavigation(menuGroups, user?.permissions ?? [])
+```
+
+- [ ] **Step 5: Create the operations dashboard**
+
+Create `frontend/src/features/dashboard/DashboardPage.tsx`:
+
+```tsx
+import { ArrowRight, Plus, ShieldAlert } from 'lucide-react'
+
+import { AppMetricCard } from '@/components/app/AppMetricCard'
+import { AppPage } from '@/components/app/AppPage'
+import { AppPageHeader } from '@/components/app/AppPageHeader'
+import { AppApprovalPanel } from '@/components/app/AppApprovalPanel'
+import { AppPrototypeNotice } from '@/components/app/AppPrototypeNotice'
+import { AppRiskBadge } from '@/components/app/AppRiskBadge'
+import { AppSection } from '@/components/app/AppSection'
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
+import { agentRuns, dashboardMetrics, mcpTools } from '@/features/prototype/mockData'
+
+const runStatusLabels = { success: '成功', failed: '失败', running: '运行中', waiting_confirm: '待确认' }
+
+export function DashboardPage() {
+ return (
+
+ (window.location.hash = '/tickets/new')}> 新建工单}
+ />
+
+
+ {dashboardMetrics.map((metric) =>
)}
+
+
+
(window.location.hash = '/agents/runs')}>查看全部 }>
+
+ {agentRuns.map((run) => (
+
(window.location.hash = `/agents/runs/${run.id}`)}>
+ {run.agent_name} {runStatusLabels[run.status]}
+ {run.output_summary}
+ 关联工单:{run.ticket_no} · 耗时 {run.duration_ms} ms
+
+ ))}
+
+
+
+
+ {mcpTools.map((tool) =>
{tool.name}
{tool.server_name}
)}
+
非只读工具不会自动执行。
+
+
+
+
+ )
+}
+```
+
+- [ ] **Step 6: Wire the dashboard route**
+
+Add to `frontend/src/app/router.tsx`:
+
+```tsx
+import { DashboardPage } from '@/features/dashboard/DashboardPage'
+```
+
+Add before the `/` branch:
+
+```tsx
+if (route === '/dashboard') {
+ return
+}
+```
+
+- [ ] **Step 7: Write and run the dashboard test**
+
+Create `frontend/src/features/dashboard/DashboardPage.test.tsx`:
+
+```tsx
+import { render, screen } from '@testing-library/react'
+import { describe, expect, it } from 'vitest'
+
+import { DashboardPage } from './DashboardPage'
+
+describe('业务概览', () => {
+ it('展示演示标识、业务指标和可信边界', () => {
+ render( )
+ expect(screen.getByRole('heading', { name: '业务概览' })).toBeInTheDocument()
+ expect(screen.getByText('当前页面使用演示数据')).toBeInTheDocument()
+ expect(screen.getByText('智能体运行')).toBeInTheDocument()
+ expect(screen.getByText('非只读工具不会自动执行。')).toBeInTheDocument()
+ })
+})
+```
+
+Run:
+
+```powershell
+npm run test:run -- src/features/prototype/navigation.test.ts src/features/dashboard/DashboardPage.test.tsx
+npm run typecheck
+npm run build
+```
+
+Expected: both tests pass; typecheck and build exit with code 0.
+
+- [ ] **Step 8: Commit Task 3**
+
+```powershell
+git add frontend/src/features/prototype/navigation.ts frontend/src/features/prototype/navigation.test.ts frontend/src/features/dashboard frontend/src/components/app/AppSidebar.tsx frontend/src/app/router.tsx
+git commit -m "feat: add stage a navigation dashboard"
+```
+
+---
+
+### Task 4: Model Channel And Model Testing Prototypes
+
+**Files:**
+- Create: `frontend/src/features/modelhub/ModelProviderPage.tsx`
+- Create: `frontend/src/features/modelhub/ModelTestingPage.tsx`
+- Test: `frontend/src/features/modelhub/modelhub-pages.test.tsx`
+- Modify: `frontend/src/app/router.tsx`
+
+**Interfaces:**
+- Consumes: `modelProviders`, `modelTestResults`。
+- Produces routes: `/modelhub/providers`, `/modelhub/testing`。
+- Stage B contract: provider list fields and model test result fields must remain stable.
+
+- [ ] **Step 1: Write failing model-page interaction tests**
+
+Create `frontend/src/features/modelhub/modelhub-pages.test.tsx`:
+
+```tsx
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { describe, expect, it } from 'vitest'
+
+import { ModelProviderPage } from './ModelProviderPage'
+import { ModelTestingPage } from './ModelTestingPage'
+
+describe('模型管理原型', () => {
+ it('可以演示模型渠道连接测试', async () => {
+ render( )
+ await userEvent.click(screen.getByRole('button', { name: '测试连接' }))
+ expect(screen.getByText('演示连接成功,响应耗时 1,280 ms。')).toBeInTheDocument()
+ })
+
+ it('可以运行一次模型测试并展示输出指标', async () => {
+ render( )
+ await userEvent.click(screen.getByRole('button', { name: '运行测试' }))
+ expect(screen.getByText('测试输出')).toBeInTheDocument()
+ expect(screen.getByText(/输入 86 tokens/)).toBeInTheDocument()
+ })
+})
+```
+
+- [ ] **Step 2: Run tests and verify RED**
+
+```powershell
+npm run test:run -- src/features/modelhub/modelhub-pages.test.tsx
+```
+
+Expected: FAIL because both page modules do not exist.
+
+- [ ] **Step 3: Implement the provider page**
+
+Create `frontend/src/features/modelhub/ModelProviderPage.tsx`:
+
+```tsx
+import * as React from 'react'
+import { Plus, Wifi } from 'lucide-react'
+
+import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
+import { AppField } from '@/components/app/AppField'
+import { AppPage } from '@/components/app/AppPage'
+import { AppPageHeader } from '@/components/app/AppPageHeader'
+import { AppPrototypeNotice } from '@/components/app/AppPrototypeNotice'
+import { AppSection } from '@/components/app/AppSection'
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
+import { Input } from '@/components/ui/input'
+import { Switch } from '@/components/ui/switch'
+import { modelProviders } from '@/features/prototype/mockData'
+import type { ModelProvider } from '@/features/prototype/types'
+import { formatDateTime } from '@/lib/format'
+
+const healthLabels = { healthy: '正常', degraded: '性能下降', unchecked: '未检查' }
+
+const columns: AppDataTableColumn[] = [
+ { key: 'name', header: '渠道名称', cell: (provider) => {provider.name} },
+ { key: 'health', header: '健康状态', cell: (provider) => {healthLabels[provider.health]} },
+ { key: 'protocol', header: '协议', cell: (provider) => provider.protocol },
+ { key: 'models', header: '模型数量', cell: (provider) => provider.model_count },
+ { key: 'checked', header: '最后检查', cell: (provider) => {formatDateTime(provider.last_checked_at)} }
+]
+
+export function ModelProviderPage() {
+ const [selected, setSelected] = React.useState(modelProviders[0])
+ const [dialogOpen, setDialogOpen] = React.useState(false)
+ const [connectionMessage, setConnectionMessage] = React.useState('')
+
+ return (
+
+ setDialogOpen(true)}> 新增渠道} />
+
+
+
provider.id} emptyTitle="暂无模型渠道" onRowClick={setSelected} />
+ {selected.enabled ? '已启用' : '已停用'}}>
+
+
+
+
+
setConnectionMessage('演示连接成功,响应耗时 1,280 ms。')}> 测试连接
+ {connectionMessage ?
{connectionMessage}
: null}
+
+
+
+
+
+ 新增模型渠道 当前只演示字段和交互,不会保存配置。
+
+ setDialogOpen(false)}>取消 setDialogOpen(false)}>确认演示
+
+
+
+ )
+}
+```
+
+- [ ] **Step 4: Implement the model-testing page**
+
+Create `frontend/src/features/modelhub/ModelTestingPage.tsx`:
+
+```tsx
+import * as React from 'react'
+import { Play } from 'lucide-react'
+
+import { AppField } from '@/components/app/AppField'
+import { AppPage } from '@/components/app/AppPage'
+import { AppPageHeader } from '@/components/app/AppPageHeader'
+import { AppPrototypeNotice } from '@/components/app/AppPrototypeNotice'
+import { AppSection } from '@/components/app/AppSection'
+import { Button } from '@/components/ui/button'
+import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
+import { Textarea } from '@/components/ui/textarea'
+import { modelProviders, modelTestResults } from '@/features/prototype/mockData'
+import type { ModelTestResult } from '@/features/prototype/types'
+
+export function ModelTestingPage() {
+ const [providerId, setProviderId] = React.useState(String(modelProviders[0].id))
+ const [modelName, setModelName] = React.useState('support-large')
+ const [prompt, setPrompt] = React.useState('分析采集进度长期不更新的可能原因')
+ const [result, setResult] = React.useState(null)
+
+ return (
+
+
+
+
+
+
+
{modelProviders.map((provider) => {provider.name} )}
+
support-large support-small
+
+
setResult({ ...modelTestResults[0], model_name: modelName, prompt })}> 运行测试
+
+
+
+ {result ? {result.output}
响应耗时
{result.latency_ms} ms
输入用量
输入 {result.input_tokens} tokens
输出用量
输出 {result.output_tokens} tokens
: 运行测试后展示模型输出、耗时和 Token 用量。
}
+
+
+
+ )
+}
+```
+
+- [ ] **Step 5: Wire routes and verify**
+
+Add imports to `frontend/src/app/router.tsx`:
+
+```tsx
+import { ModelProviderPage } from '@/features/modelhub/ModelProviderPage'
+import { ModelTestingPage } from '@/features/modelhub/ModelTestingPage'
+```
+
+Add branches before the `/` branch:
+
+```tsx
+if (route === '/modelhub/providers') return
+if (route === '/modelhub/testing') return
+```
+
+Run:
+
+```powershell
+npm run test:run -- src/features/modelhub/modelhub-pages.test.tsx
+npm run typecheck
+npm run build
+```
+
+Expected: two tests pass; typecheck and build exit with code 0.
+
+- [ ] **Step 6: Commit Task 4**
+
+```powershell
+git add frontend/src/features/modelhub frontend/src/app/router.tsx
+git commit -m "feat: add model management prototypes"
+```
+
+---
+
+### Task 5: Agent Configuration, Runs, And Trace Detail
+
+**Files:**
+- Create: `frontend/src/features/agents/AgentListPage.tsx`
+- Create: `frontend/src/features/agents/AgentRunsPage.tsx`
+- Create: `frontend/src/features/agents/AgentRunDetailPage.tsx`
+- Test: `frontend/src/features/agents/agent-pages.test.tsx`
+- Modify: `frontend/src/app/router.tsx`
+
+**Interfaces:**
+- Consumes: `agents`, `agentRuns`, `knowledgeReferences`。
+- Produces routes: `/agents`, `/agents/runs`, `/agents/runs/:id`。
+- Stage B contract: agent definition, run status, step type, duration and output summary remain stable.
+
+- [ ] **Step 1: Write failing Agent page tests**
+
+Create `frontend/src/features/agents/agent-pages.test.tsx`:
+
+```tsx
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { describe, expect, it } from 'vitest'
+
+import { AgentListPage } from './AgentListPage'
+import { AgentRunDetailPage } from './AgentRunDetailPage'
+
+describe('智能体中心原型', () => {
+ it('可以演示运行测试智能体', async () => {
+ render( )
+ await userEvent.click(screen.getByRole('button', { name: '运行测试' }))
+ expect(screen.getByText('已生成一条演示运行记录,不会调用真实模型。')).toBeInTheDocument()
+ })
+
+ it('运行详情展示轨迹、引用和人工确认步骤', () => {
+ render( )
+ expect(screen.getByText('执行轨迹')).toBeInTheDocument()
+ expect(screen.getByText('AI 产物等待确认')).toBeInTheDocument()
+ expect(screen.getByText('引用来源')).toBeInTheDocument()
+ })
+})
+```
+
+- [ ] **Step 2: Run tests and verify RED**
+
+```powershell
+npm run test:run -- src/features/agents/agent-pages.test.tsx
+```
+
+Expected: FAIL because Agent pages do not exist.
+
+- [ ] **Step 3: Implement the Agent list page**
+
+Create `frontend/src/features/agents/AgentListPage.tsx`:
+
+```tsx
+import * as React from 'react'
+import { Play } from 'lucide-react'
+
+import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
+import { AppPage } from '@/components/app/AppPage'
+import { AppPageHeader } from '@/components/app/AppPageHeader'
+import { AppPrototypeNotice } from '@/components/app/AppPrototypeNotice'
+import { AppSection } from '@/components/app/AppSection'
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { agents } from '@/features/prototype/mockData'
+import type { AgentDefinition } from '@/features/prototype/types'
+import { formatDateTime } from '@/lib/format'
+
+const columns: AppDataTableColumn[] = [
+ { key: 'name', header: '智能体', cell: (agent) => {agent.name} },
+ { key: 'model', header: '模型', cell: (agent) => agent.model_name },
+ { key: 'skills', header: '技能', cell: (agent) => agent.skill_count },
+ { key: 'tools', header: '工具', cell: (agent) => agent.tool_count },
+ { key: 'state', header: '状态', cell: (agent) => {agent.enabled ? '已启用' : '已停用'} },
+ { key: 'updated', header: '更新时间', cell: (agent) => {formatDateTime(agent.updated_at)} }
+]
+
+export function AgentListPage() {
+ const [selected, setSelected] = React.useState(agents[0])
+ const [message, setMessage] = React.useState('')
+
+ return (
+
+ (window.location.hash = '/agents/runs')}>查看运行记录} />
+
+
+
agent.id} emptyTitle="暂无智能体" onRowClick={setSelected} />
+ 内置智能体}>
+
+ 基本信息 能力范围
+ {selected.purpose}
默认模型
{selected.model_name}
+ 知识范围
{selected.knowledge_scopes.map((scope) => {scope} )}
绑定 {selected.skill_count} 个技能,{selected.tool_count} 个工具。
+
+ setMessage('已生成一条演示运行记录,不会调用真实模型。')}> 运行测试 {message ?
{message}
: null}
+
+
+
+ )
+}
+```
+
+- [ ] **Step 4: Implement the Agent runs page**
+
+Create `frontend/src/features/agents/AgentRunsPage.tsx`:
+
+```tsx
+import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
+import { AppPage } from '@/components/app/AppPage'
+import { AppPageHeader } from '@/components/app/AppPageHeader'
+import { AppPrototypeNotice } from '@/components/app/AppPrototypeNotice'
+import { Badge } from '@/components/ui/badge'
+import { agentRuns } from '@/features/prototype/mockData'
+import type { AgentRun } from '@/features/prototype/types'
+import { formatDateTime } from '@/lib/format'
+
+const statusLabels = { failed: '失败', running: '运行中', success: '成功', waiting_confirm: '待确认' }
+
+const columns: AppDataTableColumn[] = [
+ { key: 'id', header: '运行编号', cell: (run) => #{run.id} },
+ { key: 'agent', header: '智能体', cell: (run) => run.agent_name },
+ { key: 'ticket', header: '关联工单', cell: (run) => run.ticket_no },
+ { key: 'status', header: '状态', cell: (run) => {statusLabels[run.status]} },
+ { key: 'duration', header: '耗时', cell: (run) => `${run.duration_ms} ms` },
+ { key: 'created', header: '触发时间', cell: (run) => {formatDateTime(run.created_at)} }
+]
+
+export function AgentRunsPage() {
+ return (
+
+
+
+ run.id} emptyTitle="暂无运行记录" onRowClick={(run) => (window.location.hash = `/agents/runs/${run.id}`)} />
+
+ )
+}
+```
+
+- [ ] **Step 5: Implement the run-detail page**
+
+Create `frontend/src/features/agents/AgentRunDetailPage.tsx`:
+
+```tsx
+import { AppPage } from '@/components/app/AppPage'
+import { AppPageHeader } from '@/components/app/AppPageHeader'
+import { AppPrototypeNotice } from '@/components/app/AppPrototypeNotice'
+import { AppReferenceCard } from '@/components/app/AppReferenceCard'
+import { AppSection } from '@/components/app/AppSection'
+import { AppTraceTimeline } from '@/components/app/AppTraceTimeline'
+import { Badge } from '@/components/ui/badge'
+import { agentRuns, knowledgeReferences } from '@/features/prototype/mockData'
+
+const statusLabels = { failed: '失败', running: '运行中', success: '成功', waiting_confirm: '待确认' }
+
+export function AgentRunDetailPage({ runId }: { runId: number }) {
+ const run = agentRuns.find((item) => item.id === runId) ?? agentRuns[0]
+
+ return (
+
+ {statusLabels[run.status]}} />
+
+ {run.status === 'waiting_confirm' ? : null}
+
+
+
{knowledgeReferences.map((reference) =>
)}
+
+
+ )
+}
+```
+
+- [ ] **Step 6: Wire Agent routes and verify**
+
+Add imports to `frontend/src/app/router.tsx`:
+
+```tsx
+import { AgentListPage } from '@/features/agents/AgentListPage'
+import { AgentRunDetailPage } from '@/features/agents/AgentRunDetailPage'
+import { AgentRunsPage } from '@/features/agents/AgentRunsPage'
+```
+
+Add branches before the `/` branch and keep the exact route before the dynamic route:
+
+```tsx
+if (route === '/agents') return
+if (route === '/agents/runs') return
+
+const agentRunMatch = route.match(/^\/agents\/runs\/(\d+)$/)
+if (agentRunMatch) return
+```
+
+Run:
+
+```powershell
+npm run test:run -- src/features/agents/agent-pages.test.tsx
+npm run typecheck
+npm run build
+```
+
+Expected: two tests pass; typecheck and build exit with code 0.
+
+- [ ] **Step 7: Commit Task 5**
+
+```powershell
+git add frontend/src/features/agents frontend/src/app/router.tsx
+git commit -m "feat: add agent management prototypes"
+```
+
+---
+
+### Task 6: Skill Configuration And Test Prototype
+
+**Files:**
+- Create: `frontend/src/features/skills/SkillListPage.tsx`
+- Test: `frontend/src/features/skills/SkillListPage.test.tsx`
+- Modify: `frontend/src/app/router.tsx`
+
+**Interfaces:**
+- Consumes: `skills` and `SkillDefinition`。
+- Produces route: `/skills`。
+- Stage B contract: scenario, version, prompt template, input schema and output schema remain stable.
+
+- [ ] **Step 1: Write the failing Skill page test**
+
+Create `frontend/src/features/skills/SkillListPage.test.tsx`:
+
+```tsx
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { describe, expect, it } from 'vitest'
+
+import { SkillListPage } from './SkillListPage'
+
+describe('技能配置原型', () => {
+ it('展示配置边界并可以运行演示测试', async () => {
+ render( )
+ expect(screen.getByText('技能只保存模板和结构,不执行任意代码。')).toBeInTheDocument()
+ await userEvent.click(screen.getByRole('tab', { name: '测试输入' }))
+ await userEvent.click(screen.getByRole('button', { name: '运行测试' }))
+ expect(screen.getByText('演示输出:已识别为业务问题,建议补充任务编号。')).toBeInTheDocument()
+ })
+})
+```
+
+- [ ] **Step 2: Run the test and verify RED**
+
+```powershell
+npm run test:run -- src/features/skills/SkillListPage.test.tsx
+```
+
+Expected: FAIL because `SkillListPage.tsx` does not exist.
+
+- [ ] **Step 3: Implement the Skill page**
+
+Create `frontend/src/features/skills/SkillListPage.tsx`:
+
+```tsx
+import * as React from 'react'
+import { Play, ShieldCheck } from 'lucide-react'
+
+import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
+import { AppField } from '@/components/app/AppField'
+import { AppPage } from '@/components/app/AppPage'
+import { AppPageHeader } from '@/components/app/AppPageHeader'
+import { AppPrototypeNotice } from '@/components/app/AppPrototypeNotice'
+import { AppSection } from '@/components/app/AppSection'
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { Textarea } from '@/components/ui/textarea'
+import { skills } from '@/features/prototype/mockData'
+import type { SkillDefinition } from '@/features/prototype/types'
+import { formatDateTime } from '@/lib/format'
+
+const scenarioLabels = { field_extract: '字段抽取', missing_info: '缺失信息追问', solution_recommend: '方案推荐', reply_draft: '客户回复', knowledge_draft: '知识草稿' }
+
+const columns: AppDataTableColumn[] = [
+ { key: 'name', header: '技能名称', cell: (skill) => {skill.name} },
+ { key: 'scenario', header: '应用场景', cell: (skill) => {scenarioLabels[skill.scenario]} },
+ { key: 'version', header: '版本', cell: (skill) => skill.version },
+ { key: 'enabled', header: '状态', cell: (skill) => {skill.enabled ? '已启用' : '已停用'} },
+ { key: 'updated', header: '更新时间', cell: (skill) => {formatDateTime(skill.updated_at)} }
+]
+
+export function SkillListPage() {
+ const [selected, setSelected] = React.useState(skills[0])
+ const [result, setResult] = React.useState('')
+
+ return (
+
+
+
+ 技能只保存模板和结构,不执行任意代码。
+
+
skill.id} emptyTitle="暂无技能" onRowClick={(skill) => { setSelected(skill); setResult('') }} />
+ {selected.version}}>
+
+ 提示词模板 输入输出结构 测试输入
+
+
+ setResult('演示输出:已识别为业务问题,建议补充任务编号。')}> 运行测试 {result ? {result}
: null}
+
+
+
+
+ )
+}
+```
+
+- [ ] **Step 4: Wire the route and verify**
+
+Add to `frontend/src/app/router.tsx`:
+
+```tsx
+import { SkillListPage } from '@/features/skills/SkillListPage'
+```
+
+Add before the `/` branch:
+
+```tsx
+if (route === '/skills') return
+```
+
+Run:
+
+```powershell
+npm run test:run -- src/features/skills/SkillListPage.test.tsx
+npm run typecheck
+npm run build
+```
+
+Expected: one test passes; typecheck and build exit with code 0.
+
+- [ ] **Step 5: Commit Task 6**
+
+```powershell
+git add frontend/src/features/skills frontend/src/app/router.tsx
+git commit -m "feat: add skill configuration prototype"
+```
+
+---
+
+### Task 7: MCP Services, Tools, Risk, And Audit Prototype
+
+**Files:**
+- Create: `frontend/src/features/tools/McpToolsPage.tsx`
+- Test: `frontend/src/features/tools/McpToolsPage.test.tsx`
+- Modify: `frontend/src/app/router.tsx`
+
+**Interfaces:**
+- Consumes: `mcpServers`, `mcpTools`, `toolCalls`。
+- Produces route: `/tools/mcp`。
+- Stage D contract: server state, read/write access mode, risk level, human-confirm flag, call status and duration remain stable.
+
+- [ ] **Step 1: Write the failing MCP risk test**
+
+Create `frontend/src/features/tools/McpToolsPage.test.tsx`:
+
+```tsx
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { describe, expect, it } from 'vitest'
+
+import { McpToolsPage } from './McpToolsPage'
+
+describe('工具接入原型', () => {
+ it('高风险工具展示人工确认边界', async () => {
+ render( )
+ await userEvent.click(screen.getByRole('tab', { name: '工具' }))
+ await userEvent.click(screen.getByRole('button', { name: '查看确认规则' }))
+ expect(screen.getByRole('dialog', { name: '高风险工具确认规则' })).toBeInTheDocument()
+ expect(screen.getByText('高风险和非只读工具不会由智能体自动执行。')).toBeInTheDocument()
+ })
+})
+```
+
+- [ ] **Step 2: Run the test and verify RED**
+
+```powershell
+npm run test:run -- src/features/tools/McpToolsPage.test.tsx
+```
+
+Expected: FAIL because `McpToolsPage.tsx` does not exist.
+
+- [ ] **Step 3: Implement the MCP page**
+
+Create `frontend/src/features/tools/McpToolsPage.tsx`:
+
+```tsx
+import * as React from 'react'
+import { ShieldAlert } from 'lucide-react'
+
+import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
+import { AppPage } from '@/components/app/AppPage'
+import { AppPageHeader } from '@/components/app/AppPageHeader'
+import { AppPrototypeNotice } from '@/components/app/AppPrototypeNotice'
+import { AppRiskBadge } from '@/components/app/AppRiskBadge'
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { mcpServers, mcpTools, toolCalls } from '@/features/prototype/mockData'
+import type { McpServer, McpTool, ToolCall } from '@/features/prototype/types'
+import { formatDateTime } from '@/lib/format'
+
+const serverColumns: AppDataTableColumn[] = [
+ { key: 'name', header: '服务名称', cell: (server) => {server.name} },
+ { key: 'state', header: '连接状态', cell: (server) => {server.connection_state === 'connected' ? '已连接' : server.connection_state === 'error' ? '连接异常' : '未连接'} },
+ { key: 'auth', header: '认证方式', cell: (server) => server.auth_type },
+ { key: 'tools', header: '工具数量', cell: (server) => server.tool_count },
+ { key: 'sync', header: '最后同步', cell: (server) => {formatDateTime(server.last_sync_at)} }
+]
+
+const callColumns: AppDataTableColumn[] = [
+ { key: 'tool', header: '工具', cell: (call) => {call.tool_name} },
+ { key: 'agent', header: '智能体', cell: (call) => call.agent_name },
+ { key: 'input', header: '输入摘要', cell: (call) => {call.input_summary} },
+ { key: 'status', header: '状态', cell: (call) => {call.status === 'success' ? '成功' : call.status === 'blocked' ? '已阻止' : '失败'} },
+ { key: 'time', header: '调用时间', cell: (call) => {formatDateTime(call.created_at)} }
+]
+
+export function McpToolsPage() {
+ const [confirmOpen, setConfirmOpen] = React.useState(false)
+
+ const toolColumns: AppDataTableColumn[] = [
+ { key: 'name', header: '工具名称', cell: (tool) => {tool.name}
{tool.server_name}
},
+ { key: 'description', header: '能力说明', cell: (tool) => {tool.description} },
+ { key: 'mode', header: '访问方式', cell: (tool) => {tool.access_mode === 'read' ? '只读' : '写入'} },
+ { key: 'risk', header: '风险等级', cell: (tool) => },
+ { key: 'confirm', header: '人工确认', cell: (tool) => {tool.requires_human_confirm ? '必须确认' : '无需确认'} },
+ { key: 'state', header: '状态', cell: (tool) => {tool.enabled ? '已启用' : '已停用'} },
+ { key: 'action', header: '操作', cell: (tool) => tool.access_mode === 'write' ? { event.stopPropagation(); setConfirmOpen(true) }}> 查看确认规则 : null }
+ ]
+
+ return (
+
+
+
+
+ 服务 工具 调用审计
+ server.id} emptyTitle="暂无工具服务" />
+ tool.id} emptyTitle="暂无工具" />
+ call.id} emptyTitle="暂无调用记录" />
+
+
+
+ 高风险工具确认规则 高风险和非只读工具不会由智能体自动执行。
+ 真实调用前必须展示操作对象、输入摘要和影响范围。
用户确认结果必须进入工具调用记录和操作审计。
+ setConfirmOpen(false)}>我知道了
+
+
+
+ )
+}
+```
+
+- [ ] **Step 4: Wire the route and verify**
+
+Add to `frontend/src/app/router.tsx`:
+
+```tsx
+import { McpToolsPage } from '@/features/tools/McpToolsPage'
+```
+
+Add before the `/` branch:
+
+```tsx
+if (route === '/tools/mcp') return
+```
+
+Run:
+
+```powershell
+npm run test:run -- src/features/tools/McpToolsPage.test.tsx
+npm run typecheck
+npm run build
+```
+
+Expected: one test passes; typecheck and build exit with code 0.
+
+- [ ] **Step 5: Commit Task 7**
+
+```powershell
+git add frontend/src/features/tools frontend/src/app/router.tsx
+git commit -m "feat: add mcp trust boundary prototype"
+```
+
+---
+
+### Task 8: Ticket AI Panels And Existing Page Alignment
+
+**Files:**
+- Create: `frontend/src/features/tickets/TicketAiCreatePanel.tsx`
+- Create: `frontend/src/features/tickets/TicketAiAnalysisPanel.tsx`
+- Test: `frontend/src/features/tickets/ticket-ai-panels.test.tsx`
+- Modify: `frontend/src/features/tickets/TicketCreatePage.tsx`
+- Modify: `frontend/src/features/tickets/TicketDetailPage.tsx`
+- Modify: `frontend/src/features/knowledge/KnowledgeSearchPage.tsx`
+- Modify: `frontend/src/features/solutions/SolutionListPage.tsx`
+
+**Interfaces:**
+- Consumes: `knowledgeReferences`, `similarTickets`。
+- Produces: `TicketAiCreatePanel`, `TicketAiAnalysisPanel`。
+- Safety rule: these components never call `createTicket`, `transitionTicket`, solution APIs or knowledge APIs.
+
+- [ ] **Step 1: Write failing ticket AI panel tests**
+
+Create `frontend/src/features/tickets/ticket-ai-panels.test.tsx`:
+
+```tsx
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { describe, expect, it } from 'vitest'
+
+import { TicketAiAnalysisPanel } from './TicketAiAnalysisPanel'
+import { TicketAiCreatePanel } from './TicketAiCreatePanel'
+
+describe('工单 AI 原型面板', () => {
+ it('建单辅助只生成可确认草稿', async () => {
+ render( )
+ await userEvent.click(screen.getByRole('button', { name: '生成演示草稿' }))
+ expect(screen.getByText('建议分类:业务问题')).toBeInTheDocument()
+ expect(screen.getByText('不会自动写入当前表单。')).toBeInTheDocument()
+ })
+
+ it('支持分析展示建议、引用和相似工单', async () => {
+ render( )
+ expect(screen.getByText('建议先查询采集任务状态和最近一次接口响应。')).toBeInTheDocument()
+ await userEvent.click(screen.getByRole('tab', { name: '引用来源' }))
+ expect(screen.getByText('采集任务长时间无进度的排查步骤')).toBeInTheDocument()
+ await userEvent.click(screen.getByRole('tab', { name: '相似工单' }))
+ expect(screen.getByText('T202607030005')).toBeInTheDocument()
+ })
+})
+```
+
+- [ ] **Step 2: Run tests and verify RED**
+
+```powershell
+npm run test:run -- src/features/tickets/ticket-ai-panels.test.tsx
+```
+
+Expected: FAIL because both panel modules do not exist.
+
+- [ ] **Step 3: Implement the create-assistant panel**
+
+Create `frontend/src/features/tickets/TicketAiCreatePanel.tsx`:
+
+```tsx
+import * as React from 'react'
+import { Sparkles } from 'lucide-react'
+
+import { AppReferenceCard } from '@/components/app/AppReferenceCard'
+import { AppSection } from '@/components/app/AppSection'
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
+import { knowledgeReferences } from '@/features/prototype/mockData'
+
+export function TicketAiCreatePanel() {
+ const [generated, setGenerated] = React.useState(false)
+
+ return (
+ 演示数据}>
+
+
根据问题描述生成字段草稿、缺失信息和相似方案,由用户确认后再创建工单。
+
setGenerated(true)}> 生成演示草稿
+ {generated ?
建议分类:业务问题
建议补充:采集任务编号和客户所属机构。
不会自动写入当前表单。
: null}
+
+
+ )
+}
+```
+
+- [ ] **Step 4: Implement the support-analysis panel**
+
+Create `frontend/src/features/tickets/TicketAiAnalysisPanel.tsx`:
+
+```tsx
+import { Bot } from 'lucide-react'
+
+import { AppReferenceCard } from '@/components/app/AppReferenceCard'
+import { AppSection } from '@/components/app/AppSection'
+import { Badge } from '@/components/ui/badge'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { knowledgeReferences, similarTickets } from '@/features/prototype/mockData'
+
+export function TicketAiAnalysisPanel() {
+ return (
+ 演示数据}>
+
+ 分析建议 引用来源 相似工单
+ 1. 核对任务编号和当前阶段。 2. 检查上游接口超时和最近有效日志。 3. 确认仍在运行后生成客户回复。 分析结果仅供参考,不会自动改变工单状态。
+ {knowledgeReferences.map((reference) => )}
+ {similarTickets.map((ticket) => (window.location.hash = `/tickets/${ticket.id}`)}>{ticket.ticket_no} · 匹配度 {Math.round(ticket.similarity * 100)}% {ticket.title} {ticket.resolution} )}
+
+
+ )
+}
+```
+
+- [ ] **Step 5: Integrate panels without changing real API submissions**
+
+Modify `frontend/src/features/tickets/TicketCreatePage.tsx`:
+
+1. Import:
+
+```tsx
+import { TicketAiCreatePanel } from './TicketAiCreatePanel'
+```
+
+2. Remove the `Badge` import if it is no longer used.
+3. Replace the existing `AppSection` titled `AI 解决方案` with:
+
+```tsx
+
+```
+
+Do not change `initialForm`, `updateField`, `handleSubmit` or `createTicket`.
+
+Modify `frontend/src/features/tickets/TicketDetailPage.tsx`:
+
+1. Import:
+
+```tsx
+import { TicketAiAnalysisPanel } from './TicketAiAnalysisPanel'
+```
+
+2. Remove `Bot` and `Separator` imports when unused.
+3. Replace the existing `AI 支持分析` placeholder section with:
+
+```tsx
+
+```
+
+Do not change `getTicket`, `transitionTicket`, event refresh or solution adoption behavior.
+
+- [ ] **Step 6: Reuse the reference and metric components on real pages**
+
+Modify `frontend/src/features/knowledge/KnowledgeSearchPage.tsx`:
+
+1. Import `AppReferenceCard`.
+2. Remove the local `sourceLabels` mapping and `Card` imports.
+3. Replace each result card with:
+```tsx
+ base.id === item.knowledge_base_id)?.name}
+/>
+```
+
+Modify `frontend/src/features/solutions/SolutionListPage.tsx`:
+
+1. Import `AppMetricCard` and remove `Card`, `CardContent` imports.
+2. Replace the three raw metric cards with:
+
+```tsx
+
+
+
solution.status === 'published').length} change="可直接复用" tone="success" />
+ total + solution.usage_count, 0)} change="工单采纳次数" tone="ai" />
+
+```
+
+- [ ] **Step 7: Verify AI panels and existing-page compatibility**
+
+Run:
+
+```powershell
+npm run test:run -- src/features/tickets/ticket-ai-panels.test.tsx
+npm run test:run
+npm run typecheck
+npm run build
+```
+
+Expected: all frontend tests pass; typecheck and build exit with code 0. Existing ticket, solution and knowledge routes still compile against their real API types.
+
+- [ ] **Step 8: Commit Task 8**
+
+```powershell
+git add frontend/src/features/tickets/TicketAiCreatePanel.tsx frontend/src/features/tickets/TicketAiAnalysisPanel.tsx frontend/src/features/tickets/ticket-ai-panels.test.tsx frontend/src/features/tickets/TicketCreatePage.tsx frontend/src/features/tickets/TicketDetailPage.tsx frontend/src/features/knowledge/KnowledgeSearchPage.tsx frontend/src/features/solutions/SolutionListPage.tsx
+git commit -m "feat: align core pages with ai prototype"
+```
+
+---
+
+### Task 9: Chinese Copy Gate, Documentation, And Final Verification
+
+**Files:**
+- Create: `frontend/src/test/chinese-ui-copy.test.ts`
+- Create: `frontend/src/app/router.test.tsx`
+- Modify: `frontend/src/components/ui/dialog.tsx`
+- Modify: `README.md`
+- Modify: `docs/frontend/design-system.md`
+- Modify: `docs/superpowers/plans/2026-06-30-ai-ticket-platform-implementation-roadmap.md`
+- Modify: `docs/superpowers/plans/2026-07-06-stage-2-5-global-frontend-prototype.md`
+- Modify: `docs/superpowers/plans/2026-07-10-stage-a-global-product-prototype.md`
+
+**Interfaces:**
+- Produces: a source-level guard against common English action labels.
+- Produces: canonical documentation links from the historical roadmap to Blueprint V2 and Stage A.
+
+- [ ] **Step 1: Write the failing Chinese-copy test**
+
+Create `frontend/src/test/chinese-ui-copy.test.ts`:
+
+```ts
+// @vitest-environment node
+import fs from 'node:fs'
+import path from 'node:path'
+import { describe, expect, it } from 'vitest'
+
+function sourceFiles(directory: string): string[] {
+ return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
+ const fullPath = path.join(directory, entry.name)
+ if (entry.isDirectory()) return sourceFiles(fullPath)
+ return entry.name.endsWith('.tsx') ? [fullPath] : []
+ })
+}
+
+describe('中文界面文案', () => {
+ it('不包含常见英文操作标签', () => {
+ const root = path.resolve(process.cwd(), 'src')
+ const forbidden = /(?:>|["'])(Close|Cancel|Save|Submit|Loading)(?:<|["'])/
+ const violations = sourceFiles(root).filter((file) => forbidden.test(fs.readFileSync(file, 'utf8')))
+ expect(violations).toEqual([])
+ })
+})
+```
+
+- [ ] **Step 2: Run the copy test and verify RED**
+
+```powershell
+npm run test:run -- src/test/chinese-ui-copy.test.ts
+```
+
+Expected: FAIL and report `frontend/src/components/ui/dialog.tsx` because it contains `Close`.
+
+- [ ] **Step 3: Localize the shared Dialog close labels**
+
+Modify `frontend/src/components/ui/dialog.tsx`:
+
+```tsx
+关闭
+```
+
+and:
+
+```tsx
+关闭
+```
+
+Run:
+
+```powershell
+npm run test:run -- src/test/chinese-ui-copy.test.ts
+```
+
+Expected: PASS.
+
+- [ ] **Step 4: Add full prototype-route coverage**
+
+Create `frontend/src/app/router.test.tsx`:
+
+```tsx
+import { cleanup, render, screen } from '@testing-library/react'
+import { afterEach, describe, expect, it } from 'vitest'
+
+import { Router } from './router'
+
+afterEach(() => {
+ cleanup()
+ window.location.hash = '/'
+})
+
+describe('Stage A 路由', () => {
+ it.each([
+ ['/dashboard', '业务概览'],
+ ['/modelhub/providers', '模型渠道'],
+ ['/modelhub/testing', '模型测试'],
+ ['/agents', '智能体配置'],
+ ['/agents/runs', '智能体运行记录'],
+ ['/agents/runs/1001', '运行记录 #1001'],
+ ['/skills', '技能配置'],
+ ['/tools/mcp', '工具接入']
+ ])('路由 %s 渲染 %s', (route, title) => {
+ window.location.hash = route
+ render( )
+ expect(screen.getByText(title)).toBeInTheDocument()
+ })
+})
+```
+
+Run:
+
+```powershell
+npm run test:run -- src/app/router.test.tsx
+```
+
+Expected: eight route cases pass.
+
+- [ ] **Step 5: Update canonical project status**
+
+Replace the `README.md` Current Status paragraph with:
+
+```markdown
+Active implementation. The enterprise foundation, governed ticket workflow, reusable solutions, and knowledge retrieval baseline are complete. Stage A now provides the Chinese-first global product prototype for dashboards, model channels, Agents, Skills, MCP tools, and ticket AI assistance while preserving existing real API-backed business pages.
+```
+
+Add the Blueprint V2 and Stage A plan under `Key docs`:
+
+```markdown
+- [Blueprint V2](docs/superpowers/specs/2026-07-10-ai-ticket-platform-blueprint-v2-design.md)
+- [Stage A implementation plan](docs/superpowers/plans/2026-07-10-stage-a-global-product-prototype.md)
+```
+
+- [ ] **Step 6: Document the Stage A frontend rules**
+
+Append to `docs/frontend/design-system.md`:
+
+```markdown
+## Stage A Prototype Rules
+
+- Prototype-only modules use typed data from `frontend/src/features/prototype`; business pages continue using real APIs.
+- Every prototype page renders `AppPrototypeNotice` and must not claim that a configuration was saved or a real AI/tool call ran.
+- Prototype navigation is permission-aware and uses the existing `agent.use`, `agent.manage`, `model.manage`, and `tool.manage` permissions.
+- AI references use `AppReferenceCard`; Agent steps use `AppTraceTimeline`; human confirmation uses `AppApprovalPanel`; tool risk uses `AppRiskBadge`; dashboard metrics use `AppMetricCard`.
+- Frontend text is Chinese-first. Common English action labels are rejected by `frontend/src/test/chinese-ui-copy.test.ts`.
+```
+
+- [ ] **Step 7: Mark old roadmaps as historical**
+
+Add directly below the title in `docs/superpowers/plans/2026-06-30-ai-ticket-platform-implementation-roadmap.md`:
+
+```markdown
+> **Historical roadmap:** Stage ordering in this document has been superseded by [Blueprint V2](../specs/2026-07-10-ai-ticket-platform-blueprint-v2-design.md). Continue implementation from the [Stage A plan](2026-07-10-stage-a-global-product-prototype.md).
+```
+
+Add directly below the title in `docs/superpowers/plans/2026-07-06-stage-2-5-global-frontend-prototype.md`:
+
+```markdown
+> **Superseded:** This unexecuted plan is retained for history. Its valid scope has been rewritten as [Stage A](2026-07-10-stage-a-global-product-prototype.md) under Blueprint V2.
+```
+
+- [ ] **Step 8: Run full Stage A verification**
+
+Run from `frontend/`:
+
+```powershell
+npm run test:run
+npm run typecheck
+npm run build
+$env:NODE_USE_ENV_PROXY = "1"
+$env:HTTP_PROXY = "http://127.0.0.1:7897"
+$env:HTTPS_PROXY = "http://127.0.0.1:7897"
+npx shadcn@latest info
+```
+
+Expected:
+
+- Vitest reports all tests passing.
+- TypeScript exits with code 0.
+- Vite production build exits with code 0.
+- shadcn reports the existing Vite project, `radix-nova` style, CSS variables and Lucide icons.
+
+Run from the repository root:
+
+```powershell
+rg -n "route === '/(dashboard|agents|skills|modelhub|tools)|agentRunMatch" frontend/src/app/router.tsx
+rg -n "#/dashboard|#/agents|#/agents/runs|#/skills|#/modelhub/providers|#/modelhub/testing|#/tools/mcp" frontend/src/features/prototype/navigation.ts
+git diff --check
+git status --short
+```
+
+Expected: all Stage A routes and menu paths are present; `git diff --check` exits with code 0; status contains only Stage A files plus pre-existing unrelated local files.
+
+- [ ] **Step 9: Mark this plan complete**
+
+After every verification command succeeds, change all completed `- [ ]` checkboxes in this file to `- [x]`. Do not mark a checkbox whose command or acceptance condition failed.
+
+- [ ] **Step 10: Commit Task 9**
+
+```powershell
+git add frontend/src/test/chinese-ui-copy.test.ts frontend/src/app/router.test.tsx frontend/src/components/ui/dialog.tsx README.md docs/frontend/design-system.md docs/superpowers/plans/2026-06-30-ai-ticket-platform-implementation-roadmap.md docs/superpowers/plans/2026-07-06-stage-2-5-global-frontend-prototype.md docs/superpowers/plans/2026-07-10-stage-a-global-product-prototype.md
+git commit -m "docs: complete stage a product prototype"
+```
+
+---
+
+## Final Review Checklist
+
+- [ ] System administrators can reach the full Stage A product map from the sidebar.
+- [ ] Users without `agent.manage`, `model.manage`, or `tool.manage` do not see those management entries.
+- [ ] `/dashboard` renders Chinese operational metrics and the demo-data notice.
+- [ ] `/modelhub/providers` and `/modelhub/testing` provide non-persistent interactions.
+- [ ] `/agents`, `/agents/runs`, and `/agents/runs/:id` show configuration, runs, traces, and references.
+- [ ] `/skills` shows prompt, schema, and test surfaces without arbitrary code execution.
+- [ ] `/tools/mcp` shows servers, tools, risk, confirmation boundaries, and call audit.
+- [ ] Ticket create and detail pages expose AI assistance without changing existing API submissions.
+- [ ] Knowledge search uses real API data through the shared reference component.
+- [ ] Existing ticket, solution, knowledge, system, audit, login, mobile navigation, and theme behavior still compile.
+- [ ] All prototype pages explicitly identify demo data.
+- [ ] All user-facing frontend copy is Chinese-first.
+- [ ] `npm run test:run` passes.
+- [ ] `npm run typecheck` passes.
+- [ ] `npm run build` passes.
+- [ ] `npx shadcn@latest info` reports the expected project configuration.
+- [ ] Blueprint V2, the historical roadmap, the old Stage 2.5 plan, README, design-system docs, and this plan agree on Stage A status.