Skip to content

Feat/acp v1#4741

Merged
lulusir merged 91 commits into
mainfrom
feat/acp-v1
May 19, 2026
Merged

Feat/acp v1#4741
lulusir merged 91 commits into
mainfrom
feat/acp-v1

Conversation

@lulusir
Copy link
Copy Markdown
Contributor

@lulusir lulusir commented May 19, 2026

Types

  • 🎉 New Features
  • 🐛 Bug Fixes
  • 📚 Documentation Changes
  • 💄 Code Style Changes
  • 💄 Style Changes
  • 🪚 Refactors
  • 🚀 Performance Improvements
  • 🏗️ Build System
  • ⏱ Tests
  • 🧹 Chores
  • Other Changes

Background or solution

Changelog

Summary by CodeRabbit

  • 新增功能

    • 权限对话框:在 AI 执行敏感操作前弹窗确认并支持超时/取消
    • 完善 ACP/Agent 模式:会话管理、Agent 会话加载、模式切换与回退本地
    • 丰富聊天输入:@ 提及、图片上传预览、斜杠命令与主题/代理切换
    • 聊天视图增强:历史列表、侧栏头部、移动端布局改进、MCP 工具入口
  • 测试

    • 大量单元/集成测试覆盖权限、会话、文件系统、终端与 Agent 流程

Review Change Stack

lulusir and others added 30 commits April 27, 2026 13:45
- Restore original components to pre-ACP state (ChatHistory, ChatMentionInput, mention-input)
- Create ACP-specific components in acp/components/ directory
  - AcpChatMentionInput: recursive workspace file loading (limit 50)
  - AcpChatHistory: no delete button (server-managed sessions)
- Register ACP components via IChatAgentViewService contribution point
- Dynamic component selection in chat.view.tsx based on supportsAgentMode flag
- Add design document: docs/plans/2026-04-07-acp-components-refactor.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
lulusir and others added 23 commits May 15, 2026 16:19
Add AcpMCPFooterButton, AcpRulesFooterButton, and AcpSlashCommandFooter
components with chat-input-footer registry for extensible footer items.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ibution

Move slash command footer item registration from AcpChatMentionInput's
useEffect into AcpFooterContribution (ClientAppContribution pattern),
and remove MCP/Rules footer items that are no longer needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Replace prepending slash text with cursor-position insertion
- Render slash commands as styled tags (non-editable span) instead of plain text
- Reuse built-in "/" mention panel instead of separate footer dropdown
- Add opensumi-chat-input-open-slash-panel event for external triggers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ttribute selector

- Remove space in nameWithSlash getter: "/ Explain" -> "/Explain"
- Fix slash tag serialization by using span[data-command] selector instead
  of CSS class selector which failed due to CSS modules composes behavior
- Remove debug console.log

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…g logs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Re-add 8 test files covering chat agent service, chat model,
diff computer, multi-line decoration, tree-sitter, and MCP servers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…dropdown

When a slash command is selected from the MentionInput dropdown, only a
visual DOM tag was inserted but the parent's command state was never
updated. This caused AcpChatAgent to receive an empty command string,
skipping the custom invoke handler.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Removed 10 verbose console.log/logger.log debug statements and 3 noisy
dispose lifecycle logs across the ACP module. Error and warn level logs
are preserved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… mention input

- Remove dead code (unreachable return) and add null-safe fallbacks in
  acp-session-provider.ts
- Remove duplicate `localize` import in ChatMentionInput.acp.tsx

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ing space

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove the slashCommand useEffect in MentionInput that duplicated the
  slash tag alongside the custom event insertion path
- Guard CodeBlockWrapperInput to skip rendering the command prop tag
  when it was already extracted from the message text

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ervice

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add @opensumi/di mock to prevent DI container errors in unit tests
- Make AcpPermissionHandler.initStorage() lazy (ensureInitialized pattern)
- Fix auditLog assertion to match actual timestamp format
- Fix getSmartTitle assertion for undefined kind

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The else branch in getItems was using a shadowed const folders (string[])
instead of the outer MentionItem[] variable, causing a type error when
passed to expandFolderPaths which expects string[]. Renamed the intermediate
variable to folderPaths and assigned the expanded result to folders.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ChatReply: when condition was inverted — button was always rendered
and then overwritten. Now properly checks !when or when matches before
rendering. Also adds missing AgentProcessConfig import in test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add 242 tests across 8 test suites covering:
- acp-cli-client.service.ts (NDJSON transport, JSON-RPC)
- acp-cli-process-manager.ts (process lifecycle)
- acp-permission-caller.service.ts (permission routing, option sorting)
- acp-agent.service.ts (session management, notifications)
- agent-request.handler.ts (request routing delegation)
- file-system.handler.ts (file ops, workspace sandboxing)
- terminal.handler.ts (PTY terminal lifecycle)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The async branch of getChatComponentDeferred omitted messageId, causing
inconsistent props compared to the synchronous path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove unused imports (AcpAgentServiceToken, SimpleMessage)
- Remove unused mockStream variable
- Fix property shorthand lint error
- Add tests for history, images, error handling, and content conversion

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fd300e2b-1940-4e00-9318-d85e27fa5d6a

📥 Commits

Reviewing files that changed from the base of the PR and between be0610c and 02fb459.

📒 Files selected for processing (1)
  • .github/workflows/check.yml

Walkthrough

在浏览器与 Node 侧新增并接入 ACP Agent 模式:实现 NDJSON RPC 客户端、进程管理、会话与消息流;权限桥接与弹窗;文件系统/终端处理器;聊天视图/输入/注册表与默认/ACP 代理;偏好与配置、样式及大量测试。

Changes

ACP Agent 模式与权限对话集成

Layer / File(s) Summary
端到端:协议、服务、处理器、UI 与注册表集成
packages/ai-native/src/node/**, packages/ai-native/src/browser/**, packages/ai-native/__test__/**, packages/ai-native/__tests__/**, packages/ai-native/package.json, package.json
实现 ACP 协议客户端/服务/进程管理与文件/终端处理器;权限 RPC 桥接与弹窗容器/组件;聊天视图、输入与注册表、会话提供与代理选择;条件 DI 接线与偏好/样式;并新增全面单元测试。

Sequence Diagram(s)

sequenceDiagram
  participant UI as Browser UI (Chat / PermissionDialog)
  participant Bridge as AcpPermissionBridgeService
  participant RPC as AcpPermissionRpcService
  participant Client as AcpCliClientService
  participant NodeHandlers as Agent Handlers (FS/Terminal)
  UI->>Bridge: onDidRequestPermission(params)
  Bridge->>UI: render dialog
  UI->>Bridge: user decision
  Bridge->>RPC: $showPermissionDialog(params)
  RPC->>Client: JSON-RPC -> agent process
  Client->>NodeHandlers: handleRead/Write/CreateTerminal...
  NodeHandlers-->>Client: result/error
  Client-->>RPC: notification / session updates
  RPC-->>UI: stream/notification for chat/permission
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

  • opensumi/core#4395: 与 ChatAgentPromptProvider 的注入与上下文提示提供器切换相关联。
  • opensumi/core#4581: 在 ChatManager/ChatModel 相关改动上存在重叠,可能影响会话序列化/反序列化逻辑。

Suggested labels

🎨 feature

Suggested reviewers

  • erha19
  • ensorrow
  • Aaaaash
  • hacke2
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/acp-v1

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ai-native/src/browser/layout/ai-layout.tsx (1)

25-42: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

React Hooks 规则违反:条件返回后调用 useMemo

在第 25-36 行的条件返回之后,第 39 行调用了 useMemo,这违反了 React Hooks 的规则。Hooks 必须在组件顶层调用,不能在条件返回之后。这会导致在移动端和桌面端之间切换时,hooks 调用顺序不一致,可能引发运行时错误。

🐛 建议的修复方案
 export const AILayout = () => {
   const { layout } = getStorageValue();
   const designLayoutConfig = useInjectable(DesignLayoutConfig);

-  // 判断是否应该显示完整布局
-  const shouldShowFullLayout = !isMobileDevice();
-
-  // 移动端模式:只渲染 AI_CHAT_VIEW_ID,添加 mobile class
-  if (!shouldShowFullLayout) {
-    return (
-      <SlotRenderer
-        slot={AI_CHAT_VIEW_ID}
-        isTabbar={true}
-        defaultSize={layout['AI-Chat']?.currentId ? layout['AI-Chat']?.size || 360 : 0}
-        maxResize={420}
-        minResize={280}
-        minSize={0}
-      />
-    );
-  }
-
-  // 正常模式:渲染完整布局
   const defaultRightSize = useMemo(
     () => (designLayoutConfig.useMergeRightWithLeftPanel ? 0 : 49),
     [designLayoutConfig.useMergeRightWithLeftPanel],
   );

+  // 判断是否应该显示完整布局
+  const shouldShowFullLayout = !isMobileDevice();
+
+  // 移动端模式:只渲染 AI_CHAT_VIEW_ID
+  if (!shouldShowFullLayout) {
+    return (
+      <SlotRenderer
+        slot={AI_CHAT_VIEW_ID}
+        isTabbar={true}
+        defaultSize={layout['AI-Chat']?.currentId ? layout['AI-Chat']?.size || 360 : 0}
+        maxResize={420}
+        minResize={280}
+        minSize={0}
+      />
+    );
+  }
+
+  // 正常模式:渲染完整布局
   return (
     <BoxPanel direction='top-to-bottom'>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/layout/ai-layout.tsx` around lines 25 - 42,
The hook call useMemo for defaultRightSize is placed after an early conditional
return (when !shouldShowFullLayout), violating React Hooks rules; move the
useMemo call to the top level of the component (before the conditional return)
so hooks run unconditionally, e.g., compute defaultRightSize using
useMemo(designLayoutConfig.useMergeRightWithLeftPanel) above the if
(!shouldShowFullLayout) block and then reference defaultRightSize inside both
the SlotRenderer return and the full-layout rendering; ensure all other hooks
follow the same top-level placement to keep hook order stable (symbols:
shouldShowFullLayout, SlotRenderer, AI_CHAT_VIEW_ID, defaultRightSize, useMemo,
designLayoutConfig.useMergeRightWithLeftPanel).
🟡 Minor comments (8)
packages/ai-native/src/browser/acp/components/AcpChatMentionInput.tsx-498-535 (1)

498-535: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Rule 搜索逻辑与排序条件不一致,描述匹配永远不会生效。

当前先按 rule.text 过滤,导致仅命中 description 的规则被提前丢弃。

建议修复
-        return mappedRules
-          .filter((rule) => rule.text.toLocaleLowerCase().includes(lowerSearchText))
+        return mappedRules
+          .filter((rule) => {
+            const text = rule.text.toLocaleLowerCase();
+            const desc = (rule.description || '').toLocaleLowerCase();
+            return text.includes(lowerSearchText) || desc.includes(lowerSearchText);
+          })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/acp/components/AcpChatMentionInput.tsx` around
lines 498 - 535, The current filtering only checks rule.text so items that match
only in description are dropped; update the filter used on mappedRules (the
block using lowerSearchText and .filter(...)) to include rules where either
rule.text or rule.description (use toLocaleLowerCase safely, defaulting to '')
includes lowerSearchText, and keep the existing sort logic that already checks
aDescMatch/bDescMatch so description matches can be promoted; ensure you
reference searchText, lowerSearchText, mappedRules, and the comparator that
computes aTextMatch/aDescMatch/bTextMatch/bDescMatch when making the change.
packages/ai-native/src/browser/acp/components/AcpChatInput.tsx-74-106 (1)

74-106: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

指令候选列表可能不会随选中 Agent 变化。

options 计算使用了 selectedAgentIdchatAgentViewService,但依赖数组缺少这两项,导致过滤结果不会随选中 Agent 的变化而更新。

建议修复
-  }, [trigger, chatAgentService]);
+  }, [trigger, chatAgentService, chatAgentViewService, selectedAgentId]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/acp/components/AcpChatInput.tsx` around lines
74 - 106, The useMemo that computes options (function options in
AcpChatInput.tsx) is missing dependencies so the candidate list won't update
when selectedAgentId or chatAgentViewService changes; update the dependency
array of the useMemo to include selectedAgentId and chatAgentViewService (and
keep chatAgentService and trigger) so getRenderAgents() and the filter based on
selectedAgentId re-run when those values change; ensure the dependencies cover
getRenderAgents/getCommands if they are methods on objects that may change.
packages/ai-native/src/browser/acp/permission-dialog-container.tsx-300-300 (1)

300-300: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

CSS 变量表达式括号不闭合

Line 300 的 backgroundColor 少了一个右括号,最终 CSS 值非法,背景色不会按预期生效。

建议修改
-          backgroundColor: 'var(--kt-popover-background, var(--popover-background, var(--app-background))',
+          backgroundColor: 'var(--kt-popover-background, var(--popover-background, var(--app-background)))',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/acp/permission-dialog-container.tsx` at line
300, The inline style for backgroundColor in permission-dialog-container (the
style object where backgroundColor is set to 'var(--kt-popover-background,
var(--popover-background, var(--app-background))') has a missing closing
parenthesis making the CSS var expression invalid; fix it by adding the final
right parenthesis so the nested var() calls are properly closed (update the
backgroundColor value in the style object in PermissionDialogContainer /
permission-dialog-container.tsx).
packages/ai-native/src/browser/chat/chat-input-footer.registry.ts-25-48 (1)

25-48: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

同一 id 重复注册会累积旧 disposable。

当前替换旧条目时只 splice 了贡献项,但不会释放上一轮注册句柄;频繁刷新贡献项会导致 registry 内部 disposable 持续增长。

建议修改
 export class ChatInputFooterRegistry extends Disposable implements IChatInputFooterRegistry {
   private contributions: ChatInputFooterContribution[] = [];
+  private contributionDisposables = new Map<string, IDisposable>();
   private readonly onDidChangeEmitter = new Emitter<void>();
   readonly onDidChange: Event<void> = this.onDidChangeEmitter.event;

   registerFooterItem(id: string, item: ChatInputFooterItem): IDisposable {
+    this.contributionDisposables.get(id)?.dispose();
     const existing = this.contributions.findIndex((c) => c.id === id);
     if (existing !== -1) {
       this.contributions.splice(existing, 1);
     }
...
     const disposable = Disposable.create(() => {
       const idx = this.contributions.indexOf(entry);
       if (idx !== -1) {
         this.contributions.splice(idx, 1);
         this.onDidChangeEmitter.fire();
       }
+      this.contributionDisposables.delete(id);
     });
+    this.contributionDisposables.set(id, disposable);
     this.addDispose(disposable);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/chat/chat-input-footer.registry.ts` around
lines 25 - 48, registerFooterItem replaces an existing contribution entry in
this.contributions but never disposes the previous registration's disposable,
causing disposables to accumulate; update registerFooterItem (and related data
structures) to track the disposable per contribution (e.g., keep a Map<string,
IDisposable> or attach the disposable to the ChatInputFooterContribution entry)
and when an existing id is found dispose the previous disposable (call
.dispose()) before splicing and overwriting the entry, then register and store
the new disposable via Disposable.create and this.addDispose as before.
packages/ai-native/src/browser/chat/chat.internal.service.acp.ts-139-143 (1)

139-143: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

dispose() 漏掉 _onAvailableCommandsChange 的释放。

当前未释放该 emitter,可能残留订阅。请在销毁时一并 dispose。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/chat/chat.internal.service.acp.ts` around
lines 139 - 143, The dispose() override is missing disposal of the
_onAvailableCommandsChange emitter; update the override in
chat.internal.service.acp.ts (the dispose() method) to call
this._onAvailableCommandsChange.dispose() alongside
this._onModeChange.dispose(), this._onSessionLoadingChange.dispose(), and
this._onSessionModelChange.dispose() (before calling super.dispose()) so the
emitter is properly cleaned up and no subscriptions remain.
packages/ai-native/src/browser/components/acp/mention-input.module.less-3-4 (1)

3-4: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

存在空样式块,触发 stylelint block-no-empty

建议删除空的 .popover_icon {},或补充实际样式,避免流水线失败。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/components/acp/mention-input.module.less`
around lines 3 - 4, 删除或填充空的样式块以修复 stylelint 的 block-no-empty 错误:在
mention-input.module.less 中找到空选择器 .popover_icon 并要么直接移除该空规则,要么为 .popover_icon
添加实际样式(如尺寸、display、margin 等)以避免空块;提交时确保不再保留空的大括号以通过流水线检查。
packages/ai-native/src/browser/components/ChatMentionInput.acp.tsx-401-404 (1)

401-404: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

规则搜索过滤条件与排序意图不一致

当前先按 rule.text 过滤,导致“按描述命中”的规则在排序前已被剔除,描述匹配逻辑无法生效。

Also applies to: 407-435

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/components/ChatMentionInput.acp.tsx` around
lines 401 - 404, The filter currently only checks rule.text against
lowerSearchText, removing items that might match rule.description before
sorting; update the filtering to include both rule.text and rule.description
(e.g., check if either toLocaleLowerCase contains lowerSearchText) and then
adjust the sort comparator in the mappedRules.sort call to rank exact/partial
matches in rule.text higher than matches in rule.description (fall back to other
tie-breakers like name or original order). Apply the same filtering+sorting
change to the other occurrence covering lines 407-435 so both blocks use the
unified match-on-text-or-description and sort-by-relevance logic.
packages/ai-native/src/browser/components/ChatHistory.acp.tsx-120-139 (1)

120-139: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

时间分组文案未走国际化

这里直接返回英文字符串,会在非英文语言环境下出现混杂文案。建议改为 localize key。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/components/ChatHistory.acp.tsx` around lines
120 - 139, getTimeKey currently returns hard-coded English strings; change it to
return localization keys and/or formatted localized messages instead. Update the
getTimeKey function to call the project's localization utility (e.g., localize
or useIntl.formatMessage) for each case (Just now, Xm ago, Xh ago, Xd ago, Xw
ago, Xmo ago, Xy ago), supplying the numeric value as a variable where needed,
and replace direct string literals inside getTimeKey with calls like
localize('chat.time.justNow') or localize('chat.time.minutesAgo', { minutes })
so the UI uses translated text across locales.
🧹 Nitpick comments (8)
packages/ai-native/__test__/node/acp-cli-process-manager.test.ts (1)

1-228: ⚡ Quick win

该测试套与另一份 CliAgentProcessManager 用例高度重复,建议合并。

packages/ai-native/__tests__/node/acp/cli-agent-process-manager.test.ts 存在大量同类场景(start/stop/kill/isRunning/wrapError 等)重复,后续容易漂移并增加 CI 耗时。建议保留一套主测试并把另一套改为差异化场景或删除。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/__test__/node/acp-cli-process-manager.test.ts` around
lines 1 - 228, This test file largely duplicates scenarios already covered in
the other test suite for CliAgentProcessManager; consolidate to one canonical
test and remove or convert this file to only cover unique edge cases.
Specifically, keep a single comprehensive test file exercising
CliAgentProcessManager's core behaviors (startAgent, stopAgent, killAgent,
isRunning, getExitCode, listRunningAgents, killAllAgents, handleProcessExit,
killProcessGroup, wrapError) and either delete this duplicate file or refactor
it into a small focused file that only tests the differing scenarios not present
in packages/ai-native/__tests__/node/acp/cli-agent-process-manager.test.ts
(e.g., unique error paths or race conditions), ensuring references to
createMockChildProcess, mockSpawn, and the class CliAgentProcessManager remain
intact while removing overlapping test cases.
packages/ai-native/__test__/node/acp-cli-client.test.ts (1)

446-446: ⚡ Quick win

避免使用固定延时等待异步处理,当前用例有抖动风险。

Line 446、Line 461、Line 478 依赖 setTimeout(10) 等待请求处理完成,在 CI 负载波动时容易出现偶发失败。建议改为基于明确事件/回调完成信号的等待(例如等待 mockStdin.write 被调用的 Promise)。

Also applies to: 461-461, 478-478

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/__test__/node/acp-cli-client.test.ts` at line 446, Replace
the fragile fixed delays (await new Promise(r => setTimeout(r, 10))) with a
deterministic wait that resolves when the stdin write actually occurs: wrap or
replace mockStdin.write with a small helper that returns/fulfills a Promise
(e.g., create a let writeDone; const writePromise = new Promise(res => writeDone
= res); then set mockStdin.write = (...args) => { originalWrite(...args);
writeDone(); }; await writePromise) and use that in place of the setTimeout
calls so the test waits for the actual mockStdin.write invocation rather than a
fixed timeout.
packages/ai-native/src/browser/chat/chat-agent.service.ts (1)

148-148: ⚡ Quick win

错误信息建议改为输出可用 agent id 列表。

${this.agents} 可读性很低,排障价值有限。建议直接输出 keys(),便于快速定位注册状态。

建议修改
-      throw new Error(`No agent with id ${id},this.agents ${this.agents}`);
+      throw new Error(
+        `No agent with id ${id}, available agents: ${Array.from(this.agents.keys()).join(', ')}`,
+      );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/chat/chat-agent.service.ts` at line 148, The
thrown error currently interpolates this.agents (low readability); change the
error to list available agent ids by extracting the keys from this.agents (e.g.,
using this.agents.keys() or Array.from(this.agents.keys())) so the message shows
the missing id and a readable list of registered agent ids; update the throw
site that references id and this.agents to include that keys list in the error
text.
packages/ai-native/src/browser/components/permission-dialog-widget.tsx (1)

127-127: ⚡ Quick win

Line 127 建议与模块样式对齐使用本地 class。

如果按样式文件中的修复改为 .focused 本地类,这里应改为 styles.focused,避免依赖全局类名。

建议修复
-                className={cls(styles.option_button, isFocused && 'focused')}
+                className={cls(styles.option_button, isFocused && styles.focused)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/components/permission-dialog-widget.tsx` at
line 127, 在 PermissionDialogWidget 里将本地样式用于聚焦状态:把
className={cls(styles.option_button, isFocused && 'focused')} 改为使用本地类名
styles.focused(即 cls(styles.option_button, isFocused &&
styles.focused)),以避免引用全局类并与样式模块保持一致;定位相关代码可通过 className
属性、styles.option_button、isFocused 标志和 styles.focused 标识符查找并替换。
packages/ai-native/src/node/acp/acp-agent.service.ts (2)

522-524: ⚡ Quick win

错误被静默吞掉,建议记录日志

cancelRequest 方法中的 catch 块为空,错误被完全忽略。即使取消请求失败不应阻断流程,也建议记录日志以便调试。

♻️ 建议的修复
     try {
       await this.clientService.cancel(cancelNotification);
-    } catch (error) {}
+    } catch (error) {
+      this.logger?.warn('[AcpAgentService] cancelRequest failed:', error);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/node/acp/acp-agent.service.ts` around lines 522 - 524,
The catch block swallowing errors after calling
this.clientService.cancel(cancelNotification) should log the error instead of
being empty; inside the cancelRequest (or the method containing that try/catch)
replace the empty catch with a logging call (e.g., this.logger.error(...) or, if
no instance logger exists, console.error(...)) that includes a clear message and
the caught error/stack plus context (e.g., the cancelNotification or request id)
so failures to cancel are recorded but still do not throw.

213-213: 🏗️ Heavy lift

使用固定延时等待通知是不可靠的模式

第 213 行和第 342 行使用固定延时(2000ms 和 500ms)等待异步通知到达。这种模式不可靠:在高负载或慢网络下可能超时,在快速响应时又浪费时间。建议改用基于事件的方式,等待特定通知或超时。

Also applies to: 342-342

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/node/acp/acp-agent.service.ts` at line 213, Replace
the unreliable fixed-delay waits (the occurrences of "await new
Promise((resolve) => setTimeout(resolve, 2000));" and the 500ms variant) with an
event-driven wait: implement or use a helper like
waitForNotification(notificationType, predicate?, timeoutMs) that returns a
Promise which resolves when the specific notification/event is emitted (or the
predicate matches) and rejects or resolves false on timeout; inside the existing
flow (where those awaits are now) attach a one-time listener to the notification
emitter, resolve the promise when the expected notification arrives, ensure you
remove the listener on resolve/timeout to avoid leaks, and use a reasonable
timeout fallback instead of always sleeping.
packages/ai-native/src/browser/layout/ai-layout.tsx (1)

1-1: 💤 Low value

移除未使用的导入

useEffectuseState 已导入但未在组件中使用。

♻️ 建议的修复
-import React, { useEffect, useMemo, useState } from 'react';
+import React, { useMemo } from 'react';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/layout/ai-layout.tsx` at line 1, Remove the
unused imports useEffect and useState from the React import in ai-layout.tsx:
update the import line (currently "import React, { useEffect, useMemo, useState
} from 'react';") to only include the actually used symbols (e.g., "import
React, { useMemo } from 'react';"), ensuring no other references to
useEffect/useState remain in the file.
packages/ai-native/src/common/prompts/empty-prompt-provider.ts (1)

68-71: ⚡ Quick win

重复的异步调用导致性能浪费

getACPCurrentFileInfo()provideContextPrompt (第 21 行) 和 buildACPPrompt (第 68 行) 中被重复调用。由于这是一个异步方法,涉及编辑器状态访问,建议将结果作为参数传递以避免重复调用。

♻️ 建议的重构
   async provideContextPrompt(context: SerializedContext, userMessage: string): Promise<string> {
     const hasContextFields =
       context.globalRules.length > 0 ||
       context.attachedFolders.length > 0 ||
       context.attachedFiles.length > 0 ||
       context.attachedRules.length > 0;

+    const currentFileInfo = await this.getACPCurrentFileInfo();
+
     if (!hasContextFields) {
-      const currentFileInfo = await this.getACPCurrentFileInfo();
       const hasCurrentFile =
         currentFileInfo && !context.attachedFiles.some((file) => file.path === currentFileInfo.path);
       if (!hasCurrentFile) {
         return userMessage;
       }
     }

-    return this.buildACPPrompt(context, userMessage);
+    return this.buildACPPrompt(context, userMessage, currentFileInfo);
   }

-  private async buildACPPrompt(context: SerializedContext, userMessage: string): Promise<string> {
+  private buildACPPrompt(
+    context: SerializedContext,
+    userMessage: string,
+    currentFileInfo: Awaited<ReturnType<typeof this.getACPCurrentFileInfo>>
+  ): string {
     const sections: string[] = [];

     // ... rest of the method

-    let currentFileInfo = await this.getACPCurrentFileInfo();
+    let effectiveFileInfo = currentFileInfo;
     if (currentFileInfo && context.attachedFiles.some((file) => file.path === currentFileInfo!.path)) {
-      currentFileInfo = null;
+      effectiveFileInfo = null;
     }
-    if (currentFileInfo) {
-      let currentFileSection = `Current file: ${currentFileInfo.path}`;
-      if (currentFileInfo.currentLine && currentFileInfo.lineContent) {
-        currentFileSection += ` (line ${currentFileInfo.currentLine}: \`${currentFileInfo.lineContent}\`)`;
+    if (effectiveFileInfo) {
+      let currentFileSection = `Current file: ${effectiveFileInfo.path}`;
+      if (effectiveFileInfo.currentLine && effectiveFileInfo.lineContent) {
+        currentFileSection += ` (line ${effectiveFileInfo.currentLine}: \`${effectiveFileInfo.lineContent}\`)`;
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/common/prompts/empty-prompt-provider.ts` around lines
68 - 71, The code calls the async method getACPCurrentFileInfo() twice (in
provideContextPrompt and buildACPPrompt); refactor so the caller obtains
currentFileInfo once and passes it into buildACPPrompt (or have
provideContextPrompt accept a currentFileInfo param) to avoid duplicate
async/editor access; update signatures for buildACPPrompt (and any intermediary
calls) to accept the pre-fetched currentFileInfo and remove the second
getACPCurrentFileInfo() call (referencing provideContextPrompt, buildACPPrompt,
and getACPCurrentFileInfo).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 21ecf915-8f11-411a-a9f2-f2ac6832f8b3

📥 Commits

Reviewing files that changed from the base of the PR and between e6c1dab and be0610c.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (107)
  • package.json
  • packages/ai-native/__test__/browser/acp/acp-permission-rpc.service.test.ts
  • packages/ai-native/__test__/browser/acp/permission-bridge.service.test.ts
  • packages/ai-native/__test__/browser/acp/permission-dialog-container.test.ts
  • packages/ai-native/__test__/browser/acp/permission.handler.test.ts
  • packages/ai-native/__test__/browser/chat/chat-model.test.ts
  • packages/ai-native/__test__/node/acp-agent-request-handler.test.ts
  • packages/ai-native/__test__/node/acp-agent.service.test.ts
  • packages/ai-native/__test__/node/acp-cli-back.test.ts
  • packages/ai-native/__test__/node/acp-cli-client.test.ts
  • packages/ai-native/__test__/node/acp-cli-process-manager.test.ts
  • packages/ai-native/__test__/node/acp-file-system-handler.test.ts
  • packages/ai-native/__test__/node/acp-permission-caller.test.ts
  • packages/ai-native/__test__/node/acp-terminal-handler.test.ts
  • packages/ai-native/__tests__/node/acp/cli-agent-process-manager.test.ts
  • packages/ai-native/package.json
  • packages/ai-native/src/browser/acp/acp-permission-rpc.service.ts
  • packages/ai-native/src/browser/acp/components/AcpChatHistory.tsx
  • packages/ai-native/src/browser/acp/components/AcpChatInput.tsx
  • packages/ai-native/src/browser/acp/components/AcpChatMentionInput.tsx
  • packages/ai-native/src/browser/acp/components/AcpChatViewHeader.tsx
  • packages/ai-native/src/browser/acp/components/AcpChatViewWrapper.tsx
  • packages/ai-native/src/browser/acp/components/AcpFooterButtons.tsx
  • packages/ai-native/src/browser/acp/components/AcpFooterContribution.ts
  • packages/ai-native/src/browser/acp/index.ts
  • packages/ai-native/src/browser/acp/permission-bridge.service.ts
  • packages/ai-native/src/browser/acp/permission-dialog-container.module.less
  • packages/ai-native/src/browser/acp/permission-dialog-container.tsx
  • packages/ai-native/src/browser/acp/permission-dialog.module.less
  • packages/ai-native/src/browser/acp/permission-dialog.view.tsx
  • packages/ai-native/src/browser/acp/permission.handler.ts
  • packages/ai-native/src/browser/ai-core.contribution.ts
  • packages/ai-native/src/browser/chat/acp-chat-agent.ts
  • packages/ai-native/src/browser/chat/acp-session-provider.ts
  • packages/ai-native/src/browser/chat/chat-agent.service.ts
  • packages/ai-native/src/browser/chat/chat-input-footer.registry.ts
  • packages/ai-native/src/browser/chat/chat-manager.service.acp.ts
  • packages/ai-native/src/browser/chat/chat-manager.service.ts
  • packages/ai-native/src/browser/chat/chat-model.ts
  • packages/ai-native/src/browser/chat/chat-proxy.service.acp.ts
  • packages/ai-native/src/browser/chat/chat-proxy.service.ts
  • packages/ai-native/src/browser/chat/chat.history.registry.ts
  • packages/ai-native/src/browser/chat/chat.input.registry.ts
  • packages/ai-native/src/browser/chat/chat.internal.service.acp.ts
  • packages/ai-native/src/browser/chat/chat.internal.service.ts
  • packages/ai-native/src/browser/chat/chat.module.less
  • packages/ai-native/src/browser/chat/chat.render.registry.ts
  • packages/ai-native/src/browser/chat/chat.view.acp.tsx
  • packages/ai-native/src/browser/chat/chat.view.registry.ts
  • packages/ai-native/src/browser/chat/default-acp-config-provider.ts
  • packages/ai-native/src/browser/chat/default-chat-agent.ts
  • packages/ai-native/src/browser/chat/get-default-agent-type.ts
  • packages/ai-native/src/browser/chat/local-storage-provider.ts
  • packages/ai-native/src/browser/chat/pick-workspace-dir.ts
  • packages/ai-native/src/browser/chat/session-provider-registry.ts
  • packages/ai-native/src/browser/chat/session-provider.ts
  • packages/ai-native/src/browser/components/ChatEditor.tsx
  • packages/ai-native/src/browser/components/ChatHistory.acp.tsx
  • packages/ai-native/src/browser/components/ChatMentionInput.acp.tsx
  • packages/ai-native/src/browser/components/acp/ChatReply.tsx
  • packages/ai-native/src/browser/components/acp/MentionInput.tsx
  • packages/ai-native/src/browser/components/acp/chat-history.module.less
  • packages/ai-native/src/browser/components/acp/mention-input.module.less
  • packages/ai-native/src/browser/components/acp/types.ts
  • packages/ai-native/src/browser/components/components.module.less
  • packages/ai-native/src/browser/components/mention-input/mention-input.module.less
  • packages/ai-native/src/browser/components/mention-input/mention-input.tsx
  • packages/ai-native/src/browser/components/mention-input/mention-item.tsx
  • packages/ai-native/src/browser/components/mention-input/types.ts
  • packages/ai-native/src/browser/components/permission-dialog-widget.module.less
  • packages/ai-native/src/browser/components/permission-dialog-widget.tsx
  • packages/ai-native/src/browser/contrib/terminal/ai-terminal.service.ts
  • packages/ai-native/src/browser/contrib/terminal/decoration/terminal-decoration.tsx
  • packages/ai-native/src/browser/index.ts
  • packages/ai-native/src/browser/layout/ai-layout.tsx
  • packages/ai-native/src/browser/mcp/base-apply.service.ts
  • packages/ai-native/src/browser/preferences/schema.ts
  • packages/ai-native/src/browser/types.ts
  • packages/ai-native/src/common/index.ts
  • packages/ai-native/src/common/prompts/empty-prompt-provider.ts
  • packages/ai-native/src/node/acp/acp-agent.service.ts
  • packages/ai-native/src/node/acp/acp-cli-back.service.ts
  • packages/ai-native/src/node/acp/acp-cli-client.service.ts
  • packages/ai-native/src/node/acp/acp-permission-caller.service.ts
  • packages/ai-native/src/node/acp/cli-agent-process-manager.ts
  • packages/ai-native/src/node/acp/handlers/agent-request.handler.ts
  • packages/ai-native/src/node/acp/handlers/constants.ts
  • packages/ai-native/src/node/acp/handlers/file-system.handler.ts
  • packages/ai-native/src/node/acp/handlers/terminal.handler.ts
  • packages/ai-native/src/node/acp/index.ts
  • packages/ai-native/src/node/index.ts
  • packages/core-browser/src/ai-native/ai-config.service.ts
  • packages/core-common/package.json
  • packages/core-common/src/log.ts
  • packages/core-common/src/settings/ai-native.ts
  • packages/core-common/src/storage.ts
  • packages/core-common/src/types/ai-native/acp-types.ts
  • packages/core-common/src/types/ai-native/agent-types.ts
  • packages/core-common/src/types/ai-native/index.ts
  • packages/file-service/package.json
  • packages/i18n/src/common/en-US.lang.ts
  • packages/i18n/src/common/zh-CN.lang.ts
  • packages/startup/entry/sample-modules/ai-native/WelcomePage.module.less
  • packages/startup/entry/sample-modules/ai-native/WelcomePage.tsx
  • packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts
  • packages/startup/entry/web/server.ts
  • tools/playwright/src/tests/search-view.test.ts
💤 Files with no reviewable changes (1)
  • packages/ai-native/src/browser/components/mention-input/mention-input.module.less

Comment thread packages/ai-native/src/browser/acp/permission.handler.ts
Comment thread packages/ai-native/src/browser/chat/chat.view.acp.tsx
Comment thread packages/ai-native/src/node/acp/acp-agent.service.ts
Comment thread packages/ai-native/src/node/acp/handlers/file-system.handler.ts
@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Codecov Report

❌ Patch coverage is 46.25170% with 1183 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.92%. Comparing base (e6c1dab) to head (02fb459).

Files with missing lines Patch % Lines
...es/ai-native/src/browser/acp/permission.handler.ts 0.00% 96 Missing and 20 partials ⚠️
...ative/src/browser/chat/chat-manager.service.acp.ts 0.00% 80 Missing and 14 partials ⚠️
...tive/src/browser/chat/chat.internal.service.acp.ts 0.00% 82 Missing and 12 partials ⚠️
...kages/ai-native/src/browser/chat/acp-chat-agent.ts 0.00% 77 Missing and 9 partials ⚠️
...s/ai-native/src/node/acp/acp-cli-client.service.ts 71.67% 70 Missing and 11 partials ⚠️
...ai-native/src/browser/chat/acp-session-provider.ts 0.00% 60 Missing and 12 partials ⚠️
...s/ai-native/src/browser/chat/default-chat-agent.ts 0.00% 66 Missing and 6 partials ⚠️
...native/src/common/prompts/empty-prompt-provider.ts 0.00% 39 Missing and 21 partials ⚠️
...ative/src/browser/acp/permission-bridge.service.ts 0.00% 47 Missing and 5 partials ⚠️
...ckages/ai-native/src/node/acp/acp-agent.service.ts 80.69% 32 Missing and 7 partials ⚠️
... and 25 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4741      +/-   ##
==========================================
- Coverage   52.96%   52.92%   -0.05%     
==========================================
  Files        1686     1720      +34     
  Lines      105031   107381    +2350     
  Branches    22996    23525     +529     
==========================================
+ Hits        55634    56833    +1199     
- Misses      41043    42023     +980     
- Partials     8354     8525     +171     
Flag Coverage Δ
jsdom 47.24% <1.04%> (-1.03%) ⬇️
node 13.08% <45.31%> (+0.84%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Member

@Ricbet Ricbet left a comment

Choose a reason for hiding this comment

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

LGTM

@lulusir lulusir merged commit f0c4c12 into main May 19, 2026
11 checks passed
@lulusir lulusir deleted the feat/acp-v1 branch May 19, 2026 12:21
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