from __future__ import annotations import json from types import SimpleNamespace import pytest from svc.llm.chat_models import LLMResponse from runtime.gateway import OclawGateway from runtime.tools.base import ToolRegistry, ToolSpec from runtime.types import StandardMessage def test_gateway_async_trace_payload_has_pipeline_and_oc_stage(monkeypatch: pytest.MonkeyPatch) -> None: events: list[dict] = [] class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **kwargs: object) -> None: events.append(dict(kwargs)) def oclaw_task_create(self, **_kwargs: object) -> object: class T: id = "task-1" task_type = "async_turn" status = "queued" return T() monkeypatch.setattr("runtime.gateway.ensure_worker_started", lambda store: "worker-1") gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-1", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="please send to channel after summarize", attachments=[], metadata={}, ) out = gw.handle_turn(msg=msg, lang="zh", executor=object()) assert out.task_id == "task-1" sent = [e for e in events if e.get("event_type") == "response_sent"] assert sent payload = sent[-1].get("payload") or {} assert payload.get("pipeline") == "oclaw_gateway" assert payload.get("oc_stage") == "response" assert "elapsed_ms_since_gateway_start" in payload def test_gateway_received_trace_includes_relay_pointer_stats(monkeypatch: pytest.MonkeyPatch) -> None: events: list[dict] = [] class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **kwargs: object) -> None: events.append(dict(kwargs)) def oclaw_task_create(self, **_kwargs: object) -> object: class T: id = "task-2" task_type = "async_turn" status = "queued" return T() monkeypatch.setattr("runtime.gateway.ensure_worker_started", lambda store: "worker-1") gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-2", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="async with relay", attachments=[{"type": "relay_pointer", "pointer_uri": "relay://attachments/scope_1/abcdef123456"}], metadata={ "relay_share_envelope": { "schema_version": "v1", "attachments": {"pointers": [{"pointer_uri": "relay://attachments/scope_1/abcdef123456"}]}, } }, ) _ = gw.handle_turn(msg=msg, lang="zh", executor=object()) rec = [e for e in events if e.get("event_type") == "gateway_received"] assert rec payload = rec[-1].get("payload") or {} assert payload.get("relay_pointer_count") == 1 assert payload.get("relay_envelope_present") is True assert payload.get("relay_envelope_pointer_count") == 1 def test_gateway_async_task_payload_preserves_relay_envelope(monkeypatch: pytest.MonkeyPatch) -> None: captured: dict = {} class Store: def get_setting(self, _k: str) -> str: return "" def set_setting(self, _k: str, _v: str) -> None: return None def add_trace_event(self, **_kwargs: object) -> None: return None def oclaw_task_create(self, **kwargs: object) -> object: captured.update(kwargs) class T: id = "task-3" task_type = "async_turn" status = "queued" return T() monkeypatch.setattr("runtime.gateway.ensure_worker_started", lambda store: "worker-1") gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-3", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="please summarize and schedule follow-up tasks with attachment context " * 3, attachments=[{"type": "relay_pointer", "pointer_uri": "relay://attachments/scope_1/abcdef123456"}], metadata={ "relay_share_envelope": { "schema_version": "v1", "attachments": {"pointers": [{"pointer_uri": "relay://attachments/scope_1/abcdef123456"}]}, } }, ) _ = gw.handle_turn(msg=msg, lang="zh", executor=object()) payload = captured.get("payload") or {} assert isinstance(payload, dict) assert payload.get("relay_pointer_count") == 1 env = payload.get("relay_share_envelope") or {} assert isinstance(env, dict) assert env.get("schema_version") == "v1" def test_gateway_async_task_payload_preserves_acp_ids(monkeypatch: pytest.MonkeyPatch) -> None: captured: dict = {} class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **_kwargs: object) -> None: return None def oclaw_task_create(self, **kwargs: object) -> object: captured.update(kwargs) class T: id = "task-4" task_type = "async_turn" status = "queued" return T() monkeypatch.setattr("runtime.gateway.ensure_worker_started", lambda store: "worker-1") gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-4", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="please summarize and schedule follow-up tasks with attachment context " * 3, attachments=[{"type": "relay_pointer", "pointer_uri": "relay://attachments/scope_1/abcdef123456"}], metadata={"acp_parent_run_id": "parent-1", "acp_child_run_id": "child-1"}, ) _ = gw.handle_turn(msg=msg, lang="zh", executor=object()) payload = captured.get("payload") or {} assert payload.get("acp_parent_run_id") == "parent-1" assert payload.get("acp_child_run_id") == "child-1" def test_gateway_expert_mode_uses_requested_specialist() -> None: class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **_kwargs: object) -> None: return None class _Exec: model = None tools = None chosen: dict[str, str] = {} def _factory(sid: str) -> object: chosen["sid"] = sid return _Exec() gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-e1", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="hello", attachments=[], metadata={"interaction_mode": "expert", "selected_specialist": "ops"}, ) out = gw.handle_turn(msg=msg, lang="en", executor=_Exec(), specialist_executor_factory=_factory) assert out.interaction_mode == "expert" assert out.selected_specialist == "ops" assert chosen.get("sid") == "ops" def test_gateway_expert_plan_execution_mode_runs_v2_with_plan_prompt(monkeypatch: pytest.MonkeyPatch) -> None: class Store: def get_setting(self, k: str) -> str: if str(k or "") == "AIA_EXPERT_PLAN_AGENT_V2_ENABLED": return "1" return "" def set_setting(self, _k: str, _v: str) -> None: return None def add_trace_event(self, **_kwargs: object) -> None: return None def add_trace_events_batch(self, _rows: list[dict[str, object]]) -> None: return None def set_setting(self, _k: str, _v: str) -> None: return None class _Exec: model = object() tools = object() system_prompt = "base-system" captured: dict[str, object] = {} def _run_agent_core_ok(**kwargs: object) -> object: data = kwargs.get("data") captured["system_prompt"] = str(getattr(data, "system_prompt", "") or "") return SimpleNamespace(outcome=SimpleNamespace(final_text="plan_llm_reply", turn_uuid="turn-1")) monkeypatch.setattr("runtime.gateway.run_agent_core", _run_agent_core_ok) gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-plan-1", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="先给我一个执行计划", attachments=[], metadata={"interaction_mode": "expert", "selected_specialist": "generalist", "execution_mode": "plan"}, ) out = gw.handle_turn(msg=msg, lang="zh", executor=_Exec()) assert out.interaction_mode == "expert" assert out.dispatch_reason == "expert_direct" assert str(out.reply_text or "") == "plan_llm_reply" prompt_text = str(captured.get("system_prompt") or "") assert ("plan 模式" in prompt_text) or ("Plan mode is active" in prompt_text) assert ("计划工作流" in prompt_text) or ("Plan Workflow" in prompt_text) def test_gateway_expert_plan_mode_filters_non_readonly_tools(monkeypatch: pytest.MonkeyPatch) -> None: class Store: def get_setting(self, k: str) -> str: if str(k or "") == "AIA_EXPERT_PLAN_AGENT_V2_ENABLED": return "1" return "" def set_setting(self, _k: str, _v: str) -> None: return None def add_trace_event(self, **_kwargs: object) -> None: return None def add_trace_events_batch(self, _rows: list[dict[str, object]]) -> None: return None def _mk_tool(name: str, read_only: bool) -> ToolSpec: return ToolSpec( name=name, description=name, parameters={"type": "object", "properties": {}, "additionalProperties": True}, handler=lambda args: {"ok": True, "args": args}, read_only=read_only, ) class _Exec: model = object() system_prompt = "base-system" tools = ToolRegistry([_mk_tool("read_file", True), _mk_tool("edit_file", False)]) captured: dict[str, object] = {} def _run_agent_core_ok(**kwargs: object) -> object: data = kwargs.get("data") tools = getattr(data, "tools", None) captured["tool_names"] = [t.name for t in tools.list()] if hasattr(tools, "list") else [] return SimpleNamespace(outcome=SimpleNamespace(final_text="ok", turn_uuid="turn-1")) monkeypatch.setattr("runtime.gateway.run_agent_core", _run_agent_core_ok) gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-plan-tools", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="先给我一个执行计划", attachments=[], metadata={"interaction_mode": "expert", "selected_specialist": "generalist", "execution_mode": "plan"}, ) out = gw.handle_turn(msg=msg, lang="zh", executor=_Exec()) assert str(out.reply_text or "") == "ok" names = list(captured.get("tool_names") or []) assert "read_file" in names assert "edit_file" not in names def test_gateway_expert_agent_mode_injects_plan_control_tools(monkeypatch: pytest.MonkeyPatch) -> None: class Store: def __init__(self) -> None: self.kv: dict[str, str] = {} def get_setting(self, k: str) -> str: return str(self.kv.get(k) or "") def set_setting(self, k: str, v: str) -> None: self.kv[k] = str(v or "") def add_trace_event(self, **_kwargs: object) -> None: return None def add_trace_events_batch(self, _rows: list[dict[str, object]]) -> None: return None def _mk_tool(name: str, read_only: bool) -> ToolSpec: return ToolSpec( name=name, description=name, parameters={"type": "object", "properties": {}, "additionalProperties": True}, handler=lambda args: {"ok": True, "args": args}, read_only=read_only, ) class _Exec: model = object() system_prompt = "base-system" tools = ToolRegistry([_mk_tool("read_file", True)]) captured: dict[str, object] = {} def _run_agent_core_ok(**kwargs: object) -> object: data = kwargs.get("data") tools = getattr(data, "tools", None) captured["tool_names"] = [t.name for t in tools.list()] if hasattr(tools, "list") else [] return SimpleNamespace(outcome=SimpleNamespace(final_text="ok", turn_uuid="turn-1")) monkeypatch.setattr("runtime.gateway.run_agent_core", _run_agent_core_ok) store = Store() store.kv["AIA_EXPERT_PLAN_AGENT_V2_ENABLED"] = "1" gw = OclawGateway(store=store) msg = StandardMessage( session_id="sid-agent-tools", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="直接执行", attachments=[], metadata={"interaction_mode": "expert", "selected_specialist": "generalist", "execution_mode": "agent"}, ) out = gw.handle_turn(msg=msg, lang="zh", executor=_Exec()) assert str(out.reply_text or "") == "ok" names = list(captured.get("tool_names") or []) assert "enter_plan_mode_v2" in names assert "exit_plan_mode_v2" in names assert store.get_setting("AIA_PLAN_AGENT_V2_DEFAULT_SESSION_ID") == "sid-agent-tools" def test_gateway_comprehensive_mode_manager_first_selects_specialist(monkeypatch: pytest.MonkeyPatch) -> None: class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **_kwargs: object) -> None: return None class _ManagerModel: def chat(self, _messages, _tools, *, on_token=None): return LLMResponse( content='{"route":{"specialist":"generalist","reason":"needs image edits"},"dispatch":{"instruction_text":"Please edit the image background."}}', tool_calls=[], ) class _Exec: def __init__(self, model=None): self.model = model self.tools = object() self.system_prompt = "" monkeypatch.setattr( "runtime.gateway.get_manager_prompt_prebuild", lambda **kwargs: { "manager_context": "manager", "allowed_fixed": ("generalist", "ops", "memory", "image"), "allowed_fixed_quoted": '"generalist", "ops", "memory", "image"', }, ) captured: dict = {} def _run_agent_core(**kwargs): data = kwargs.get("data") captured["exec_text"] = getattr(getattr(data, "msg", None), "text", None) captured["persisted_user_text"] = getattr(data, "persisted_user_text", None) return SimpleNamespace(outcome=SimpleNamespace(final_text="specialist_answer")) monkeypatch.setattr("runtime.gateway.run_agent_core", _run_agent_core) chosen: dict[str, str] = {} def _factory(sid: str) -> object: chosen["sid"] = sid return _Exec(model=object()) gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-c1", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="edit this image background", attachments=[], metadata={"interaction_mode": "comprehensive", "selected_specialist": "ops"}, ) out = gw.handle_turn(msg=msg, lang="en", executor=_Exec(model=_ManagerModel()), specialist_executor_factory=_factory) assert out.interaction_mode == "comprehensive" assert out.selected_specialist == "generalist" assert chosen.get("sid") == "generalist" assert captured.get("exec_text") == "Please edit the image background." assert captured.get("persisted_user_text") == "edit this image background" def test_gateway_comprehensive_mode_writes_task_assignment_reasoning(monkeypatch: pytest.MonkeyPatch) -> None: written: list[dict] = [] class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **_kwargs: object) -> None: return None def add_message(self, **kwargs: object) -> None: written.append(dict(kwargs)) class _ManagerModel: def chat(self, _messages, _tools, *, on_token=None): return LLMResponse( content='{"route":{"specialist":"ops","reason":"ops task"},"dispatch":{"instruction_text":"请检查并修复网关启动失败。"}}', tool_calls=[], ) class _Exec: def __init__(self, model=None): self.model = model self.tools = object() self.system_prompt = "" monkeypatch.setattr( "runtime.gateway.get_manager_prompt_prebuild", lambda **kwargs: { "manager_context": "manager", "allowed_fixed": ("generalist", "ops", "memory", "image"), "allowed_fixed_quoted": '"generalist", "ops", "memory", "image"', }, ) monkeypatch.setattr( "runtime.gateway.run_agent_core", lambda **kwargs: SimpleNamespace(outcome=SimpleNamespace(final_text="specialist_answer")), ) gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-assign-1", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="网关起不来,帮我修复", attachments=[], metadata={"interaction_mode": "comprehensive"}, ) _ = gw.handle_turn(msg=msg, lang="zh", executor=_Exec(model=_ManagerModel())) reasoning_rows = [x for x in written if str(x.get("event_type") or "") == "reasoning"] assert reasoning_rows, "expected task-assignment reasoning row" content = str(reasoning_rows[-1].get("content") or "") assert "任务分配" in content assert "specialist=ops" in content assert "请检查并修复网关启动失败" in content def test_gateway_comprehensive_ignores_wiki_inject_flags(monkeypatch: pytest.MonkeyPatch) -> None: class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **_kwargs: object) -> None: return None class _ManagerModel: def chat(self, _messages, _tools, *, on_token=None): return LLMResponse( content=( '{"route":{"specialist":"generalist","reason":"general",' '"need_wiki_inject":true,"wiki_query":"router issue"},' '"dispatch":{"instruction_text":"请先分析问题并给出结论。",' '"need_wiki_inject":true,"wiki_query":"router issue"}}' ), tool_calls=[], ) class _Exec: def __init__(self, model=None): self.model = model self.tools = object() self.system_prompt = "" monkeypatch.setattr( "runtime.gateway.get_manager_prompt_prebuild", lambda **kwargs: { "manager_context": "manager", "allowed_fixed": ("generalist", "ops", "memory", "image"), "allowed_fixed_quoted": '"generalist", "ops", "memory", "image"', }, ) captured: dict[str, object] = {} def _run_agent_core(**kwargs): data = kwargs.get("data") msg = getattr(data, "msg", None) captured["metadata"] = dict(getattr(msg, "metadata", {}) or {}) return SimpleNamespace(outcome=SimpleNamespace(final_text="ok")) monkeypatch.setattr("runtime.gateway.run_agent_core", _run_agent_core) gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-wi-1", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="分析一下", attachments=[], metadata={"interaction_mode": "comprehensive"}, ) out = gw.handle_turn(msg=msg, lang="zh", executor=_Exec(model=_ManagerModel())) assert out.interaction_mode == "comprehensive" md = captured.get("metadata") or {} assert isinstance(md, dict) assert "need_wiki_inject" not in md assert "wiki_query" not in md def test_gateway_comprehensive_mode_has_manager_final_pass(monkeypatch: pytest.MonkeyPatch) -> None: class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **_kwargs: object) -> None: return None class _ManagerModel: def __init__(self) -> None: self.calls = 0 def chat(self, _messages, _tools, *, on_token=None): self.calls += 1 if self.calls == 1: return LLMResponse( content='{"route":{"specialist":"generalist","reason":"general"},"dispatch":{"instruction_text":"Analyze the finance report and provide key points."}}', tool_calls=[], ) return LLMResponse(content="final_from_manager", tool_calls=[]) class _Exec: def __init__(self, model=None): self.model = model self.tools = object() self.system_prompt = "" monkeypatch.setattr( "runtime.gateway.get_manager_prompt_prebuild", lambda **kwargs: { "manager_context": "manager", "allowed_fixed": ("generalist", "ops", "memory", "image"), "allowed_fixed_quoted": '"generalist", "ops", "memory", "image"', }, ) monkeypatch.setattr( "runtime.gateway.run_agent_core", lambda **kwargs: SimpleNamespace(outcome=SimpleNamespace(final_text="specialist_answer")), ) gw = OclawGateway(store=Store()) model = _ManagerModel() msg = StandardMessage( session_id="sid-d1", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="analyze finance report", attachments=[], metadata={"interaction_mode": "comprehensive", "selected_specialist": "ops"}, ) out = gw.handle_turn(msg=msg, lang="en", executor=_Exec(model=model)) assert out.interaction_mode == "comprehensive" assert out.selected_specialist == "generalist" assert out.reply_text == "final_from_manager" def test_gateway_comprehensive_mode_dynamic_agent_dispatches_instruction_only(monkeypatch: pytest.MonkeyPatch) -> None: class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **_kwargs: object) -> None: return None class _ManagerModel: def chat(self, _messages, _tools, *, on_token=None): return LLMResponse( content=( '{"route":{"specialist":"dyn:sql","reason":"needs ad-hoc expert"},' '"dispatch":{"instruction_text":"Write a SQL query to compute daily active users."},' '"dynamic_agent":{"name":"dyn:sql","system_prompt":"You are a SQL expert.","tool_policy":{"allow_tags":[],"allow_tools":[]},"reason":"dynamic"}}' ), tool_calls=[], ) class _Exec: def __init__(self, model=None): self.model = model self.tools = object() self.system_prompt = "" monkeypatch.setattr( "runtime.gateway.get_manager_prompt_prebuild", lambda **kwargs: { "manager_context": "manager", "allowed_fixed": ("generalist", "ops", "memory", "image"), "allowed_fixed_quoted": '"generalist", "ops", "memory", "image"', }, ) monkeypatch.setattr("runtime.gateway.build_ephemeral_executor", lambda *args, **kwargs: _Exec(model=object())) captured: dict = {} def _run_agent_core(**kwargs): data = kwargs.get("data") captured["exec_text"] = getattr(getattr(data, "msg", None), "text", None) return SimpleNamespace(outcome=SimpleNamespace(final_text="dynamic_specialist_answer")) monkeypatch.setattr("runtime.gateway.run_agent_core", _run_agent_core) gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-dyn-1", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="How do I compute DAU from events table?", attachments=[], metadata={"interaction_mode": "comprehensive"}, ) out = gw.handle_turn(msg=msg, lang="en", executor=_Exec(model=_ManagerModel())) assert out.interaction_mode == "comprehensive" assert out.selected_specialist == "dyn:sql" assert captured.get("exec_text") == "Write a SQL query to compute daily active users." def test_gateway_comprehensive_mode_ignores_manager_self_and_dispatches_specialist(monkeypatch: pytest.MonkeyPatch) -> None: class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **_kwargs: object) -> None: return None class _ManagerModel: def __init__(self) -> None: self.calls = 0 def chat(self, _messages, _tools, *, on_token=None): self.calls += 1 if self.calls == 1: return LLMResponse( content='{"route":{"kind":"manager_self","specialist":"generalist","reason":"can answer directly"},"dispatch":{"instruction_text":"请直接给用户简明答案。"}}', tool_calls=[], ) return LLMResponse(content="manager_self_final", tool_calls=[]) class _Exec: def __init__(self, model=None): self.model = model self.tools = object() self.system_prompt = "" monkeypatch.setattr( "runtime.gateway.get_manager_prompt_prebuild", lambda **kwargs: { "manager_context": "manager", "allowed_fixed": ("generalist", "ops", "memory", "image"), "allowed_fixed_quoted": '"generalist", "ops", "memory", "image"', }, ) captured: dict[str, str] = {} def _run_agent_core(**kwargs): data = kwargs["data"] captured["exec_text"] = str(getattr(data.msg, "text", "")) class _Outcome: final_text = "specialist_result" class _Out: outcome = _Outcome() return _Out() monkeypatch.setattr("runtime.gateway.run_agent_core", _run_agent_core) gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-self-1", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="请直接回答这个简单问题", attachments=[], metadata={"interaction_mode": "comprehensive"}, ) out = gw.handle_turn(msg=msg, lang="zh", executor=_Exec(model=_ManagerModel())) assert out.interaction_mode == "comprehensive" assert out.selected_specialist == "generalist" assert captured.get("exec_text") == "请直接给用户简明答案。" assert out.reply_text == "manager_self_final" def test_gateway_comprehensive_mode_suppresses_instruction_echo(monkeypatch: pytest.MonkeyPatch) -> None: class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **_kwargs: object) -> None: return None instruction = "用户说厉害;请友好回应,表达感谢并询问是否有具体问题。" class _ManagerModel: def __init__(self) -> None: self.calls = 0 def chat(self, _messages, _tools, *, on_token=None): self.calls += 1 if self.calls == 1: return LLMResponse( content=( '{"route":{"specialist":"generalist","reason":"smalltalk"},' f'"dispatch":{{"instruction_text":"{instruction}"}}' "}" ), tool_calls=[], ) # Simulate finalize path echoing instruction. return LLMResponse(content=instruction, tool_calls=[]) class _Exec: def __init__(self, model=None): self.model = model self.tools = object() self.system_prompt = "" monkeypatch.setattr( "runtime.gateway.get_manager_prompt_prebuild", lambda **kwargs: { "manager_context": "manager", "allowed_fixed": ("generalist", "ops", "memory", "image"), "allowed_fixed_quoted": '"generalist", "ops", "memory", "image"', }, ) # Specialist output also echoes instruction -> should still be suppressed. def _run_agent_core(**kwargs): class _Outcome: final_text = instruction class _Out: outcome = _Outcome() return _Out() monkeypatch.setattr("runtime.gateway.run_agent_core", _run_agent_core) gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-echo-1", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="你真厉害", attachments=[], metadata={"interaction_mode": "comprehensive"}, ) out = gw.handle_turn(msg=msg, lang="zh", executor=_Exec(model=_ManagerModel())) assert out.interaction_mode == "comprehensive" assert "用户说厉害" not in out.reply_text assert "请友好回应" not in out.reply_text assert out.reply_text == "抱歉,我暂时无法给出可展示的结果,请稍后再试。" def test_gateway_command_hook_uses_parsed_command_and_context(monkeypatch: pytest.MonkeyPatch) -> None: calls: list[dict] = [] class Store: def get_setting(self, _k: str) -> str: return "" def add_trace_event(self, **_kwargs: object) -> None: return None def oclaw_task_create(self, **_kwargs: object) -> object: class T: id = "task-cmd-1" task_type = "async_turn" status = "queued" return T() monkeypatch.setattr("runtime.gateway.ensure_worker_started", lambda store: "worker-1") monkeypatch.setattr("runtime.gateway.initialize_hooks_runtime", lambda **kwargs: 0) monkeypatch.setattr( "runtime.gateway.get_active_hooks_config", lambda: {"hooks": {"internal": {"enabled": True, "entries": {"session-memory": {"messages": 33}}}}}, ) def _capture(**kwargs): calls.append(dict(kwargs)) monkeypatch.setattr("runtime.gateway.trigger_hook_event", _capture) gw = OclawGateway(store=Store()) msg = StandardMessage( session_id="sid-cmd-1", tenant_id="t1", user_id="u1", role="user", channel="admin_chat", text="/重置 now", attachments=[], metadata={"workspace_dir": "D:/ws", "source": "webchat"}, ) _ = gw.handle_turn(msg=msg, lang="zh", executor=object()) assert calls command_events = [c for c in calls if c.get("event_type") == "command" and c.get("action") == "reset"] assert command_events event = command_events[-1] assert event.get("session_key") == "sid-cmd-1" ctx = event.get("context") or {} assert ctx.get("workspaceDir") == "D:/ws" assert ctx.get("commandSource") == "webchat" cfg = ctx.get("cfg") or {} assert ((cfg.get("hooks") or {}).get("internal") or {}).get("entries", {}).get("session-memory", {}).get("messages") == 33 sess = ctx.get("sessionEntry") or {} assert sess.get("sessionId") == "sid-cmd-1" def test_tabular_system_hint_uses_configured_preview_and_rows_read( tmp_path, monkeypatch: pytest.MonkeyPatch ) -> None: cfg = { "plugins": { "entries": { "memory-wiki": { "auto": { "attachments": { "tabular": { "large_table_preview_rows": 30, "max_rows_read": 5000, } } } } } } } cfg_path = tmp_path / "oclaw.json" cfg_path.write_text(json.dumps(cfg, ensure_ascii=False), encoding="utf-8") monkeypatch.setenv("AIA_OCLAW_CONFIG_PATH", str(cfg_path)) zh_hint = OclawGateway._tabular_query_system_hint("zh") en_hint = OclawGateway._tabular_query_system_hint("en") assert "前30行" in zh_hint assert "5000行" in zh_hint assert "first 30 preview rows" in en_hint assert "capped at 5000 rows" in en_hint def test_channel_file_delivery_hint_goes_to_system_not_user_message() -> None: from runtime.types import StandardMessage zh_hint = OclawGateway._channel_file_delivery_system_hint("zh") assert "save_deliverable_attachment" in zh_hint msg = StandardMessage( session_id="s1", tenant_id="t1", user_id="u1", role="member", channel="weixin", text="hi", attachments=[], metadata={}, ) assert OclawGateway._is_channel_delivery_channel(msg) msg_admin = StandardMessage( session_id="s1", tenant_id="t1", user_id="u1", role="member", channel="admin", text="hi", attachments=[], metadata={}, ) assert not OclawGateway._is_channel_delivery_channel(msg_admin) def test_group_focus_system_hint_only_for_shared_group_scope() -> None: from runtime.types import StandardMessage shared = StandardMessage( session_id="s1", tenant_id="t1", user_id="u1", role="member", channel="whatsapp", text="@bot alarms", attachments=[], metadata={"is_group": True, "group_session_scope": "chat"}, ) per_user = StandardMessage( session_id="s1", tenant_id="t1", user_id="u1", role="member", channel="whatsapp", text="@bot alarms", attachments=[], metadata={"is_group": True, "group_session_scope": "user_in_chat"}, ) dm = StandardMessage( session_id="s1", tenant_id="t1", user_id="u1", role="member", channel="whatsapp", text="alarms", attachments=[], metadata={"is_group": False}, ) hint = OclawGateway._group_focus_system_hint(shared, "en") assert "current sender" in hint assert OclawGateway._group_focus_system_hint(per_user, "en") == "" assert OclawGateway._group_focus_system_hint(dm, "en") == "" def test_ops_short_intent_caps_tool_rounds(tmp_path) -> None: from runtime.types import StandardMessage from svc.persistence.sqlite_store import SqliteStore store = SqliteStore(str(tmp_path / "rounds.sqlite")) gw = OclawGateway(store=store) short = StandardMessage( session_id="s1", tenant_id="t1", user_id="u1", role="member", channel="whatsapp", text="fiber cut report", attachments=[], metadata={}, ) long = StandardMessage( session_id="s1", tenant_id="t1", user_id="u1", role="member", channel="whatsapp", text="Please investigate the full OSPF adjacency flap history across all PE routers and draft a long RCA.", attachments=[], metadata={}, ) admin = StandardMessage( session_id="s1", tenant_id="t1", user_id="u1", role="member", channel="admin", text="fiber cut report", attachments=[], metadata={}, ) assert gw._resolve_max_tool_rounds(short, base=200) == 200 assert gw._resolve_max_tool_rounds(long, base=200) == 200 assert gw._resolve_max_tool_rounds(admin, base=200) == 200 store.set_setting("AIA_OPS_SHORT_INTENT_MAX_TOOL_ROUNDS", "12") assert gw._resolve_max_tool_rounds(short, base=200) == 12 assert gw._resolve_max_tool_rounds(long, base=200) == 200