diff --git a/skills/browser-automation/atomk-desktop-development/SKILL.md b/skills/browser-automation/atomk-desktop-development/SKILL.md new file mode 100644 index 0000000..9efaeb5 --- /dev/null +++ b/skills/browser-automation/atomk-desktop-development/SKILL.md @@ -0,0 +1,2043 @@ +--- +name: atomk-desktop-development +description: AtomK-Desktop Electron 应用的开发工作流:spec 驱动开发、Gitea PR 流程、review pipeline、构建触发规则、常见构建问题。 +category: browser-automation +tags: [atomk, desktop, electron, gitea, workflow, build, pr, development] +--- + +# AtomK-Desktop 开发工作流 + +AtomK-Desktop (Electron + TypeScript + React) 的开发、审查、提交全流程。 + +## 触发条件 + +- 用户说"开发 Desktop"、"改 AtomK-Desktop"、"提交 Desktop 代码"、"给 Desktop 加功能" +- 按 spec 实现新功能 +- 修复 Desktop bug +- 用户说"下载页面"、"放到下载页"、"WooCommerce 下载"、"us1.atomk.cn" → 加载 `atomk-platform` skill 的 `references/atomk-desktop-upload.md` +- 用户说"更新下载页"、"下载页面也更新" → 见 `references/wp-download-page.md` +- **Release 发布流程**(build → COS 上传 → manifest 更新 → 下载页更新)→ 见 `references/release-workflow.md` 和 `references/wp-download-page.md` +- **Electron-Updater 自动更新**(tools/update/ latest.yml + blockmap COS 上传 + 降级防护)→ 见 `references/electron-updater-cos.md` +- **COS 公开访问**(上传后需 set ACL public-read)→ `references/cos-public-read.md` +- **Cookie 提取 `Network.enable`**(Chrome CDP 必需先 enable Network domain)→ `references/cookie-network-enable.md` +- **新增 API 代理屏幕**(Images 面板开发 + proxyPostForm 文件上传)→ `references/images-panel-development.md` +- **Desktop 内置浏览器打开 URL**(cdpNavigate 模式 + ExternalLink UI)→ `references/cdp-navigate-builtin-browser.md` +- **Desktop 技能注册表迁移**(Gitea 服务器切换 + registry URL 更新)→ `references/skills-registry-migration.md` + +## 常见构建陷阱 + +### TypeScript 类型声明未同步 + +新增 IPC 方法到 `preload/index.ts` 后,**必须同步更新** `preload/index.d.ts` 的 TypeScript 类型声明,否则 `tsc --noEmit` 报 `Property 'xxx' does not exist`。 + +```typescript +// preload/index.d.ts 同步添加 +proxyPostForm: (path: string, body: unknown) => Promise; +``` + +### 未使用的变量声明 + +`tsc --noEmit` 开启了 `noUnusedLocals`,声明但未使用的变量会阻止构建。未使用的 `setXxx` 需改为 `const [xxx] = useState(...)`,未使用的导入直接删除。 + +--- + +## 1. Repo 结构 + +``` +AtomK-Desktop/ +├── src/main/mcp/ # ChromeDevToolsMCP (Phase 0/1, v4.0.3+) +│ ├── BridgeRelayTransport.ts # MCP Transport 经 Bridge WS +│ ├── TargetManager.ts # Chrome 多 tab CDP 管理 +│ ├── CDPAdapter.ts # CDP domain 命令执行 +│ ├── tools.ts # 22 工具 (合法 JSON Schema) +│ └── MCPController.ts # McpServer + CallToolResult 分派 +├── src/main/ # Electron 主进程 +│ ├── index.ts # 入口 +│ ├── chrome-bridge.ts # CDP relay + CloudBridge +├── src/main/bridge-manager.ts # 多 profile Bridge 管理 (JWT auth_type/token 字段 v4.0.13+) +│ ├── bridge-message-router.ts # v4.0.0 playwright.*/ziniao.*/hubstudio.*/hubstudio_cdp.* 路由 +│ ├── playwright-controller.ts # v4.0.0 Playwright 控制器 +│ ├── ziniao-client.ts # v4.0.0 紫鸟 HTTP API 客户端 +│ ├── hubstudio-client.ts # v4.0.1+ Hubstudio API 客户端 +│ ├── hubstudio-cdp-controller.ts # v4.0.x HubStudio Chrome CDP 中继 (Playwright connectOverCDP) +│ └── ... +├── src/renderer/ # React 前端 +├── package.json # v4.0.0, electron ^39.2.6, playwright-core ~1.56.0 +├── docs/ # 项目文档 +└── ... +``` + +--- + +## 2. 开发流水线(Spec-Driven + 双 Review) + +``` +Spec v2.1 → 实现代码 → Claude Code 双端并行审查 + → 分类 🔴P0 / 🟡P1 / 🟢P2 + → delegate_task 并行修复 (Desktop + Bridge 各一子代理) + → 独立 PR → merge +``` + +### Claude Code 审查(双端并行) + +```bash +# Desktop 和 Bridge 并行审查 +cat file1.ts ... | claude -p "review prompt" & # Desktop +cat server.py | claude -p "review prompt" & # Bridge +``` + +详见 `references/mcp-security-review-4.0.3.md`(完整 35 项审查报告 + 三轮修复方案)。 + +### 三轮修复顺序 +| 轮次 | 级别 | 典型修复 | 分支命名 | +|:---:|:---:|------|------| +| P0 | 🔴 Critical | CDP白名单、Schema校验、auth绕过、跨用户路由 | `fix/p0-claude-review-mcp` | +| P1 | 🟡 Warning | 内存泄漏、并发去重、futures resolve、路径穿越 | `fix/p1-claude-review-mcp` | +| S | 🟢 Suggestion | ClientSession池、错误脱敏、注释修正 | `fix/s-claude-review-mcp` | + +每轮独立 PR → merge main → 下一轮(避免冲突累积)。 + +**三审流水线(Claude Code + GLM + 自身 并行,推荐)** + +用户要求:「用 Claude Code + GLM 5.1 + 你自身三重审核」。标准流程: + +已验证于 v4.1.1→v4.1.2 审查(Claude Code ×3 + GLM-5.1,发现 3🔴+5🟡), +完整报告见 `references/claude-review-v4.1.1-2026-07-06.md`。 +v4.1.7 GLM-5.2 审查(11项:2🔴+5🟡+4🟢)见 `references/glm-review-v4.1.7-2026-07-06.md`。 + +已验证于 v4.1.6 self-review(v4.1.1→v4.1.6 diff,发现 1🔴(批量上限)+1🟡(getImageUrl弱校验)+1🟢(isSafeUrl重复)),详见 pitfall #107。\n\n已验证于 v4.1.6 三件套全量审查(Claude Opus 4.8 单审 Desktop+Bridge+Server,发现 6🔴+21🟡+15🟢),详见 `references/claude-review-v4.1.6-2026-07-06.md`。 + +**Self-Review 审查清单(自身审查 pass):** + +当 Claude Code + GLM 双审完成后,自身审查聚焦以下高风险面: + +1. **批量/循环上限**:所有数组/列表操作有无 max 限制(batch 提交、文件列表、URL 列表)。Server 有限制不代表 Desktop 不需要。 +2. **URL 协议校验**:`startsWith("http")` 不等于安全校验—必须 `new URL()` + 白名单 `["http:", "https:"]`。检查所有 `src`、`href`、`cdpNavigate`、`openExternal` 调用点。 +3. **IPC handler 参数校验**:`as` 类型断言不是校验—检查 `Array.isArray()`、`typeof`、空值守卫是否到位。 +4. **Auth gate 一致性**:所有面板是否用 `authChecked` + `authReady` 双状态 gating(见 pitfall #102)。新面板是否从 Submit/Products 复制完整 auth block。 +5. **错误消息 XSS**:用户输入(URL、CSV 行)是否拼接进 error message 后直插 JSX `{error}`。React 自动转义所以通常安全,但如果用了 `dangerouslySetInnerHTML` 则高危。 +6. **proxyGet 白名单同步**:`index.ts` 和 `atomlisting.ts` 两处 `PROXY_ALLOWED_PREFIXES` 是否一致(pitfall #100)。 + +**Self-Review 快速扫描命令**: +```bash +# 差量文件列表 +git diff ..HEAD --stat +# URL 校验覆盖度 +rg 'startsWith\("http"\)' src/renderer/ # 潜在弱校验 +rg 'isSafeUrl|safeUrl|ALLOWED_PROTOCOL' src/renderer/ # 已有防护 +# Batch/loop 上限 +rg '\.length\s*(>|===?\s*0)' src/main/ --type ts | grep -v node_modules +# Auth gate 模式 +grep -l 'authChecked' src/renderer/src/screens/*/**.tsx | sort +grep -l 'authReady' src/renderer/src/screens/*/**.tsx | sort +# diff 两者输出看哪些面板有 authChecked +``` + +已验证于 v4.1.1→v4.1.2 审查(Claude Code ×3 + GLM-5.1,发现 3🔴+5🟡), +完整报告见 `references/claude-review-v4.1.1-2026-07-06.md`。 +v4.1.7 GLM-5.2 审查(11项:2🔴+5🟡+4🟢)见 `references/glm-review-v4.1.7-2026-07-06.md`。 + +已验证于 v4.1.6 self-review(v4.1.1→v4.1.6 diff,发现 1🔴(批量上限)+1🟡(getImageUrl弱校验)+1🟢(isSafeUrl重复)),详见 pitfall #107。\n\n已验证于 v4.1.6 三件套全量审查(Claude Opus 4.8 单审 Desktop+Bridge+Server,发现 6🔴+21🟡+15🟢),详见 `references/claude-review-v4.1.6-2026-07-06.md`。 + +**Self-Review 审查清单(自身审查 pass):** + +当 Claude Code + GLM 双审完成后,自身审查聚焦以下高风险面: + +1. **批量/循环上限**:所有数组/列表操作有无 max 限制(batch 提交、文件列表、URL 列表)。Server 有限制不代表 Desktop 不需要。 +2. **URL 协议校验**:`startsWith("http")` 不等于安全校验—必须 `new URL()` + 白名单 `["http:", "https:"]`。检查所有 `src`、`href`、`cdpNavigate`、`openExternal` 调用点。 +3. **IPC handler 参数校验**:`as` 类型断言不是校验—检查 `Array.isArray()`、`typeof`、空值守卫是否到位。 +4. **Auth gate 一致性**:所有面板是否用 `authChecked` + `authReady` 双状态 gating(见 pitfall #102)。新面板是否从 Submit/Products 复制完整 auth block。 +5. **错误消息 XSS**:用户输入(URL、CSV 行)是否拼接进 error message 后直插 JSX `{error}`。React 自动转义所以通常安全,但如果用了 `dangerouslySetInnerHTML` 则高危。 +6. **proxyGet 白名单同步**:`index.ts` 和 `atomlisting.ts` 两处 `PROXY_ALLOWED_PREFIXES` 是否一致(pitfall #100)。 + +**Self-Review 快速扫描命令**: +```bash +# 差量文件列表 +git diff ..HEAD --stat +# URL 校验覆盖度 +rg 'startsWith\("http"\)' src/renderer/ # 潜在弱校验 +rg 'isSafeUrl|safeUrl|ALLOWED_PROTOCOL' src/renderer/ # 已有防护 +# Batch/loop 上限 +rg '\.length\s*(>|===?\s*0)' src/main/ --type ts | grep -v node_modules +# Auth gate 模式 +grep -l 'authChecked' src/renderer/src/screens/*/**.tsx | sort +grep -l 'authReady' src/renderer/src/screens/*/**.tsx | sort +# diff 两者输出看哪些面板有 authChecked +``` + +**模型选择**:GLM-5.1 稳定可靠;GLM-5.2 (2026-07 起可用) 是 reasoning 模型, +审查质量更高但需 2-3x token 预算(`max_tokens≥8000` 防 reasoning 吃掉全部 content)。 +对超大文件 (>4000 行) 审查,GLM-5.2 的 reasoning 优势在分段提取后更明显。 +详见 `zhipu-coding-subagent` skill。 + +**R1(首轮三重并行审查):** +```python +# 三个审查同时启动 +terminal("cat changed.ts | claude -p 'review...'", background=true, notify=true) # Claude +delegate_task(goal="审查全部新增代码...", toolsets=["terminal","file"]) # GLM-5.1 +# 自身审查 — 直接在当前上下文中审查 +``` + +**输出**:三个独立审查报告 → 合并去重 → 生成统一 🔴🟡🟢 分类表 → 逐级修复。 + +**三重审查合并报告格式**: +| # | 级别 | 问题 | GLM-5.1 | Claude | Self | +|---|---|---|---|---|---| +每行标注哪方发现了此问题,便于评估置信度。三方都发现的 🔴 → 最高优先级。 + +**R1→修复→R2(二轮验证修复):** +修复所有 🔴 后再次并行审查,验证修复正确性且无回归。 +R2 用同模式,prompt 改为: +- GLM: "R2审查:检查R1修复是否正确,是否有回归或新问题" +- Claude: "R2 review: check if R1 fixes were correctly implemented" + +**每轮输出格式**:🔴 CRITICAL / 🟡 WARNING / 🟢 SUGGESTION,文件+行号+描述+修复建议。 + +### 双审流水线(GLM-5.1 + Claude Code 并行,旧模式) + +用户单独要求双审时使用。流程同三审但去掉自身审查。 + +### delegate_task 并行修复模式(推荐) + +审查报告生成后,P0/P1 问题用两个并行子代理分别修复 Desktop 和 Bridge: + +```python +delegate_task(tasks=[ + {"goal": "Fix Desktop P0 issues...", "toolsets": ["terminal", "file"]}, + {"goal": "Fix Bridge P0 issues...", "toolsets": ["terminal", "file"]}, +]) +``` + +每个子代理独立:读文件 → patch → git commit → push → Gitea API PR + merge。 +优点:隔离上下文,避免跨仓库混乱;并行执行,效率高。 + +### GLM-5.1 审查(第二阶段 — delegate_task) + +```python +# 用 delegate_task 调用智谱 GLM-5.1 做代码审查 +delegate_task( + goal="审查 Desktop ChromeDevToolsMCP 实现的全部代码", + context="项目: AtomK-Desktop v4.0.3 ...", + toolsets=["terminal", "file"] +) +``` + +GLM-5.1 通过 `file` + `terminal` 工具集可读全部源码,输出完整审查报告含 🔴🟡🟢 分级。 + +### Claude Code 审查 — 完整提示词模板 + +当审查 MCP/CDP/Bridge 安全相关代码时,使用以下详细提示词。 + +**⚠️ 大文件安全审计完整流水线**(GLM-5.1 审查 → Spec → Claude Code 三阶段实现): +详见 `references/glm-claude-review-pipeline.md`。已验证于 Bridge server.py (3628行) 全量审计, +发现 5🔴+11🟡+10🟢,三阶段 Claude Code 实现,总变更 +722/-169,零回归。 + +**Desktop + Bridge 应并行审查**(两个 `terminal(background=true)` + `notify_on_complete=true`),各自 `timeout=300`。 + +**Claude Code 从 Spec 实现代码**:完整流水线见 `references/claude-code-spec-implementation.md`(验证于 2026-06-18: GLM-5.1 审查 → Spec → Claude Code 实现 Phase 1,5 CRITICAL 修复)。 + +**Desktop 审查提示词:** + +``` +You are a senior TypeScript/Electron security engineer. Review the following code. +Output in Chinese. Use format: +🔴 CRITICAL — security, memory leak, data corruption +🟡 WARNING — logic bugs, race conditions, edge cases +🟢 SUGGESTION — code quality, performance, maintainability + +For each finding: file, line number, severity, problem description, fix suggestion. + +Focus on: +1. Security: can an attacker through Bridge WS execute arbitrary commands, read files, or bypass auth? +2. Protocol correctness: does MCP JSON-RPC 2.0 compliance hold? +3. CDP safety: are dangerous CDP domains restricted? +4. Resource leaks: any unclosed CDP connections, listeners, or memory leaks? +5. Error handling: do failures cascade or get swallowed? +6. Race conditions: in multi-tab, multi-Bridge scenarios +``` + +**Bridge 审查提示词:** + +``` +You are a senior Python/security engineer. Review the following code. +Output in Chinese. Use format: +🔴 CRITICAL — security, data loss, auth bypass +🟡 WARNING — logic bugs, race conditions, edge cases +🟢 SUGGESTION — code quality, performance, maintainability + +For each finding: function/route name, line number, severity, problem description, fix suggestion. + +Focus on: +1. Auth: can JWT or API key checks be bypassed? Are all sensitive endpoints protected? +2. Path traversal: can an attacker manipulate file paths in upload/download endpoints? +3. WS security: any injection, message spoofing, or unauthorized routing? +4. CDP tunnel: can an attacker hijack another user's CDP session? +5. Race conditions: in multi-user, multi-Desktop scenarios +6. Error handling: do unhandled exceptions crash the server? +7. Resource management: connections, file descriptors, memory +``` + +**多文件审查方法:** +```bash +# ✅ 正确:pipe stdin,安全且 Claude 可看到全部文件 +cat file1.ts file2.ts file3.ts | claude -p "review prompt here" + +# ✅ 错误:$(...) 先被 bash 展开,TypeScript 关键字被当命令执行 +claude -p "review $(cat file.ts)" # 禁止! +``` + +--- + +## 3. Git 工作流(Gitea 受保护 main 分支 → frp.9webs.online:3000) + +main 分支受保护,禁止直接 push。必须走 PR: + +```bash +# 1. 从 main 切 release 分支 +cd /home/ubuntu/AtomK-Desktop +git checkout main && git pull origin main +git checkout -b release/vX.Y.Z + +# 2. 修改代码 + commit +git add -A +git commit -m "feat: ..." + +# 3. Push release 分支 +git push origin release/vX.Y.Z +``` + +**通过 Gitea API 创建 PR + 合并**(Basic Auth `admin9webs:Tt123456!`): + +> ⚠️ `execute_code` 在部分环境下被阻止。直接用 `terminal` + Python inline 替代,不要走 cron 脚本模式: + +```python +import urllib.request, json, base64, ssl + +auth = base64.b64encode(b'admin9webs:Tt123456!').decode() +ctx = ssl._create_unverified_context() +gitea = 'http://frp.9webs.online:3000' +owner, repo = '9webs', 'AtomK-Desktop' +head = 'release/vX.Y.Z' + +# 创建 PR +pr_data = json.dumps({'title': '...', 'head': head, 'base': 'main'}).encode() +pr_req = urllib.request.Request(f'{gitea}/api/v1/repos/{owner}/{repo}/pulls', + data=pr_data, method='POST', + headers={'Authorization': f'Basic {auth}', 'Content-Type': 'application/json'}) +pr = json.loads(urllib.request.urlopen(pr_req, context=ctx).read()) + +# 合并 PR +merge_data = json.dumps({'Do': 'merge'}).encode() +merge_req = urllib.request.Request( + f'{gitea}/api/v1/repos/{owner}/{repo}/pulls/{pr["number"]}/merge', + data=merge_data, method='POST', + headers={'Authorization': f'Basic {auth}', 'Content-Type': 'application/json'}) +urllib.request.urlopen(merge_req, context=ctx) +``` + +**清理:** +```bash +git checkout main && git pull origin main +git branch -d release/vX.Y.Z +git push origin --delete release/vX.Y.Z +``` + +--- + +## 4. 构建规则(重要!) + +**只有用户明确说 "build" 时才构建。** 永远不要在代码提交后自动构建。 + +**用户偏好:先 build 验证再提交 PR。** 当代码改动就绪后的标准流程: +```bash +# ① stash → pull → pop(对齐 origin/main) +# ② typecheck 双验证(web + node 并行) +npx tsc --noEmit -p tsconfig.web.json --composite false +npx tsc --noEmit -p tsconfig.node.json --composite false +# ③ 修复 typecheck 错误(若有) +# ④ build 验证 +rm -f dist/*.exe dist/*.blockmap +NODE_OPTIONS=--max-old-space-size=4096 npm run build:win +# ⑤ 确认产物存在(ls -lh dist/*.exe dist/latest.yml) +# ⑥ 然后才 commit → release 分支 → PR → merge +``` + +构建命令: +```bash +NODE_OPTIONS=--max-old-space-size=4096 npm run build:win +``` + +⚠️ **构建用 foreground 模式**:`terminal(background=true)` + `notify_on_complete=true` 在 npm build 场景下输出捕获不可靠(typecheck 阶段无输出导致进程看起来死掉)。用 `terminal(timeout=600)` 前台运行,输出正常流式显示,构建完成即可见结果。 + +构建产物上传 COS: +```bash +coscmd upload /path/to/dist/*.exe desktop/ +``` + +> Desktop 当前 v4.0.1(main @ `ec24404`),已 build → `dist/atomk-desktop-4.0.1-setup.exe` (240MB),已上传 COS 三个路径。 + +--- + +## 5. Spec 文档编写 + +新功能开发前先写 spec,路径 `docs/superpowers/specs/YYYY-MM-DD-title.md`。 + +**模板结构:** +```markdown +# Title + +**Status:** Draft — pending review +**Date:** YYYY-MM-DD +**Branch target:** `feat/xxx` → PR `main` + +## Goal +## Non-goals +## Architecture +## Implementation +## Phases +## Open Questions +## References +``` + +**提交流程:** 切分支 → 写 spec → commit → push → Gitea API 创建 PR → merge。spec 不需要 build。 + +**示例**: `docs/superpowers/specs/2026-06-17-desktop-chrome-mcp-design.md` + +**跨仓库架构改进 spec 模板**:当审查覆盖 Desktop + Bridge + Server 三端时,用 `references/cross-repo-architecture-spec.md` 的模板(Part A/B/C 结构、Phase 表含 LOC 估算、多批次追加策略、跨层总结矩阵)。 + +### 5d. Spec 编写 → Claude Code Opus 审查 → 修 Spec(架构设计,推荐) + +架构级设计 spec 最有效审查方式:写 spec → 直接 pipe 给 Claude Code Opus。 + +```bash +# 1. 写 Spec → /home/ubuntu/docs/spec-.md +# 模板: Status/Date/Goal/Non-goals/Architecture/Implementation/Test Plan + +# 2. Claude Code Opus 审查 spec +cat /home/ubuntu/docs/spec-b2-s2-s4-architecture.md | claude -p --model opus --permission-mode acceptEdits \ + "You are a senior security architect. Review this spec. + Output in Chinese. Format: 🔴 CRITICAL / 🟡 WARNING / 🟢 SUGGESTION + Focus on: Attack surface, protocol correctness, failure modes, backward compat, key management" \ + > /tmp/spec-review.md 2>&1 & + +# 3. 审查结果通常发现: +# - 概念性错误(如 SameSite 是响应头不是入站属性) +# - 端点缺鉴权 +# - 威胁模型自相矛盾 +# - 上线顺序/回滚方案缺失 + +# 4. 按反馈逐项修 Spec → 再次审查 → 确认无 🔴 后开始实现 +``` + +**优势**:Opus 4.8 发现概念性错误的能力远超代码审查——在设计阶段纠错成本最低。 + +**⚠️ 关键前置步骤**:送审前必须用 `search_files` 核实 spec 中所有 API 调用名与实际代码签名一致。Opus 发现的最高频错误就是臆造的 API 名。详见 pitfall #95 和 `references/hermes-desktop-vs-atomk-analysis.md`(spec v1→v2 完整修订实录)。 + +完整 spec v1→v2 示例见 `references/spec-b2-s2-s4-architecture-v2.md`(B2 JWT/S2 Bridge key/S4 CSRF 三方案,源文件 `/home/ubuntu/docs/spec-b2-s2-s4-architecture-v2.md`)。 + +**注意**: +- `claude -p --model opus` 在 13KB+ 大 spec 上约需 90–120s 才有输出(pipe 模式无流式预览,全部缓冲到完成) +- 用 `terminal(background=true, notify_on_complete=true)` 后台运行,避免阻塞当前 turn +- 等待期间用 `process(action='poll')` **低频轮询**(每隔 10–15s),不要每秒 poll——用户会不耐烦 +- `--model` 用 `opus`(不是 `opus-4.8`,Claude Code 别名映射会自动选择最新 opus) +- 若 `notify_on_complete` 已收到通知但 `output_preview` 为空,用 `process(action='log')` 获取完整输出 + +完整的 spec 驱动开发流水线(SG7 已验证): + +``` +1. 写 Spec v1 → /home/ubuntu/docs/spec-.md +2. GLM-5.1 审查 → Python 脚本直调 API(非 delegate_task) + python3 /tmp/glm-review-sg7.py # 后台运行,spec 全文喂入 +3. 读审查结果 → /tmp/glm-sg7-review.md +4. 修 Spec v2 → 逐项修复 P0/P1,patch spec 文件 +5. 自审脚本 → Python 自动化检查所有修复项是否落地 +6. 实现代码 → 迁移 + 模型 + 端点 + 路由注册 → push +``` + +### GLM-5.1 审查 — 直接用 API(推荐,优于 delegate_task) + +`delegate_task` 在长输出场景下经常截断(只返回一句话)。对于 spec/code review,写入 Python 脚本直调 Coding Plan API: + +```python +# 脚本模板:/tmp/glm-review-*.py +API_KEY = os.environ.get('GLM_CODING_API_KEY', '') +# ... (从 ~/.hermes/config.yaml fallback) +body = json.dumps({"model": "glm-5.1", "messages": [...], "max_tokens": 4000, "temperature": 0.3}) +# ... +``` + +**大文件分批策略(关键)**:GLM-5.1 是思考模型,`max_tokens` 需 ≥4000(推理占用大量 token)。 +大文件(>3000行)一次性发送会超时(180s+无响应)。策略: +- 单文件 ≤1500 行:直接审计,120s 超时 +- 单文件 1500-3000 行:截取前 12000 字符 +- 多文件总量 >6000 行:按文件大小拆成 2-3 批并行发送,每批 `notify_on_complete=true` +``` + +**自审脚本模板**: + +```python +# 逐项检查 spec 中是否包含特定关键词 +checks = { + "P0-1: XX": [("条件描述", "关键词" in spec), ...], +} +# 输出 ✅/❌ 矩阵 +``` + +**关键点**: +- GLM-5.1 API key 通过 heredoc 读取(避免 shell 明文) +- `terminal(background=true, notify_on_complete=true)` 后台运行审查 +- 审查结果存 `/tmp/glm-review-*.md`,后续可直接引用 +- 所有 P0 必须修完才能开始编码 +- 修完 spec 后跑自审脚本确认无遗漏 +- ⚠️ GLM-5.1 是思考模型,`max_tokens < 4000` 会导致 reasoning 耗尽全部 token → `content=""`, `finish_reason="length"` +- ⚠️ 大批量文件(6+文件/10K+行)单次请求可能超时 180s → 拆分为单文件或 ≤1500 行/批次,后台并行 +- 完整审计实录见 `references/glm-audit-desktop-2026-06-24.md` + +### 5e. Desktop 新增面板模式(SG7: Store + Posts 双 tab) + +```typescript +// 步骤 1: i18n keys(zh-CN + en) +// src/shared/i18n/locales/zh-CN/navigation.ts +store: "网店", + +// 步骤 2: Layout.tsx 改动(最小化) +// - import { ShoppingBag } from "lucide-react" +// - View type 加 "store" +// - NAV_GROUPS[1] 加 { view: "store", icon: ShoppingBag, labelKey: "navigation.store" } +// - render: visitedViews.has("store") && + +// 步骤 3: 新建 Store.tsx — 全状态矩阵 +// loading → error → not-provisioned → provisioning → suspended → active+tabs +// 每个 tab (products/orders/reports) 有独立空态+错误态 + +// 步骤 4: 增强 Posts.tsx — 双 tab 模式 +// contentTab state + localStorage 持久化 +// Tab bar: [社媒帖子 | 博客文章] +// Blog tab: 独立状态矩阵 (loading/error/not-provisioned/provisioning/suspended/empty/list)"> + +// 步骤 5: proxyGet IPC(四件套模式) +// main/atomlisting.ts → proxyGet(path) 实现 +// main/index.ts → ipcMain.handle("atomlisting-proxy-get", ...) +// preload/index.ts → ipcRenderer.invoke("atomlisting-proxy-get", path) +// preload/index.d.ts → proxyGet: (path: string) => Promise +``` + +**常见坑**: +- `window.hermesAPI.atomlisting.get()` 不存在 — 需加 `proxyGet` 通用方法 +- 双 tab 要做 `localStorage` 持久化,否则刷新丢状态 +- 新面板必须包裹 ``,否则 WC 代理失败会崩整个页面 + +--- + +### 5c. Desktop 面板重组模式(含 SG7 Posts 双 tab + Store 新增) + +当需要在 Desktop 面板间移动功能或新增面板时,遵循以下模式: + +### 移动功能到其他面板(A2A Inbox: Tools → Mail) + +```bash +# 1. 目标面板 (Mail.tsx) 新增 imports +patch: 在 lucide-react import 中加 Inbox/RefreshCw/Check/ChevronDown/ChevronRight + +# 2. 加 tab 类型 +patch: tab type 从 "webmail" | "accounts" 扩展为 "webmail" | "accounts" | "inbox" + +# 3. 用 Python heredoc 插入 A2A state + functions + useEffect +python3 << 'PYEOF' +# 精确字符串替换插入完整代码块 +content = open("Mail.tsx").read() +# 在 marker 后插入 state +content = before + marker + a2a_state + after +# 在 marker 前插入 functions(在 Auth 之前) +content = before + a2a_functions + marker + after +# 在 Render 前插入 useEffect(必须在条件 return 之前!) +content = before + a2a_effect + marker + after +PYEOF + +# 4. 加 Inbox tab 按钮 + 内容 +python3 << 'PYEOF' +# 按钮追加到 Accounts 按钮后 +content.replace(accounts_end, accounts_end + inbox_btn) +# 内容插入到 Webmail Tab 之前 +content.replace(webmail_marker, inbox_content + webmail_marker) +PYEOF + +# 5. 源面板 (Tools.tsx) 清理 +python3 << 'PYEOF' +# 移除 A2A imports (ChevronDown/ChevronRight/Inbox/RefreshCw/Check/Trash2) +# 移除 A2A state 块 +# 移除 A2A functions (loadA2aInbox/handleA2aMarkRead/handleA2aDelete) +# 移除 A2A useEffect +# 移除 A2A JSX 段 +PYEOF +``` + +### 面板重命名(文件 + 组件 + import + i18n,v4.1.8 验证) + +当需要重命名已有面板时(如 Store → Assignment),需改 4 层: + +``` +步骤 1: 目录 + 文件重命名 + mv src/renderer/src/screens/Store src/renderer/src/screens/Assignment + mv Assignment/Store.tsx Assignment/Assignment.tsx + +步骤 2: 组件内部改名 + - function Store() → function Assignment() + - export default Store → export default Assignment + - 错误日志 tag: [Store] → [Assignment] + - catch 块中的 console.error tag 同步更新 + +步骤 3: Layout.tsx import + JSX + - import Store from "../Store/Store" → import Assignment from "../Assignment/Assignment" + - + +步骤 4: i18n keys(zh-CN + en) + - navigation.ts: store → assignment +``` + +**验证**: `grep -rn 'Store\|store' src/renderer/src/screens/Layout/Layout.tsx` 应无匹配。 + +### i18n 品牌名称统一(atomlisting.com → AtomK Server,v4.1.9 验证) + +Settings 页面及其他 UI 中的品牌名称需统一为 `AtomK Server`(替代 `atomlisting.com` / `Atomlisting.com`)。改 3 个 locale 文件 + Welcome 提示: + +```bash +# 受影响的 i18n keys(共 4 处/语言): +# atomlistingSection: "AtomK Server" # 区块标题 +# atomlistingHint: "Manage your AtomK Server..." # 描述 +# v4bridgeHint: "...AtomK Server..." # Bridge 提示 +# loginRequiredForBridge: "...AtomK Server..." # 登录提示 + +# 修改文件: +src/shared/i18n/locales/en/settings.ts # English +src/shared/i18n/locales/zh-CN/settings.ts # 中文 +src/shared/i18n/locales/pt-BR/settings.ts # 葡萄牙语 +src/shared/i18n/locales/en/welcome.ts # 登录页提示(en only) +``` + +**验证**:改完跑双 typecheck (`tsconfig.web.json` + `tsconfig.node.json`) 确认无 TS 错误。 + +### i18n 品牌名称统一(atomlisting.com → AtomK Server,v4.1.9 验证) + +Settings 页面及其他 UI 中的品牌名称需统一为 `AtomK Server`。改 3 locale + welcome: + +| locale | 文件 | keys | +|--------|------|------| +| en | `settings.ts`, `welcome.ts` | `atomlistingSection`, `atomlistingHint`, `v4bridgeHint`, `loginRequiredForBridge`, `loginHint` | +| zh-CN | `settings.ts` | 同 4 keys | +| pt-BR | `settings.ts` | 同 4 keys | + +将所有 `atomlisting.com` / `Atomlisting.com` 文本替换为 `AtomK Server`(保留 API URL placeholder 不动)。 + +### 侧边栏菜单项合并到已有面板(sidebar → tab) + +当需要减少侧边栏菜单项时,将独立的面板合并到逻辑相关的已有面板中作为 tab。 + +**示例**:Schedules 合并到 Browser Agent(PR @ `0378db5`) + +``` +步骤 1: 目标面板 (BrowserAgent.tsx) 改动 + - SubTab type 扩展: "agent" | "sessions" → "agent" | "sessions" | "schedules" + - import Schedules from "../Schedules/Schedules" + - import Timer icon from lucide-react + - 复制 agent/sessions 的 tab 按钮模板,新增 schedules 按钮 + - 新增 if (activeTab === "schedules") 返回块(含 tab bar + ) + - 注意:三个 tab 块各自有完整的 sub-tab bar,重复但有独立 inactive/active 样式 + +步骤 2: Layout.tsx 清理 + - 删除 import Schedules + - 删除 View type 中的 "schedules" + - 删除 NAV_GROUPS 中的 schedules 项 + - 删除 visitedViews.has("schedules") 渲染块 + - 删除 Timer 图标 import(如仅 schedules 使用) + - ⚠️ 确认删除后 group items 数组不为空(否则只剩 `items: []`) + +步骤 3: typecheck 验证 + - npx tsc --noEmit -p tsconfig.web.json (renderer 侧) + - npx tsc --noEmit -p tsconfig.node.json (main 侧) +``` + +### 新增工具卡片到 Tools + +```tsx +// ... + +### 轻量面板模式(proxyGet,无需 IPC 四件套) + +当快速原型开发且 Server 端点可能变化时,跳过完整的 IPC 四件套 + atomlisting.ts 类型方法, +直接用 `proxyGet` 从 renderer 调用 atomlisting API: + +```typescript +// 直接调任意 Server 端点,无需新增 IPC handler +const resp = await window.hermesAPI.atomListing.proxyGet( + `/api/v1/products/remote/by-code/${encodeURIComponent(code)}` +); +``` + +**适用场景**: +- 新面板快速原型,Server 端点可能变 +- 简单 GET 查询/提交(query params 传参) +- 不想为每个面板添加 4 个文件的 IPC 链路 + +**限制**:仅支持 GET 请求。需要 POST/PUT/DELETE 时仍需完整 IPC 模式。 + +完整示例见 `references/submit-panel-development.md`(Submit 页面,PR #23)。 +// 1. Import 图标 +import { Rocket, DollarSign, Search, Shield, ChevronDown, ChevronRight } from "lucide-react"; + +// 2. State: 当前展开的工具卡片 +const [ecoTool, setEcoTool] = useState(null); + +// 3. 卡片数据(可放组件外避免重复创建) +const ECO_TOOLS = [ + { key: "listing", icon: Rocket, label: "智能刊登", desc: "1688→Ozon" }, + // ... +]; + +// 4. 卡片 JSX(点击展开/收起) +{ECO_TOOLS.map(tool => ( +
setEcoTool(isExpanded ? null : tool.key)}> + + {isExpanded ? : } + {isExpanded &&
{tool.desc_detail}
} +
+))} +``` + +### 5f. 新增 proxyGet/proxyPost IPC 四件套模式 + +通用 API 代理层,让任意 Desktop 面板直调 atomlisting Server API: + +```bash +# 四文件必须同步(漏一即崩): +# ① atomlisting.ts — 实际 HTTP client 调用 + D1 白名单 +# ② index.ts — ipcMain.handle() + normalizeProxyPath 防路径穿越 +# ③ preload/index.ts — ipcRenderer.invoke() 暴露给 renderer +# ④ preload/index.d.ts — TypeScript 类型声明 + +# proxyGet (GET): +atomlisting.ts: async proxyGet(rawPath) → normalizeProxyPath → 白名单 check → createClient().get() +index.ts: ipcMain.handle("atomlisting-proxy-get", ...) +preload/index.ts: proxyGet: (path) => ipcRenderer.invoke("atomlisting-proxy-get", path) +preload/index.d.ts: proxyGet: (path: string) => Promise + +# proxyPost (POST, v4.1.7+): +atomlisting.ts: async proxyPost(rawPath, body) → normalizeProxyPath → 白名单 check → createClient().post() +index.ts: ipcMain.handle("atomlisting-proxy-post", ...) +preload/index.ts: proxyPost: (path, body) => ipcRenderer.invoke("atomlisting-proxy-post", path, body) +preload/index.d.ts: proxyPost: (path: string, body: unknown) => Promise + +# proxyPatch (PATCH, v4.1.8+): +atomlisting.ts: async proxyPatch(rawPath, body) → normalizeProxyPath → 白名单 check → createClient().patch() +index.ts: ipcMain.handle("atomlisting-proxy-patch", ...) +preload/index.ts: proxyPatch: (path, body) => ipcRenderer.invoke("atomlisting-proxy-patch", path, body) +preload/index.d.ts: proxyPatch: (path: string, body: unknown) => Promise + +# proxyDelete (DELETE, v4.1.8+): +atomlisting.ts: async proxyDelete(rawPath) → normalizeProxyPath → 白名单 check → createClient().delete() +index.ts: ipcMain.handle("atomlisting-proxy-delete", ...) +preload/index.ts: proxyDelete: (path) => ipcRenderer.invoke("atomlisting-proxy-delete", path) +preload/index.d.ts: proxyDelete: (path: string) => Promise +``` + +**双白名单守卫(D1 双层防护)**:`index.ts` 的 `PROXY_ALLOWED_PREFIXES` 和 `atomlisting.ts` 的 `PROXY_ALLOWED_PREFIXES` 必须同步更新。漏了任一处 → 请求被拦截。 +**新增 proxy 方法时**:atomlisting.ts 中每个 proxy 方法(proxyGet/poxyPost/poxyPatch/proxyDelete)有**独立的**白名单数组,新增路径必须全部更新(当前 4 个方法 × 各 1 处 = index.ts 1 处 + atomlisting.ts 4 处 = 共 5 处)。 + +**renderer 用法**: +```typescript +// GET +const data = await window.hermesAPI.atomListing.proxyGet('/api/v1/products/remote?limit=10'); +// POST +await window.hermesAPI.atomListing.proxyPost('/api/v1/products/remote/claim', { code: '82AB6133' }); +// PATCH +await window.hermesAPI.atomListing.proxyPatch('/api/v1/premium-products/by-code/ABC123', { name: 'Updated' }); +// DELETE +await window.hermesAPI.atomListing.proxyDelete('/api/v1/premium-products/by-code/ABC123'); +``` +但 index.ts 中所有方法共用同一个数组。新增路径时必须 5 处同步(index.ts ×1 + atomlisting.ts ×4)。 + +**双白名单守卫(D1 双层防护)**:`index.ts` 的 `PROXY_ALLOWED_PREFIXES` 和 `atomlisting.ts` 的 `PROXY_ALLOWED_PREFIXES` 必须同步更新。漏了任一处 → 请求被拦截。 +⚠️ atomlisting.ts 中有 4 个独立白名单数组(proxyGet/proxyPost/proxyPatch/proxyDelete),全部需要同步。 + +**renderer 用法**: +```typescript +// GET — list/search +const data = await window.hermesAPI.atomListing.proxyGet('/api/v1/premium-products?search=xxx&skip=0&limit=15'); +// POST — create +await window.hermesAPI.atomListing.proxyPost('/api/v1/premium-products', { name: '...', reference_urls: [...] }); +// PATCH — update +await window.hermesAPI.atomListing.proxyPatch('/api/v1/premium-products/by-code/AB123456', { name: '...' }); +// DELETE — soft-delete +await window.hermesAPI.atomListing.proxyDelete('/api/v1/premium-products/by-code/AB123456'); +``` + +**常见坑**: + +- **Python heredoc 优于 patch 工具**:TSX 文件大且含复杂字符串时,`patch` 工具频繁 escape-drift。用 `python3 << 'PYEOF'` heredoc + 精确字符串替换更可靠。 +- **🔴 `proxyPost` 四文件必须同步**:与 `proxyGet` 同模四件套,atomlisting.ts + index.ts + preload/index.ts + preload/index.d.ts 缺一即 TS2339。D1 白名单要双端一致。 +- **🔴 Server product_code 格式**:Server 端生成 `string.ascii_uppercase + string.digits` 随机 8 位字母数字(如 `82AB6133`)。Desktop 用 `toDisplayCode()` 提取纯 8 位码显示。MongoDB 统一格式,不存 SUBMIT- 前缀。 +- **🔴 批量提交上限**:Desktop IPC handler + Server API 双重限制,默认 50 条。改一处必须改另一处。 +- **useEffect 必须在条件 return 之前**:React rules-of-hooks 违规 → 运行时崩溃。移到所有条件 return 之前。 +- **R1 修复可能引入新 bug**:`patch` 工具批量修改 imports 时容易产生重复/错误 import。每次修改后验证 imports 行。 +- **ECO_TOOLS 去重**:向组件外移动时可能产生两份定义,用 `grep -c` 确认。 + +## 5b. 本地仓库路径 + +| 项目 | 路径 | SG5 Repo | 说明 | +|------|------|------|------| +| Desktop | `/home/ubuntu/AtomK-Desktop` | `9webs/AtomK-Desktop` | Electron 桌面端 | +| Bridge | `/home/ubuntu/AtomK_Bridge` | `9webs/AtomK_Bridge` | Cloud Bridge 服务端 (曾用名 atomk-page-bridge → AtomK-Cloud-Bridge) | +| Server | `/home/ubuntu/AtomK_Server` | `9webs/Atomlisting_Server` | 后端 API | + +> 旧 Gitea `gitea9webs.sh3.ikuai7.com` 已下线。远程操作全部走 `frp.9webs.online:3000`。 +> TAT 远程命令执行(无需 SSH 读生产配置/调试 MongoDB):见 `references/tat-remote-execution.md` +> 所有仓库预配 `sg5` remote。迁移细节见 `references/sg5-gitea-migration.md`。 + +## 6. 关键端口与版本 + +| 项目 | 值 | 说明 | +|------|-----|------| +| CDP_PORT | **9322** | chrome-bridge.ts line 373(从 9222 迁移,避免紫鸟冲突) | +| Electron | ^39.2.6 | Chromium 142.0.7444.226 | +| playwright-core | ~1.56.0 | 匹配 Chromium 142 | +| 紫鸟 WebDriver 默认端口 | 9222 | 与 Desktop 9322 不冲突 | + +--- + +## 6. 新模块架构(v4.0.0) + +### playwright-controller.ts +- `BrowserContext` 隔离:`Map` 区分自助(`self`)/紫鸟(`ziniao`)通道 +- 通道互斥锁:异步排队 Mutex(Promise 链式等待),不再同步 throw +- 操作队列:同 slot 串行化,`prev.then(fn, fn)` 模式(失败不阻塞后续) +- `screenshot()` 支持 `useZiniao`/`ziniaoPort` 参数 +- `SELF_CDP_PORT = 9322` 常量(替代硬编码) +- `selfConnect()` 有友好错误(Chromium not running) +- `ziniaoConnect()` 重连前清理 stale slotPages + `disconnected` 清理 +- `execute()` 加 `source: 'local' | 'bridge'` 守卫,Bridge 来源拒绝 + +### ziniao-client.ts +- `request()` 有 AbortController 15s 超时 +- `findStore()` 精确匹配 + 前缀唯一匹配 +- `startBrowserWithTTL` 返回 `{result, cancelTtl}` +- `markStoreActive`/`markStoreInactive` + `startOrphanSweep()` + +### hubstudio-client.ts (v4.0.2+) + +### hubstudio-cdp-controller.ts (v4.0.x NEW) + +- HubStudio 独立 Chrome CDP 中继控制器 +- Playwright `connectOverCDP(port)` 连接 HubStudio Chrome(动态端口,如 58289) +- 每个环境独立 `BrowserContext`,支持多环境并行 +- 7 个方法: `connect/disconnect/navigate/evaluate/click/screenshot/snapshot` +- `hubstudio.open_env` 成功后自动连接 CDP;`hubstudio.close_env` 自动断开 + +### hubstudio-client.ts (v4.0.2+) +- Hubstudio 浏览器 Local API 客户端(`http://127.0.0.1:6873`) +- 认证:`Authorization: Bearer *** + Accept-Language: zh-CN` +- 完整方法:`listEnvs()` / `findEnv()` / `startBrowser()` / `stopBrowser()` / `getOpenedEnvs()` / `browserStatus()` +- `startBrowserWithTTL()` 超时自动关闭、`markEnvActive`/`markEnvInactive` + `startOrphanSweep()` +- 凭证存储在 `~/.atomk/hubstudio.json`(safeStorage 加密) +- Desktop UI 入口:Chrome Bridge 页面 → 「紫鸟 & Hubstudio API」配置区 + +### bridge-message-router.ts +- 路由 `playwright.*` (7 methods) + `ziniao.*` (5 methods) + `hubstudio.*` (5 methods, v4.0.2+) +- `playwright.execute` **不在路由中**(安全) +- `ziniaoGuard()` + `validateCoreVersion()` 前置守卫 +- `HubstudioClient` 集成:`hubstudio.status` / `list_envs` / `open_env` / `close_env` / `opened_envs` + +--- + +--- + +## X. Desktop UI 功能速查 + +### 紫鸟 & Hubstudio API 凭证输入 + +| 项目 | 说明 | +|------|------| +| **位置** | **Chrome Bridge 页面** → 底部「紫鸟 & Hubstudio API」配置区 | +| **紫鸟字段** | API Key + Base URL(默认 `http://127.0.0.1:19481`) | +| **Hubstudio 字段** | App ID + App Secret + Base URL(默认 `http://127.0.0.1:6873`) | +| **存储** | `~/.atomk/ziniao.json` / `~/.atomk/hubstudio.json`(safeStorage 加密) | +| **后端** | `src/main/ziniao-client.ts` / `src/main/hubstudio-client.ts` | +| **IPC** | `ziniao:*` / `hubstudio:*` channels | + +### Products 面板(premiumproducts CRUD,v4.1.8+ 重写) + +走 AtomK Server API (`/api/v1/premium-products`) 对 MongoDB premiumproducts 做全 CRUD。 +- 列表:`proxyGet` 带 search/status/skip/limit 参数,分页展示 +- 创建:`proxyPost`,编辑:`proxyPatch`,删除:`proxyDelete`(软删除 → archived) +- 列表行展示 product_code、SKU、tags、categories、创建日期 +- 创建/编辑用 ``,删除用 `` +- auth gate:`authChecked` + `authReady` 双状态 +- Server 端点:`backend/app/api/v1/premium_products.py`(JWT auth) +- 旧版 Products(Remote Pool + Claimed 双 tab)已被完全替代 + +常见坑: +- 新增 proxy 方法(patch/delete)必须走完整四件套,白名单 5 处同步 +- `PremiumProductCreate` 统一用于创建和编辑(edit 时额外传 status) +- Server PATCH 只更新非 None 字段,delete 是软删除 + +### 检查 Desktop 更新 + +| 项目 | 说明 | +|------|------| +| **位置** | **Settings 页面** → Hermes 版本区 →「检查Desktop更新」按钮(运行诊断 旁边) | +| **原理** | electron-updater 读 COS `tools/update/latest.yml`,对比版本号 | +| **有新版本** | 自动下载,应用重启后安装 | +| **无更新** | 显示「已是最新版本 ✅」 | +| **IPC** | `check-for-updates` → `autoUpdater.checkForUpdates()` | + +### Cloud Bridge prompt(hermes.ts 内置提示词) + +| 项目 | 说明 | +|------|------| +| **文件** | `src/main/hermes.ts` → `buildCloudBridgePrompt()` | +| **内容** | v4.0.8 起精简为上下文触发消息(英文 ~1.2KB):告知 agent 这是 Cloud Bridge 会话、Desktop ID、当前用户,并指引加载 `bridge-cdp-agent` skill 获取完整 CDP 指令 | +| **旧版** | v4.0.7 之前为中文全文 ~4KB,含 CDP 操作流程、平台特殊处理(已移除)、通用模板 | +| **维护** | 平台特定操作指南放在对应 skill 中(ozon-operations、miaoshou-erp 等),无需改 hermes.ts。改 skill 即时生效,无需 rebuild Desktop | +| **注意** | ⚠️ `quickPrompts.ts` 也有硬编码账号信息,详见 pitfall #60 | + +### LLM 模型选择器(已移除 v4.0.2+) + +| 项目 | 说明 | +|------|------| +| **原位置** | **Chat 页面底部** — `src/renderer/src/screens/Chat/Chat.tsx` L238-246 | +| **组件** | `` — 下拉框选择模型(Sonnet 4.6 等) | +| **关联 hook** | `useModelConfig()` — `src/renderer/src/screens/Chat/hooks/useModelConfig.ts` | +| **状态** | **已移除**(PR #43)。如用户要求恢复,恢复 import + hook 调用 + JSX 三处 | + +## 常见坑 + +1. **`getPageForSlot` channel 参数**:传 `'self'` 或 `'ziniao'`,key 格式 `${slotId}:${channel}` +2. **版本比较降级 Bug**:`check-for-updates` 中不能用 `latest !== current` 字符串不等比较。如果 latest.yml 写入旧版本,equality check 会触发降级提示。必须用 `compareSemverLike(latest, current) > 0` 只通知升级。 +3. **useCallback 闭包陈旧**:`useEffect([], [])` 里注册 DOM 事件若调用非 memoized 函数,拖拽等操作会使用挂载时的旧闭包。将 handler 包 `useCallback`,effect deps 设为 `[handler]`。 +2. **`$$` shell 变量展开**:`--key Bing2026Cao$$$` 中的 `$$` 会被 bash 展开为当前 PID,导致 key 错误。 +2. **`ziniaoConnect` 重连前清理 slotContexts**:关闭旧 browser 后所有 Context/Page 失效 +3. **`cancelTtl` 变量名**:用 `cancelTtl` 而非 `clearTimeout`(不遮蔽全局函数) +4. **`params.url` 必须非空 + 协议校验**:仅允许 `http://` 或 `https://` +97. **🔴 Renderer 组件中外部 URL 必须做协议白名单校验**:任何从后端数据(remote products、scraped data)渲染到 ``、`cdpNavigate()`、`window.open()` 的 URL,必须在 renderer 侧做协议校验。典型漏洞: + - `` + `target="_blank"` — 中键点击不触发 onClick/preventDefault,直接跟随 href。若 source_url 含 `javascript:` → 代码执行 + - `cdpNavigate(product.source_url!)` — URL 直传 CDP `Page.navigate`,`file:///etc/passwd` 可读本地文件 + **修复模板**: + ```typescript + const ALLOWED_PROTOCOLS = ['http:', 'https:']; + function safeUrl(url: string | null | undefined): string { + if (!url) return '#'; + try { const u = new URL(url); return ALLOWED_PROTOCOLS.includes(u.protocol) ? url : '#'; } + catch { return '#'; } + } + // 然后:href={safeUrl(product.source_url)} + // 或用