重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 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

@ -3,6 +3,33 @@ from __future__ import annotations
import json
from typing import Any
from starlette.websockets import WebSocketState
def _ws_is_disconnected(conn: Any) -> bool:
ws = getattr(conn, "ws", None)
if ws is None:
return True
state = getattr(ws, "application_state", None)
if state == WebSocketState.DISCONNECTED:
return True
state = getattr(ws, "client_state", None)
if state == WebSocketState.DISCONNECTED:
return True
return False
async def _safe_send_text(conn: Any, text: str) -> None:
if _ws_is_disconnected(conn):
return
try:
await conn.ws.send_text(text)
except Exception as e:
msg = str(e or "")
if "Unexpected ASGI message 'websocket.send'" in msg or "response already completed" in msg:
return
raise
async def send_res(conn: Any, req_id: str, *, ok: bool, payload: Any | None = None, error: Any | None = None) -> None:
frame: dict[str, Any] = {"type": "res", "id": str(req_id or "invalid"), "ok": bool(ok)}
@ -10,15 +37,17 @@ async def send_res(conn: Any, req_id: str, *, ok: bool, payload: Any | None = No
frame["payload"] = payload
if error is not None:
frame["error"] = error
await conn.ws.send_text(json.dumps(frame, ensure_ascii=False))
await _safe_send_text(conn, json.dumps(frame, ensure_ascii=False))
async def send_event(conn: Any, event: str, payload: Any | None = None) -> None:
if _ws_is_disconnected(conn):
return
conn.seq += 1
frame: dict[str, Any] = {"type": "event", "event": str(event or "event"), "seq": int(conn.seq)}
if payload is not None:
frame["payload"] = payload
await conn.ws.send_text(json.dumps(frame, ensure_ascii=False))
await _safe_send_text(conn, json.dumps(frame, ensure_ascii=False))
async def emit_agent_event(conn: Any, *, run_id: str, stream: str, data: dict[str, Any], now_ms: int) -> None:

View file

@ -40,6 +40,12 @@ def build_gateway_context(
if not sid or not txt:
return False
rid = str(run_id or "").strip() or uuid.uuid4().hex
try:
existing = store.oclaw_run_get(run_id=rid)
if existing is not None:
return True
except Exception:
pass
with abort_lock:
active_run_session[rid] = sid
p = dict(params or {})
@ -61,7 +67,11 @@ def build_gateway_context(
"acp_child_run_id": str(p.get("acp_child_run_id") or ""),
"runId": rid,
}
asyncio.create_task(run_agent_turn("server_methods.chat.send", agent_params, session_id=sid, send_response=False))
async def _delayed() -> None:
await asyncio.sleep(0.05)
await run_agent_turn("server_methods.chat.send", agent_params, session_id=sid, send_response=False)
asyncio.create_task(_delayed())
return True
def _run_agent(params: dict[str, Any]) -> dict[str, Any]:
@ -89,7 +99,11 @@ def build_gateway_context(
}
with abort_lock:
active_run_session[str(run_id)] = str(session_id)
asyncio.create_task(run_agent_turn("server_methods.agent", agent_params, session_id=session_id, send_response=False))
async def _delayed() -> None:
await asyncio.sleep(0.05)
await run_agent_turn("server_methods.agent", agent_params, session_id=session_id, send_response=False)
asyncio.create_task(_delayed())
return {"runId": run_id, "status": "started"}
def _enqueue_session_send(session_key: str, message: str, params: dict[str, Any]) -> dict[str, Any]:

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio
import threading
import uuid
from datetime import datetime
from typing import Any, Callable
from oclaw.runtime.agents.factory import build_gateway_executor
@ -24,6 +25,68 @@ async def run_agent_turn_via_bridge(
now_ms: Callable[[], int],
error_shape: Callable[[str, str], dict[str, Any]],
) -> None:
def _to_ms(v: Any) -> int | None:
s = str(v or "").strip()
if not s:
return None
try:
if s.endswith("Z"):
s = s[:-1] + "+00:00"
return int(datetime.fromisoformat(s).timestamp() * 1000)
except Exception:
return None
def _delta(a: int | None, b: int | None) -> int | None:
if a is None or b is None:
return None
d = int(b - a)
return d if d >= 0 else None
def _compute_ttft(trace_rows: list[dict[str, Any]], *, accepted_ms: int | None) -> dict[str, Any]:
ws_accepted_ms = accepted_ms
gateway_received_ms = None
model_chat_start_ms = None
ws_first_token_ms = None
for row in trace_rows or []:
if not isinstance(row, dict):
continue
et = str(row.get("event_type") or "").strip()
payload = row.get("payload") if isinstance(row.get("payload"), dict) else {}
rts = _to_ms(row.get("timestamp"))
if et == "gateway_received":
gateway_received_ms = rts if rts is not None else gateway_received_ms
if ws_accepted_ms is None:
try:
vv = payload.get("ws_client_send_ms")
if vv is not None:
ws_accepted_ms = int(vv)
except Exception:
pass
elif et == "model_chat_start":
model_chat_start_ms = rts if rts is not None else model_chat_start_ms
elif et == "ws_first_token":
if ws_first_token_ms is None:
try:
vf = payload.get("ws_first_token_ms")
if vf is not None:
ws_first_token_ms = int(vf)
except Exception:
pass
if ws_first_token_ms is None:
ws_first_token_ms = rts if rts is not None else ws_first_token_ms
return {
"ws_accepted_ms": ws_accepted_ms,
"gateway_received_ms": gateway_received_ms,
"model_chat_start_ms": model_chat_start_ms,
"ws_first_token_ms": ws_first_token_ms,
"accepted_to_gateway_ms": _delta(ws_accepted_ms, gateway_received_ms),
"gateway_to_model_start_ms": _delta(gateway_received_ms, model_chat_start_ms),
"model_start_to_first_token_ms": _delta(model_chat_start_ms, ws_first_token_ms),
"gateway_to_first_token_ms": _delta(gateway_received_ms, ws_first_token_ms),
"accepted_to_first_token_ms": _delta(ws_accepted_ms, ws_first_token_ms),
}
accepted_ms = now_ms()
msg_text = str(p.get("message") or "").strip()
attachments = list(p.get("attachments") or [])
store = SqliteStore(db_path())
@ -66,6 +129,7 @@ async def run_agent_turn_via_bridge(
buf_lock = threading.Lock()
token_chunks: list[str] = []
first_token_ms_holder: dict[str, int | None] = {"ms": None}
marker_turn_count = 0
marker_session_count = 0
marker_keep_count = 0
@ -79,6 +143,8 @@ async def run_agent_turn_via_bridge(
def on_token(tok: str) -> None:
token_text = str(tok)
rid = run_id_holder.get("run_id") or ""
if first_token_ms_holder["ms"] is None and token_text:
first_token_ms_holder["ms"] = now_ms()
if rid:
with conn._abort_lock:
if rid in conn._aborted_run_ids:
@ -129,49 +195,58 @@ async def run_agent_turn_via_bridge(
else {},
"acp_parent_run_id": str(p.get("acp_parent_run_id") or ""),
"acp_child_run_id": str(p.get("acp_child_run_id") or ""),
"ws_accepted_ms": int(accepted_ms),
},
)
await conn.emit_agent_event(run_id=run_id_holder["run_id"], stream="lifecycle", data={"phase": "start", "status": "accepted"})
try:
relay_env = p.get("relay_share_envelope") if isinstance(p.get("relay_share_envelope"), dict) else {}
relay_pointers = 0
relay_turn = 0
relay_session = 0
relay_keep = 0
if isinstance(relay_env, dict):
ad = relay_env.get("attachments") if isinstance(relay_env.get("attachments"), dict) else {}
ps = ad.get("pointers") if isinstance(ad.get("pointers"), list) else []
relay_pointers = len(ps)
for item in ps:
if not isinstance(item, dict):
continue
ttl = str(item.get("ttl_policy") or "").strip().lower()
if ttl == "turn":
relay_turn += 1
elif ttl == "session":
relay_session += 1
elif ttl == "keep":
relay_keep += 1
marker_turn_count = int(relay_turn)
marker_session_count = int(relay_session)
marker_keep_count = int(relay_keep)
await conn.send_event(
"session.marker",
{
"runId": run_id_holder["run_id"],
"sessionKey": str(session_id),
"action": "ingress",
"relayPointerCount": int(relay_pointers),
"relayEnvelopePresent": bool(isinstance(relay_env, dict) and bool(relay_env)),
"relayEnvelopePointerCount": int(relay_pointers),
"relayTtlTurnCount": int(relay_turn),
"relayTtlSessionCount": int(relay_session),
"relayTtlKeepCount": int(relay_keep),
},
)
except Exception:
pass
if send_response:
try:
await conn.send_event(
"session.turn_started",
{"runId": run_id_holder["run_id"], "sessionKey": str(session_id), "acceptedAt": int(accepted_ms)},
)
except Exception:
pass
try:
relay_env = p.get("relay_share_envelope") if isinstance(p.get("relay_share_envelope"), dict) else {}
relay_pointers = 0
relay_turn = 0
relay_session = 0
relay_keep = 0
if isinstance(relay_env, dict):
ad = relay_env.get("attachments") if isinstance(relay_env.get("attachments"), dict) else {}
ps = ad.get("pointers") if isinstance(ad.get("pointers"), list) else []
relay_pointers = len(ps)
for item in ps:
if not isinstance(item, dict):
continue
ttl = str(item.get("ttl_policy") or "").strip().lower()
if ttl == "turn":
relay_turn += 1
elif ttl == "session":
relay_session += 1
elif ttl == "keep":
relay_keep += 1
marker_turn_count = int(relay_turn)
marker_session_count = int(relay_session)
marker_keep_count = int(relay_keep)
await conn.send_event(
"session.marker",
{
"runId": run_id_holder["run_id"],
"sessionKey": str(session_id),
"action": "ingress",
"relayPointerCount": int(relay_pointers),
"relayEnvelopePresent": bool(isinstance(relay_env, dict) and bool(relay_env)),
"relayEnvelopePointerCount": int(relay_pointers),
"relayTtlTurnCount": int(relay_turn),
"relayTtlSessionCount": int(relay_session),
"relayTtlKeepCount": int(relay_keep),
},
)
except Exception:
pass
def _run_turn_sync() -> Any:
return gw.handle_turn(
@ -193,7 +268,8 @@ async def run_agent_turn_via_bridge(
await conn.emit_agent_event(
run_id=run_id_holder.get("run_id") or "",
stream="lifecycle",
data={"phase": "error", "status": "error", "error": str(exc or "agent_failed")},
# Always emit a terminal lifecycle event even on failure.
data={"phase": "end", "status": "error", "error": str(exc or "agent_failed")},
)
await conn.emit_chat_event(run_id=run_id_holder.get("run_id") or "", state="error", error=str(exc or "agent_failed"))
try:
@ -220,6 +296,29 @@ async def run_agent_turn_via_bridge(
conn._aborted_run_ids.discard(_rid0)
rid = str(getattr(result, "run_id", "") or run_id_holder.get("run_id") or "")
ttft_payload: dict[str, Any] | None = None
try:
ft = first_token_ms_holder.get("ms")
tid = str(getattr(result, "trace_id", "") or "")
if ft is not None and tid:
store.add_trace_event(
session_id=str(session_id),
trace_id=tid,
span_id=uuid.uuid4().hex,
parent_span_id=None,
event_type="ws_first_token",
payload={"run_id": str(rid), "ws_first_token_ms": int(ft), "ws_accepted_ms": int(accepted_ms)},
)
except Exception:
pass
try:
show_ttft = str(store.get_setting("AIA_CHAT_SHOW_TTFT_DEBUG") or "").strip().lower() in {"1", "true", "yes", "on"}
tid = str(getattr(result, "trace_id", "") or "")
if show_ttft and tid:
rows = store.list_trace_events_for_trace(session_id=str(session_id), trace_id=tid, limit=400)
ttft_payload = _compute_ttft(rows, accepted_ms=int(accepted_ms))
except Exception:
ttft_payload = None
with conn._abort_lock:
if rid and rid in conn._aborted_run_ids:
try:
@ -262,6 +361,7 @@ async def run_agent_turn_via_bridge(
"relayTtlTurnCount": int(getattr(result, "relay_ttl_turn_count", 0) or 0),
"relayTtlSessionCount": int(getattr(result, "relay_ttl_session_count", 0) or 0),
"relayTtlKeepCount": int(getattr(result, "relay_ttl_keep_count", 0) or 0),
"ttft": ttft_payload if isinstance(ttft_payload, dict) else None,
},
)
await conn.emit_agent_event(