Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d16398a
feat(computer): add shipyard_neo booter runtime and sandbox config
w31r4 Feb 11, 2026
73251db
feat(skills): add neo lifecycle tools and stable sync manager
w31r4 Feb 11, 2026
a8cc995
feat(dashboard): add neo skills APIs and management UI
w31r4 Feb 11, 2026
d4dcc64
chore: apply pre-commit formatting fixes for neo integration
w31r4 Feb 11, 2026
afe292d
fix: address neo skill review findings
w31r4 Feb 11, 2026
40c7cf3
feat(skills): merge sandbox built-ins with uploaded skill sync
w31r4 Feb 12, 2026
1d81c52
feat(computer): add INFO-level lifecycle logging to booter implementa…
w31r4 Feb 15, 2026
401dfb9
feat(dashboard): log Computer/sandbox config changes on save
w31r4 Feb 15, 2026
aa3b012
feat: add Shipyard Neo quick-start script
w31r4 Feb 15, 2026
963122b
chore: update gitignore, Makefile, skills route, and test scaffolding
w31r4 Feb 15, 2026
9d44947
feat(dashboard): update Shipyard Neo config hints
w31r4 Feb 16, 2026
64d8daa
feat(scripts): update start-with-neo.sh for auto-provisioned API key
w31r4 Feb 16, 2026
4b07aa2
test(computer): add tests for credentials discovery and config logging
w31r4 Feb 16, 2026
418913a
docs: add PR verification workflow to CONTRIBUTING.md
w31r4 Feb 16, 2026
1a53983
Merge branch 'master' into feat/neo-skill-self-iteration
RC-CHN Feb 17, 2026
d62a6f1
fix(computer): mask bay api key in logs
RC-CHN Feb 17, 2026
cf9a723
fix(computer): return none for unsupported browser capability
RC-CHN Feb 17, 2026
b489192
refactor(api): centralize neo client lifecycle in skills route
RC-CHN Feb 17, 2026
591803d
refactor(skills): centralize neo promote and sync flow
RC-CHN Feb 17, 2026
707db76
style: format code
RC-CHN Feb 17, 2026
7c8dac2
feat(computer): add Bay credentials.json auto-discovery
w31r4 Feb 16, 2026
4043a10
fix(computer): improve ShipyardNeoBooter error message
w31r4 Feb 16, 2026
bc3e09f
refactor(computer): split sandbox skill sync phases
RC-CHN Feb 18, 2026
7e24647
fix(dashboard): graceful error handling for Neo skills when unconfigured
w31r4 Feb 18, 2026
18ebeae
test(computer): add tests for credentials discovery and config logging
w31r4 Feb 18, 2026
3769f14
feat(dashboard): validate Bay connectivity on config save
w31r4 Feb 18, 2026
92a8e40
feat(computer): auto-start Bay container for zero-config Neo integration
w31r4 Feb 20, 2026
1962ff2
feat(computer): expose sandbox capabilities and smart profile selection
w31r4 Feb 20, 2026
1df1138
feat(agent): conditionally register browser tools based on sandbox ca…
w31r4 Feb 20, 2026
b816045
refactor(skills): rewrite skills prompt and sanitize example paths
w31r4 Feb 20, 2026
c1917eb
fix(computer): resolve absolute skill paths at runtime in scan command
w31r4 Feb 20, 2026
d21212d
test(computer): add profile-aware sandbox selection tests
w31r4 Feb 20, 2026
48a0b97
test(skills): add skill metadata enrichment tests
w31r4 Feb 20, 2026
847ef0f
Merge remote-tracking branch 'origin/master' into feat/neo-skill-self…
RC-CHN Feb 26, 2026
f01c23a
fix(agent): enforce relative paths for neo sandbox tools
RC-CHN Feb 26, 2026
e1719ef
fix(skills): normalize release stage and handle rollback skip
RC-CHN Feb 26, 2026
8faed94
fix(skills): ensure synced markdown has frontmatter
RC-CHN Feb 26, 2026
8d5841b
feat(skills): add neo candidate and release deletion
RC-CHN Feb 26, 2026
d5a3107
style: format code
RC-CHN Feb 26, 2026
e95bd8d
style: format code
RC-CHN Feb 26, 2026
1ebc207
fix(skills): gate neo mode by runtime config
RC-CHN Feb 26, 2026
87cbcc9
fix(neo): sanitize skill name in frontmatter to prevent injection
camera-2018 Feb 26, 2026
18114ea
fix(neo): sanitize skill name in frontmatter to prevent injection
camera-2018 Feb 26, 2026
4b1bda5
Merge pull request #2 from camera-2018/feat/neo-skill-self-iteration
w31r4 Feb 26, 2026
73e665b
feat(neo): guide skill lifecycle tool workflow
RC-CHN Feb 26, 2026
4ff4c5f
fix(skills): remove deleted skills from sandbox cache
RC-CHN Feb 26, 2026
13c8fa3
fix(skills): use workspace path for sandbox skills
RC-CHN Feb 27, 2026
c1de265
feat(skills): mark sandbox preset skills readonly
RC-CHN Feb 27, 2026
a219a8b
Merge remote-tracking branch 'origin/master' into feat/neo-skill-self…
RC-CHN Feb 27, 2026
edf0982
feat(skills): enhance candidate promotion buttons with loading and di…
Soulter Feb 28, 2026
7c91309
feat(i18n): add neoFilterHint for filtering candidates and release re…
Soulter Feb 28, 2026
0644956
feat(i18n): add neoDeactivate messages for extension management
Soulter Feb 28, 2026
f818ad0
Merge remote-tracking branch 'origin/master' into feat/neo-skill-self…
RC-CHN Mar 2, 2026
6e1be64
Merge branch 'feat/neo-skill-self-iteration' of https://github.com/w3…
RC-CHN Mar 2, 2026
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: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@ IFLOW.md
# genie_tts data
CharacterModels/
GenieData/
.agent/
.codex/
.opencode/
.kilocode/
52 changes: 52 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,32 @@ 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 @@ -88,3 +114,29 @@ 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: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: worktree worktree-add worktree-rm
.PHONY: worktree worktree-add worktree-rm pr-test-neo pr-test-full pr-test-full-fast

WORKTREE_DIR ?= ../astrbot_worktree
BRANCH ?= $(word 2,$(MAKECMDGOALS))
Expand Down Expand Up @@ -27,6 +27,15 @@ 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: 70 additions & 2 deletions astrbot/core/astr_main_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,32 @@
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 @@ -832,19 +846,73 @@ def _apply_sandbox_tools(
) -> None:
if req.func_tool is None:
req.func_tool = ToolSet()
if config.sandbox_cfg.get("booter") == "shipyard":
booter = config.sandbox_cfg.get("booter", "shipyard_neo")
if 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)
req.system_prompt = f"{req.system_prompt}\n{SANDBOX_MODE_PROMPT}\n"
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"


def _proactive_cron_job_tools(req: ProviderRequest) -> None:
Expand Down
28 changes: 28 additions & 0 deletions astrbot/core/astr_main_agent_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,25 @@
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 @@ -449,6 +463,20 @@ 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: 19 additions & 1 deletion astrbot/core/computer/booters/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
from ..olayer import FileSystemComponent, PythonComponent, ShellComponent
from ..olayer import (
BrowserComponent,
FileSystemComponent,
PythonComponent,
ShellComponent,
)


class ComputerBooter:
Expand All @@ -11,6 +16,19 @@ 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