fix(command): explain empty dream runs

This commit is contained in:
chengyongru
2026-06-17 00:47:59 +08:00
committed by Xubin Ren
parent dcb33cf919
commit dcf76117ab
2 changed files with 73 additions and 3 deletions
+19 -1
View File
@@ -326,7 +326,8 @@ async def cmd_dream(ctx: CommandContext) -> OutboundMessage:
if result is None:
await loop.bus.publish_outbound(OutboundMessage(
channel=msg.channel, chat_id=msg.chat_id,
content="Dream: nothing to process.",
content=_format_dream_no_input_message(),
metadata={"render_as": "text"},
))
return
prompt, last_cursor = result
@@ -374,6 +375,23 @@ async def cmd_dream(ctx: CommandContext) -> OutboundMessage:
)
def _format_dream_no_input_message() -> str:
return "\n".join([
"Dream has no conversation history to process yet.",
"",
"Dream reads new entries from `memory/history.jsonl` after the current Dream cursor.",
(
"Short chats only reach that file after token compaction or idle auto-compact, "
"so a fresh or short WebUI chat may leave Dream with no input."
),
"",
"Next steps:",
"- Enable `agents.defaults.idleCompactAfterMinutes` so completed chats become Dream input automatically.",
"- Compact the current chat into memory once that manual action is available.",
"- If you expected history to exist, check whether `memory/history.jsonl` has new entries after the Dream cursor.",
])
def _extract_changed_files(diff: str) -> list[str]:
"""Extract changed file paths from a unified diff."""
files: list[str] = []
+54 -2
View File
@@ -1,23 +1,32 @@
from __future__ import annotations
import asyncio
from types import SimpleNamespace
import pytest
from nanobot.bus.events import InboundMessage
from nanobot.command.builtin import cmd_dream_log, cmd_dream_restore
from nanobot.command.builtin import cmd_dream, cmd_dream_log, cmd_dream_restore
from nanobot.command.router import CommandContext
from nanobot.utils.gitstore import CommitInfo
class _FakeStore:
def __init__(self, git, last_dream_cursor: int = 1):
def __init__(self, git, last_dream_cursor: int = 1, dream_prompt_result=None):
self.git = git
self._last_dream_cursor = last_dream_cursor
self._dream_prompt_result = dream_prompt_result
self.compact_history_called = False
def get_last_dream_cursor(self) -> int:
return self._last_dream_cursor
def build_dream_prompt(self):
return self._dream_prompt_result
def compact_history(self) -> None:
self.compact_history_called = True
class _FakeGit:
def __init__(
@@ -45,6 +54,17 @@ class _FakeGit:
def revert(self, sha: str) -> str | None:
return self._revert_result
def auto_commit(self, message: str) -> str | None:
return None
class _FakeBus:
def __init__(self):
self.outbound = []
async def publish_outbound(self, message):
self.outbound.append(message)
def _make_ctx(raw: str, git: _FakeGit, *, args: str = "", last_dream_cursor: int = 1) -> CommandContext:
msg = InboundMessage(channel="cli", sender_id="u1", chat_id="direct", content=raw)
@@ -53,6 +73,38 @@ def _make_ctx(raw: str, git: _FakeGit, *, args: str = "", last_dream_cursor: int
return CommandContext(msg=msg, session=None, key=msg.session_key, raw=raw, args=args, loop=loop)
def _make_dream_ctx(tmp_path) -> tuple[CommandContext, _FakeBus]:
msg = InboundMessage(channel="cli", sender_id="u1", chat_id="direct", content="/dream")
store = _FakeStore(_FakeGit(initialized=False), dream_prompt_result=None)
bus = _FakeBus()
sessions_dir = tmp_path / "sessions"
sessions_dir.mkdir()
loop = SimpleNamespace(
bus=bus,
context=SimpleNamespace(memory=store, timezone="UTC"),
sessions=SimpleNamespace(sessions_dir=sessions_dir),
)
ctx = CommandContext(msg=msg, session=None, key=msg.session_key, raw="/dream", args="", loop=loop)
return ctx, bus
@pytest.mark.asyncio
async def test_dream_no_history_explains_how_to_create_input(tmp_path) -> None:
ctx, bus = _make_dream_ctx(tmp_path)
immediate = await cmd_dream(ctx)
await asyncio.sleep(0)
assert immediate.content == "Dreaming..."
assert len(bus.outbound) == 1
content = bus.outbound[0].content
assert "Dream has no conversation history to process yet." in content
assert "`memory/history.jsonl`" in content
assert "idle auto-compact" in content
assert "Dream cursor" in content
assert "agents.defaults.idleCompactAfterMinutes" in content
@pytest.mark.asyncio
async def test_dream_log_latest_is_more_user_friendly() -> None:
commit = CommitInfo(sha="abcd1234", message="dream: 2026-04-04, 2 change(s)", timestamp="2026-04-04 12:00")