fix(command): explain empty dream runs
This commit is contained in:
@@ -326,7 +326,8 @@ async def cmd_dream(ctx: CommandContext) -> OutboundMessage:
|
|||||||
if result is None:
|
if result is None:
|
||||||
await loop.bus.publish_outbound(OutboundMessage(
|
await loop.bus.publish_outbound(OutboundMessage(
|
||||||
channel=msg.channel, chat_id=msg.chat_id,
|
channel=msg.channel, chat_id=msg.chat_id,
|
||||||
content="Dream: nothing to process.",
|
content=_format_dream_no_input_message(),
|
||||||
|
metadata={"render_as": "text"},
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
prompt, last_cursor = result
|
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]:
|
def _extract_changed_files(diff: str) -> list[str]:
|
||||||
"""Extract changed file paths from a unified diff."""
|
"""Extract changed file paths from a unified diff."""
|
||||||
files: list[str] = []
|
files: list[str] = []
|
||||||
|
|||||||
@@ -1,23 +1,32 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from nanobot.bus.events import InboundMessage
|
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.command.router import CommandContext
|
||||||
from nanobot.utils.gitstore import CommitInfo
|
from nanobot.utils.gitstore import CommitInfo
|
||||||
|
|
||||||
|
|
||||||
class _FakeStore:
|
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.git = git
|
||||||
self._last_dream_cursor = last_dream_cursor
|
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:
|
def get_last_dream_cursor(self) -> int:
|
||||||
return self._last_dream_cursor
|
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:
|
class _FakeGit:
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -45,6 +54,17 @@ class _FakeGit:
|
|||||||
def revert(self, sha: str) -> str | None:
|
def revert(self, sha: str) -> str | None:
|
||||||
return self._revert_result
|
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:
|
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)
|
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)
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_dream_log_latest_is_more_user_friendly() -> None:
|
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")
|
commit = CommitInfo(sha="abcd1234", message="dream: 2026-04-04, 2 change(s)", timestamp="2026-04-04 12:00")
|
||||||
|
|||||||
Reference in New Issue
Block a user