Skip to content
Closed
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
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,3 @@ IFLOW.md
# genie_tts data
CharacterModels/
GenieData/
.agent/
.codex/
.opencode/
.kilocode/
52 changes: 0 additions & 52 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,32 +46,6 @@ ruff check .

如果您使用 VSCode,可以安装 `Ruff` 插件。

##### PR 功能完整性验证(推荐)

如果您希望在本地做一套接近 CI 的完整验证,可使用:

```bash
make pr-test-neo
```

该命令会执行:
- `uv sync --group dev`
- `ruff format --check .` 与 `ruff check .`
- Neo 相关关键测试
- `main.py` 启动 smoke test(检测 `http://localhost:6185`)

需要全量验证时可使用:

```bash
make pr-test-full
```

如果只想快速重复执行(跳过依赖同步和 dashboard 构建):

```bash
make pr-test-full-fast
```


## Contributing Guide

Expand Down Expand Up @@ -114,29 +88,3 @@ We use Ruff as our code formatter and static analysis tool. Before submitting yo
ruff format .
ruff check .
```

##### PR completeness checks (recommended)

To run a local validation flow close to CI, use:

```bash
make pr-test-neo
```

This command runs:
- `uv sync --group dev`
- `ruff format --check .` and `ruff check .`
- Neo-related critical tests
- a startup smoke test against `http://localhost:6185`

For full validation, use:

```bash
make pr-test-full
```

For faster repeated runs (skip dependency sync and dashboard build), use:

```bash
make pr-test-full-fast
```
11 changes: 1 addition & 10 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: worktree worktree-add worktree-rm pr-test-neo pr-test-full pr-test-full-fast
.PHONY: worktree worktree-add worktree-rm

WORKTREE_DIR ?= ../astrbot_worktree
BRANCH ?= $(word 2,$(MAKECMDGOALS))
Expand Down Expand Up @@ -27,15 +27,6 @@ endif
echo "Worktree $(WORKTREE_DIR)/$(BRANCH) not found."; \
fi

pr-test-neo:
./scripts/pr_test_env.sh --profile neo

pr-test-full:
./scripts/pr_test_env.sh --profile full

pr-test-full-fast:
./scripts/pr_test_env.sh --profile full --skip-sync --no-dashboard

# Swallow extra args (branch/base) so make doesn't treat them as targets
%:
@true
72 changes: 2 additions & 70 deletions astrbot/core/astr_main_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,18 @@
from astrbot.core.astr_agent_run_util import AgentRunner
from astrbot.core.astr_agent_tool_exec import FunctionToolExecutor
from astrbot.core.astr_main_agent_resources import (
ANNOTATE_EXECUTION_TOOL,
BROWSER_BATCH_EXEC_TOOL,
BROWSER_EXEC_TOOL,
CHATUI_SPECIAL_DEFAULT_PERSONA_PROMPT,
CREATE_SKILL_CANDIDATE_TOOL,
CREATE_SKILL_PAYLOAD_TOOL,
EVALUATE_SKILL_CANDIDATE_TOOL,
EXECUTE_SHELL_TOOL,
FILE_DOWNLOAD_TOOL,
FILE_UPLOAD_TOOL,
GET_EXECUTION_HISTORY_TOOL,
GET_SKILL_PAYLOAD_TOOL,
KNOWLEDGE_BASE_QUERY_TOOL,
LIST_SKILL_CANDIDATES_TOOL,
LIST_SKILL_RELEASES_TOOL,
LIVE_MODE_SYSTEM_PROMPT,
LLM_SAFETY_MODE_SYSTEM_PROMPT,
LOCAL_EXECUTE_SHELL_TOOL,
LOCAL_PYTHON_TOOL,
PROMOTE_SKILL_CANDIDATE_TOOL,
PYTHON_TOOL,
ROLLBACK_SKILL_RELEASE_TOOL,
RUN_BROWSER_SKILL_TOOL,
SANDBOX_MODE_PROMPT,
SEND_MESSAGE_TO_USER_TOOL,
SYNC_SKILL_RELEASE_TOOL,
TOOL_CALL_PROMPT,
TOOL_CALL_PROMPT_SKILLS_LIKE_MODE,
retrieve_knowledge_base,
Expand Down Expand Up @@ -846,73 +832,19 @@ def _apply_sandbox_tools(
) -> None:
if req.func_tool is None:
req.func_tool = ToolSet()
booter = config.sandbox_cfg.get("booter", "shipyard_neo")
if booter == "shipyard":
if config.sandbox_cfg.get("booter") == "shipyard":
ep = config.sandbox_cfg.get("shipyard_endpoint", "")
at = config.sandbox_cfg.get("shipyard_access_token", "")
if not ep or not at:
logger.error("Shipyard sandbox configuration is incomplete.")
return
os.environ["SHIPYARD_ENDPOINT"] = ep
os.environ["SHIPYARD_ACCESS_TOKEN"] = at

req.func_tool.add_tool(EXECUTE_SHELL_TOOL)
req.func_tool.add_tool(PYTHON_TOOL)
req.func_tool.add_tool(FILE_UPLOAD_TOOL)
req.func_tool.add_tool(FILE_DOWNLOAD_TOOL)
if booter == "shipyard_neo":
# Neo-specific path rule: filesystem tools operate relative to sandbox
# workspace root. Do not prepend "/workspace".
req.system_prompt += (
"\n[Shipyard Neo File Path Rule]\n"
"When using sandbox filesystem tools (upload/download/read/write/list/delete), "
"always pass paths relative to the sandbox workspace root. "
"Example: use `baidu_homepage.png` instead of `/workspace/baidu_homepage.png`.\n"
)

req.system_prompt += (
"\n[Neo Skill Lifecycle Workflow]\n"
"When user asks to create/update a reusable skill in Neo mode, use lifecycle tools instead of directly writing local skill folders.\n"
"Preferred sequence:\n"
"1) Use `astrbot_create_skill_payload` to store canonical payload content and get `payload_ref`.\n"
"2) Use `astrbot_create_skill_candidate` with `skill_key` + `source_execution_ids` (and optional `payload_ref`) to create a candidate.\n"
"3) Use `astrbot_promote_skill_candidate` to release: `stage=canary` for trial; `stage=stable` for production.\n"
"For stable release, set `sync_to_local=true` to sync `payload.skill_markdown` into local `SKILL.md`.\n"
"Do not treat ad-hoc generated files as reusable Neo skills unless they are captured via payload/candidate/release.\n"
"To update an existing skill, create a new payload/candidate and promote a new release version; avoid patching old local folders directly.\n"
)

# Determine sandbox capabilities from an already-booted session.
# If no session exists yet (first request), capabilities is None
# and we register all tools conservatively.
from astrbot.core.computer.computer_client import session_booter

sandbox_capabilities: list[str] | None = None
existing_booter = session_booter.get(session_id)
if existing_booter is not None:
sandbox_capabilities = getattr(existing_booter, "capabilities", None)

# Browser tools: only register if profile supports browser
# (or if capabilities are unknown because sandbox hasn't booted yet)
if sandbox_capabilities is None or "browser" in sandbox_capabilities:
req.func_tool.add_tool(BROWSER_EXEC_TOOL)
req.func_tool.add_tool(BROWSER_BATCH_EXEC_TOOL)
req.func_tool.add_tool(RUN_BROWSER_SKILL_TOOL)

# Neo-specific tools (always available for shipyard_neo)
req.func_tool.add_tool(GET_EXECUTION_HISTORY_TOOL)
req.func_tool.add_tool(ANNOTATE_EXECUTION_TOOL)
req.func_tool.add_tool(CREATE_SKILL_PAYLOAD_TOOL)
req.func_tool.add_tool(GET_SKILL_PAYLOAD_TOOL)
req.func_tool.add_tool(CREATE_SKILL_CANDIDATE_TOOL)
req.func_tool.add_tool(LIST_SKILL_CANDIDATES_TOOL)
req.func_tool.add_tool(EVALUATE_SKILL_CANDIDATE_TOOL)
req.func_tool.add_tool(PROMOTE_SKILL_CANDIDATE_TOOL)
req.func_tool.add_tool(LIST_SKILL_RELEASES_TOOL)
req.func_tool.add_tool(ROLLBACK_SKILL_RELEASE_TOOL)
req.func_tool.add_tool(SYNC_SKILL_RELEASE_TOOL)

req.system_prompt = f"{req.system_prompt or ''}\n{SANDBOX_MODE_PROMPT}\n"
req.system_prompt = f"{req.system_prompt}\n{SANDBOX_MODE_PROMPT}\n"
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

The system_prompt can be None, which would cause this f-string to produce the literal string "None\n..." instead of just the sandbox prompt. The previous implementation f"{req.system_prompt or ''}..." was safer as it correctly handled the None case by converting it to an empty string. I recommend restoring that behavior to prevent unexpected system prompts.

Suggested change
req.system_prompt = f"{req.system_prompt}\n{SANDBOX_MODE_PROMPT}\n"
req.system_prompt = f"{req.system_prompt or ''}\n{SANDBOX_MODE_PROMPT}\n"



def _proactive_cron_job_tools(req: ProviderRequest) -> None:
Expand Down
28 changes: 0 additions & 28 deletions astrbot/core/astr_main_agent_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,11 @@
from astrbot.core.astr_agent_context import AstrAgentContext
from astrbot.core.computer.computer_client import get_booter
from astrbot.core.computer.tools import (
AnnotateExecutionTool,
BrowserBatchExecTool,
BrowserExecTool,
CreateSkillCandidateTool,
CreateSkillPayloadTool,
EvaluateSkillCandidateTool,
ExecuteShellTool,
FileDownloadTool,
FileUploadTool,
GetExecutionHistoryTool,
GetSkillPayloadTool,
ListSkillCandidatesTool,
ListSkillReleasesTool,
LocalPythonTool,
PromoteSkillCandidateTool,
PythonTool,
RollbackSkillReleaseTool,
RunBrowserSkillTool,
SyncSkillReleaseTool,
)
from astrbot.core.message.message_event_result import MessageChain
from astrbot.core.platform.message_session import MessageSession
Expand Down Expand Up @@ -463,20 +449,6 @@ async def retrieve_knowledge_base(
LOCAL_PYTHON_TOOL = LocalPythonTool()
FILE_UPLOAD_TOOL = FileUploadTool()
FILE_DOWNLOAD_TOOL = FileDownloadTool()
BROWSER_EXEC_TOOL = BrowserExecTool()
BROWSER_BATCH_EXEC_TOOL = BrowserBatchExecTool()
RUN_BROWSER_SKILL_TOOL = RunBrowserSkillTool()
GET_EXECUTION_HISTORY_TOOL = GetExecutionHistoryTool()
ANNOTATE_EXECUTION_TOOL = AnnotateExecutionTool()
CREATE_SKILL_PAYLOAD_TOOL = CreateSkillPayloadTool()
GET_SKILL_PAYLOAD_TOOL = GetSkillPayloadTool()
CREATE_SKILL_CANDIDATE_TOOL = CreateSkillCandidateTool()
LIST_SKILL_CANDIDATES_TOOL = ListSkillCandidatesTool()
EVALUATE_SKILL_CANDIDATE_TOOL = EvaluateSkillCandidateTool()
PROMOTE_SKILL_CANDIDATE_TOOL = PromoteSkillCandidateTool()
LIST_SKILL_RELEASES_TOOL = ListSkillReleasesTool()
ROLLBACK_SKILL_RELEASE_TOOL = RollbackSkillReleaseTool()
SYNC_SKILL_RELEASE_TOOL = SyncSkillReleaseTool()

# we prevent astrbot from connecting to known malicious hosts
# these hosts are base64 encoded
Expand Down
20 changes: 1 addition & 19 deletions astrbot/core/computer/booters/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
from ..olayer import (
BrowserComponent,
FileSystemComponent,
PythonComponent,
ShellComponent,
)
from ..olayer import FileSystemComponent, PythonComponent, ShellComponent


class ComputerBooter:
Expand All @@ -16,19 +11,6 @@ def python(self) -> PythonComponent: ...
@property
def shell(self) -> ShellComponent: ...

@property
def capabilities(self) -> tuple[str, ...] | None:
"""Sandbox capabilities (e.g. ('python', 'shell', 'filesystem', 'browser')).

Returns None if the booter doesn't support capability introspection
(backward-compatible default). Subclasses override after boot.
"""
return None

@property
def browser(self) -> BrowserComponent | None:
return None

async def boot(self, session_id: str) -> None: ...

async def shutdown(self) -> None: ...
Expand Down
Loading