Fix prewarm prompt preview staleness (cache + Admin wait)

Include skill_role_binding store JSON in executor and manager prompt cache signatures so skill catalog updates after binding edits without restart. Admin async prewarm now polls prewarm/status until not running before router() so parallel prompts fetch matches completed warm.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-05-11 16:24:33 +08:00
parent 4dbb834c95
commit c33ca303a2
4 changed files with 84 additions and 2 deletions

View file

@ -1643,10 +1643,27 @@ async function renderStack() {
class: "btn btn--primary",
text: currentLang === "zh" ? "立即预热" : "Run prewarm now",
onclick: async () => {
prewarmStatus.textContent = currentLang === "zh" ? "[预热] 提交中…" : "[prewarm] submitting…";
const resp = await apiPost("/admin/api/runtime/prewarm", { mode: "async", reason: "admin_manual" });
prewarmStatus.textContent = `[prewarm] accepted=${Boolean(resp && resp.accepted)}`;
if (!(resp && resp.accepted)) {
prewarmStatus.textContent = `[prewarm] accepted=${Boolean(resp && resp.accepted)}`;
return;
}
prewarmStatus.textContent =
currentLang === "zh"
? "[预热] 后台执行中,完成后自动刷新本页数据(含提示词)…"
: "[prewarm] running in background; refreshing this page when finished…";
runtimePrewarmReminder = "";
router();
const maxWaitMs = 120000;
const stepMs = 400;
let waited = 0;
while (waited < maxWaitMs) {
await new Promise((r) => setTimeout(r, stepMs));
waited += stepMs;
const st = await apiGet("/admin/api/runtime/prewarm/status");
if (!(st && st.running)) break;
}
await router();
},
});
const prewarmSummary = [

View file

@ -9,6 +9,7 @@ from oclaw.platform.persistence.sqlite_store import SqliteStore
from oclaw.runtime.agent_context import build_role_system_context
from oclaw.runtime.agents.specialists import discover_specialist_ids
from oclaw.runtime.direct_loop import tool_wire_freeze_status, warm_tool_wire_cache
from oclaw.runtime.skill_role_binding import SKILL_ROLE_BINDING_KEY
from oclaw.runtime.system_prompt import get_executor_prompt_static, warm_executor_prompt_cache
from oclaw.runtime.tools.catalog import default_registry
from oclaw.runtime.workspaces.experts import expert_workspace_signature_token, list_experts
@ -36,6 +37,7 @@ def _manager_settings_signature(store: Any) -> tuple[str, ...]:
"AIA_SKILL_DISABLED_NAMES",
"AIA_SKILL_ROLE_BINDING_ENABLED",
"AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT",
SKILL_ROLE_BINDING_KEY,
"AIA_CHAT_SPECIALIST_FLAGS_JSON",
)
parts: list[str] = []

View file

@ -8,6 +8,7 @@ from typing import Any
from oclaw.platform.config.paths import PROJECT_ROOT
from oclaw.runtime.memory_stage import render_memory_context_block
from oclaw.runtime.project_context_prompt import build_project_context_block
from oclaw.runtime.skill_role_binding import SKILL_ROLE_BINDING_KEY
from oclaw.runtime.skills_prompt import build_skills_catalog_block
from oclaw.runtime.skills_workspace_lane import (
fs_safe_workspace_lane_segment,
@ -83,6 +84,7 @@ def _executor_prompt_settings_signature(store: Any) -> tuple[str, ...]:
"AIA_SKILL_DISABLED_NAMES",
"AIA_SKILL_ROLE_BINDING_ENABLED",
"AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT",
SKILL_ROLE_BINDING_KEY,
"AIA_PROJECT_CONTEXT_MAX_FILE_CHARS",
"AIA_PROJECT_CONTEXT_MAX_TOTAL_CHARS",
)

View file

@ -129,6 +129,67 @@ def test_executor_static_prompt_cache_invalidates_on_settings_change(monkeypatch
assert calls["n"] == 2
def test_executor_static_prompt_cache_invalidates_on_skill_role_binding_change(monkeypatch: pytest.MonkeyPatch) -> None:
from oclaw.runtime import system_prompt as sp
class DummyStore:
def __init__(self) -> None:
self.values: dict[str, str] = {"skill_role_binding": "{}"}
def get_setting(self, key: str) -> str:
return str(self.values.get(key, ""))
class DummyReg:
pass
calls = {"n": 0}
def _skills_block(**kwargs) -> str:
_ = kwargs
calls["n"] += 1
return "skills-block"
monkeypatch.setattr(sp, "expert_workspace_signature_token", lambda: ("sig",))
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
store.values["skill_role_binding"] = '{"generalist": ["alpha-skill"]}'
_ = 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
def test_executor_static_prompt_cache_invalidates_on_workspace_revision_change(monkeypatch: pytest.MonkeyPatch) -> None:
from oclaw.runtime import system_prompt as sp