Files
wehub-resource-sync 0418dc5cf9
CI / Python tests (3.10) (push) Has been cancelled
CI / Python tests (3.11) (push) Has been cancelled
CI / Python quality (push) Has been cancelled
CI / Frontend typecheck (push) Has been cancelled
Autopilot Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:45:00 +08:00

3357 lines
122 KiB
Python

import asyncio
import contextlib
import logging
import subprocess
import sys
from types import SimpleNamespace
from datetime import datetime
import json
from pathlib import Path
import pytest
from openharness.api.client import ApiMessageCompleteEvent
from openharness.api.usage import UsageSnapshot
from openharness.autopilot.service import RepoAutopilotStore
from openharness.bridge import get_bridge_manager
from openharness.channels.bus.events import InboundMessage
from openharness.channels.bus.queue import MessageBus
from openharness.commands import CommandResult
from openharness.commands.registry import SlashCommand, create_default_command_registry
from openharness.config.paths import get_project_issue_file, get_project_pr_comments_file
from openharness.config.settings import PermissionSettings, ProviderProfile, Settings
from openharness.engine.messages import ConversationMessage, ImageBlock, TextBlock, ToolUseBlock
from openharness.engine.query_engine import QueryEngine
from openharness.engine.stream_events import (
AssistantTextDelta,
CompactProgressEvent,
ErrorEvent,
ToolExecutionCompleted,
ToolExecutionStarted,
)
from openharness.memory import add_memory_entry as add_project_memory_entry
from openharness.memory import list_memory_files as list_project_memory_files
from openharness.permissions import PermissionChecker, PermissionMode
from openharness.tasks.manager import get_task_manager
from openharness.tools.base import ToolExecutionContext, ToolRegistry
from ohmo.gateway.bridge import OhmoGatewayBridge, _format_gateway_error
from ohmo.gateway.config import load_gateway_config, save_gateway_config
from ohmo.gateway.group_tool import OhmoCreateFeishuGroupInput, OhmoCreateFeishuGroupTool
from ohmo.gateway.models import GatewayConfig, GatewayState
from ohmo.gateway.provider_commands import handle_gateway_model_command, handle_gateway_provider_command
from ohmo.gateway.runtime import (
OhmoSessionRuntimePool,
_build_inbound_user_message,
_format_channel_progress,
_sanitize_group_command_metadata,
_sanitize_group_command_prompts,
)
from ohmo.gateway.service import OhmoGatewayService, gateway_status, start_gateway_process, stop_gateway_process
from ohmo.group_registry import load_managed_group_record, save_managed_group_record
from ohmo.memory import add_memory_entry as add_ohmo_memory_entry
from ohmo.memory import list_memory_files as list_ohmo_memory_files
from ohmo.gateway.router import session_key_for_message
from ohmo.session_storage import save_session_snapshot
from ohmo.workspace import get_gateway_restart_notice_path, get_skills_dir, initialize_workspace
def test_gateway_router_uses_thread_and_sender_for_group_when_present():
message = InboundMessage(
channel="slack",
sender_id="u1",
chat_id="c1",
content="hello",
timestamp=datetime.utcnow(),
metadata={"thread_ts": "t1", "chat_type": "group"},
)
assert session_key_for_message(message) == "slack:c1:t1:u1"
def test_gateway_router_keeps_private_chat_scope_for_legacy_sessions():
message = InboundMessage(
channel="feishu",
sender_id="u1",
chat_id="ou_legacy",
content="hello",
timestamp=datetime.utcnow(),
metadata={"chat_type": "p2p"},
)
assert session_key_for_message(message) == "feishu:ou_legacy"
def test_gateway_router_falls_back_to_chat_scope_when_chat_type_unknown():
message = InboundMessage(
channel="telegram",
sender_id="u1",
chat_id="chat-1",
content="hello",
timestamp=datetime.utcnow(),
)
assert session_key_for_message(message) == "telegram:chat-1"
def test_gateway_router_separates_senders_in_same_chat_thread():
first = InboundMessage(
channel="slack",
sender_id="alice",
chat_id="shared-chat",
content="hello",
timestamp=datetime.utcnow(),
metadata={"thread_ts": "thread-1", "chat_type": "group"},
)
second = InboundMessage(
channel="slack",
sender_id="bob",
chat_id="shared-chat",
content="hello",
timestamp=datetime.utcnow(),
metadata={"thread_ts": "thread-1", "chat_type": "group"},
)
assert session_key_for_message(first) == "slack:shared-chat:thread-1:alice"
assert session_key_for_message(second) == "slack:shared-chat:thread-1:bob"
def test_gateway_router_separates_senders_in_same_group_without_thread():
first = InboundMessage(
channel="feishu",
sender_id="alice",
chat_id="oc_shared",
content="hello",
timestamp=datetime.utcnow(),
metadata={"chat_type": "group"},
)
second = InboundMessage(
channel="feishu",
sender_id="bob",
chat_id="oc_shared",
content="hello",
timestamp=datetime.utcnow(),
metadata={"chat_type": "group"},
)
assert session_key_for_message(first) == "feishu:oc_shared:alice"
assert session_key_for_message(second) == "feishu:oc_shared:bob"
@pytest.mark.asyncio
async def test_slack_thread_messages_use_sender_scoped_router_keys(monkeypatch):
import sys
import types
def install_stub(name, **attrs):
module = types.ModuleType(name)
for key, value in attrs.items():
setattr(module, key, value)
monkeypatch.setitem(sys.modules, name, module)
return module
install_stub("slackify_markdown", slackify_markdown=lambda text: text)
install_stub("slack_sdk")
install_stub("slack_sdk.socket_mode")
install_stub("slack_sdk.socket_mode.request", SocketModeRequest=object)
class FakeSocketModeResponse:
def __init__(self, envelope_id=None):
self.envelope_id = envelope_id
install_stub("slack_sdk.socket_mode.response", SocketModeResponse=FakeSocketModeResponse)
install_stub("slack_sdk.socket_mode.websockets", SocketModeClient=object)
install_stub("slack_sdk.web")
install_stub("slack_sdk.web.async_client", AsyncWebClient=object)
from openharness.channels.impl.slack import SlackChannel
from openharness.config.schema import SlackConfig
class CapturingBus:
def __init__(self):
self.messages = []
async def publish_inbound(self, msg):
self.messages.append(msg)
class FakeSocketClient:
async def send_socket_mode_response(self, response):
return None
async def send_thread_message(channel, *, user):
request = SimpleNamespace(
type="events_api",
envelope_id=f"env-{user}",
payload={
"event": {
"type": "app_mention",
"user": user,
"channel": "C_SHARED",
"channel_type": "channel",
"text": "<@BOT> /summary 50",
"thread_ts": "1710000000.000100",
"ts": f"1710000000.{user[-1]}",
}
},
)
await channel._on_socket_request(FakeSocketClient(), request)
bus = CapturingBus()
config = SlackConfig(
allow_from=["U_ALICE", "U_BOB"],
bot_token="xoxb-fake",
app_token="xapp-fake",
mode="socket",
group_policy="mention",
reply_in_thread=True,
react_emoji="eyes",
dm=SimpleNamespace(enabled=True, policy="allowlist", allow_from=["U_ALICE", "U_BOB"]),
)
channel = SlackChannel(config, bus)
channel._bot_user_id = "BOT"
channel._web_client = None
await send_thread_message(channel, user="U_ALICE")
await send_thread_message(channel, user="U_BOB")
alice, bob = bus.messages
assert alice.session_key_override is None
assert bob.session_key_override is None
assert alice.metadata["thread_ts"] == "1710000000.000100"
assert bob.metadata["thread_ts"] == "1710000000.000100"
assert alice.metadata["chat_type"] == "group"
assert bob.metadata["chat_type"] == "group"
assert session_key_for_message(alice) == "slack:C_SHARED:1710000000.000100:U_ALICE"
assert session_key_for_message(bob) == "slack:C_SHARED:1710000000.000100:U_BOB"
@pytest.mark.asyncio
async def test_runtime_pool_summary_does_not_restore_other_slack_thread_sender(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
alice_message = InboundMessage(
channel="slack",
sender_id="U_ALICE",
chat_id="C_SHARED",
content="hello",
timestamp=datetime.utcnow(),
metadata={"thread_ts": "1710000000.000100", "chat_type": "group"},
)
bob_message = InboundMessage(
channel="slack",
sender_id="U_BOB",
chat_id="C_SHARED",
content="/summary 50",
timestamp=datetime.utcnow(),
metadata={"thread_ts": "1710000000.000100", "chat_type": "group"},
)
alice_key = session_key_for_message(alice_message)
bob_key = session_key_for_message(bob_message)
assert alice_key != bob_key
save_session_snapshot(
cwd=tmp_path,
workspace=workspace,
model="gpt-5.4",
system_prompt="test",
session_key=alice_key,
usage=UsageSnapshot(),
messages=[
ConversationMessage(
role="user",
content=[TextBlock(text="Alice private note: ALICE_PRIVATE_SUMMARY_SECRET")],
)
],
)
async def fake_build_runtime(**kwargs):
restored = [ConversationMessage.model_validate(item) for item in (kwargs.get("restore_messages") or [])]
class FakeEngine:
def __init__(self):
self.messages = restored
self.total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=create_default_command_registry(),
cwd=str(tmp_path),
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
tool_registry=None,
app_state=None,
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
updates = [u async for u in pool.stream_message(bob_message, bob_key)]
assert updates[-1].kind == "final"
assert updates[-1].text == "/summary is only available in the local OpenHarness UI."
assert "ALICE_PRIVATE_SUMMARY_SECRET" not in updates[-1].text
@pytest.mark.asyncio
async def test_runtime_pool_blocks_registered_resume_without_listing_or_loading_other_sessions(
tmp_path, monkeypatch
):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
registry = create_default_command_registry()
command, _ = registry.lookup("/resume alice-session")
assert command is not None
assert command.name == "resume"
assert command.remote_invocable is False
alice_secret = "ALICE_PRIVATE_RESUME_SECRET"
alice_key = "slack:C_SHARED:thread1:U_ALICE"
bob_key = "slack:C_SHARED:thread1:U_BOB"
save_session_snapshot(
cwd=tmp_path,
workspace=workspace,
model="gpt-5.4",
system_prompt="test",
session_id="alice-session",
session_key=alice_key,
usage=UsageSnapshot(),
messages=[ConversationMessage.from_user_text(f"Alice private note: {alice_secret}")],
)
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
def load_messages(self, messages):
raise AssertionError("remote /resume must not load saved messages")
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(tmp_path),
session_id="bob-session",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=registry,
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
for payload in ("/resume", "/resume alice-session"):
message = InboundMessage(
channel="slack",
sender_id="U_BOB",
chat_id="C_SHARED",
content=payload,
timestamp=datetime.utcnow(),
metadata={"thread_ts": "thread1", "chat_type": "group"},
)
updates = [u async for u in pool.stream_message(message, bob_key)]
assert updates[-1].kind == "final"
assert updates[-1].text == "/resume is only available in the local OpenHarness UI."
assert "alice-session" not in updates[-1].text
assert alice_secret not in updates[-1].text
def test_gateway_error_formats_claude_refresh_failure():
exc = ValueError("Claude OAuth refresh failed: HTTP Error 400: Bad Request")
assert "claude-login" in _format_gateway_error(exc)
assert "Claude subscription auth refresh failed" in _format_gateway_error(exc)
def test_gateway_error_formats_generic_auth_failure():
exc = ValueError("API key missing for current profile")
assert "Authentication failed" in _format_gateway_error(exc)
def test_compact_progress_formats_reactive_channel_hint_in_chinese():
text = _format_channel_progress(
channel="feishu",
kind="compact_progress",
text="",
session_key="feishu:c1",
content="帮我继续处理",
compact_phase="compact_start",
compact_trigger="reactive",
attempt=None,
)
assert "重试" in text
def test_gateway_status_prefers_live_config_over_stale_state(tmp_path):
workspace = tmp_path / ".ohmo-home"
workspace.mkdir()
(workspace / "gateway.json").write_text(
json.dumps({"provider_profile": "codex", "enabled_channels": ["feishu"]}) + "\n",
encoding="utf-8",
)
(workspace / "state.json").write_text(
GatewayState(
running=False,
provider_profile="claude-subscription",
enabled_channels=["feishu"],
).model_dump_json(indent=2)
+ "\n",
encoding="utf-8",
)
state = gateway_status(tmp_path, workspace)
assert state.running is False
assert state.provider_profile == "codex"
assert state.enabled_channels == ["feishu"]
def test_start_gateway_process_uses_child_log_file_handler_without_console_duplication(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
captured: dict[str, object] = {}
class FakeProcess:
pid = 1234
def fake_popen(args, **kwargs):
captured["args"] = args
captured["kwargs"] = kwargs
return FakeProcess()
monkeypatch.setattr("ohmo.gateway.service.subprocess.Popen", fake_popen)
assert start_gateway_process(tmp_path, workspace) == 1234
args = captured["args"]
kwargs = captured["kwargs"]
assert isinstance(args, list)
assert args[:4] == [sys.executable, "-m", "ohmo", "gateway"]
assert "run" in args
assert "--no-console-log" in args
assert isinstance(kwargs, dict)
assert kwargs["stdout"] is kwargs["stderr"]
assert getattr(kwargs["stdout"], "name", "").endswith("gateway.log")
def test_stop_gateway_process_kills_matching_workspace_processes(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
workspace.mkdir()
(workspace / "gateway.json").write_text('{"provider_profile":"codex"}\n', encoding="utf-8")
(workspace / "gateway.pid").write_text("123\n", encoding="utf-8")
killed: list[int] = []
def fake_run(*args, **kwargs):
class Result:
stdout = (
f"123 python -m ohmo gateway run --workspace {workspace}\n"
f"456 python -m ohmo gateway run --workspace {workspace}\n"
)
return Result()
monkeypatch.setattr("ohmo.gateway.service.subprocess.run", fake_run)
monkeypatch.setattr("ohmo.gateway.service._pid_is_running", lambda pid: True)
monkeypatch.setattr("ohmo.gateway.service.os.kill", lambda pid, sig: killed.append(pid))
assert stop_gateway_process(tmp_path, workspace) is True
assert killed == [123, 456]
@pytest.mark.asyncio
async def test_runtime_pool_restores_messages_for_private_legacy_session_key(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
save_session_snapshot(
cwd=tmp_path,
workspace=workspace,
model="gpt-5.4",
system_prompt="system",
messages=[ConversationMessage.from_user_text("remember private chat")],
usage=UsageSnapshot(),
session_id="sess123",
session_key="feishu:chat-1",
)
captured: dict[str, object] = {}
async def fake_build_runtime(**kwargs):
captured["restore_messages"] = kwargs.get("restore_messages")
return SimpleNamespace(
engine=SimpleNamespace(set_system_prompt=lambda prompt: None, messages=[]),
session_id="newsession",
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
bundle = await pool.get_bundle("feishu:chat-1")
assert captured["restore_messages"] is not None
assert bundle.session_id == "sess123"
@pytest.mark.asyncio
async def test_runtime_pool_blocks_registered_diff_full_without_leaking_workspace_changes(
tmp_path, monkeypatch
):
workspace = tmp_path / ".ohmo-home"
repo = tmp_path / "repo"
repo.mkdir()
initialize_workspace(workspace)
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=repo, check=True)
subprocess.run(["git", "config", "user.name", "OpenHarness Test"], cwd=repo, check=True)
changed_file = repo / "app.env"
changed_file.write_text("OPENHARNESS_VALUE=old\n", encoding="utf-8")
subprocess.run(["git", "add", "app.env"], cwd=repo, check=True)
subprocess.run(["git", "commit", "-q", "-m", "init"], cwd=repo, check=True)
changed_file.write_text("OPENHARNESS_VALUE=LEAKMARK_REMOTE_DIFF_VALUE\n", encoding="utf-8")
registry = create_default_command_registry()
command, _ = registry.lookup("/diff full")
assert command is not None
assert command.name == "diff"
assert command.remote_invocable is False
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
tool_metadata = {}
def set_system_prompt(self, prompt):
return None
async def fake_build_runtime(**kwargs):
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(repo),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=registry,
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=repo, workspace=workspace, provider_profile="codex")
message = InboundMessage(
channel="slack",
sender_id="U_ALLOWED",
chat_id="C_SHARED",
content="/diff full",
)
updates = [update async for update in pool.stream_message(message, "slack:C_SHARED:U_ALLOWED")]
assert updates[-1].kind == "final"
assert updates[-1].text == "/diff is only available in the local OpenHarness UI."
assert "LEAKMARK_REMOTE_DIFF_VALUE" not in updates[-1].text
@pytest.mark.asyncio
async def test_runtime_pool_uses_managed_group_cwd_binding(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
project = tmp_path / "OpenHarness-new"
project.mkdir()
initialize_workspace(workspace)
save_managed_group_record(
workspace=workspace,
channel="feishu",
chat_id="oc_group",
owner_open_id="ou_user",
name="HKUDS/OpenHarness",
cwd=str(project),
repo="HKUDS/OpenHarness",
binding_status="bound",
)
captured: dict[str, object] = {}
async def fake_build_runtime(**kwargs):
captured["cwd"] = kwargs.get("cwd")
return SimpleNamespace(
engine=SimpleNamespace(set_system_prompt=lambda prompt: None, messages=[]),
session_id="newsession",
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(
channel="feishu",
sender_id="ou_user",
chat_id="oc_group",
content="hello",
metadata={"chat_type": "group"},
)
bundle = await pool.get_bundle(
"feishu:oc_group:ou_user",
cwd=pool._cwd_for_message(message),
)
assert captured["cwd"] == str(project.resolve())
assert bundle.session_id == "newsession"
@pytest.mark.asyncio
async def test_runtime_pool_restores_messages_for_group_sender_scoped_session_key(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
save_session_snapshot(
cwd=tmp_path,
workspace=workspace,
model="gpt-5.4",
system_prompt="system",
messages=[ConversationMessage.from_user_text("remember alice only")],
usage=UsageSnapshot(),
session_id="sess123",
session_key="feishu:chat-1:alice",
)
captured: dict[str, object] = {}
async def fake_build_runtime(**kwargs):
captured["restore_messages"] = kwargs.get("restore_messages")
return SimpleNamespace(
engine=SimpleNamespace(set_system_prompt=lambda prompt: None, messages=[]),
session_id="newsession",
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
bundle = await pool.get_bundle("feishu:chat-1:alice")
assert captured["restore_messages"] is not None
assert bundle.session_id == "sess123"
@pytest.mark.asyncio
async def test_runtime_pool_does_not_restore_other_group_sender_session_key(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
save_session_snapshot(
cwd=tmp_path,
workspace=workspace,
model="gpt-5.4",
system_prompt="system",
messages=[ConversationMessage.from_user_text("remember alice only")],
usage=UsageSnapshot(),
session_id="sess123",
session_key="feishu:chat-1:alice",
)
captured: dict[str, object] = {}
async def fake_build_runtime(**kwargs):
captured["restore_messages"] = kwargs.get("restore_messages")
return SimpleNamespace(
engine=SimpleNamespace(set_system_prompt=lambda prompt: None, messages=[]),
session_id="newsession",
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
bundle = await pool.get_bundle("feishu:chat-1:bob")
assert captured["restore_messages"] is None
assert bundle.session_id == "newsession"
@pytest.mark.asyncio
async def test_runtime_pool_stream_message_emits_progress_and_tool_hint(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
async def submit_message(self, content):
yield ToolExecutionStarted(tool_name="web_fetch", tool_input={"url": "https://example.com"})
yield AssistantTextDelta(text="done")
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(tmp_path),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: None),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="check")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[0].kind == "progress"
assert updates[0].text.startswith(("🤔", "🧠", "✨", "🔎", "🪄"))
assert updates[1].kind == "tool_hint"
assert updates[1].text.startswith("🛠️ ")
assert "web_fetch" in updates[1].text
assert updates[-1].kind == "final"
assert updates[-1].text == "done"
@pytest.mark.asyncio
@pytest.mark.asyncio
async def test_runtime_pool_stream_message_emits_media_for_generated_tool_paths(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
image_path = tmp_path / "generated.png"
image_path.write_bytes(b"png")
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
async def submit_message(self, content):
yield ToolExecutionCompleted(
tool_name="image_generation",
output=f"Wrote {image_path}",
metadata={"paths": [str(image_path)], "provider": "codex"},
)
yield AssistantTextDelta(text="done")
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(tmp_path),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: None),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="draw")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
media_updates = [u for u in updates if u.kind == "media"]
assert len(media_updates) == 1
assert media_updates[0].media == [str(image_path)]
assert media_updates[0].metadata["_media"] == [str(image_path)]
assert "已生成图片 via codex" in media_updates[0].text
@pytest.mark.asyncio
async def test_runtime_pool_attaches_final_reply_image_path_as_media(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
image_path = tmp_path / "generated.png"
image_path.write_bytes(b"png")
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
async def submit_message(self, content):
yield AssistantTextDelta(text=f"已生成图片:\n```text\n{image_path}\n```")
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(tmp_path),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: None),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="draw")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[-1].kind == "final"
assert updates[-1].media == [str(image_path)]
assert updates[-1].metadata["_media"] == [str(image_path)]
assert updates[-1].metadata["_final_media_fallback"] is True
@pytest.mark.asyncio
async def test_runtime_pool_does_not_duplicate_final_reply_image_media(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
image_path = tmp_path / "generated.png"
image_path.write_bytes(b"png")
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
async def submit_message(self, content):
yield ToolExecutionCompleted(
tool_name="image_generation",
output=f"Wrote {image_path}",
metadata={"paths": [str(image_path)], "provider": "codex"},
)
yield AssistantTextDelta(text=f"已生成图片:\n```text\n{image_path}\n```")
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(tmp_path),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: None),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="draw")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert [u.kind for u in updates].count("media") == 1
assert updates[-1].kind == "final"
assert updates[-1].media is None
assert "_final_media_fallback" not in updates[-1].metadata
@pytest.mark.asyncio
async def test_runtime_pool_stream_message_formats_auto_compact_status_for_feishu(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
async def submit_message(self, content):
yield CompactProgressEvent(phase="compact_start", trigger="auto")
yield AssistantTextDelta(text="done")
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(tmp_path),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: None),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="继续")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[1].kind == "progress"
assert updates[1].text == "🧠 聊天有点长啦,我先帮你悄悄压缩一下记忆,马上继续~"
assert updates[-1].kind == "final"
assert updates[-1].text == "done"
@pytest.mark.asyncio
async def test_runtime_pool_stream_message_formats_compact_retry_for_feishu(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
async def submit_message(self, content):
yield CompactProgressEvent(phase="compact_retry", trigger="auto", attempt=2, message="retrying")
yield AssistantTextDelta(text="done")
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: None),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="继续")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[1].kind == "progress"
assert "再试一次" in updates[1].text
@pytest.mark.asyncio
async def test_runtime_pool_stream_message_formats_compact_hooks_start_for_feishu(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
async def submit_message(self, content):
yield CompactProgressEvent(phase="hooks_start", trigger="auto")
yield AssistantTextDelta(text="done")
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: None),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="继续")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[1].kind == "progress"
assert "准备" in updates[1].text
@pytest.mark.asyncio
async def test_runtime_pool_stream_message_uses_english_progress_for_english_input(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
async def submit_message(self, content):
yield ToolExecutionStarted(tool_name="web_fetch", tool_input={"url": "https://example.com"})
yield AssistantTextDelta(text="done")
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: None),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="can you check this")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[0].kind == "progress"
assert updates[0].text.startswith(("🤔", "🧠", "✨", "🔎", "🪄"))
assert "Thinking" in updates[0].text or "Working" in updates[0].text or "Looking" in updates[0].text or "Following" in updates[0].text or "Pulling" in updates[0].text
assert updates[1].kind == "tool_hint"
assert updates[1].text.startswith("🛠️ Using web_fetch")
@pytest.mark.asyncio
async def test_runtime_pool_blocks_local_only_commands_from_remote_messages(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
handler_called = False
async def forbidden_handler(args, context):
nonlocal handler_called
handler_called = True
return CommandResult(message="should not run")
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
command = SlashCommand(
"permissions",
"Show or update permission mode",
forbidden_handler,
remote_invocable=False,
)
command.remote_admin_opt_in = True
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: (command, "full_auto")),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="/permissions full_auto")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert handler_called is False
assert updates[-1].kind == "final"
assert updates[-1].text == "/permissions is only available in the local OpenHarness UI."
@pytest.mark.asyncio
async def test_runtime_pool_blocks_bridge_spawn_from_remote_messages(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
handler_called = False
async def forbidden_bridge_handler(args, context):
nonlocal handler_called
handler_called = True
return CommandResult(message="spawned")
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
command = SlashCommand(
"bridge",
"Inspect bridge helpers and spawn bridge sessions",
forbidden_bridge_handler,
remote_invocable=False,
remote_admin_opt_in=True,
)
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: (command, "spawn id")),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="/bridge spawn id")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert handler_called is False
assert updates[-1].kind == "final"
assert updates[-1].text == "/bridge is only available in the local OpenHarness UI."
@pytest.mark.asyncio
async def test_runtime_pool_blocks_registered_bridge_spawn_without_shelling_out(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
marker = tmp_path / "remote-bridge-marker.txt"
payload = f"/bridge spawn printf REMOTE_BRIDGE_EXEC > {marker}"
registry = create_default_command_registry()
command, _ = registry.lookup(payload)
existing_bridge_sessions = {session.session_id for session in get_bridge_manager().list_sessions()}
assert command is not None
assert command.name == "bridge"
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(tmp_path),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=registry,
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content=payload)
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[-1].kind == "final"
assert updates[-1].text == "/bridge is only available in the local OpenHarness UI."
assert {session.session_id for session in get_bridge_manager().list_sessions()} == existing_bridge_sessions
assert marker.exists() is False
@pytest.mark.asyncio
async def test_runtime_pool_blocks_registered_commit_without_running_git_hooks(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
repo = tmp_path / "repo"
repo.mkdir()
subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True, text=True)
subprocess.run(
["git", "config", "user.email", "openharness-test@example.invalid"],
cwd=repo,
check=True,
capture_output=True,
text=True,
)
subprocess.run(
["git", "config", "user.name", "OpenHarness Test"],
cwd=repo,
check=True,
capture_output=True,
text=True,
)
tracked = repo / "tracked.txt"
tracked.write_text("before\n", encoding="utf-8")
subprocess.run(["git", "add", "tracked.txt"], cwd=repo, check=True, capture_output=True, text=True)
subprocess.run(
["git", "commit", "-m", "initial"],
cwd=repo,
check=True,
capture_output=True,
text=True,
)
marker = repo / "remote-commit-hook-marker.txt"
hook = repo / ".git" / "hooks" / "pre-commit"
hook.write_text(f"#!/bin/sh\nprintf REMOTE_COMMIT_HOOK_EXEC > {marker}\n", encoding="utf-8")
hook.chmod(0o755)
tracked.write_text("before\nremote change\n", encoding="utf-8")
registry = create_default_command_registry()
command, _ = registry.lookup("/commit remote requested commit")
assert command is not None
assert command.name == "commit"
assert command.remote_invocable is False
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(repo),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=registry,
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=repo, workspace=workspace, provider_profile="codex")
message = InboundMessage(
channel="slack",
sender_id="U_ATTACKER",
chat_id="C_SHARED",
content="/commit remote requested commit",
)
updates = [u async for u in pool.stream_message(message, "slack:C_SHARED:U_ATTACKER")]
assert updates[-1].kind == "final"
assert updates[-1].text == "/commit is only available in the local OpenHarness UI."
assert marker.exists() is False
last_commit = subprocess.run(
["git", "log", "-1", "--pretty=%s"],
cwd=repo,
check=True,
capture_output=True,
text=True,
).stdout.strip()
assert last_commit == "initial"
@pytest.mark.asyncio
async def test_runtime_pool_blocks_registered_tasks_run_without_shelling_out(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
marker = tmp_path / "remote-tasks-marker.txt"
payload = f"/tasks run printf REMOTE_TASKS_EXEC > {marker}"
registry = create_default_command_registry()
command, _ = registry.lookup(payload)
existing_tasks = {task.id for task in get_task_manager().list_tasks()}
assert command is not None
assert command.name == "tasks"
assert command.remote_invocable is False
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(tmp_path),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=registry,
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content=payload)
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[-1].kind == "final"
assert updates[-1].text == "/tasks is only available in the local OpenHarness UI."
assert {task.id for task in get_task_manager().list_tasks()} == existing_tasks
assert marker.exists() is False
@pytest.mark.asyncio
async def test_runtime_pool_blocks_project_context_commands_without_writing_files(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
repo = tmp_path / "repo"
repo.mkdir()
initialize_workspace(workspace)
registry = create_default_command_registry()
for payload, expected_name in (
("/issue set Remote supplied issue :: REMOTE_ISSUE_CONTEXT_POISON", "issue"),
("/pr_comments add src/app.py:1 :: REMOTE_PR_COMMENT_POISON", "pr_comments"),
):
command, _ = registry.lookup(payload)
assert command is not None
assert command.name == expected_name
assert command.remote_invocable is False
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(repo),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=registry,
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=repo, workspace=workspace, provider_profile="codex")
for payload, expected_denial in (
("/issue set Remote supplied issue :: REMOTE_ISSUE_CONTEXT_POISON", "/issue is only available in the local OpenHarness UI."),
("/pr_comments add src/app.py:1 :: REMOTE_PR_COMMENT_POISON", "/pr_comments is only available in the local OpenHarness UI."),
):
message = InboundMessage(channel="slack", sender_id="U_ATTACKER", chat_id="C_SHARED", content=payload)
updates = [u async for u in pool.stream_message(message, "slack:C_SHARED:U_ATTACKER")]
assert updates[-1].kind == "final"
assert updates[-1].text == expected_denial
assert get_project_issue_file(repo).exists() is False
assert get_project_pr_comments_file(repo).exists() is False
@pytest.mark.asyncio
async def test_runtime_pool_blocks_registered_config_show_without_leaking_secrets(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
registry = create_default_command_registry()
command, _ = registry.lookup("/config show")
assert command is not None
assert command.name == "config"
assert command.remote_invocable is False
fake_settings = Settings(
mcp_servers={
"internal-http": {
"type": "http",
"url": "https://mcp.internal",
"headers": {"Authorization": "Bearer MCP_FAKE_SECRET"},
},
},
vision={"model": "vision-test", "api_key": "VISION_FAKE_SECRET"},
)
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(tmp_path),
session_id="sess123",
current_settings=lambda: fake_settings,
commands=registry,
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="slack", sender_id="U_ATTACKER", chat_id="C_SHARED", content="/config show")
updates = [u async for u in pool.stream_message(message, "slack:C_SHARED:U_ATTACKER")]
assert updates[-1].kind == "final"
assert updates[-1].text == "/config is only available in the local OpenHarness UI."
assert "MCP_FAKE_SECRET" not in updates[-1].text
assert "VISION_FAKE_SECRET" not in updates[-1].text
@pytest.mark.asyncio
async def test_runtime_pool_memory_command_uses_ohmo_personal_memory(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
registry = create_default_command_registry()
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
self.system_prompt = prompt
return SimpleNamespace(
engine=FakeEngine(),
cwd=str(tmp_path),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=registry,
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(
channel="feishu",
sender_id="u1",
chat_id="c1",
content="/memory add Profile :: prefers concise answers",
)
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[-1].text == "Added memory entry profile.md"
assert [path.name for path in list_ohmo_memory_files(workspace)] == ["profile.md"]
assert "prefers concise answers" in (workspace / "memory" / "profile.md").read_text(encoding="utf-8")
assert list_project_memory_files(tmp_path) == []
@pytest.mark.asyncio
async def test_runtime_pool_prompt_excludes_project_memory(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
add_ohmo_memory_entry(workspace, "personal", "ohmo-only personal fact")
monkeypatch.delenv("CLAUDE_CODE_COORDINATOR_MODE", raising=False)
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
add_project_memory_entry(tmp_path, "project", "project memory should not leak")
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
self.system_prompt = prompt
engine = FakeEngine()
return SimpleNamespace(
engine=engine,
session_id="sess123",
current_settings=lambda: Settings(system_prompt=kwargs["system_prompt"]),
commands=create_default_command_registry(),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
bundle = await pool.get_bundle("feishu:c1", latest_user_prompt="hello")
assert "ohmo-only personal fact" in bundle.engine.system_prompt
assert "project memory should not leak" not in bundle.engine.system_prompt
@pytest.mark.asyncio
async def test_runtime_pool_allows_opted_in_remote_admin_commands(tmp_path, monkeypatch, caplog):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
save_gateway_config(
GatewayConfig(
provider_profile="codex",
allow_remote_admin_commands=True,
allowed_remote_admin_commands=["permissions"],
),
workspace,
)
handler_called = False
async def allowed_handler(args, context):
nonlocal handler_called
handler_called = True
return CommandResult(message=f"ran with {args}")
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
command = SlashCommand(
"permissions",
"Show or update permission mode",
allowed_handler,
remote_invocable=False,
)
command.remote_admin_opt_in = True
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: (command, "full_auto")),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
cwd=str(tmp_path),
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
with caplog.at_level(logging.WARNING):
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="/permissions full_auto")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert handler_called is True
assert updates[-1].kind == "final"
assert updates[-1].text == "ran with full_auto"
assert "remote administrative command accepted" in caplog.text
@pytest.mark.asyncio
async def test_runtime_pool_includes_media_paths_in_prompt(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
image_path = tmp_path / "example.png"
image_path.write_bytes(
b"\x89PNG\r\n\x1a\n"
b"\x00\x00\x00\rIHDR"
b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00"
b"\x90wS\xde\x00\x00\x00\nIDATx\x9cc`\x00\x00\x00\x02\x00\x01"
b"\xe2!\xbc3\x00\x00\x00\x00IEND\xaeB`\x82"
)
report_path = tmp_path / "report.txt"
report_path.write_text("Quarterly summary\nRevenue up 12%\n", encoding="utf-8")
captured: dict[str, object] = {}
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
async def submit_message(self, content):
captured["content"] = content
yield AssistantTextDelta(text="done")
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: None),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(
channel="feishu",
sender_id="u1",
chat_id="c1",
content="请看这个图片",
media=[str(image_path), str(report_path)],
)
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[-1].text == "done"
submitted = captured["content"]
assert isinstance(submitted, ConversationMessage)
assert any(isinstance(block, ImageBlock) for block in submitted.content)
text = "".join(block.text for block in submitted.content if isinstance(block, TextBlock))
assert "[Channel attachments]" in text
assert f"image: example.png (path: {image_path})" in text
assert f"file: report.txt (path: {report_path})" in text
assert "text preview: Quarterly summary Revenue up 12%" in text
@pytest.mark.asyncio
async def test_runtime_pool_retries_with_attachment_summary_when_model_rejects_images(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
image_path = tmp_path / "example.png"
image_path.write_bytes(
b"\x89PNG\r\n\x1a\n"
b"\x00\x00\x00\rIHDR"
b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00"
b"\x90wS\xde\x00\x00\x00\nIDATx\x9cc`\x00\x00\x00\x02\x00\x01"
b"\xe2!\xbc3\x00\x00\x00\x00IEND\xaeB`\x82"
)
captured: dict[str, object] = {}
async def fake_build_runtime(**kwargs):
class FakeEngine:
def __init__(self):
self.messages = []
self.total_usage = UsageSnapshot()
self.max_turns = 8
def set_system_prompt(self, prompt):
return None
def load_messages(self, messages):
self.messages = list(messages)
async def submit_message(self, content):
self.messages.append(content)
yield ErrorEvent(
message=(
"API error: Error code: 404 - {'error': {'message': "
"'No endpoints found that support image input', 'code': 404}}"
)
)
async def continue_pending(self, *, max_turns=None):
captured["retry_messages"] = list(self.messages)
yield AssistantTextDelta(text="done")
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="openrouter/text-only"),
commands=SimpleNamespace(lookup=lambda raw: None),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="openrouter")
message = InboundMessage(
channel="telegram",
sender_id="u1",
chat_id="c1",
content="帮我看这个图片",
media=[str(image_path)],
)
updates = [u async for u in pool.stream_message(message, "telegram:c1")]
assert updates[-1].kind == "final"
assert updates[-1].text == "done"
assert not any(update.kind == "error" for update in updates)
assert any(update.metadata.get("_image_fallback") for update in updates)
retry_messages = captured["retry_messages"]
assert all(
not isinstance(block, ImageBlock)
for item in retry_messages
for block in item.content
)
text = "".join(
block.text
for item in retry_messages
for block in item.content
if isinstance(block, TextBlock)
)
assert "[Channel attachments]" in text
assert f"image: example.png (path: {image_path})" in text
def test_runtime_pool_includes_group_speaker_context():
built = _build_inbound_user_message(
InboundMessage(
channel="feishu",
sender_id="ou_123",
chat_id="oc_group",
content="请帮我看一下",
metadata={"chat_type": "group", "sender_display_name": "Tang Jiabin"},
)
)
text = "".join(block.text for block in built.content if isinstance(block, TextBlock))
assert "[Channel speaker]" in text
assert "Tang Jiabin" in text
assert "Sender id: ou_123" in text
assert "请帮我看一下" in text
@pytest.mark.asyncio
async def test_gateway_bridge_publishes_media_updates():
bus = MessageBus()
class FakeRuntimePool:
async def stream_message(self, message, session_key):
yield SimpleNamespace(
kind="media",
text="已生成图片:generated.png",
media=["/tmp/generated.png"],
metadata={"_session_key": session_key, "_media": ["/tmp/generated.png"]},
)
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool())
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="draw"))
outbound = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
assert outbound.content == "已生成图片:generated.png"
assert outbound.media == ["/tmp/generated.png"]
@pytest.mark.asyncio
async def test_gateway_bridge_publishes_final_media_updates():
bus = MessageBus()
class FakeRuntimePool:
async def stream_message(self, message, session_key):
yield SimpleNamespace(
kind="final",
text="已生成图片:generated.png",
media=["/tmp/generated.png"],
metadata={"_session_key": session_key, "_media": ["/tmp/generated.png"]},
)
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool())
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="draw"))
outbound = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
assert outbound.content == "已生成图片:generated.png"
assert outbound.media == ["/tmp/generated.png"]
@pytest.mark.asyncio
async def test_gateway_bridge_publishes_progress_updates():
bus = MessageBus()
class FakeRuntimePool:
async def stream_message(self, message, session_key):
yield SimpleNamespace(kind="progress", text="🤔 想一想…", metadata={"_progress": True, "_session_key": session_key})
yield SimpleNamespace(kind="tool_hint", text="🛠️ 正在使用 web_fetch: https://example.com", metadata={"_progress": True, "_tool_hint": True, "_session_key": session_key})
yield SimpleNamespace(kind="final", text="Done", metadata={"_session_key": session_key})
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool())
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="hi")
)
first = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
second = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
third = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
assert first.content.startswith(("🤔", "🧠", "✨", "🔎", "🪄"))
assert first.metadata["_progress"] is True
assert second.metadata["_tool_hint"] is True
assert second.content.startswith("🛠️ ")
assert "web_fetch" in second.content
assert third.content == "Done"
@pytest.mark.asyncio
async def test_gateway_bridge_does_not_thread_private_feishu_replies():
bus = MessageBus()
class FakeRuntimePool:
async def stream_message(self, message, session_key):
yield SimpleNamespace(kind="progress", text="🤔 想一想…", metadata={"_progress": True})
yield SimpleNamespace(kind="final", text="Done", metadata={})
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool())
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(
channel="feishu",
sender_id="ou_1",
chat_id="ou_1",
content="hi",
metadata={"chat_type": "p2p", "message_id": "om_private"},
)
)
progress = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
final = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert "message_id" not in progress.metadata
assert "message_id" not in final.metadata
assert final.metadata["_session_key"] == "feishu:ou_1"
@pytest.mark.asyncio
async def test_gateway_bridge_threads_group_feishu_replies():
bus = MessageBus()
class FakeRuntimePool:
async def stream_message(self, message, session_key):
yield SimpleNamespace(kind="progress", text="🤔 想一想…", metadata={"_progress": True})
yield SimpleNamespace(kind="final", text="Done", metadata={})
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool())
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(
channel="feishu",
sender_id="ou_1",
chat_id="oc_group",
content="hi",
metadata={"chat_type": "group", "message_id": "om_group", "thread_id": "thread_1"},
)
)
progress = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
final = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert progress.metadata["message_id"] == "om_group"
assert final.metadata["message_id"] == "om_group"
assert final.metadata["_session_key"] == "feishu:oc_group:thread_1:ou_1"
@pytest.mark.asyncio
async def test_gateway_bridge_ignores_unmentioned_unmanaged_feishu_group(tmp_path):
bus = MessageBus()
calls: list[InboundMessage] = []
class FakeRuntimePool:
async def stream_message(self, message, session_key):
calls.append(message)
yield SimpleNamespace(kind="final", text="should not happen", metadata={"_session_key": session_key})
bridge = OhmoGatewayBridge(
bus=bus,
runtime_pool=FakeRuntimePool(),
workspace=tmp_path,
feishu_group_policy="managed_or_mention",
)
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(
channel="feishu",
sender_id="ou_1",
chat_id="oc_random",
content="这个普通群消息不应该触发",
metadata={"chat_type": "group", "mentions_bot": False},
)
)
with pytest.raises(asyncio.TimeoutError):
await asyncio.wait_for(bus.consume_outbound(), timeout=0.1)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert calls == []
@pytest.mark.asyncio
async def test_gateway_bridge_processes_managed_feishu_group_without_mention(tmp_path):
bus = MessageBus()
save_managed_group_record(
workspace=tmp_path,
channel="feishu",
chat_id="oc_managed",
owner_open_id="ou_owner",
name="Managed Group",
)
class FakeRuntimePool:
async def stream_message(self, message, session_key):
yield SimpleNamespace(kind="final", text="managed done", metadata={"_session_key": session_key})
bridge = OhmoGatewayBridge(
bus=bus,
runtime_pool=FakeRuntimePool(),
workspace=tmp_path,
feishu_group_policy="managed_or_mention",
)
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(
channel="feishu",
sender_id="ou_1",
chat_id="oc_managed",
content="不用 @ 也应该处理",
metadata={"chat_type": "group", "mentions_bot": False},
)
)
final = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert final.content == "managed done"
@pytest.mark.asyncio
async def test_gateway_bridge_processes_mentioned_unmanaged_feishu_group(tmp_path):
bus = MessageBus()
class FakeRuntimePool:
async def stream_message(self, message, session_key):
yield SimpleNamespace(kind="final", text="mentioned done", metadata={"_session_key": session_key})
bridge = OhmoGatewayBridge(
bus=bus,
runtime_pool=FakeRuntimePool(),
workspace=tmp_path,
feishu_group_policy="managed_or_mention",
)
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(
channel="feishu",
sender_id="ou_1",
chat_id="oc_random",
content="@ohmo 帮我看下",
metadata={"chat_type": "group", "mentions_bot": True},
)
)
final = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert final.content == "mentioned done"
@pytest.mark.asyncio
async def test_gateway_bridge_mention_policy_overrides_managed_feishu_group(tmp_path):
bus = MessageBus()
calls: list[InboundMessage] = []
save_managed_group_record(
workspace=tmp_path,
channel="feishu",
chat_id="oc_managed",
owner_open_id="ou_owner",
name="Managed Group",
)
class FakeRuntimePool:
async def stream_message(self, message, session_key):
calls.append(message)
yield SimpleNamespace(kind="final", text="should not happen", metadata={"_session_key": session_key})
bridge = OhmoGatewayBridge(
bus=bus,
runtime_pool=FakeRuntimePool(),
workspace=tmp_path,
feishu_group_policy="mention",
)
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(
channel="feishu",
sender_id="ou_1",
chat_id="oc_managed",
content="mention policy 下没有 @ 不应该处理",
metadata={"chat_type": "group", "mentions_bot": False},
)
)
with pytest.raises(asyncio.TimeoutError):
await asyncio.wait_for(bus.consume_outbound(), timeout=0.1)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert calls == []
@pytest.mark.asyncio
async def test_gateway_bridge_logs_inbound_and_final(caplog):
bus = MessageBus()
class FakeRuntimePool:
async def stream_message(self, message, session_key):
yield SimpleNamespace(kind="progress", text="🤔 想一想…", metadata={"_progress": True, "_session_key": session_key})
yield SimpleNamespace(kind="final", text="Done", metadata={"_session_key": session_key})
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool())
task = asyncio.create_task(bridge.run())
caplog.set_level(logging.INFO)
try:
await bus.publish_inbound(
InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="please translate this")
)
await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
assert "ohmo inbound received" in caplog.text
assert "ohmo outbound final" in caplog.text
assert "please translate this" in caplog.text
@pytest.mark.asyncio
async def test_gateway_bridge_stop_command_cancels_current_session():
bus = MessageBus()
cancelled = asyncio.Event()
release = asyncio.Event()
class FakeRuntimePool:
async def stream_message(self, message, session_key):
try:
yield SimpleNamespace(kind="progress", text="🤔 想一想…", metadata={"_progress": True, "_session_key": session_key})
await release.wait()
yield SimpleNamespace(kind="final", text="Done", metadata={"_session_key": session_key})
except asyncio.CancelledError:
cancelled.set()
raise
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool())
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="long task")
)
first = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
assert first.metadata["_progress"] is True
await bus.publish_inbound(
InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="/stop")
)
stopped = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
await asyncio.wait_for(cancelled.wait(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert stopped.content == "⏹️ 已停止当前正在运行的任务。"
@pytest.mark.asyncio
async def test_gateway_bridge_restart_command_requests_gateway_restart():
bus = MessageBus()
restarted = asyncio.Event()
restart_payloads: list[tuple[str, str, str]] = []
class FakeRuntimePool:
async def stream_message(self, message, session_key):
if False:
yield
async def fake_restart(message, session_key: str) -> None:
restart_payloads.append((message.channel, message.chat_id, session_key))
restarted.set()
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool(), restart_gateway=fake_restart)
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="/restart")
)
restarting = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
await asyncio.wait_for(restarted.wait(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert restarting.content == (
"🔄 正在重启 gateway,马上回来。\n"
"Restarting the gateway now. I'll be back in a moment."
)
assert restart_payloads == [("feishu", "c1", "feishu:c1")]
@pytest.mark.asyncio
async def test_gateway_bridge_group_command_routes_to_agent_tool_prompt():
bus = MessageBus()
captured: list[tuple[InboundMessage, str]] = []
class FakeRuntimePool:
async def stream_message(self, message, session_key):
captured.append((message, session_key))
yield SimpleNamespace(kind="final", text="agent handled group request", metadata={})
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool())
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(
channel="feishu",
sender_id="ou_user",
chat_id="ou_user",
content="/group 帮我创建一个群聊专门处理HKUDS/OpenHarness的问题吧,绑定cwd就在~/OpenHarness-new",
metadata={"chat_type": "p2p", "sender_display_name": "Tang"},
)
)
reply = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert reply.content == "agent handled group request"
assert len(captured) == 1
message, session_key = captured[0]
assert session_key == "feishu:ou_user"
assert "ohmo_create_feishu_group" in message.content
assert "HKUDS/OpenHarness" in message.content
assert "~/OpenHarness-new" in message.content
assert message.metadata["_ohmo_group_command"] is True
assert message.metadata["_ohmo_group_raw_request"].startswith("帮我创建一个群聊")
@pytest.mark.asyncio
async def test_ohmo_create_feishu_group_tool_creates_and_binds_metadata(tmp_path):
project = tmp_path / "OpenHarness-new"
project.mkdir()
created: list[tuple[str, str]] = []
welcomes: list[tuple[str, str, str]] = []
async def fake_create_group(user_open_id: str, name: str) -> str:
created.append((user_open_id, name))
return "oc_project_group"
async def fake_publish_welcome(chat_id: str, content: str, owner_open_id: str) -> None:
welcomes.append((chat_id, content, owner_open_id))
tool = OhmoCreateFeishuGroupTool(
workspace=tmp_path,
create_group=fake_create_group,
publish_group_welcome=fake_publish_welcome,
)
result = await tool.execute(
OhmoCreateFeishuGroupInput(
name="HKUDS/OpenHarness",
cwd=str(project),
repo="HKUDS/OpenHarness",
reason="The request names the repo and cwd directly.",
),
ToolExecutionContext(
cwd=tmp_path,
metadata={
"ohmo_group_request": {
"channel": "feishu",
"chat_type": "p2p",
"sender_id": "ou_user",
"source_chat_id": "ou_user",
"source_session_key": "feishu:ou_user",
"sender_display_name": "Tang",
"raw_request": "帮我创建 OpenHarness 群",
"used": False,
}
},
),
)
assert result.is_error is False
assert created == [("ou_user", "HKUDS/OpenHarness")]
assert len(welcomes) == 1
assert welcomes[0][0] == "oc_project_group"
assert welcomes[0][2] == "ou_user"
assert "已绑定工作目录" in welcomes[0][1]
record = load_managed_group_record(workspace=tmp_path, channel="feishu", chat_id="oc_project_group")
assert record is not None
assert record["owner_open_id"] == "ou_user"
assert record["name"] == "HKUDS/OpenHarness"
assert record["cwd"] == str(project.resolve())
assert record["repo"] == "HKUDS/OpenHarness"
assert record["binding_status"] == "bound"
@pytest.mark.asyncio
async def test_agent_loop_can_create_group_via_ohmo_group_tool(tmp_path):
project = tmp_path / "ClawTeam"
project.mkdir()
created: list[tuple[str, str]] = []
class FakeApiClient:
def __init__(self):
self.requests = []
self.responses = [
ConversationMessage(
role="assistant",
content=[
ToolUseBlock(
id="toolu_group",
name="ohmo_create_feishu_group",
input={
"name": "HKUDS/ClawTeam",
"cwd": str(project),
"repo": "HKUDS/ClawTeam",
"reason": "The user asked for a ClawTeam project group.",
},
)
],
),
ConversationMessage(
role="assistant",
content=[TextBlock(text="已创建 HKUDS/ClawTeam 群。")],
),
]
async def stream_message(self, request):
self.requests.append(request)
yield ApiMessageCompleteEvent(
message=self.responses.pop(0),
usage=UsageSnapshot(input_tokens=1, output_tokens=1),
stop_reason=None,
)
async def fake_create_group(user_open_id: str, name: str) -> str:
created.append((user_open_id, name))
return "oc_clawteam"
registry = ToolRegistry()
registry.register(OhmoCreateFeishuGroupTool(workspace=tmp_path, create_group=fake_create_group))
client = FakeApiClient()
engine = QueryEngine(
api_client=client,
tool_registry=registry,
permission_checker=PermissionChecker(PermissionSettings(mode=PermissionMode.DEFAULT)),
cwd=tmp_path,
model="fake-model",
system_prompt="system",
tool_metadata={
"ohmo_group_request": {
"channel": "feishu",
"chat_type": "p2p",
"sender_id": "ou_user",
"source_chat_id": "ou_user",
"source_session_key": "feishu:ou_user",
"raw_request": "帮我创建一个群聊专门处理HKUDS/ClawTeam的问题吧",
"used": False,
}
},
)
events = [event async for event in engine.submit_message("create group")]
completed = [event for event in events if isinstance(event, ToolExecutionCompleted)]
assert len(completed) == 1
assert completed[0].tool_name == "ohmo_create_feishu_group"
assert completed[0].is_error is False
assert created == [("ou_user", "HKUDS/ClawTeam")]
assert "ohmo_create_feishu_group" in {tool["name"] for tool in client.requests[0].tools}
record = load_managed_group_record(workspace=tmp_path, channel="feishu", chat_id="oc_clawteam")
assert record is not None
assert record["cwd"] == str(project.resolve())
assert record["repo"] == "HKUDS/ClawTeam"
@pytest.mark.asyncio
async def test_ohmo_create_feishu_group_tool_rejects_without_slash_group_context(tmp_path):
async def fake_create_group(user_open_id: str, name: str) -> str:
raise AssertionError("tool should reject before creating a group")
tool = OhmoCreateFeishuGroupTool(workspace=tmp_path, create_group=fake_create_group)
result = await tool.execute(
OhmoCreateFeishuGroupInput(name="HKUDS/OpenHarness"),
ToolExecutionContext(cwd=tmp_path, metadata={}),
)
assert result.is_error is True
assert "only run immediately" in result.output
@pytest.mark.asyncio
async def test_gateway_bridge_group_command_rejects_group_chat(tmp_path):
bus = MessageBus()
class FakeRuntimePool:
async def stream_message(self, message, session_key):
raise AssertionError("/group should not enter the agent runtime")
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool())
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(
channel="feishu",
sender_id="ou_user",
chat_id="oc_existing",
content="/group New Group",
metadata={"chat_type": "group"},
)
)
reply = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert "私聊" in reply.content
@pytest.mark.asyncio
async def test_gateway_bridge_group_command_without_details_still_routes_to_agent():
bus = MessageBus()
captured: list[InboundMessage] = []
class FakeRuntimePool:
async def stream_message(self, message, session_key):
del session_key
captured.append(message)
yield SimpleNamespace(kind="final", text="need details", metadata={})
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool())
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(
channel="feishu",
sender_id="ou_user",
chat_id="ou_user",
content="/group",
metadata={"chat_type": "p2p"},
)
)
reply = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert reply.content == "need details"
assert "(user did not provide details)" in captured[0].content
@pytest.mark.asyncio
async def test_gateway_service_request_restart_waits_before_stop(monkeypatch):
service = object.__new__(OhmoGatewayService)
service._restart_requested = False
service._stop_event = asyncio.Event()
service._workspace = "/tmp/ohmo"
slept: list[float] = []
async def fake_sleep(delay: float) -> None:
slept.append(delay)
monkeypatch.setattr("ohmo.gateway.service.asyncio.sleep", fake_sleep)
monkeypatch.setattr(
"ohmo.gateway.service.get_gateway_restart_notice_path",
lambda workspace: Path("/tmp/restart-notice.json"),
)
writes: list[str] = []
monkeypatch.setattr(
"pathlib.Path.write_text",
lambda self, content, encoding=None: writes.append(content) or len(content),
)
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="/restart")
await OhmoGatewayService.request_restart(service, message, "feishu:c1")
assert service._restart_requested is True
assert service._stop_event.is_set() is True
assert slept == [0.75]
assert writes
@pytest.mark.asyncio
async def test_gateway_service_publishes_pending_restart_notice(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
notice_path = get_gateway_restart_notice_path(workspace)
notice_path.write_text(
json.dumps(
{
"channel": "feishu",
"chat_id": "chat-1",
"session_key": "feishu:chat-1",
"content": "✅ gateway 已经重新连上,可以继续了。\nGateway is back online. We can continue.",
},
ensure_ascii=False,
),
encoding="utf-8",
)
service = object.__new__(OhmoGatewayService)
service._workspace = workspace
service._bus = MessageBus()
async def fake_sleep(delay: float) -> None:
return None
monkeypatch.setattr("ohmo.gateway.service.asyncio.sleep", fake_sleep)
await OhmoGatewayService._publish_pending_restart_notice(service)
outbound = await asyncio.wait_for(service._bus.consume_outbound(), timeout=1.0)
assert outbound.content == "✅ gateway 已经重新连上,可以继续了。\nGateway is back online. We can continue."
assert outbound.chat_id == "chat-1"
assert not notice_path.exists()
@pytest.mark.asyncio
async def test_gateway_bridge_new_message_interrupts_same_session():
bus = MessageBus()
first_cancelled = asyncio.Event()
second_started = asyncio.Event()
class FakeRuntimePool:
async def stream_message(self, message, session_key):
if message.content == "first":
try:
yield SimpleNamespace(kind="progress", text="🤔 想一想…", metadata={"_progress": True, "_session_key": session_key})
await asyncio.Event().wait()
except asyncio.CancelledError:
first_cancelled.set()
raise
else:
second_started.set()
yield SimpleNamespace(kind="final", text="second-done", metadata={"_session_key": session_key})
bridge = OhmoGatewayBridge(bus=bus, runtime_pool=FakeRuntimePool())
task = asyncio.create_task(bridge.run())
try:
await bus.publish_inbound(
InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="first")
)
await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
await bus.publish_inbound(
InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="second")
)
interrupted = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
final = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
await asyncio.wait_for(first_cancelled.wait(), timeout=1.0)
await asyncio.wait_for(second_started.wait(), timeout=1.0)
finally:
bridge.stop()
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
assert interrupted.content == "⏹️ 已停止上一条正在处理的任务,继续看你的最新消息。"
assert final.content == "second-done"
@pytest.mark.asyncio
async def test_runtime_pool_logs_session_lifecycle(tmp_path, monkeypatch, caplog):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
async def fake_build_runtime(**kwargs):
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
return None
async def submit_message(self, content):
yield ToolExecutionStarted(tool_name="web_fetch", tool_input={"url": "https://example.com"})
yield AssistantTextDelta(text="done")
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: None),
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="check")
caplog.set_level(logging.INFO)
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[-1].text == "done"
assert "ohmo runtime processing start" in caplog.text
assert "ohmo runtime tool start" in caplog.text
assert "ohmo runtime saved snapshot" in caplog.text
assert "ohmo runtime processing complete" in caplog.text
def test_gateway_provider_command_uses_ohmo_gateway_profile(tmp_path, monkeypatch):
workspace = initialize_workspace(tmp_path / ".ohmo-home")
save_gateway_config(GatewayConfig(provider_profile="kimi-anthropic"), workspace)
statuses = {
"codex": {
"label": "Codex subscription",
"configured": True,
"base_url": None,
"model": "gpt-5.4",
},
"kimi-anthropic": {
"label": "Kimi Anthropic",
"configured": True,
"base_url": "https://api.example.test",
"model": "kimi-k2.5",
},
}
class FakeAuthManager:
def __init__(self, settings):
del settings
def get_profile_statuses(self):
return statuses
monkeypatch.setattr("ohmo.gateway.provider_commands.load_settings", lambda: object())
monkeypatch.setattr("ohmo.gateway.provider_commands.AuthManager", FakeAuthManager)
text, refresh = handle_gateway_provider_command("list", workspace=workspace)
assert refresh is False
assert "ohmo gateway provider profiles:" in text
assert "* kimi-anthropic [ready]" in text
assert " codex [ready]" in text
text, refresh = handle_gateway_provider_command("codex", workspace=workspace)
assert refresh is True
assert "provider_profile set to codex" in text
assert load_gateway_config(workspace).provider_profile == "codex"
def test_gateway_model_command_updates_selected_gateway_profile(tmp_path, monkeypatch):
workspace = initialize_workspace(tmp_path / ".ohmo-home")
save_gateway_config(GatewayConfig(provider_profile="codex"), workspace)
profile = ProviderProfile(
label="Codex",
provider="openai_codex",
api_format="responses",
auth_source="codex_subscription",
default_model="gpt-5.4",
allowed_models=["gpt-5.4", "gpt-5.5"],
)
updates: list[tuple[str, dict[str, object]]] = []
class FakeAuthManager:
def __init__(self, settings):
del settings
def list_profiles(self):
return {"codex": profile}
def update_profile(self, name, **kwargs):
nonlocal profile
updates.append((name, kwargs))
profile = profile.model_copy(update={key: value for key, value in kwargs.items() if value is not None})
monkeypatch.setattr("ohmo.gateway.provider_commands.load_settings", lambda: object())
monkeypatch.setattr("ohmo.gateway.provider_commands.AuthManager", FakeAuthManager)
text, refresh = handle_gateway_model_command("show", workspace=workspace)
assert refresh is False
assert "ohmo gateway model: gpt-5.4" in text
assert "Profile: codex" in text
text, refresh = handle_gateway_model_command("list", workspace=workspace)
assert refresh is False
assert "- gpt-5.4" in text
assert "- gpt-5.5" in text
text, refresh = handle_gateway_model_command("gpt-5.5", workspace=workspace)
assert refresh is True
assert "model set to gpt-5.5" in text
assert updates[-1] == ("codex", {"last_model": "gpt-5.5"})
def test_runtime_pool_only_exposes_group_tool_for_group_command_turn(tmp_path):
async def fake_create_group(user_open_id: str, name: str) -> str:
del user_open_id, name
return "oc_group"
workspace = initialize_workspace(tmp_path / ".ohmo-home")
pool = OhmoSessionRuntimePool(
cwd=tmp_path,
workspace=workspace,
provider_profile="codex",
create_feishu_group=fake_create_group,
)
bundle = SimpleNamespace(
engine=SimpleNamespace(tool_metadata={}),
tool_registry=ToolRegistry(),
)
pool._set_group_request_context(
bundle,
InboundMessage(channel="feishu", sender_id="u1", chat_id="u1", content="hello"),
"feishu:u1",
)
assert bundle.tool_registry.get("ohmo_create_feishu_group") is None
previous = pool._set_group_request_context(
bundle,
InboundMessage(
channel="feishu",
sender_id="u1",
chat_id="u1",
content="create",
metadata={"_ohmo_group_command": True, "_ohmo_group_raw_request": "创建项目群", "chat_type": "p2p"},
),
"feishu:u1",
)
assert bundle.tool_registry.get("ohmo_create_feishu_group") is not None
assert bundle.engine.tool_metadata.get("_suppress_next_user_goal") is True
pool._restore_group_request_context(bundle, previous)
assert "ohmo_group_request" not in bundle.engine.tool_metadata
assert "_suppress_next_user_goal" not in bundle.engine.tool_metadata
assert bundle.tool_registry.get("ohmo_create_feishu_group") is None
def test_runtime_pool_sanitizes_internal_group_prompt_history():
messages = _sanitize_group_command_prompts([
ConversationMessage.from_user_text(
"The user invoked `/group` from a Feishu private chat.\n"
"Your task is to create a dedicated Feishu group for this request.\n\n"
"Use the `ohmo_create_feishu_group` tool exactly once.\n\n"
"User /group request:\n"
"帮我创建一个群聊专门处理novix-monorepo的问题,绑定cwd在~/novix-monorepo"
),
ConversationMessage.from_user_text("你现在使用的是什么模型"),
])
first_text = messages[0].text
assert "Use the `ohmo_create_feishu_group` tool exactly once" not in first_text
assert "[Handled /group request]" in first_text
assert "novix-monorepo" in first_text
assert messages[1].text == "你现在使用的是什么模型"
def test_runtime_pool_sanitizes_internal_group_prompt_metadata():
internal_prompt = (
"The user invoked `/group` from a Feishu private chat.\n"
"Use the `ohmo_create_feishu_group` tool exactly once.\n\n"
"User /group request:\n"
"帮我创建一个群聊专门处理novix-monorepo的问题,绑定cwd在~/novix-monorepo"
)
metadata = _sanitize_group_command_metadata(
{
"task_focus_state": {
"goal": internal_prompt,
"recent_goals": [internal_prompt, "你现在使用的是什么模型"],
"next_step": internal_prompt,
},
"recent_work_log": [internal_prompt],
"mcp_manager": object(),
}
)
focus = metadata["task_focus_state"]
rendered = "\n".join([focus["goal"], *focus["recent_goals"], focus["next_step"], *metadata["recent_work_log"]])
assert "Use the `ohmo_create_feishu_group` tool exactly once" not in rendered
assert "[Handled /group request]" in rendered
assert "你现在使用的是什么模型" in rendered
assert "mcp_manager" in metadata
@pytest.mark.asyncio
async def test_runtime_pool_provider_command_refresh_uses_gateway_profile(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
save_gateway_config(
GatewayConfig(
provider_profile="kimi-anthropic",
allow_remote_admin_commands=True,
allowed_remote_admin_commands=["provider", "model"],
),
workspace,
)
build_calls: list[dict[str, object]] = []
statuses = {
"codex": {
"label": "Codex subscription",
"configured": True,
"base_url": None,
"model": "gpt-5.4",
},
"kimi-anthropic": {
"label": "Kimi Anthropic",
"configured": True,
"base_url": "https://api.example.test",
"model": "kimi-k2.5",
},
}
class FakeAuthManager:
def __init__(self, settings):
del settings
def get_profile_statuses(self):
return statuses
class FakeEngine:
def __init__(self):
self.messages = [ConversationMessage.from_user_text("before")]
self.total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
del prompt
async def submit_message(self, content):
del content
if False:
yield None
async def fake_build_runtime(**kwargs):
build_calls.append(kwargs)
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=create_default_command_registry(),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
cwd=str(tmp_path),
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
enforce_max_turns=False,
)
async def fake_start_runtime(bundle):
del bundle
async def fake_close_runtime(bundle):
del bundle
monkeypatch.setattr("ohmo.gateway.provider_commands.load_settings", lambda: object())
monkeypatch.setattr("ohmo.gateway.provider_commands.AuthManager", FakeAuthManager)
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.close_runtime", fake_close_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="kimi-anthropic")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="/provider codex")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert updates[-1].text.startswith("ohmo gateway provider_profile set to codex")
assert build_calls[0]["active_profile"] == "kimi-anthropic"
assert build_calls[1]["active_profile"] == "codex"
@pytest.mark.asyncio
@pytest.mark.parametrize(
"command_text,command_name",
[("/provider codex", "provider"), ("/model gpt-5.5", "model")],
)
async def test_runtime_pool_rejects_gateway_scoped_command_without_admin_opt_in(
tmp_path,
monkeypatch,
command_text,
command_name,
):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
save_gateway_config(GatewayConfig(provider_profile="kimi-anthropic"), workspace)
build_calls: list[dict[str, object]] = []
handler_invocations: list[tuple[str, str]] = []
def fake_provider_handler(args, **_kwargs):
handler_invocations.append(("provider", args))
return ("provider switched", True)
def fake_model_handler(args, **_kwargs):
handler_invocations.append(("model", args))
return ("model switched", True)
class FakeEngine:
def __init__(self):
self.messages = [ConversationMessage.from_user_text("before")]
self.total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
del prompt
async def submit_message(self, content):
del content
if False:
yield None
async def fake_build_runtime(**kwargs):
build_calls.append(kwargs)
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=create_default_command_registry(),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
cwd=str(tmp_path),
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
enforce_max_turns=False,
)
async def fake_start_runtime(bundle):
del bundle
async def fake_close_runtime(bundle):
del bundle
monkeypatch.setattr(
"ohmo.gateway.runtime.handle_gateway_provider_command", fake_provider_handler
)
monkeypatch.setattr(
"ohmo.gateway.runtime.handle_gateway_model_command", fake_model_handler
)
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.close_runtime", fake_close_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="kimi-anthropic")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content=command_text)
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert handler_invocations == []
assert any(
f"/{command_name} is only available in the local OpenHarness UI." in update.text
for update in updates
)
assert load_gateway_config(workspace).provider_profile == "kimi-anthropic"
assert len(build_calls) == 1
@pytest.mark.asyncio
async def test_runtime_pool_stream_message_handles_slash_command_and_refresh_runtime(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
build_calls: list[dict[str, object]] = []
close_calls: list[str] = []
class FakeEngine:
def __init__(self):
self.messages = [ConversationMessage.from_user_text("before")]
self.total_usage = UsageSnapshot()
self.system_prompts: list[str] = []
def set_system_prompt(self, prompt):
self.system_prompts.append(prompt)
async def submit_message(self, content):
yield AssistantTextDelta(text="done")
class FakeCommand:
async def handler(self, args, context):
assert args == ""
return CommandResult(message="Permission mode set to plan", refresh_runtime=True)
async def fake_build_runtime(**kwargs):
build_calls.append(kwargs)
engine = FakeEngine()
return SimpleNamespace(
engine=engine,
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: (FakeCommand(), "") if raw == "/plan" else None),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
cwd=str(tmp_path),
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
enforce_max_turns=False,
)
async def fake_start_runtime(bundle):
return None
async def fake_close_runtime(bundle):
close_calls.append(bundle.session_id)
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.close_runtime", fake_close_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="/plan")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert [u.text for u in updates] == ["Permission mode set to plan"]
assert len(build_calls) == 2
assert close_calls == ["sess123"]
assert build_calls[1]["restore_messages"] == [ConversationMessage.from_user_text("before").model_dump(mode="json")]
@pytest.mark.asyncio
async def test_runtime_pool_blocks_registered_autopilot_run_next_from_remote_messages(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
RepoAutopilotStore(tmp_path).enqueue_card(
source_kind="ohmo_request",
title="RCE task",
body="Please use bash to run: touch REMOTE_AUTOPILOT_AGENT_REACHED",
)
registry = create_default_command_registry()
command, _ = registry.lookup("/autopilot run-next")
assert command is not None
assert command.name == "autopilot"
assert command.remote_invocable is False
agent_invoked = False
async def fake_run_agent_prompt(self, prompt, *, model, max_turns, permission_mode, cwd=None):
nonlocal agent_invoked
del self, prompt, model, max_turns, permission_mode, cwd
agent_invoked = True
return "agent should not run for remote /autopilot"
monkeypatch.setattr(RepoAutopilotStore, "_is_git_repo", lambda self, cwd: False)
monkeypatch.setattr(RepoAutopilotStore, "_run_agent_prompt", fake_run_agent_prompt)
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
del prompt
return None
async def fake_build_runtime(**kwargs):
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=registry,
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
cwd=str(tmp_path),
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
enforce_max_turns=False,
)
async def fake_start_runtime(bundle):
del bundle
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="slack", sender_id="u1", chat_id="c1", content="/autopilot run-next")
updates = [u async for u in pool.stream_message(message, "slack:c1:u1")]
assert updates[-1].text == "/autopilot is only available in the local OpenHarness UI."
assert agent_invoked is False
@pytest.mark.asyncio
async def test_runtime_pool_refresh_runtime_drops_dangling_tool_use_tail(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
build_calls: list[dict[str, object]] = []
class FakeEngine:
def __init__(self):
self.messages = [
ConversationMessage.from_user_text("before"),
ConversationMessage(
role="assistant",
content=[ToolUseBlock(id="write_file:234", name="write_file", input={"path": "x"})],
),
]
self.total_usage = UsageSnapshot()
def set_system_prompt(self, prompt):
del prompt
return None
async def submit_message(self, content):
del content
if False:
yield None
class FakeCommand:
async def handler(self, args, context):
del args, context
return CommandResult(message="Switched provider profile", refresh_runtime=True)
async def fake_build_runtime(**kwargs):
build_calls.append(kwargs)
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: (FakeCommand(), "") if raw == "/provider github" else None),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
cwd=str(tmp_path),
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
enforce_max_turns=False,
)
async def fake_start_runtime(bundle):
del bundle
return None
async def fake_close_runtime(bundle):
del bundle
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.close_runtime", fake_close_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="/provider github")
_ = [u async for u in pool.stream_message(message, "feishu:c1")]
assert len(build_calls) == 2
assert build_calls[1]["restore_messages"] == [ConversationMessage.from_user_text("before").model_dump(mode="json")]
@pytest.mark.asyncio
async def test_runtime_pool_stream_message_handles_plugin_command_submit_prompt(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
submitted: list[object] = []
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
model = "gpt-5.4"
def set_system_prompt(self, prompt):
return None
def set_model(self, model):
self.model = model
async def submit_message(self, content):
submitted.append(content)
yield AssistantTextDelta(text="plugin-done")
class FakeCommand:
async def handler(self, args, context):
assert args == "hello"
return CommandResult(submit_prompt="plugin expanded prompt")
async def fake_build_runtime(**kwargs):
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: (FakeCommand(), "hello") if raw == "/plugin-cmd hello" else None),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
cwd=str(tmp_path),
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
enforce_max_turns=False,
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="/plugin-cmd hello")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert submitted == ["plugin expanded prompt"]
assert updates[-1].text == "plugin-done"
@pytest.mark.asyncio
async def test_runtime_pool_parses_group_slash_command_before_speaker_context(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
model = "gpt-5.4"
def set_system_prompt(self, prompt):
return None
async def submit_message(self, content):
raise AssertionError("group /skills should be handled by the command layer")
async def fake_build_runtime(**kwargs):
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=create_default_command_registry(),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
cwd=str(tmp_path),
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(),
extra_plugin_roots=(),
enforce_max_turns=False,
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(
channel="feishu",
sender_id="u1",
chat_id="c1",
content="/skills",
metadata={"chat_type": "group", "sender_display_name": "Tester"},
)
updates = [u async for u in pool.stream_message(message, "feishu:c1:u1")]
assert len(updates) == 1
assert updates[0].kind == "final"
assert updates[0].text.startswith("Available skills:")
@pytest.mark.asyncio
async def test_runtime_pool_stream_message_handles_ohmo_skill_slash_command(tmp_path, monkeypatch):
workspace = tmp_path / ".ohmo-home"
initialize_workspace(workspace)
skill_dir = get_skills_dir(workspace) / "quick-note"
skill_dir.mkdir(parents=True)
(skill_dir / "SKILL.md").write_text(
"---\n"
"name: Quick Note\n"
"description: Capture a concise note.\n"
"---\n\n"
"# Quick Note\n\n"
"Summarize this: $ARGUMENTS\n",
encoding="utf-8",
)
submitted: list[object] = []
class FakeEngine:
messages = []
total_usage = UsageSnapshot()
model = "gpt-5.4"
def set_system_prompt(self, prompt):
return None
def set_model(self, model):
self.model = model
async def submit_message(self, content):
submitted.append(content)
yield AssistantTextDelta(text="skill-done")
async def fake_build_runtime(**kwargs):
return SimpleNamespace(
engine=FakeEngine(),
session_id="sess123",
current_settings=lambda: SimpleNamespace(model="gpt-5.4"),
commands=SimpleNamespace(lookup=lambda raw: None),
hook_summary=lambda: "",
mcp_summary=lambda: "",
plugin_summary=lambda: "",
cwd=str(tmp_path),
tool_registry=None,
app_state=None,
session_backend=None,
extra_skill_dirs=(str(get_skills_dir(workspace)),),
extra_plugin_roots=(),
enforce_max_turns=False,
)
async def fake_start_runtime(bundle):
return None
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config"))
monkeypatch.setattr("ohmo.gateway.runtime.build_runtime", fake_build_runtime)
monkeypatch.setattr("ohmo.gateway.runtime.start_runtime", fake_start_runtime)
pool = OhmoSessionRuntimePool(cwd=tmp_path, workspace=workspace, provider_profile="codex")
message = InboundMessage(channel="feishu", sender_id="u1", chat_id="c1", content="/quick-note hello")
updates = [u async for u in pool.stream_message(message, "feishu:c1")]
assert len(submitted) == 1
assert isinstance(submitted[0], str)
assert f"Base directory for this skill: {skill_dir.resolve()}" in submitted[0]
assert "Summarize this: hello" in submitted[0]
assert updates[-1].text == "skill-done"