mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-10 10:20:45 +08:00
完成运行时与启动链路的全面相对路径整改,统一以仓库根为路径基准并移除 oclaw 双层硬编码。
同时补齐路径回归测试与开源自检文档,确保从非仓库 cwd 启动时 workspace 根、配置和 wiki 路径仍稳定可用。 Made-with: Cursor
This commit is contained in:
parent
dbbe3add6a
commit
9d2900db02
40 changed files with 223 additions and 82 deletions
|
|
@ -90,3 +90,24 @@ def test_main_hooks_list_invocation(tmp_path, monkeypatch) -> None:
|
|||
assert code == 0
|
||||
obj = json.loads(buf.getvalue())
|
||||
assert "hooks" in obj
|
||||
|
||||
|
||||
def test_resolve_cli_workspace_uses_env_over_cwd(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
expected = tmp_path / "repo-root"
|
||||
expected.mkdir(parents=True)
|
||||
monkeypatch.setenv("OCLAW_WORKSPACE", str(expected))
|
||||
from oclaw.runtime.operations.hooks_cmd import _resolve_cli_workspace
|
||||
|
||||
ns = argparse.Namespace(workspace="")
|
||||
assert _resolve_cli_workspace(ns) == str(expected)
|
||||
|
||||
|
||||
def test_resolve_cli_workspace_falls_back_to_workspace_root(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
monkeypatch.delenv("OCLAW_WORKSPACE", raising=False)
|
||||
monkeypatch.setenv("OPS_WORKSPACE_ROOT", str(tmp_path))
|
||||
from oclaw.runtime.operations.hooks_cmd import _resolve_cli_workspace
|
||||
|
||||
ns = argparse.Namespace(workspace="")
|
||||
assert _resolve_cli_workspace(ns) == str(tmp_path.resolve())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from oclaw.platform.config.paths import PROJECT_ROOT
|
||||
from oclaw.interfaces.http.fastapi_app import _resolve_startup_workspace_dir, _resolve_startup_workspace_dirs
|
||||
|
||||
|
||||
|
|
@ -30,3 +31,9 @@ def test_resolve_startup_workspace_dirs_all_agents() -> None:
|
|||
assert ("main", "D:/project/chatgpt/oclaw/workspace-main") in got_norm
|
||||
assert ("social", "D:/project/chatgpt/oclaw/workspace-social") in got_norm
|
||||
|
||||
|
||||
def test_resolve_startup_workspace_dir_fallback_to_repo_runtime_main() -> None:
|
||||
got = _resolve_startup_workspace_dir({})
|
||||
expect = str((PROJECT_ROOT / "runtime" / "workspaces" / "main").resolve()).replace("\\", "/")
|
||||
assert got.replace("\\", "/") == expect
|
||||
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ from oclaw.platform.config.paths import PROJECT_ROOT
|
|||
|
||||
|
||||
_PROMPT_CRITICAL_FILES = (
|
||||
"oclaw/runtime/chat/agent.py",
|
||||
"oclaw/runtime/agents/network_ops_agent.py",
|
||||
"oclaw/runtime/agents/factory.py",
|
||||
"oclaw/runtime/chat/agent_messages.py",
|
||||
"oclaw/runtime/system_prompt.py",
|
||||
"oclaw/runtime/memory_stage.py",
|
||||
"oclaw/runtime/project_context_prompt.py",
|
||||
"oclaw/runtime/chat/agent_errors.py",
|
||||
"oclaw/platform/llm/image_message_client.py",
|
||||
"oclaw/runtime/gateway.py",
|
||||
"runtime/chat/agent.py",
|
||||
"runtime/agents/network_ops_agent.py",
|
||||
"runtime/agents/factory.py",
|
||||
"runtime/chat/agent_messages.py",
|
||||
"runtime/system_prompt.py",
|
||||
"runtime/memory_stage.py",
|
||||
"runtime/project_context_prompt.py",
|
||||
"runtime/chat/agent_errors.py",
|
||||
"platform/llm/image_message_client.py",
|
||||
"runtime/gateway.py",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -33,8 +33,8 @@ def test_prompt_critical_paths_use_prompt_templates() -> None:
|
|||
|
||||
def test_prompt_markdown_frontmatter_keys_present() -> None:
|
||||
roots = [
|
||||
PROJECT_ROOT / "oclaw" / "prompts",
|
||||
PROJECT_ROOT / "oclaw" / "prompts_runtime",
|
||||
PROJECT_ROOT / "prompts",
|
||||
PROJECT_ROOT / "prompts_runtime",
|
||||
]
|
||||
for prompts_root in roots:
|
||||
for p in prompts_root.rglob("*.md"):
|
||||
|
|
|
|||
|
|
@ -12,18 +12,18 @@ def _read(rel: str) -> str:
|
|||
def test_no_legacy_inline_manager_prompts() -> None:
|
||||
# Manager orchestrator was removed; keep this test as a smoke-guard for any new inline
|
||||
# manager-style prompt leakage into the runtime entrypoints.
|
||||
content = _read("oclaw/runtime/gateway.py")
|
||||
content = _read("runtime/gateway.py")
|
||||
assert "【重要:只返回合法 JSON】" not in content
|
||||
assert "你是 AI 助手的路由(Routing)智能体" not in content
|
||||
|
||||
|
||||
def test_no_legacy_inline_specialist_prompts() -> None:
|
||||
content = _read("oclaw/runtime/agents/specialists.py")
|
||||
content = _read("runtime/agents/specialists.py")
|
||||
assert "【最高优先级执行规则(必须遵守)】" not in content
|
||||
assert "【职责范围】" not in content
|
||||
|
||||
|
||||
def test_no_legacy_inline_runtime_prompt() -> None:
|
||||
content = _read("oclaw/runtime/chat/agent.py")
|
||||
content = _read("runtime/chat/agent.py")
|
||||
assert "你是一个通用 AI 助手。" not in content
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from fastapi.testclient import TestClient
|
|||
from oclaw.interfaces.http.fastapi_app import create_app
|
||||
from oclaw.platform.config.paths import db_path
|
||||
from oclaw.platform.persistence.sqlite_store import SqliteStore
|
||||
from oclaw.runtime.tools.experts.workspace.fs_tools import list_files_tool
|
||||
from oclaw.runtime.tools.experts.workspace.fs_tools import list_files_tool, write_file_tool
|
||||
from oclaw.runtime.tools.experts.workspace.workspace_base import (
|
||||
access_from_env,
|
||||
build_workspace_path_access,
|
||||
|
|
@ -100,6 +100,21 @@ class WorkspacePathGuardTests(unittest.TestCase):
|
|||
p = resolve_workspace_path(str(f))
|
||||
self.assertEqual(p, f.resolve())
|
||||
|
||||
def test_write_file_relative_path_defaults_to_data_workspace_subdir(self) -> None:
|
||||
with mock.patch.dict(
|
||||
os.environ,
|
||||
{"OPS_WORKSPACE_ROOT": str(self.root), "OPS_WORKSPACE_EXTRA_ROOTS": "", "OPS_WORKSPACE_ALLOW_ANY_PATH": ""},
|
||||
clear=False,
|
||||
):
|
||||
clear_workspace_path_access_for_tests()
|
||||
spec = write_file_tool()
|
||||
with workspace_path_access_scope(None, None):
|
||||
r = spec.handler({"path": "generated.py", "content": "print('ok')\n", "mode": "overwrite"})
|
||||
self.assertTrue(r.get("ok"), r)
|
||||
expected = (self.root / "data" / self.root.name / "generated.py").resolve()
|
||||
self.assertEqual(str(expected), str(r.get("path")))
|
||||
self.assertTrue(expected.exists())
|
||||
|
||||
def test_per_user_extra_roots_from_db(self) -> None:
|
||||
f = self.extra / "u.txt"
|
||||
f.write_text("u", encoding="utf-8")
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ def _read(path: Path) -> str:
|
|||
|
||||
|
||||
def test_main_dynamic_placeholder_only_in_role_system() -> None:
|
||||
root = (PROJECT_ROOT / "oclaw" / "runtime" / "workspaces" / "main").resolve()
|
||||
root = (PROJECT_ROOT / "runtime" / "workspaces" / "main").resolve()
|
||||
role_system = _read(root / "ROLE_SYSTEM.md")
|
||||
soul = _read(root / "SOUL.md")
|
||||
assert "{{MANAGER_DYNAMIC_EXPERTS_HINT}}" in role_system
|
||||
|
|
@ -22,7 +22,7 @@ def test_main_dynamic_placeholder_only_in_role_system() -> None:
|
|||
|
||||
|
||||
def test_main_hard_routing_rule_only_in_role_system() -> None:
|
||||
root = (PROJECT_ROOT / "oclaw" / "runtime" / "workspaces" / "main").resolve()
|
||||
root = (PROJECT_ROOT / "runtime" / "workspaces" / "main").resolve()
|
||||
role_system = _read(root / "ROLE_SYSTEM.md")
|
||||
assert "回退 `generalist`" in role_system
|
||||
for name in ("AGENTS.md", "IDENTITY.md", "USER.md", "SOUL.md"):
|
||||
|
|
@ -32,7 +32,7 @@ def test_main_hard_routing_rule_only_in_role_system() -> None:
|
|||
|
||||
|
||||
def test_every_workspace_has_role_system() -> None:
|
||||
root = (PROJECT_ROOT / "oclaw" / "runtime" / "workspaces").resolve()
|
||||
root = (PROJECT_ROOT / "runtime" / "workspaces").resolve()
|
||||
for item in root.iterdir():
|
||||
if not item.is_dir():
|
||||
continue
|
||||
|
|
@ -42,7 +42,7 @@ def test_every_workspace_has_role_system() -> None:
|
|||
|
||||
|
||||
def test_workspace_legacy_prompt_files_removed() -> None:
|
||||
root = (PROJECT_ROOT / "oclaw" / "runtime" / "workspaces").resolve()
|
||||
root = (PROJECT_ROOT / "runtime" / "workspaces").resolve()
|
||||
legacy = ("AGENTS.md", "IDENTITY.md", "USER.md")
|
||||
for item in root.iterdir():
|
||||
if not item.is_dir():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue