Skip to content

refactor(web-ui): route business invokes through adapter layer and dr… - #2428

Open
zvzuola wants to merge 5 commits into
GCWing:mainfrom
zvzuola:chore/remove-dead-agent-service
Open

refactor(web-ui): route business invokes through adapter layer and dr…#2428
zvzuola wants to merge 5 commits into
GCWing:mainfrom
zvzuola:chore/remove-dead-agent-service

Conversation

@zvzuola

@zvzuola zvzuola commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Route Web UI business-layer Tauri commands through the shared ApiClient adapter layer and remove the unused legacy AgentService.

This keeps platform invocation behind the frontend adapter boundary and allows the existing transport routing, including peer-device scenarios, to remain centralized.

Type and Areas

Type: Refactor / architecture boundary / cleanup

Areas: Web UI, frontend API adapters, Peer Device Mode integration

Motivation / Impact

Several Web UI business modules imported invoke directly from @tauri-apps/api/core. This bypassed the shared ApiClient routing layer and made it possible for new business code to bypass transport-aware behavior.

This PR:

  • Adds an ESLint no-restricted-imports rule that blocks direct invoke imports from @tauri-apps/api/core in business code.
  • Allows direct platform invocation only inside src/infrastructure/api/adapters/** and the intentional PeerHostInvokeBridge exception.
  • Routes the following modules through api.invoke:
    • Insights API
    • I18n API
    • Agent companion pet service
    • Announcement service
    • File context validation
    • Image context validation
    • IDE control event bus
  • Preserves the existing command names and request payloads while changing only the invocation path.
  • Removes the unused src/web-ui/src/shared/services/agent-service.ts legacy wrapper.
  • Removes the obsolete AgentService field, initialization, and getAvailableAgents() method from FlowChatManager.
  • Removes the corresponding obsolete test mock.

There is no intended direct user-facing behavior change. The main impact is improved architectural enforcement and consistent routing through the adapter layer.

Verification

GitHub checks for head commit 433f601122f6fac24630e1c6d5e9df7af213c3b5:

  • Rust / CLI Validation — passed.
  • Rust / CLI Impact — passed.
  • Shell Deploy Scripts — passed.
  • DSH Profile Packaging (windows-latest) — passed.
  • Frontend Build — in progress at the time this description was prepared.
  • Rust Build Check — skipped because this PR does not modify Rust sources.
  • CLI Tests — skipped because this PR does not modify CLI behavior.

The changed frontend tests and the new ESLint boundary rule should be covered by the frontend validation workflow.

Reviewer Notes

  • This PR intentionally does not move the peer-device host bridge behind ApiClient. PeerHostInvokeBridge executes dynamic commands on the host side of Peer Device Mode and remains the documented exception.
  • The deleted AgentService had no remaining consumers. Active agent and tool operations are already exposed through the existing service APIs and FlowChatManager.
  • The API command names and payload shapes are unchanged.
  • This is a frontend-only change; no Rust, backend, locale, or user-facing copy changes are included.

Checklist

  • This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
  • Relevant verification is recorded above.
  • User-facing strings, docs, and locales are updated where applicable.

zvzuola and others added 2 commits August 22, 2026 17:24
…op dead AgentService

- Add eslint no-restricted-imports fence: business code must reach the
  platform only via api.invoke (ApiClient); direct invoke from
  '@tauri-apps/api/core' is reserved for adapters/** and the intentional
  PeerHostInvokeBridge exception.
- Reroute 8 A-class modules (insights, i18n, companion pet, announcement,
  file/image context, ide-control event bus) from direct invoke to api.invoke.
- Delete the dead legacy agent-service.ts wrapper (no consumers) and its
  FlowChatManager field/import/initialization, the orphaned getAvailableAgents()
  method, and its test mock.

Co-Authored-By: Claude <noreply@anthropic.com>
Remove 11 files and prune the api barrel of entries with no real consumers
anywhere in src (verified by full-tree grep + dynamic import + bitfunAPI.<prop>
access). Follow-up to 433f601 which dropped the legacy agent-service.ts.

Deleted (11 files):
- infrastructure/services/ dead chain (5): business/agentService.ts,
  infra/contextManager.ts, infra/index.ts, api/index.ts (broken barrel
  exporting non-existent contextService), index.ts (barrel with no importer)
- infrastructure/api/service-api/ProjectAPI.ts + GitRepoHistoryAPI.ts
  (only referenced inside the dead bitfunAPI collection)
- shared/crypto/ (e2e-encryption.ts + index.ts, no @/shared/crypto import)
- infrastructure/agents/constants.ts (BUILTIN_SUB_AGENT_IDS/isBuiltinSubAgent)
- shared/context-menu-system/examples/FileTreeIntegrationExample.tsx

Barrel edit (infrastructure/api/index.ts): drop the dead bitfunAPI collection
object, its default export, the GitRepoHistory type re-export, and the
projectAPI/gitRepoHistoryAPI imports; keep the 23 re-exports that have real
consumers. Sync the eslint examples/** ignore to the deleted example dir.

Conservatively retained:
- ContextAPI.ts: contextAPI loses its only consumer (ContextManager) but
  wraps backend session commands (compress_context/save_session_data/...) —
  cross-layer decision, pruned from bitfunAPI but file kept.
- Method-level dead code (~60 methods across RemoteConnectAPI/MiniAppAPI/
  SubagentAPI/AgentAPI/etc): TS wrapper dead != Rust handler dead; deferred
  to a follow-up batch that checks the backend command table per method.

Verified: tsc --noEmit introduces no new errors (only a pre-existing,
unrelated websocket-adapter GitTrustReport import error remains); eslint src
clean; vitest failures pre-exist on baseline (jsdom localStorage env issue).

Co-Authored-By: Claude <noreply@anthropic.com>

@limityan limityan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复审结论:Request Changes。本次按当前 head f5e5d1281117c1038343b3b92fc022b39b2eb99a 重新检查了全部 23 个变更文件,并沿 Desktop 本地、Desktop Peer、CLI Peer 三条生产路径核对。删除项未发现真实消费者或副作用回归;当前需要处理 3 个阻断问题、3 个非阻断问题和 1 个低优先级一致性问题。

阻断问题

1. [P1] 新路由没有声明命令 owner,CLI Peer 上 22 条命令仅 1 条可执行

问题

insightsApi.ts:179I18nAPI.ts:24AgentCompanionPetService.ts:175AnnouncementService.ts:21FileContextImpl.tsx:45ImageContextImpl.tsx:89IdeControlEventBus.ts:112 改用 api.invoke 后,命令会随当前 surface 发往 Peer;这些命令都没有进入 peer-device-adapter.ts 的 local-only 表。逐条对照 src/apps/cli/src/peer_host/commands/mod.rs:24-138,22 条新增路由命令中只有 check_path_exists(第 69 行)受 CLI Peer Host 支持。

风险

控制端切到 CLI Peer 后,Insights、i18n、Companion Pet、Announcement、FileContext 的 fs_exists 和 IDE 结果上报都会返回 command ... is not supported on CLI peer host。其中部分调用会吞掉错误,用户只会看到设置重启后丢失、公告状态不保存或文件上下文被判无效。

建议

逐条确定 owner:属于控制端应用壳/本机资源的命令加入前端、Desktop Host、CLI Host 三处一致的 local-only 契约;确实属于 Peer 产品面的命令补齐 CLI capability/实现与明确的 unsupported gate。请增加覆盖这批命令的 Desktop/CLI Peer 支持矩阵测试。

2. [P1] Insights 的命令、进度和报告文件分落两台设备

问题

src/web-ui/src/infrastructure/api/insightsApi.ts:179generate_insights 发往 Peer,但 listenProgress 仍在第 207 行监听控制端本机 Tauri 事件,openReport 仍在第 211 行用控制端 openPath。Peer 事件 fan-out 白名单 src/apps/desktop/src/api/remote_connect_api.rs:571-589 没有 insights-progress。此外该长任务被当作普通 mutation,受 peer-device-adapter.ts:323-327 的 30 秒超时约束;后端最终返回的 html_report_path 是 Peer 上的绝对路径(src/crates/assembly/core/src/agentic/insights/service.rs:1454-1459)。

风险

Desktop A 查看 Desktop B 时生成报告:A 收不到进度,约 30 秒后可能得到超时,但 B 仍继续执行;即使之后读到结果,“打开 HTML”也会拿 B 的路径在 A 上打开。

建议

把远端 Insights 做成可追踪的启动任务,并补齐状态/结果查询、进度 fan-out(或轮询)和报告文件传输;在闭环完成前对 Peer Mode 显式 gate,避免产生未知结果的长任务。

3. [P1] Companion Pet 跨设备传递裸路径,导入和预览都会失败

问题

src/web-ui/src/infrastructure/config/components/SessionConfig.tsx:407-414 在控制端 A 用本机 dialog 选择 zip,随后 AgentCompanionPetService.ts:185 把 A 的绝对路径发给 Peer B。B 返回的 spritesheetPath 又在 AgentCompanionPetService.ts:162-167 通过 A 的 plugin-fs 读取。

风险

B 无法读取 A 选中的 zip;列表或导入返回的用户 Pet 也无法在 A 读取 B 的 spritesheet。Desktop Peer 的导入、预览链路因此直接不可用,CLI Peer 还会在命令分发阶段失败。

建议

使用显式文件上传/下载或字节流协议,并让预览资源经过同一 owner;若本次不实现跨设备文件传输,应在 Peer Mode 清晰禁用这些操作。

非阻断问题

4. [P2] 控制端应用状态会在 surface 切换后写到 Peer

问题

I18nAPI.ts:34 的语言持久化随活动 surface 路由:UI 在 A 立即切换语言,配置却写到 B;CLI B 则报 unsupported,且 I18nService.ts:280-285 会吞掉保存失败。Announcement 首次队列通常由 A 在启动时加载,但用户稍后切到 B 后,announcementStore.ts:80,89,99-101 的 seen/dismiss/never-show 会通过当前 adapter 写到 B。

风险

A 的语言重启后回退,同时可能意外改变 B 的语言;公告展示队列和持久状态属于不同设备,用户已经处理的卡片会再次出现,或修改了另一台设备的公告状态。

建议

把 UI locale 和应用壳 announcement 状态明确归为 controller-local;若产品希望它们跨设备同步,需要独立的同步契约,不能依赖当前 surface 隐式选 owner。

5. [P2] Announcement 的副作用型 get_* 被当作可重试读取

问题

AnnouncementService.ts:21,71 调用的 get_pending_announcements / get_announcement_tips 都会执行 scheduler;scheduler.rs:53-72 每次 run() 都增加并持久化 app_open_count。Peer 策略在 peer-device-adapter.ts:229-233get_ 前缀将它们归为可重试读,网络失败时可额外重试 4 次。

风险

一次用户操作可能多次推进 open-count 和版本调度,导致 AppNthOpen 等卡片提前、错过或重复展示。

建议

将 scheduler mutation 与纯读取拆开,或把这两个命令显式排除出 read retry;同时补充超时后重放的状态测试。

6. [P2] 新增 ESLint fence 没有覆盖它声明的边界

问题

src/web-ui/eslint.config.mjs:43 使用的 no-restricted-imports 只拦静态 import,现有生产代码仍可通过 import('@tauri-apps/api/core') 获取 invoke,例如 PanelController.ts:241。本 PR 修改的 FileContextImpl.tsx / ImageContextImpl.tsx 又落在顶层全局 ignore 的 src/shared/context-system/core/types/**(第 21 行),对它们连该 fence 都不会执行。

风险

PR 注释所述“重新引入 direct invoke 会让 build 失败”并不成立,后续代码可以无意绕过统一 adapter,重复本次跨 surface 问题。

建议

增加覆盖 ImportExpression 的规则,并把 Context 目录从全局 ignore 调整为只跳过无关规则、仍执行 transport fence;仅保留有 owner 说明的精确例外。

低优先级一致性问题

7. [P3] IDE control 的监听与成功/失败回报使用不同 owner

问题

IdeControlEventBus.ts:48-49 仍监听本机 ide-control-event,错误分支在第 112 行走当前 api.invoke,成功分支 PanelController.ts:241-242 仍动态导入并直调本机 invoke

风险

同一个 request 的成功和失败结果可能发到不同 host;当前这条链路也会继续绕过新增 lint fence。

建议

先明确 IDE control event 的 owner,再让监听和两条结果回报共用同一 transport,并补一条 Peer surface 切换测试。

验证

  • git diff --check:通过。
  • pnpm --dir src/web-ui exec eslint src:通过;单独验证动态 import 文件仍为 0 错误,Context 文件被全局 ignore。
  • peer-device-adapterinsightsStoreFlowChatManager:49/49 通过。
  • exact-head GitHub checks:Frontend Build 等均通过或按影响矩阵跳过;现有检查未覆盖上述跨设备 owner、长任务和裸路径语义。
  • 对删除的 API、service、crypto、constants、example 做了全树静态/动态消费者复核,未发现删除导致的新问题。

zvzuola and others added 3 commits August 24, 2026 19:53
…gaps

Address PR GCWing#2428 review (CHANGES_REQUESTED):

- Declare LOCAL_ONLY owners for i18n/announcement/companion-pet/insights/
  IDE-control/browser/webview/devtools/desktop-pet commands routed to peer
  without an owner; cross-device routing regressed controller app-shell state.
- Add SIDE_EFFECTING_GET_COMMANDS so get_pending/get_announcement_tips
  (scheduler-mutating reads) are never auto-retried by the peer read path.
- Add no-restricted-syntax ImportExpression selector to the ESLint fence so
  dynamic import('@tauri-apps/api/core') bypasses fail the build too.
- Migrate all ~30 pre-existing dynamic-import sites to api.invoke (15 files);
  each command's peer-vs-local owner is declared to preserve behavior.
- FileContextImpl: fs_exists -> check_path_exists (peer-routed, CLI-peer
  supported) so file-tree path checks resolve on the rendered surface.
- PanelController: route report_ide_control_result success branch through
  api.invoke so both branches use the same LOCAL_ONLY transport.

Verified: eslint src -> 0 errors; peer-device-adapter.test.ts 39/39 passed.

Co-Authored-By: Claude <noreply@anthropic.com>
The core-boundaries check enforces a one-way ownership boundary: every
command the FE adapter marks LOCAL_ONLY must also be refused by each peer
host, because an older or non-Web-UI controller can still HostInvoke them.
The previous commit added i18n/announcement/companion-pet/insights/IDE-
control/browser/webview/devtools/desktop-pet/diagnostics commands to the
FE deny list but not to the desktop and CLI peer-host deny lists, so CI's
"Check core boundaries" step failed.

Add the 37 controller-owned commands to both
src/apps/desktop/src/api/peer_host_invoke.rs and
src/apps/cli/src/peer_host/deny.rs, grouped with owner comments mirroring
the FE adapter. Being unimplemented on the CLI peer is not the boundary —
they are refused explicitly.

Verified: check-core-boundaries -> passed; check-core-boundaries.test ->
126/126; cargo test -p bitfun-desktop peer_host -> 6/6; cargo test --bin
bitfun peer_host -> 79/79.

Co-Authored-By: Claude <noreply@anthropic.com>
The dynamic-import migration moved useDebugInspector and
agentCompanionPetCommands off `@tauri-apps/api/core` and onto
`api.invoke`, but their tests still mocked `@tauri-apps/api/core`, so the
mock never intercepted the call. CI "Run web UI tests" failed:

- useDebugInspector.test.tsx: expected mocks.invoke to be called with
  'debug_devtools_available' but it was called 0 times.
- agentCompanionPetCommands.test.ts: api.invoke('show_main_window') hit the
  real ApiClient (WebSocket connection failed).

Repoint both tests at `@/infrastructure/api/service-api/ApiClient` (the
established pattern) and flush the post-invoke microtask in
useDebugInspector before dispatching keys, since the keydown listener now
registers right after the (synchronous) api.invoke resolves rather than
after the old dynamic-import microtask.

Verified: useDebugInspector 4/4, agentCompanionPetCommands 7/7.

Co-Authored-By: Claude <noreply@anthropic.com>
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.

2 participants