Skip to content

fix(frontend): round-64 批2——desktop 右键「转发」接上真 Hub mutation + toast.actionUnavailable zh/en 键落地(不再谎称「请重试」) + 消息动作 port 假注释订正 (#2241) - #2249

Merged
DeliciousBuding merged 3 commits into
masterfrom
fix/desktop-menu-i18n-forward
Sep 2, 2026

Conversation

@DeliciousBuding

Copy link
Copy Markdown
Collaborator

这是什么

round-64 wave 批 2(tracker #2241),lane B。批 1(edge 审批门属主化 + hub health 兜底诚实化)已在 #2242 合入 master 4a7af8c6;本分支已 rebase 到该 master,rebase 无冲突(批 1 只碰 Go 与 api/openapi.yaml,本 PR 只碰 app/**)。

3 个 commit,均带 (#2241)

  1. fix(i18n) — 补 toast.actionUnavailable 的 zh/en 键。workbenchTranscriptChromeActionMappers.ts:545 早已导出 UNAVAILABLE_ACTION_TOAST_KEY = 'toast.actionUnavailable' 并在 7 个调用点(:625/:660/:693/:709/:725/:741/:757)使用,但 chatview bundle 里没有这个键 ⇒ i18next 回落判据 resolved !== KEY 不成立,announceUnavailableAction 落到 effect.failureMessage,用户看到的是「…失败,请重试」——一句对「这个端根本没接线」的错误指导
  2. docs(workbench) — 订正 AgentHubWorkbenchTypes.ts:165-171 的假注释。原文称 "Desktop/demo shells omit them and pin/unpin/recall/react stay hidden ([P1] 假内容与假交互清理:任务页/上下文菜单/设置项/demo 身份 #1818)",与 workbenchTranscriptChromeHelpers.ts:515-531 的逐 action capabilities、以及 Desktop 确实设 activeConversationId 并接线 pin/unpin/recall([整理] 静态审计 backlog 跟踪(探索 lane 剩余项) #2154)的事实矛盾。
  3. feat(desktop) — 接上 Hub 转发 mutation:新增 desktop/src/api/sessionQueries.tsuseHubForwardMessage,在 App.tsx 接线 onForwardMessage,desktop 右键「转发」条目重新渲染并真派发(此前 fix(frontend): desktop 消息右键菜单死条目收口——pin/unpin/recall 接上已有 mutation、forward/regenerate 按 handler fail-closed、派发器 7 处静默 break 改为一次可感知反馈 (#2154) #2238 按 fail-closed 把它关掉了,因为缺 port;现在 port 补上了)。

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

证据(本机实测)

红→绿双向:

  • 任务 1 红(临时抽掉 zh/en 两行键):workbench unavailableActionToastI18n.test.ts expected 'toast.actionUnavailable' to be undefined(i18next 把键 echo 回来)3 failed;shared resources.test.ts expected undefined to be '该操作在当前端未接入' 3 failed | 12 passed。绿:shared 15 passed(基线 12 + 新 3)、workbench 97 passed(基线 92 + 新 5)。
  • 任务 3 红(把 App.tsxonForwardMessage 整块注掉):onForwardMessage must reach the workbench deps: expected undefined to be type of 'function',1 failed | 3 passed。绿:desktop messageActions + App.v4 共 18 passed。
  • 写集外夹具红证据:抽掉 App.v4.test.tsx 那 1 行 vi.mock 导出 ⇒ 12+ 用例全红;补回 ⇒ 14 passed(该文件任何断言一字未改)。

主机侧独立复跑(rebase 到 4a7af8c6 之后):

pnpm --filter agenthub-desktop exec vitest run src/__tests__/App.messageActions.test.tsx src/__tests__/App.v4.test.tsx
  → Test Files 2 passed (2) / Tests 18 passed (18)   35.16s
pnpm --filter @agenthub/workbench exec vitest run src/__tests__/forwardMenuPort.test.tsx src/__tests__/unavailableActionToastI18n.test.ts
  → Test Files 2 passed (2) / Tests 5 passed (5)     11.34s
git diff origin/master --stat -- .github scripts/verify Makefile \
    app/workbench/src/workbenchTranscriptChromeActionMappers.ts app/workbench/vitest.config.ts
  → 空(判卷面 + 7 个调用点所在文件一行未动)
resources.ts:317 'toast.actionUnavailable': '该操作在当前端未接入'
resources.ts:1216 'toast.actionUnavailable': 'This action is not wired in this client'   ← zh/en 键名逐字一致
App.tsx:741 messageId.replace(/^hub-message-/, '')  ← 与 pin/unpin/recall 的 :707/:717/:727 逐字同形态,且同样 gate 在 chatActions 上

lane B 自跑门禁(HEAD 7c3a92c5,rebase 前):

verify-i18n-deadkeys.py      → i18n dead-key gate ok (bundles: 2, dynamic prefixes: 6)
verify-i18n-terminology.py   → i18n terminology gate ok (zh definition surfaces scanned: 5)
verify-i18n-callsites.py     → baseline 78 files/608 CJK lines; current 74/597; PASS: current <= baseline
tsc --noEmit                 → workbench 0 / desktop tsconfig.app.json 0 / desktop tsconfig.json 0 / shared 0
eslint(改动的 7 个文件)    → 0 新增 problem(App.tsx:266 `tIm` useMemo warning 与 Types.ts:81/:85 4×no-invalid-void-type
                               均经 master blob 探针确认为 pre-existing,未顺手修)

两处需要 reviewer 知情的取舍

  1. 删了一条既有断言App.messageActions.test.tsxexpect(props.onForwardMessage).toBeUndefined()。它的前提写在它自己的注释里——"forward needs a useHubForwardMessage port",即「desktop 没有这个 mutation 所以只能是 undefined」;本 PR 的交付物正是补上该 hook 并接线,接线后该断言与事实相反。处理:只删这一条,同 test 内 onRegenerate/onAddMessageReaction 两条 fail-closed 断言原样保留;另在「chatActions 缺失」那个 test 里新增 expect(props.onForwardMessage).toBeUndefined(),保住 [整理] 静态审计 backlog 跟踪(探索 lane 剩余项) #2154 的 fail-closed 不变量(demo / Hub 未就绪时条目仍不渲染)。测试数只增不减(3 → 4)。
  2. commit 02a1dee0(原 5cd150e1)的注释里已写 "plus forward ([整理] round-64 wave:edge 审批门零属主(安全 P1) + desktop 菜单尾巴(前端/i18n) + health/version 诚实化(可观测性) #2241)",而 forward 由紧随其后的 commit 才落地 ⇒ 该中间 SHA 上注释超前一句;分支 tip 与 squash 后的结果完全为真。squash merge 会消掉这个中间态。

未验证(如实)

  • coverage 未跑、全量 vitest 未跑、pnpm -r build 未跑、web/mobile 包测试未跑(4 核机 + 本轮纪律)⇒ web src/App.test.tsx 的 10 passed 基线未本地复跑。本 PR 不含 web 文件,但 resources.ts 是 web 也吃的 bundle ⇒ 由 CI 的 frontend-web / frontend-required 判。
  • packaged Desktop(Tauri)真点击路径未验证:本机证据等级=vitest + jsdom 真实渲染(真实 AgentHubWorkbench / 真实 i18next bundle / 真实 hubClient.forwardMessage 方法调用,仅在 client 边界打桩),不是 packaged 端到端;按 AGENTS.md §10 标注 real_tested=false(无真 Hub 往返)。
  • forward 成功后目标会话是否即时刷新未在真 Hub 上验证(失效 ['hub','sessions'],与 pin/unpin/recall 及 web 侧同契约)。
  • 环境偏差(不进 git):本 worktree 原本没有 app/node_modules,按 fix(frontend): desktop 消息右键菜单死条目收口——pin/unpin/recall 接上已有 mutation、forward/regenerate 按 handler fail-closed、派发器 7 处静默 break 改为一次可感知反馈 (#2154) #2238 同款做法软链到主 checkout、包级 @agenthub/* 重建为指向本 worktree(readlink -f 已验证跑的是本分支代码),未跑 pnpm install

DeliciousBuding and others added 3 commits September 3, 2026 03:54
#2238 登记的写集外尾巴之一:announceUnavailableAction 已经按 UNAVAILABLE_ACTION_TOAST_KEY='toast.actionUnavailable' 查找,但键不在 chatview bundle 里,i18next 把键名 echo 回来 ⇒ 派发器回落到 effect 自带的 failureMessage(如"置顶失败,请重试")。不静默、不假成功,但"请重试"是假的:该端根本没接这个 port,重试永远不会成功。

本次只加键、不改派发器(mappers 禁改,键一落地自动切换):
- zh '该操作在当前端未接入' / en 'This action is not wired in this client'(#2238 正文建议值),两边都插在 toast.artifactDownloadFailed 之后=对应相对位置;TransKey 取自 en 块,故 zh/en 同时加且键名逐字一致。
- resources.test.ts 追加 3 个 it:钉死 zh/en 具体文案、不许出现"重试/retry"、键确在 dispatcher 所用的 chatview namespace 下。
- 新增 workbench/src/__tests__/unavailableActionToastI18n.test.ts:用真实 i18next 实例(getFixedT('zh'|'en','chatview'),非 stub)驱动真实 applyTranscriptChromeSideEffects,对 7 个未接线 effect 逐个断言恰好一次 toast == 新键文案,且不是裸键/成功文案/原 failureMessage,并且 softHide/pulse/dispatchComposer 均未发生。

红→绿实测:抽掉键后 workbench 该文件 3 failed、shared resources.test.ts 3 failed|12 passed;键落地后 workbench 6 files/97 passed(基线 92 + 新 5)、shared 15 passed(基线 12 + 新 3)。门禁:deadkeys ok / terminology ok / callsites PASS(current 74 files 597 行 ≤ baseline 78/608)。

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
原文断言 'Optional — Desktop/demo shells omit them and pin/unpin/recall/react stay hidden (#1818)',在 #2238 之后与代码相反:
- 判据早已不是"有没有 sessionId",而是 workbenchTranscriptChromeHelpers.contextMenuGroups 的逐 action capabilities(pin/unpin/recall = Boolean(sessionId) && handler 存在;forward = handler && conversations;regenerate = handler);
- Desktop 确实设 sessionId(AgentHubWorkbenchHelpers.ts:156-157,activeConversationId doubles as the session id),也确实接了 pin/unpin/recall(#2154)与 forward(本 lane 下一个 commit,#2241);真正"没有 port"的只有 demo shell 与 Hub 未就绪的 shell;
- react 条目当前根本不渲染(#1822 把只写的表情子菜单摘了),所以没有 capability 依赖 onAddMessageReaction。

只改注释:类型定义、字段、可选性一字未动。验收:pnpm --filter @agenthub/workbench exec tsc --noEmit 0 error;eslint 该文件仅 4 条 pre-existing no-invalid-void-type(:81/:85,用 42ba064 原始 blob 就地探针复现同样 4 条,与本次改动无关,未顺手修)。

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
#2238 登记的写集外尾巴之三:shared hubClient.forwardMessage(messageId, targetSessionIds) 一直在,desktop 只缺 api 层包装,所以 #2154 把菜单改成按 handler fail-closed 之后,desktop 的"转发"条目直接消失了。

- desktop/src/api/sessionQueries.ts 新增 useHubForwardMessage(形态照抄同文件 pin/unpin/recall 包装:mutateAsync({messageId, targetSessionIds}) → 真实 hubClient.forwardMessage,onSuccess 失效 ['hub','sessions'];web 侧 useWebWorkbenchModel.forwardMessageMut 同一 REST 契约,不分叉)。
- desktop/src/App.tsx 接线 onForwardMessage,剥前缀与取 sessionId 的方式与 pin/unpin/recall 完全一致(messageId.replace(/^hub-message-/, '')),并同样以 chatActions 存在为门(demo / Hub 未就绪时保持 undefined ⇒ 条目继续隐藏,不丢 #2154 不变量)。未改门禁本身:条目因 handler 存在而自动回来。
- 顺手订正 App.tsx 里 #2154 那段注释(原文断言 forward 保持 undefined,接上后同样会变成假话)。
- regenerate 明确不接(死规矩):regenerateAgentTask 在 desktop 的 DesktopHubTaskBridge/agent task 语义下未验证,接了等于把未证语义推给用户;onRegenerate/onAddMessageReaction 的 undefined 断言原样保留。

测试(分层,均未 mock 被测物):
- 新增 workbench/src/__tests__/forwardMenuPort.test.tsx:真实 AgentHubWorkbench + desktop 形态 fixture,port 在 ⇒ "转发"条目渲染、点开选择器选 sess-2 确认 ⇒ spy 收到 ('hub-message-m1', ['sess-2']);port 缺 ⇒ 条目与选择器都不渲染。
- desktop App.messageActions.test.tsx 新增正向用例:真实 useHubForwardMessage → 真实 hubClient.forwardMessage('m1', ['sess-2','sess-3'])(只在 client 边界打桩),并在 chatActions 缺失用例里补 onForwardMessage undefined。既有断言 expect(props.onForwardMessage).toBeUndefined() 按规矩登记在 /tmp/lane-b-BLOCKED.md §1(该断言的前提"desktop 没有 forward hook"已被本 commit 证伪)后移除,其余断言未动,测试数 3 → 4。
- App.v4.test.tsx 只加 1 行 sessionQueries mock 夹具(该 mock 是显式导出清单,不补则 App 渲染即 not a function);实测抽掉该行 App.v4 全红、补回 14 passed。断言一字未改。

红→绿实测:注掉 App.tsx 的 onForwardMessage 后 desktop App.messageActions 1 failed|3 passed(onForwardMessage must reach the workbench deps: expected undefined to be type of 'function');还原后 4 passed。workbench 6 files/97 passed。desktop tsc -p tsconfig.app.json 与 -p tsconfig.json 均 0 error。

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
@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: c6138a0c-0c3e-493a-aa27-d0e1584ff01f

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
DeliciousBuding merged commit e77808b into master Sep 2, 2026
41 checks passed
@DeliciousBuding
DeliciousBuding deleted the fix/desktop-menu-i18n-forward branch September 2, 2026 20:05
DeliciousBuding added a commit that referenced this pull request Sep 2, 2026
…ticcheck 拆到与 shard 并行的 go-*-static job(聚合 job 2m52s→23s、1m40s→19s,真实 run 已证),并补 TEST_RESULT/STATIC_RESULT fail-closed 断言修掉「测试 shard 红了 required check 仍可能全绿」的既有漏洞;7 个 required check 名与全部门禁一字未改 (#2251)

#2251 切片 1(PR #2257,round-65 lane G + 主机两次追加裁决)。

## 改了什么

`go-hub` / `go-edge` 里**不消费 shard 覆盖率产物**的静态门禁搬到与测试**并行**的新 job `go-hub-static` / `go-edge-static`(`needs: changes` only,与 `go-*-test` 同时起跑);两个 required check **保留原名**,只留「下载 shard 覆盖率 → 合并 → 覆盖率门」+ 既有两个兜底 step,并新增对 test/static 两条上游 lane 的 fail-closed 结果断言。

- `go-hub-static`:checkout、setup-go、Lint(硬门 #1812)、Hub lint fingerprint ratchet(#1573)+ 自测、sa-cache、gosec、`go vet`、staticcheck。
- `go-edge-static`:同上(Lint 仍 `continue-on-error: true` + `only-new-issues`,#1840 软硬性质未变,由 `verify-ci-gates.py` 与 `quality-debt-baseline.json` 双向钉住)+ orchestrator 依赖方向门(#1566)+ 自测。
- `backend-required` 的 `needs` **未改**(static 失败经 `go-edge`/`go-hub` 传导,注释已写明)。其余 32 job 解析级逐一相等;`concurrency` 块与 `changes` 的 filter 定义一字未动。

## 顺带修掉一个既有 CI 诚实性漏洞

改前 `go-hub`/`go-edge` 的 job 级 `if: ${{ !cancelled() }}` 让它在 **shard 测试失败时照样运行**,而 job 内从不检查 `needs.go-*-test.result`;`go test -coverprofile` 失败时仍落盘 profile ⇒ Download + Merge + Coverage 三步全过 ⇒ **required check 可能报绿**,`backend-required` 只断言 `go-hub`/`go-edge` ⇒ 跟着绿。即「单元测试 shard 红了,7 个 required check 仍可能全绿」。改后由 `TEST_RESULT` / `STATIC_RESULT` 显式 fail-closed(`!= success` 即 `exit 1`,**skipped 不算通过**),对齐 `windows-go` / `backend-required` 既有约定。诚实边界:近 120 次 run 无 `go-*-test` 失败样本可实证,属结构性推论。

## 真实 run 证据(本 PR 自己的 run `33685438788`,全矩阵)

新 job 图按设计工作,聚合 job 的串行尾巴已消失:

| job | 起→止 | 时长 | 改前同族时长 |
|---|---|---|---|
| `go-hub-static`(与 shard **并行**) | 21:31:25 → 21:34:12 | **2m47s** | (原本串行挂在测试之后) |
| `go-hub-test (2)` | 21:32:41 → 21:37:28 | 4m47s | 4m09s(#2255 run) |
| `go-hub`(聚合,只剩覆盖率 3 步 + 2 条断言) | 21:39:24 → 21:39:47 | **23s** | **2m52s**(#2255 run)/ 2m45s(#2249 run) |
| `go-edge-static` | 21:31:25 → 21:33:01 | 1m36s | — |
| `go-edge`(聚合) | 21:34:18 → 21:34:37 | **19s** | **1m40s** |
| `backend-required` | 21:39:51 → 21:39:56 | 5s | 4s |

⇒ 静态门禁(2m47s)已**不在关键路径上**(被 4m47s 的 shard 覆盖),聚合 job 从 2m52s 降到 23s,与 lane G 用 step 级计时推演的 ~20s 一致。

⚠️ **本 run 的总墙钟不能当稳态收益读数**:改 `checks.yml` 命中 `changes` 的全部 filter ⇒ 跑全矩阵;且实测 `go-hub-test (1)` 直到 21:36:27 才起跑(比同批晚 ~5min),是新增 2 job 后的 runner 排队(lane G 已预告峰值并发 ~18 可能触档)。**稳态收益必须由合并后第一个 Go-only PR 的真实 run 回填 #2251 验收表**(改前基线:Go-only 8m36s,推演改后 ≈6m03s)。

## 写集(7 文件)

```
.github/workflows/checks.yml                     | 317 ++++++++-----   ← 主体
docs/architecture/github-actions-ci-cd-policy.md |  10 +-              ← job 图 SSOT
scripts/verify/verify-ci-gates.py                |  65 +++--           ← 被迫(按 job 名定位 step)
scripts/verify/tests/verify-ci-gates.Tests.py    | 113 ++++--          ← 被迫
scripts/verify/quality-debt-baseline.json        |   4 +-              ← 被迫(soft gate 以 "go-edge: Lint" 为键)
docs/governance/verifier-map.md                  |  10 +-              ← 主机追加(4 行 job ID 过期)
AGENTS.md                                        |   2 +-              ← 主机追加(L0 分层行;仍 284 行 / 预算 300)
```
被迫扩展的 3 个文件属**改宿主、不减断言**:`verify-ci-gates.py` **新增 6 条拆分契约断言**(含「`go-*-static` 必须不含 `download-artifact`」防止把覆盖率门也搬走、「聚合 job 必须绑定并断言两条 lane 结果」),自测 35 → 39 用例;`quality-debt-baseline.json` 只把 soft gate 的 `location` 从 `go-edge: Lint` 改为 `go-edge-static: Lint`,`issue`/`owner`/`introduced_at`/`review_by` 语义一字未改。不改这 3 个则 required check `validate` 必红、PR 不可合(lane 报告 §2.7 有实测红输出)。

`verifier-map.md` 的 `:81`(`merge-coverprofiles.py → go-edge / go-hub`)**仍正确、一字未改**——合并步骤本来就留在聚合 job。

## 门禁(主机在 rebase 到 `c226baab` 之后的树上实跑)

```
actionlint -no-color .github/workflows/checks.yml → EXIT=0;findings 多重集与 pristine master 完全一致(11 条:6×SC2034 / 2×SC2069 / 3×SC2086)⇒ 零新增
python3 yaml.safe_load → 36 jobs;7 个 required check 名 missing=[];go-* needs 图核对
verify-ci-gates.py → ci gate policy ok        verify-ci-gates.Tests.py → Ran 39 tests OK
verify-doc-ssot.py → doc SSOT ok(AGENTS 96 paths)
verify-commit-messages.sh origin/master → ok (3 commits)     git diff --check → clean
```

## 未验证 / 未做

1. **稳态墙钟收益未经 Go-only run 实测**(本 PR 是全矩阵);#2251 验收线「Go-only ≤6m00s」待下一个 Go-only PR 回填。推演落点 ≈6m03s,**卡在验收线之上 ~3s** ⇒ 要稳过线需做**切片 2**(shard 重平衡:`go-hub-test` 291s vs 163s,`checks.yml` 注释自称 round-robin「balanced and disjoint」已被实测推翻——disjoint 成立、balanced 不成立)。本 PR 刻意不做切片 2(需可复核的成本表,不接受拍脑袋分片)。
2. 切片 3(frontend `@agenthub/workbench` coverage 7m31s 长杆)未做,#2251 要求先出耗时构成实测。
3. org 的 Actions 并发档未查询(Free=20 是文档口径推断);本 run 观察到的 ~5min shard 起跑延迟与此一致。
4. actionlint 的 11 条 baseline 告警未修:`go test $SHARD_PKGS` 的 SC2086 **不能**加引号(靠 word splitting 展开包列表,加引号=改语义=可能破坏 shard 门),其余 8 条在 `frontend-mobile`/`real-e2e-stack`/`vuln-scan-js`,与本切片无关。
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