Files
qiming/qimingclaw/crates/agent-electron-client/AGENTS.md

14 KiB
Raw Blame History

Agent 开发指南

Agent 输出规则

优先使用中文输出:回复用户、编写注释与文档、解释逻辑与方案时,默认使用中文;仅在专有名词、代码、命令、配置键名等保持英文。


项目概览

Qiming Agent Electron 客户端:多引擎 AI 助手(通过 ACP 支持 claude-code / qimingcode。跨平台、本地运行并可选沙箱支持 IMTelegram / Discord / 钉钉 / 飞书持久化偏好。沙箱支持Docker / WSL / Firejail。


架构

  • 主进程窗口、托盘、SQLite、引擎管理ACP、沙箱管理、IM 网关、进程清理、40+ IPC、上下文隔离。
  • 渲染进程React 18 + Ant Design仅通过 IPC 通信。状态通过 React Context + useState + IPC + SQLite 管理(无 Redux

服务

主进程src/main/services/):统一 Agent engines/unifiedAgent.tsACP engines/acp/,引擎管理,依赖/Shell/工作区 system/MCP/包定位/管理 packages/Computer Server computerServer.ts

渲染进程src/renderer/services/Setup/认证/AI文件服务/Lanproxy/Agent Runner沙箱/权限/技能/IM/调度/日志/API。

组件src/renderer/components/EmbeddedWebview、SetupWizard、SetupDependencies、ClientPage、SettingsPage、DependenciesPage、AgentSettings、AgentRunnerSettings、MCPSettings、LanproxySettings、SkillsSync、IMSettings、TaskSettings。


统一 Agent 与引擎

  • 引擎claude-code → claude-code-acp-tsnpm 本地包无参数qimingcode → qimingcode acpnpm 本地包,参数:['acp'])。二者均通过 stdin/stdout 使用 ACP/NDJSON。
  • 架构UnifiedAgentService统一入口、事件总线→ AcpEngine会话、同步/异步 prompt、权限、MCP 注入、SSE
  • 用法agentService.init({ engine, apiKey, model, workspaceDir, env?, mcpServers? })createSessionprompt / promptAsyncon('message.updated'|'permission.updated')respondPermissiondestroy。详见 unifiedAgent.ts
  • 隔离PATH/NODE_PATH 指向 ~/.qimingclawHOME 等设为 /tmp/qimingclaw-run-*API 密钥通过环境变量注入。
  • 会话过滤listAllSessionsDetailed() 仅返回 isReady 为 true 的引擎会话,避免崩溃/终止的 ACP 进程污染活跃会话列表。

沙箱与权限

  • 沙箱macOS Docker/App SandboxWindows Docker/WSLLinux Docker/Firejail。sandboxManager.init({ enabled, workspaceDir })execute(cmd, args)。见 sandbox.ts
  • 权限tool:read/file:read → 直接放行tool:edit/file:write/command:bash/network:http → 需用户确认。permissionManager.checkPermission(...) / approveRequest(...)。见 permissions.ts

依赖与路径

  • 必需uv随包分发、qiming-file-server、claude-code-acp-ts、qimingcode、qiming-mcp-stdio-proxy均为 npm 本地包。Node 由 Electron 提供。
  • 数据目录~/.qimingclaw/engines、workspaces、node_modules、bin、logs、qimingclaw.db。不使用 app.getPath('userData')
  • 环境:子进程会注入 PATH(含 .qimingclaw/node_modules/.binresources/uv/bin)和 NODE_PATH。见 system/dependencies.tsgetAppEnv()
  • 随包资源resources/uv/bin/uv(打包后路径:process.resourcesPath)。

会话与工作区

一个会话对应一个工作区。工作区目录由用户指定,保存前校验,随后供引擎使用。


MCP 代理与容错

  • 日志MCP 代理写入 MCP_PROXY_LOG_FILE(默认 ~/.qimingclaw/logs/mcp-proxy.log。Electron 通过 fs.watchFile 尾随并转发到 electron-log。由 McpProxyManager.start/stop 控制。
  • ResilientTransport:基于 URL 的 MCP 使用 20s 心跳,连续 3 次失败后重连,指数退避上限 60s请求队列上限 100。

日志

使用 electron-log v5,按天轮转。配置在 src/main/bootstrap/logConfig.ts

  • 日志目录~/.qimingclaw/logs/main.YYYY-MM-DD.log,并有 latest.log 符号链接。
  • 级别:文件 → debug/info控制台 → debug。
  • 保留:生产 7 天,开发 30 天。
  • PERF 性能日志:独立文件 perf.YYYY-MM-DD.log,详见 docs/PERF-LOG-REFACTOR-2026-03-24.md

注册同步与启动顺序

  • 注册POST /api/sandbox/config/regregisterClient()core/api.tssyncConfigToServer()core/auth.ts。同步端口等返回写入 lanproxy_configserverHost/serverPort必须在启动 lanproxy 之前调用。
  • 统一流程reg → mcpProxy → agent → fileServer → lanproxyreg 在所有服务启动之前完成)
  • 场景
    • 登录 → loginAndRegister → reg → 启动所有服务
    • 启动全部 → reg → 启动所有服务
    • 手动启动单个 → 直接启动(不调用 reg
    • 自动重连 → reg → 启动所有服务
  • 注册参数username、password、savedKey?、sandboxConfigValue。响应configKey、serverHost/serverPort、online、name、token
  • 详细文档:见 docs/REG-FLOW.md

reg 接口返回的 token 用于同步登录状态到 webview实现桌面端与 web 端的登录打通。

流程

reg 接口返回 token
  ↓
1. 保存到 AUTH_TOKEN持久化      ← 给后续打开 webview 用
  ↓
2. 尝试立即同步到 webview cookiename="ticket"
  ↓
  ├─ 成功 → 清除 AUTH_TOKEN        ← 已消费,清空
  │
  └─ 失败 → 保留 AUTH_TOKEN        ← 等 webview 打开时再同步

触发点

函数 场景 文件
loginAndRegister 用户登录 core/auth.ts
reRegisterClient 自动重注册 core/auth.ts
syncConfigToServer 配置同步 core/auth.ts
syncCookieAndBuildUrl 打开 webview兜底 utils/sessionUrl.ts

核心函数

  • syncSessionCookie(domain, token):将 token 写入 webview cookiename="ticket"),不设 domainhost-only不设 secure主进程根据 URL scheme 自动判断)
  • syncCookieAndBuildUrl():打开 webview 前同步 token。有 token → 检查 JWT exp 是否过期30s 宽限容忍时钟偏移)→ 未过期则覆盖 cookie已过期则只清除对应来源缓存并跳过无 token → 跳过(不清空)
  • persistTicketCookie(domain)webview 内登录成功后,将 ticket cookie 刷盘并清除 settings 中所有旧 token 缓存(AUTH_TOKEN + domain key防止下次打开 webview 时旧缓存覆盖新 ticket

安全考虑

  • token 不长期驻留内存,同步成功后立即清除
  • 日志中不记录敏感 token 值
  • 失败时保留 token 以便重试,但不影响用户体验
  • Cookie 属性host-only不设 domain避免 count=2、httpOnly、secure 由主进程根据 URL scheme 自动判断
  • 过期 token 清除精确到来源one-shot 清 AUTH_TOKENdomain cache 清 domain key避免误清另一个有效缓存

进程清理

退出时顺序agentService.destroy → Agent Runner → Lanproxy → File Server → MCP Proxy → 引擎进程 → DB。主进程变量agentRunnerProcessagentRunner:*、lanproxyProcesslanproxy:*、fileServerProcessfileServer:*)。引擎通过 IPC agent:init / agent:destroy / agent:serviceStatus 通信。


测试

使用 Vitest。主进程、渲染进程与共享代码中共 29+ 个测试文件475+ 用例。

npm test              # 监听模式
npm run test:run      # 单次运行
npm run test:coverage # 带覆盖率

测试分层

层级 范围 说明
Unit 单文件/类,外部依赖 mock 纯逻辑与状态断言
Integration 多模块协作 如 IPC handler + serviceManager

可测试性设计

  • DI 模式:如 runManualStartService(key, deps) 抽离依赖注入,便于 mock 和断言调用顺序
  • 私有属性访问:单测中使用 as any 访问私有属性(如 engines),生产代码通过 public 方法访问

开发

npm install && npm run dev
npm run build
npm run dist:mac    # macOS
npm run dist:win    # Windows
npm run dist:linux  # Linux
  • 跨平台打包Mac → Win:支持。若出现 zip: not a valid zip file,清理缓存(~/Library/Caches/electronelectron-buildernode_modules/app-builder-bin后重试。Win x64 须在 Windows/CI 上构建(原生模块跨平台编译限制)。
  • CI:标签 electron-v*release-electron.yml(独立 Release。push/PR 导致路径变更 → ci-electron.yml测试构建。Tauri 仍使用 v* → release-tauri。本地 OSS 同步:./scripts/sync-oss.sh <tag>(需 gh、jq

目录结构src/main/main.ts、preload、ipc、services/engines|packages|system|utilssrc/renderer/main.tsx、App、components、services、stylessrc/shared/resources/uv/scripts/docs/(项目文档如 TESTING-0.9.1-2026-03-19.md。别名@main/* → main@renderer/* → renderer@shared/* → shared。


配置与平台

  • 敏感配置存于 SQLite明文未加密anthropic_api_key、default_model、server_host。
  • 兼容性:多引擎 / 沙箱(Docker) / IM / 托盘 / 无命令行弹窗 — 全平台。WSL 仅 Windows。Firejail 仅 Linux。

国际化i18n规则

多语言机制

  • Locale 文件src/shared/locales/ 下 4 个文件en-US、zh-CN、zh-HK、zh-TWkey 必须完全对齐。
  • 翻译函数renderer 用 t(key, ...values)src/renderer/services/core/i18n.ts),主进程用 t(key)src/main/services/i18n.ts)。
  • Key 格式{Client}.{Scope}.{Domain}.{key},如 Claw.Auth.error.loginExpired
  • 占位符:位置占位符 t(key, arg1, arg2){0} {1};命名占位符 t(key, {error: "xxx"}){error}
  • I18N_KEYS 常量src/shared/constants.ts 中集中定义,避免拼写错误。新增 locale key 时须同步更新此常量。

启动初始化命中逻辑(新客户端)

  1. 并行初始化src/renderer/main.tsx 启动时并行执行 initSupportedLangs()initI18n()
  2. 动态语言列表src/renderer/services/i18n.ts/api/i18n/lang/list,按当前接口格式读取 data[].lang,仅纳入 status === 1 的语言,合并到 i18next.supportedLngsfetchI18nLangList() 使用 Promise 缓存,避免多处调用重复请求。
  3. 当前语言来源src/renderer/services/core/i18n.ts 优先用缓存 i18n.active_lang,无缓存则用 navigator.languagenavigator.language 为 BCP 47 格式(如 en-USzh-CNzh-TWzh-HK),内部统一转小写比较(如 zh-tw)。
  4. 主进程语言优先级src/main/main.tsapp.whenReady() 后同步语言,优先级:本地保存(i18n.active_lang> Electron 系统语言(app.getLocale()> 英文兜底("en")。
  5. 主进程同步initI18n() 完成后,渲染进程会通过 window.electronAPI.i18n.setLang() 同步主进程语言,保证托盘/对话框与页面语言一致。
  6. antd locale 命中:优先精确命中 zh-tw / zh-hk,再落到泛中文 zh,避免繁体用户被错误命中简体组件文案。

语言切换流程

  1. 预拉取handleLangConfirm()prefetchLangMap(lang)minLoadingDelay 并行,将目标语言翻译缓存到 DBPromise.allSettled,失败不阻塞)。
  2. reload:页面刷新后 _doInitI18n() 从 DB 缓存读取翻译,无需等待网络。
  3. 非本地 locale 文件的语言(如日语):无本地 JSON 文件,getLocaleMap() 回退 enUS。依赖 DB 缓存提供翻译。首次切换时 prefetchLangMap 确保缓存已写入。
  4. 中文反向映射:非中文语言使用本地 zh-CN.json 构建反向映射(buildZhValueToKeyMap),不再额外请求 query?lang=zh-cn

日志 vs UI 的语言规则

场景 函数 语言要求 是否走 i18n
日志文件输出 log.*() / logger.*() / perfLog() 仅英文
UI 提示 message.*() / notification.*() 跟随用户语言 是(t()
错误消息展示 getAuthErrorMessage() 跟随用户语言 是(t()
错误消息抛出 throw new Error() / reject() 英文(可能被 catch 后记入日志)
HTTP 响应 message { code: "0000", message: "..." } 英文API 协议)
CSV 导出表头 exportLogs() 跟随用户语言 是(t()
权限默认名称 DEFAULT_CONFIG.rules 跟随用户语言 是(t()

权限相关命名空间

Namespace Keys 用途
Claw.Permissions.* command/envVars/file/tool/url/second/deny/allowOnce/allowAlways 权限弹窗内的按钮标签、描述文本
Claw.PermissionRules.* defaultToolRead/Edit/Bash/defaultNetwork/FileRead/FileWrite 默认权限规则名称DEFAULT_CONFIG.rules
Claw.PermissionsPage.* title/description/refresh/openSettings/allGranted/cannotOpenSettings + macosAccessibility/Desc等 权限设置页面 UI + macOS 系统权限项名称/描述

避免混用:代码中引用时须与 locale key 命名空间严格对应,不可交叉使用。

核心原则

  1. logger 输出只用英文,不进 locale 文件。日志是开发者工具,应保持语言一致性。
  2. 用户可见的 UI 文本必须走 t(),包括 message.success/error/info/loading、按钮标签、表单提示等。
  3. 新增 locale key 时4 个 locale 文件 + I18N_KEYS 常量 + 代码中 t() 调用,三者同步。
  4. 主进程中的用户可见文案(如 MCP_RECONNECT_PROMPT_MESSAGE)通过主进程 t() 走 i18n避免硬编码英文。
  5. 调试工具组件不接入 i18nsrc/renderer/components/dev/ 下的调试工具面板(如 DevToolsPanel.tsx)仅在开发模式加载,所有 UI 文案硬编码英文,不走 t(),不在 locale 文件中维护对应 key。

最后更新2026-04-10