feat(memory): harden legacy history migration and Dream UX

This commit is contained in:
Xubin Ren
2026-04-04 08:41:46 +00:00
parent 7e0c196797
commit 6e896249c8
7 changed files with 629 additions and 38 deletions
+97 -14
View File
@@ -104,6 +104,78 @@ async def cmd_dream(ctx: CommandContext) -> OutboundMessage:
)
def _extract_changed_files(diff: str) -> list[str]:
"""Extract changed file paths from a unified diff."""
files: list[str] = []
seen: set[str] = set()
for line in diff.splitlines():
if not line.startswith("diff --git "):
continue
parts = line.split()
if len(parts) < 4:
continue
path = parts[3]
if path.startswith("b/"):
path = path[2:]
if path in seen:
continue
seen.add(path)
files.append(path)
return files
def _format_changed_files(diff: str) -> str:
files = _extract_changed_files(diff)
if not files:
return "No tracked memory files changed."
return ", ".join(f"`{path}`" for path in files)
def _format_dream_log_content(commit, diff: str, *, requested_sha: str | None = None) -> str:
files_line = _format_changed_files(diff)
lines = [
"## Dream Update",
"",
"Here is the selected Dream memory change." if requested_sha else "Here is the latest Dream memory change.",
"",
f"- Commit: `{commit.sha}`",
f"- Time: {commit.timestamp}",
f"- Changed files: {files_line}",
]
if diff:
lines.extend([
"",
f"Use `/dream-restore {commit.sha}` to undo this change.",
"",
"```diff",
diff.rstrip(),
"```",
])
else:
lines.extend([
"",
"Dream recorded this version, but there is no file diff to display.",
])
return "\n".join(lines)
def _format_dream_restore_list(commits: list) -> str:
lines = [
"## Dream Restore",
"",
"Choose a Dream memory version to restore. Latest first:",
"",
]
for c in commits:
lines.append(f"- `{c.sha}` {c.timestamp} - {c.message.splitlines()[0]}")
lines.extend([
"",
"Preview a version with `/dream-log <sha>` before restoring it.",
"Restore a version with `/dream-restore <sha>`.",
])
return "\n".join(lines)
async def cmd_dream_log(ctx: CommandContext) -> OutboundMessage:
"""Show what the last Dream changed.
@@ -115,9 +187,9 @@ async def cmd_dream_log(ctx: CommandContext) -> OutboundMessage:
if not git.is_initialized():
if store.get_last_dream_cursor() == 0:
msg = "Dream has not run yet."
msg = "Dream has not run yet. Run `/dream`, or wait for the next scheduled Dream cycle."
else:
msg = "Git not initialized for memory files."
msg = "Dream history is not available because memory versioning is not initialized."
return OutboundMessage(
channel=ctx.msg.channel, chat_id=ctx.msg.chat_id,
content=msg, metadata={"render_as": "text"},
@@ -130,19 +202,23 @@ async def cmd_dream_log(ctx: CommandContext) -> OutboundMessage:
sha = args.split()[0]
result = git.show_commit_diff(sha)
if not result:
content = f"Commit `{sha}` not found."
content = (
f"Couldn't find Dream change `{sha}`.\n\n"
"Use `/dream-restore` to list recent versions, "
"or `/dream-log` to inspect the latest one."
)
else:
commit, diff = result
content = commit.format(diff)
content = _format_dream_log_content(commit, diff, requested_sha=sha)
else:
# Default: show the latest commit's diff
commits = git.log(max_entries=1)
result = git.show_commit_diff(commits[0].sha) if commits else None
if result:
commit, diff = result
content = commit.format(diff)
content = _format_dream_log_content(commit, diff)
else:
content = "No commits yet."
content = "Dream memory has no saved versions yet."
return OutboundMessage(
channel=ctx.msg.channel, chat_id=ctx.msg.chat_id,
@@ -162,7 +238,7 @@ async def cmd_dream_restore(ctx: CommandContext) -> OutboundMessage:
if not git.is_initialized():
return OutboundMessage(
channel=ctx.msg.channel, chat_id=ctx.msg.chat_id,
content="Git not initialized for memory files.",
content="Dream history is not available because memory versioning is not initialized.",
)
args = ctx.args.strip()
@@ -170,19 +246,26 @@ async def cmd_dream_restore(ctx: CommandContext) -> OutboundMessage:
# Show recent commits for the user to pick
commits = git.log(max_entries=10)
if not commits:
content = "No commits found."
content = "Dream memory has no saved versions to restore yet."
else:
lines = ["## Recent Dream Commits\n", "Use `/dream-restore <sha>` to revert a commit.\n"]
for c in commits:
lines.append(f"- `{c.sha}` {c.message.splitlines()[0]} ({c.timestamp})")
content = "\n".join(lines)
content = _format_dream_restore_list(commits)
else:
sha = args.split()[0]
result = git.show_commit_diff(sha)
changed_files = _format_changed_files(result[1]) if result else "the tracked memory files"
new_sha = git.revert(sha)
if new_sha:
content = f"Reverted commit `{sha}` → new commit `{new_sha}`."
content = (
f"Restored Dream memory to the state before `{sha}`.\n\n"
f"- New safety commit: `{new_sha}`\n"
f"- Restored files: {changed_files}\n\n"
f"Use `/dream-log {new_sha}` to inspect the restore diff."
)
else:
content = f"Failed to revert commit `{sha}`. Check if the SHA is correct."
content = (
f"Couldn't restore Dream change `{sha}`.\n\n"
"It may not exist, or it may be the first saved version with no earlier state to restore."
)
return OutboundMessage(
channel=ctx.msg.channel, chat_id=ctx.msg.chat_id,
content=content, metadata={"render_as": "text"},