mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-11 04:10:44 +08:00
将 workspace 缓存失效机制切换到 revision token,并补齐回归验证。
通过 revision 驱动 prompt/prebuild 缓存重建,避免请求路径上的目录签名扫描成本,同时新增缓存失效与路径约定相关测试,确保行为稳定。 Made-with: Cursor
This commit is contained in:
parent
768f175c3e
commit
87ff00aa06
4 changed files with 169 additions and 19 deletions
|
|
@ -127,3 +127,63 @@ def test_executor_static_prompt_cache_invalidates_on_settings_change(monkeypatch
|
|||
skill_binding_role="generalist",
|
||||
)
|
||||
assert calls["n"] == 2
|
||||
|
||||
|
||||
def test_executor_static_prompt_cache_invalidates_on_workspace_revision_change(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
from oclaw.runtime import system_prompt as sp
|
||||
|
||||
class DummyStore:
|
||||
def get_setting(self, key: str) -> str:
|
||||
_ = key
|
||||
return ""
|
||||
|
||||
class DummyReg:
|
||||
pass
|
||||
|
||||
calls = {"n": 0}
|
||||
token = {"v": 1}
|
||||
|
||||
def _skills_block(**kwargs) -> str:
|
||||
_ = kwargs
|
||||
calls["n"] += 1
|
||||
return "skills-block"
|
||||
|
||||
monkeypatch.setattr(sp, "expert_workspace_signature_token", lambda: ("revision", token["v"]))
|
||||
monkeypatch.setattr(sp, "build_project_context_block", lambda **kwargs: "")
|
||||
monkeypatch.setattr(sp, "build_skills_catalog_block", _skills_block)
|
||||
monkeypatch.setattr(
|
||||
sp,
|
||||
"render_runtime_prompt",
|
||||
lambda prompt_id, variables, strict: f"{prompt_id}\n{variables.get('skills_catalog') or ''}",
|
||||
)
|
||||
|
||||
store = DummyStore()
|
||||
reg = DummyReg()
|
||||
_ = sp.get_executor_prompt_static(
|
||||
store=store,
|
||||
tools=reg, # type: ignore[arg-type]
|
||||
base_url="",
|
||||
base_system="base",
|
||||
workspace_dir=None,
|
||||
skill_binding_role="generalist",
|
||||
)
|
||||
_ = sp.get_executor_prompt_static(
|
||||
store=store,
|
||||
tools=reg, # type: ignore[arg-type]
|
||||
base_url="",
|
||||
base_system="base",
|
||||
workspace_dir=None,
|
||||
skill_binding_role="generalist",
|
||||
)
|
||||
assert calls["n"] == 1
|
||||
|
||||
token["v"] = 2
|
||||
_ = sp.get_executor_prompt_static(
|
||||
store=store,
|
||||
tools=reg, # type: ignore[arg-type]
|
||||
base_url="",
|
||||
base_system="base",
|
||||
workspace_dir=None,
|
||||
skill_binding_role="generalist",
|
||||
)
|
||||
assert calls["n"] == 2
|
||||
|
|
|
|||
|
|
@ -101,3 +101,45 @@ def test_runtime_prewarm_prompts_snapshot_returns_roles(monkeypatch) -> None:
|
|||
assert "manager_system_prompt" not in prompts["manager"]
|
||||
assert "executor_system_prompt" not in prompts["manager"]
|
||||
assert "manager_user_scaffold" not in prompts["manager"]
|
||||
|
||||
|
||||
def test_manager_prompt_prebuild_cache_invalidates_on_workspace_revision_change(monkeypatch) -> None:
|
||||
token = {"v": 1}
|
||||
calls = {"ctx": 0}
|
||||
|
||||
monkeypatch.setattr(pp, "discover_specialist_ids", lambda: ("generalist", "ops"))
|
||||
monkeypatch.setattr(
|
||||
pp,
|
||||
"list_experts",
|
||||
lambda: [{"id": "generalist", "files": {"ROLE_SYSTEM.md": "General specialist"}}],
|
||||
)
|
||||
monkeypatch.setattr(pp, "expert_workspace_signature_token", lambda: ("revision", token["v"]))
|
||||
|
||||
def _ctx(_role: str, template_vars: dict[str, Any] | None = None) -> str:
|
||||
calls["ctx"] += 1
|
||||
return f"CTX\n{str((template_vars or {}).get('MANAGER_DYNAMIC_EXPERTS_HINT') or '')}"
|
||||
|
||||
monkeypatch.setattr(pp, "build_role_system_context", _ctx)
|
||||
|
||||
_ = pp.get_manager_prompt_prebuild(
|
||||
store=_DummyStore(),
|
||||
registry=object(),
|
||||
base_url="",
|
||||
memory_enabled=True,
|
||||
)
|
||||
_ = pp.get_manager_prompt_prebuild(
|
||||
store=_DummyStore(),
|
||||
registry=object(),
|
||||
base_url="",
|
||||
memory_enabled=True,
|
||||
)
|
||||
assert calls["ctx"] == 1
|
||||
|
||||
token["v"] = 2
|
||||
_ = pp.get_manager_prompt_prebuild(
|
||||
store=_DummyStore(),
|
||||
registry=object(),
|
||||
base_url="",
|
||||
memory_enabled=True,
|
||||
)
|
||||
assert calls["ctx"] == 2
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ def _set_project_root(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|||
|
||||
def test_list_experts_reads_runtime_workspaces(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
_set_project_root(monkeypatch, tmp_path)
|
||||
ws = tmp_path / "oclaw" / "runtime" / "workspaces" / "qa"
|
||||
ws = tmp_path / "runtime" / "workspaces" / "qa"
|
||||
ws.mkdir(parents=True, exist_ok=True)
|
||||
(ws / "SOUL.md").write_text("qa soul", encoding="utf-8")
|
||||
rows = experts_mod.list_experts()
|
||||
|
|
@ -24,7 +24,7 @@ def test_list_experts_reads_runtime_workspaces(monkeypatch: pytest.MonkeyPatch,
|
|||
|
||||
def test_create_expert_requires_soul(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
_set_project_root(monkeypatch, tmp_path)
|
||||
with pytest.raises(ValueError, match="soul_required"):
|
||||
with pytest.raises(ValueError, match="soul_or_role_system_required"):
|
||||
experts_mod.create_expert(expert_id="qa", files={})
|
||||
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ def test_create_update_delete_expert_flow(monkeypatch: pytest.MonkeyPatch, tmp_p
|
|||
|
||||
def test_delete_builtin_expert_is_protected(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
_set_project_root(monkeypatch, tmp_path)
|
||||
ws = tmp_path / "oclaw" / "runtime" / "workspaces" / "main"
|
||||
ws = tmp_path / "runtime" / "workspaces" / "main"
|
||||
ws.mkdir(parents=True, exist_ok=True)
|
||||
(ws / "SOUL.md").write_text("main soul", encoding="utf-8")
|
||||
with pytest.raises(ValueError, match="builtin_expert_protected"):
|
||||
|
|
@ -62,7 +62,7 @@ def test_delete_builtin_expert_is_protected(monkeypatch: pytest.MonkeyPatch, tmp
|
|||
|
||||
def test_discover_specialists_reads_workspaces(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
_set_project_root(monkeypatch, tmp_path)
|
||||
ws = tmp_path / "oclaw" / "runtime" / "workspaces"
|
||||
ws = tmp_path / "runtime" / "workspaces"
|
||||
(ws / "main").mkdir(parents=True, exist_ok=True)
|
||||
(ws / "generalist").mkdir(parents=True, exist_ok=True)
|
||||
(ws / "generalist" / "ROLE_SYSTEM.md").write_text("g", encoding="utf-8")
|
||||
|
|
@ -77,7 +77,7 @@ def test_discover_specialists_reads_workspaces(monkeypatch: pytest.MonkeyPatch,
|
|||
|
||||
def test_build_expert_catalog_block_contains_dynamic_experts(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
_set_project_root(monkeypatch, tmp_path)
|
||||
ws = tmp_path / "oclaw" / "runtime" / "workspaces"
|
||||
ws = tmp_path / "runtime" / "workspaces"
|
||||
(ws / "main").mkdir(parents=True, exist_ok=True)
|
||||
(ws / "qa").mkdir(parents=True, exist_ok=True)
|
||||
(ws / "qa" / "SOUL.md").write_text("QA expert soul", encoding="utf-8")
|
||||
|
|
@ -90,7 +90,7 @@ def test_build_expert_catalog_block_contains_dynamic_experts(monkeypatch: pytest
|
|||
|
||||
def test_discover_specialists_cache_invalidates_after_create(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
_set_project_root(monkeypatch, tmp_path)
|
||||
ws = tmp_path / "oclaw" / "runtime" / "workspaces"
|
||||
ws = tmp_path / "runtime" / "workspaces"
|
||||
(ws / "main").mkdir(parents=True, exist_ok=True)
|
||||
(ws / "generalist").mkdir(parents=True, exist_ok=True)
|
||||
(ws / "generalist" / "SOUL.md").write_text("g", encoding="utf-8")
|
||||
|
|
@ -99,3 +99,21 @@ def test_discover_specialists_cache_invalidates_after_create(monkeypatch: pytest
|
|||
experts_mod.create_expert(expert_id="qa", files={"SOUL.md": "qa soul"})
|
||||
after = set(discover_specialist_ids())
|
||||
assert "qa" in after
|
||||
|
||||
|
||||
def test_workspace_revision_token_changes_after_mutation(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
_set_project_root(monkeypatch, tmp_path)
|
||||
before = experts_mod.expert_workspace_signature_token()
|
||||
experts_mod.create_expert(expert_id="qa", files={"SOUL.md": "qa"})
|
||||
after = experts_mod.expert_workspace_signature_token()
|
||||
assert before != after
|
||||
|
||||
|
||||
def test_workspace_signature_token_no_longer_scans_filesystem(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def _boom():
|
||||
raise AssertionError("should_not_scan_workspace_signature")
|
||||
|
||||
monkeypatch.setattr(experts_mod, "_workspace_signature", _boom)
|
||||
token = experts_mod.expert_workspace_signature_token()
|
||||
assert isinstance(token, tuple)
|
||||
assert token and str(token[0]) == "revision"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue