重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。

同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。

Made-with: Cursor
This commit is contained in:
oliver 2026-04-26 08:34:33 +08:00
parent 4a23b715a2
commit dbbe3add6a
14438 changed files with 2693620 additions and 2546 deletions

View file

@ -211,14 +211,6 @@ class Agent:
tenant_id = tenant_id or str(owner.get("tenant_id") or "")
user_id = user_id or str(owner.get("user_id") or "")
session = self.store.get_session(session_id)
if session and session.title in ("新会话", "New Chat"):
title = user_text.strip().replace("\n", " ")
if not title and attachments:
title = str(attachments[0].get("name") or "New Chat")
if title:
self.store.rename_session(session_id, title[:SESSION_TITLE_MAX_LEN])
self._emit_progress(
on_progress,
"Received. Working on your request…",
@ -267,11 +259,14 @@ class Agent:
from oclaw.runtime.chat.agent_messages import build_llm_messages
msgs = self.store.get_messages(session_id=session_id, limit=self.config.max_messages)
trunc_raw = str(self.store.get_setting("AIA_TOOL_CONTEXT_TRUNCATE_ENABLED") or "").strip().lower()
tool_context_truncate_enabled = trunc_raw not in ("0", "false", "no", "off")
return build_llm_messages(
store_messages=msgs,
system_prompt=self._compose_system_prompt(),
model=self.model,
lang=self.lang,
tool_context_truncate_enabled=tool_context_truncate_enabled,
)

View file

@ -21,6 +21,7 @@ from oclaw.runtime.relay_pointer import parse_pointer_uri
logger = logging.getLogger(__name__)
_THINK_BLOCK_RE = re.compile(r"<think>\s*(.*?)\s*</think>\s*", flags=re.IGNORECASE | re.DOTALL)
_TOOL_CONTEXT_RESULT_MAX_CHARS = 50
def _replay_recent_tool_rounds() -> int:
@ -175,12 +176,23 @@ def _summarize_unpaired_tool_content(raw: str, *, cap: int) -> str:
return s
def _truncate_tool_context(text: str, *, lang: str) -> str:
s = str(text or "").strip()
if not s:
return s
if len(s) <= _TOOL_CONTEXT_RESULT_MAX_CHARS:
return s
tip = "…(详情请重新阅读)" if not str(lang or "").startswith("en") else "... (details truncated, please re-read)"
return s[:_TOOL_CONTEXT_RESULT_MAX_CHARS] + tip
def build_llm_messages(
*,
store_messages: list[Any],
system_prompt: str,
model: ChatModel,
lang: str,
tool_context_truncate_enabled: bool = True,
) -> list[dict[str, Any]]:
"""把 DB 中的消息序列转换为 LLM messages。"""
out: list[dict[str, Any]] = [{"role": "system", "content": (system_prompt or "").strip()}]
@ -427,6 +439,8 @@ def build_llm_messages(
r0 = getattr(m, "content", "") or ""
cap0 = tool_llm_message_max_chars()
pretty = _summarize_unpaired_tool_content(r0, cap=cap0)
if tool_context_truncate_enabled:
pretty = _truncate_tool_context(pretty, lang=lang)
out.append(
{
"role": "assistant",
@ -457,6 +471,10 @@ def build_llm_messages(
tool_content_out = raw_tc_content[: max(1, cap - 80)] + "\n...<truncated>"
except Exception:
tool_content_out = raw_tc_content[: max(1, cap - 80)] + "\n...<truncated>"
if tool_context_truncate_enabled:
# Preserve explicit guard markers from upstream context guards.
if "_tool_result_guarded" not in str(tool_content_out or ""):
tool_content_out = _truncate_tool_context(tool_content_out, lang=lang)
tool_row: dict[str, Any] = {
"role": "tool",
"tool_call_id": tool_call_id,
@ -476,6 +494,8 @@ def build_llm_messages(
r = getattr(m, "content", "") or ""
cap2 = tool_llm_message_max_chars()
pretty2 = _summarize_unpaired_tool_content(r, cap=cap2)
if tool_context_truncate_enabled:
pretty2 = _truncate_tool_context(pretty2, lang=lang)
out.append(
{
"role": "assistant",

View file

@ -23,6 +23,7 @@ from oclaw.runtime.tools.tool_validation import validate_tool_arguments
from oclaw.runtime.tools.experts.workspace.workspace_base import workspace_path_access_scope
logger = logging.getLogger(__name__)
_tool_exec_log = logging.getLogger("oclaw.tool_exec")
_TOOL_ERROR_MAP = {
"tool_timeout_or_failed": "tool_timeout_or_failed",
@ -275,13 +276,43 @@ class ToolExecutor:
ex.shutdown(wait=False)
else:
result = _call()
return normalize_tool_result(result), int((time.perf_counter() - t0) * 1000)
out = normalize_tool_result(result)
dur_ms = int((time.perf_counter() - t0) * 1000)
try:
_tool_exec_log.info(
"tool_exec name=%s ok=%s dur_ms=%s session=%s specialist=%s tool_call_id=%s error_code=%s",
str(tc.name or ""),
str(bool(out.get("ok"))),
str(dur_ms),
str(ctx.session_id or ""),
str(ctx.specialist or ""),
str(getattr(tc, "id", "") or ""),
str(out.get("error_code") or ""),
)
except Exception:
pass
return out, dur_ms
except Exception as e:
if ctx.lang.startswith("en"):
err = {"ok": False, "error_code": "tool_execution_error", "error": f"Tool execution error: {type(e).__name__}: {e}"}
else:
err = {"ok": False, "error_code": "tool_execution_error", "error": f"工具执行异常: {type(e).__name__}: {e}"}
return normalize_tool_result(err), int((time.perf_counter() - t0) * 1000)
out = normalize_tool_result(err)
dur_ms = int((time.perf_counter() - t0) * 1000)
try:
_tool_exec_log.info(
"tool_exec name=%s ok=%s dur_ms=%s session=%s specialist=%s tool_call_id=%s error_code=%s",
str(tc.name or ""),
str(bool(out.get("ok"))),
str(dur_ms),
str(ctx.session_id or ""),
str(ctx.specialist or ""),
str(getattr(tc, "id", "") or ""),
str(out.get("error_code") or ""),
)
except Exception:
pass
return out, dur_ms
@staticmethod
def _json_dumps_safe(obj: Any) -> str: