mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-13 01:10:45 +08:00
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:
parent
4dbb834c95
commit
c33ca303a2
4 changed files with 84 additions and 2 deletions
|
|
@ -1643,10 +1643,27 @@ async function renderStack() {
|
||||||
class: "btn btn--primary",
|
class: "btn btn--primary",
|
||||||
text: currentLang === "zh" ? "立即预热" : "Run prewarm now",
|
text: currentLang === "zh" ? "立即预热" : "Run prewarm now",
|
||||||
onclick: async () => {
|
onclick: async () => {
|
||||||
|
prewarmStatus.textContent = currentLang === "zh" ? "[预热] 提交中…" : "[prewarm] submitting…";
|
||||||
const resp = await apiPost("/admin/api/runtime/prewarm", { mode: "async", reason: "admin_manual" });
|
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 = "";
|
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 = [
|
const prewarmSummary = [
|
||||||
|
|
|
||||||
|
|
@ -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.agent_context import build_role_system_context
|
||||||
from oclaw.runtime.agents.specialists import discover_specialist_ids
|
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.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.system_prompt import get_executor_prompt_static, warm_executor_prompt_cache
|
||||||
from oclaw.runtime.tools.catalog import default_registry
|
from oclaw.runtime.tools.catalog import default_registry
|
||||||
from oclaw.runtime.workspaces.experts import expert_workspace_signature_token, list_experts
|
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_DISABLED_NAMES",
|
||||||
"AIA_SKILL_ROLE_BINDING_ENABLED",
|
"AIA_SKILL_ROLE_BINDING_ENABLED",
|
||||||
"AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT",
|
"AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT",
|
||||||
|
SKILL_ROLE_BINDING_KEY,
|
||||||
"AIA_CHAT_SPECIALIST_FLAGS_JSON",
|
"AIA_CHAT_SPECIALIST_FLAGS_JSON",
|
||||||
)
|
)
|
||||||
parts: list[str] = []
|
parts: list[str] = []
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from typing import Any
|
||||||
from oclaw.platform.config.paths import PROJECT_ROOT
|
from oclaw.platform.config.paths import PROJECT_ROOT
|
||||||
from oclaw.runtime.memory_stage import render_memory_context_block
|
from oclaw.runtime.memory_stage import render_memory_context_block
|
||||||
from oclaw.runtime.project_context_prompt import build_project_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_prompt import build_skills_catalog_block
|
||||||
from oclaw.runtime.skills_workspace_lane import (
|
from oclaw.runtime.skills_workspace_lane import (
|
||||||
fs_safe_workspace_lane_segment,
|
fs_safe_workspace_lane_segment,
|
||||||
|
|
@ -83,6 +84,7 @@ def _executor_prompt_settings_signature(store: Any) -> tuple[str, ...]:
|
||||||
"AIA_SKILL_DISABLED_NAMES",
|
"AIA_SKILL_DISABLED_NAMES",
|
||||||
"AIA_SKILL_ROLE_BINDING_ENABLED",
|
"AIA_SKILL_ROLE_BINDING_ENABLED",
|
||||||
"AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT",
|
"AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT",
|
||||||
|
SKILL_ROLE_BINDING_KEY,
|
||||||
"AIA_PROJECT_CONTEXT_MAX_FILE_CHARS",
|
"AIA_PROJECT_CONTEXT_MAX_FILE_CHARS",
|
||||||
"AIA_PROJECT_CONTEXT_MAX_TOTAL_CHARS",
|
"AIA_PROJECT_CONTEXT_MAX_TOTAL_CHARS",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,67 @@ def test_executor_static_prompt_cache_invalidates_on_settings_change(monkeypatch
|
||||||
assert calls["n"] == 2
|
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:
|
def test_executor_static_prompt_cache_invalidates_on_workspace_revision_change(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
from oclaw.runtime import system_prompt as sp
|
from oclaw.runtime import system_prompt as sp
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue