Skip to content

fix(extension-agent): restore keep-alive sandbox reconnects and skill bin checks#791

Merged
dingyi222666 merged 5 commits into
v1-devfrom
fix/e2b-keepAlive
Mar 20, 2026
Merged

fix(extension-agent): restore keep-alive sandbox reconnects and skill bin checks#791
dingyi222666 merged 5 commits into
v1-devfrom
fix/e2b-keepAlive

Conversation

@dingyi222666
Copy link
Copy Markdown
Member

Summary

  • reconnect kept-alive E2B sandboxes when the previous instance is gone and reset sandbox timeouts after command runs
  • check required skill binaries through the active computer backend so local, Windows shell, and remote sandbox environments report availability correctly
  • clean up the skills page action layout so the primary actions stay aligned and diagnostics remain easy to access

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the reliability of E2B sandbox interactions by handling disconnections and timeouts more gracefully. It also refines how the system verifies the presence of necessary binaries for skills, making these checks more accurate across various execution environments. Additionally, the user interface for managing skills has been tidied up for a better user experience.

Highlights

  • E2B Sandbox Robustness: Implemented logic to automatically reconnect kept-alive E2B sandboxes if the previous instance is no longer available and to reset sandbox timeouts after each command execution, improving session stability.
  • Skill Binary Availability Checks: Enhanced the mechanism for checking required skill binaries by routing checks through the active computer backend, ensuring accurate reporting for local, Windows shell, and remote sandbox environments.
  • Skills Page UI/UX Improvement: Refactored the layout of action buttons on the skills page to ensure primary actions remain aligned and diagnostic information is easily accessible, improving user interface consistency.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 20, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1ad681b1-2f9c-4591-b126-1fb75f9b6a59

📥 Commits

Reviewing files that changed from the base of the PR and between cc80855 and 3f790c1.

⛔ Files ignored due to path filters (2)
  • packages/extension-agent/package.json is excluded by !**/*.json
  • packages/extension-tools/package.json is excluded by !**/*.json
📒 Files selected for processing (12)
  • packages/extension-agent/src/cli/dispatch.ts
  • packages/extension-agent/src/computer/backends/e2b.ts
  • packages/extension-agent/src/service/computer.ts
  • packages/extension-agent/src/service/index.ts
  • packages/extension-agent/src/skills/catalog.ts
  • packages/extension-agent/src/skills/render.ts
  • packages/extension-agent/src/skills/scan.ts
  • packages/extension-agent/src/sub-agent/scan.ts
  • packages/extension-agent/src/types/skills.ts
  • packages/extension-agent/src/utils/remote_path.ts
  • packages/extension-agent/src/utils/shadow.ts
  • packages/extension-agent/src/utils/shell.ts

Walkthrough

在多处引入并传递上下文(ctx),新增远程技能/子代理扫描与移除、hasBin 检测与 agentcli 工具,增强 E2B 沙箱恢复与错误分类;导出若干本地 shell 发现函数,调整技能/子代理删除流与 UI 布局,引入并传播 remote 标记。

Changes

Cohort / File(s) Summary
技能 UI 与删除动作
packages/extension-agent/client/components/skills/skills-page.vue
重构技能动作为 .skill-actions-main 网格布局,移除“复制路径”、新增“删除”操作与 removeSkill(item) 流程(含确认对话、per-skill busy 状态、send/removeSkill 调用与 emit refresh),调整 emits 使用 const emit,并更新样式与响应式按钮。
子代理 UI 调整
packages/extension-agent/client/components/sub-agent/sub-agent-catalog.vue, packages/extension-agent/client/components/sub-agent/sub-agent-detail.vue, packages/extension-agent/client/components/sub-agent/sub-agent-page.vue
为子代理列表/详情添加条件“删除”按钮、removableIds prop 与 remove 事件;统一并扩展 canRemove 规则;删除流改为在删除当前选中项时切回列表并更宽容处理取消/关闭;增加危险样式类与样式覆盖;调整容器宽度样式。
E2B 沙箱后端与会话改进
packages/extension-agent/src/computer/backends/e2b.ts
注入 Context,扩展 SandboxWrapper 以含内部 sandbox,引入 _connecting 序列化 connect 调用,新增 run(...) 统一执行/超时处理、缺失沙箱识别与重连策略,增强 disconnect/ensureSandbox 错误处理与日志包装。
计算机服务:远程操作与二进制检测
packages/extension-agent/src/service/computer.ts
新增 hasBin(name, backend?)(本地含权限与 Windows 分支,远端通过会话探测);新增远程接口 scanRemoteSkills/scanRemoteSubAgents/removeRemoteSkill/removeRemoteSubAgent;为 E2B 会话构造传入 ctx。
技能扫描/解析/渲染(ctx 与 remote)
packages/extension-agent/src/skills/scan.ts, packages/extension-agent/src/skills/import.ts, packages/extension-agent/src/skills/catalog.ts, packages/extension-agent/src/skills/render.ts
新增远程技能扫描及资源枚举 (scanRemoteSkills, listRemoteSkillResources),在扫描/解析/可用性检测中传递 ctx,引入 remote 标记到 ScannedSkill/SkillInfo,替换异步 extra-metadata 读取为同步解析,render 支持外部传入 resources
远程子代理扫描与 catalog 合并
packages/extension-agent/src/sub-agent/scan.ts, packages/extension-agent/src/sub-agent/catalog.ts, packages/extension-agent/src/types/sub_agent.ts
新增 scanRemoteMarkdownAgentsREMOTE_SUBAGENTS_ROOT,将 ScanTarget/SubAgentInfo 扩展 remote 字段;buildSubAgentCatalog 增加 extra 参数以合并远程项。
Materialize 与同步相关改动
packages/extension-agent/src/computer/materialize.ts, packages/extension-agent/src/cli/dispatch.ts, packages/extension-agent/src/cli/render.ts, packages/extension-agent/src/cli/service.ts, packages/extension-agent/src/cli/tool.ts, packages/extension-agent/src/cli/types.ts
远程 skill 在 materialize 时直接使用 skill.dir 并跳过复制;sync 改为计算多个本地 skill roots(含 DEFAULT_SKILL_DIRS)并据此判定 targetPath;新增 agentcli 工具实现与常量、在运行时注册工具并使用常量 prompt 标记。
配置与路径默认项
packages/extension-agent/src/config/path.ts, packages/extension-agent/src/config/defaults.ts
新增并导出 DEFAULT_SKILL_DIRS,默认配置改用该常量;为 agentcli 添加默认工具权限(getDefaultToolAuthority)。
导出/API 小改动
packages/extension-agent/src/computer/backends/local/shell.ts, packages/extension-agent/src/types/skills.ts, packages/extension-agent/src/utils/shell.ts
findPowerShell / findGitBash 导出;SkillSource 新增 'openclaw'SkillInfo/SubAgentInfo 增加可选 remote 字段;新增 quoteShellPath() 实用函数。
阴影/显示逻辑与服务整合
packages/extension-agent/src/utils/shadow.ts, packages/extension-agent/src/service/skills.ts, packages/extension-agent/src/service/sub_agent.ts, packages/extension-agent/src/service/index.ts
applyShadowing 添加对 remote 的偏好排序并放宽字段约束;服务层将远程扫描结果合并到 reload()/refreshCatalog(),list/export/remove 等操作加入对 remote 的特殊处理(如禁止导出 remote、远端删除走 remote 接口)。
会话/创建并发控制
packages/extension-agent/src/computer/session.ts
添加 _creating map 以协调并发会话创建,避免重复创建并在创建完成后正确设置 lastActiveAt/cwd。
远程路径与引用工具
packages/extension-agent/src/utils/remote_path.ts, packages/extension-agent/src/utils/shell.ts
新增 computeRemoteDirisRemotePathInside 用于生成/检测远程目录路径;新增 quoteShellPath 以处理带 ~ 的 shell 路径引用。
布局样式微调
多个客户端组件 packages/extension-agent/client/components/*
多个页面容器宽度由 width: min(100%, ...) 改为 width: 100% + min-width: 0,移除若干 .compact 宽度重写以改善响应式行为。
文档
packages/extension-agent/resources/skills/agent-config-admin/SKILL.md
文档把示例中对虚拟 bash CLI 的说明改为使用专用 agentcli 工具,调整命令链语义及不可用时的说明。

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant UI as Client UI
    participant AgentService as Agent Service
    participant Computer as Computer Service
    participant Backend as Backend (E2B / Local)

    UI->>AgentService: 请求扫描 / 删除 / 导出(含 ctx)
    AgentService->>Computer: scanRemoteSkills(ctx) / hasBin(name) / removeRemoteSkill(path)
    alt 后端为远程(E2B)
        Computer->>Backend: 建立或复用会话(_connecting)并执行 probe/rm/run
        Backend-->>Computer: 返回 exit code / 输出 / 错误(可能为 missing-sandbox)
    else 本地
        Computer->>Backend: 本地探测(which / findPowerShell / findGitBash)
        Backend-->>Computer: 返回路径或存在性
    end
    Computer-->>AgentService: 返回结果或错误(已包装消息)
    AgentService-->>UI: 更新 catalog / 触发 refresh / 显示成功或错误提示
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰✨ 我在草间嗅到变更的风,
ctx 携手远近穿梭通,
沙箱复苏能找到路,
技能子代理远近同,
小命令在林中跳舞。

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 拉取请求标题清晰准确地总结了主要变更:E2B沙箱保活重连和技能二进制文件检查。
Description check ✅ Passed 拉取请求描述与变更集相关,涵盖了三个主要方面:沙箱重连、二进制文件检查和技能页面布局优化。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/e2b-keepAlive
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request significantly enhances the agent's capabilities by improving E2B sandbox management and introducing robust skill binary checks. The E2B sandbox connection logic now gracefully handles cases where a kept-alive sandbox might be unavailable, ensuring a new instance is created. The introduction of a centralized run method in E2BComputerSession streamlines command execution and ensures sandbox timeouts are consistently reset. Furthermore, a comprehensive mechanism for checking required skill binaries has been implemented, supporting local environments (including Windows-specific shells) and remote sandboxes. The UI changes in skills-page.vue also improve the layout and accessibility of skill actions. Overall, these changes contribute to a more reliable and functional agent.

Comment thread packages/extension-agent/src/skills/scan.ts
Comment thread packages/extension-agent/src/skills/scan.ts
Comment thread packages/extension-agent/src/skills/scan.ts
Comment thread packages/extension-agent/src/skills/scan.ts
Comment thread packages/extension-agent/src/computer/backends/e2b.ts Outdated
Comment thread packages/extension-agent/src/computer/backends/e2b.ts Outdated
Comment thread packages/extension-agent/src/computer/backends/e2b.ts Outdated
Comment thread packages/extension-agent/src/service/computer.ts
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: 3

Caution

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

⚠️ Outside diff range comments (1)
packages/extension-agent/src/skills/import.ts (1)

122-129: ⚠️ Potential issue | 🔴 Critical

ctx 不在 previewMaterializedSource() 的作用域内,当前代码无法通过 TypeScript 编译。

Line 129 调用 scanSkillRoot(root, ctx)ctx 没有定义。需要为 previewMaterializedSource() 添加 ctx: Context 参数,并在两处调用点传入 ctx

修复
 async function previewMaterializedSource(
+    ctx: Context,
     root: string,
     source: SkillImportInput['type'],
     target: string,
     diagnostics: string[]
 ): Promise<SkillImportPreviewResult> {
     const entries = await collectPreviewEntries(root)
     const scanned = await scanSkillRoot(root, ctx)
-        return await previewMaterializedSource(
-            source.root,
+        return await previewMaterializedSource(
+            ctx,
+            source.root,
             input.type,
             input.name,
             source.diagnostics
         )
-        const preview = await previewMaterializedSource(
-            source.root,
+        const preview = await previewMaterializedSource(
+            ctx,
+            source.root,
             input.type,
             input.type === 'zip'
                 ? stripExt(input.name)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-agent/src/skills/import.ts` around lines 122 - 129,
previewMaterializedSource currently calls scanSkillRoot(root, ctx) but ctx is
undefined; add a new parameter ctx: Context to the previewMaterializedSource
function signature and update both call sites to pass the Context through.
Locate previewMaterializedSource and change its signature to accept ctx, then
update the two places that invoke previewMaterializedSource to forward the
existing Context object so scanSkillRoot(root, ctx) uses a valid ctx.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/extension-agent/src/computer/backends/e2b.ts`:
- Around line 78-93: The current connect/create fallback in ensureSandbox() and
the try/catch around E2BSandbox.connect(...) (and the swallowed exception from
this._sandbox.internal.isRunning()) indiscriminately treats all errors as
"sandbox missing" and recreates a sandbox; change it so you only fall back to
E2BSandbox.create(...) when the error is a concrete "not found"/"terminated"
sandbox error (e.g., specific error.code or E2BSandboxError type/message), and
for other exceptions rethrow (or surface them to be retried by caller) so
transient/network/API errors do not trigger create() and leak resources; update
the catch blocks around E2BSandbox.connect, this._sandbox.internal.isRunning(),
and connect() to inspect the error and only call create() when it matches the
terminated/not-exist condition, otherwise propagate the error.
- Around line 105-112: connect() initializes the sandbox timeout from
this.cfg.timeoutMs but run()'s finally currently overwrites it with the
hardcoded SANDBOX_COMMAND_TIMEOUT, which permanently erases user-configured
timeouts; fix run() so it does not reset the sandbox to the hardcoded
value—either save the sandbox's previous timeout before executing the command
and restore that previous value in the finally block, or restore
this.cfg.timeoutMs instead of SANDBOX_COMMAND_TIMEOUT—and remove the
SANDBOX_COMMAND_TIMEOUT constant definition entirely; update references in run()
and any callers to rely on the restored/ cfg timeout behavior.

In `@packages/extension-agent/src/service/computer.ts`:
- Around line 420-461: Normalize Windows shell aliases (map 'pwsh'↔'powershell',
'bash'↔'sh', 'cmd'↔'cmd.exe') before performing the permission checks so
blockedCommands/allowedCommands comparisons use the canonical name; move or
repeat the alias-mapping logic to run prior to the checks that reference
this.config.computer.local.blockedCommands and allowedCommands (and still keep
the platform-specific detection using findGitBash() and findPowerShell() after
mapping). Update comparisons that call name.toLowerCase() to use the
normalized/canonical command string, and ensure the process.platform === 'win32'
handling (including calls to findGitBash() and findPowerShell()) uses the same
normalization to avoid mismatches.

---

Outside diff comments:
In `@packages/extension-agent/src/skills/import.ts`:
- Around line 122-129: previewMaterializedSource currently calls
scanSkillRoot(root, ctx) but ctx is undefined; add a new parameter ctx: Context
to the previewMaterializedSource function signature and update both call sites
to pass the Context through. Locate previewMaterializedSource and change its
signature to accept ctx, then update the two places that invoke
previewMaterializedSource to forward the existing Context object so
scanSkillRoot(root, ctx) uses a valid ctx.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ec53f12b-4599-4834-b708-1970ee749971

📥 Commits

Reviewing files that changed from the base of the PR and between 00fed05 and 6dc3682.

📒 Files selected for processing (6)
  • packages/extension-agent/client/components/skills/skills-page.vue
  • packages/extension-agent/src/computer/backends/e2b.ts
  • packages/extension-agent/src/computer/backends/local/shell.ts
  • packages/extension-agent/src/service/computer.ts
  • packages/extension-agent/src/skills/import.ts
  • packages/extension-agent/src/skills/scan.ts

Comment thread packages/extension-agent/src/computer/backends/e2b.ts Outdated
Comment thread packages/extension-agent/src/computer/backends/e2b.ts Outdated
Comment thread packages/extension-agent/src/service/computer.ts
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

Caution

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

⚠️ Outside diff range comments (1)
packages/extension-agent/src/computer/backends/e2b.ts (1)

108-141: ⚠️ Potential issue | 🟠 Major

不要在初始化完成前写回 _sandbox 状态。

Line 108-109 先把 _sandbox / _sandboxId 挂到实例上,但后面的 pwdcwd 探测还会继续 await。如果这些步骤里任一步失败,当前对象会留下一个“活着但未完成初始化”的沙箱;后续 ensureSandbox() 只看 isRunning(),会直接复用它,_home / _root / _cwd 可能还是旧值。把实例字段赋值移到初始化全部成功之后,或在失败时显式回滚这些字段。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-agent/src/computer/backends/e2b.ts` around lines 108 -
141, The instance assigns this._sandbox and this._sandboxId before performing
async initialization steps (calls to sandbox.setTimeout, this.run('pwd'),
resolvePath and directory probe), which can leave a partially-initialized
sandbox on failure; change the flow in the initialization routine so that you
only set this._sandbox and this._sandboxId after all async probes succeed (and
after setting this._home, this._root, this._cwd and this._connected), or if you
prefer to keep early assignment wrap the async block in try/catch and explicitly
roll back/clear this._sandbox, this._sandboxId, this._home, this._root,
this._cwd and this._connected in the catch before rethrowing; update usages of
run, resolvePath and setTimeout accordingly and ensure
ensureSandbox()/isRunning() will not see a live sandbox until initialization
completes.
🧹 Nitpick comments (1)
packages/extension-agent/src/cli/dispatch.ts (1)

1817-1840: 同一个远端文件下的本地根检查可以并行。

现在每个 localRoot 都串行 readFile,兼容根越多,agentcli sync 预览越慢。这里适合至少对单个远端文件对应的本地目标用 Promise.all

As per coding guidelines, 'ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE'.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-agent/src/cli/dispatch.ts` around lines 1817 - 1840, The
loop that checks each localRoot for a remote file is doing serial readFile calls
causing slowness; in the block that iterates localRoots (inside the loop over
listRemoteFiles and after reading content via session.readFile), run readFile
for all targetPath candidates in parallel using Promise.all, then iterate the
results to decide equality, increment unchanged, and push to the files array
with the same properties (kind, path, sourcePath, targetPath, content, mode) but
derived from the parallel read results; preserve behavior where current == null
=> mode 'create' and equality check uses strict === against content.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/extension-agent/src/cli/dispatch.ts`:
- Line 1982: The help string "Skill sync fans out to ChatLuna and compatibility
directories such as .agents/skills, .openclaw/skills, .codex/skills,
.claude/skills, and OpenCode skill roots." exceeds the repo max line width;
split it into multiple concatenated string literals or a multi-line template
literal so no single source line goes over the limit (e.g., break after
"ChatLuna and", or after listing a few directories) and ensure the resulting
text is identical when joined; update the long string occurrence in dispatch.ts
(the help/usage text where that sentence appears) to the multi-line form.
- Around line 1823-1825: The current read of targetPath using
readFile(...).catch(() => undefined) hides all IO errors; update the read to
catch errors and only treat ENOENT as "file not found" (return undefined), but
rethrow any other errors (e.g., EACCES, EISDIR) so they are not mistaken for a
create case—locate the readFile usage that assigns current and replace the
blanket catch with an error handler that checks err.code === 'ENOENT' and
otherwise throws the error, ensuring downstream apply/writeFile logic sees real
I/O failures.

In `@packages/extension-agent/src/computer/backends/e2b.ts`:
- Around line 496-515: ensureSandbox currently calls this.connect() from
multiple paths causing concurrent reconnects and sandbox leakage; introduce a
shared pending connection promise (e.g., this._connectPromise) that
ensureSandbox checks and awaits instead of calling connect() directly, have
connect() set this._connectPromise at start and clear it on success or failure,
ensure callers await the same promise so only one sandbox instance is created,
and preserve existing behavior for setting this._connected and this._sandbox
while clearing the pending promise on errors; update references in ensureSandbox
and connect to use this shared promise.
- Around line 518-535: 在 run 方法中目前 finally 块直接 throw err(来自
current.setTimeout),这会覆盖或替换 current.commands.run 的返回值或原始异常;修改 run 以保存 run
的结果或捕获到的原始异常,然后在调用 current.setTimeout
后再决定是否抛出清理错误(例如:记录/抑制非关键清理错误或在保留原始异常时优先抛出原始异常);具体定位
run、ensureSandbox、current.setTimeout 和 isMissingSandboxError,改为先保存 run 的
outcome(返回值或捕获的 error),在 finally 中执行 setTimeout 并仅在没有原始错误且 setTimeout 失败且不是
isMissingSandboxError 时再抛出清理错误,或完全静默/记录清理错误以避免覆盖命令结果。

---

Outside diff comments:
In `@packages/extension-agent/src/computer/backends/e2b.ts`:
- Around line 108-141: The instance assigns this._sandbox and this._sandboxId
before performing async initialization steps (calls to sandbox.setTimeout,
this.run('pwd'), resolvePath and directory probe), which can leave a
partially-initialized sandbox on failure; change the flow in the initialization
routine so that you only set this._sandbox and this._sandboxId after all async
probes succeed (and after setting this._home, this._root, this._cwd and
this._connected), or if you prefer to keep early assignment wrap the async block
in try/catch and explicitly roll back/clear this._sandbox, this._sandboxId,
this._home, this._root, this._cwd and this._connected in the catch before
rethrowing; update usages of run, resolvePath and setTimeout accordingly and
ensure ensureSandbox()/isRunning() will not see a live sandbox until
initialization completes.

---

Nitpick comments:
In `@packages/extension-agent/src/cli/dispatch.ts`:
- Around line 1817-1840: The loop that checks each localRoot for a remote file
is doing serial readFile calls causing slowness; in the block that iterates
localRoots (inside the loop over listRemoteFiles and after reading content via
session.readFile), run readFile for all targetPath candidates in parallel using
Promise.all, then iterate the results to decide equality, increment unchanged,
and push to the files array with the same properties (kind, path, sourcePath,
targetPath, content, mode) but derived from the parallel read results; preserve
behavior where current == null => mode 'create' and equality check uses strict
=== against content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5ee96a5c-3ec4-4176-aae7-b2008b6e586d

📥 Commits

Reviewing files that changed from the base of the PR and between 6dc3682 and 92e1364.

⛔ Files ignored due to path filters (1)
  • packages/extension-agent/package.json is excluded by !**/*.json
📒 Files selected for processing (8)
  • packages/extension-agent/src/cli/dispatch.ts
  • packages/extension-agent/src/computer/backends/e2b.ts
  • packages/extension-agent/src/config/defaults.ts
  • packages/extension-agent/src/config/path.ts
  • packages/extension-agent/src/index.ts
  • packages/extension-agent/src/skills/import.ts
  • packages/extension-agent/src/skills/scan.ts
  • packages/extension-agent/src/types/skills.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/extension-agent/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/extension-agent/src/skills/import.ts
  • packages/extension-agent/src/skills/scan.ts

Comment thread packages/extension-agent/src/cli/dispatch.ts
Comment thread packages/extension-agent/src/cli/dispatch.ts
Comment thread packages/extension-agent/src/computer/backends/e2b.ts
Comment thread packages/extension-agent/src/computer/backends/e2b.ts
Register a dedicated agentcli tool so admin sessions stop routing through bash, and surface remote skill and sub-agent scanning plus removal in the service and UI.
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: 12

Caution

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

⚠️ Outside diff range comments (1)
packages/extension-agent/client/components/sub-agent/sub-agent-page.vue (1)

469-494: ⚠️ Potential issue | 🟠 Major

删除请求缺少进行中保护。

这里在确认后直接发起 removeSubAgent,但整个删除和刷新期间都没有把页面置为 busy。现在目录卡片上也有删除入口,用户可以在首个请求未完成前再次点删除或切换状态,结果会依赖后端删除是否幂等。建议确认通过后立刻锁住 UI(全局 busy 或按 item 的 pending 状态),并在 finally 中恢复。

💡 一个最小改法
 async function removeAgent(item: SubAgentInfo) {
     const selected = selectedId.value === item.id

     try {
         await ElMessageBox.confirm(
             `删除"${item.name}"后需要手动重新导入或重新创建,确定继续吗?`,
             '删除 Sub Agent',
             {
                 confirmButtonText: '删除',
                 cancelButtonText: '取消',
                 type: 'warning'
             }
         )
+        busy.value = true

         await send('chatluna-agent/removeSubAgent', item.id)
         if (selected) {
             currentView.value = 'list'
         }
         await loadExtraData()
         ElMessage.success('已删除该 sub-agent。')
     } catch (error) {
         if (error !== 'cancel' && error !== 'close') {
             ElMessage.error('删除失败,请稍后重试。')
         }
+    } finally {
+        busy.value = false
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-agent/client/components/sub-agent/sub-agent-page.vue`
around lines 469 - 494, The removeAgent function lacks UI locking during the
confirm/remove/refresh flow, so lock the UI immediately after confirmation
(either set a global busy flag or mark the specific SubAgentInfo item with a
pending/loading state) before calling send('chatluna-agent/removeSubAgent',
item.id), ensure subsequent UI changes (currentView switching and loadExtraData)
occur while locked, and always clear that busy/pending flag in a finally block
to restore interactivity; use the existing symbols removeAgent, selectedId,
currentView, loadExtraData and the item (SubAgentInfo) to implement the per-item
pending state if preferred.
🧹 Nitpick comments (1)
packages/extension-agent/src/service/computer.ts (1)

835-836: 行长度超过限制。

Line 836 长度为 170 字符,超过配置的最大 160 字符。建议拆分命令字符串。

建议修改
+        const cmd = [
+            `if [ -d ${quoteShellPath(target)} ]; then rm -rf ${quoteShellPath(target)};`,
+            `elif [ -e ${quoteShellPath(target)} ]; then rm -f ${quoteShellPath(target)}; fi`
+        ].join(' ')
+
         const result = await session.execute(
-            `if [ -d ${quoteShellPath(target)} ]; then rm -rf ${quoteShellPath(target)}; elif [ -e ${quoteShellPath(target)} ]; then rm -f ${quoteShellPath(target)}; fi`,
+            cmd,
             {
                 timeout: 15000
             }
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-agent/src/service/computer.ts` around lines 835 - 836, The
shell command string passed to session.execute (the template literal using
quoteShellPath(target)) exceeds the max line length; split the long command into
smaller concatenated parts or build it as a multi-line template literal so the
line is under 160 chars. Locate the session.execute call around the code using
quoteShellPath(target) and break the command into pieces (e.g., store the
conditional rm command in a const cmdParts or use string concatenation/template
lines) and then pass the assembled cmd to session.execute, preserving the same
logic (check directory -> rm -rf, else if exists -> rm -f).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/extension-agent/src/cli/render.ts`:
- Line 85: The long help string "'Command chains support `&`, `&&`, `|`, `|&`,
`||`, and `;` inside the `command` string. Pipe operators only separate agentcli
calls; they do not stream stdin.'" exceeds the max line width; locate that exact
string in render.ts and split it into multiple shorter string literals
concatenated (or use an array of strings joined) so no source line is longer
than the configured max (80 chars/prettier); keep the same wording and
punctuation, ensuring the final runtime string is identical.

In `@packages/extension-agent/src/computer/backends/e2b.ts`:
- Around line 618-626: The finally block currently throws errors from
current.setTimeout (via the catch that rethrows when
!isMissingSandboxError(err)), which can overwrite an earlier exception from
current.commands.run(); change the logic to preserve and rethrow the original
command error: capture the original exception (e.g., let originalErr) before
entering finally (or detect if one exists), then in the finally catch for
current.setTimeout, do not throw the new setTimeout error; instead, if
originalErr exists rethrow originalErr, otherwise rethrow the setTimeout error;
use the symbols current.setTimeout, current.commands.run, and
isMissingSandboxError to locate and implement this behavior.

In `@packages/extension-agent/src/computer/tools/bash.ts`:
- Around line 104-108: 当前拦截逻辑只在 parseAgentCliCommand(input.command ?? '')
成功时阻止执行,导致带有 agentcli 前缀但解析失败的命令仍会回落到 bash;请把条件改成只要检测到命令以 agentcli 开头即拒绝(例如在检查
action === 'run' 时用 input.command?.trim().match(/^agentcli\b/i) 或
startsWith('agentcli') 代替 parseAgentCliCommand 的成功判定),保留对 this.formatResult(...)
的返回行为来提示使用专用工具;参考标识符:parseAgentCliCommand、input.command、action、this.formatResult。

In `@packages/extension-agent/src/service/computer.ts`:
- Around line 476-485: The current check in session.execute double-escapes the
binary name by calling quoteShell(name) inside the inner template and again on
the outer sh -lc, causing command -v to fail; update the call that builds the
shell command so you only escape the full command once (remove the inner
quoteShell around name and use quoteShell on the whole string passed to sh -lc),
i.e. construct `command -v ${name} >/dev/null 2>&1` without quoting name
separately and then call quoteShell(...) around that full command before passing
it to session.execute; keep using the same session.execute, timeout, and catch
behavior.

In `@packages/extension-agent/src/service/skills.ts`:
- Around line 111-115: The current call assumes this.ctx.chatluna_agent.computer
always exists and may invoke scanRemoteSkills on undefined; update the
invocation to use full optional chaining on both computer and method (e.g.,
this.ctx.chatluna_agent?.computer?.scanRemoteSkills?.()) so the call is skipped
if either is undefined, and still await the result with a fallback to an empty
array and proper error handling (keep or replace the current .catch(() => [])
with an explicit try/catch or a .catch that logs/errors as needed) to ensure no
unhandled exceptions from missing properties or promise rejections; reference
this.ctx.chatluna_agent, computer, and scanRemoteSkills to locate the code.

In `@packages/extension-agent/src/service/sub_agent.ts`:
- Around line 454-458: The current catch(() => []) on
this.ctx.chatluna_agent.computer.scanRemoteSubAgents() silently turns
remote-scan failures into an empty list causing refreshCatalog() to rebuild
_catalog incorrectly; remove the swallowing catch so the error can propagate (or
at minimum catch, log with context and rethrow) and ensure refreshCatalog() is
only called with a successful result from scanRemoteSubAgents(); reference
scanRemoteSubAgents, this.ctx.chatluna_agent, refreshCatalog, and _catalog when
making the change.

In `@packages/extension-agent/src/skills/catalog.ts`:
- Around line 56-58: The current check in the catalog loop that skips IDs
matching /^[a-f0-9]{16}$/i is wrong because createSkillId(file) produces
hash-style IDs for both local and remote skills; replace this heuristic with an
explicit remote-metadata check: remove the regex-based continue in the loop in
packages/extension-agent/src/skills/catalog.ts and instead detect remote skills
using the scanned skill entry’s metadata (e.g. a property such as
entry.metadata.remote, entry.source === 'remote', or whatever flag the scanner
in packages/extension-agent/src/skills/scan.ts sets) so only true remote entries
are skipped and missing local markdown skills will be emitted as state:
'missing'.

In `@packages/extension-agent/src/skills/render.ts`:
- Around line 93-95: 当前代码在构建 `resources` 时用 `.catch(() => [] as string[])` 将
`listSkillResources(skill.dir)` 的 I/O 错误吞掉并当作“无资源”处理;请移除该降级处理:如果
`options.resources` 已显式提供则直接使用它,否则直接 `await listSkillResources(skill.dir)`
不捕获异常,让真实错误冒泡;定位并修改包含 `resources = options.resources ?? (await
listSkillResources(skill.dir).catch(() => [] as string[]))` 的表达式(注意
`listSkillResources`、`options.resources`、`skill.dir` 标识符)以实现上述行为。

In `@packages/extension-agent/src/skills/scan.ts`:
- Around line 200-220: listRemoteSkillResources fails on BSD because the shell
command uses GNU-only "find -printf '%P\n'"; replace the remote command passed
to session.execute with a POSIX-compatible pipeline: use "find ... -type f !
-name SKILL.md -print" (with the same quoteShellPath(dir) usage) and then strip
the leading directory prefix using a portable tool like sed or awk in the same
command so the output returns relative paths; keep the existing stderr check,
timeout, and the function name listRemoteSkillResources unchanged.

In `@packages/extension-agent/src/sub-agent/scan.ts`:
- Around line 340-386: There are duplicated path utilities with divergent
behavior (missing `.openclaw/`) and small helpers extracted unnecessarily;
update so implementations are consistent and follow the coding guideline: keep a
single non-"resolve" top-level function (rename resolveRemoteDir to e.g.
computeRemoteDir) with identical logic to the counterpart (ensure the
`.openclaw/` check is included), inline the tiny helpers trimRemotePrefix,
normalizeRemoteBase, and normalizeRemoteKey into their call sites inside
computeRemoteDir and isRemotePathInside, and remove the separate extracted
functions or ensure all usages use the unified implementation so both modules
behave the same.

In `@packages/extension-agent/src/utils/shadow.ts`:
- Around line 27-35: The current selection picks winner = list[0] after sorting
by remote and priority, which can choose entries that are disabled/invalid or
available === false and thus incorrectly shadow runnable items; before assigning
winner (in the logic around list.sort and winner), filter the list to exclude
entries with disabled === true, invalid === true, or available === false (or
otherwise treat them as non-candidates), then sort the filtered candidates by
the existing remote/priority comparator and pick the first viable candidate;
ensure the same filtering logic applies where shadowing decisions are made so
that shadowedBy/visible calculations (used downstream) only consider
runnable/available entries.

In `@packages/extension-agent/src/utils/shell.ts`:
- Around line 12-13: The branch handling when value === '~' returns an unquoted
$HOME which is not shell-safe; change that branch so it returns the $HOME token
wrapped in double quotes (i.e. a string that yields "$HOME" to the shell)
matching the quoting used by the '~/...' branch so paths with spaces/special
characters are protected; update the if (value === '~') return to produce the
double-quoted form and keep the rest of the function behavior unchanged.

---

Outside diff comments:
In `@packages/extension-agent/client/components/sub-agent/sub-agent-page.vue`:
- Around line 469-494: The removeAgent function lacks UI locking during the
confirm/remove/refresh flow, so lock the UI immediately after confirmation
(either set a global busy flag or mark the specific SubAgentInfo item with a
pending/loading state) before calling send('chatluna-agent/removeSubAgent',
item.id), ensure subsequent UI changes (currentView switching and loadExtraData)
occur while locked, and always clear that busy/pending flag in a finally block
to restore interactivity; use the existing symbols removeAgent, selectedId,
currentView, loadExtraData and the item (SubAgentInfo) to implement the per-item
pending state if preferred.

---

Nitpick comments:
In `@packages/extension-agent/src/service/computer.ts`:
- Around line 835-836: The shell command string passed to session.execute (the
template literal using quoteShellPath(target)) exceeds the max line length;
split the long command into smaller concatenated parts or build it as a
multi-line template literal so the line is under 160 chars. Locate the
session.execute call around the code using quoteShellPath(target) and break the
command into pieces (e.g., store the conditional rm command in a const cmdParts
or use string concatenation/template lines) and then pass the assembled cmd to
session.execute, preserving the same logic (check directory -> rm -rf, else if
exists -> rm -f).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8db4ef0d-6da2-4117-b545-761662a05c21

📥 Commits

Reviewing files that changed from the base of the PR and between 92e1364 and a38af4d.

⛔ Files ignored due to path filters (1)
  • packages/extension-agent/package.json is excluded by !**/*.json
📒 Files selected for processing (30)
  • packages/extension-agent/client/components/computer/computer-page.vue
  • packages/extension-agent/client/components/layout/agent-shell.vue
  • packages/extension-agent/client/components/mcp/mcp-page.vue
  • packages/extension-agent/client/components/skills/skills-page.vue
  • packages/extension-agent/client/components/sub-agent/sub-agent-catalog.vue
  • packages/extension-agent/client/components/sub-agent/sub-agent-detail.vue
  • packages/extension-agent/client/components/sub-agent/sub-agent-page.vue
  • packages/extension-agent/client/components/tool/tool-page.vue
  • packages/extension-agent/resources/skills/agent-config-admin/SKILL.md
  • packages/extension-agent/src/cli/render.ts
  • packages/extension-agent/src/cli/service.ts
  • packages/extension-agent/src/cli/tool.ts
  • packages/extension-agent/src/cli/types.ts
  • packages/extension-agent/src/computer/backends/e2b.ts
  • packages/extension-agent/src/computer/materialize.ts
  • packages/extension-agent/src/computer/tools/bash.ts
  • packages/extension-agent/src/config/defaults.ts
  • packages/extension-agent/src/service/computer.ts
  • packages/extension-agent/src/service/index.ts
  • packages/extension-agent/src/service/skills.ts
  • packages/extension-agent/src/service/sub_agent.ts
  • packages/extension-agent/src/skills/catalog.ts
  • packages/extension-agent/src/skills/render.ts
  • packages/extension-agent/src/skills/scan.ts
  • packages/extension-agent/src/sub-agent/catalog.ts
  • packages/extension-agent/src/sub-agent/scan.ts
  • packages/extension-agent/src/types/skills.ts
  • packages/extension-agent/src/types/sub_agent.ts
  • packages/extension-agent/src/utils/shadow.ts
  • packages/extension-agent/src/utils/shell.ts
✅ Files skipped from review due to trivial changes (5)
  • packages/extension-agent/client/components/sub-agent/sub-agent-detail.vue
  • packages/extension-agent/src/service/index.ts
  • packages/extension-agent/src/types/sub_agent.ts
  • packages/extension-agent/resources/skills/agent-config-admin/SKILL.md
  • packages/extension-agent/client/components/layout/agent-shell.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/extension-agent/src/config/defaults.ts
  • packages/extension-agent/src/types/skills.ts

Comment thread packages/extension-agent/src/cli/render.ts
Comment thread packages/extension-agent/src/computer/backends/e2b.ts Outdated
Comment thread packages/extension-agent/src/computer/tools/bash.ts
Comment thread packages/extension-agent/src/service/computer.ts
Comment thread packages/extension-agent/src/service/skills.ts
Comment thread packages/extension-agent/src/skills/render.ts Outdated
Comment thread packages/extension-agent/src/skills/scan.ts
Comment thread packages/extension-agent/src/sub-agent/scan.ts Outdated
Comment thread packages/extension-agent/src/utils/shadow.ts Outdated
Comment thread packages/extension-agent/src/utils/shell.ts Outdated
Prevent concurrent keep-alive reconnects from creating duplicate sandboxes or duplicate session entries for the same conversation.
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: 1

♻️ Duplicate comments (1)
packages/extension-agent/src/computer/backends/e2b.ts (1)

640-658: ⚠️ Potential issue | 🟠 Major

finally 块中的 throw 会覆盖原始结果或异常。

current.commands.run() 成功返回但 setTimeout() 抛出非 missing-sandbox 错误时,调用方会收到 setTimeout 的异常而非命令结果。更糟糕的是,如果命令执行本身抛出异常,也会被 setTimeout 的错误覆盖,导致真正的错误信息丢失。

🐛 建议修复:保留原始异常优先级
     private async run(
         command: string,
         options?: CommandStartOpts,
         sandbox?: SandboxWrapper
     ) {
         const current = sandbox ?? (await this.ensureSandbox())
+        let commandError: unknown

         try {
             return await current.commands.run(command, options)
+        } catch (err) {
+            commandError = err
+            throw err
         } finally {
             try {
                 await current.setTimeout(this.cfg.timeoutMs)
             } catch (err) {
-                if (!isMissingSandboxError(err)) {
+                if (!isMissingSandboxError(err) && !commandError) {
                     throw err
                 }
             }
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-agent/src/computer/backends/e2b.ts` around lines 640 -
658, In run, the finally block's throw from current.setTimeout can overwrite the
original result or exception from current.commands.run; change run to capture
the run result or error (e.g., store result in a local variable and store any
run exception), then call current.setTimeout and only propagate the setTimeout
error when there was no prior run error (or attach it as secondary/cause) and
when it isn't a missing-sandbox error via isMissingSandboxError; ensure
commands.run result is returned when successful and original run exceptions are
rethrown if present, while still handling setTimeout errors as non-fatal when
appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/extension-agent/src/computer/backends/e2b.ts`:
- Around line 703-722: The isMissingSandboxError function contains speculative
message-based checks; update function isMissingSandboxError to remove the
fallback message matching (the checks against err.message including 'not found',
'not running anymore', 'terminated') and instead only return true for actual
NotFoundError types — keep the initial instanceof NotFoundError check, ensure
non-Error values still return false, and then simply return err.name ===
'NotFoundError' for Error instances; adjust control flow accordingly so no
message string inspections remain.

---

Duplicate comments:
In `@packages/extension-agent/src/computer/backends/e2b.ts`:
- Around line 640-658: In run, the finally block's throw from current.setTimeout
can overwrite the original result or exception from current.commands.run; change
run to capture the run result or error (e.g., store result in a local variable
and store any run exception), then call current.setTimeout and only propagate
the setTimeout error when there was no prior run error (or attach it as
secondary/cause) and when it isn't a missing-sandbox error via
isMissingSandboxError; ensure commands.run result is returned when successful
and original run exceptions are rethrown if present, while still handling
setTimeout errors as non-fatal when appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f0b3716d-e14e-4df8-87d3-d52896d1d1ce

📥 Commits

Reviewing files that changed from the base of the PR and between a38af4d and cc80855.

⛔ Files ignored due to path filters (1)
  • packages/extension-agent/package.json is excluded by !**/*.json
📒 Files selected for processing (2)
  • packages/extension-agent/src/computer/backends/e2b.ts
  • packages/extension-agent/src/computer/session.ts

Comment thread packages/extension-agent/src/computer/backends/e2b.ts
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