Skip to content

Fix/8978#8988

Open
restyboy wants to merge 4 commits into
AstrBotDevs:masterfrom
restyboy:fix/8978
Open

Fix/8978#8988
restyboy wants to merge 4 commits into
AstrBotDevs:masterfrom
restyboy:fix/8978

Conversation

@restyboy

@restyboy restyboy commented Jun 24, 2026

Copy link
Copy Markdown

… (#8978)

修复启用沙箱的情况下,工具响应数据过长,自动转换为文件后,文件未上传至沙箱的问题。同时兼容沙箱和astrbot未部署在同一主机的情况。

Modifications / 改动点

文件 变更
tool_loop_agent_runner.py reset() 新增 overflow_file_writer 参数;_write_tool_result_overflow_file() 优先使用回调写入
computer_client.py 新增 make_sandbox_overflow_writer() 工厂函数
astr_main_agent.py 沙箱模式下构建 overflow writer 并传入 reset()
context.py 同上,增加 _is_sandbox_runtime() 辅助方法
  • [√] This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

QQ_1782298300810 QQ_1782298344719

Checklist / 检查清单

  • [√] 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • [√] 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • [√] 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • [√] 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Handle large tool responses in sandbox mode by delegating overflow file writes to a sandbox-aware writer and wiring it through the agent runner and contexts.

Enhancements:

  • Introduce a sandbox-specific overflow writer factory that writes tool overflow content into the sandbox filesystem and returns a sandbox-relative path.
  • Wire the sandbox overflow writer into main agent construction and star context so sandbox runtimes use it automatically when available.
  • Extend the tool loop agent runner to accept an optional overflow file writer callback, preferring it over host-disk overflow directory handling when present.

Tests:

  • Add unit and integration-style tests covering the sandbox overflow writer behavior, filename sanitization, and its integration with the tool loop agent runner, including fallback to disk when no writer is provided.

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. area:core The bug / feature is about astrbot's core, backend labels Jun 24, 2026

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

Copy link
Copy Markdown
Contributor

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 introduces a mechanism to write tool-result overflow content directly into a sandbox environment instead of the host disk by adding an 'overflow_file_writer' callback. The feedback highlights that several newly added tests incorrectly assert absolute '/tmp/' paths instead of the sandbox-relative paths actually returned by the implementation. Additionally, a potential 'AttributeError' was flagged in '_is_sandbox_runtime' if 'provider_settings' is explicitly set to 'None'.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread tests/test_tool_loop_agent_runner.py Outdated
Comment on lines +1920 to +1922
assert result_path.startswith("/tmp/astrbot_overflow_"), (
f"Expected sandbox /tmp/ path, got: {result_path}"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

由于 make_sandbox_overflow_writer 的实现已经改为返回相对路径(不带 /tmp/ 前缀,如其文档字符串所述),此处的断言会失败。应该将断言修改为检查是否以 astrbot_overflow_ 开头。

Suggested change
assert result_path.startswith("/tmp/astrbot_overflow_"), (
f"Expected sandbox /tmp/ path, got: {result_path}"
)
assert result_path.startswith("astrbot_overflow_"), (
f"Expected sandbox path starting with 'astrbot_overflow_', got: {result_path}"
)

Comment thread tests/unit/test_computer.py Outdated
Comment on lines +809 to +811
assert result.startswith("/tmp/astrbot_overflow_"), (
f"Expected sandbox /tmp/ path, got: {result}"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

由于 make_sandbox_overflow_writer 实际返回的是相对路径(不带 /tmp/ 前缀),此处的断言会失败。应该将断言修改为检查是否以 astrbot_overflow_ 开头。

Suggested change
assert result.startswith("/tmp/astrbot_overflow_"), (
f"Expected sandbox /tmp/ path, got: {result}"
)
assert result.startswith("astrbot_overflow_"), (
f"Expected sandbox path starting with 'astrbot_overflow_', got: {result}"
)

Comment thread tests/unit/test_computer.py Outdated
result = await writer("data", "chatcmpl-tool_abc:123/456")

# Path must be a valid POSIX filename under /tmp/
assert result.startswith("/tmp/astrbot_overflow_")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

同上,由于返回的是相对路径,此处的断言会失败。应该修改为检查是否以 astrbot_overflow_ 开头。

Suggested change
assert result.startswith("/tmp/astrbot_overflow_")
assert result.startswith("astrbot_overflow_")

Comment on lines +516 to +519
cfg = self.get_config(umo=umo)
runtime = str(
cfg.get("provider_settings", {}).get("computer_use_runtime", "local")
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

如果配置中的 provider_settings 显式为 Nonecfg.get("provider_settings", {}) 将返回 None,随后调用 .get() 会抛出 AttributeError。建议使用 cfg.get("provider_settings") or {} 进行防御性保护。

        cfg = self.get_config(umo=umo)
        provider_settings = cfg.get("provider_settings") or {}
        runtime = str(provider_settings.get("computer_use_runtime", "local"))

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • The construction of overflow_file_writer for sandbox runtimes is duplicated in both astr_main_agent.build_main_agent and Context.tool_loop_agent with slightly different config checks; consider centralizing this logic or reusing _is_sandbox_runtime to avoid future drift.
  • make_sandbox_overflow_writer’s docstring and callers/tests appear to disagree on whether it should return a sandbox-relative path or an absolute /tmp/... path; please align the implementation, docstring, and call sites on a single contract to avoid confusion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The construction of `overflow_file_writer` for sandbox runtimes is duplicated in both `astr_main_agent.build_main_agent` and `Context.tool_loop_agent` with slightly different config checks; consider centralizing this logic or reusing `_is_sandbox_runtime` to avoid future drift.
- `make_sandbox_overflow_writer`’s docstring and callers/tests appear to disagree on whether it should return a sandbox-relative path or an absolute `/tmp/...` path; please align the implementation, docstring, and call sites on a single contract to avoid confusion.

## Individual Comments

### Comment 1
<location path="astrbot/core/computer/computer_client.py" line_range="557-566" />
<code_context>
+    the sandbox working directory rather than an absolute ``/tmp/...`` path.
+    """
+
+    async def _write(content: str, tool_call_id: str) -> str:
+        safe_id = (
+            "".join(
+                ch if ch.isalnum() or ch in {"-", "_", "."} else "_"
+                for ch in tool_call_id
+            ).strip("._")
+            or "tool_call"
+        )
+        sandbox_path = f"astrbot_overflow_{safe_id}_{uuid.uuid4().hex[:8]}.txt"
+        booter = await get_booter(context, unified_msg_origin)
+        await booter.fs.write_file(sandbox_path, content)
</code_context>
<issue_to_address>
**suggestion:** Consider truncating the sanitized tool_call_id to avoid excessively long filenames in the sandbox.

If `tool_call_id` can be very long, `safe_id` may create filenames that exceed filesystem limits or be unwieldy in logs. Consider truncating `safe_id` (e.g., to 32–64 chars) before appending the UUID so filenames stay within reasonable bounds while remaining debuggable.

```suggestion
    async def _write(content: str, tool_call_id: str) -> str:
        safe_id = (
            "".join(
                ch if ch.isalnum() or ch in {"-", "_", "."} else "_"
                for ch in tool_call_id
            ).strip("._")
            or "tool_call"
        )
        max_safe_id_len = 64
        if len(safe_id) > max_safe_id_len:
            safe_id = safe_id[:max_safe_id_len]
        sandbox_path = f"astrbot_overflow_{safe_id}_{uuid.uuid4().hex[:8]}.txt"
        booter = await get_booter(context, unified_msg_origin)
```
</issue_to_address>

### Comment 2
<location path="tests/test_tool_loop_agent_runner.py" line_range="1919-1920" />
<code_context>
+
+    result_path = await writer("hello sandbox", "call_abc123")
+
+    # Must return a /tmp/ path
+    assert result_path.startswith("/tmp/astrbot_overflow_"), (
+        f"Expected sandbox /tmp/ path, got: {result_path}"
+    )
</code_context>
<issue_to_address>
**issue (testing):** Avoid hard-coding a `/tmp/` prefix for sandbox overflow paths in this test

This assertion couples the test to a specific `/tmp`-based layout and will break if the sandbox implementation changes (as suggested in the PR description, which mentions returning a sandbox-relative path). Instead, assert the stable parts of the contract, e.g. that the result is a string, includes an `astrbot_overflow_` prefix and `.txt` suffix, and possibly excludes unsafe characters, without requiring a `/tmp/` root.
</issue_to_address>

### Comment 3
<location path="tests/unit/test_computer.py" line_range="808-809" />
<code_context>
+
+        result = await writer("overflow content", "tool-call-001")
+
+        # Must return a /tmp/ sandbox path
+        assert result.startswith("/tmp/astrbot_overflow_"), (
+            f"Expected sandbox /tmp/ path, got: {result}"
+        )
</code_context>
<issue_to_address>
**issue (testing):** Relax the `/tmp/`-specific assertion to avoid over-constraining sandbox writer behaviour

As with the other sandbox writer tests, asserting a `/tmp/` prefix makes this brittle if the implementation later uses a different base directory or relative paths. Instead, assert only the invariant pieces: that the path includes `astrbot_overflow_`, ends with `.txt`, and encodes the `tool_call_id` in the basename (which you already verify).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +557 to +566
async def _write(content: str, tool_call_id: str) -> str:
safe_id = (
"".join(
ch if ch.isalnum() or ch in {"-", "_", "."} else "_"
for ch in tool_call_id
).strip("._")
or "tool_call"
)
sandbox_path = f"astrbot_overflow_{safe_id}_{uuid.uuid4().hex[:8]}.txt"
booter = await get_booter(context, unified_msg_origin)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: Consider truncating the sanitized tool_call_id to avoid excessively long filenames in the sandbox.

If tool_call_id can be very long, safe_id may create filenames that exceed filesystem limits or be unwieldy in logs. Consider truncating safe_id (e.g., to 32–64 chars) before appending the UUID so filenames stay within reasonable bounds while remaining debuggable.

Suggested change
async def _write(content: str, tool_call_id: str) -> str:
safe_id = (
"".join(
ch if ch.isalnum() or ch in {"-", "_", "."} else "_"
for ch in tool_call_id
).strip("._")
or "tool_call"
)
sandbox_path = f"astrbot_overflow_{safe_id}_{uuid.uuid4().hex[:8]}.txt"
booter = await get_booter(context, unified_msg_origin)
async def _write(content: str, tool_call_id: str) -> str:
safe_id = (
"".join(
ch if ch.isalnum() or ch in {"-", "_", "."} else "_"
for ch in tool_call_id
).strip("._")
or "tool_call"
)
max_safe_id_len = 64
if len(safe_id) > max_safe_id_len:
safe_id = safe_id[:max_safe_id_len]
sandbox_path = f"astrbot_overflow_{safe_id}_{uuid.uuid4().hex[:8]}.txt"
booter = await get_booter(context, unified_msg_origin)

Comment thread tests/test_tool_loop_agent_runner.py Outdated
Comment on lines +1919 to +1920
# Must return a /tmp/ path
assert result_path.startswith("/tmp/astrbot_overflow_"), (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (testing): Avoid hard-coding a /tmp/ prefix for sandbox overflow paths in this test

This assertion couples the test to a specific /tmp-based layout and will break if the sandbox implementation changes (as suggested in the PR description, which mentions returning a sandbox-relative path). Instead, assert the stable parts of the contract, e.g. that the result is a string, includes an astrbot_overflow_ prefix and .txt suffix, and possibly excludes unsafe characters, without requiring a /tmp/ root.

Comment thread tests/unit/test_computer.py Outdated
Comment on lines +808 to +809
# Must return a /tmp/ sandbox path
assert result.startswith("/tmp/astrbot_overflow_"), (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (testing): Relax the /tmp/-specific assertion to avoid over-constraining sandbox writer behaviour

As with the other sandbox writer tests, asserting a /tmp/ prefix makes this brittle if the implementation later uses a different base directory or relative paths. Instead, assert only the invariant pieces: that the path includes astrbot_overflow_, ends with .txt, and encodes the tool_call_id in the basename (which you already verify).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant