diff --git a/docs/协作/B02-需求.md b/docs/协作/B02-需求.md new file mode 100644 index 0000000..f11b72c --- /dev/null +++ b/docs/协作/B02-需求.md @@ -0,0 +1,83 @@ +# B02:推荐技能的已安装样式与重复入口 + +日期:2026-10-21(Asia/Shanghai)。状态:**待开发**。用户新增需求,不改变 B01 的范围、交付或验收结论。 + +用户要求:“专家技能连接器这块的技能,已经安装的在推荐中的样式也不对,还保留有添加的按钮,参考一下 WorkBuddy。还有下方也不应该有查看已安装技能,这个要移除掉。” + +## 1. 已确认问题与复现 + +接收时 `gitea/master` 为 `ddd677bae3c43a5514eb40f7a1d9749fdf389115`。独立窗口使用既有固定代码 `51531dcae8d8ade85db9e203bc8c0f712805b2ae`,对应推荐技能组件、样式、CardFrame 与测试从该提交到接收版本无改动。 + +Windows Electron 加载真实 `CapabilityCenter`、推荐技能组件和 CSS,使用受控 IPC、临时 userData 与模拟安装结果,不下载网络包或改用户技能。安装后关闭详情,推荐列表实测: + +```text +data-recommendation-state = current +卡片 class = sm-card,没有 sm-card--installed +添加按钮仍存在,disabled=true,按钮中仍有 Plus SVG +原动作槽位没有 sm-use-btn +卡片下方另有“去使用”文字动作 +列表底部存在“查看已安装技能”按钮 +renderer errors = [] +``` + +所以不是“完全没有识别到已安装”,而是**识别成功后,卡片仍渲染添加动作与未安装壳**。不能把仅禁用加号当作完成样式修复。 + +源码定位: + +- `src/renderer/src/capabilities/RecommendedSkills.tsx:259`:`Card` 没有传 installed 身份;actions 无条件创建 `.sm-add-btn` 和 `PlusIcon`,current 状态只影响 disabled。 +- `RecommendedSkills.tsx:263`:独立 footer 按钮“查看已安装技能”。 +- `RecommendedSkills.tsx:247`:目录不可用分支也有同名入口,需与正常列表的移除要求一并检查,不要留下另一处无依据的底部重复入口。 +- `src/renderer/src/capabilities/SkillsPage.tsx:103`:推荐页当前的 installed 导航 callback;顶部已有“已安装(n)”入口,应保留这一条正常导航。 +- `scripts/test-recommended-skills-ui.mjs:232`:旧测试要求 `.sm-add-btn.disabled === true`;`:241` 附近依赖 footer 按钮进入已安装。这两处把当前错误设计写进了测试,需同步修正。 + +证据:[实测状态](evidence/B02-01/installed-state.json)、[推荐列表截图](evidence/B02-01/installed-recommendation-list.png)、[只读窗口探针](evidence/B02-01/skills-installed-probe.mjs)。 + +## 2. WorkBuddy 依据与未验证边界 + +可复用的已有 REA 恢复记录: + +- `src/renderer/src/capabilities/skills-source-manifest.json` 标注 WorkBuddy **5.8.2**,原包 SHA-256 `035254bc76310ea46317433e4840602094543ef323d2d5c5aff7c131729de918`。 +- Evidence:JavaScript `ev_717fa5e846e64d67ac19c8925d121bff632c0d3aefb3603b10858559cabb2dcb`,技能市场相关 `ev_e8faf60b3d22b99ede302f2d0efdfb3145f53f59789d4a345f5892dc13a589a4`,CSS `ev_377c8790468b3c67dad8577aabb327f98d3140659f0fffdbabfcff5a40cc9e94`。 +- 该恢复链在项目中已有 `CardFrame.installed`/`sm-card--installed`、`UseSkillIcon` 与 `.sm-use-btn`。已恢复 CSS 的使用动作是 24×24px、默认透明背景、16px SVG;与未安装 `.sm-add-btn` 的 24×24px 灰底、12px SVG 不同。 +- [技能详情安装与使用链路修正](../技能详情安装与使用链路修正-2026-10-20.md) 第原版边界部分记录:历史原版 ActionRow 随安装状态切换安装/使用,成功提示带使用动作。不要把“+保留但禁用”当作原版已安装设计。 + +**本次当前版本并未完整核验**: + +- 本轮仅通过 REA 静态读取当前安装包,没有用直接 CDP、手写提取器、computer-use 或其他通道绕过。 +- 当前 ASAR SHA-256 为 `026a1d0805a2366d06390cef52e157a7277543cdd27dd308e6b1acb47ffffdc1`,版本号未读取确认,不能继续写成 5.8.2/5.8.3。 +- REA 父 Evidence `ev_ef70f1417c069be6bf352efcb858244efaae444f71f1f9893ed2fb579ed88849`,摘要 Evidence `ev_4d76ad1b9f386f97343307067ee2f5b5b91d2189366ab1cbe3890954db4634a6`。本次选择的源文件是 `renderer/assets/src-DbE1_mgM.js`,不是当前完整技能市场组件;存在完整性矛盾、未选文件和语义覆盖上限,**不据此对当前技能市场样式作结论**。 +- REA 整包提取又因缺失声明的 Darwin companion `cli/node_modules/koffi/build/koffi/darwin_arm64/koffi.node` 失败。没有绕过改用其他提取器,也没有启动 WorkBuddy 去安装测试技能。 +- [完整摘要记录](evidence/B02-01/rea-summary-record.json)、[REA 提取限制](evidence/B02-01/rea-extract-limitation.json) 已提交。父 Evidence 的大型 bundle 仅保留于验收端临时证据,不把其路径当作跨机交接的唯一依据。 + +开发可依据已有恢复组件和用户明确要求先修确认问题;当前原版推荐卡是否另有状态图标、精确操作位置及复杂安装态细节,须后续通过 REA 补同状态证据,未拿到不能宣称完整 1:1。 + +## 3. 开发任务与验收标准 + +### YC-005 / P2:推荐卡片切换真实已安装状态 + +允许边界:`RecommendedSkills.tsx`、`SkillsPage.tsx`、推荐局部 CSS/既有卡片适配、相关 UI 回归。原版恢复组件本体、共享卡片与技能身份模型如需变更,由开发主对话统一审查;不重写安装下载后端或扩展市场服务。 + +- **AC5.1**:同推荐身份、同来源版本的 current 安装项,卡片不再渲染添加 Plus 按钮,不仅是禁用或透明隐藏;使用与已安装语义一致的卡片状态和动作。辅助技术标签也不能继续叫“安装某技能”。 +- **AC5.2**:按既有 REA 恢复记录实现原版的已安装/使用展示,核对布局、图标槽位、字体、默认/hover/focus 与浅深色;不要把文字“已安装”加在角落就认定完成。 +- **AC5.3**:未安装项仍可添加并显示真实安装进度;安装成功后自动切换,关闭/重开页面及外部安装/卸载通知后状态同步,不要求重启。 +- **AC5.4**:禁用、不可调用、损坏、同名本地、版本不同、本地修改、工作区技能等状态仍明确区分。不要只按同名或当前标签把不同来源/scope 误认为同一个已安装技能;保留已有保护工作区和本地修改的规则。 +- **AC5.5**:版本不同/本地修改等确有替换需求时,用清楚的更新/替换动作及既有确认流程,不伪装成首次添加,也不直接覆盖。正常 current 状态不发重复安装请求。 +- **AC5.6**:使用动作保留既有进入新任务草稿、不自动发送的语义;禁用技能先启用及失败反馈保持。原动作槽位与卡片下方不要同时留下无依据的重复使用按钮;按原版证据确定位置。 + +### YC-006 / P2:移除底部“查看已安装技能” + +- **AC6.1**:正常推荐列表底部不再渲染 `.yc-skill-recommendation-footer` 及“查看已安装技能”按钮,清理只服务于该入口的空白、样式和 props;不能只是 CSS 隐藏。 +- **AC6.2**:保留顶部“已安装(n)”导航及真实计数,进入已安装、返回推荐、搜索与目录刷新仍正常。 +- **AC6.3**:复核目录不可用/空状态中的重复同名入口;保持清楚的错误与恢复方式,使用现有顶部导航,不为删除正常 footer 而留下另一个底部重复入口。未使用的 callback 清理要核对所有分支与调用方。 +- **AC6.4**:测试通过顶部现有入口进入已安装,新增断言 footer 不存在;不允许为旧测试便利保留用户明确要求移除的按钮。 + +## 4. 验证与交付 + +- 必跑 `npm run test:recommended-skills:ui`、相关能力输入/技能管理回归,以及 `npm run build`。源码/class 检查不能替代实际已安装推荐卡的计算样式、截图与交互。 +- 受控 Electron fixture 覆盖未安装→安装→current、禁用/损坏、不同来源与版本、本地修改、卸载/重装及目录错误;不得使用用户真实技能或为验样式安装 WorkBuddy 技能。 +- 对已有 UI 脚本中的“禁用 Plus 即通过”和 footer 导航断言改成真实目标,保留安装确认、进度、取消、重复请求、外部通知、使用草稿等有效回归。 +- Windows 浅/深、窄窗下记录相同状态的 DOM、计算样式和截图;同版本 WorkBuddy 对照尚缺则明确“未验证”,不将本地恢复样式称为像素通过。 +- 本次复现探针在固定受测检出运行:独立安装依赖后,将 Git 中的 `skills-installed-probe.mjs` 复制为 `.tmp/b01-acceptance/skills-installed-probe.mjs` 再执行。该路径用于其自身 Electron 子进程启动;脚本只用受控宿主,不真实安装。 +- 创建 `docs/协作/B02-交付-01.md` 接单、记录完整代码 SHA、逐项 AC、命令及证据。不要预填验收通过;不把此项混入 B01/YC-001 的修复完成声明。 + +本次只发布新需求和证据,**未修改产品代码**。文档推送成功后窗口交回开发端,用户可将 B02 与 B01 的后续安排一并交给开发对话。 diff --git a/docs/协作/evidence/B02-01/installed-recommendation-list.png b/docs/协作/evidence/B02-01/installed-recommendation-list.png new file mode 100644 index 0000000..a3f0f32 Binary files /dev/null and b/docs/协作/evidence/B02-01/installed-recommendation-list.png differ diff --git a/docs/协作/evidence/B02-01/installed-state.json b/docs/协作/evidence/B02-01/installed-state.json new file mode 100644 index 0000000..14d1164 --- /dev/null +++ b/docs/协作/evidence/B02-01/installed-state.json @@ -0,0 +1,16 @@ +{ + "codeSha": "51531dcae8d8ade85db9e203bc8c0f712805b2ae", + "installedState": { + "state": "current", + "cardClasses": "sm-card", + "installedClass": false, + "addButtonPresent": true, + "addButtonDisabled": true, + "addSvgPresent": true, + "useSlotPresent": false, + "footerPresent": true, + "footerText": "查看已安装技能" + }, + "errors": [], + "scope": "Actual recommended-skill component and CSS in Electron with controlled host; no network or actual skill installation" +} \ No newline at end of file diff --git a/docs/协作/evidence/B02-01/rea-extract-limitation.json b/docs/协作/evidence/B02-01/rea-extract-limitation.json new file mode 100644 index 0000000..b5e4df9 --- /dev/null +++ b/docs/协作/evidence/B02-01/rea-extract-limitation.json @@ -0,0 +1 @@ +{"error":"Analysis failed","code":"artifact_operation_failed","category":"unavailable","message":"ASAR unpacked companion bytes are unavailable for cli/node_modules/koffi/build/koffi/darwin_arm64/koffi.node. Select the archive in place with its .unpacked directory beside it, then retry.","retryable":false,"remediation":{"action":"ASAR unpacked companion bytes are unavailable for cli/node_modules/koffi/build/koffi/darwin_arm64/koffi.node. Select the archive in place with its .unpacked directory beside it, then retry."},"details":{"logical_path":"cli/node_modules/koffi/build/koffi/darwin_arm64/koffi.node","declared_sha256":null,"calculated_sha256":null,"unpacked":true}} diff --git a/docs/协作/evidence/B02-01/rea-summary-record.json b/docs/协作/evidence/B02-01/rea-summary-record.json new file mode 100644 index 0000000..288e3ab --- /dev/null +++ b/docs/协作/evidence/B02-01/rea-summary-record.json @@ -0,0 +1,419 @@ +{ + "analysis_profile": null, + "authority": "shipped-artifact", + "confidence": "derived", + "environment": null, + "evidence_id": "ev_4d76ad1b9f386f97343307067ee2f5b5b91d2189366ab1cbe3890954db4634a6", + "evidence_links": [ + "ev_ef70f1417c069be6bf352efcb858244efaae444f71f1f9893ed2fb579ed88849" + ], + "limitations": [ + "Artifact integrity metadata contradicts observed bytes at cli/bin/native/win32-x64/job-object.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty/OpenConsole.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty_console_list.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty/package.json; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/koffi/build/koffi/win32_x64/koffi.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/koffi/package.json; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/koffi/src/cnoke/package.json; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/genie-trash/win32-x64.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/ripgrep/x64-win32/rg.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/ripgrep/x64-win32/ripgrep.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/betterleaks.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox-center.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox-cli-gc.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox-cli.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox_ffi.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbx.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbx_sdk.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbxldr.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbxlow.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/wecom-cli/wecom-cli.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at native/perf-metrics/build/Release/perf_metrics.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at native/turing-sdk/build/Release/turing_sdk.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at native/wechat-copydata-decoder/build/Release/wechat_copydata_decoder.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@ban-dal/os-bridge-win32-x64-msvc/os-bridge.win32-x64-msvc.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty/OpenConsole.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty_console_list.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@tencent/qimei-node/build/Release/qimei.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@tencent/tencent-docs-ai-engine/bin/win32-x64/editor_sdk.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@tencent/wwapi-electron/prebuilds/win32-x64/node.napi.node; the observed bytes are untrusted.", + "34 additional limitations omitted from this view; consult source parent Evidence ev_ef70f1417c069be6bf352efcb858244efaae444f71f1f9893ed2fb579ed88849 for the complete list (the caller-supplied record, or its retaining session)." + ], + "locations": [ + { + "kind": "artifact-path", + "path": "C:\\Users\\Administrator\\AppData\\Local\\Programs\\WorkBuddy\\resources\\app.asar" + } + ], + "normalized_result": { + "artifact": { + "path": "C:\\Users\\Administrator\\AppData\\Local\\Programs\\WorkBuddy\\resources\\app.asar", + "sha256": "026a1d0805a2366d06390cef52e157a7277543cdd27dd308e6b1acb47ffffdc1" + }, + "coverage": { + "examined": 7200, + "exhausted": true, + "next_offset": null, + "status": "complete-within-view", + "total": 30363 + }, + "kind": "summary", + "limitations": [ + "Artifact integrity metadata contradicts observed bytes at cli/bin/native/win32-x64/job-object.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty/OpenConsole.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty_console_list.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty/package.json; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/koffi/build/koffi/win32_x64/koffi.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/koffi/package.json; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/koffi/src/cnoke/package.json; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/genie-trash/win32-x64.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/ripgrep/x64-win32/rg.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/ripgrep/x64-win32/ripgrep.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/betterleaks.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox-center.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox-cli-gc.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox-cli.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox_ffi.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbx.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbx_sdk.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbxldr.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbxlow.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/wecom-cli/wecom-cli.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at native/perf-metrics/build/Release/perf_metrics.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at native/turing-sdk/build/Release/turing_sdk.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at native/wechat-copydata-decoder/build/Release/wechat_copydata_decoder.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@ban-dal/os-bridge-win32-x64-msvc/os-bridge.win32-x64-msvc.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty/OpenConsole.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty_console_list.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@tencent/qimei-node/build/Release/qimei.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@tencent/tencent-docs-ai-engine/bin/win32-x64/editor_sdk.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@tencent/wwapi-electron/prebuilds/win32-x64/node.napi.node; the observed bytes are untrusted.", + "34 additional limitations omitted from this view; consult source parent Evidence ev_ef70f1417c069be6bf352efcb858244efaae444f71f1f9893ed2fb579ed88849 for the complete list (the caller-supplied record, or its retaining session)." + ], + "parent_digest": "ef70f1417c069be6bf352efcb858244efaae444f71f1f9893ed2fb579ed88849", + "parent_evidence_id": "ev_ef70f1417c069be6bf352efcb858244efaae444f71f1f9893ed2fb579ed88849", + "parent_operation": "analyze_javascript_application", + "summary": { + "coverage": { + "application": { + "limits": [], + "omitted_count": null, + "status": "partial", + "truncated": false + }, + "application_unknowns": 11145, + "semantic": { + "families": [ + { + "family": "boundary", + "omitted_relations": null, + "retained_relations": 0, + "status": "partial", + "unknown_count": 6667 + }, + { + "family": "call-flow", + "omitted_relations": null, + "retained_relations": 0, + "status": "partial", + "unknown_count": 6667 + }, + { + "family": "child-process", + "omitted_relations": null, + "retained_relations": 0, + "status": "partial", + "unknown_count": 6667 + }, + { + "family": "closure", + "omitted_relations": null, + "retained_relations": 5729, + "status": "partial", + "unknown_count": 6667 + }, + { + "family": "configuration", + "omitted_relations": null, + "retained_relations": 0, + "status": "partial", + "unknown_count": 6667 + }, + { + "family": "data-flow", + "omitted_relations": null, + "retained_relations": 0, + "status": "partial", + "unknown_count": 18596 + }, + { + "family": "event", + "omitted_relations": null, + "retained_relations": 0, + "status": "partial", + "unknown_count": 6667 + }, + { + "family": "object-flow", + "omitted_relations": null, + "retained_relations": 0, + "status": "partial", + "unknown_count": 14738 + }, + { + "family": "promise-ownership", + "omitted_relations": null, + "retained_relations": 0, + "status": "partial", + "unknown_count": 6666 + }, + { + "family": "request", + "omitted_relations": null, + "retained_relations": 0, + "status": "partial", + "unknown_count": 6666 + }, + { + "family": "resource-lifecycle", + "omitted_relations": null, + "retained_relations": 0, + "status": "partial", + "unknown_count": 6666 + }, + { + "family": "timer", + "omitted_relations": null, + "retained_relations": 0, + "status": "partial", + "unknown_count": 6666 + } + ], + "limits": [ + { + "name": "semantic_graph_file_node_ceiling", + "unit": "items", + "value": 20000 + }, + { + "name": "semantic_graph_file_unknown_ceiling", + "unit": "items", + "value": 20000 + }, + { + "name": "semantic_graph_node_ceiling", + "unit": "items", + "value": 100000 + }, + { + "name": "semantic_graph_unknown_ceiling", + "unit": "items", + "value": 100000 + } + ], + "omitted_nodes": null, + "omitted_relations": null, + "omitted_unknown_occurrences": [ + { + "family": "boundary", + "occurrences": 3618, + "reason": "incomplete-module" + }, + { + "family": "call-flow", + "occurrences": 23349, + "reason": "dynamic-call" + }, + { + "family": "call-flow", + "occurrences": 3618, + "reason": "incomplete-module" + }, + { + "family": "child-process", + "occurrences": 3618, + "reason": "incomplete-module" + }, + { + "family": "closure", + "occurrences": 3618, + "reason": "incomplete-module" + }, + { + "family": "configuration", + "occurrences": 3618, + "reason": "incomplete-module" + }, + { + "family": "data-flow", + "occurrences": 3618, + "reason": "incomplete-module" + }, + { + "family": "event", + "occurrences": 3618, + "reason": "incomplete-module" + }, + { + "family": "object-flow", + "occurrences": 25351, + "reason": "ambiguous-target" + }, + { + "family": "object-flow", + "occurrences": 1541, + "reason": "dynamic-property" + }, + { + "family": "object-flow", + "occurrences": 3618, + "reason": "incomplete-module" + }, + { + "family": "promise-ownership", + "occurrences": 3619, + "reason": "incomplete-module" + }, + { + "family": "request", + "occurrences": 3619, + "reason": "incomplete-module" + }, + { + "family": "resource-lifecycle", + "occurrences": 3619, + "reason": "incomplete-module" + }, + { + "family": "timer", + "occurrences": 67, + "reason": "ambiguous-target" + }, + { + "family": "timer", + "occurrences": 3619, + "reason": "incomplete-module" + } + ], + "status": "partial", + "truncated": true + }, + "semantic_unknowns": 100000 + }, + "electron": { + "browser_windows": 0, + "context_bridge_apis": 0, + "explicit_web_preferences": 0, + "exposed_api_members": 0, + "ipc": { + "ambiguous_renderer_transmissions": 0, + "dynamic_channel_operations": 0, + "literal_channels": 0, + "main_handlers": 0, + "operations": 0, + "paired_renderer_transmissions": 0, + "renderer_listeners": 0, + "renderer_transmissions": 0, + "unpaired_literal_renderer_transmissions": 0 + }, + "native_addon_bindings": 0, + "preload_entrypoints": 0, + "resolved_native_addon_bindings": 0, + "resolved_utility_entrypoints": 0, + "sender_validation_observations": 1, + "utility_processes": 0 + }, + "format": "asar", + "input_path": "C:\\Users\\Administrator\\AppData\\Local\\Programs\\WorkBuddy\\resources\\app.asar", + "limitation_count": 53, + "root_artifact_sha256": "026a1d0805a2366d06390cef52e157a7277543cdd27dd308e6b1acb47ffffdc1", + "source_selection": { + "requested_paths": [ + "renderer/assets/src-DbE1_mgM.js" + ], + "unselected_files": 11183 + }, + "statistics": { + "findings": 678, + "invalid_utf8_files": 0, + "modules": 0, + "nested_asar_containers": 0, + "parse_failures": 0, + "parsed_javascript_files": 1, + "relevant_files": 11211, + "skipped_javascript_files": 0, + "text_bytes_read": 1968427, + "visited_ast_nodes": 415338 + } + }, + "unknowns": [ + "100000 semantic graph unknowns remain in the parent Evidence.", + "11145 application graph unknowns remain in the parent Evidence.", + "Artifact integrity metadata contradicts observed bytes at cli/bin/native/win32-x64/job-object.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty/OpenConsole.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty_console_list.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/@lydell/node-pty/package.json; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/koffi/build/koffi/win32_x64/koffi.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/koffi/package.json; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/node_modules/koffi/src/cnoke/package.json; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/genie-trash/win32-x64.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/ripgrep/x64-win32/rg.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/ripgrep/x64-win32/ripgrep.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/betterleaks.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox-center.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox-cli-gc.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox-cli.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/sandbox_ffi.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbx.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbx_sdk.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbxldr.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/sandbox/5.8.6/tsbxlow.dll; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at cli/vendor/wecom-cli/wecom-cli.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at native/perf-metrics/build/Release/perf_metrics.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at native/turing-sdk/build/Release/turing_sdk.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at native/wechat-copydata-decoder/build/Release/wechat_copydata_decoder.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@ban-dal/os-bridge-win32-x64-msvc/os-bridge.win32-x64-msvc.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty/OpenConsole.exe; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@lydell/node-pty-win32-x64/prebuilds/win32-x64/conpty_console_list.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@tencent/qimei-node/build/Release/qimei.node; the observed bytes are untrusted.", + "Artifact integrity metadata contradicts observed bytes at node_modules/@tencent/tencent-docs-ai-engine/bin/win32-x64/editor_sdk.exe; the observed bytes are untrusted.", + "graph.coverage.status is partial", + "semantic_graph.coverage.status is partial", + "35 additional unknowns omitted from this view; consult source parent Evidence ev_ef70f1417c069be6bf352efcb858244efaae444f71f1f9893ed2fb579ed88849 for the complete list (the caller-supplied record, or its retaining session)." + ], + "view": { + "kind": "summary" + }, + "view_digest": "f6bfbc925ceccbfd39e72ae31b623c5c3d5c07b9cedba7f03f15d923ab0fbf8b" + }, + "operation": "inspect_analysis_view", + "parameters": { + "parent_evidence_id": "ev_ef70f1417c069be6bf352efcb858244efaae444f71f1f9893ed2fb579ed88849", + "view": { + "kind": "summary" + } + }, + "predicate_type": "rea.analysis-view", + "provider": { + "id": "rea-analysis-view", + "name": "REA analysis view projection", + "version": "1" + }, + "raw_result": null, + "relationship_coverage": null, + "subject": { + "architecture": null, + "digest": { + "sha256": "026a1d0805a2366d06390cef52e157a7277543cdd27dd308e6b1acb47ffffdc1" + }, + "format": "asar", + "local_path": "C:\\Users\\Administrator\\AppData\\Local\\Programs\\WorkBuddy\\resources\\app.asar", + "name": "app.asar" + } +} diff --git a/docs/协作/evidence/B02-01/skills-installed-probe.mjs b/docs/协作/evidence/B02-01/skills-installed-probe.mjs new file mode 100644 index 0000000..9ef56e4 --- /dev/null +++ b/docs/协作/evidence/B02-01/skills-installed-probe.mjs @@ -0,0 +1,386 @@ +/** Isolated Electron fixture for the recommended-skill page. + * The host is fully controlled: no network, no real skill storage, no user data. */ +import fs from 'node:fs' +import path from 'node:path' +import assert from 'node:assert/strict' +import { createRequire } from 'node:module' +import { spawn } from 'node:child_process' + +const require = createRequire(import.meta.url) +const electronModule = require('electron') +const electron = typeof electronModule === 'object' && electronModule.app ? electronModule : null +const repository = path.resolve('.'), base = path.join(repository, '.tmp') + +if (!electron) { + fs.mkdirSync(base, { recursive: true }) + const output = fs.mkdtempSync(path.join(base, 'recommended-skills-ui-')) + const env = { ...process.env } + delete env.ELECTRON_RUN_AS_NODE + delete env.ELECTRON_RENDERER_URL + const child = spawn(electronModule, ['--no-sandbox', '--disable-gpu-sandbox', path.resolve('.tmp/b01-acceptance/skills-installed-probe.mjs'), output], { stdio: 'inherit', windowsHide: true, env }) + const timer = setTimeout(() => child.kill(), 120000) + process.exitCode = await new Promise(resolve => child.on('exit', code => { clearTimeout(timer); resolve(code ?? 1) })) +} else { + void (async () => { + const { app, BrowserWindow, ipcMain } = electron + const output = path.resolve(process.argv.at(-1)), relative = path.relative(base, output) + assert.ok(relative.startsWith('recommended-skills-ui-') && !relative.includes('..') && !path.isAbsolute(relative)) + const userData = path.join(output, 'data') + fs.mkdirSync(userData, { recursive: true }) + app.setPath('userData', userData) + const checks = [], errors = [], calls = [], screenshots = [] + + const recommendation = { + id: 'archify', + name: 'Archify', + description: '生成可交互的架构图、流程图和一页式说明', + author: 'tt-a1i', + license: 'MIT', + repository: 'tt-a1i/archify', + version: '3.2.0', + commit: '25cf37080fc8b1dbe758a4efc3935b55f3327083', + packagePath: 'archify.zip', + sha256: '70e210265c59f24dcbdf2a502ff671104b5454e7ee7d3554aecd86bad32384f5', + expectedBytes: 5271959, + containsScripts: true + } + const localSkill = { + id: 'user:local', name: 'Archify', description: '本地导入的同名技能', version: '1.0.0', + path: 'C:/local/archify', scope: 'user', enabled: true, userInvocable: true, + disableModelInvocation: false, source: 'imported', userModified: true + } + const installedRecommendation = { + id: 'user:rec', name: 'Archify', description: recommendation.description, version: '3.2.0', + path: 'C:/managed/archify', scope: 'user', enabled: true, userInvocable: true, + disableModelInvocation: false, source: 'imported', userModified: false, + origin: { + recommendationId: 'archify', repository: recommendation.repository, commit: recommendation.commit, + packagePath: recommendation.packagePath, archiveSha256: recommendation.sha256, version: '3.2.0', installedAt: 1 + } + } + + let installed = [], progress = null, failNext = false, cancelRequested = false, delay = 0, recommendationOverride = null, recommendationFails = false + let clock = 0 + const listeners = new Set(), changedListeners = new Set() + const emit = state => { + // The host stamps a monotonic time; a strict comparison rejects a stale frame. + const stamped = { ...state, updatedAt: ++clock } + progress = stamped + for (const listener of listeners) listener(stamped) + for (const win of BrowserWindow.getAllWindows()) if (!win.isDestroyed()) win.webContents.send('skills:install-progress', stamped) + } + const changed = () => { + for (const listener of changedListeners) listener() + for (const win of BrowserWindow.getAllWindows()) if (!win.isDestroyed()) win.webContents.send('skills:changed') + } + + ipcMain.handle('skills-qa', async (_event, method, args) => { + calls.push([method, ...args]) + if (method === 'recommendations') { + if (recommendationFails) throw Error('推荐目录服务不可用') + return recommendationOverride ?? [recommendation] + } + if (method === 'list') return installed + if (method === 'setEnabled') { installed = installed.map(skill => skill.id === args[0] ? { ...skill, enabled: args[1] } : skill); changed(); return } + if (method === 'installState') return progress + // The host only accepts the cancel request; the terminal frame still comes from the install flow. + if (method === 'cancelInstall') { cancelRequested = true; return { cancelled: true } } + if (method === 'installRecommended') { + const [id, options] = args + if (id !== recommendation.id) throw Error('未知推荐技能') + if (failNext) { + failNext = false + emit({ operationId: options.operationId, recommendationId: id, phase: 'failed', receivedBytes: 0, error: '下载失败:网络错误', updatedAt: Date.now() }) + throw Error('下载失败:网络错误') + } + emit({ operationId: options.operationId, recommendationId: id, phase: 'downloading', receivedBytes: 0, totalBytes: recommendation.expectedBytes, updatedAt: Date.now() }) + await new Promise(resolve => setTimeout(resolve, delay)) + if (cancelRequested) { cancelRequested = false; emit({ operationId: options.operationId, recommendationId: id, phase: 'cancelled', receivedBytes: 0, updatedAt: Date.now() }); throw Object.assign(Error('推荐技能安装已取消'), { name: 'AbortError' }) } + emit({ operationId: options.operationId, recommendationId: id, phase: 'validating', receivedBytes: recommendation.expectedBytes, totalBytes: recommendation.expectedBytes, updatedAt: Date.now() }) + await new Promise(resolve => setTimeout(resolve, delay)) + if (cancelRequested) { cancelRequested = false; emit({ operationId: options.operationId, recommendationId: id, phase: 'cancelled', receivedBytes: recommendation.expectedBytes, updatedAt: Date.now() }); throw Object.assign(Error('推荐技能安装已取消'), { name: 'AbortError' }) } + emit({ operationId: options.operationId, recommendationId: id, phase: 'installing', receivedBytes: recommendation.expectedBytes, totalBytes: recommendation.expectedBytes, updatedAt: Date.now() }) + await new Promise(resolve => setTimeout(resolve, delay)) + installed = [installedRecommendation] + emit({ operationId: options.operationId, recommendationId: id, phase: 'installed', receivedBytes: recommendation.expectedBytes, totalBytes: recommendation.expectedBytes, updatedAt: Date.now() }) + changed() + return installedRecommendation + } + if (method === 'qaSetInstalled') { installed = args[0]; changed(); return installed } + if (method === 'qaFailNext') { failNext = true; return true } + if (method === 'qaDelay') { delay = args[0]; return true } + if (method === 'qaSetRecommendations') { recommendationOverride = args[0]; return true } + if (method === 'qaFailRecommendations') { recommendationFails = args[0] === true; return true } + // Simulate an operation owned by another window. + if (method === 'qaEmitForeign') { + const [phase, operationId] = args + emit({ operationId, recommendationId: recommendation.id, phase, receivedBytes: 0, totalBytes: recommendation.expectedBytes, updatedAt: Date.now() }) + return true + } + // Replay a frame with an older timestamp to prove the terminal state never regresses. + if (method === 'qaEmitStale') { + const [phase, operationId, updatedAt] = args + const stamped = { operationId, recommendationId: recommendation.id, phase, receivedBytes: 0, totalBytes: recommendation.expectedBytes, updatedAt } + progress = stamped + for (const listener of listeners) listener(stamped) + for (const win of BrowserWindow.getAllWindows()) if (!win.isDestroyed()) win.webContents.send('skills:install-progress', stamped) + return true + } + if (method === 'qaSubscribe') { listeners.add(args[0]); return true } + if (method === 'qaSubscribeChanged') { changedListeners.add(args[0]); return true } + throw Error('Unknown QA method ' + method) + }) + + fs.writeFileSync(path.join(output, 'preload.cjs'), `const{contextBridge,ipcRenderer}=require('electron'); +const invoke=(method,...args)=>ipcRenderer.invoke('skills-qa',method,args); +contextBridge.exposeInMainWorld('ycode',{skills:{ + list:(cwd)=>invoke('list',cwd), + recommendations:()=>invoke('recommendations'), + installRecommended:(id,options)=>invoke('installRecommended',id,options), + cancelInstall:(operationId)=>invoke('cancelInstall',operationId), + installState:()=>invoke('installState'), + onInstallProgress:(listener)=>{const wrapped=(_event,state)=>listener(state);ipcRenderer.on('skills:install-progress',wrapped);return()=>ipcRenderer.removeListener('skills:install-progress',wrapped)}, + onChanged:(listener)=>{const wrapped=()=>listener();ipcRenderer.on('skills:changed',wrapped);return()=>ipcRenderer.removeListener('skills:changed',wrapped)}, + read:async()=>({skill:{},content:'',files:[]}), + setEnabled:(...args)=>invoke('setEnabled',...args), remove:async()=>{}, export:async()=>null, importPath:async()=>null + ,qaSetInstalled:(v)=>invoke('qaSetInstalled',v), + qaFailNext:()=>invoke('qaFailNext'), + qaDelay:(v)=>invoke('qaDelay',v), + qaSetRecommendations:(v)=>invoke('qaSetRecommendations',v), + qaFailRecommendations:(v)=>invoke('qaFailRecommendations',v), + qaEmitForeign:(phase,operationId)=>invoke('qaEmitForeign',phase,operationId), + qaEmitStale:(phase,operationId,updatedAt)=>invoke('qaEmitStale',phase,operationId,updatedAt) +},resources:{openExternal:async()=>undefined}});`) + + const location = file => JSON.stringify(path.join(repository, file).replaceAll('\\', '/')) + fs.writeFileSync(path.join(output, 'fixture.tsx'), `import React from 'react'; +import{createRoot}from'react-dom/client'; +import Center from ${location('src/renderer/src/capabilities/CapabilityCenter')}; +import${location('src/renderer/src/styles/tokens.css')}; +import${location('src/renderer/src/styles/app.css')}; +window.__qa={uses:[],created:0,find:0}; +createRoot(document.getElementById('root')).render( +