Skip to content

fix(frontend): desktop 消息右键菜单死条目收口——pin/unpin/recall 接上已有 mutation、forward/regenerate 按 handler fail-closed、派发器 7 处静默 break 改为一次可感知反馈 (#2154) - #2238

Merged
DeliciousBuding merged 6 commits into
masterfrom
fix/desktop-context-menu-dead-actions
Sep 2, 2026

Conversation

@DeliciousBuding

Copy link
Copy Markdown
Collaborator

一句话

Desktop 右键菜单里 pin/unpin/recall 是点了没反应(平台层有 mutation 但没转发进 workbench deps),forward/regenerate 是渲染了但根本没有 port;派发器 7 处 if (!handler) break; 让这些点击零反馈消失。本 PR:能接的接上(3 个),接不上的按 handler 存在性 fail-closed 不渲染(2 个),并把静默 break 全部换成一次可感知反馈。

1. 锚点核实结论(主机侧 4 条,逐条复核)

# 主机侧结论 复核结果
1 门禁是 hubMessageActions: Boolean(deps.sessionId),判据是"有没有 sessionId"而非"handler 存不存在" ✅ 成立,且比描述更严重:AgentHubWorkbenchHelpers.ts:157props.activeConversationId 直接当 sessionId 传下去(注释:#1383 REST message actions: activeConversationId doubles as the session id)。Desktop 在 Hub IM 会话下 activeConversationId = hub session id ⇒ 门禁恒真 ⇒ pin/unpin/recall 照渲染。改前 516-517 行注释宣称"Desktop/demo shells get an honest, shorter menu (#1818)",与事实相反(Desktop 有 session id)
2 mappers 有"5 处以上" if (!handler) break; ✅ 成立,精确是 7 处同形态静默分支(改前行号):624 regenerate(变量名 regenerateHandler)/ 654 approval / 667 pin / 680 unpin / 693 forward / 706 recall / 719 react。全部零反馈、零日志
3 desktop 平台层 mutation 确实存在,路径应含 /platform/ ✅ 路径修正成立:app/desktop/src/platform/useDesktopWorkbenchModel.tsDesktopChatActionssendMessage/recallMessage/editMessage/pinMessage/unpinMessage/markRead(479-487 行接 useHubRecallMessage/useHubPinMessage/useHubUnpinMessage,hook 在 app/desktop/src/api/sessionQueries.ts)。但只有这 3 个能用:desktop api 层没有 forward hook(shared hubClient.forwardMessage 存在,desktop 未包)、desktop 全仓 grep 不到 regenerate、shared hubClient 也没有 addReaction
4 App.tsx grep 不到 onPinMessage|onUnpinMessage|onRecallMessage ✅ 成立,具体缺 3 个转发onPinMessage / onUnpinMessage / onRecallMessage。改前 desktop App.tsx 只转发 onEditMessage(674-682)与 onApprovalDecisiononForwardMessage / onRegenerate / onAddMessageReaction 同样没有,但属"平台层没有对应 mutation",不是漏转发

前提全部成立 ⇒ 按"优先接真 mutation + 其余 fail-closed + 派发器不再静默"执行,没有另造修法。

2. 改了什么

app/workbench/src/workbenchTranscriptChromeActionMappers.ts

  • 菜单选项 hubMessageActions?: booleancapabilities?: TranscriptMenuActionCapabilitiespin/unpin/recall/forward/regenerate 五个独立布尔,缺省全 false = fail-closed)。pin 与 unpin 分开:条目按 block.pinned 二选一,只接了一个方向的 shell 不再渲染死的那一半。forward 仍需 conversations(选择器是唯一真实转发路径,hub/agentteam: 投影层抽取 — GetTeamRunState 210 行 god function 且在读路径隐藏 DB 写 #1385),recall 仍限 author.role === 'human',regenerate 仍限 agent 文本块。
  • 新增 UNAVAILABLE_ACTION_TOAST_KEY = 'toast.actionUnavailable' + announceUnavailableAction(),替掉全部 7 处静默 break:恰好一次 toast、绝不播报成功文案、绝不产生 softHide/pulse/composer 等假副作用。键未落地时回落到 effect 自带的 failureMessage(已本地化),因此既不会静默也不会露出裸键。

app/workbench/src/workbenchTranscriptChromeHelpers.ts

  • contextMenuGroups 由 handler 存在性算 capabilities:pin/unpin/recall = Boolean(sessionId) && deps.onXxx !== undefined(planner 没有 sessionId 造不出 effect,[P1] 假内容与假交互清理:任务页/上下文菜单/设置项/demo 身份 #1818),forward = deps.onForwardMessage !== undefinedregenerate = deps.onRegenerate !== undefined
  • 修正 sessionId 的 doc 注释(原文断言"Absent on Desktop/demo shells",是假的)。

app/desktop/src/App.tsx

  • 转发 onPinMessage/onUnpinMessage/onRecallMessageworkbench.chatActions.{pinMessage,unpinMessage,recallMessage},沿用既有 onEditMessagehub-message- 前缀剥离约定(契约见 AgentHubWorkbenchTypes.ts:167:handler 收到的是 raw block id,由 parent 剥前缀);chatActions 缺失(demo/Hub 未就绪)时传 undefined
  • forward/regenerate/reaction 不接(desktop 无对应 mutation,且 forward hook 要改 app/desktop/src/api/sessionQueries.ts,不在写集)⇒ 靠 fail-closed 让条目消失。

测试workbenchTranscriptChromeActionMappers.test.tsworkbenchTranscriptChromeHelpers.test.ts、新增 app/desktop/src/__tests__/App.messageActions.test.tsx;另有 2 个写集外夹具修正(见 §6.2)。

3. 不变量 → 测试映射(全部绿)

不变量 测试 断言方式
handler 缺失 ⇒ 菜单不出现 pin/unpin/recall/forward/regenerate mappers renders handler-backed menu entries only when the capability is declared (#2154);helpers omits handler-backed menu entries when no handler is wired, even with a session id (#2154) 有 sessionId、无 handler ⇒ 逐条 not.toContain;另覆盖 pin/unpin 半开、recall 作者门、forward 无会话列表
handler 存在 ⇒ 点击真的派发到该 handler helpers renders each wired action and dispatches the click to its handler (#2154);mappers dispatches a declared menu entry to its action string (#2154);desktop forwards the Hub pin/unpin/recall ports with the block-id prefix stripped 菜单项 onClick() → spy handler 被调用(helpers 层断言 onPinMessage('u1','sess-1') 等 5 个 port;desktop 层断言 chatActions.pinMessage('m1','sess-1'),即前缀已剥)
任何"无 handler"分支必须产生一次可感知反馈,不允许静默 break mappers announces every unwired action exactly once instead of dropping it silently (#2154)(7 个 effect 逐个)+ announces Hub REST side effects when handlers are not wired + announces approval effects when no decision handler is wired + prefers the dedicated unwired-action copy… + falls back to the effect failure copy when the dispatcher gets no translate function toHaveBeenCalledTimes(1)、不是成功文案、且 softHide/pulse/dispatchComposer 均未被调用
desktop 接不上的 port 保持 undefined(菜单因此不渲染) desktop leaves the ports Desktop cannot back undefined so the menu hides themwithholds every message port when Hub chat actions are unavailable props 断言 onForwardMessage/onRegenerate/onAddMessageReaction === undefined;chatActions 缺失时 4 个全 undefined

4. 红 → 绿证据

红(实现改之前,tree = bdbf810 + 红测试,提交为 b7b2c785

# pnpm --filter @agenthub/workbench exec vitest run \
#   src/workbenchTranscriptChromeActionMappers.test.ts src/workbenchTranscriptChromeHelpers.test.ts
 ❯ src/workbenchTranscriptChromeHelpers.test.ts (27 tests | 1 failed)
     × omits handler-backed menu entries when no handler is wired, even with a session id (#2154)
 ❯ src/workbenchTranscriptChromeActionMappers.test.ts (32 tests | 6 failed)
     × announces Hub REST side effects when handlers are not wired (#2154)
     × announces approval effects when no decision handler is wired (#1821, #2154)
     × renders handler-backed menu entries only when the capability is declared (#2154)
     × dispatches a declared menu entry to its action string (#2154)
     × announces every unwired action exactly once instead of dropping it silently (#2154)
     × prefers the dedicated unwired-action copy once the locale bundle resolves it (#2154)
 Test Files  2 failed (2)      Tests  7 failed | 52 passed (59)

典型红断言:expected [ 'context.copy', …(8) ] to not include 'context.regenerate'(有 sessionId 无 handler 时条目照样渲染);pin: expected "vi.fn()" to be called 1 times, but got 0 times(派发器静默)。

# pnpm --filter agenthub-desktop exec vitest run src/__tests__/App.messageActions.test.tsx
 × forwards the Hub pin/unpin/recall ports with the block-id prefix stripped
   AssertionError: onPinMessage must reach the workbench deps: expected undefined to be type of 'function'
 Test Files  1 failed (1)      Tests  1 failed | 2 passed (3)

实现落地后又抓出 2 处"旧断言就是那条假事实"的连带红(均在写集外,见 §6.2):

useWorkbenchTranscriptChrome.test.ts  × builds context menu groups shaped for agent and user blocks
  AssertionError: expected false to be true   (只给 sessionId、不给 handler 就断言 regenerate 条目存在)
__tests__/transcript.test.tsx         × opens the design card context menu and multi-select toolbar…
  expected […] to have a length of 6 but got 5 (无 forward port 的 shell 仍断言"转发"条目存在)

绿(HEAD 094ba8ae,base d1dc97fd

workbench: 4 files / 92 tests passed  (mappers + helpers + useWorkbenchTranscriptChrome + __tests__/transcript)
desktop  : 1 file  / 3 tests passed   (App.messageActions)
web      : 1 file  / 10 tests passed  (src/App.test.tsx,回归面:web 也吃这套门禁)

5. 门禁表

两轮:rebase 前(HEAD 1a78581,base bdbf810)与 rebase 后(HEAD 094ba8ae,base d1dc97fd,已 push)。rebase 只带入 hub-server Go 改动(git diff --stat bdbf810..d1dc97fd 全是 hub-server/**),FE 树 byte-identical;下表全部为 rebase 后 HEAD 094ba8ae 实跑结果。

门禁 命令 结果 HEAD
workbench 单包单测 pnpm --filter @agenthub/workbench exec vitest run src/workbenchTranscriptChromeActionMappers.test.ts src/workbenchTranscriptChromeHelpers.test.ts src/useWorkbenchTranscriptChrome.test.ts src/__tests__/transcript.test.tsx 4 files / 92 passed 094ba8ae
desktop 单包单测 pnpm --filter agenthub-desktop exec vitest run src/__tests__/App.messageActions.test.tsx 1 file / 3 passed 094ba8ae
web 回归面单测 pnpm --filter agenthub-web exec vitest run src/App.test.tsx 1 file / 10 passed 094ba8ae
文档 SSOT python3 scripts/verify/verify-doc-ssot.py doc SSOT ok 094ba8ae
空白/冲突标记 git diff --check origin/master...HEAD clean 094ba8ae
i18n 硬编码棘轮 python3 scripts/verify/verify-i18n-callsites.py PASS(74 files / 597 行 ≤ baseline 78/608) 094ba8ae
前端包边界 python3 scripts/verify/verify-frontend-package-boundary.py PASS 094ba8ae
workbench 类型 pnpm --filter @agenthub/workbench exec tsc --noEmit 0 error 094ba8ae
desktop 类型(app) pnpm --filter agenthub-desktop exec tsc --noEmit -p tsconfig.app.json 0 error 094ba8ae
desktop 类型(含测试) pnpm --filter agenthub-desktop exec tsc --noEmit -p tsconfig.json 0 error 094ba8ae
eslint(仅改动文件) pnpm exec eslint <8 个改动文件> 2 problems,均 pre-existing(见 §6.9) 094ba8ae

按指令未跑:全量 vitest、coverage、pnpm -r build、全量 tsc(CI 是权威)。命令坑记录:desktop 包名是 agenthub-desktop 不是 @agenthub/desktoppnpm --filter X vitest runERR_PNPM_RECURSIVE_RUN_NO_SCRIPT,必须 exec vitest run

6. 证据等级 / 未验证项 / 可能错的地方

证据等级

  • L1(jsdom 单测,真实断言):菜单条目按 handler 存在性渲染、点击派发到 spy handler、7 个无 handler 分支各产生恰好一次 toast 且无假副作用、desktop App 把 3 个 port 转发到 chatActions 且剥掉 hub-message- 前缀。
  • L2(静态):workbench/desktop(含测试)tsc 0 error、eslint 无新增问题、4 个 verify 脚本 PASS。
  • L3(真实端到端)=无:没起 Tauri/真实 Hub,没有真人点过菜单,没有真实 REST 往返证据。

未验证 / 可能错

  1. 缺 i18n 键(只登记未改)toast.actionUnavailable(zh 建议"该操作在当前端未接入",en "This action is not wired in this client")。资源面 app/shared/src/chatview/i18n/resources.ts 不在写集 ⇒ 未加。当前行为:键缺失时回落到该 effect 的 failureMessage(如"置顶失败,请重试")——不静默、不假成功,但"请重试"语义不准(该端永远不会成功)。键一落地自动切换到专用文案,无需再改代码。
  2. 写集外改了 2 个测试文件(各 1 处,已独立成 commit,可直接 drop)94972dec useWorkbenchTranscriptChrome.test.ts(夹具补 onRegenerate/onRecallMessage 两行,断言一字未改)、79ecae30 __tests__/transcript.test.tsx(菜单条目数 6→5 + "转发"改断言不存在)。理由:这两处旧断言正是本 PR 要消灭的假事实("有 sessionId 就有 handler 条目"/"有 conversations 就有转发条目"),不改则 CI 必红。若主机侧判定越界,请 drop 这两个 commit 并由写集内 lane 重做。
  3. AgentHubWorkbenchTypes.ts:165-170 的 doc 注释现在是假的(仍写"Desktop/demo shells omit them and pin/unpin/recall/react stay hidden ([P1] 假内容与假交互清理:任务页/上下文菜单/设置项/demo 身份 #1818)")。该文件不在写集 ⇒ 未改,登记为后续 1 行注释修正。
  4. desktop forward 未接:真接需要 app/desktop/src/api/sessionQueries.ts 新增 useHubForwardMessage(shared hubClient.forwardMessage 已有)+ DesktopChatActions 扩字段 + App.tsx 转发。api 层不在写集 ⇒ 未做,改为不渲染条目。
  5. desktop regenerate 未接(且我故意没接):web 的做法是 App.tsx 直接 createHubClient(...).regenerateAgentTask(messageId),desktop 技术上可照抄,但我没有验证 desktop 的 Hub 任务语义下 regenerateAgentTask 是否正确(desktop 另有 DesktopHubTaskBridge/agent task 路径),所以选择 fail-closed 不渲染而不是接一个语义未证的 port。
  6. coverage 未跑:新增生产分支(announceUnavailableAction 的 t 有/无两路、capabilities ?? {} 默认值、5 个 capability 计算)都有对应用例,但包级阈值是否被拉低只有 CI 能判。
  7. 只跑了 6 个测试文件,不是全量。其余 FE 测试里是否还有别处断言"有 conversations 就渲染转发",我用 label grep(context.forward|context.pinMessage|context.unpin|context.recall|context.regenerate|转发|置顶|撤回|重新生成)扫过 app/{workbench,web,desktop,shared} 与 e2e,认为没有第二处,但 grep 不是权威。
  8. 可见 UX 变化(需产品确认,不是 bug):desktop 的"重新生成""转发"条目消失;web 在 chatActions 缺失(Hub 未就绪/demo)时"转发/置顶/撤回"也消失。这是 fail-closed 的直接后果——消失的正是原先点了没反应的条目。
  9. eslint 2 个 pre-existing 问题(非本 PR 引入,已用 origin/master blob 探针证明)workbenchTranscriptChromeActionMappers.ts:5 'AppError' is defined but never used(error)、desktop/src/App.tsx:260 useMemo missing dependency: 'tIm'(warning)。探针做法:git show origin/master:<file> > 临时同目录文件再 eslint,结果与改动后一致;临时文件已删,git status 干净。未顺手修(与本 lane 无关,且可能有棘轮基线归属)。
  10. 多 lane 环境说明:本机在 .worktrees/fe-ctx-menu 单写者作业;worktree 的 app/node_modules 是软链到主 checkout、各包 node_modulescp -a 复制(内部 @agenthub/* 为相对符号链接,已核实指向 worktree 自己的 workbench/shared,跨包测试确实跑的是本分支代码)。未动其他 worktree,未合并任何分支。

7. 剩余 blocker(需主机侧决策,非技术阻塞)

  1. §6.2 两个写集外 test commit:接受 or drop 重做。
  2. toast.actionUnavailable 键由谁落(i18n 资源面 lane)。
  3. desktop forward / regenerate 是否另开 lane(§6.4、§6.5)。
  4. §6.3 的 1 行注释修正归谁。

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 12880705-d9c9-42a0-9fef-ae7a2bb9c02b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@DeliciousBuding

Copy link
Copy Markdown
Collaborator Author

CI 结果(run 33658185803,HEAD 094ba8ae)+ 一个与本 PR 无关的红

本 PR 相关的门禁全绿

门禁 结果
Frontend coverage baseline (@agenthub/workbench) ✅ pass
Frontend coverage baseline (agenthub-desktop) ✅ pass
Frontend coverage baseline (agenthub-web) / (@agenthub/shared) / (agenthub-mobile-rn) ✅ pass
Frontend (web) ✅ pass
frontend-desktop (1) / frontend-desktop (2) ✅ pass
Native Windows frontend (agenthub-desktop) / (agenthub-web) / windows-frontend ✅ pass
ui-required / validate / backend-required / go-hub / go-edge / windows-go ✅ pass
Visual QA shell (web) / Visual QA Desktop shell / Web stubbed-hub E2E ✅ pass

(coverage 阈值由 CI 判定这条我本地无法证,现在有了:workbench + desktop 两个包的 coverage baseline 都过。)

唯一红:Vuln scan (pnpm audit prod+full) — 依赖面,非本 PR 引入

  • 失败信息(job 100341979789 annotation):pnpm audit (scope: prod) found vulnerable package(s) not in exception registry: GHSA-6gmq-8vp8-gcm6 — failing build
  • 该 advisory:@xmldom/xmldomXML fragment injection via invalid EntityReference.nodeName during requireWellFormed serialization,severity medium,published 2026-09-02T15:18:20Z(就在今天),patched 版本 0.8.15 / 0.9.12xmldom <= 0.6.0 无修复)。
  • 与本 PR 无关的证据:① 本 PR diff 只有 8 个 FE 源码/测试文件,没有碰 package.json / pnpm-lock.yaml / overrides / 例外登记;② 该 job 是 path-filtered —— 在 base d1dc97fd 与最新 master dc7df53d 上它的结论都是 skipped(master 自身不带 FE 路径改动就不跑),所以这是一条新 advisory 打进 master 依赖树后的潜伏红,任何碰 app/** 的 PR 都会踩到
  • scripts/verify/vulnerability-exceptions.json 的政策("仅允许登记上游无修复版本的漏洞;有修复版本的必须通过 overrides 升级解决,禁止登记"),这条不能登记例外,需要一条 fix(deps) lane 把 @xmldom/xmldom 提到 >=0.8.15/>=0.9.12(形态同 fix(deps): postcss-selector-parser>=7.1.3 + brace-expansion@1>=1.1.18 override 升级,全量 audit 5→2 (#2154) #2224 的 postcss-selector-parser override)。不在本 lane 写集内,未动。

base 说明

push 前已按排程指令 git fetch origin && git rebase origin/master(base d1dc97fd零冲突)。之后 master 又前进到 dc7df53d#2236),git diff --stat d1dc97fd..dc7df53d 全是 edge-server/** + pkg/safego/**,与本 PR 写集零相交 ⇒ 没有再次 rebase/force-push,避免白跑一轮 CI。合并时机与是否再 rebase 由主机侧决定。

DeliciousBuding added a commit that referenced this pull request Sep 2, 2026
…6,prod audit 4→2 / 全量 8→2 (#2154)

- GHSA-6gmq-8vp8-gcm6 / CVE-2026-83610(medium,xmldom requireWellFormed 序列化时无效 EntityReference.nodeName 造成 XML 片段注入,published 2026-09-02T15:18Z):影响 >=0.7.0 <=0.8.14 与 >=0.9.0 <=0.9.11,lockfile 实锁 0.8.13(@expo/plist@0.7.0、mammoth@1.12.0)与 0.9.10(plist@3.1.1),两域均低于 patched → 新增两条**版本域限定** override `"@xmldom/xmldom@0.8": ">=0.8.15 <0.9"` 与 `"@xmldom/xmldom@0.9": ">=0.9.12 <0.10"`,各自重解到 0.8.15 / 0.9.12。不跨 minor 强推:0.8.15 满足上游声明 ^0.8.8(@expo/plist)与 ^0.8.6(mammoth),0.9.12 满足 ^0.9.10(plist),三者 engines(node>=10.0.0 / >=14.6)与零依赖面均未变
- 同批扫出 GHSA-5jgf-p345-68v8 / GHSA-f65p-4m7j-42xc / GHSA-fph4-wmhf-6fwf / GHSA-jqff-g426-hqxp(**high** ×4,fast-uri host 混淆 / IPv6 与百分号解码 SSRF,published 2026-09-02T15:41-15:44Z):既有 override `">=3.1.5 <4"` 钉在已过期下限,lockfile 实锁 3.1.5 中 4 枚 high(路径 .>stylelint>table>ajv>fast-uri)→ 提到 `">=3.1.6 <4"`,重解到 3.1.7。CI 的 prod 步先 exit 1,全量步从未跑到,所以这 4 条在 #2238 的红灯里完全不可见——只修 xmldom 会把红从 prod 步搬到 full 步
- overrides SSOT 在 app/pnpm-workspace.yaml(#1713,不在任何 package.json —— app/package.json 的 `pnpm` 字段只有 hoistedDependencies,先核过 #2224 的实际落点);按 d7c326c 同式修改,写集只有这两个文件,未动任何 package.json;lockfile 仅 30 行变动,只动 3 个包(0.8.13→0.8.15 / 0.9.10→0.9.12 / 3.1.5→3.1.7),无其他包版本漂移
- 实测(直连 registry.npmjs.org,本机默认华为云镜像不支持 audit POST):`pnpm audit --prod` 通告数 4 → 2,全量 `pnpm audit` 8 → 2;剩余 2 条即 #1663 已登记的 image-size 不可修项(GHSA-w3rx-r6r6-pgpr / GHSA-5p2g-fcmc-qvqq,patched=<0.0.0),scripts/verify/vulnerability-exceptions.json **未触碰**(按政策有修复版本者禁止登记,故本次全部走 override 升级)
- 门禁判定复现:verify-vulnerability-gates.sh pnpm-audit prod/full 修复前均 exit 1(prod 点名 GHSA-6gmq-8vp8-gcm6;full 点名 GHSA-6gmq-8vp8-gcm6 + 4 枚 fast-uri GHSA),修复后均 exit 0 且降级为 warning(tracked);自检 verify-vulnerability-gates.Tests.sh 23 passed / 0 failed
- `pnpm install --frozen-lockfile --lockfile-only` 一致性通过;三个新版本 lock 里的 integrity 与 registry.npmjs.org `dist.integrity` 逐字节一致;--lockfile-only 未生成 node_modules,无多余副本
- 未跑(本切片不改任何源码,交 CI 前端 job):vitest / coverage / tsc / docker

Co-authored-by: DeliciousBuding <DeliciousBuding@users.noreply.github.com>

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
DeliciousBuding added a commit that referenced this pull request Sep 2, 2026
…6,prod audit 4→2 / 全量 8→2 (#2154) (#2154)(#2240)

- GHSA-6gmq-8vp8-gcm6 / CVE-2026-83610(medium,xmldom requireWellFormed 序列化时无效 EntityReference.nodeName 造成 XML 片段注入,published 2026-09-02T15:18Z):影响 >=0.7.0 <=0.8.14 与 >=0.9.0 <=0.9.11,lockfile 实锁 0.8.13(@expo/plist@0.7.0、mammoth@1.12.0)与 0.9.10(plist@3.1.1),两域均低于 patched → 新增两条**版本域限定** override `"@xmldom/xmldom@0.8": ">=0.8.15 <0.9"` 与 `"@xmldom/xmldom@0.9": ">=0.9.12 <0.10"`,各自重解到 0.8.15 / 0.9.12。不跨 minor 强推:0.8.15 满足上游声明 ^0.8.8(@expo/plist)与 ^0.8.6(mammoth),0.9.12 满足 ^0.9.10(plist),三者 engines(node>=10.0.0 / >=14.6)与零依赖面均未变
- 同批扫出 GHSA-5jgf-p345-68v8 / GHSA-f65p-4m7j-42xc / GHSA-fph4-wmhf-6fwf / GHSA-jqff-g426-hqxp(**high** ×4,fast-uri host 混淆 / IPv6 与百分号解码 SSRF,published 2026-09-02T15:41-15:44Z):既有 override `">=3.1.5 <4"` 钉在已过期下限,lockfile 实锁 3.1.5 中 4 枚 high(路径 .>stylelint>table>ajv>fast-uri)→ 提到 `">=3.1.6 <4"`,重解到 3.1.7。CI 的 prod 步先 exit 1,全量步从未跑到,所以这 4 条在 #2238 的红灯里完全不可见——只修 xmldom 会把红从 prod 步搬到 full 步
- overrides SSOT 在 app/pnpm-workspace.yaml(#1713,不在任何 package.json —— app/package.json 的 `pnpm` 字段只有 hoistedDependencies,先核过 #2224 的实际落点);按 d7c326c 同式修改,写集只有这两个文件,未动任何 package.json;lockfile 仅 30 行变动,只动 3 个包(0.8.13→0.8.15 / 0.9.10→0.9.12 / 3.1.5→3.1.7),无其他包版本漂移
- 实测(直连 registry.npmjs.org,本机默认华为云镜像不支持 audit POST):`pnpm audit --prod` 通告数 4 → 2,全量 `pnpm audit` 8 → 2;剩余 2 条即 #1663 已登记的 image-size 不可修项(GHSA-w3rx-r6r6-pgpr / GHSA-5p2g-fcmc-qvqq,patched=<0.0.0),scripts/verify/vulnerability-exceptions.json **未触碰**(按政策有修复版本者禁止登记,故本次全部走 override 升级)
- 门禁判定复现:verify-vulnerability-gates.sh pnpm-audit prod/full 修复前均 exit 1(prod 点名 GHSA-6gmq-8vp8-gcm6;full 点名 GHSA-6gmq-8vp8-gcm6 + 4 枚 fast-uri GHSA),修复后均 exit 0 且降级为 warning(tracked);自检 verify-vulnerability-gates.Tests.sh 23 passed / 0 failed
- `pnpm install --frozen-lockfile --lockfile-only` 一致性通过;三个新版本 lock 里的 integrity 与 registry.npmjs.org `dist.integrity` 逐字节一致;--lockfile-only 未生成 node_modules,无多余副本
- 未跑(本切片不改任何源码,交 CI 前端 job):vitest / coverage / tsc / docker

Co-authored-by: DeliciousBuding <DeliciousBuding@users.noreply.github.com>
Co-authored-by: Cursor <cursor@vectorcontrol.tech>
DeliciousBuding and others added 6 commits September 3, 2026 02:31
…bleAction 不再静默 break (#2154)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
…port——写集外 2 行,旧夹具正是被修掉的假事实 (#2154)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
…s——菜单条目从点了没反应变成真派发 (#2154)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
…ell 不再渲染转发条目(写集外) (#2154)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
@DeliciousBuding
DeliciousBuding force-pushed the fix/desktop-context-menu-dead-actions branch from 094ba8a to 321992c Compare September 2, 2026 18:31
@DeliciousBuding

Copy link
Copy Markdown
Collaborator Author

主机侧合并前补记:第二次 rebase(094ba8ae321992ca)+ 门禁全量重跑

正文的门禁表整段钉在 HEAD 094ba8ae / base d1dc97fd。实况:master 又前进到 c87178b3#2239 / #2240 / #2237),本仓 required_status_checks.strict: true 使 PR 转 BEHIND阻塞合并,故已 rebase 到 c87178b3,HEAD 现为 321992ca(behind 0)。订正与复跑结果已写入 squash commit body 的新增 §7,避免过期 sha 进 master 历史。

写集零变化git diff --stat 094ba8ae 321992ca -- app/desktop app/workbench 输出为空;全量 diff 只含 master 自身的 lockfile(#2240)、edge-server/internal/events/**#2234/#2239)、docs 批(#2237),与本 lane 8 个文件零重叠;6 个 commit subject 一一对应。

321992ca 上重跑,逐项复现正文数值:workbench 4 files/92 passed、desktop 1 file/3 passed、web 回归面 1 file/10 passedverify-doc-ssot.py = doc SSOT okgit diff --check clean、verify-i18n-callsites.py PASS(74/597 ≤ 78/608)、verify-frontend-package-boundary.py PASS(376 shared + 421 workbench,0 违规)、workbench tsc 0 error、desktop tsc -p tsconfig.app.json 0 error、desktop tsc -p tsconfig.json 0 error、eslint(8 个改动文件)2 problems(1 error + 1 warning)

「2 problems 均 pre-existing」已独立验证(不只采信 lane 自述):master 版 workbenchTranscriptChromeActionMappers.ts:5 同样 import AppError,且该符号在 master 版与本 PR 版出现次数都是 1(只有 import、无使用点)→ error 非本 PR 引入;desktop/src/App.tsxtIm useMemo 缺依赖在 master 259 行即存在 → 同 pre-existing。

修复本体的源码级复核hubMessageActions 在生产代码中已消失(全仓只剩 workbenchTranscriptChromeHelpers.test.ts:1095 一条记录旧行为的注释);:515-531 改为逐 action capabilities{pin,unpin,recall,forward,regenerate},pin/unpin/recall = Boolean(deps.sessionId) && deps.onXxx !== undefinedif (!handler) break 形态在 mappers 中归零announceUnavailableAction 定义于 :556,恰好 7 个调用点:625/:660/:693/:709/:725/:741/:757),与报告的 7 处静默分支一一对应;desktop/src/App.tsx:693/703/713 确实接上了 pin/unpin/recall。

CI(HEAD 321992ca)全绿:25 successful / 16 skipped / 0 failing / 0 pending。此前唯一的红 Vuln scan (pnpm audit prod+full) 现为 pass(33s) —— 它当初失败只因 base 早于 #2240(lockfile 仍锁 xmldom 0.8.13/0.9.10 + fast-uri 3.1.5),rebase 带入 #2240 的 override 后自动解除;本 PR 未为过门禁改任何依赖或例外登记mergeStateStatus: CLEAN

L3 仍是:没起 Tauri、没有真人点过菜单、没有真实 REST 往返证据(正文 §6 已声明,合并后不变)。

@DeliciousBuding
DeliciousBuding merged commit 42ba064 into master Sep 2, 2026
41 checks passed
@DeliciousBuding
DeliciousBuding deleted the fix/desktop-context-menu-dead-actions branch September 2, 2026 18:43
DeliciousBuding added a commit that referenced this pull request Sep 2, 2026
…tionUnavailable zh/en 键落地(未接入动作不再谎称「请重试」) + 消息动作 port 假注释订正 (#2241)

round-64 wave 批 2(tracker #2241),lane B。批 1(#2242)已合入 master `4a7af8c6`;本分支已 rebase 到该 master,无冲突(批 1 只碰 Go 与 `api/openapi.yaml`,本批只碰 `app/**`)。

原 commit 粒度(均带 (#2241)):
- fix(i18n): 补 toast.actionUnavailable zh/en 键,未接入动作的提示不再说"请重试"
- docs(workbench): 订正消息动作 port 的 doc 注释——#2238 之后它已是假话
- feat(desktop): 接上 Hub 转发 mutation,desktop 右键"转发"条目重新渲染并真派发

三件事的因果链:`workbenchTranscriptChromeActionMappers.ts:545` 早已导出 `UNAVAILABLE_ACTION_TOAST_KEY = 'toast.actionUnavailable'` 并在 7 个调用点使用,但 chatview bundle 里没有这个键 ⇒ i18next 回落判据 `resolved !== KEY` 不成立 ⇒ `announceUnavailableAction` 落到 `effect.failureMessage`,用户看到「…失败,请重试」这句对「这个端根本没接线」的错误指导(#2238 登记的尾巴 ①)。补键后,其中 desktop 的 forward 不再属于「未接线」——因为本批同时补上了 `useHubForwardMessage` port 并接线(#2238 当初是按 handler 存在性 fail-closed 关掉的,缺的正是这个 port,尾巴 ③)。`AgentHubWorkbenchTypes.ts:165-171` 的 doc 注释在 #2238 之后已是假话(尾巴 ②),一并订正。

**`regenerate` 明确不接线**:`regenerateAgentTask` 在 Desktop 的 `DesktopHubTaskBridge` / agent task 语义下未经验证,接了等于把未证语义推给用户 ⇒ 保持 fail-closed,`onRegenerate` / `onAddMessageReaction` 的 undefined 断言原样保留。

两处需要 reviewer 知情的取舍:
1. 删了一条既有断言 `expect(props.onForwardMessage).toBeUndefined()`——它的前提写在它自己的注释里("forward needs a `useHubForwardMessage` port"),本批交付物正是补上该 hook,接线后该断言与事实相反。只删这一条;同 test 内 `onRegenerate`/`onAddMessageReaction` 两条 fail-closed 断言原样保留,另在「chatActions 缺失」test 里新增 `expect(props.onForwardMessage).toBeUndefined()` 保住 #2154 不变量。测试数只增不减(3 → 4)。
2. `App.v4.test.tsx` 加了 1 行 `vi.mock` 夹具导出(白名单式 mock 不提供新 hook 会直接红)——**该文件任何断言一字未改**;抽掉这 1 行实测 12+ 用例全红,补回 14 passed。

证据与未验证项详见 PR #2249 描述(含红→绿双向证据、主机侧 rebase 后独立复跑 desktop 18 passed / workbench 5 passed、3 个 i18n 门禁数值、tsc×4 = 0 error、eslint 0 新增 problem 且 2 条 pre-existing 经 master blob 探针确认、以及 `real_tested=false` 的明确标注)。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant