mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-19 16:12:30 +00:00
fix(dream): restore cursor with memory state
Track the Dream cursor in memory versioning so restores do not skip history after rolling back Dream commits. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
ac18a8baad
commit
44a341335a
@ -8,19 +8,24 @@ import os
|
||||
import re
|
||||
import weakref
|
||||
from contextlib import suppress
|
||||
import tiktoken
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterator
|
||||
|
||||
import tiktoken
|
||||
from loguru import logger
|
||||
|
||||
from nanobot.utils.prompt_templates import render_template
|
||||
from nanobot.utils.helpers import ensure_dir, estimate_message_tokens, estimate_prompt_tokens_chain, strip_think, truncate_text
|
||||
|
||||
from nanobot.agent.runner import AgentRunSpec, AgentRunner
|
||||
from nanobot.agent.runner import AgentRunner, AgentRunSpec
|
||||
from nanobot.agent.tools.registry import ToolRegistry
|
||||
from nanobot.utils.gitstore import GitStore
|
||||
from nanobot.utils.helpers import (
|
||||
ensure_dir,
|
||||
estimate_message_tokens,
|
||||
estimate_prompt_tokens_chain,
|
||||
strip_think,
|
||||
truncate_text,
|
||||
)
|
||||
from nanobot.utils.prompt_templates import render_template
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nanobot.providers.base import LLMProvider
|
||||
@ -55,7 +60,7 @@ class MemoryStore:
|
||||
self._corruption_logged = False # rate-limit non-int cursor warning
|
||||
self._oversize_logged = False # rate-limit oversized-entry warning
|
||||
self._git = GitStore(workspace, tracked_files=[
|
||||
"SOUL.md", "USER.md", "memory/MEMORY.md",
|
||||
"SOUL.md", "USER.md", "memory/MEMORY.md", "memory/.dream_cursor",
|
||||
])
|
||||
self._maybe_migrate_legacy_history()
|
||||
|
||||
@ -350,7 +355,7 @@ class MemoryStore:
|
||||
read_size = min(size, 4096)
|
||||
f.seek(size - read_size)
|
||||
data = f.read().decode("utf-8")
|
||||
lines = [l for l in data.split("\n") if l.strip()]
|
||||
lines = [line for line in data.split("\n") if line.strip()]
|
||||
if not lines:
|
||||
return None
|
||||
return json.loads(lines[-1])
|
||||
@ -780,7 +785,7 @@ class Dream:
|
||||
|
||||
from nanobot.agent.skills import BUILTIN_SKILLS_DIR
|
||||
|
||||
_DESC_RE = _re.compile(r"^description:\s*(.+)$", _re.MULTILINE | _re.IGNORECASE)
|
||||
desc_re = _re.compile(r"^description:\s*(.+)$", _re.MULTILINE | _re.IGNORECASE)
|
||||
entries: dict[str, str] = {}
|
||||
for base in (self.store.workspace / "skills", BUILTIN_SKILLS_DIR):
|
||||
if not base.exists():
|
||||
@ -795,7 +800,7 @@ class Dream:
|
||||
if d.name in entries and base == BUILTIN_SKILLS_DIR:
|
||||
continue
|
||||
content = skill_md.read_text(encoding="utf-8")[:500]
|
||||
m = _DESC_RE.search(content)
|
||||
m = desc_re.search(content)
|
||||
desc = m.group(1).strip() if m else "(no description)"
|
||||
entries[d.name] = desc
|
||||
return [f"{name} — {desc}" for name, desc in sorted(entries.items())]
|
||||
|
||||
@ -5,7 +5,7 @@ from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from nanobot.agent.memory import MemoryStore, _HISTORY_ENTRY_HARD_CAP
|
||||
from nanobot.agent.memory import _HISTORY_ENTRY_HARD_CAP, MemoryStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -241,6 +241,26 @@ class TestDreamCursor:
|
||||
store2 = MemoryStore(store.workspace)
|
||||
assert store2.get_last_dream_cursor() == 3
|
||||
|
||||
def test_git_restore_rolls_back_dream_cursor(self, tmp_path):
|
||||
store = MemoryStore(tmp_path)
|
||||
store.write_memory("before")
|
||||
store.set_last_dream_cursor(1)
|
||||
assert store.git.init() is True
|
||||
|
||||
store.write_memory("after")
|
||||
store.set_last_dream_cursor(2)
|
||||
dream_sha = store.git.auto_commit("dream: update")
|
||||
assert dream_sha is not None
|
||||
|
||||
store.write_memory("newer")
|
||||
store.set_last_dream_cursor(3)
|
||||
|
||||
restore_sha = store.git.revert(dream_sha)
|
||||
|
||||
assert restore_sha is not None
|
||||
assert store.read_memory() == "before"
|
||||
assert store.get_last_dream_cursor() == 1
|
||||
|
||||
|
||||
class TestLegacyHistoryMigration:
|
||||
def test_read_unprocessed_history_handles_entries_without_cursor(self, store):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user