refactor(core): remove redundant runtime scaffolding (#5127)

This commit is contained in:
chengyongru
2026-07-28 11:07:58 +08:00
committed by GitHub
parent 4c77126b3d
commit ef9e687f19
19 changed files with 89 additions and 312 deletions
+19
View File
@@ -369,6 +369,25 @@ class TestBuildMessages:
assert messages[1]["role"] == "user"
assert "hello" in str(messages[1]["content"])
def test_public_builder_preserves_assistant_role_compatibility(self, tmp_path):
from nanobot.agent import ContextBuilder as PublicContextBuilder
builder = PublicContextBuilder(tmp_path)
messages = builder.build_messages(
history=[{"role": "assistant", "content": "previous result"}],
current_message="subagent result",
current_role="assistant",
runtime_context_blocks=[
RuntimeContextBlock(source="test", content="user-only runtime context"),
],
)
assert len(messages) == 2
assert messages[-1]["role"] == "assistant"
assert messages[-1]["content"] == "previous result\n\nsubagent result"
assert "user-only runtime context" not in messages[-1]["content"]
assert "_meta" not in messages[-1]
def test_runtime_context_is_not_injected_by_default(self, tmp_path):
builder = _builder(tmp_path)
messages = builder.build_messages([], "hello", channel="cli")
+11 -16
View File
@@ -340,21 +340,6 @@ def test_system_prompt_keeps_message_tool_out_of_current_chat_replies(tmp_path)
assert "Wait for the tool results, then answer once" in prompt
def test_subagent_result_does_not_create_consecutive_assistant_messages(tmp_path) -> None:
workspace = _make_workspace(tmp_path)
builder = ContextBuilder(workspace)
messages = builder.build_messages(
history=[{"role": "assistant", "content": "previous result"}],
current_message="subagent result",
channel="cli",
current_role="assistant",
)
for left, right in zip(messages, messages[1:]):
assert not (left.get("role") == right.get("role") == "assistant")
def test_memory_skill_is_lazy_loaded_from_skills_index(tmp_path) -> None:
"""Memory search guidance should be discoverable without loading its full body."""
workspace = _make_workspace(tmp_path)
@@ -398,7 +383,7 @@ def test_template_memory_md_is_skipped(tmp_path) -> None:
assert "This file is automatically updated by nanobot" not in prompt
def test_customized_memory_md_is_injected(tmp_path) -> None:
def test_customized_memory_md_is_injected(tmp_path, monkeypatch) -> None:
"""A Dream-populated MEMORY.md should be injected normally."""
workspace = _make_workspace(tmp_path)
from nanobot.utils.helpers import sync_workspace_templates
@@ -409,7 +394,17 @@ def test_customized_memory_md_is_injected(tmp_path) -> None:
)
builder = ContextBuilder(workspace)
read_memory = builder.memory.read_memory
calls = 0
def tracked_read_memory() -> str:
nonlocal calls
calls += 1
return read_memory()
monkeypatch.setattr(builder.memory, "read_memory", tracked_read_memory)
prompt = builder.build_system_prompt()
assert "# Memory\n\n## Long-term Memory" in prompt
assert "User prefers dark mode" in prompt
assert calls == 1
+3 -54
View File
@@ -34,7 +34,7 @@ from nanobot.session.keys import (
LAST_CHANNEL_METADATA_KEY,
UNIFIED_SESSION_KEY,
)
from nanobot.session.manager import Session, SessionManager
from nanobot.session.manager import Session
from nanobot.session.turn_continuation import (
INTERNAL_CONTINUATION_META,
INTERNAL_CONTINUATION_RUN_STARTED_AT_META,
@@ -49,7 +49,6 @@ from nanobot.session.webui_turns import (
maybe_generate_webui_title,
)
from nanobot.triggers.local_session_turns import LOCAL_TRIGGER_META
from nanobot.utils.llm_runtime import LLMRuntime
def _mk_loop() -> AgentLoop:
@@ -314,55 +313,6 @@ async def test_generate_webui_title_ignores_cron_internal_turns(tmp_path: Path)
loop.provider.chat_with_retry.assert_not_awaited()
def test_webui_title_update_uses_captured_llm_runtime(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
bus = MessageBus()
sessions = SessionManager(tmp_path)
scheduled: list[object] = []
captured: dict[str, object] = {}
async def fake_title_after_turn(**kwargs: object) -> bool:
captured.update(kwargs)
return False
monkeypatch.setattr(
"nanobot.session.webui_turns.maybe_generate_webui_title_after_turn",
fake_title_after_turn,
)
coordinator = WebuiTurnCoordinator(
bus=bus,
sessions=sessions,
schedule_background=lambda coro: scheduled.append(coro),
)
provider = MagicMock()
msg = InboundMessage(
channel="websocket",
sender_id="u1",
chat_id="chat1",
content="say hello",
metadata={"webui": True},
)
coordinator.capture_title_context(
"websocket:chat1",
msg,
LLMRuntime.capture(provider, "turn-model", context_window_tokens=32_768),
)
asyncio.run(coordinator.handle_turn_end(
msg,
session_key="websocket:chat1",
latency_ms=None,
))
assert len(scheduled) == 1
asyncio.run(scheduled[0]) # type: ignore[arg-type]
assert captured["provider"] is provider
assert captured["model"] == "turn-model"
def test_save_turn_keeps_multimodal_runtime_context_for_model_replay() -> None:
loop = _mk_loop()
session = Session(key="test:runtime-only")
@@ -1386,7 +1336,7 @@ async def test_stop_preserves_runtime_checkpoint_for_next_turn(tmp_path: Path) -
first_msg = InboundMessage(channel="feishu", sender_id="u1", chat_id="c4", content="keep progress")
task = asyncio.create_task(loop._process_message(first_msg))
loop._active_tasks[first_msg.session_key] = [task]
loop._active_tasks[first_msg.session_key] = {task}
await asyncio.wait_for(checkpoint_saved.wait(), timeout=1.0)
stop_msg = InboundMessage(channel="feishu", sender_id="u1", chat_id="c4", content="/stop")
@@ -1451,7 +1401,7 @@ async def test_system_subagent_followup_is_persisted_before_prompt_assembly(tmp_
runtime = loop.llm_runtime()
seen: dict[str, object] = {}
record_runtime = MagicMock(wraps=loop._runtime_events().record_turn_runtime)
record_runtime = MagicMock(wraps=loop.runtime_event_publisher.record_turn_runtime)
loop.runtime_event_publisher.record_turn_runtime = record_runtime
async def fake_run_agent_loop(initial_messages, **kwargs):
@@ -1691,7 +1641,6 @@ def test_subagent_followup_uses_user_model_input_and_assistant_history(tmp_path:
projected = builder.build_messages(
history=history,
current_message="subagent result",
current_role="user",
channel="cli",
)
+5 -6
View File
@@ -10,7 +10,6 @@ import pytest
from agent.runner_helpers import make_run_spec
from nanobot.agent.context_governance import (
BACKFILL_CONTENT,
MICROCOMPACT_KEEP_RECENT,
ContextGovernanceConfig,
ContextGovernor,
)
@@ -495,7 +494,7 @@ def test_microcompact_skips_when_prompt_under_hard_budget(monkeypatch):
tools = MagicMock()
tools.get_definitions.return_value = []
total = MICROCOMPACT_KEEP_RECENT + 5
total = 15
long_content = "x" * 600
messages = _microcompact_messages(total=total, tool_name="read_file", content=long_content)
spec = make_run_spec(provider,
@@ -529,7 +528,7 @@ def test_microcompact_overflow_compacts_to_low_watermark(monkeypatch):
tools = MagicMock()
tools.get_definitions.return_value = []
total = MICROCOMPACT_KEEP_RECENT + 8
total = 18
long_content = "x" * 600
messages = _microcompact_messages(total=total, tool_name="read_file", content=long_content)
spec = make_run_spec(provider,
@@ -617,7 +616,7 @@ def test_context_governor_keeps_compaction_boundary_stable(monkeypatch):
tools = MagicMock()
tools.get_definitions.return_value = []
total = MICROCOMPACT_KEEP_RECENT + 8
total = 18
long_content = "x" * 600
messages = _microcompact_messages(total=total, tool_name="read_file", content=long_content)
spec = make_run_spec(provider,
@@ -658,7 +657,7 @@ def test_microcompact_preserves_short_results(monkeypatch):
tools = MagicMock()
tools.get_definitions.return_value = []
total = MICROCOMPACT_KEEP_RECENT + 5
total = 15
messages = _microcompact_messages(total=total, tool_name="exec", content="short")
spec = make_run_spec(provider,
initial_messages=messages,
@@ -690,7 +689,7 @@ def test_microcompact_skips_non_compactable_tools(monkeypatch):
tools = MagicMock()
tools.get_definitions.return_value = []
total = MICROCOMPACT_KEEP_RECENT + 5
total = 15
long_content = "y" * 1000
messages = _microcompact_messages(total=total, tool_name="message", content=long_content)
spec = make_run_spec(provider,
+2 -2
View File
@@ -73,7 +73,7 @@ class TestHandleStop:
task = asyncio.create_task(slow_task())
await asyncio.sleep(0)
loop._active_tasks["test:c1"] = [task]
loop._active_tasks["test:c1"] = {task}
msg = InboundMessage(channel="test", sender_id="u1", chat_id="c1", content="/stop")
ctx = CommandContext(msg=msg, session=None, key=msg.session_key, raw="/stop", loop=loop)
@@ -100,7 +100,7 @@ class TestHandleStop:
tasks = [asyncio.create_task(slow(i)) for i in range(2)]
await asyncio.sleep(0)
loop._active_tasks["test:c1"] = tasks
loop._active_tasks["test:c1"] = set(tasks)
msg = InboundMessage(channel="test", sender_id="u1", chat_id="c1", content="/stop")
ctx = CommandContext(msg=msg, session=None, key=msg.session_key, raw="/stop", loop=loop)
+4 -4
View File
@@ -454,7 +454,7 @@ class TestStopCommandWithUnifiedSession:
# Simulate the task creation flow (from _run loop)
effective_key = UNIFIED_SESSION_KEY if loop._unified_session and not msg.session_key_override else msg.session_key
task = asyncio.create_task(loop._dispatch(msg))
loop._active_tasks.setdefault(effective_key, []).append(task)
loop._active_tasks.setdefault(effective_key, set()).add(task)
# Wait for task to complete
await task
@@ -475,7 +475,7 @@ class TestStopCommandWithUnifiedSession:
await asyncio.sleep(10) # Will be cancelled
task = asyncio.create_task(long_running())
loop._active_tasks[UNIFIED_SESSION_KEY] = [task]
loop._active_tasks[UNIFIED_SESSION_KEY] = {task}
# Create a message that would have session_key=UNIFIED_SESSION_KEY after dispatch
msg = InboundMessage(
@@ -506,7 +506,7 @@ class TestStopCommandWithUnifiedSession:
await asyncio.sleep(10)
task = asyncio.create_task(long_running())
loop._active_tasks[UNIFIED_SESSION_KEY] = [task]
loop._active_tasks[UNIFIED_SESSION_KEY] = {task}
msg = InboundMessage(
channel="telegram",
chat_id="123456",
@@ -533,7 +533,7 @@ class TestStopCommandWithUnifiedSession:
task1 = asyncio.create_task(long_running())
task2 = asyncio.create_task(long_running())
loop._active_tasks[UNIFIED_SESSION_KEY] = [task1, task2]
loop._active_tasks[UNIFIED_SESSION_KEY] = {task1, task2}
# /stop from discord should cancel tasks started from telegram
msg = InboundMessage(