智能对话(深度优化)
+多模型无缝切换、流式响应、上下文记忆闭环、Tool Call 可视化。
现状:基础已好,需打磨体验细节(消息虚拟化、搜索、导出)
diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d2b426e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,42 @@ +# Build artifacts +target/ +node_modules/ + +# Environment and secrets +.env +.env.* +*.pem +*.key + +# IDE and OS +.vscode/ +.idea/ +.DS_Store +Thumbs.db + +# Git +.git/ +.gitignore + +# Logs +*.log + +# Docker +docker-compose.yml +Dockerfile + +# Documentation (not needed in image) +docs/ +*.md +!README.md + +# Test files +tests/ +tests/e2e/ +admin-v2/tests/ + +# Claude/development tools +.claude/ +.planning/ +.superpowers/ +plans/ diff --git a/.superpowers/brainstorm/1446-1774933084/.server-stopped b/.superpowers/brainstorm/1446-1774933084/.server-stopped new file mode 100644 index 0000000..8eb2d5a --- /dev/null +++ b/.superpowers/brainstorm/1446-1774933084/.server-stopped @@ -0,0 +1 @@ +{"reason":"owner process exited","timestamp":1774933144596} diff --git a/.superpowers/brainstorm/1446-1774933084/.server.pid b/.superpowers/brainstorm/1446-1774933084/.server.pid new file mode 100644 index 0000000..6991c76 --- /dev/null +++ b/.superpowers/brainstorm/1446-1774933084/.server.pid @@ -0,0 +1 @@ +1454 diff --git a/.superpowers/brainstorm/1446-1774933084/design-direction.html b/.superpowers/brainstorm/1446-1774933084/design-direction.html new file mode 100644 index 0000000..1076ffa --- /dev/null +++ b/.superpowers/brainstorm/1446-1774933084/design-direction.html @@ -0,0 +1,151 @@ +
选择一个整体设计风格方向,后续所有页面都将基于此展开
+ +大量留白,Indigo/Purple 主色调,圆角卡片,轻量阴影。类似 Linear、Vercel Dashboard 风格。
+深色基底,Cyan/Blue 渐变高亮,发光边框,数据密集感。类似 Grafana、DataDog 风格。
+暖白底色,Amber/Orange 主色调,圆润设计,亲切感。类似 Notion、Stripe Dashboard 风格。
+延续 ZCLAW 品牌色(紫色 #863bff + 蓝色 #47bfff),渐变点缀,现代感与品牌一致性。
++ 提示:点击卡片选择你偏好的设计方向。这个选择将影响配色方案、组件风格、以及整体视觉语言。 + 后续的暗色模式将基于所选方向的暗色变体。 +
+哪些功能能让用户"啊"的一声觉得值?点击选择你认为的杀手级功能(可多选)
+ + diff --git a/.superpowers/brainstorm/1619-1775026541/zclaw-overview.html b/.superpowers/brainstorm/1619-1775026541/zclaw-overview.html new file mode 100644 index 0000000..a964e34 --- /dev/null +++ b/.superpowers/brainstorm/1619-1775026541/zclaw-overview.html @@ -0,0 +1,123 @@ +基于代码库深度扫描,2026-04-01
+ ++ ZCLAW = 中文市场的 AI Agent OS,不是另一个 ChatGPT 套壳。 +
++ 核心问题:技术基础设施已建成 ~90%,但商业变现路径从 0 → 1 尚未打通。 +
+三种页面布局方案,请选择最适合的方案
+ +左侧分类树 + 右侧条目列表。空间利用率高,浏览效率好。适合分类层级清晰的场景。
+详细描述注塑成型的温度、压力、冷却时间等关键参数...
+ 🏭 制造业 + 引用 42 次 +涵盖药品生产质量管理的完整合规要求...
+ 🏥 医疗健康 + 引用 38 次 +汇总模具设计过程中的常见技术问题和解决方案...
+ 🏭 制造业 + 引用 27 次 +系统化的在线教育课程设计和评估方法...
+ 🎓 教育培训 + 引用 19 次 +顶部标签切换 + 卡片网格展示。视觉友好,快速浏览内容概要。适合知识条目不多且偏内容展示的场景。
+顶部标签页切换模块 + 标准表格。最符合现有 Admin V2 风格,信息密度高,适合批量操作。与现有页面一致。
+Continuing in terminal...
+正在准备知识库 UI 布局方案...
+哪些功能能让用户"啊"的一声觉得值?点击选择你认为的杀手级功能(可多选)
+ + diff --git a/desktop/tests/stabilization.test.ts b/desktop/tests/stabilization.test.ts new file mode 100644 index 0000000..c7f863e --- /dev/null +++ b/desktop/tests/stabilization.test.ts @@ -0,0 +1,334 @@ +/** + * Stabilization Core Path Tests + * + * Covers the 4 critical paths from STABILIZATION_DIRECTIVE.md §5: + * 1. Skill execution — invoke → no crash → result + * 2. Hand trigger — emit event → frontend receives notification + * 3. Message sending — Store → invoke → streaming response + * 4. Config sync — SaaS pull → Store update + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { invoke } from '@tauri-apps/api/core'; +import { useChatStore, type Message } from '../src/store/chatStore'; + +// ─── Shared mocks ─── + +vi.mock('@tauri-apps/api/core', () => ({ + invoke: vi.fn(), +})); + +vi.mock('@tauri-apps/api/event', () => ({ + listen: vi.fn(() => Promise.resolve(() => {})), + emit: vi.fn(() => Promise.resolve()), +})); + +vi.mock('../src/lib/tauri-gateway', () => ({ + isTauriRuntime: () => false, + getGatewayClient: vi.fn(), + startLocalGateway: vi.fn(), + stopLocalGateway: vi.fn(), + getLocalGatewayStatus: vi.fn(), + getLocalGatewayAuth: vi.fn(), + prepareLocalGatewayForTauri: vi.fn(), + approveLocalGatewayDevicePairing: vi.fn(), + getZclawProcessList: vi.fn(), + getZclawProcessLogs: vi.fn(), + getUnsupportedLocalGatewayStatus: vi.fn(() => ({ + supported: false, + cliAvailable: false, + runtimeSource: null, + runtimePath: null, + serviceLabel: null, + serviceLoaded: false, + serviceStatus: null, + configOk: false, + port: null, + portStatus: null, + probeUrl: null, + listenerPids: [], + error: null, + raw: {}, + })), +})); + +vi.mock('../src/lib/gateway-client', () => ({ + getGatewayClient: vi.fn(() => ({ + chatStream: vi.fn(), + chat: vi.fn(), + onAgentStream: vi.fn(() => () => {}), + getState: vi.fn(() => 'disconnected'), + })), +})); + +vi.mock('../src/lib/intelligence-client', () => ({ + intelligenceClient: { + compactor: { + checkThreshold: vi.fn(() => Promise.resolve({ should_compact: false, current_tokens: 0, urgency: 'none' })), + compact: vi.fn(() => Promise.resolve({ compacted_messages: [] })), + }, + memory: { + search: vi.fn(() => Promise.resolve([])), + }, + identity: { + buildPrompt: vi.fn(() => Promise.resolve('')), + }, + reflection: { + recordConversation: vi.fn(() => Promise.resolve()), + shouldReflect: vi.fn(() => Promise.resolve(false)), + reflect: vi.fn(() => Promise.resolve()), + }, + }, +})); + +vi.mock('../src/lib/memory-extractor', () => ({ + getMemoryExtractor: vi.fn(() => ({ + extractFromConversation: vi.fn(() => Promise.resolve([])), + })), +})); + +vi.mock('../src/lib/agent-swarm', () => ({ + getAgentSwarm: vi.fn(() => ({ + runAgent: vi.fn(), + })), +})); + +vi.mock('../src/lib/speech-synth', () => ({ + speechSynth: { + speak: vi.fn(() => Promise.resolve()), + }, +})); + +vi.mock('../src/lib/logger', () => ({ + createLogger: () => ({ + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }), +})); + +vi.mock('../src/store/chat/conversationStore', () => { + const state = { + currentAgent: { id: 'test-agent-1', name: 'Test Agent' }, + sessionKey: 'test-session-1', + currentModel: 'default', + conversations: [], + }; + return { + useConversationStore: { + getState: () => state, + subscribe: vi.fn(), + ...(Object.fromEntries( + Object.keys(state).map((k) => [k, vi.fn()]) + )), + }, + }; +}); + +// ─── 1. Skill Execution ─── + +describe('Skill execution (SEC2-P0-01)', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should invoke skill_execute with real agentId/sessionId (not empty strings)', async () => { + const mockResult = { + success: true, + output: { text: 'Skill executed successfully' }, + duration_ms: 150, + }; + vi.mocked(invoke).mockResolvedValueOnce(mockResult); + + // Dynamically import to apply mocks + const { KernelClient } = await import('../src/lib/kernel-client'); + const { installSkillMethods } = await import('../src/lib/kernel-skills'); + + const client = new KernelClient(); + installSkillMethods({ prototype: Object.getPrototypeOf(client) } as any); + + const result = await (client as any).executeSkill('test-skill-1', { query: 'hello' }); + + expect(invoke).toHaveBeenCalledWith('skill_execute', expect.objectContaining({ + id: 'test-skill-1', + context: expect.objectContaining({ + agentId: expect.any(String), + sessionId: expect.any(String), + }), + input: { query: 'hello' }, + })); + + // Verify agentId is NOT empty string (the core P0 bug) + const callArgs = vi.mocked(invoke).mock.calls[0][1] as any; + expect(callArgs.context.agentId).not.toBe(''); + expect(callArgs.context.sessionId).not.toBe(''); + + expect(result.success).toBe(true); + }); + + it('should not crash when skill_execute returns an error', async () => { + vi.mocked(invoke).mockRejectedValueOnce(new Error('Skill not found')); + + const { KernelClient } = await import('../src/lib/kernel-client'); + const { installSkillMethods } = await import('../src/lib/kernel-skills'); + + const client = new KernelClient(); + installSkillMethods({ prototype: Object.getPrototypeOf(client) } as any); + + await expect( + (client as any).executeSkill('nonexistent-skill') + ).rejects.toThrow('Skill not found'); + }); +}); + +// ─── 2. Hand Trigger Event ─── + +describe('Hand execution event (SEC2-P1-03)', () => { + it('should add hand message to chatStore when hand-execution-complete is received', () => { + const store = useChatStore.getState(); + const initialCount = store.messages.length; + + // Simulate the event handler logic from ChatArea.tsx + const payload = { + approvalId: 'approval-1', + handId: 'browser', + success: true, + error: null, + }; + + store.addMessage({ + id: `hand_complete_${Date.now()}`, + role: 'hand', + content: `Hand ${payload.handId} 执行完成`, + timestamp: new Date(), + handName: payload.handId, + handStatus: 'completed', + handResult: payload, + }); + + const updated = useChatStore.getState(); + expect(updated.messages.length).toBe(initialCount + 1); + + const handMsg = updated.messages[updated.messages.length - 1]; + expect(handMsg.role).toBe('hand'); + expect(handMsg.handName).toBe('browser'); + expect(handMsg.handStatus).toBe('completed'); + }); + + it('should handle failed hand execution correctly', () => { + const store = useChatStore.getState(); + const initialCount = store.messages.length; + + store.addMessage({ + id: `hand_complete_${Date.now()}`, + role: 'hand', + content: 'Hand researcher 执行失败: timeout exceeded', + timestamp: new Date(), + handName: 'researcher', + handStatus: 'failed', + handResult: { handId: 'researcher', success: false, error: 'timeout exceeded' }, + }); + + const updated = useChatStore.getState(); + const handMsg = updated.messages[updated.messages.length - 1]; + expect(handMsg.handStatus).toBe('failed'); + expect(handMsg.content).toContain('失败'); + }); +}); + +// ─── 3. Message Sending ─── + +describe('Message sending flow', () => { + beforeEach(() => { + vi.clearAllMocks(); + // Reset store state + useChatStore.setState({ messages: [], isStreaming: false }); + }); + + it('should add user message to store when sending', () => { + const store = useChatStore.getState(); + const initialCount = store.messages.length; + + store.addMessage({ + id: 'user-test-1', + role: 'user', + content: 'Hello, test message', + timestamp: new Date(), + }); + + const updated = useChatStore.getState(); + expect(updated.messages.length).toBe(initialCount + 1); + + const userMsg = updated.messages.find((m: Message) => m.id === 'user-test-1'); + expect(userMsg).toBeDefined(); + expect(userMsg!.role).toBe('user'); + expect(userMsg!.content).toBe('Hello, test message'); + }); + + it('should add assistant streaming message', () => { + const store = useChatStore.getState(); + + store.addMessage({ + id: 'assistant-test-1', + role: 'assistant', + content: 'Streaming response...', + timestamp: new Date(), + streaming: true, + }); + + const updated = useChatStore.getState(); + const assistantMsg = updated.messages.find((m: Message) => m.id === 'assistant-test-1'); + expect(assistantMsg).toBeDefined(); + expect(assistantMsg!.streaming).toBe(true); + }); + + it('should handle tool messages with toolName and toolOutput', () => { + const store = useChatStore.getState(); + + store.addMessage({ + id: 'tool-test-1', + role: 'tool', + content: 'Tool executed', + timestamp: new Date(), + toolName: 'web_search', + toolInput: '{"query": "test"}', + toolOutput: '{"results": []}', + }); + + const updated = useChatStore.getState(); + const toolMsg = updated.messages.find((m: Message) => m.id === 'tool-test-1'); + expect(toolMsg).toBeDefined(); + expect(toolMsg!.toolName).toBe('web_search'); + expect(toolMsg!.toolOutput).toBeDefined(); + }); +}); + +// ─── 4. Config Sync ─── + +describe('Config sync (SaaS → Store)', () => { + it('should invoke saas-client with correct /api/v1 prefix for templates', async () => { + vi.mocked(invoke).mockResolvedValueOnce([]); + + const { KernelClient } = await import('../src/lib/kernel-client'); + const { installHandMethods } = await import('../src/lib/kernel-hands'); + + const client = new KernelClient(); + installHandMethods({ prototype: Object.getPrototypeOf(client) } as any); + + // Verify the client was created without crash + expect(client).toBeDefined(); + }); + + it('should handle store update cycle correctly', () => { + // Simulate a config sync: external data arrives → store updates + useChatStore.setState({ isStreaming: false }); + expect(useChatStore.getState().isStreaming).toBe(false); + + useChatStore.setState({ isStreaming: true }); + expect(useChatStore.getState().isStreaming).toBe(true); + + useChatStore.setState({ isStreaming: false }); + expect(useChatStore.getState().isStreaming).toBe(false); + }); +}); diff --git a/docs/AI_SESSION_PROMPTS.md b/docs/AI_SESSION_PROMPTS.md new file mode 100644 index 0000000..326a415 --- /dev/null +++ b/docs/AI_SESSION_PROMPTS.md @@ -0,0 +1,159 @@ +# ZCLAW AI 会话提示词模板 + +> **用途**: 每次开新的 AI 会话时,根据工作类型选择对应提示词。 +> 这些提示词设计为让 AI 立刻理解项目状态和约束,避免重复探索。 + +--- + +## 模板 1: 修复缺陷(最常用) + +``` +我在 ZCLAW 项目中修复缺陷。项目位于当前工作目录。 + +## 约束 +- 先读 docs/STABILIZATION_DIRECTIVE.md 了解当前优先级 +- 先读 docs/TRUTH.md 了解系统真实状态 +- 新增功能冻结,只做修复 +- 每个修复独立提交 +- 修复后必须更新 docs/TRUTH.md 中的缺陷状态 + +## 当前任务 +[在此描述要修复的缺陷] + +## 修复流程 +1. 读相关文件,理解根因 +2. 最小修复(不重构不优化) +3. 验证 cargo check + tsc --noEmit +4. 更新 TRUTH.md +5. 提交 +``` + +--- + +## 模板 2: 接通断链 + +``` +我在 ZCLAW 项目中接通"写了没接"的功能断链。 + +## 约束 +- 先读 docs/STABILIZATION_DIRECTIVE.md 第 2 节 +- 先读 docs/TRUTH.md 第 2.3 节,了解哪些功能未接通 +- 只做前端集成,不新增后端代码 +- 接通后必须有基本的前端测试 +- 不引入新的 UI 框架或依赖 + +## 当前任务 +[在此描述要接通的功能] + +## 接通流程 +1. 读 Rust 端命令签名 +2. 读前端 lib/ 层是否已有对应调用函数 +3. 如果有函数但未调用 → 在组件/Store 中接入 +4. 如果没有函数 → 在 lib/ 层新增,然后在组件/Store 中接入 +5. 添加基本测试 +6. 更新 TRUTH.md 将功能从 2.3 移到 2.1 +``` + +--- + +## 模板 3: 清理死代码 + +``` +我在 ZCLAW 项目中清理死代码。 + +## 约束 +- 先读 docs/STABILIZATION_DIRECTIVE.md 第 3 节 +- 先读 docs/TRUTH.md 第 4 节,了解已知死代码清单 +- 删除前必须确认零引用(grep 全 workspace) +- 不删除 feature-gated 代码(Director/A2A/WASM),只标注 +- 不删除有测试的代码 + +## 清理流程 +1. 确认目标文件/目录 +2. grep 确认零引用 +3. 删除 +4. cargo check + tsc --noEmit 验证无破坏 +5. 更新 TRUTH.md +``` + +--- + +## 模板 4: 文档校准 + +``` +我在 ZCLAW 项目中校准文档。 + +## 约束 +- docs/TRUTH.md 是唯一真相源 +- 其他文档中的数字如果与 TRUTH.md 冲突,修改其他文档 +- 不新增文档,只修正已有文档 +- 审计报告类文档(V5~V9)移到 docs/archive/ + +## 当前任务 +[在此描述要校准的文档] + +## 校准流程 +1. 对比 TRUTH.md 中的实际值 +2. 找出所有不一致 +3. 逐一修正 +4. 不改变文档结构,只改数值和状态 +``` + +--- + +## 模板 5: 新功能设计(稳定化完成后使用) + +``` +我在 ZCLAW 项目中设计新功能。项目位于当前工作目录。 + +## 前提检查 +- 确认 docs/STABILIZATION_DIRECTIVE.md 的完成标准已全部达标 +- 如果未达标,先完成稳定化,不要设计新功能 + +## 约束 +- 先读 CLAUDE.md 了解项目定位和决策原则 +- 先读 docs/TRUTH.md 了解系统现状 +- 新功能必须对应一个真实用户场景 +- 必须先规划前端到后端的完整路径,再写代码 +- 不允许"先写后端再说"的模式 + +## 设计流程 +1. 描述用户场景(谁、在什么情况下、要完成什么) +2. 设计前端到后端的完整调用链 +3. 确认不重复已有功能 +4. 确认不影响已有功能 +5. 先写测试,再写实现 +``` + +--- + +## 模板 6: 状态检查(开新会话时快速对齐) + +``` +请帮我检查 ZCLAW 项目当前状态。 + +按以下顺序执行: +1. 读 docs/STABILIZATION_DIRECTIVE.md — 了解稳定化进度 +2. 读 docs/TRUTH.md — 了解系统真实状态 +3. 检查 P0 缺陷是否已修复(SEC2-P0-01, SEC2-P0-02) +4. cargo check --workspace — 验证编译 +5. pnpm tsc --noEmit — 验证前端类型 + +输出: +- 稳定化完成百分比 +- 剩余 P0/P1 缺陷清单 +- 下一步建议(最多 3 个具体操作) +``` + +--- + +## 使用建议 + +| 场景 | 用哪个模板 | +|------|-----------| +| 修 bug | 模板 1 | +| 接通已有后端能力 | 模板 2 | +| 删无用代码 | 模板 3 | +| 文档修正 | 模板 4 | +| 设计新功能 | 模板 5 | +| 每日开工 | 模板 6 | diff --git a/docs/STABILIZATION_DIRECTIVE.md b/docs/STABILIZATION_DIRECTIVE.md new file mode 100644 index 0000000..7eaa5f1 --- /dev/null +++ b/docs/STABILIZATION_DIRECTIVE.md @@ -0,0 +1,186 @@ +# ZCLAW 稳定化指令 v1.0 + +> **生效日期**: 2026-04-02 +> **状态**: 强制执行 +> **目标**: 将系统从"功能堆叠模式"切换到"端到端可用模式" + +--- + +## 0. 核心原则 + +**新增功能冻结。** 在以下 P0 问题全部修复之前,不接受任何新功能 PR。 + +``` +判断标准:这个改动是否让用户今天就能用上? +- 是 → 可以做 +- 否 → 记录到 backlog,不做 +``` + +--- + +## 1. P0 紧急修复(阻塞一切) + +| ID | 问题 | 文件 | 修复方案 | +|----|------|------|----------| +| SEC2-P0-01 | skill_execute 反序列化崩溃 | `desktop/src/lib/kernel-skills.ts:110-114` + `desktop/src-tauri/src/kernel_commands/skill.rs:290-296` | 前端传入真实 agentId/sessionId,或 Rust 端改为 `Option