fix(migration): resolve workspace files from agents.defaults.workspace

OpenClaw users who started before the rebrand (when the project was
clawd/clawdbot) often have a custom workspace directory configured via
agents.defaults.workspace in openclaw.json (e.g. ~/clawd/ instead of
~/.openclaw/workspace/).

The migration tool only checked hardcoded relative paths (workspace/,
workspace-main/, workspace-assistant/) inside the source root, so files
like MEMORY.md, skills, and daily memory in custom workspaces were
silently skipped.

This change:
- Reads agents.defaults.workspace from openclaw.json at init time
- Uses it as a final fallback in source_candidate() when files aren't
  found in the standard locations
- Standard workspace paths are still preferred (custom is fallback only)
- Custom workspace is only used when it's outside the source_root tree
  (avoids double-matching when workspace/ is the default)

Adds two tests:
- Custom workspace files are discovered and migrated
- Standard workspace location is preferred over custom
This commit is contained in:
in-liberty420
2026-04-12 10:03:55 -03:00
committed by Teknium
parent 8081425a1c
commit 2dfd73a497
2 changed files with 144 additions and 0 deletions

View File

@@ -280,6 +280,102 @@ def test_migrator_records_preset_in_report(tmp_path: Path):
assert report["selection"]["skill_conflict_mode"] == "skip"
def test_source_candidate_finds_files_in_custom_workspace(tmp_path: Path):
"""When agents.defaults.workspace points outside ~/.openclaw, files should
be discovered there as a fallback."""
mod = load_module()
source = tmp_path / ".openclaw"
target = tmp_path / ".hermes"
custom_ws = tmp_path / "my-custom-workspace"
target.mkdir()
source.mkdir()
custom_ws.mkdir()
# No workspace/ directory inside .openclaw — files live in custom workspace
(custom_ws / "MEMORY.md").write_text("# Memory\n\n- custom workspace entry\n", encoding="utf-8")
(custom_ws / "SOUL.md").write_text("# Soul\n\nI am me.\n", encoding="utf-8")
(custom_ws / "skills" / "my-skill").mkdir(parents=True)
(custom_ws / "skills" / "my-skill" / "SKILL.md").write_text(
"---\nname: my-skill\ndescription: test\n---\n\nbody\n",
encoding="utf-8",
)
(custom_ws / "memory").mkdir()
(custom_ws / "memory" / "2026-01-01.md").write_text("- daily note\n", encoding="utf-8")
(source / "openclaw.json").write_text(
json.dumps({"agents": {"defaults": {"workspace": str(custom_ws)}}}),
encoding="utf-8",
)
migrator = mod.Migrator(
source_root=source,
target_root=target,
execute=True,
workspace_target=None,
overwrite=False,
migrate_secrets=False,
output_dir=target / "migration-report",
selected_options={"soul", "memory", "skills", "daily-memory"},
)
report = migrator.migrate()
# SOUL.md should have been found and migrated
assert (target / "SOUL.md").exists()
# MEMORY.md should have been found and migrated
assert (target / "memories" / "MEMORY.md").exists()
mem_content = (target / "memories" / "MEMORY.md").read_text(encoding="utf-8")
assert "custom workspace entry" in mem_content
# Skills should have been found and migrated
imported_skill = target / "skills" / mod.SKILL_CATEGORY_DIRNAME / "my-skill" / "SKILL.md"
assert imported_skill.exists()
migrated_kinds = {item["kind"] for item in report["items"] if item["status"] == "migrated"}
assert "soul" in migrated_kinds
assert "memory" in migrated_kinds
assert "skill" in migrated_kinds
def test_source_candidate_prefers_standard_workspace_over_custom(tmp_path: Path):
"""When files exist in both ~/.openclaw/workspace/ and the custom workspace,
the standard location should win (custom is a fallback only)."""
mod = load_module()
source = tmp_path / ".openclaw"
target = tmp_path / ".hermes"
custom_ws = tmp_path / "my-custom-workspace"
target.mkdir()
custom_ws.mkdir()
(source / "workspace").mkdir(parents=True)
# File in both locations
(source / "workspace" / "SOUL.md").write_text("# Standard soul\n", encoding="utf-8")
(custom_ws / "SOUL.md").write_text("# Custom soul\n", encoding="utf-8")
(source / "openclaw.json").write_text(
json.dumps({"agents": {"defaults": {"workspace": str(custom_ws)}}}),
encoding="utf-8",
)
migrator = mod.Migrator(
source_root=source,
target_root=target,
execute=True,
workspace_target=None,
overwrite=False,
migrate_secrets=False,
output_dir=target / "migration-report",
selected_options={"soul"},
)
migrator.migrate()
# Standard workspace location should have been preferred
content = (target / "SOUL.md").read_text(encoding="utf-8")
assert "Standard soul" in content
def test_migrator_exports_full_overflow_entries(tmp_path: Path):
mod = load_module()
source = tmp_path / ".openclaw"