Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions astrbot/core/computer/booters/shipyard.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,17 @@ async def exec(
cwd=cwd,
)
payload = _maybe_model_dump(result)
data = payload.get("data")
if isinstance(data, dict):
payload.update(
{key: value for key, value in data.items() if value is not None}
)

stdout = payload.get("output", payload.get("stdout", "")) or ""
stderr = payload.get("error", payload.get("stderr", "")) or ""
stdout = payload.get("output") or payload.get("stdout") or ""
stderr = payload.get("error") or payload.get("stderr") or ""
exit_code = payload.get("exit_code")
if exit_code is None:
exit_code = payload.get("return_code")
if background:
pid: int | None = None
try:
Expand Down
68 changes: 68 additions & 0 deletions tests/unit/test_computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,74 @@ def test_base_class_is_protocol(self):
class TestShipyardBooter:
"""Tests for ShipyardBooter."""

@pytest.mark.asyncio
async def test_shipyard_shell_unwraps_bay_exec_response(self):
"""Bay exec responses keep shell output under the data field."""
from astrbot.core.computer.booters.shipyard import ShipyardShellWrapper

raw_shell = AsyncMock()
raw_shell.exec = AsyncMock(
return_value={
"success": True,
"data": {
"success": True,
"return_code": 0,
"stdout": "hello_from_shipyard_bay\n/workspace\n",
"stderr": "",
"pid": 16,
"process_id": None,
"error": None,
},
"error": None,
}
)

result = await ShipyardShellWrapper(raw_shell).exec(
"echo hello_from_shipyard_bay && pwd"
)

assert result == {
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
"stdout": "hello_from_shipyard_bay\n/workspace\n",
"stderr": "",
"exit_code": 0,
"success": True,
"execution_id": None,
"execution_time_ms": None,
"command": None,
}

@pytest.mark.asyncio
async def test_shipyard_shell_keeps_flat_exec_response_compatible(self):
"""Flat shell responses remain compatible with ShipyardShellWrapper."""
from astrbot.core.computer.booters.shipyard import ShipyardShellWrapper

raw_shell = AsyncMock()
raw_shell.exec = AsyncMock(
return_value={
"success": True,
"stdout": "hello_from_legacy_shell\n/workspace\n",
"stderr": "",
"exit_code": 0,
"execution_id": "exec-legacy",
"execution_time_ms": 45,
"command": "echo hello_from_legacy_shell && pwd",
}
)

result = await ShipyardShellWrapper(raw_shell).exec(
"echo hello_from_legacy_shell && pwd"
)

assert result == {
"stdout": "hello_from_legacy_shell\n/workspace\n",
"stderr": "",
"exit_code": 0,
"success": True,
"execution_id": "exec-legacy",
"execution_time_ms": 45,
"command": "echo hello_from_legacy_shell && pwd",
}

@pytest.mark.asyncio
async def test_shipyard_booter_init(self):
"""Test ShipyardBooter initialization."""
Expand Down